diff options
823 files changed, 6644 insertions, 5220 deletions
diff --git a/.travis.yml b/.travis.yml index 5214b989af..012e795caa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ rvm: - 2.0.0 env: - "GEM=railties" - - "GEM=ap,am,amo,as" + - "GEM=ap,am,amo,as,av" - "GEM=ar:mysql" - "GEM=ar:mysql2" - "GEM=ar:sqlite3" @@ -8,7 +8,7 @@ gemspec gem 'mocha', '~> 0.14', require: false gem 'rack-cache', '~> 1.2' -gem 'bcrypt-ruby', '~> 3.0.0' +gem 'bcrypt-ruby', '~> 3.1.0' gem 'jquery-rails', '~> 2.2.0' gem 'turbolinks' gem 'coffee-rails', '~> 4.0.0' @@ -56,12 +56,11 @@ platforms :ruby do group :db do gem 'pg', '>= 0.11.0' gem 'mysql', '>= 2.9.0' - gem 'mysql2', '>= 0.3.10' + gem 'mysql2', '>= 0.3.13' end end platforms :jruby do - gem 'json' gem 'activerecord-jdbcsqlite3-adapter', '>= 1.2.7' group :db do @@ -8,10 +8,10 @@ require 'railties/lib/rails/api/task' desc "Build gem files for all projects" task :build => "all:build" -desc "Release all gems to gemcutter and create a tag" +desc "Release all gems to rubygems and create a tag" task :release => "all:release" -PROJECTS = %w(activesupport activemodel actionpack actionmailer activerecord railties) +PROJECTS = %w(activesupport activemodel actionpack actionview actionmailer activerecord railties) desc 'Run all tests by default' task :default => %w(test test:isolated) @@ -47,20 +47,25 @@ task :install => :build do end desc "Generate documentation for the Rails framework" -Rails::API::RepoTask.new('rdoc') +if ENV['EDGE'] + Rails::API::EdgeTask.new('rdoc') +else + Rails::API::StableTask.new('rdoc') +end desc 'Bump all versions to match version.rb' task :update_versions do require File.dirname(__FILE__) + "/version" File.open("RAILS_VERSION", "w") do |f| - f.puts Rails.version + f.puts Rails::VERSION::STRING end constants = { "activesupport" => "ActiveSupport", "activemodel" => "ActiveModel", "actionpack" => "ActionPack", + "actionview" => "ActionView", "actionmailer" => "ActionMailer", "activerecord" => "ActiveRecord", "railties" => "Rails" diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index 9e9d07b386..1659696bfb 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,3 +1,7 @@ -* No changes. +* invoke mailer defaults as procs only if they are procs, do not convert + with to_proc. That an object is convertible to a proc does not mean it's + meant to be always used as a proc. Fixes #11533 + + *Alex Tsukernik* Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/actionmailer/CHANGELOG.md) for previous changes. diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile index d1134ba2b5..5ddd90020b 100644 --- a/actionmailer/Rakefile +++ b/actionmailer/Rakefile @@ -14,9 +14,8 @@ Rake::TestTask.new { |t| namespace :test do task :isolated do - ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) Dir.glob("test/**/*_test.rb").all? do |file| - sh(ruby, '-Ilib:test', file) + sh(Gem.ruby, '-w', '-Ilib:test', file) end or raise "Failures" end end @@ -27,7 +26,7 @@ Gem::PackageTask.new(spec) do |p| p.gem_spec = spec end -desc "Release to gemcutter" +desc "Release to rubygems" task release: :package do require 'rake/gemcutter' Rake::Gemcutter::Tasks.new(spec).define diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index fcdd6747b8..cc3a412221 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -685,7 +685,7 @@ module ActionMailer # Call all the procs (if any) class_default = self.class.default default_values = class_default.merge(class_default) do |k,v| - v.respond_to?(:to_proc) ? instance_eval(&v) : v + v.is_a?(Proc) ? instance_eval(&v) : v end # Handle defaults diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb index 9a1a27c8ed..aedcd81e52 100644 --- a/actionmailer/lib/action_mailer/delivery_methods.rb +++ b/actionmailer/lib/action_mailer/delivery_methods.rb @@ -64,7 +64,7 @@ module ActionMailer raise "Delivery method cannot be nil" when Symbol if klass = delivery_methods[method] - mail.delivery_method(klass,(send(:"#{method}_settings") || {}).merge!(options || {})) + mail.delivery_method(klass, (send(:"#{method}_settings") || {}).merge(options || {})) else raise "Invalid delivery method #{method.inspect}" end diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index b9c56c540d..b74728ae34 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -578,6 +578,10 @@ class BaseTest < ActiveSupport::TestCase assert(mail1.to_s.to_i > mail2.to_s.to_i) end + test 'default values which have to_proc (e.g. symbols) should not be considered procs' do + assert(ProcMailer.welcome['x-has-to-proc'].to_s == 'symbol') + end + test "we can call other defined methods on the class as needed" do mail = ProcMailer.welcome assert_equal("Thanks for signing up this afternoon", mail.subject) diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb index 61a037ea18..20412c7bb2 100644 --- a/actionmailer/test/delivery_methods_test.rb +++ b/actionmailer/test/delivery_methods_test.rb @@ -152,6 +152,9 @@ class MailDeliveryTest < ActiveSupport::TestCase assert_equal "overridden", delivery_method_instance.settings[:user_name] assert_equal "somethingobtuse", delivery_method_instance.settings[:password] assert_equal delivery_method_instance.settings.merge(overridden_options), delivery_method_instance.settings + + # make sure that overriding delivery method options per mail instance doesn't affect the Base setting + assert_equal settings, ActionMailer::Base.smtp_settings end test "non registered delivery methods raises errors" do diff --git a/actionmailer/test/mailers/proc_mailer.rb b/actionmailer/test/mailers/proc_mailer.rb index 733633b575..7e189d861f 100644 --- a/actionmailer/test/mailers/proc_mailer.rb +++ b/actionmailer/test/mailers/proc_mailer.rb @@ -1,7 +1,8 @@ class ProcMailer < ActionMailer::Base default to: 'system@test.lindsaar.net', 'X-Proc-Method' => Proc.new { Time.now.to_i.to_s }, - subject: Proc.new { give_a_greeting } + subject: Proc.new { give_a_greeting }, + 'x-has-to-proc' => :symbol def welcome mail diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 6f5027dc23..f8308299fa 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,91 @@ -* Fix an issue where partials with a number in the filename weren't being digested for cache dependencies. +* Allow REMOTE_ADDR, HTTP_HOST and HTTP_USER_AGENT to be overridden from + the environment passed into `ActionDispatch::TestRequest.new`. - *Bryan Ricker* + Fixes #11590 + + *Andrew White* + +* Fix an issue where Journey was failing to clear the named routes hash when the + routes were reloaded and since it doesn't overwrite existing routes then if a + route changed but wasn't renamed it kept the old definition. This was being + masked by the optimised url helpers so it only became apparent when passing an + options hash to the url helper. + + *Andrew White* + +* Skip routes pointing to a redirect or mounted application when generating urls + using an options hash as they aren't relevant and generate incorrect urls. + + Fixes #8018 + + *Andrew White* + +* Move `MissingHelperError` out of the `ClassMethods` module. + + *Yves Senn* + +* Fix an issue where rails raise exception about missing helper where it + should throw `LoadError`. When helper file exists and only loaded file from + this helper does not exist rails should throw LoadError instead of + `MissingHelperError`. + + *Piotr Niełacny* + +* Fix `ActionDispatch::ParamsParser#parse_formatted_parameters` to rewind body input stream on + parsing json params. + + Fixes #11345 + + *Yuri Bol*, *Paul Nikitochkin* + +* Ignore spaces around delimiter in Set-Cookie header. + + *Yamagishi Kazutoshi* + +* Remove deprecated Rails application fallback for integration testing, set + `ActionDispatch.test_app` instead. + + *Carlos Antonio da Silva* + +* Remove deprecated `page_cache_extension` config. + + *Francesco Rodriguez* + +* Remove deprecated constants from Action Controller: + + ActionController::AbstractRequest => ActionDispatch::Request + ActionController::Request => ActionDispatch::Request + ActionController::AbstractResponse => ActionDispatch::Response + ActionController::Response => ActionDispatch::Response + ActionController::Routing => ActionDispatch::Routing + ActionController::Integration => ActionDispatch::Integration + ActionController::IntegrationTest => ActionDispatch::IntegrationTest + + *Carlos Antonio da Silva* + +* Fix `Mime::Type.parse` when bad accepts header is looked up. Previously it + was setting `request.formats` with an array containing a `nil` value, which + raised an error when setting the controller formats. + + Fixes #10965 + + *Becker* + +* Merge `:action` from routing scope and assign endpoint if both `:controller` + and `:action` are present. The endpoint assignment only occurs if there is + no `:to` present in the options hash so should only affect routes using the + shorthand syntax (i.e. endpoint is inferred from the path). + + Fixes #9856 + + *Yves Senn*, *Andrew White* + +* ActionView extracted from ActionPack + + *Piotr Sarnacki*, *Łukasz Strzałkowski* + +* Fix removing trailing slash for mounted apps #3215 + + *Piotr Sarnacki* Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/actionpack/CHANGELOG.md) for previous changes. diff --git a/actionpack/RUNNING_UNIT_TESTS.rdoc b/actionpack/RUNNING_UNIT_TESTS.rdoc index 1b29abd2d1..08767ae133 100644 --- a/actionpack/RUNNING_UNIT_TESTS.rdoc +++ b/actionpack/RUNNING_UNIT_TESTS.rdoc @@ -15,13 +15,3 @@ To run a single test suite which can be further narrowed down to one test: rake test TEST=path/to/test.rb TESTOPTS="--name=test_something" - -== Dependency on Active Record and database setup - -Test cases in the test/active_record/ directory depend on having -activerecord and sqlite installed. If Active Record is not in -actionpack/../activerecord directory, or the sqlite rubygem is not installed, -these tests are skipped. - -Other tests are runnable from a fresh copy of actionpack without any configuration. - diff --git a/actionpack/Rakefile b/actionpack/Rakefile index 56fc92963a..7eab972595 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -1,20 +1,18 @@ require 'rake/testtask' require 'rubygems/package_task' +test_files = Dir.glob('test/{abstract,controller,dispatch,assertions,journey,routing}/**/*_test.rb') + desc "Default Task" task :default => :test # Run the unit tests - -desc "Run all unit tests" -task :test => [:test_action_pack, :test_active_record_integration] - -Rake::TestTask.new(:test_action_pack) do |t| +Rake::TestTask.new do |t| t.libs << 'test' # make sure we include the tests in alphabetical order as on some systems # this will not happen automatically and the tests (as a whole) will error - t.test_files = Dir.glob('test/{abstract,controller,dispatch,template,assertions,journey}/**/*_test.rb').sort + t.test_files = test_files.sort t.warning = true t.verbose = true @@ -22,33 +20,19 @@ end namespace :test do task :isolated do - ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) - Dir.glob("test/{abstract,controller,dispatch,template}/**/*_test.rb").all? do |file| - sh(ruby, '-Ilib:test', file) + test_files.all? do |file| + sh(Gem.ruby, '-w', '-Ilib:test', file) end or raise "Failures" end end -namespace :test do - Rake::TestTask.new(:template) do |t| - t.libs << 'test' - t.pattern = 'test/template/**/*.rb' - end -end - -desc 'ActiveRecord Integration Tests' -Rake::TestTask.new(:test_active_record_integration) do |t| - t.libs << 'test' - t.test_files = Dir.glob("test/activerecord/*_test.rb") -end - spec = eval(File.read('actionpack.gemspec')) Gem::PackageTask.new(spec) do |p| p.gem_spec = spec end -desc "Release to gemcutter" +desc "Release to rubygems" task :release => :package do require 'rake/gemcutter' Rake::Gemcutter::Tasks.new(spec).define diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index cc8351a489..e3aa84ba0f 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -20,10 +20,10 @@ Gem::Specification.new do |s| s.requirements << 'none' s.add_dependency 'activesupport', version - s.add_dependency 'builder', '~> 3.1.0' + s.add_dependency 'actionview', version + 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 s.add_development_dependency 'tzinfo', '~> 0.3.37' diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index 19cfd7dae1..21c6191691 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -71,7 +71,7 @@ module AbstractController # * <tt>name</tt> - The callback to be added # * <tt>options</tt> - A hash of options to be used when adding the callback def _insert_callbacks(callbacks, block = nil) - options = callbacks.last.is_a?(Hash) ? callbacks.pop : {} + options = callbacks.extract_options! _normalize_callback_options(options) callbacks.push(block) if block callbacks.each do |callback| diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index 5ae8c6c3b0..e77e4e01e9 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -12,7 +12,24 @@ module AbstractController self._helper_methods = Array.new end + class MissingHelperError < LoadError + def initialize(error, path) + @error = error + @path = "helpers/#{path}.rb" + set_backtrace error.backtrace + + if error.path =~ /^#{path}(\.rb)?$/ + super("Missing helper file helpers/%s.rb" % path) + else + raise error + end + end + end + module ClassMethods + MissingHelperError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('AbstractController::Helpers::ClassMethods::MissingHelperError', + 'AbstractController::Helpers::MissingHelperError') + # When a class is inherited, wrap its helper module in a new module. # This ensures that the parent class's module can be changed # independently of the child class's. @@ -134,7 +151,7 @@ module AbstractController begin require_dependency(file_name) rescue LoadError => e - raise MissingHelperError.new(e, file_name) + raise AbstractController::Helpers::MissingHelperError.new(e, file_name) end file_name.camelize.constantize when Module @@ -145,15 +162,6 @@ module AbstractController end end - class MissingHelperError < LoadError - def initialize(error, path) - @error = error - @path = "helpers/#{path}.rb" - set_backtrace error.backtrace - super("Missing helper file helpers/%s.rb" % path) - end - end - private # Makes all the (instance) methods in the helper module available to templates # rendered through this controller. diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 9fdad63b45..cc03da4904 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -40,9 +40,6 @@ module ActionController autoload :UrlFor end - autoload :Integration, 'action_controller/deprecated/integration_test' - autoload :IntegrationTest, 'action_controller/deprecated/integration_test' - autoload :Routing, 'action_controller/deprecated' autoload :TestCase, 'action_controller/test_case' autoload :TemplateAssertions, 'action_controller/test_case' diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index d7b09b67c0..168ae0a529 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -59,7 +59,7 @@ module ActionController # <input type="text" name="post[address]" value="hyacintvej"> # # A request stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>. - # If the address input had been named "post[address][street]", the params would have included + # If the address input had been named <tt>post[address][street]</tt>, the params would have included # <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting. # # == Sessions diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index ea33d975ef..12d798d0c1 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -9,7 +9,7 @@ module ActionController # You can read more about each approach by clicking the modules below. # # Note: To turn off all caching, set - # config.action_controller.perform_caching = false. + # config.action_controller.perform_caching = false # # == \Caching stores # @@ -58,16 +58,6 @@ module ActionController config_accessor :default_static_extension self.default_static_extension ||= '.html' - def self.page_cache_extension=(extension) - ActiveSupport::Deprecation.deprecation_warning(:page_cache_extension, :default_static_extension) - self.default_static_extension = extension - end - - def self.page_cache_extension - ActiveSupport::Deprecation.deprecation_warning(:page_cache_extension, :default_static_extension) - default_static_extension - end - config_accessor :perform_caching self.perform_caching = true if perform_caching.nil? diff --git a/actionpack/lib/action_controller/deprecated.rb b/actionpack/lib/action_controller/deprecated.rb deleted file mode 100644 index 2405bebb97..0000000000 --- a/actionpack/lib/action_controller/deprecated.rb +++ /dev/null @@ -1,7 +0,0 @@ -ActionController::AbstractRequest = ActionController::Request = ActionDispatch::Request -ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response -ActionController::Routing = ActionDispatch::Routing - -ActiveSupport::Deprecation.warn 'ActionController::AbstractRequest and ActionController::Request are deprecated and will be removed, use ActionDispatch::Request instead.' -ActiveSupport::Deprecation.warn 'ActionController::AbstractResponse and ActionController::Response are deprecated and will be removed, use ActionDispatch::Response instead.' -ActiveSupport::Deprecation.warn 'ActionController::Routing is deprecated and will be removed, use ActionDispatch::Routing instead.'
\ No newline at end of file diff --git a/actionpack/lib/action_controller/deprecated/integration_test.rb b/actionpack/lib/action_controller/deprecated/integration_test.rb deleted file mode 100644 index 54eae48f47..0000000000 --- a/actionpack/lib/action_controller/deprecated/integration_test.rb +++ /dev/null @@ -1,5 +0,0 @@ -ActionController::Integration = ActionDispatch::Integration -ActionController::IntegrationTest = ActionDispatch::IntegrationTest - -ActiveSupport::Deprecation.warn 'ActionController::Integration is deprecated and will be removed, use ActionDispatch::Integration instead.' -ActiveSupport::Deprecation.warn 'ActionController::IntegrationTest is deprecated and will be removed, use ActionDispatch::IntegrationTest instead.' diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb index b078beb675..1d77e331f8 100644 --- a/actionpack/lib/action_controller/metal/flash.rb +++ b/actionpack/lib/action_controller/metal/flash.rb @@ -11,6 +11,23 @@ module ActionController #:nodoc: end module ClassMethods + # Creates new flash types. You can pass as many types as you want to create + # flash types other than the default <tt>alert</tt> and <tt>notice</tt> in + # your controllers and views. For instance: + # + # # in application_controller.rb + # class ApplicationController < ActionController::Base + # add_flash_types :warning + # end + # + # # in your controller + # redirect_to user_path(@user), warning: "Incomplete profile" + # + # # in your view + # <%= warning %> + # + # This method will automatically define a new method for each of the given + # names, and it will be available in your views. def add_flash_types(*types) types.each do |type| next if _flash_types.include?(type) diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index 891819968b..fd5b661209 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -97,8 +97,12 @@ module ActionController #:nodoc: # # This will return status 201 if the task was saved successfully. If not, # it will simply ignore the given options and return status 422 and the - # resource errors. To customize the failure scenario, you can pass a - # a block to <code>respond_with</code>: + # resource errors. You can also override the location to redirect to: + # + # respond_with(@project, location: root_path) + # + # To customize the failure scenario, you can pass a block to + # <code>respond_with</code>: # # def create # @project = Project.find(params[:project_id]) diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 0cbbbbe1fd..5ed3d2ebc1 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -524,7 +524,6 @@ module ActionController def process(action, http_method = 'GET', *args) check_required_ivars - http_method, args = handle_old_process_api(http_method, args, caller) if args.first.is_a?(String) && http_method != 'HEAD' @request.env['RAW_POST_DATA'] = args.shift @@ -628,17 +627,6 @@ module ActionController end end - def handle_old_process_api(http_method, args, callstack) - # 4.0: Remove this method. - if http_method.is_a?(Hash) - ActiveSupport::Deprecation.warn("TestCase#process now expects the HTTP method as second argument: process(action, http_method, params, session, flash)", callstack) - args.unshift(http_method) - http_method = args.last.is_a?(String) ? args.last : "GET" - end - - [http_method, args] - end - def build_request_uri(action, parameters) unless @request.env["PATH_INFO"] options = @controller.respond_to?(:url_options) ? @controller.__send__(:url_options).merge(parameters) : parameters diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 61dbf2b96c..ef144c3c76 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -175,7 +175,7 @@ module Mime def parse(accept_header) if accept_header !~ /,/ accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first - parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)] + parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact else list, index = AcceptList.new, 0 accept_header.split(',').each do |header| diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 4ca1d35489..b4cf8ad2f7 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -226,7 +226,7 @@ module ActionDispatch def raw_post unless @env.include? 'RAW_POST_DATA' raw_post_body = body - @env['RAW_POST_DATA'] = raw_post_body.read(@env['CONTENT_LENGTH'].to_i) + @env['RAW_POST_DATA'] = raw_post_body.read(content_length) raw_post_body.rewind if raw_post_body.respond_to?(:rewind) end @env['RAW_POST_DATA'] diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 5697282791..d3696cbb8a 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -210,7 +210,9 @@ module ActionDispatch # :nodoc: if body.respond_to?(:to_path) @stream = body else - @stream = build_buffer self, munge_body_object(body) + synchronize do + @stream = build_buffer self, munge_body_object(body) + end end end diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb index e288f026c7..7764763791 100644 --- a/actionpack/lib/action_dispatch/journey/formatter.rb +++ b/actionpack/lib/action_dispatch/journey/formatter.rb @@ -18,7 +18,11 @@ module ActionDispatch match_route(name, constraints) do |route| parameterized_parts = extract_parameterized_parts(route, options, recall, parameterize) - next if !name && route.requirements.empty? && route.parts.empty? + + # Skip this route unless a name has been provided or it is a + # standard Rails route since we can't determine whether an options + # hash passed to url_for matches a Rack application or a redirect. + next unless name || route.dispatcher? missing_keys = missing_keys(route, parameterized_parts) next unless missing_keys.empty? diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index 50e1853094..c8eb0f6f2d 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -16,6 +16,14 @@ module ActionDispatch @app = app @path = path + # Unwrap any constraints so we can see what's inside for route generation. + # This allows the formatter to skip over any mounted applications or redirects + # that shouldn't be matched when using a url_for without a route name. + while app.is_a?(Routing::Mapper::Constraints) do + app = app.app + end + @dispatcher = app.is_a?(Routing::RouteSet::Dispatcher) + @constraints = constraints @defaults = defaults @required_defaults = nil @@ -93,6 +101,10 @@ module ActionDispatch end end + def dispatcher? + @dispatcher + end + def matches?(request) constraints.all? do |method, value| next true unless request.respond_to?(method) diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb index 419e665d12..da32f1bfe7 100644 --- a/actionpack/lib/action_dispatch/journey/router.rb +++ b/actionpack/lib/action_dispatch/journey/router.rb @@ -54,7 +54,7 @@ module ActionDispatch end def call(env) - env['PATH_INFO'] = Utils.normalize_path(env['PATH_INFO']) + env['PATH_INFO'] = normalize_path(env['PATH_INFO']) find_routes(env).each do |match, parameters, route| script_name, path_info, set_params = env.values_at('SCRIPT_NAME', @@ -103,6 +103,12 @@ module ActionDispatch private + def normalize_path(path) + path = "/#{path}" + path.squeeze!('/') + path + end + def partitioned_routes routes.partitioned_routes end diff --git a/actionpack/lib/action_dispatch/journey/routes.rb b/actionpack/lib/action_dispatch/journey/routes.rb index a99d6d0d6a..80e3818ccd 100644 --- a/actionpack/lib/action_dispatch/journey/routes.rb +++ b/actionpack/lib/action_dispatch/journey/routes.rb @@ -30,6 +30,7 @@ module ActionDispatch def clear routes.clear + named_routes.clear end def partitioned_routes diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb index 2964d80d9f..0a8cb1b4d4 100644 --- a/actionpack/lib/action_dispatch/journey/visitors.rb +++ b/actionpack/lib/action_dispatch/journey/visitors.rb @@ -4,7 +4,7 @@ module ActionDispatch module Visitors # :nodoc: class Visitor # :nodoc: DISPATCH_CACHE = Hash.new { |h,k| - h[k] = "visit_#{k}" + h[k] = :"visit_#{k}" } def accept(node) diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 5b914f293d..3ccd0c9ee8 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -77,7 +77,7 @@ module ActionDispatch # domain and subdomains. # # * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object. - # * <tt>:secure</tt> - Whether this cookie is a only transmitted to HTTPS servers. + # * <tt>:secure</tt> - Whether this cookie is only transmitted to HTTPS servers. # Default is +false+. # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or # only HTTP. Defaults to +false+. @@ -160,7 +160,7 @@ module ActionDispatch end end - # Returns the +signed+ or +encrypted jar, preferring +encrypted+ if +secret_key_base+ is set. + # Returns the +signed+ or +encrypted+ jar, preferring +encrypted+ if +secret_key_base+ is set. # Used by ActionDispatch::Session::CookieStore to avoid the need to introduce new cookie stores. def signed_or_encrypted @signed_or_encrypted ||= diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb index fb70b60ef6..b426183488 100644 --- a/actionpack/lib/action_dispatch/middleware/params_parser.rb +++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb @@ -41,7 +41,7 @@ module ActionDispatch when Proc strategy.call(request.raw_post) when :json - data = ActiveSupport::JSON.decode(request.body) + data = ActiveSupport::JSON.decode(request.raw_post) data = {:_json => data} unless data.is_a?(Hash) Request::Utils.deep_munge(data).with_indifferent_access else diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb index 9e03cbf2b7..999c022535 100644 --- a/actionpack/lib/action_dispatch/middleware/ssl.rb +++ b/actionpack/lib/action_dispatch/middleware/ssl.rb @@ -36,8 +36,7 @@ module ActionDispatch url.scheme = "https" url.host = @host if @host url.port = @port if @port - headers = hsts_headers.merge('Content-Type' => 'text/html', - 'Location' => url.to_s) + headers = { 'Content-Type' => 'text/html', 'Location' => url.to_s } [301, headers, []] end @@ -58,7 +57,7 @@ module ActionDispatch cookies = cookies.split("\n") headers['Set-Cookie'] = cookies.map { |cookie| - if cookie !~ /;\s+secure(;|$)/ + if cookie !~ /;\s*secure\s*(;|$)/i "#{cookie}; secure" else cookie diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index edf37bb9a5..2dfaab3587 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -20,8 +20,7 @@ module ActionDispatch config.action_dispatch.default_headers = { 'X-Frame-Options' => 'SAMEORIGIN', 'X-XSS-Protection' => '1; mode=block', - 'X-Content-Type-Options' => 'nosniff', - 'X-UA-Compatible' => 'chrome=1' + 'X-Content-Type-Options' => 'nosniff' } config.eager_load_namespaces << ActionDispatch diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 3c58a2cfc3..288ce3e867 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -11,8 +11,8 @@ module ActionDispatch class Mapper URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port] SCOPE_OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module, - :controller, :path_names, :constraints, :defaults, - :shallow, :blocks, :options] + :controller, :action, :path_names, :constraints, + :shallow, :blocks, :defaults, :options] class Constraints #:nodoc: def self.new(app, constraints, request = Rack::Request) @@ -874,6 +874,10 @@ module ActionDispatch child end + def merge_action_scope(parent, child) #:nodoc: + child + end + def merge_path_names_scope(parent, child) #:nodoc: merge_options_scope(parent, child) end @@ -1383,6 +1387,10 @@ module ActionDispatch raise ArgumentError, "Unknown scope #{on.inspect} given to :on" end + if @scope[:controller] && @scope[:action] + options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}" + end + paths.each do |_path| route_options = options.dup route_options[:path] ||= _path if _path.is_a?(String) diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 3ae9f92c0b..943fc15026 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -186,10 +186,16 @@ module ActionDispatch klass = Journey::Router::Utils @path_parts.zip(args) do |part, arg| + parameterized_arg = arg.to_param + + if parameterized_arg.nil? || parameterized_arg.empty? + raise_generation_error(args) + end + # 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)) + path.gsub!(/(\*|:)#{part}/, klass.escape_fragment(parameterized_arg)) end path end @@ -197,6 +203,25 @@ module ActionDispatch def optimize_routes_generation?(t) t.send(:optimize_routes_generation?) end + + def raise_generation_error(args) + parts, missing_keys = [], [] + + @path_parts.zip(args) do |part, arg| + parameterized_arg = arg.to_param + + if parameterized_arg.nil? || parameterized_arg.empty? + missing_keys << part + end + + parts << [part, arg] + end + + message = "No route matches #{Hash[parts].inspect}" + message << " missing required keys: #{missing_keys.inspect}" + + raise ActionController::UrlGenerationError, message + end end def initialize(route, options) @@ -489,11 +514,12 @@ module ActionDispatch @recall = recall.dup @set = set + normalize_recall! normalize_options! normalize_controller_action_id! use_relative_controller! normalize_controller! - handle_nil_action! + normalize_action! end def controller @@ -512,6 +538,11 @@ module ActionDispatch end end + # Set 'index' as default action for recall + def normalize_recall! + @recall[:action] ||= 'index' + end + def normalize_options! # If an explicit :controller was given, always make :action explicit # too, so that action expiry works as expected for things like @@ -527,8 +558,8 @@ module ActionDispatch options[:controller] = options[:controller].to_s end - if options[:action] - options[:action] = options[:action].to_s + if options.key?(:action) + options[:action] = (options[:action] || 'index').to_s end end @@ -538,8 +569,6 @@ module ActionDispatch # :controller, :action or :id is not found, don't pull any # more keys from the recall. def normalize_controller_action_id! - @recall[:action] ||= 'index' if current_controller - use_recall_for(:controller) or return use_recall_for(:action) or return use_recall_for(:id) @@ -561,13 +590,11 @@ module ActionDispatch @options[:controller] = controller.sub(%r{^/}, '') if controller end - # This handles the case of action: nil being explicitly passed. - # It is identical to action: "index" - def handle_nil_action! - if options.has_key?(:action) && options[:action].nil? - options[:action] = 'index' + # Move 'index' action from options to recall + def normalize_action! + if @options[:action] == 'index' + @recall[:action] = @options.delete(:action) end - recall[:action] = options.delete(:action) if options[:action] == 'index' end # Generates a path from routes, returns [path, params]. diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 1f899a434c..9beb30307b 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -298,8 +298,6 @@ module ActionDispatch session = Rack::Test::Session.new(_mock_session) - env.merge!(env) - # NOTE: rack-test v0.5 doesn't build a default uri correctly # Make sure requested path is always a full uri uri = URI.parse('/') @@ -489,10 +487,6 @@ module ActionDispatch @@app = nil def self.app - if !@@app && !ActionDispatch.test_app - ActiveSupport::Deprecation.warn "Rails application fallback is deprecated and no longer works, please set ActionDispatch.test_app" - end - @@app || ActionDispatch.test_app end diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb index c63778f870..57c678843b 100644 --- a/actionpack/lib/action_dispatch/testing/test_request.rb +++ b/actionpack/lib/action_dispatch/testing/test_request.rb @@ -3,7 +3,11 @@ require 'rack/utils' module ActionDispatch class TestRequest < Request - DEFAULT_ENV = Rack::MockRequest.env_for('/') + DEFAULT_ENV = Rack::MockRequest.env_for('/', + 'HTTP_HOST' => 'test.host', + 'REMOTE_ADDR' => '0.0.0.0', + 'HTTP_USER_AGENT' => 'Rails Testing' + ) def self.new(env = {}) super @@ -12,10 +16,6 @@ module ActionDispatch def initialize(env = {}) env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application super(default_env.merge(env)) - - self.host = 'test.host' - self.remote_addr = '0.0.0.0' - self.user_agent = 'Rails Testing' end def request_method=(method) diff --git a/actionpack/test/abstract/helper_test.rb b/actionpack/test/abstract/helper_test.rb index 7960e5b55b..bc3e34684c 100644 --- a/actionpack/test/abstract/helper_test.rb +++ b/actionpack/test/abstract/helper_test.rb @@ -48,6 +48,14 @@ module AbstractController end end + class AbstractInvalidHelpers < AbstractHelpers + include ActionController::Helpers + + path = File.join(File.expand_path('../../fixtures', __FILE__), "helpers_missing") + $:.unshift(path) + self.helpers_path = path + end + class TestHelpers < ActiveSupport::TestCase def setup @controller = AbstractHelpers.new @@ -97,5 +105,22 @@ module AbstractController assert_equal "Hello Default", @controller.response_body end end + + class InvalidHelpersTest < ActiveSupport::TestCase + def test_controller_raise_error_about_real_require_problem + e = assert_raise(LoadError) { AbstractInvalidHelpers.helper(:invalid_require) } + assert_equal "No such file to load -- very_invalid_file_name", e.message + end + + def test_controller_raise_error_about_missing_helper + e = assert_raise(AbstractController::Helpers::MissingHelperError) { AbstractInvalidHelpers.helper(:missing) } + assert_equal "Missing helper file helpers/missing_helper.rb", e.message + end + + def test_missing_helper_error_has_the_right_path + e = assert_raise(AbstractController::Helpers::MissingHelperError) { AbstractInvalidHelpers.helper(:missing) } + assert_equal "helpers/missing_helper.rb", e.path + end + end end end diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index ca86837a2c..a67dff5436 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -1,6 +1,5 @@ require 'fileutils' require 'abstract_unit' -require 'active_record_unit' CACHE_DIR = 'test_cache' # Don't change '/../temp/' cavalierly or you might hose something you don't want hosed @@ -313,18 +312,3 @@ class ViewCacheDependencyTest < ActionController::TestCase assert_equal %w(trombone flute), HasDependenciesController.new.view_cache_dependencies end end - -class DeprecatedPageCacheExtensionTest < ActiveSupport::TestCase - def test_page_cache_extension_binds_default_static_extension - deprecation_behavior = ActiveSupport::Deprecation.behavior - ActiveSupport::Deprecation.behavior = :silence - old_extension = ActionController::Base.default_static_extension - - ActionController::Base.page_cache_extension = '.rss' - - assert_equal '.rss', ActionController::Base.default_static_extension - ensure - ActiveSupport::Deprecation.behavior = deprecation_behavior - ActionController::Base.default_static_extension = old_extension - end -end diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index f7ec6d71b3..e851cc6a63 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -277,7 +277,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest end def redirect - redirect_to :action => "get" + redirect_to action_url('get') end end @@ -511,8 +511,8 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest end set.draw do - match ':action', :to => controller, :via => [:get, :post] - get 'get/:action', :to => controller + match ':action', :to => controller, :via => [:get, :post], :as => :action + get 'get/:action', :to => controller, :as => :get_action end self.singleton_class.send(:include, set.url_helpers) diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index a9c62899b5..2d89969fd3 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -646,6 +646,7 @@ class RespondWithControllerTest < ActionController::TestCase Mime::Type.register_alias('text/html', :iphone) Mime::Type.register_alias('text/html', :touch) Mime::Type.register('text/x-mobile', :mobile) + Customer.send(:undef_method, :to_json) if Customer.method_defined?(:to_json) end def teardown diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb index f4bdd3e1d4..2b36a399bb 100644 --- a/actionpack/test/controller/new_base/render_streaming_test.rb +++ b/actionpack/test/controller/new_base/render_streaming_test.rb @@ -92,7 +92,7 @@ module RenderStreaming io.rewind assert_match "(undefined method `invalid!' for nil:NilClass)", io.read ensure - ActionController::Base.logger = _old + ActionView::Base.logger = _old end end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 72411ec900..98b34a872b 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -775,6 +775,10 @@ class RenderTest < ActionController::TestCase @request.host = "www.nextangle.com" end + def teardown + ActionView::Base.logger = nil + end + # :ported: def test_simple_show get :hello_world @@ -1089,6 +1093,12 @@ class RenderTest < ActionController::TestCase assert_equal '<test>passed formatted html erb</test>', @response.body end + def test_should_render_formatted_html_erb_template_with_bad_accepts_header + @request.env["HTTP_ACCEPT"] = "; a=dsf" + get :formatted_xml_erb + assert_equal '<test>passed formatted html erb</test>', @response.body + end + def test_should_render_formatted_html_erb_template_with_faulty_accepts_header @request.accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, appliction/x-shockwave-flash, */*" get :formatted_xml_erb diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 7c27458f46..f75c604277 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -280,13 +280,6 @@ XML assert_equal "/test_case_test/test/test_uri/7", @response.body end - def test_process_with_old_api - assert_deprecated do - process :test_uri, :id => 7 - assert_equal "/test_case_test/test/test_uri/7", @response.body - end - end - def test_process_with_request_uri_with_params_with_explicit_uri @request.env['PATH_INFO'] = "/explicit/uri" process :test_uri, "GET", :id => 7 diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb index e5e28c28be..30e95a0b75 100644 --- a/actionpack/test/dispatch/mount_test.rb +++ b/actionpack/test/dispatch/mount_test.rb @@ -33,6 +33,11 @@ class TestRoutingMount < ActionDispatch::IntegrationTest Router end + def test_trailing_slash_is_not_removed_from_path_info + get "/sprockets/omg/" + assert_equal "/sprockets -- /omg/", response.body + end + def test_mounting_sets_script_name get "/sprockets/omg" assert_equal "/sprockets -- /omg", response.body diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb index b62ed6a8b2..dba9ab688f 100644 --- a/actionpack/test/dispatch/request/json_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb @@ -70,6 +70,13 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest end end + test 'raw_post is not empty for JSON request' do + with_test_routing do + post '/parse', '{"posts": [{"title": "Post Title"}]}', 'CONTENT_TYPE' => 'application/json' + assert_equal '{"posts": [{"title": "Post Title"}]}', request.raw_post + end + end + private def assert_parses(expected, actual, headers = {}) with_test_routing do diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb index 3c30a705e9..2a2f92b5b3 100644 --- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb @@ -108,8 +108,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest # Rack doesn't handle multipart/mixed for us. files = params['files'] - files.force_encoding('ASCII-8BIT') - assert_equal 19756, files.size + assert_equal 19756, files.bytesize end test "does not create tempfile if no file has been selected" do diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb index 9169658c22..9a77454f30 100644 --- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb @@ -17,10 +17,9 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest end test "parses unbalanced query string with array" do - assert_parses( - {'location' => ["1", "2"], 'age_group' => ["2"]}, - "location[]=1&location[]=2&age_group[]=2" - ) + query = "location[]=1&location[]=2&age_group[]=2" + expected = { 'location' => ["1", "2"], 'age_group' => ["2"] } + assert_parses expected, query end test "parses nested hash" do @@ -30,9 +29,17 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest "note[viewers][viewer][][type]=Group", "note[viewers][viewer][][id]=2" ].join("&") - - expected = { "note" => { "viewers"=>{"viewer"=>[{ "id"=>"1", "type"=>"User"}, {"type"=>"Group", "id"=>"2"} ]} } } - assert_parses(expected, query) + expected = { + "note" => { + "viewers" => { + "viewer" => [ + { "id" => "1", "type" => "User" }, + { "type" => "Group", "id" => "2" } + ] + } + } + } + assert_parses expected, query end test "parses more complex nesting" do @@ -48,7 +55,6 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest "products[second]=Pc", "=Save" ].join("&") - expected = { "customers" => { "boston" => { @@ -70,13 +76,12 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest "second" => "Pc" } } - assert_parses expected, query end test "parses params with array" do - query = "selected[]=1&selected[]=2&selected[]=3" - expected = { "selected" => [ "1", "2", "3" ] } + query = "selected[]=1&selected[]=2&selected[]=3" + expected = { "selected" => ["1", "2", "3"] } assert_parses expected, query end @@ -88,13 +93,13 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest test "parses params with array prefix and hashes" do query = "a[][b][c]=d" - expected = {"a" => [{"b" => {"c" => "d"}}]} + expected = { "a" => [{ "b" => { "c" => "d" } }] } assert_parses expected, query end test "parses params with complex nesting" do query = "a[][b][c][][d][]=e" - expected = {"a" => [{"b" => {"c" => [{"d" => ["e"]}]}}]} + expected = { "a" => [{ "b" => { "c" => [{ "d" => ["e"] }] } }] } assert_parses expected, query end @@ -104,7 +109,6 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest "something_else=blah", "logo=#{File.expand_path(__FILE__)}" ].join("&") - expected = { "customers" => { "boston" => { @@ -116,22 +120,20 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest "something_else" => "blah", "logo" => File.expand_path(__FILE__), } - assert_parses expected, query end test "parses params with Safari 2 trailing null character" do - query = "selected[]=1&selected[]=2&selected[]=3\0" - expected = { "selected" => [ "1", "2", "3" ] } + query = "selected[]=1&selected[]=2&selected[]=3\0" + expected = { "selected" => ["1", "2", "3"] } assert_parses expected, query end test "ambiguous params returns a bad request" do with_routing do |set| set.draw do - post ':action', :to => ::UrlEncodedParamsParsingTest::TestController + post ':action', to: ::UrlEncodedParamsParsingTest::TestController end - post "/parse", "foo[]=bar&foo[4]=bar" assert_response :bad_request end @@ -141,7 +143,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest def with_test_routing with_routing do |set| set.draw do - post ':action', :to => ::UrlEncodedParamsParsingTest::TestController + post ':action', to: ::UrlEncodedParamsParsingTest::TestController end yield end @@ -151,8 +153,8 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest with_test_routing do post "/parse", actual assert_response :ok - assert_equal(expected, TestController.last_request_parameters) - assert_utf8(TestController.last_request_parameters) + assert_equal expected, TestController.last_request_parameters + assert_utf8 TestController.last_request_parameters end end @@ -167,11 +169,11 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest object.each_value do |v| case v when Hash - assert_utf8(v) + assert_utf8 v when Array - v.each {|el| assert_utf8(el) } + v.each { |el| assert_utf8 el } else - assert_utf8(v) + assert_utf8 v end end end diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 74f5253c11..2fbe7358f9 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -182,8 +182,7 @@ class ResponseTest < ActiveSupport::TestCase ActionDispatch::Response.default_headers = { 'X-Frame-Options' => 'DENY', 'X-Content-Type-Options' => 'nosniff', - 'X-XSS-Protection' => '1;', - 'X-UA-Compatible' => 'chrome=1' + 'X-XSS-Protection' => '1;' } resp = ActionDispatch::Response.new.tap { |response| response.body = 'Hello' @@ -193,7 +192,6 @@ 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_test.rb b/actionpack/test/dispatch/routing_test.rb index 16ba746b9c..e4c3ddd3f9 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1253,6 +1253,19 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'api/v3/products#list', @response.body end + def test_controller_option_with_nesting_and_leading_slash + draw do + scope '/job', controller: 'job' do + scope ':id', action: 'manage_applicant' do + get "/active" + end + end + end + + get '/job/5/active' + assert_equal 'job#manage_applicant', @response.body + end + def test_dynamically_generated_helpers_on_collection_do_not_clobber_resources_url_helper draw do resources :replies do @@ -3607,3 +3620,56 @@ class TestRouteDefaults < ActionDispatch::IntegrationTest assert_equal '/projects/1', url_for(controller: 'projects', action: 'show', id: 1, only_path: true) end end + +class TestRackAppRouteGeneration < ActionDispatch::IntegrationTest + stub_controllers do |routes| + Routes = routes + Routes.draw do + rack_app = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] } + mount rack_app, at: '/account', as: 'account' + mount rack_app, at: '/:locale/account', as: 'localized_account' + end + end + + def app + Routes + end + + include Routes.url_helpers + + def test_mounted_application_doesnt_match_unnamed_route + assert_raise(ActionController::UrlGenerationError) do + assert_equal '/account?controller=products', url_for(controller: 'products', action: 'index', only_path: true) + end + + assert_raise(ActionController::UrlGenerationError) do + assert_equal '/de/account?controller=products', url_for(controller: 'products', action: 'index', :locale => 'de', only_path: true) + end + end +end + +class TestRedirectRouteGeneration < ActionDispatch::IntegrationTest + stub_controllers do |routes| + Routes = routes + Routes.draw do + get '/account', to: redirect('/myaccount'), as: 'account' + get '/:locale/account', to: redirect('/%{locale}/myaccount'), as: 'localized_account' + end + end + + def app + Routes + end + + include Routes.url_helpers + + def test_redirect_doesnt_match_unnamed_route + assert_raise(ActionController::UrlGenerationError) do + assert_equal '/account?controller=products', url_for(controller: 'products', action: 'index', only_path: true) + end + + assert_raise(ActionController::UrlGenerationError) do + assert_equal '/de/account?controller=products', url_for(controller: 'products', action: 'index', :locale => 'de', only_path: true) + end + end +end diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb index a9bea7ea73..94969f795a 100644 --- a/actionpack/test/dispatch/ssl_test.rb +++ b/actionpack/test/dispatch/ssl_test.rb @@ -37,6 +37,11 @@ class SSLTest < ActionDispatch::IntegrationTest response.headers['Strict-Transport-Security'] end + def test_no_hsts_with_insecure_connection + get "http://example.org/" + assert_not response.headers['Strict-Transport-Security'] + end + def test_hsts_header self.app = ActionDispatch::SSL.new(default_app, :hsts => true) get "https://example.org/" @@ -119,6 +124,49 @@ class SSLTest < ActionDispatch::IntegrationTest response.headers['Set-Cookie'].split("\n") end + + def test_flag_cookies_as_secure_with_has_not_spaces_before + self.app = ActionDispatch::SSL.new(lambda { |env| + headers = { + 'Content-Type' => "text/html", + 'Set-Cookie' => "problem=def; path=/;secure; HttpOnly" + } + [200, headers, ["OK"]] + }) + + get "https://example.org/" + assert_equal ["problem=def; path=/;secure; HttpOnly"], + response.headers['Set-Cookie'].split("\n") + end + + def test_flag_cookies_as_secure_with_has_not_spaces_after + self.app = ActionDispatch::SSL.new(lambda { |env| + headers = { + 'Content-Type' => "text/html", + 'Set-Cookie' => "problem=def; path=/; secure;HttpOnly" + } + [200, headers, ["OK"]] + }) + + get "https://example.org/" + assert_equal ["problem=def; path=/; secure;HttpOnly"], + response.headers['Set-Cookie'].split("\n") + end + + def test_flag_cookies_as_secure_with_ignore_case + self.app = ActionDispatch::SSL.new(lambda { |env| + headers = { + 'Content-Type' => "text/html", + 'Set-Cookie' => "problem=def; path=/; Secure; HttpOnly" + } + [200, headers, ["OK"]] + }) + + get "https://example.org/" + assert_equal ["problem=def; path=/; Secure; HttpOnly"], + response.headers['Set-Cookie'].split("\n") + end + def test_no_cookies self.app = ActionDispatch::SSL.new(lambda { |env| [200, {'Content-Type' => "text/html"}, ["OK"]] diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb index 3db862c810..65ad8677f3 100644 --- a/actionpack/test/dispatch/test_request_test.rb +++ b/actionpack/test/dispatch/test_request_test.rb @@ -62,6 +62,36 @@ class TestRequestTest < ActiveSupport::TestCase assert_equal false, req.env.empty? end + test "default remote address is 0.0.0.0" do + req = ActionDispatch::TestRequest.new + assert_equal '0.0.0.0', req.remote_addr + end + + test "allows remote address to be overridden" do + req = ActionDispatch::TestRequest.new('REMOTE_ADDR' => '127.0.0.1') + assert_equal '127.0.0.1', req.remote_addr + end + + test "default host is test.host" do + req = ActionDispatch::TestRequest.new + assert_equal 'test.host', req.host + end + + test "allows host to be overridden" do + req = ActionDispatch::TestRequest.new('HTTP_HOST' => 'www.example.com') + assert_equal 'www.example.com', req.host + end + + test "default user agent is 'Rails Testing'" do + req = ActionDispatch::TestRequest.new + assert_equal 'Rails Testing', req.user_agent + end + + test "allows user agent to be overridden" do + req = ActionDispatch::TestRequest.new('HTTP_USER_AGENT' => 'GoogleBot') + assert_equal 'GoogleBot', req.user_agent + end + private def assert_cookies(expected, cookie_jar) assert_equal(expected, cookie_jar.instance_variable_get("@cookies")) diff --git a/actionpack/test/fixtures/helpers_missing/invalid_require_helper.rb b/actionpack/test/fixtures/helpers_missing/invalid_require_helper.rb new file mode 100644 index 0000000000..d8801e54d5 --- /dev/null +++ b/actionpack/test/fixtures/helpers_missing/invalid_require_helper.rb @@ -0,0 +1,5 @@ +require 'very_invalid_file_name' + +module InvalidRequireHelper +end + diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb index 3d52b2e9ee..a286f77633 100644 --- a/actionpack/test/journey/router_test.rb +++ b/actionpack/test/journey/router_test.rb @@ -4,9 +4,13 @@ require 'abstract_unit' module ActionDispatch module Journey class TestRouter < ActiveSupport::TestCase + # TODO : clean up routing tests so we don't need this hack + class StubDispatcher < Routing::RouteSet::Dispatcher; end + attr_reader :routes def setup + @app = StubDispatcher.new @routes = Routes.new @router = Router.new(@routes, {}) @formatter = Formatter.new(@routes) @@ -306,7 +310,7 @@ module ActionDispatch def test_nil_path_parts_are_ignored path = Path::Pattern.new "/:controller(/:action(.:format))" - @router.routes.add_route nil, path, {}, {}, {} + @router.routes.add_route @app, path, {}, {}, {} params = { :controller => "tasks", :format => nil } extras = { :action => 'lol' } @@ -321,7 +325,7 @@ module ActionDispatch str = Router::Strexp.new("/", Hash[params], ['/', '.', '?'], true) path = Path::Pattern.new str - @router.routes.add_route nil, path, {}, {}, {} + @router.routes.add_route @app, path, {}, {}, {} path, _ = @formatter.generate(:path_info, nil, Hash[params], {}) assert_equal '/', path @@ -329,7 +333,7 @@ module ActionDispatch def test_generate_calls_param_proc path = Path::Pattern.new '/:controller(/:action)' - @router.routes.add_route nil, path, {}, {}, {} + @router.routes.add_route @app, path, {}, {}, {} parameterized = [] params = [ [:controller, "tasks"], @@ -347,7 +351,7 @@ module ActionDispatch def test_generate_id path = Path::Pattern.new '/:controller(/:action)' - @router.routes.add_route nil, path, {}, {}, {} + @router.routes.add_route @app, path, {}, {}, {} path, params = @formatter.generate( :path_info, nil, {:id=>1, :controller=>"tasks", :action=>"show"}, {}) @@ -357,7 +361,7 @@ module ActionDispatch def test_generate_escapes path = Path::Pattern.new '/:controller(/:action)' - @router.routes.add_route nil, path, {}, {}, {} + @router.routes.add_route @app, path, {}, {}, {} path, _ = @formatter.generate(:path_info, nil, { :controller => "tasks", @@ -368,7 +372,7 @@ module ActionDispatch def test_generate_extra_params path = Path::Pattern.new '/:controller(/:action)' - @router.routes.add_route nil, path, {}, {}, {} + @router.routes.add_route @app, path, {}, {}, {} path, params = @formatter.generate(:path_info, nil, { :id => 1, @@ -382,7 +386,7 @@ module ActionDispatch def test_generate_uses_recall_if_needed path = Path::Pattern.new '/:controller(/:action(/:id))' - @router.routes.add_route nil, path, {}, {}, {} + @router.routes.add_route @app, path, {}, {}, {} path, params = @formatter.generate(:path_info, nil, @@ -394,7 +398,7 @@ module ActionDispatch def test_generate_with_name path = Path::Pattern.new '/:controller(/:action)' - @router.routes.add_route nil, path, {}, {}, {} + @router.routes.add_route @app, path, {}, {}, {} path, params = @formatter.generate(:path_info, "tasks", @@ -541,7 +545,7 @@ module ActionDispatch def add_routes router, paths paths.each do |path| path = Path::Pattern.new path - router.routes.add_route nil, path, {}, {}, {} + router.routes.add_route @app, path, {}, {}, {} end end diff --git a/actionpack/test/routing/helper_test.rb b/actionpack/test/routing/helper_test.rb index a5588d95fa..0028aaa629 100644 --- a/actionpack/test/routing/helper_test.rb +++ b/actionpack/test/routing/helper_test.rb @@ -22,7 +22,7 @@ module ActionDispatch x = Class.new { include rs.url_helpers } - assert_raises ActionController::RoutingError do + assert_raises ActionController::UrlGenerationError do x.new.pond_duck_path Duck.new end end diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md new file mode 100644 index 0000000000..b961dce4d1 --- /dev/null +++ b/actionview/CHANGELOG.md @@ -0,0 +1,142 @@ +* Fix `text_area` to behave like `text_field` when `nil` is given as + value. + + Before: + + f.text_field :field, value: nil #=> <input value=""> + f.text_area :field, value: nil #=> <textarea>value of field</textarea> + + After: + + f.text_area :field, value: nil #=> <textarea></textarea> + + *Joel Cogen* + +* Element of the `grouped_options_for_select` can + optionally contain html attributes as the last element of the array. + + grouped_options_for_select( + [["North America", [['United States','US'],"Canada"], data: { foo: 'bar' }]] + ) + + *Vasiliy Ermolovich* + +* Fix default rendered format problem when calling `render` without :content_type option. + It should return :html. Fix #11393. + + *Gleb Mazovetskiy* *Oleg* *kennyj* + +* Fix `link_to` with block and url hashes. + + Before: + + link_to(action: 'bar', controller: 'foo') { content_tag(:span, 'Example site') } + # => "<a action=\"bar\" controller=\"foo\"><span>Example site</span></a>" + + After: + + link_to(action: 'bar', controller: 'foo') { content_tag(:span, 'Example site') } + # => "<a href=\"/foo/bar\"><span>Example site</span></a>" + + *Murahashi Sanemat Kenichi* + +* Fix "Stack Level Too Deep" error when redering recursive partials. + + Fixes #11340. + + *Rafael Mendonça França* + +* Added an `enforce_utf8` hash option for `form_tag` method. + + Control to output a hidden input tag with name `utf8` without monkey + patching. + + Before: + + form_tag + # => '<form>..<input name="utf8" type="hidden" value="✓" />..</form>' + + After: + + form_tag + # => '<form>..<input name="utf8" type="hidden" value="✓" />..</form>' + + form_tag({}, { :enforce_utf8 => false }) + # => '<form>....</form>' + + *ma2gedev* + +* Remove the deprecated `include_seconds` argument from `distance_of_time_in_words`, + pass in an `:include_seconds` hash option to use this feature. + + *Carlos Antonio da Silva* + +* Remove deprecated block passing to `FormBuilder#new`. + + *Vipul A M* + +* Pick `DateField` `DateTimeField` and `ColorField` values from stringified options allowing use of symbol keys with helpers. + + *Jon Rowe* + +* Remove the deprecated `prompt` argument from `grouped_options_for_select`, + pass in a `:prompt` hash option to use this feature. + + *kennyj* + +* Always escape the result of `link_to_unless` method. + + Before: + + link_to_unless(true, '<b>Showing</b>', 'github.com') + # => "<b>Showing</b>" + + After: + + link_to_unless(true, '<b>Showing</b>', 'github.com') + # => "<b>Showing</b>" + + *dtaniwaki* + +* Use a case insensitive URI Regexp for #asset_path. + + This fix a problem where the same asset path using different case are generating + different URIs. + + Before: + + image_tag("HTTP://google.com") + # => "<img alt=\"Google\" src=\"/assets/HTTP://google.com\" />" + image_tag("http://google.com") + # => "<img alt=\"Google\" src=\"http://google.com\" />" + + After: + + image_tag("HTTP://google.com") + # => "<img alt=\"Google\" src=\"HTTP://google.com\" />" + image_tag("http://google.com") + # => "<img alt=\"Google\" src=\"http://google.com\" />" + + *David Celis* + +* Element of the `collection_check_boxes` and `collection_radio_buttons` can + optionally contain html attributes as the last element of the array. + + *Vasiliy Ermolovich* + +* Update the HTML `BOOLEAN_ATTRIBUTES` in `ActionView::Helpers::TagHelper` + to conform to the latest HTML 5.1 spec. Add attributes `allowfullscreen`, + `default`, `inert`, `sortable`, `truespeed`, `typemustmatch`. Fix attribute + `seamless` (previously misspelled `seemless`). + + *Alex Peattie* + +* Fix an issue where partials with a number in the filename weren't being digested for cache dependencies. + + *Bryan Ricker* + +* First release, ActionView extracted from ActionPack + + *Piotr Sarnacki*, *Łukasz Strzałkowski* + +Please check [4-0-stable (ActionPack's CHANGELOG)](https://github.com/rails/rails/blob/4-0-stable/actionpack/CHANGELOG.md) for previous changes. diff --git a/actionview/MIT-LICENSE b/actionview/MIT-LICENSE new file mode 100644 index 0000000000..5c668d9624 --- /dev/null +++ b/actionview/MIT-LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2004-2013 David Heinemeier Hansson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/actionview/README.rdoc b/actionview/README.rdoc new file mode 100644 index 0000000000..ab6c7e878a --- /dev/null +++ b/actionview/README.rdoc @@ -0,0 +1,29 @@ += Action View + +== Download and installation + +The latest version of Action View can be installed with RubyGems: + + % [sudo] gem install actionview + +Source code can be downloaded as part of the Rails project on GitHub + +* https://github.com/rails/rails/tree/master/actionview + + +== License + +Action View is released under the MIT license: + +* http://www.opensource.org/licenses/MIT + + +== Support + +API documentation is at + +* http://api.rubyonrails.org + +Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here: + +* https://github.com/rails/rails/issues diff --git a/actionview/RUNNING_UNIT_TESTS.rdoc b/actionview/RUNNING_UNIT_TESTS.rdoc new file mode 100644 index 0000000000..a0c35e1810 --- /dev/null +++ b/actionview/RUNNING_UNIT_TESTS.rdoc @@ -0,0 +1,27 @@ +== Running with Rake + +The easiest way to run the unit tests is through Rake. The default task runs +the entire test suite for all classes. For more information, checkout the +full array of rake tasks with "rake -T" + +Rake can be found at http://rake.rubyforge.org + +== Running by hand + +To run a single test suite + + rake test TEST=path/to/test.rb + +which can be further narrowed down to one test: + + rake test TEST=path/to/test.rb TESTOPTS="--name=test_something" + +== Dependency on Active Record and database setup + +Test cases in the test/active_record/ directory depend on having +activerecord and sqlite installed. If Active Record is not in +actionview/../activerecord directory, or the sqlite rubygem is not installed, +these tests are skipped. + +Other tests are runnable from a fresh copy of actionview without any configuration. + diff --git a/actionview/Rakefile b/actionview/Rakefile new file mode 100644 index 0000000000..8e980df7fc --- /dev/null +++ b/actionview/Rakefile @@ -0,0 +1,75 @@ +require 'rake/testtask' +require 'rubygems/package_task' + +desc "Default Task" +task :default => :test + +# Run the unit tests + +desc "Run all unit tests" +task :test => [:test_action_view, :test_active_record_integration] + +Rake::TestTask.new(:test_action_view) do |t| + t.libs << 'test' + t.test_files = Dir.glob('test/template/**/*_test.rb').sort + t.warning = true + t.verbose = true +end + +namespace :test do + task :isolated do + Dir.glob("test/{active_record,template}/**/*_test.rb").all? do |file| + sh(Gem.ruby, '-w', '-Ilib:test', file) + end or raise "Failures" + end + + Rake::TestTask.new(:template) do |t| + t.libs << 'test' + t.pattern = 'test/template/**/*.rb' + end +end + +desc 'ActiveRecord Integration Tests' +Rake::TestTask.new(:test_active_record_integration) do |t| + t.libs << 'test' + t.test_files = Dir.glob("test/activerecord/*_test.rb") + t.warning = true + t.verbose = true +end + +spec = eval(File.read('actionview.gemspec')) + +Gem::PackageTask.new(spec) do |p| + p.gem_spec = spec +end + +desc "Release to rubygems" +task :release => :package do + require 'rake/gemcutter' + Rake::Gemcutter::Tasks.new(spec).define + Rake::Task['gem:push'].invoke +end + +task :lines do + lines, codelines, total_lines, total_codelines = 0, 0, 0, 0 + + FileList["lib/**/*.rb"].each do |file_name| + next if file_name =~ /vendor/ + File.open(file_name, 'r') do |f| + while line = f.gets + lines += 1 + next if line =~ /^\s*$/ + next if line =~ /^\s*#/ + codelines += 1 + end + end + puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}" + + total_lines += lines + total_codelines += codelines + + lines, codelines = 0, 0 + end + + puts "Total: Lines #{total_lines}, LOC #{total_codelines}" +end diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec new file mode 100644 index 0000000000..cdac074973 --- /dev/null +++ b/actionview/actionview.gemspec @@ -0,0 +1,29 @@ +version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip + +Gem::Specification.new do |s| + s.platform = Gem::Platform::RUBY + s.name = 'actionview' + s.version = version + s.summary = 'Rendering framework putting the V in MVC (part of Rails).' + s.description = 'Simple, battle-tested conventions and helpers for building web pages.' + + s.required_ruby_version = '>= 1.9.3' + + s.license = 'MIT' + + s.author = 'David Heinemeier Hansson' + s.email = 'david@loudthinking.com' + s.homepage = 'http://www.rubyonrails.org' + + s.files = Dir['CHANGELOG.md', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*'] + s.require_path = 'lib' + s.requirements << 'none' + + s.add_dependency 'activesupport', version + s.add_dependency 'activemodel', version + + s.add_dependency 'builder', '~> 3.1.0' + s.add_dependency 'erubis', '~> 2.7.0' + + s.add_development_dependency 'actionpack', version +end diff --git a/actionpack/lib/action_view.rb b/actionview/lib/action_view.rb index 4aafbcb655..8def4ba7c5 100644 --- a/actionpack/lib/action_view.rb +++ b/actionview/lib/action_view.rb @@ -23,7 +23,6 @@ require 'active_support' require 'active_support/rails' -require 'action_pack' module ActionView extend ActiveSupport::Autoload diff --git a/actionpack/lib/action_view/base.rb b/actionview/lib/action_view/base.rb index 08253de3f4..08253de3f4 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionview/lib/action_view/base.rb diff --git a/actionpack/lib/action_view/buffers.rb b/actionview/lib/action_view/buffers.rb index 361a0dccbe..361a0dccbe 100644 --- a/actionpack/lib/action_view/buffers.rb +++ b/actionview/lib/action_view/buffers.rb diff --git a/actionpack/lib/action_view/context.rb b/actionview/lib/action_view/context.rb index ee263df484..ee263df484 100644 --- a/actionpack/lib/action_view/context.rb +++ b/actionview/lib/action_view/context.rb diff --git a/actionpack/lib/action_view/dependency_tracker.rb b/actionview/lib/action_view/dependency_tracker.rb index b2e8334077..b2e8334077 100644 --- a/actionpack/lib/action_view/dependency_tracker.rb +++ b/actionview/lib/action_view/dependency_tracker.rb diff --git a/actionpack/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb index 9324a1ac50..a674e4d7ea 100644 --- a/actionpack/lib/action_view/digestor.rb +++ b/actionview/lib/action_view/digestor.rb @@ -7,10 +7,14 @@ module ActionView @@cache = ThreadSafe::Cache.new def self.digest(name, format, finder, options = {}) - cache_key = [name, format] + Array.wrap(options[:dependencies]) - @@cache[cache_key.join('.')] ||= begin + cache_key = ([name, format] + Array.wrap(options[:dependencies])).join('.') + @@cache.fetch(cache_key) do + @@cache[cache_key] ||= nil if options[:partial] # Prevent re-entry + klass = options[:partial] || name.include?("/_") ? PartialDigestor : Digestor - klass.new(name, format, finder, options).digest + digest = klass.new(name, format, finder, options).digest + + @@cache[cache_key] = digest # Store the value end end diff --git a/actionpack/lib/action_view/flows.rb b/actionview/lib/action_view/flows.rb index c0e458cd41..ba24510e56 100644 --- a/actionpack/lib/action_view/flows.rb +++ b/actionview/lib/action_view/flows.rb @@ -36,7 +36,7 @@ module ActionView @root = Fiber.current.object_id end - # Try to get an stored content. If the content + # Try to get stored content. If the content # is not available and we are inside the layout # fiber, we set that we are waiting for the given # key and yield. @@ -73,4 +73,4 @@ module ActionView Fiber.current.object_id != @root end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_view/helpers.rb b/actionview/lib/action_view/helpers.rb index 8a78685ae1..8a78685ae1 100644 --- a/actionpack/lib/action_view/helpers.rb +++ b/actionview/lib/action_view/helpers.rb diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionview/lib/action_view/helpers/active_model_helper.rb index 901f433c70..901f433c70 100644 --- a/actionpack/lib/action_view/helpers/active_model_helper.rb +++ b/actionview/lib/action_view/helpers/active_model_helper.rb diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index 2b3a3c6a29..2b3a3c6a29 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb diff --git a/actionpack/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb index b5f2df76ab..0b957adb91 100644 --- a/actionpack/lib/action_view/helpers/asset_url_helper.rb +++ b/actionview/lib/action_view/helpers/asset_url_helper.rb @@ -105,7 +105,7 @@ module ActionView # ) # module AssetUrlHelper - URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//} + URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}i # Computes the path to asset in public directory. If :type # options is set, a file extension will be appended and scoped diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionview/lib/action_view/helpers/atom_feed_helper.rb index 42b1dd8933..42b1dd8933 100644 --- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionview/lib/action_view/helpers/atom_feed_helper.rb diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb index 8fc78ea7fb..2a38e5c446 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionview/lib/action_view/helpers/cache_helper.rb @@ -176,20 +176,24 @@ module ActionView # TODO: Create an object that has caching read/write on it def fragment_for(name = {}, options = nil, &block) #:nodoc: - if fragment = controller.read_fragment(name, options) - fragment - else - # VIEW TODO: Make #capture usable outside of ERB - # This dance is needed because Builder can't use capture - pos = output_buffer.length - yield - output_safe = output_buffer.html_safe? - fragment = output_buffer.slice!(pos..-1) - if output_safe - self.output_buffer = output_buffer.class.new(output_buffer) - end - controller.write_fragment(name, fragment, options) + read_fragment_for(name, options) || write_fragment_for(name, options, &block) + end + + def read_fragment_for(name, options) #:nodoc: + controller.read_fragment(name, options) + end + + def write_fragment_for(name, options) #:nodoc: + # VIEW TODO: Make #capture usable outside of ERB + # This dance is needed because Builder can't use capture + pos = output_buffer.length + yield + output_safe = output_buffer.html_safe? + fragment = output_buffer.slice!(pos..-1) + if output_safe + self.output_buffer = output_buffer.class.new(output_buffer) end + controller.write_fragment(name, fragment, options) end end end diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionview/lib/action_view/helpers/capture_helper.rb index 5afe435459..5afe435459 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionview/lib/action_view/helpers/capture_helper.rb diff --git a/actionpack/lib/action_view/helpers/controller_helper.rb b/actionview/lib/action_view/helpers/controller_helper.rb index 74ef25f7c1..74ef25f7c1 100644 --- a/actionpack/lib/action_view/helpers/controller_helper.rb +++ b/actionview/lib/action_view/helpers/controller_helper.rb diff --git a/actionpack/lib/action_view/helpers/csrf_helper.rb b/actionview/lib/action_view/helpers/csrf_helper.rb index eeb0ed94b9..eeb0ed94b9 100644 --- a/actionpack/lib/action_view/helpers/csrf_helper.rb +++ b/actionview/lib/action_view/helpers/csrf_helper.rb diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb index 8fb5eb1548..8e1aea50a9 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionview/lib/action_view/helpers/date_helper.rb @@ -64,15 +64,7 @@ module ActionView # distance_of_time_in_words(from_time, to_time, include_seconds: true) # => about 6 years # distance_of_time_in_words(to_time, from_time, include_seconds: true) # => about 6 years # distance_of_time_in_words(Time.now, Time.now) # => less than a minute - def distance_of_time_in_words(from_time, to_time = 0, include_seconds_or_options = {}, options = {}) - if include_seconds_or_options.is_a?(Hash) - options = include_seconds_or_options - else - ActiveSupport::Deprecation.warn "distance_of_time_in_words and time_ago_in_words now accept :include_seconds " + - "as a part of options hash, not a boolean argument" - options[:include_seconds] ||= !!include_seconds_or_options - end - + def distance_of_time_in_words(from_time, to_time = 0, options = {}) options = { scope: :'datetime.distance_in_words' }.merge!(options) diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionview/lib/action_view/helpers/debug_helper.rb index c29c1b1eea..c29c1b1eea 100644 --- a/actionpack/lib/action_view/helpers/debug_helper.rb +++ b/actionview/lib/action_view/helpers/debug_helper.rb diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index 36dedf0676..8a4830d887 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -692,7 +692,7 @@ module ActionView # # Note that fields_for will automatically generate a hidden field # to store the ID of the record. There are circumstances where this - # hidden field is not needed and you can pass <tt>hidden_field_id: false</tt> + # hidden field is not needed and you can pass <tt>include_id: false</tt> # to prevent fields_for from rendering it automatically. def fields_for(record_name, record_object = nil, options = {}, &block) builder = instantiate_builder(record_name, record_object, options) @@ -1237,11 +1237,7 @@ module ActionView self end - def initialize(object_name, object, template, options, block=nil) - if block - ActiveSupport::Deprecation.warn "Giving a block to FormBuilder is deprecated and has no effect anymore." - end - + def initialize(object_name, object, template, options) @nested_child_index = {} @object_name, @object, @template, @options = object_name, object, template, options @default_options = @options ? @options.slice(:index, :namespace) : {} @@ -1507,7 +1503,7 @@ module ActionView # # Note that fields_for will automatically generate a hidden field # to store the ID of the record. There are circumstances where this - # hidden field is not needed and you can pass <tt>hidden_field_id: false</tt> + # hidden field is not needed and you can pass <tt>include_id: false</tt> # to prevent fields_for from rendering it automatically. def fields_for(record_name, record_object = nil, fields_options = {}, &block) fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options? diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb index ad26505086..e7f932ca06 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionview/lib/action_view/helpers/form_options_helper.rb @@ -346,8 +346,8 @@ module ActionView html_attributes = option_html_attributes(element) text, value = option_text_and_value(element).map { |item| item.to_s } - html_attributes[:selected] = 'selected' if option_value_selected?(value, selected) - html_attributes[:disabled] = 'disabled' if disabled && option_value_selected?(value, disabled) + html_attributes[:selected] = option_value_selected?(value, selected) + html_attributes[:disabled] = disabled && option_value_selected?(value, disabled) html_attributes[:value] = value content_tag_string(:option, text, html_attributes) @@ -384,8 +384,8 @@ module ActionView end selected, disabled = extract_selected_and_disabled(selected) select_deselect = { - :selected => extract_values_from_collection(collection, value_method, selected), - :disabled => extract_values_from_collection(collection, value_method, disabled) + selected: extract_values_from_collection(collection, value_method, selected), + disabled: extract_values_from_collection(collection, value_method, disabled) } options_for_select(options, select_deselect) @@ -444,7 +444,7 @@ module ActionView option_tags = options_from_collection_for_select( group.send(group_method), option_key_method, option_value_method, selected_key) - content_tag(:optgroup, option_tags, :label => group.send(group_label_method)) + content_tag(:optgroup, option_tags, label: group.send(group_label_method)) end.join.html_safe end @@ -510,29 +510,26 @@ module ActionView # <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to # wrap the output in an appropriate <tt><select></tt> tag. def grouped_options_for_select(grouped_options, selected_key = nil, options = {}) - if options.is_a?(Hash) - prompt = options[:prompt] - divider = options[:divider] - else - prompt = options - message = "Passing the prompt to grouped_options_for_select as an argument is deprecated. " \ - "Please use an options hash like `{ prompt: #{prompt.inspect} }`." - ActiveSupport::Deprecation.warn message - end + prompt = options[:prompt] + divider = options[:divider] body = "".html_safe if prompt - body.safe_concat content_tag(:option, prompt_text(prompt), :value => "") + body.safe_concat content_tag(:option, prompt_text(prompt), value: "") end grouped_options.each do |container| + html_attributes = option_html_attributes(container) + if divider label = divider else label, container = container end - body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), :label => label) + + html_attributes = { label: label }.merge!(html_attributes) + body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), html_attributes) end body @@ -568,7 +565,7 @@ module ActionView end zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected) - zone_options.safe_concat content_tag(:option, '-------------', :value => '', :disabled => 'disabled') + zone_options.safe_concat content_tag(:option, '-------------', value: '', disabled: true) zone_options.safe_concat "\n" zones = zones - priority_zones @@ -653,7 +650,7 @@ module ActionView # # Example object structure for use with this method: # class Post < ActiveRecord::Base - # has_and_belongs_to_many :author + # has_and_belongs_to_many :authors # end # class Author < ActiveRecord::Base # has_and_belongs_to_many :posts @@ -751,7 +748,7 @@ module ActionView end def prompt_text(prompt) - prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', :default => 'Please select') + prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', default: 'Please select') end end @@ -759,7 +756,7 @@ module ActionView # 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.select :person_id, Person.all.collect { |p| [ p.name, p.id ] }, include_blank: true %> # <%= f.submit %> # <% end %> # @@ -783,7 +780,7 @@ module ActionView # 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.grouped_collection_select :country_id, @continents, :countries, :name, :id, :name %> # <%= f.submit %> # <% end %> # diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index 3fa7696b83..142c27ace0 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -38,6 +38,7 @@ module ActionView # * A list of parameters to feed to the URL the form will be posted to. # * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the # submit behavior. By default this behavior is an ajax submit. + # * <tt>:enforce_utf8</tt> - If set to false, a hidden input with name utf8 is not output. # # ==== Examples # form_tag('/posts') @@ -719,7 +720,8 @@ module ActionView method_tag(method) + token_tag(authenticity_token) end - tags = utf8_enforcer_tag << method_tag + enforce_utf8 = html_options.delete("enforce_utf8") { true } + tags = (enforce_utf8 ? utf8_enforcer_tag : ''.html_safe) << method_tag content_tag(:div, tags, :style => 'margin:0;padding:0;display:inline') end diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionview/lib/action_view/helpers/javascript_helper.rb index e475d5b018..e475d5b018 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionview/lib/action_view/helpers/javascript_helper.rb diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb index fda7038a5d..fda7038a5d 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionview/lib/action_view/helpers/number_helper.rb diff --git a/actionpack/lib/action_view/helpers/output_safety_helper.rb b/actionview/lib/action_view/helpers/output_safety_helper.rb index 60a4478c26..60a4478c26 100644 --- a/actionpack/lib/action_view/helpers/output_safety_helper.rb +++ b/actionview/lib/action_view/helpers/output_safety_helper.rb diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionview/lib/action_view/helpers/record_tag_helper.rb index f767957fa9..f767957fa9 100644 --- a/actionpack/lib/action_view/helpers/record_tag_helper.rb +++ b/actionview/lib/action_view/helpers/record_tag_helper.rb diff --git a/actionpack/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb index 458086de96..458086de96 100644 --- a/actionpack/lib/action_view/helpers/rendering_helper.rb +++ b/actionview/lib/action_view/helpers/rendering_helper.rb diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index e5cb843670..e5cb843670 100644 --- a/actionpack/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb index 3939e4737b..732f35643a 100644 --- a/actionpack/lib/action_view/helpers/tag_helper.rb +++ b/actionview/lib/action_view/helpers/tag_helper.rb @@ -12,8 +12,11 @@ module ActionView BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer autoplay controls loop selected hidden scoped async - defer reversed ismap seemless muted required - autofocus novalidate formnovalidate open pubdate itemscope).to_set + defer reversed ismap seamless muted required + autofocus novalidate formnovalidate open pubdate + itemscope allowfullscreen default inert sortable + truespeed typemustmatch).to_set + BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym }) PRE_CONTENT_STRINGS = { diff --git a/actionpack/lib/action_view/helpers/tags.rb b/actionview/lib/action_view/helpers/tags.rb index a05e16979a..a05e16979a 100644 --- a/actionpack/lib/action_view/helpers/tags.rb +++ b/actionview/lib/action_view/helpers/tags.rb diff --git a/actionpack/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb index 3fe3f4e9df..3fe3f4e9df 100644 --- a/actionpack/lib/action_view/helpers/tags/base.rb +++ b/actionview/lib/action_view/helpers/tags/base.rb diff --git a/actionpack/lib/action_view/helpers/tags/check_box.rb b/actionview/lib/action_view/helpers/tags/check_box.rb index 6d51f2629a..6d51f2629a 100644 --- a/actionpack/lib/action_view/helpers/tags/check_box.rb +++ b/actionview/lib/action_view/helpers/tags/check_box.rb diff --git a/actionpack/lib/action_view/helpers/tags/checkable.rb b/actionview/lib/action_view/helpers/tags/checkable.rb index 052e9df662..052e9df662 100644 --- a/actionpack/lib/action_view/helpers/tags/checkable.rb +++ b/actionview/lib/action_view/helpers/tags/checkable.rb diff --git a/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb index 52006d856b..52006d856b 100644 --- a/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb +++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb diff --git a/actionpack/lib/action_view/helpers/tags/collection_helpers.rb b/actionview/lib/action_view/helpers/tags/collection_helpers.rb index cd12ddaf65..388dcf1f13 100644 --- a/actionpack/lib/action_view/helpers/tags/collection_helpers.rb +++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb @@ -73,8 +73,9 @@ module ActionView value = value_for_collection(item, @value_method) text = value_for_collection(item, @text_method) default_html_options = default_html_options_for_collection(item, value) + additional_html_options = option_html_attributes(item) - yield item, value, text, default_html_options + yield item, value, text, default_html_options.merge(additional_html_options) end.join.html_safe end end diff --git a/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb b/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb index 20be34c1f2..20be34c1f2 100644 --- a/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb +++ b/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb diff --git a/actionpack/lib/action_view/helpers/tags/collection_select.rb b/actionview/lib/action_view/helpers/tags/collection_select.rb index 6cb2b2e0d3..6cb2b2e0d3 100644 --- a/actionpack/lib/action_view/helpers/tags/collection_select.rb +++ b/actionview/lib/action_view/helpers/tags/collection_select.rb diff --git a/actionpack/lib/action_view/helpers/tags/color_field.rb b/actionview/lib/action_view/helpers/tags/color_field.rb index d8fc797035..b4bbe92746 100644 --- a/actionpack/lib/action_view/helpers/tags/color_field.rb +++ b/actionview/lib/action_view/helpers/tags/color_field.rb @@ -4,7 +4,7 @@ module ActionView class ColorField < TextField # :nodoc: def render options = @options.stringify_keys - options["value"] = @options.fetch("value") { validate_color_string(value(object)) } + options["value"] ||= validate_color_string(value(object)) @options = options super end diff --git a/actionpack/lib/action_view/helpers/tags/date_field.rb b/actionview/lib/action_view/helpers/tags/date_field.rb index c22be0db29..c22be0db29 100644 --- a/actionpack/lib/action_view/helpers/tags/date_field.rb +++ b/actionview/lib/action_view/helpers/tags/date_field.rb diff --git a/actionpack/lib/action_view/helpers/tags/date_select.rb b/actionview/lib/action_view/helpers/tags/date_select.rb index 0c4ac40070..0c4ac40070 100644 --- a/actionpack/lib/action_view/helpers/tags/date_select.rb +++ b/actionview/lib/action_view/helpers/tags/date_select.rb diff --git a/actionpack/lib/action_view/helpers/tags/datetime_field.rb b/actionview/lib/action_view/helpers/tags/datetime_field.rb index 9a2279c611..25e7e05ec6 100644 --- a/actionpack/lib/action_view/helpers/tags/datetime_field.rb +++ b/actionview/lib/action_view/helpers/tags/datetime_field.rb @@ -4,7 +4,7 @@ module ActionView class DatetimeField < TextField # :nodoc: def render options = @options.stringify_keys - options["value"] = @options.fetch("value") { format_date(value(object)) } + options["value"] ||= format_date(value(object)) options["min"] = format_date(options["min"]) options["max"] = format_date(options["max"]) @options = options diff --git a/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb b/actionview/lib/action_view/helpers/tags/datetime_local_field.rb index b4a74185d1..b4a74185d1 100644 --- a/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb +++ b/actionview/lib/action_view/helpers/tags/datetime_local_field.rb diff --git a/actionpack/lib/action_view/helpers/tags/datetime_select.rb b/actionview/lib/action_view/helpers/tags/datetime_select.rb index 563de1840e..563de1840e 100644 --- a/actionpack/lib/action_view/helpers/tags/datetime_select.rb +++ b/actionview/lib/action_view/helpers/tags/datetime_select.rb diff --git a/actionpack/lib/action_view/helpers/tags/email_field.rb b/actionview/lib/action_view/helpers/tags/email_field.rb index 7ce3ccb9bf..7ce3ccb9bf 100644 --- a/actionpack/lib/action_view/helpers/tags/email_field.rb +++ b/actionview/lib/action_view/helpers/tags/email_field.rb diff --git a/actionpack/lib/action_view/helpers/tags/file_field.rb b/actionview/lib/action_view/helpers/tags/file_field.rb index 476b820d84..476b820d84 100644 --- a/actionpack/lib/action_view/helpers/tags/file_field.rb +++ b/actionview/lib/action_view/helpers/tags/file_field.rb diff --git a/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb b/actionview/lib/action_view/helpers/tags/grouped_collection_select.rb index 2ed4712dac..2ed4712dac 100644 --- a/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb +++ b/actionview/lib/action_view/helpers/tags/grouped_collection_select.rb diff --git a/actionpack/lib/action_view/helpers/tags/hidden_field.rb b/actionview/lib/action_view/helpers/tags/hidden_field.rb index c3757c2461..c3757c2461 100644 --- a/actionpack/lib/action_view/helpers/tags/hidden_field.rb +++ b/actionview/lib/action_view/helpers/tags/hidden_field.rb diff --git a/actionpack/lib/action_view/helpers/tags/label.rb b/actionview/lib/action_view/helpers/tags/label.rb index 35d3ba8434..35d3ba8434 100644 --- a/actionpack/lib/action_view/helpers/tags/label.rb +++ b/actionview/lib/action_view/helpers/tags/label.rb diff --git a/actionpack/lib/action_view/helpers/tags/month_field.rb b/actionview/lib/action_view/helpers/tags/month_field.rb index 4c0fb846ee..4c0fb846ee 100644 --- a/actionpack/lib/action_view/helpers/tags/month_field.rb +++ b/actionview/lib/action_view/helpers/tags/month_field.rb diff --git a/actionpack/lib/action_view/helpers/tags/number_field.rb b/actionview/lib/action_view/helpers/tags/number_field.rb index 4f95b1b4de..4f95b1b4de 100644 --- a/actionpack/lib/action_view/helpers/tags/number_field.rb +++ b/actionview/lib/action_view/helpers/tags/number_field.rb diff --git a/actionpack/lib/action_view/helpers/tags/password_field.rb b/actionview/lib/action_view/helpers/tags/password_field.rb index 6099fa6f19..6099fa6f19 100644 --- a/actionpack/lib/action_view/helpers/tags/password_field.rb +++ b/actionview/lib/action_view/helpers/tags/password_field.rb diff --git a/actionpack/lib/action_view/helpers/tags/radio_button.rb b/actionview/lib/action_view/helpers/tags/radio_button.rb index 4849c537a5..4849c537a5 100644 --- a/actionpack/lib/action_view/helpers/tags/radio_button.rb +++ b/actionview/lib/action_view/helpers/tags/radio_button.rb diff --git a/actionpack/lib/action_view/helpers/tags/range_field.rb b/actionview/lib/action_view/helpers/tags/range_field.rb index f98ae88043..f98ae88043 100644 --- a/actionpack/lib/action_view/helpers/tags/range_field.rb +++ b/actionview/lib/action_view/helpers/tags/range_field.rb diff --git a/actionpack/lib/action_view/helpers/tags/search_field.rb b/actionview/lib/action_view/helpers/tags/search_field.rb index c09e2f1be7..c09e2f1be7 100644 --- a/actionpack/lib/action_view/helpers/tags/search_field.rb +++ b/actionview/lib/action_view/helpers/tags/search_field.rb diff --git a/actionpack/lib/action_view/helpers/tags/select.rb b/actionview/lib/action_view/helpers/tags/select.rb index d64e2f68ef..d64e2f68ef 100644 --- a/actionpack/lib/action_view/helpers/tags/select.rb +++ b/actionview/lib/action_view/helpers/tags/select.rb diff --git a/actionpack/lib/action_view/helpers/tags/tel_field.rb b/actionview/lib/action_view/helpers/tags/tel_field.rb index 987bb9e67a..987bb9e67a 100644 --- a/actionpack/lib/action_view/helpers/tags/tel_field.rb +++ b/actionview/lib/action_view/helpers/tags/tel_field.rb diff --git a/actionpack/lib/action_view/helpers/tags/text_area.rb b/actionview/lib/action_view/helpers/tags/text_area.rb index c81156c0c8..9ee83ee7c2 100644 --- a/actionpack/lib/action_view/helpers/tags/text_area.rb +++ b/actionview/lib/action_view/helpers/tags/text_area.rb @@ -10,7 +10,7 @@ module ActionView options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split) end - content_tag("textarea", options.delete('value') || value_before_type_cast(object), options) + content_tag("textarea", options.delete("value") { value_before_type_cast(object) }, options) end end end diff --git a/actionpack/lib/action_view/helpers/tags/text_field.rb b/actionview/lib/action_view/helpers/tags/text_field.rb index baa5ff768e..e910879ebf 100644 --- a/actionpack/lib/action_view/helpers/tags/text_field.rb +++ b/actionview/lib/action_view/helpers/tags/text_field.rb @@ -5,8 +5,8 @@ module ActionView def render options = @options.stringify_keys options["size"] = options["maxlength"] unless options.key?("size") - options["type"] ||= field_type - options["value"] = options.fetch("value"){ value_before_type_cast(object) } unless field_type == "file" + options["type"] ||= field_type + options["value"] = options.fetch("value") { value_before_type_cast(object) } unless field_type == "file" options["value"] &&= ERB::Util.html_escape(options["value"]) add_default_name_and_id(options) tag("input", options) diff --git a/actionpack/lib/action_view/helpers/tags/time_field.rb b/actionview/lib/action_view/helpers/tags/time_field.rb index 0e90a3aed7..0e90a3aed7 100644 --- a/actionpack/lib/action_view/helpers/tags/time_field.rb +++ b/actionview/lib/action_view/helpers/tags/time_field.rb diff --git a/actionpack/lib/action_view/helpers/tags/time_select.rb b/actionview/lib/action_view/helpers/tags/time_select.rb index 0b06311d25..0b06311d25 100644 --- a/actionpack/lib/action_view/helpers/tags/time_select.rb +++ b/actionview/lib/action_view/helpers/tags/time_select.rb diff --git a/actionpack/lib/action_view/helpers/tags/time_zone_select.rb b/actionview/lib/action_view/helpers/tags/time_zone_select.rb index 80d165ec7e..80d165ec7e 100644 --- a/actionpack/lib/action_view/helpers/tags/time_zone_select.rb +++ b/actionview/lib/action_view/helpers/tags/time_zone_select.rb diff --git a/actionpack/lib/action_view/helpers/tags/url_field.rb b/actionview/lib/action_view/helpers/tags/url_field.rb index d76340178d..d76340178d 100644 --- a/actionpack/lib/action_view/helpers/tags/url_field.rb +++ b/actionview/lib/action_view/helpers/tags/url_field.rb diff --git a/actionpack/lib/action_view/helpers/tags/week_field.rb b/actionview/lib/action_view/helpers/tags/week_field.rb index 5b3d0494e9..5b3d0494e9 100644 --- a/actionpack/lib/action_view/helpers/tags/week_field.rb +++ b/actionview/lib/action_view/helpers/tags/week_field.rb diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb index 147f9fd8ed..6fe250d3c1 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionview/lib/action_view/helpers/text_helper.rb @@ -254,7 +254,7 @@ module ActionView # # => "<p>Unblinkable.</p>" # # simple_format("<blink>Blinkable!</blink> It's true.", {}, sanitize: false) - # # => "<p><blink>Blinkable!</span> It's true.</p>" + # # => "<p><blink>Blinkable!</blink> It's true.</p>" def simple_format(text, html_options = {}, options = {}) wrapper_tag = options.fetch(:wrapper_tag, :p) diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb index ad8eb47f1f..ad8eb47f1f 100644 --- a/actionpack/lib/action_view/helpers/translation_helper.rb +++ b/actionview/lib/action_view/helpers/translation_helper.rb diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb index 8a83f6f356..a4f04b0b3b 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -16,7 +16,7 @@ module ActionView # provided here will only work in the context of a request # (link_to_unless_current, for instance), which must be provided # as a method called #request on the context. - + BUTTON_TAG_METHOD_VERBS = %w{patch put delete} extend ActiveSupport::Concern include TagHelper @@ -172,7 +172,7 @@ module ActionView # link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" } # # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?">Visit Other Site</a> def link_to(name = nil, options = nil, html_options = nil, &block) - html_options, options = options, name if block_given? + html_options, options, name = options, name, block if block_given? options ||= {} html_options = convert_options_to_data_attributes(options, html_options) @@ -289,7 +289,7 @@ module ActionView remote = html_options.delete('remote') method = html_options.delete('method').to_s - method_tag = %w{patch put delete}.include?(method) ? method_tag(method) : ''.html_safe + method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : ''.html_safe form_method = method == 'get' ? 'get' : 'post' form_options = html_options.delete('form') || {} @@ -380,7 +380,7 @@ module ActionView if block_given? block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block) else - name + ERB::Util.html_escape(name) end else link_to(name, options, html_options) diff --git a/actionpack/lib/action_view/locale/en.yml b/actionview/lib/action_view/locale/en.yml index 8a56f147b8..8a56f147b8 100644 --- a/actionpack/lib/action_view/locale/en.yml +++ b/actionview/lib/action_view/locale/en.yml diff --git a/actionpack/lib/action_view/log_subscriber.rb b/actionview/lib/action_view/log_subscriber.rb index fd9a543e0a..fd9a543e0a 100644 --- a/actionpack/lib/action_view/log_subscriber.rb +++ b/actionview/lib/action_view/log_subscriber.rb diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb index f9d5b97fe3..f9d5b97fe3 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionview/lib/action_view/lookup_context.rb diff --git a/actionpack/lib/action_view/model_naming.rb b/actionview/lib/action_view/model_naming.rb index e09ebd60df..e09ebd60df 100644 --- a/actionpack/lib/action_view/model_naming.rb +++ b/actionview/lib/action_view/model_naming.rb diff --git a/actionpack/lib/action_view/path_set.rb b/actionview/lib/action_view/path_set.rb index 91ee2ea8f5..91ee2ea8f5 100644 --- a/actionpack/lib/action_view/path_set.rb +++ b/actionview/lib/action_view/path_set.rb diff --git a/actionpack/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb index e80e0ed9b0..e80e0ed9b0 100644 --- a/actionpack/lib/action_view/railtie.rb +++ b/actionview/lib/action_view/railtie.rb diff --git a/actionpack/lib/action_view/record_identifier.rb b/actionview/lib/action_view/record_identifier.rb index 63f645431a..63f645431a 100644 --- a/actionpack/lib/action_view/record_identifier.rb +++ b/actionview/lib/action_view/record_identifier.rb diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionview/lib/action_view/renderer/abstract_renderer.rb index 73c19a0ae2..73c19a0ae2 100644 --- a/actionpack/lib/action_view/renderer/abstract_renderer.rb +++ b/actionview/lib/action_view/renderer/abstract_renderer.rb diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb index 821026268a..821026268a 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionview/lib/action_view/renderer/partial_renderer.rb diff --git a/actionpack/lib/action_view/renderer/renderer.rb b/actionview/lib/action_view/renderer/renderer.rb index 964b18337e..964b18337e 100644 --- a/actionpack/lib/action_view/renderer/renderer.rb +++ b/actionview/lib/action_view/renderer/renderer.rb diff --git a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb b/actionview/lib/action_view/renderer/streaming_template_renderer.rb index 9cf6eb0c65..9cf6eb0c65 100644 --- a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb +++ b/actionview/lib/action_view/renderer/streaming_template_renderer.rb diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb index 4d5c5db80c..668831dff3 100644 --- a/actionpack/lib/action_view/renderer/template_renderer.rb +++ b/actionview/lib/action_view/renderer/template_renderer.rb @@ -11,7 +11,7 @@ module ActionView prepend_formats(template.formats) unless context.rendered_format - context.rendered_format = template.formats.first || formats.last + context.rendered_format = template.formats.first || formats.first end render_template(template, options[:layout], options[:locals]) diff --git a/actionpack/lib/action_view/routing_url_for.rb b/actionview/lib/action_view/routing_url_for.rb index f10e7e88ba..f10e7e88ba 100644 --- a/actionpack/lib/action_view/routing_url_for.rb +++ b/actionview/lib/action_view/routing_url_for.rb diff --git a/actionpack/lib/action_view/template.rb b/actionview/lib/action_view/template.rb index c25b1efc2b..e2c50fec47 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionview/lib/action_view/template.rb @@ -267,7 +267,7 @@ module ActionView method_name = self.method_name code = @handler.call(self) - # Make sure that the resulting String to be evalled is in the + # Make sure that the resulting String to be eval'd is in the # encoding of the code source = <<-end_src def #{method_name}(local_assigns, output_buffer) diff --git a/actionpack/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb index a89d51221e..a89d51221e 100644 --- a/actionpack/lib/action_view/template/error.rb +++ b/actionview/lib/action_view/template/error.rb diff --git a/actionpack/lib/action_view/template/handlers.rb b/actionview/lib/action_view/template/handlers.rb index d9cddc0040..d9cddc0040 100644 --- a/actionpack/lib/action_view/template/handlers.rb +++ b/actionview/lib/action_view/template/handlers.rb diff --git a/actionpack/lib/action_view/template/handlers/builder.rb b/actionview/lib/action_view/template/handlers/builder.rb index d90b0c6378..d90b0c6378 100644 --- a/actionpack/lib/action_view/template/handlers/builder.rb +++ b/actionview/lib/action_view/template/handlers/builder.rb diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb index 7d7a7af51d..c8a0059596 100644 --- a/actionpack/lib/action_view/template/handlers/erb.rb +++ b/actionview/lib/action_view/template/handlers/erb.rb @@ -1,4 +1,3 @@ -require 'action_dispatch/http/mime_type' require 'erubis' module ActionView diff --git a/actionpack/lib/action_view/template/handlers/raw.rb b/actionview/lib/action_view/template/handlers/raw.rb index 0c0d1fffcb..0c0d1fffcb 100644 --- a/actionpack/lib/action_view/template/handlers/raw.rb +++ b/actionview/lib/action_view/template/handlers/raw.rb diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb index 3304605c1a..3304605c1a 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionview/lib/action_view/template/resolver.rb diff --git a/actionpack/lib/action_view/template/text.rb b/actionview/lib/action_view/template/text.rb index 859c7bc3ce..859c7bc3ce 100644 --- a/actionpack/lib/action_view/template/text.rb +++ b/actionview/lib/action_view/template/text.rb diff --git a/actionpack/lib/action_view/template/types.rb b/actionview/lib/action_view/template/types.rb index db77cb5d19..db77cb5d19 100644 --- a/actionpack/lib/action_view/template/types.rb +++ b/actionview/lib/action_view/template/types.rb diff --git a/actionpack/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb index 3145446114..3145446114 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionview/lib/action_view/test_case.rb diff --git a/actionpack/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb index 7afa2fa613..7afa2fa613 100644 --- a/actionpack/lib/action_view/testing/resolvers.rb +++ b/actionview/lib/action_view/testing/resolvers.rb diff --git a/actionpack/lib/action_view/vendor/html-scanner.rb b/actionview/lib/action_view/vendor/html-scanner.rb index 775b827529..775b827529 100644 --- a/actionpack/lib/action_view/vendor/html-scanner.rb +++ b/actionview/lib/action_view/vendor/html-scanner.rb diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/document.rb b/actionview/lib/action_view/vendor/html-scanner/html/document.rb index 386820300a..386820300a 100644 --- a/actionpack/lib/action_view/vendor/html-scanner/html/document.rb +++ b/actionview/lib/action_view/vendor/html-scanner/html/document.rb diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/node.rb b/actionview/lib/action_view/vendor/html-scanner/html/node.rb index 7e7cd4f7b6..7e7cd4f7b6 100644 --- a/actionpack/lib/action_view/vendor/html-scanner/html/node.rb +++ b/actionview/lib/action_view/vendor/html-scanner/html/node.rb diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb b/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb index 30b6b8b141..30b6b8b141 100644 --- a/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb +++ b/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb b/actionview/lib/action_view/vendor/html-scanner/html/selector.rb index 7f8609c408..7f8609c408 100644 --- a/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb +++ b/actionview/lib/action_view/vendor/html-scanner/html/selector.rb diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/tokenizer.rb b/actionview/lib/action_view/vendor/html-scanner/html/tokenizer.rb index 8ac8d34430..8ac8d34430 100644 --- a/actionpack/lib/action_view/vendor/html-scanner/html/tokenizer.rb +++ b/actionview/lib/action_view/vendor/html-scanner/html/tokenizer.rb diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/version.rb b/actionview/lib/action_view/vendor/html-scanner/html/version.rb index 6d645c3e14..6d645c3e14 100644 --- a/actionpack/lib/action_view/vendor/html-scanner/html/version.rb +++ b/actionview/lib/action_view/vendor/html-scanner/html/version.rb diff --git a/actionview/lib/action_view/version.rb b/actionview/lib/action_view/version.rb new file mode 100644 index 0000000000..094dd474df --- /dev/null +++ b/actionview/lib/action_view/version.rb @@ -0,0 +1,11 @@ +module ActionView + # Returns the version of the currently loaded ActionView as a Gem::Version + def self.version + Gem::Version.new "4.1.0.beta" + end + + module VERSION #:nodoc: + MAJOR, MINOR, TINY, PRE = ActionView.version.segments + STRING = ActionView.version.to_s + end +end diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb new file mode 100644 index 0000000000..8213997f4e --- /dev/null +++ b/actionview/test/abstract_unit.rb @@ -0,0 +1,382 @@ +require File.expand_path('../../../load_paths', __FILE__) + +$:.unshift(File.dirname(__FILE__) + '/lib') +$:.unshift(File.dirname(__FILE__) + '/fixtures/helpers') +$:.unshift(File.dirname(__FILE__) + '/fixtures/alternate_helpers') + +ENV['TMPDIR'] = File.join(File.dirname(__FILE__), 'tmp') + +require 'active_support/core_ext/kernel/reporting' + +# These are the normal settings that will be set up by Railties +# TODO: Have these tests support other combinations of these values +silence_warnings do + Encoding.default_internal = "UTF-8" + Encoding.default_external = "UTF-8" +end + +require 'active_support/testing/autorun' +require 'abstract_controller' +require 'action_controller' +require 'action_view' +require 'action_view/testing/resolvers' +require 'action_dispatch' +require 'active_support/dependencies' +require 'active_model' +require 'active_record' +require 'action_controller/caching' + +require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late + +module Rails + class << self + def env + @_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "test") + end + end +end + +ActiveSupport::Dependencies.hook! + +Thread.abort_on_exception = true + +# Show backtraces for deprecated behavior for quicker cleanup. +ActiveSupport::Deprecation.debug = true + +# Register danish language for testing +I18n.backend.store_translations 'da', {} +I18n.backend.store_translations 'pt-BR', {} +ORIGINAL_LOCALES = I18n.available_locales.map {|locale| locale.to_s }.sort + +FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures') +FIXTURES = Pathname.new(FIXTURE_LOAD_PATH) + +module RackTestUtils + def body_to_string(body) + if body.respond_to?(:each) + str = "" + body.each {|s| str << s } + str + else + body + end + end + extend self +end + +module RenderERBUtils + def view + @view ||= begin + path = ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH) + view_paths = ActionView::PathSet.new([path]) + ActionView::Base.new(view_paths) + end + end + + def render_erb(string) + @virtual_path = nil + + template = ActionView::Template.new( + string.strip, + "test template", + ActionView::Template::Handlers::ERB, + {}) + + template.render(self, {}).strip + end +end + +SharedTestRoutes = ActionDispatch::Routing::RouteSet.new + +module ActionDispatch + module SharedRoutes + def before_setup + @routes = SharedTestRoutes + super + end + end + + # Hold off drawing routes until all the possible controller classes + # have been loaded. + module DrawOnce + class << self + attr_accessor :drew + end + self.drew = false + + def before_setup + super + return if DrawOnce.drew + + SharedTestRoutes.draw do + get ':controller(/:action)' + end + + ActionDispatch::IntegrationTest.app.routes.draw do + get ':controller(/:action)' + end + + DrawOnce.drew = true + end + end +end + +module ActiveSupport + class TestCase + include ActionDispatch::DrawOnce + end +end + +class RoutedRackApp + attr_reader :routes + + def initialize(routes, &blk) + @routes = routes + @stack = ActionDispatch::MiddlewareStack.new(&blk).build(@routes) + end + + def call(env) + @stack.call(env) + end +end + +class BasicController + attr_accessor :request + + def config + @config ||= ActiveSupport::InheritableOptions.new(ActionController::Base.config).tap do |config| + # VIEW TODO: View tests should not require a controller + public_dir = File.expand_path("../fixtures/public", __FILE__) + config.assets_dir = public_dir + config.javascripts_dir = "#{public_dir}/javascripts" + config.stylesheets_dir = "#{public_dir}/stylesheets" + config.assets = ActiveSupport::InheritableOptions.new({ :prefix => "assets" }) + config + end + end +end + +class ActionDispatch::IntegrationTest < ActiveSupport::TestCase + include ActionDispatch::SharedRoutes + + def self.build_app(routes = nil) + RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware| + middleware.use "ActionDispatch::ShowExceptions", ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public") + middleware.use "ActionDispatch::DebugExceptions" + middleware.use "ActionDispatch::Callbacks" + middleware.use "ActionDispatch::ParamsParser" + middleware.use "ActionDispatch::Cookies" + middleware.use "ActionDispatch::Flash" + middleware.use "Rack::Head" + yield(middleware) if block_given? + end + end + + self.app = build_app + + # Stub Rails dispatcher so it does not get controller references and + # simply return the controller#action as Rack::Body. + class StubDispatcher < ::ActionDispatch::Routing::RouteSet::Dispatcher + protected + def controller_reference(controller_param) + controller_param + end + + def dispatch(controller, action, env) + [200, {'Content-Type' => 'text/html'}, ["#{controller}##{action}"]] + end + end + + def self.stub_controllers + old_dispatcher = ActionDispatch::Routing::RouteSet::Dispatcher + ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher } + ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, StubDispatcher } + yield ActionDispatch::Routing::RouteSet.new + ensure + ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher } + ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, old_dispatcher } + end + + def with_routing(&block) + temporary_routes = ActionDispatch::Routing::RouteSet.new + old_app, self.class.app = self.class.app, self.class.build_app(temporary_routes) + old_routes = SharedTestRoutes + silence_warnings { Object.const_set(:SharedTestRoutes, temporary_routes) } + + yield temporary_routes + ensure + self.class.app = old_app + silence_warnings { Object.const_set(:SharedTestRoutes, old_routes) } + end + + def with_autoload_path(path) + path = File.join(File.dirname(__FILE__), "fixtures", path) + if ActiveSupport::Dependencies.autoload_paths.include?(path) + yield + else + begin + ActiveSupport::Dependencies.autoload_paths << path + yield + ensure + ActiveSupport::Dependencies.autoload_paths.reject! {|p| p == path} + ActiveSupport::Dependencies.clear + end + end + end +end + +# Temporary base class +class Rack::TestCase < ActionDispatch::IntegrationTest + def self.testing(klass = nil) + if klass + @testing = "/#{klass.name.underscore}".sub!(/_controller$/, '') + else + @testing + end + end + + def get(thing, *args) + if thing.is_a?(Symbol) + super("#{self.class.testing}/#{thing}", *args) + else + super + end + end + + def assert_body(body) + assert_equal body, Array(response.body).join + end + + def assert_status(code) + assert_equal code, response.status + end + + def assert_response(body, status = 200, headers = {}) + assert_body body + assert_status status + headers.each do |header, value| + assert_header header, value + end + end + + def assert_content_type(type) + assert_equal type, response.headers["Content-Type"] + end + + def assert_header(name, value) + assert_equal value, response.headers[name] + end +end + +module ActionController + class Base + include ActionController::Testing + # This stub emulates the Railtie including the URL helpers from a Rails application + include SharedTestRoutes.url_helpers + include SharedTestRoutes.mounted_helpers + + self.view_paths = FIXTURE_LOAD_PATH + + def self.test_routes(&block) + routes = ActionDispatch::Routing::RouteSet.new + routes.draw(&block) + include routes.url_helpers + end + end + + class TestCase + include ActionDispatch::TestProcess + include ActionDispatch::SharedRoutes + end +end + +class ::ApplicationController < ActionController::Base +end + +module ActionView + class TestCase + # Must repeat the setup because AV::TestCase is a duplication + # of AC::TestCase + include ActionDispatch::SharedRoutes + end +end + +class Workshop + extend ActiveModel::Naming + include ActiveModel::Conversion + attr_accessor :id + + def initialize(id) + @id = id + end + + def persisted? + id.present? + end + + def to_s + id.to_s + end +end + +module ActionDispatch + class DebugExceptions + private + remove_method :stderr_logger + # Silence logger + def stderr_logger + nil + end + end +end + +module ActionDispatch + module RoutingVerbs + def get(uri_or_host, path = nil) + host = uri_or_host.host unless path + path ||= uri_or_host.path + + params = {'PATH_INFO' => path, + 'REQUEST_METHOD' => 'GET', + 'HTTP_HOST' => host} + + routes.call(params)[2].join + end + end +end + +module RoutingTestHelpers + def url_for(set, options, recall = nil) + set.send(:url_for, options.merge(:only_path => true, :_recall => recall)) + end +end + +class ResourcesController < ActionController::Base + def index() render :nothing => true end + alias_method :show, :index +end + +class ThreadsController < ResourcesController; end +class MessagesController < ResourcesController; end +class CommentsController < ResourcesController; end +class ReviewsController < ResourcesController; end +class AuthorsController < ResourcesController; end +class LogosController < ResourcesController; end + +class AccountsController < ResourcesController; end +class AdminController < ResourcesController; end +class ProductsController < ResourcesController; end +class ImagesController < ResourcesController; end +class PreferencesController < ResourcesController; end + +module Backoffice + class ProductsController < ResourcesController; end + class TagsController < ResourcesController; end + class ManufacturersController < ResourcesController; end + class ImagesController < ResourcesController; end + + module Admin + class ProductsController < ResourcesController; end + class ImagesController < ResourcesController; end + end +end diff --git a/actionpack/test/active_record_unit.rb b/actionview/test/active_record_unit.rb index 95fbb112c0..95fbb112c0 100644 --- a/actionpack/test/active_record_unit.rb +++ b/actionview/test/active_record_unit.rb diff --git a/actionpack/test/activerecord/controller_runtime_test.rb b/actionview/test/activerecord/controller_runtime_test.rb index 368bec1c70..368bec1c70 100644 --- a/actionpack/test/activerecord/controller_runtime_test.rb +++ b/actionview/test/activerecord/controller_runtime_test.rb diff --git a/actionpack/test/activerecord/form_helper_activerecord_test.rb b/actionview/test/activerecord/form_helper_activerecord_test.rb index 2e302c65a7..2e302c65a7 100644 --- a/actionpack/test/activerecord/form_helper_activerecord_test.rb +++ b/actionview/test/activerecord/form_helper_activerecord_test.rb diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionview/test/activerecord/polymorphic_routes_test.rb index afb714484b..afb714484b 100644 --- a/actionpack/test/activerecord/polymorphic_routes_test.rb +++ b/actionview/test/activerecord/polymorphic_routes_test.rb diff --git a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb b/actionview/test/activerecord/render_partial_with_record_identification_test.rb index 409370104d..409370104d 100644 --- a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb +++ b/actionview/test/activerecord/render_partial_with_record_identification_test.rb diff --git a/actionpack/test/fixtures/_top_level_partial.html.erb b/actionview/test/fixtures/_top_level_partial.html.erb index 0b1c2e46e0..0b1c2e46e0 100644 --- a/actionpack/test/fixtures/_top_level_partial.html.erb +++ b/actionview/test/fixtures/_top_level_partial.html.erb diff --git a/actionview/test/fixtures/_top_level_partial_only.erb b/actionview/test/fixtures/_top_level_partial_only.erb new file mode 100644 index 0000000000..44f25b61d0 --- /dev/null +++ b/actionview/test/fixtures/_top_level_partial_only.erb @@ -0,0 +1 @@ +top level partial
\ No newline at end of file diff --git a/actionpack/test/fixtures/blog_public/.gitignore b/actionview/test/fixtures/blog_public/.gitignore index 312e635ee6..312e635ee6 100644 --- a/actionpack/test/fixtures/blog_public/.gitignore +++ b/actionview/test/fixtures/blog_public/.gitignore diff --git a/actionpack/test/fixtures/blog_public/blog.html b/actionview/test/fixtures/blog_public/blog.html index 79ad44c010..79ad44c010 100644 --- a/actionpack/test/fixtures/blog_public/blog.html +++ b/actionview/test/fixtures/blog_public/blog.html diff --git a/actionpack/test/fixtures/blog_public/index.html b/actionview/test/fixtures/blog_public/index.html index 2de3825481..2de3825481 100644 --- a/actionpack/test/fixtures/blog_public/index.html +++ b/actionview/test/fixtures/blog_public/index.html diff --git a/actionpack/test/fixtures/blog_public/subdir/index.html b/actionview/test/fixtures/blog_public/subdir/index.html index 517bded335..517bded335 100644 --- a/actionpack/test/fixtures/blog_public/subdir/index.html +++ b/actionview/test/fixtures/blog_public/subdir/index.html diff --git a/actionpack/test/fixtures/comments/empty.de.html.erb b/actionview/test/fixtures/comments/empty.de.html.erb index cffd90dd26..cffd90dd26 100644 --- a/actionpack/test/fixtures/comments/empty.de.html.erb +++ b/actionview/test/fixtures/comments/empty.de.html.erb diff --git a/actionpack/test/fixtures/comments/empty.html.builder b/actionview/test/fixtures/comments/empty.html.builder index 2b0c7207a3..2b0c7207a3 100644 --- a/actionpack/test/fixtures/comments/empty.html.builder +++ b/actionview/test/fixtures/comments/empty.html.builder diff --git a/actionpack/test/fixtures/comments/empty.html.erb b/actionview/test/fixtures/comments/empty.html.erb index 827f3861de..827f3861de 100644 --- a/actionpack/test/fixtures/comments/empty.html.erb +++ b/actionview/test/fixtures/comments/empty.html.erb diff --git a/actionpack/test/fixtures/comments/empty.xml.erb b/actionview/test/fixtures/comments/empty.xml.erb index db1027cd7d..db1027cd7d 100644 --- a/actionpack/test/fixtures/comments/empty.xml.erb +++ b/actionview/test/fixtures/comments/empty.xml.erb diff --git a/actionpack/test/fixtures/companies.yml b/actionview/test/fixtures/companies.yml index ed2992e0b1..ed2992e0b1 100644 --- a/actionpack/test/fixtures/companies.yml +++ b/actionview/test/fixtures/companies.yml diff --git a/actionview/test/fixtures/company.rb b/actionview/test/fixtures/company.rb new file mode 100644 index 0000000000..f3ac3642fa --- /dev/null +++ b/actionview/test/fixtures/company.rb @@ -0,0 +1,9 @@ +class Company < ActiveRecord::Base + has_one :mascot + self.sequence_name = :companies_nonstd_seq + + validates_presence_of :name + def validate + errors.add('rating', 'rating should not be 2') if rating == 2 + end +end diff --git a/actionpack/test/fixtures/custom_pattern/another.html.erb b/actionview/test/fixtures/custom_pattern/another.html.erb index 6d7f3bafbb..6d7f3bafbb 100644 --- a/actionpack/test/fixtures/custom_pattern/another.html.erb +++ b/actionview/test/fixtures/custom_pattern/another.html.erb diff --git a/actionpack/test/fixtures/custom_pattern/html/another.erb b/actionview/test/fixtures/custom_pattern/html/another.erb index dbd7e96ab6..dbd7e96ab6 100644 --- a/actionpack/test/fixtures/custom_pattern/html/another.erb +++ b/actionview/test/fixtures/custom_pattern/html/another.erb diff --git a/actionpack/test/fixtures/custom_pattern/html/path.erb b/actionview/test/fixtures/custom_pattern/html/path.erb index 6d7f3bafbb..6d7f3bafbb 100644 --- a/actionpack/test/fixtures/custom_pattern/html/path.erb +++ b/actionview/test/fixtures/custom_pattern/html/path.erb diff --git a/actionview/test/fixtures/customers/_customer.html.erb b/actionview/test/fixtures/customers/_customer.html.erb new file mode 100644 index 0000000000..483571e22a --- /dev/null +++ b/actionview/test/fixtures/customers/_customer.html.erb @@ -0,0 +1 @@ +<%= greeting %>: <%= customer.name %>
\ No newline at end of file diff --git a/actionpack/test/fixtures/db_definitions/sqlite.sql b/actionview/test/fixtures/db_definitions/sqlite.sql index 99df4b3e61..99df4b3e61 100644 --- a/actionpack/test/fixtures/db_definitions/sqlite.sql +++ b/actionview/test/fixtures/db_definitions/sqlite.sql diff --git a/actionpack/test/fixtures/developer.rb b/actionview/test/fixtures/developer.rb index 4941463015..4941463015 100644 --- a/actionpack/test/fixtures/developer.rb +++ b/actionview/test/fixtures/developer.rb diff --git a/actionpack/test/fixtures/developers.yml b/actionview/test/fixtures/developers.yml index 3656564f63..3656564f63 100644 --- a/actionpack/test/fixtures/developers.yml +++ b/actionview/test/fixtures/developers.yml diff --git a/actionpack/test/fixtures/developers/_developer.erb b/actionview/test/fixtures/developers/_developer.erb index 904a3137e7..904a3137e7 100644 --- a/actionpack/test/fixtures/developers/_developer.erb +++ b/actionview/test/fixtures/developers/_developer.erb diff --git a/actionpack/test/fixtures/developers_projects.yml b/actionview/test/fixtures/developers_projects.yml index cee359c7cf..cee359c7cf 100644 --- a/actionpack/test/fixtures/developers_projects.yml +++ b/actionview/test/fixtures/developers_projects.yml diff --git a/actionpack/test/fixtures/digestor/comments/_comment.html.erb b/actionview/test/fixtures/digestor/comments/_comment.html.erb index f172e749da..f172e749da 100644 --- a/actionpack/test/fixtures/digestor/comments/_comment.html.erb +++ b/actionview/test/fixtures/digestor/comments/_comment.html.erb diff --git a/actionpack/test/fixtures/digestor/comments/_comments.html.erb b/actionview/test/fixtures/digestor/comments/_comments.html.erb index c28646a283..c28646a283 100644 --- a/actionpack/test/fixtures/digestor/comments/_comments.html.erb +++ b/actionview/test/fixtures/digestor/comments/_comments.html.erb diff --git a/actionpack/test/fixtures/digestor/events/_event.html.erb b/actionview/test/fixtures/digestor/events/_event.html.erb index e69de29bb2..e69de29bb2 100644 --- a/actionpack/test/fixtures/digestor/events/_event.html.erb +++ b/actionview/test/fixtures/digestor/events/_event.html.erb diff --git a/actionview/test/fixtures/digestor/level/_recursion.html.erb b/actionview/test/fixtures/digestor/level/_recursion.html.erb new file mode 100644 index 0000000000..ee5aaf09c3 --- /dev/null +++ b/actionview/test/fixtures/digestor/level/_recursion.html.erb @@ -0,0 +1 @@ +<%= render 'recursion' %> diff --git a/actionpack/test/fixtures/digestor/level/below/_header.html.erb b/actionview/test/fixtures/digestor/level/below/_header.html.erb index e69de29bb2..e69de29bb2 100644 --- a/actionpack/test/fixtures/digestor/level/below/_header.html.erb +++ b/actionview/test/fixtures/digestor/level/below/_header.html.erb diff --git a/actionpack/test/fixtures/digestor/level/below/index.html.erb b/actionview/test/fixtures/digestor/level/below/index.html.erb index b92f49a8f8..b92f49a8f8 100644 --- a/actionpack/test/fixtures/digestor/level/below/index.html.erb +++ b/actionview/test/fixtures/digestor/level/below/index.html.erb diff --git a/actionview/test/fixtures/digestor/level/recursion.html.erb b/actionview/test/fixtures/digestor/level/recursion.html.erb new file mode 100644 index 0000000000..ee5aaf09c3 --- /dev/null +++ b/actionview/test/fixtures/digestor/level/recursion.html.erb @@ -0,0 +1 @@ +<%= render 'recursion' %> diff --git a/actionpack/test/fixtures/digestor/messages/_form.html.erb b/actionview/test/fixtures/digestor/messages/_form.html.erb index e69de29bb2..e69de29bb2 100644 --- a/actionpack/test/fixtures/digestor/messages/_form.html.erb +++ b/actionview/test/fixtures/digestor/messages/_form.html.erb diff --git a/actionpack/test/fixtures/digestor/messages/_header.html.erb b/actionview/test/fixtures/digestor/messages/_header.html.erb index e69de29bb2..e69de29bb2 100644 --- a/actionpack/test/fixtures/digestor/messages/_header.html.erb +++ b/actionview/test/fixtures/digestor/messages/_header.html.erb diff --git a/actionpack/test/fixtures/digestor/messages/_message.html.erb b/actionview/test/fixtures/digestor/messages/_message.html.erb index 406a0fb848..406a0fb848 100644 --- a/actionpack/test/fixtures/digestor/messages/_message.html.erb +++ b/actionview/test/fixtures/digestor/messages/_message.html.erb diff --git a/actionpack/test/fixtures/digestor/messages/actions/_move.html.erb b/actionview/test/fixtures/digestor/messages/actions/_move.html.erb index e69de29bb2..e69de29bb2 100644 --- a/actionpack/test/fixtures/digestor/messages/actions/_move.html.erb +++ b/actionview/test/fixtures/digestor/messages/actions/_move.html.erb diff --git a/actionpack/test/fixtures/digestor/messages/edit.html.erb b/actionview/test/fixtures/digestor/messages/edit.html.erb index a9e0a88e32..a9e0a88e32 100644 --- a/actionpack/test/fixtures/digestor/messages/edit.html.erb +++ b/actionview/test/fixtures/digestor/messages/edit.html.erb diff --git a/actionpack/test/fixtures/digestor/messages/index.html.erb b/actionview/test/fixtures/digestor/messages/index.html.erb index 1937b652e4..1937b652e4 100644 --- a/actionpack/test/fixtures/digestor/messages/index.html.erb +++ b/actionview/test/fixtures/digestor/messages/index.html.erb diff --git a/actionpack/test/fixtures/digestor/messages/show.html.erb b/actionview/test/fixtures/digestor/messages/show.html.erb index 130e67109e..42aa2363dd 100644 --- a/actionpack/test/fixtures/digestor/messages/show.html.erb +++ b/actionview/test/fixtures/digestor/messages/show.html.erb @@ -6,9 +6,9 @@ <%= render @message.history.events %> -<%# render "something_missing" %> -<%# render "something_missing_1" %> +<%# render "something_missing" %> +<%# render "something_missing_1" %> <% # Template Dependency: messages/form -%> +%>
\ No newline at end of file diff --git a/actionpack/test/fixtures/fun/games/_game.erb b/actionview/test/fixtures/fun/games/_game.erb index f0f542ff92..f0f542ff92 100644 --- a/actionpack/test/fixtures/fun/games/_game.erb +++ b/actionview/test/fixtures/fun/games/_game.erb diff --git a/actionview/test/fixtures/fun/games/hello_world.erb b/actionview/test/fixtures/fun/games/hello_world.erb new file mode 100644 index 0000000000..1ebfbe2539 --- /dev/null +++ b/actionview/test/fixtures/fun/games/hello_world.erb @@ -0,0 +1 @@ +Living in a nested world
\ No newline at end of file diff --git a/actionpack/test/fixtures/fun/serious/games/_game.erb b/actionview/test/fixtures/fun/serious/games/_game.erb index 523bc55bd7..523bc55bd7 100644 --- a/actionpack/test/fixtures/fun/serious/games/_game.erb +++ b/actionview/test/fixtures/fun/serious/games/_game.erb diff --git a/actionview/test/fixtures/functional_caching/fragment_cached_without_digest.html.erb b/actionview/test/fixtures/functional_caching/fragment_cached_without_digest.html.erb new file mode 100644 index 0000000000..3125583a28 --- /dev/null +++ b/actionview/test/fixtures/functional_caching/fragment_cached_without_digest.html.erb @@ -0,0 +1,3 @@ +<body> +<%= cache 'nodigest', skip_digest: true do %><p>ERB</p><% end %> +</body> diff --git a/actionpack/test/fixtures/games/_game.erb b/actionview/test/fixtures/games/_game.erb index 1aeb81fcba..1aeb81fcba 100644 --- a/actionpack/test/fixtures/games/_game.erb +++ b/actionview/test/fixtures/games/_game.erb diff --git a/actionview/test/fixtures/good_customers/_good_customer.html.erb b/actionview/test/fixtures/good_customers/_good_customer.html.erb new file mode 100644 index 0000000000..a2d97ebc6d --- /dev/null +++ b/actionview/test/fixtures/good_customers/_good_customer.html.erb @@ -0,0 +1 @@ +<%= greeting %> good customer: <%= good_customer.name %><%= good_customer_counter %>
\ No newline at end of file diff --git a/actionpack/test/fixtures/happy_path/render_action/hello_world.erb b/actionview/test/fixtures/happy_path/render_action/hello_world.erb index 6769dd60bd..6769dd60bd 100644 --- a/actionpack/test/fixtures/happy_path/render_action/hello_world.erb +++ b/actionview/test/fixtures/happy_path/render_action/hello_world.erb diff --git a/actionpack/test/fixtures/layout_tests/alt/hello.erb b/actionview/test/fixtures/layout_tests/alt/hello.erb index 1055c36659..1055c36659 100644 --- a/actionpack/test/fixtures/layout_tests/alt/hello.erb +++ b/actionview/test/fixtures/layout_tests/alt/hello.erb diff --git a/actionpack/test/fixtures/layouts/_column.html.erb b/actionview/test/fixtures/layouts/_column.html.erb index 96db002b8a..96db002b8a 100644 --- a/actionpack/test/fixtures/layouts/_column.html.erb +++ b/actionview/test/fixtures/layouts/_column.html.erb diff --git a/actionview/test/fixtures/layouts/_customers.erb b/actionview/test/fixtures/layouts/_customers.erb new file mode 100644 index 0000000000..ae63f13cd3 --- /dev/null +++ b/actionview/test/fixtures/layouts/_customers.erb @@ -0,0 +1 @@ +<title><%= yield Struct.new(:name).new("David") %></title>
\ No newline at end of file diff --git a/actionpack/test/fixtures/layouts/_partial_and_yield.erb b/actionview/test/fixtures/layouts/_partial_and_yield.erb index 74cc428ffa..74cc428ffa 100644 --- a/actionpack/test/fixtures/layouts/_partial_and_yield.erb +++ b/actionview/test/fixtures/layouts/_partial_and_yield.erb diff --git a/actionpack/test/fixtures/layouts/_yield_only.erb b/actionview/test/fixtures/layouts/_yield_only.erb index 37f0bddbd7..37f0bddbd7 100644 --- a/actionpack/test/fixtures/layouts/_yield_only.erb +++ b/actionview/test/fixtures/layouts/_yield_only.erb diff --git a/actionpack/test/fixtures/layouts/_yield_with_params.erb b/actionview/test/fixtures/layouts/_yield_with_params.erb index 68e6557fb8..68e6557fb8 100644 --- a/actionpack/test/fixtures/layouts/_yield_with_params.erb +++ b/actionview/test/fixtures/layouts/_yield_with_params.erb diff --git a/actionpack/test/fixtures/layouts/streaming.erb b/actionview/test/fixtures/layouts/streaming.erb index d3f896a6ca..d3f896a6ca 100644 --- a/actionpack/test/fixtures/layouts/streaming.erb +++ b/actionview/test/fixtures/layouts/streaming.erb diff --git a/actionview/test/fixtures/layouts/yield.erb b/actionview/test/fixtures/layouts/yield.erb new file mode 100644 index 0000000000..482dc9022e --- /dev/null +++ b/actionview/test/fixtures/layouts/yield.erb @@ -0,0 +1,2 @@ +<title><%= yield :title %></title> +<%= yield %> diff --git a/actionpack/test/fixtures/layouts/yield_with_render_inline_inside.erb b/actionview/test/fixtures/layouts/yield_with_render_inline_inside.erb index 7298d79690..7298d79690 100644 --- a/actionpack/test/fixtures/layouts/yield_with_render_inline_inside.erb +++ b/actionview/test/fixtures/layouts/yield_with_render_inline_inside.erb diff --git a/actionpack/test/fixtures/layouts/yield_with_render_partial_inside.erb b/actionview/test/fixtures/layouts/yield_with_render_partial_inside.erb index 74cc428ffa..74cc428ffa 100644 --- a/actionpack/test/fixtures/layouts/yield_with_render_partial_inside.erb +++ b/actionview/test/fixtures/layouts/yield_with_render_partial_inside.erb diff --git a/actionpack/test/fixtures/mascot.rb b/actionview/test/fixtures/mascot.rb index f9f1448b8f..f9f1448b8f 100644 --- a/actionpack/test/fixtures/mascot.rb +++ b/actionview/test/fixtures/mascot.rb diff --git a/actionpack/test/fixtures/mascots.yml b/actionview/test/fixtures/mascots.yml index 17b7dff454..17b7dff454 100644 --- a/actionpack/test/fixtures/mascots.yml +++ b/actionview/test/fixtures/mascots.yml diff --git a/actionpack/test/fixtures/mascots/_mascot.html.erb b/actionview/test/fixtures/mascots/_mascot.html.erb index 432773a1da..432773a1da 100644 --- a/actionpack/test/fixtures/mascots/_mascot.html.erb +++ b/actionview/test/fixtures/mascots/_mascot.html.erb diff --git a/actionview/test/fixtures/multipart/bracketed_utf8_param b/actionview/test/fixtures/multipart/bracketed_utf8_param new file mode 100644 index 0000000000..df9cecea08 --- /dev/null +++ b/actionview/test/fixtures/multipart/bracketed_utf8_param @@ -0,0 +1,5 @@ +--AaB03x +Content-Disposition: form-data; name="Iñtërnâtiônàlizætiøn_name[Iñtërnâtiônàlizætiøn_nested_name]" + +Iñtërnâtiônàlizætiøn_value +--AaB03x-- diff --git a/actionview/test/fixtures/multipart/single_utf8_param b/actionview/test/fixtures/multipart/single_utf8_param new file mode 100644 index 0000000000..1d9fae7b17 --- /dev/null +++ b/actionview/test/fixtures/multipart/single_utf8_param @@ -0,0 +1,5 @@ +--AaB03x +Content-Disposition: form-data; name="Iñtërnâtiônàlizætiøn_name" + +Iñtërnâtiônàlizætiøn_value +--AaB03x-- diff --git a/actionpack/test/fixtures/plain_text.raw b/actionview/test/fixtures/plain_text.raw index b13985337f..b13985337f 100644 --- a/actionpack/test/fixtures/plain_text.raw +++ b/actionview/test/fixtures/plain_text.raw diff --git a/actionpack/test/fixtures/plain_text_with_characters.raw b/actionview/test/fixtures/plain_text_with_characters.raw index 1e86e44fb4..1e86e44fb4 100644 --- a/actionpack/test/fixtures/plain_text_with_characters.raw +++ b/actionview/test/fixtures/plain_text_with_characters.raw diff --git a/actionpack/test/fixtures/project.rb b/actionview/test/fixtures/project.rb index c124a9e605..c124a9e605 100644 --- a/actionpack/test/fixtures/project.rb +++ b/actionview/test/fixtures/project.rb diff --git a/actionpack/test/fixtures/projects.yml b/actionview/test/fixtures/projects.yml index 02800c7824..02800c7824 100644 --- a/actionpack/test/fixtures/projects.yml +++ b/actionview/test/fixtures/projects.yml diff --git a/actionpack/test/fixtures/projects/_project.erb b/actionview/test/fixtures/projects/_project.erb index 480c4c2af3..480c4c2af3 100644 --- a/actionpack/test/fixtures/projects/_project.erb +++ b/actionview/test/fixtures/projects/_project.erb diff --git a/actionpack/test/fixtures/public/.gitignore b/actionview/test/fixtures/public/.gitignore index 312e635ee6..312e635ee6 100644 --- a/actionpack/test/fixtures/public/.gitignore +++ b/actionview/test/fixtures/public/.gitignore diff --git a/actionpack/test/fixtures/public/elsewhere/cools.js b/actionview/test/fixtures/public/elsewhere/cools.js index 6e12fe29c4..6e12fe29c4 100644 --- a/actionpack/test/fixtures/public/elsewhere/cools.js +++ b/actionview/test/fixtures/public/elsewhere/cools.js diff --git a/actionpack/test/fixtures/public/elsewhere/file.css b/actionview/test/fixtures/public/elsewhere/file.css index 6aea0733b1..6aea0733b1 100644 --- a/actionpack/test/fixtures/public/elsewhere/file.css +++ b/actionview/test/fixtures/public/elsewhere/file.css diff --git a/actionview/test/fixtures/public/foo/baz.css b/actionview/test/fixtures/public/foo/baz.css new file mode 100644 index 0000000000..b5173fbef2 --- /dev/null +++ b/actionview/test/fixtures/public/foo/baz.css @@ -0,0 +1,3 @@ +body { +background: #000; +} diff --git a/actionpack/test/fixtures/public/javascripts/application.js b/actionview/test/fixtures/public/javascripts/application.js index 9702692980..9702692980 100644 --- a/actionpack/test/fixtures/public/javascripts/application.js +++ b/actionview/test/fixtures/public/javascripts/application.js diff --git a/actionpack/test/fixtures/public/javascripts/bank.js b/actionview/test/fixtures/public/javascripts/bank.js index 4a1bee7182..4a1bee7182 100644 --- a/actionpack/test/fixtures/public/javascripts/bank.js +++ b/actionview/test/fixtures/public/javascripts/bank.js diff --git a/actionpack/test/fixtures/public/javascripts/common.javascript b/actionview/test/fixtures/public/javascripts/common.javascript index 2ae1929056..2ae1929056 100644 --- a/actionpack/test/fixtures/public/javascripts/common.javascript +++ b/actionview/test/fixtures/public/javascripts/common.javascript diff --git a/actionpack/test/fixtures/public/javascripts/controls.js b/actionview/test/fixtures/public/javascripts/controls.js index 88168d9f13..88168d9f13 100644 --- a/actionpack/test/fixtures/public/javascripts/controls.js +++ b/actionview/test/fixtures/public/javascripts/controls.js diff --git a/actionpack/test/fixtures/public/javascripts/dragdrop.js b/actionview/test/fixtures/public/javascripts/dragdrop.js index c07061ac0c..c07061ac0c 100644 --- a/actionpack/test/fixtures/public/javascripts/dragdrop.js +++ b/actionview/test/fixtures/public/javascripts/dragdrop.js diff --git a/actionpack/test/fixtures/public/javascripts/effects.js b/actionview/test/fixtures/public/javascripts/effects.js index b555d63034..b555d63034 100644 --- a/actionpack/test/fixtures/public/javascripts/effects.js +++ b/actionview/test/fixtures/public/javascripts/effects.js diff --git a/actionpack/test/fixtures/public/javascripts/prototype.js b/actionview/test/fixtures/public/javascripts/prototype.js index 9780064a0e..9780064a0e 100644 --- a/actionpack/test/fixtures/public/javascripts/prototype.js +++ b/actionview/test/fixtures/public/javascripts/prototype.js diff --git a/actionpack/test/fixtures/public/javascripts/robber.js b/actionview/test/fixtures/public/javascripts/robber.js index eb82fcbdf4..eb82fcbdf4 100644 --- a/actionpack/test/fixtures/public/javascripts/robber.js +++ b/actionview/test/fixtures/public/javascripts/robber.js diff --git a/actionpack/test/fixtures/public/javascripts/subdir/subdir.js b/actionview/test/fixtures/public/javascripts/subdir/subdir.js index 9d23a67aa1..9d23a67aa1 100644 --- a/actionpack/test/fixtures/public/javascripts/subdir/subdir.js +++ b/actionview/test/fixtures/public/javascripts/subdir/subdir.js diff --git a/actionpack/test/fixtures/public/javascripts/version.1.0.js b/actionview/test/fixtures/public/javascripts/version.1.0.js index cfd5fce70e..cfd5fce70e 100644 --- a/actionpack/test/fixtures/public/javascripts/version.1.0.js +++ b/actionview/test/fixtures/public/javascripts/version.1.0.js diff --git a/actionpack/test/fixtures/public/stylesheets/bank.css b/actionview/test/fixtures/public/stylesheets/bank.css index ea161b12b2..ea161b12b2 100644 --- a/actionpack/test/fixtures/public/stylesheets/bank.css +++ b/actionview/test/fixtures/public/stylesheets/bank.css diff --git a/actionpack/test/fixtures/public/stylesheets/random.styles b/actionview/test/fixtures/public/stylesheets/random.styles index d4eeead95c..d4eeead95c 100644 --- a/actionpack/test/fixtures/public/stylesheets/random.styles +++ b/actionview/test/fixtures/public/stylesheets/random.styles diff --git a/actionpack/test/fixtures/public/stylesheets/robber.css b/actionview/test/fixtures/public/stylesheets/robber.css index 0fdd00a6a5..0fdd00a6a5 100644 --- a/actionpack/test/fixtures/public/stylesheets/robber.css +++ b/actionview/test/fixtures/public/stylesheets/robber.css diff --git a/actionpack/test/fixtures/public/stylesheets/subdir/subdir.css b/actionview/test/fixtures/public/stylesheets/subdir/subdir.css index 241152a905..241152a905 100644 --- a/actionpack/test/fixtures/public/stylesheets/subdir/subdir.css +++ b/actionview/test/fixtures/public/stylesheets/subdir/subdir.css diff --git a/actionpack/test/fixtures/public/stylesheets/version.1.0.css b/actionview/test/fixtures/public/stylesheets/version.1.0.css index 30f5f9ba6e..30f5f9ba6e 100644 --- a/actionpack/test/fixtures/public/stylesheets/version.1.0.css +++ b/actionview/test/fixtures/public/stylesheets/version.1.0.css diff --git a/actionpack/test/fixtures/replies.yml b/actionview/test/fixtures/replies.yml index 2a3454b8bf..2a3454b8bf 100644 --- a/actionpack/test/fixtures/replies.yml +++ b/actionview/test/fixtures/replies.yml diff --git a/actionpack/test/fixtures/replies/_reply.erb b/actionview/test/fixtures/replies/_reply.erb index 68baf548d8..68baf548d8 100644 --- a/actionpack/test/fixtures/replies/_reply.erb +++ b/actionview/test/fixtures/replies/_reply.erb diff --git a/actionpack/test/fixtures/reply.rb b/actionview/test/fixtures/reply.rb index 047522c55b..047522c55b 100644 --- a/actionpack/test/fixtures/reply.rb +++ b/actionview/test/fixtures/reply.rb diff --git a/actionview/test/fixtures/respond_to/using_defaults_with_all.html.erb b/actionview/test/fixtures/respond_to/using_defaults_with_all.html.erb new file mode 100644 index 0000000000..9f1f855269 --- /dev/null +++ b/actionview/test/fixtures/respond_to/using_defaults_with_all.html.erb @@ -0,0 +1 @@ +HTML! diff --git a/actionview/test/fixtures/ruby_template.ruby b/actionview/test/fixtures/ruby_template.ruby new file mode 100644 index 0000000000..5097bce47c --- /dev/null +++ b/actionview/test/fixtures/ruby_template.ruby @@ -0,0 +1,2 @@ +body = "" +body << ["Hello", "from", "Ruby", "code"].join(" ") diff --git a/actionpack/test/fixtures/scope/test/modgreet.erb b/actionview/test/fixtures/scope/test/modgreet.erb index 8947726e89..8947726e89 100644 --- a/actionpack/test/fixtures/scope/test/modgreet.erb +++ b/actionview/test/fixtures/scope/test/modgreet.erb diff --git a/actionview/test/fixtures/shared.html.erb b/actionview/test/fixtures/shared.html.erb new file mode 100644 index 0000000000..af262fc9f8 --- /dev/null +++ b/actionview/test/fixtures/shared.html.erb @@ -0,0 +1 @@ +Elastica
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/_200.html.erb b/actionview/test/fixtures/test/_200.html.erb index c9f45675dc..c9f45675dc 100644 --- a/actionpack/test/fixtures/test/_200.html.erb +++ b/actionview/test/fixtures/test/_200.html.erb diff --git a/actionpack/test/fixtures/test/_b_layout_for_partial.html.erb b/actionview/test/fixtures/test/_b_layout_for_partial.html.erb index e918ba8f83..e918ba8f83 100644 --- a/actionpack/test/fixtures/test/_b_layout_for_partial.html.erb +++ b/actionview/test/fixtures/test/_b_layout_for_partial.html.erb diff --git a/actionpack/test/fixtures/test/_b_layout_for_partial_with_object.html.erb b/actionview/test/fixtures/test/_b_layout_for_partial_with_object.html.erb index bdd53014cd..bdd53014cd 100644 --- a/actionpack/test/fixtures/test/_b_layout_for_partial_with_object.html.erb +++ b/actionview/test/fixtures/test/_b_layout_for_partial_with_object.html.erb diff --git a/actionpack/test/fixtures/test/_b_layout_for_partial_with_object_counter.html.erb b/actionview/test/fixtures/test/_b_layout_for_partial_with_object_counter.html.erb index 44d6121297..44d6121297 100644 --- a/actionpack/test/fixtures/test/_b_layout_for_partial_with_object_counter.html.erb +++ b/actionview/test/fixtures/test/_b_layout_for_partial_with_object_counter.html.erb diff --git a/actionview/test/fixtures/test/_changing_priority.html.erb b/actionview/test/fixtures/test/_changing_priority.html.erb new file mode 100644 index 0000000000..3225efc49a --- /dev/null +++ b/actionview/test/fixtures/test/_changing_priority.html.erb @@ -0,0 +1 @@ +HTML
\ No newline at end of file diff --git a/actionview/test/fixtures/test/_changing_priority.json.erb b/actionview/test/fixtures/test/_changing_priority.json.erb new file mode 100644 index 0000000000..7fa41dce66 --- /dev/null +++ b/actionview/test/fixtures/test/_changing_priority.json.erb @@ -0,0 +1 @@ +JSON
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/_content_tag_nested_in_content_tag.erb b/actionview/test/fixtures/test/_content_tag_nested_in_content_tag.erb index 2f21a75dd9..2f21a75dd9 100644 --- a/actionpack/test/fixtures/test/_content_tag_nested_in_content_tag.erb +++ b/actionview/test/fixtures/test/_content_tag_nested_in_content_tag.erb diff --git a/actionview/test/fixtures/test/_counter.html.erb b/actionview/test/fixtures/test/_counter.html.erb new file mode 100644 index 0000000000..fd245bfc70 --- /dev/null +++ b/actionview/test/fixtures/test/_counter.html.erb @@ -0,0 +1 @@ +<%= counter_counter %>
\ No newline at end of file diff --git a/actionview/test/fixtures/test/_customer.erb b/actionview/test/fixtures/test/_customer.erb new file mode 100644 index 0000000000..d8220afeda --- /dev/null +++ b/actionview/test/fixtures/test/_customer.erb @@ -0,0 +1 @@ +Hello: <%= customer.name rescue "Anonymous" %>
\ No newline at end of file diff --git a/actionview/test/fixtures/test/_customer_greeting.erb b/actionview/test/fixtures/test/_customer_greeting.erb new file mode 100644 index 0000000000..6acbcb20c4 --- /dev/null +++ b/actionview/test/fixtures/test/_customer_greeting.erb @@ -0,0 +1 @@ +<%= greeting %>: <%= customer_greeting.name %>
\ No newline at end of file diff --git a/actionview/test/fixtures/test/_customer_with_var.erb b/actionview/test/fixtures/test/_customer_with_var.erb new file mode 100644 index 0000000000..00047dd20e --- /dev/null +++ b/actionview/test/fixtures/test/_customer_with_var.erb @@ -0,0 +1 @@ +<%= customer.name %> <%= customer.name %> <%= customer.name %>
\ No newline at end of file diff --git a/actionview/test/fixtures/test/_directory/_partial_with_locales.html.erb b/actionview/test/fixtures/test/_directory/_partial_with_locales.html.erb new file mode 100644 index 0000000000..1cc8d41475 --- /dev/null +++ b/actionview/test/fixtures/test/_directory/_partial_with_locales.html.erb @@ -0,0 +1 @@ +Hello <%= name %> diff --git a/actionview/test/fixtures/test/_first_json_partial.json.erb b/actionview/test/fixtures/test/_first_json_partial.json.erb new file mode 100644 index 0000000000..790ee896db --- /dev/null +++ b/actionview/test/fixtures/test/_first_json_partial.json.erb @@ -0,0 +1 @@ +<%= render :partial => "test/second_json_partial" %>
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/_from_helper.erb b/actionview/test/fixtures/test/_from_helper.erb index 16de7c0f8a..16de7c0f8a 100644 --- a/actionpack/test/fixtures/test/_from_helper.erb +++ b/actionview/test/fixtures/test/_from_helper.erb diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/app/mailers/.empty_directory b/actionview/test/fixtures/test/_json_change_priority.json.erb index e69de29bb2..e69de29bb2 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/app/mailers/.empty_directory +++ b/actionview/test/fixtures/test/_json_change_priority.json.erb diff --git a/actionpack/test/fixtures/test/_label_with_block.erb b/actionview/test/fixtures/test/_label_with_block.erb index 40117e594e..40117e594e 100644 --- a/actionpack/test/fixtures/test/_label_with_block.erb +++ b/actionview/test/fixtures/test/_label_with_block.erb diff --git a/actionpack/test/fixtures/test/_layout_for_block_with_args.html.erb b/actionview/test/fixtures/test/_layout_for_block_with_args.html.erb index 307533208d..307533208d 100644 --- a/actionpack/test/fixtures/test/_layout_for_block_with_args.html.erb +++ b/actionview/test/fixtures/test/_layout_for_block_with_args.html.erb diff --git a/actionview/test/fixtures/test/_layout_for_partial.html.erb b/actionview/test/fixtures/test/_layout_for_partial.html.erb new file mode 100644 index 0000000000..666efadbb6 --- /dev/null +++ b/actionview/test/fixtures/test/_layout_for_partial.html.erb @@ -0,0 +1,3 @@ +Before (<%= name %>) +<%= yield %> +After
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/_layout_with_partial_and_yield.html.erb b/actionview/test/fixtures/test/_layout_with_partial_and_yield.html.erb index 820e7db789..820e7db789 100644 --- a/actionpack/test/fixtures/test/_layout_with_partial_and_yield.html.erb +++ b/actionview/test/fixtures/test/_layout_with_partial_and_yield.html.erb diff --git a/actionpack/test/fixtures/test/_local_inspector.html.erb b/actionview/test/fixtures/test/_local_inspector.html.erb index e6765c0882..e6765c0882 100644 --- a/actionpack/test/fixtures/test/_local_inspector.html.erb +++ b/actionview/test/fixtures/test/_local_inspector.html.erb diff --git a/actionpack/test/fixtures/test/_object_inspector.erb b/actionview/test/fixtures/test/_object_inspector.erb index 53af593821..53af593821 100644 --- a/actionpack/test/fixtures/test/_object_inspector.erb +++ b/actionview/test/fixtures/test/_object_inspector.erb diff --git a/actionpack/test/fixtures/test/_one.html.erb b/actionview/test/fixtures/test/_one.html.erb index f796291cb4..f796291cb4 100644 --- a/actionpack/test/fixtures/test/_one.html.erb +++ b/actionview/test/fixtures/test/_one.html.erb diff --git a/actionview/test/fixtures/test/_partial.erb b/actionview/test/fixtures/test/_partial.erb new file mode 100644 index 0000000000..e466dcbd8e --- /dev/null +++ b/actionview/test/fixtures/test/_partial.erb @@ -0,0 +1 @@ +invalid
\ No newline at end of file diff --git a/actionview/test/fixtures/test/_partial.html.erb b/actionview/test/fixtures/test/_partial.html.erb new file mode 100644 index 0000000000..e39f6c9827 --- /dev/null +++ b/actionview/test/fixtures/test/_partial.html.erb @@ -0,0 +1 @@ +partial html
\ No newline at end of file diff --git a/actionview/test/fixtures/test/_partial.js.erb b/actionview/test/fixtures/test/_partial.js.erb new file mode 100644 index 0000000000..b350cdd7ef --- /dev/null +++ b/actionview/test/fixtures/test/_partial.js.erb @@ -0,0 +1 @@ +partial js
\ No newline at end of file diff --git a/actionview/test/fixtures/test/_partial_for_use_in_layout.html.erb b/actionview/test/fixtures/test/_partial_for_use_in_layout.html.erb new file mode 100644 index 0000000000..3a03a64e31 --- /dev/null +++ b/actionview/test/fixtures/test/_partial_for_use_in_layout.html.erb @@ -0,0 +1 @@ +Inside from partial (<%= name %>)
\ No newline at end of file diff --git a/actionview/test/fixtures/test/_partial_name_local_variable.erb b/actionview/test/fixtures/test/_partial_name_local_variable.erb new file mode 100644 index 0000000000..cc3a91c89f --- /dev/null +++ b/actionview/test/fixtures/test/_partial_name_local_variable.erb @@ -0,0 +1 @@ +<%= partial_name_local_variable %> diff --git a/actionview/test/fixtures/test/_partial_only.erb b/actionview/test/fixtures/test/_partial_only.erb new file mode 100644 index 0000000000..a44b3eed40 --- /dev/null +++ b/actionview/test/fixtures/test/_partial_only.erb @@ -0,0 +1 @@ +only partial
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/_partial_with_layout.erb b/actionview/test/fixtures/test/_partial_with_layout.erb index 2a50c834fe..2a50c834fe 100644 --- a/actionpack/test/fixtures/test/_partial_with_layout.erb +++ b/actionview/test/fixtures/test/_partial_with_layout.erb diff --git a/actionpack/test/fixtures/test/_partial_with_layout_block_content.erb b/actionview/test/fixtures/test/_partial_with_layout_block_content.erb index 65dafd93a8..65dafd93a8 100644 --- a/actionpack/test/fixtures/test/_partial_with_layout_block_content.erb +++ b/actionview/test/fixtures/test/_partial_with_layout_block_content.erb diff --git a/actionpack/test/fixtures/test/_partial_with_layout_block_partial.erb b/actionview/test/fixtures/test/_partial_with_layout_block_partial.erb index 444197a7d0..444197a7d0 100644 --- a/actionpack/test/fixtures/test/_partial_with_layout_block_partial.erb +++ b/actionview/test/fixtures/test/_partial_with_layout_block_partial.erb diff --git a/actionpack/test/fixtures/test/_partial_with_only_html_version.html.erb b/actionview/test/fixtures/test/_partial_with_only_html_version.html.erb index 00e6b6d6da..00e6b6d6da 100644 --- a/actionpack/test/fixtures/test/_partial_with_only_html_version.html.erb +++ b/actionview/test/fixtures/test/_partial_with_only_html_version.html.erb diff --git a/actionview/test/fixtures/test/_partial_with_partial.erb b/actionview/test/fixtures/test/_partial_with_partial.erb new file mode 100644 index 0000000000..ee0d5037b6 --- /dev/null +++ b/actionview/test/fixtures/test/_partial_with_partial.erb @@ -0,0 +1,2 @@ +<%= render 'test/partial' %> +partial with partial diff --git a/actionpack/test/fixtures/test/_raise.html.erb b/actionview/test/fixtures/test/_raise.html.erb index 68b08181d3..68b08181d3 100644 --- a/actionpack/test/fixtures/test/_raise.html.erb +++ b/actionview/test/fixtures/test/_raise.html.erb diff --git a/actionview/test/fixtures/test/_raise_indentation.html.erb b/actionview/test/fixtures/test/_raise_indentation.html.erb new file mode 100644 index 0000000000..f9a93728fe --- /dev/null +++ b/actionview/test/fixtures/test/_raise_indentation.html.erb @@ -0,0 +1,13 @@ +<p>First paragraph</p> +<p>Second paragraph</p> +<p>Third paragraph</p> +<p>Fourth paragraph</p> +<p>Fifth paragraph</p> +<p>Sixth paragraph</p> +<p>Seventh paragraph</p> +<p>Eight paragraph</p> +<p>Ninth paragraph</p> +<p>Tenth paragraph</p> +<%= raise "error here!" %> +<p>Eleventh paragraph</p> +<p>Twelfth paragraph</p>
\ No newline at end of file diff --git a/actionview/test/fixtures/test/_second_json_partial.json.erb b/actionview/test/fixtures/test/_second_json_partial.json.erb new file mode 100644 index 0000000000..5ebb7f1afd --- /dev/null +++ b/actionview/test/fixtures/test/_second_json_partial.json.erb @@ -0,0 +1 @@ +Third level
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/_two.html.erb b/actionview/test/fixtures/test/_two.html.erb index 5ab2f8a432..5ab2f8a432 100644 --- a/actionpack/test/fixtures/test/_two.html.erb +++ b/actionview/test/fixtures/test/_two.html.erb diff --git a/actionpack/test/fixtures/test/_utf8_partial.html.erb b/actionview/test/fixtures/test/_utf8_partial.html.erb index 8d717fd427..8d717fd427 100644 --- a/actionpack/test/fixtures/test/_utf8_partial.html.erb +++ b/actionview/test/fixtures/test/_utf8_partial.html.erb diff --git a/actionpack/test/fixtures/test/_utf8_partial_magic.html.erb b/actionview/test/fixtures/test/_utf8_partial_magic.html.erb index 4e2224610a..4e2224610a 100644 --- a/actionpack/test/fixtures/test/_utf8_partial_magic.html.erb +++ b/actionview/test/fixtures/test/_utf8_partial_magic.html.erb diff --git a/actionpack/test/fixtures/test/basic.html.erb b/actionview/test/fixtures/test/basic.html.erb index ea696d7e01..ea696d7e01 100644 --- a/actionpack/test/fixtures/test/basic.html.erb +++ b/actionview/test/fixtures/test/basic.html.erb diff --git a/actionview/test/fixtures/test/calling_partial_with_layout.html.erb b/actionview/test/fixtures/test/calling_partial_with_layout.html.erb new file mode 100644 index 0000000000..ac44bc0d81 --- /dev/null +++ b/actionview/test/fixtures/test/calling_partial_with_layout.html.erb @@ -0,0 +1 @@ +<%= render(:layout => "layout_for_partial", :partial => "partial_for_use_in_layout", :locals => { :name => "David" }) %>
\ No newline at end of file diff --git a/actionview/test/fixtures/test/change_priority.html.erb b/actionview/test/fixtures/test/change_priority.html.erb new file mode 100644 index 0000000000..5618977d05 --- /dev/null +++ b/actionview/test/fixtures/test/change_priority.html.erb @@ -0,0 +1,2 @@ +<%= render :partial => "test/json_change_priority", formats: :json %> +HTML Template, but <%= render :partial => "test/changing_priority" %> partial
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/dont_pick_me b/actionview/test/fixtures/test/dont_pick_me index 0157c9e503..0157c9e503 100644 --- a/actionpack/test/fixtures/test/dont_pick_me +++ b/actionview/test/fixtures/test/dont_pick_me diff --git a/actionview/test/fixtures/test/dot.directory/render_file_with_ivar.erb b/actionview/test/fixtures/test/dot.directory/render_file_with_ivar.erb new file mode 100644 index 0000000000..8b8a449236 --- /dev/null +++ b/actionview/test/fixtures/test/dot.directory/render_file_with_ivar.erb @@ -0,0 +1 @@ +The secret is <%= @secret %> diff --git a/actionview/test/fixtures/test/greeting.xml.erb b/actionview/test/fixtures/test/greeting.xml.erb new file mode 100644 index 0000000000..62fb0293f0 --- /dev/null +++ b/actionview/test/fixtures/test/greeting.xml.erb @@ -0,0 +1 @@ +<p>This is grand!</p> diff --git a/actionview/test/fixtures/test/hello.builder b/actionview/test/fixtures/test/hello.builder new file mode 100644 index 0000000000..a471553941 --- /dev/null +++ b/actionview/test/fixtures/test/hello.builder @@ -0,0 +1,4 @@ +xml.html do + xml.p "Hello #{@name}" + xml << render(:file => "test/greeting") +end
\ No newline at end of file diff --git a/actionview/test/fixtures/test/hello/hello.erb b/actionview/test/fixtures/test/hello/hello.erb new file mode 100644 index 0000000000..6769dd60bd --- /dev/null +++ b/actionview/test/fixtures/test/hello/hello.erb @@ -0,0 +1 @@ +Hello world!
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/hello_world.da.html.erb b/actionview/test/fixtures/test/hello_world.da.html.erb index 10ec443291..10ec443291 100644 --- a/actionpack/test/fixtures/test/hello_world.da.html.erb +++ b/actionview/test/fixtures/test/hello_world.da.html.erb diff --git a/actionview/test/fixtures/test/hello_world.erb b/actionview/test/fixtures/test/hello_world.erb new file mode 100644 index 0000000000..6769dd60bd --- /dev/null +++ b/actionview/test/fixtures/test/hello_world.erb @@ -0,0 +1 @@ +Hello world!
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/hello_world.erb~ b/actionview/test/fixtures/test/hello_world.erb~ index 21934a1c95..21934a1c95 100644 --- a/actionpack/test/fixtures/test/hello_world.erb~ +++ b/actionview/test/fixtures/test/hello_world.erb~ diff --git a/actionpack/test/fixtures/test/hello_world.pt-BR.html.erb b/actionview/test/fixtures/test/hello_world.pt-BR.html.erb index 773b3c8c6e..773b3c8c6e 100644 --- a/actionpack/test/fixtures/test/hello_world.pt-BR.html.erb +++ b/actionview/test/fixtures/test/hello_world.pt-BR.html.erb diff --git a/actionview/test/fixtures/test/hello_world_with_partial.html.erb b/actionview/test/fixtures/test/hello_world_with_partial.html.erb new file mode 100644 index 0000000000..ec31545356 --- /dev/null +++ b/actionview/test/fixtures/test/hello_world_with_partial.html.erb @@ -0,0 +1,2 @@ +Hello world! +<%= render '/test/partial' %> diff --git a/actionview/test/fixtures/test/html_template.html.erb b/actionview/test/fixtures/test/html_template.html.erb new file mode 100644 index 0000000000..1bbc2b7f09 --- /dev/null +++ b/actionview/test/fixtures/test/html_template.html.erb @@ -0,0 +1 @@ +<%= render :partial => "test/first_json_partial", formats: :json %>
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/layout_render_file.erb b/actionview/test/fixtures/test/layout_render_file.erb index 2f8e921c5f..2f8e921c5f 100644 --- a/actionpack/test/fixtures/test/layout_render_file.erb +++ b/actionview/test/fixtures/test/layout_render_file.erb diff --git a/actionpack/test/fixtures/test/layout_render_object.erb b/actionview/test/fixtures/test/layout_render_object.erb index acc4453c08..acc4453c08 100644 --- a/actionpack/test/fixtures/test/layout_render_object.erb +++ b/actionview/test/fixtures/test/layout_render_object.erb diff --git a/actionview/test/fixtures/test/list.erb b/actionview/test/fixtures/test/list.erb new file mode 100644 index 0000000000..0a4bda58ee --- /dev/null +++ b/actionview/test/fixtures/test/list.erb @@ -0,0 +1 @@ +<%= @test_unchanged = 'goodbye' %><%= render :partial => 'customer', :collection => @customers %><%= @test_unchanged %> diff --git a/actionpack/test/fixtures/test/malformed/malformed.en.html.erb~ b/actionview/test/fixtures/test/malformed/malformed.en.html.erb~ index d009950384..d009950384 100644 --- a/actionpack/test/fixtures/test/malformed/malformed.en.html.erb~ +++ b/actionview/test/fixtures/test/malformed/malformed.en.html.erb~ diff --git a/actionpack/test/fixtures/test/malformed/malformed.erb~ b/actionview/test/fixtures/test/malformed/malformed.erb~ index d009950384..d009950384 100644 --- a/actionpack/test/fixtures/test/malformed/malformed.erb~ +++ b/actionview/test/fixtures/test/malformed/malformed.erb~ diff --git a/actionpack/test/fixtures/test/malformed/malformed.html.erb~ b/actionview/test/fixtures/test/malformed/malformed.html.erb~ index d009950384..d009950384 100644 --- a/actionpack/test/fixtures/test/malformed/malformed.html.erb~ +++ b/actionview/test/fixtures/test/malformed/malformed.html.erb~ diff --git a/actionview/test/fixtures/test/malformed/malformed~ b/actionview/test/fixtures/test/malformed/malformed~ new file mode 100644 index 0000000000..d009950384 --- /dev/null +++ b/actionview/test/fixtures/test/malformed/malformed~ @@ -0,0 +1 @@ +Don't render me!
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/nested_layout.erb b/actionview/test/fixtures/test/nested_layout.erb index 6078f74b4c..6078f74b4c 100644 --- a/actionpack/test/fixtures/test/nested_layout.erb +++ b/actionview/test/fixtures/test/nested_layout.erb diff --git a/actionpack/test/fixtures/test/nested_streaming.erb b/actionview/test/fixtures/test/nested_streaming.erb index 55525e0c92..55525e0c92 100644 --- a/actionpack/test/fixtures/test/nested_streaming.erb +++ b/actionview/test/fixtures/test/nested_streaming.erb diff --git a/actionpack/test/fixtures/test/one.html.erb b/actionview/test/fixtures/test/one.html.erb index 0151874809..0151874809 100644 --- a/actionpack/test/fixtures/test/one.html.erb +++ b/actionview/test/fixtures/test/one.html.erb diff --git a/actionview/test/fixtures/test/render_file_with_ivar.erb b/actionview/test/fixtures/test/render_file_with_ivar.erb new file mode 100644 index 0000000000..8b8a449236 --- /dev/null +++ b/actionview/test/fixtures/test/render_file_with_ivar.erb @@ -0,0 +1 @@ +The secret is <%= @secret %> diff --git a/actionview/test/fixtures/test/render_file_with_locals.erb b/actionview/test/fixtures/test/render_file_with_locals.erb new file mode 100644 index 0000000000..ebe09faee6 --- /dev/null +++ b/actionview/test/fixtures/test/render_file_with_locals.erb @@ -0,0 +1 @@ +The secret is <%= secret %> diff --git a/actionview/test/fixtures/test/render_file_with_locals_and_default.erb b/actionview/test/fixtures/test/render_file_with_locals_and_default.erb new file mode 100644 index 0000000000..9b4900acc5 --- /dev/null +++ b/actionview/test/fixtures/test/render_file_with_locals_and_default.erb @@ -0,0 +1 @@ +<%= secret ||= 'one' %>
\ No newline at end of file diff --git a/actionview/test/fixtures/test/render_partial_inside_directory.html.erb b/actionview/test/fixtures/test/render_partial_inside_directory.html.erb new file mode 100644 index 0000000000..1461b95186 --- /dev/null +++ b/actionview/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/actionview/test/fixtures/test/render_two_partials.html.erb b/actionview/test/fixtures/test/render_two_partials.html.erb new file mode 100644 index 0000000000..3db6025860 --- /dev/null +++ b/actionview/test/fixtures/test/render_two_partials.html.erb @@ -0,0 +1,2 @@ +<%= render :partial => 'partial', :locals => {'first' => '1'} %> +<%= render :partial => 'partial', :locals => {'second' => '2'} %> diff --git a/actionpack/test/fixtures/test/streaming.erb b/actionview/test/fixtures/test/streaming.erb index fb9b8b1ade..fb9b8b1ade 100644 --- a/actionpack/test/fixtures/test/streaming.erb +++ b/actionview/test/fixtures/test/streaming.erb diff --git a/actionpack/test/fixtures/test/streaming_buster.erb b/actionview/test/fixtures/test/streaming_buster.erb index 4221d56dcc..4221d56dcc 100644 --- a/actionpack/test/fixtures/test/streaming_buster.erb +++ b/actionview/test/fixtures/test/streaming_buster.erb diff --git a/actionpack/test/fixtures/test/sub_template_raise.html.erb b/actionview/test/fixtures/test/sub_template_raise.html.erb index f38c0bda90..f38c0bda90 100644 --- a/actionpack/test/fixtures/test/sub_template_raise.html.erb +++ b/actionview/test/fixtures/test/sub_template_raise.html.erb diff --git a/actionpack/test/fixtures/test/template.erb b/actionview/test/fixtures/test/template.erb index 785afa8f6a..785afa8f6a 100644 --- a/actionpack/test/fixtures/test/template.erb +++ b/actionview/test/fixtures/test/template.erb diff --git a/actionpack/test/fixtures/test/update_element_with_capture.erb b/actionview/test/fixtures/test/update_element_with_capture.erb index fa3ef200f9..fa3ef200f9 100644 --- a/actionpack/test/fixtures/test/update_element_with_capture.erb +++ b/actionview/test/fixtures/test/update_element_with_capture.erb diff --git a/actionpack/test/fixtures/test/utf8.html.erb b/actionview/test/fixtures/test/utf8.html.erb index ac98c2f012..ac98c2f012 100644 --- a/actionpack/test/fixtures/test/utf8.html.erb +++ b/actionview/test/fixtures/test/utf8.html.erb diff --git a/actionpack/test/fixtures/test/utf8_magic.html.erb b/actionview/test/fixtures/test/utf8_magic.html.erb index 257279c29f..257279c29f 100644 --- a/actionpack/test/fixtures/test/utf8_magic.html.erb +++ b/actionview/test/fixtures/test/utf8_magic.html.erb diff --git a/actionpack/test/fixtures/test/utf8_magic_with_bare_partial.html.erb b/actionview/test/fixtures/test/utf8_magic_with_bare_partial.html.erb index cb22692f9a..cb22692f9a 100644 --- a/actionpack/test/fixtures/test/utf8_magic_with_bare_partial.html.erb +++ b/actionview/test/fixtures/test/utf8_magic_with_bare_partial.html.erb diff --git a/actionpack/test/fixtures/topic.rb b/actionview/test/fixtures/topic.rb index 9fa9746535..9fa9746535 100644 --- a/actionpack/test/fixtures/topic.rb +++ b/actionview/test/fixtures/topic.rb diff --git a/actionpack/test/fixtures/topics.yml b/actionview/test/fixtures/topics.yml index 7fdd49d54e..7fdd49d54e 100644 --- a/actionpack/test/fixtures/topics.yml +++ b/actionview/test/fixtures/topics.yml diff --git a/actionpack/test/fixtures/topics/_topic.html.erb b/actionview/test/fixtures/topics/_topic.html.erb index 98659ca098..98659ca098 100644 --- a/actionpack/test/fixtures/topics/_topic.html.erb +++ b/actionview/test/fixtures/topics/_topic.html.erb diff --git a/actionpack/test/fixtures/translations/templates/array.erb b/actionview/test/fixtures/translations/templates/array.erb index d86045a172..d86045a172 100644 --- a/actionpack/test/fixtures/translations/templates/array.erb +++ b/actionview/test/fixtures/translations/templates/array.erb diff --git a/actionpack/test/fixtures/translations/templates/default.erb b/actionview/test/fixtures/translations/templates/default.erb index 8b70031071..8b70031071 100644 --- a/actionpack/test/fixtures/translations/templates/default.erb +++ b/actionview/test/fixtures/translations/templates/default.erb diff --git a/actionpack/test/fixtures/translations/templates/found.erb b/actionview/test/fixtures/translations/templates/found.erb index 080c9c0aee..080c9c0aee 100644 --- a/actionpack/test/fixtures/translations/templates/found.erb +++ b/actionview/test/fixtures/translations/templates/found.erb diff --git a/actionpack/test/fixtures/translations/templates/missing.erb b/actionview/test/fixtures/translations/templates/missing.erb index 0f3f17f8ef..0f3f17f8ef 100644 --- a/actionpack/test/fixtures/translations/templates/missing.erb +++ b/actionview/test/fixtures/translations/templates/missing.erb diff --git a/actionpack/test/fixtures/with_format.json.erb b/actionview/test/fixtures/with_format.json.erb index a7f480ab1d..a7f480ab1d 100644 --- a/actionpack/test/fixtures/with_format.json.erb +++ b/actionview/test/fixtures/with_format.json.erb diff --git a/actionview/test/lib/controller/fake_controllers.rb b/actionview/test/lib/controller/fake_controllers.rb new file mode 100644 index 0000000000..1a2863b689 --- /dev/null +++ b/actionview/test/lib/controller/fake_controllers.rb @@ -0,0 +1,35 @@ +class ContentController < ActionController::Base; end + +module Admin + class AccountsController < ActionController::Base; end + class PostsController < ActionController::Base; end + class StuffController < ActionController::Base; end + class UserController < ActionController::Base; end + class UsersController < ActionController::Base; end +end + +module Api + class UsersController < ActionController::Base; end + class ProductsController < ActionController::Base; end +end + +class AccountController < ActionController::Base; end +class ArchiveController < ActionController::Base; end +class ArticlesController < ActionController::Base; end +class BarController < ActionController::Base; end +class BlogController < ActionController::Base; end +class BooksController < ActionController::Base; end +class CarsController < ActionController::Base; end +class CcController < ActionController::Base; end +class CController < ActionController::Base; end +class FooController < ActionController::Base; end +class GeocodeController < ActionController::Base; end +class NewsController < ActionController::Base; end +class NotesController < ActionController::Base; end +class PagesController < ActionController::Base; end +class PeopleController < ActionController::Base; end +class PostsController < ActionController::Base; end +class SubpathBooksController < ActionController::Base; end +class SymbolsController < ActionController::Base; end +class UserController < ActionController::Base; end +class UsersController < ActionController::Base; end diff --git a/actionview/test/lib/controller/fake_models.rb b/actionview/test/lib/controller/fake_models.rb new file mode 100644 index 0000000000..a463a08bb6 --- /dev/null +++ b/actionview/test/lib/controller/fake_models.rb @@ -0,0 +1,185 @@ +require "active_model" + +class Customer < Struct.new(:name, :id) + extend ActiveModel::Naming + include ActiveModel::Conversion + + undef_method :to_json + + def to_xml(options={}) + if options[:builder] + options[:builder].name name + else + "<name>#{name}</name>" + end + end + + def to_js(options={}) + "name: #{name.inspect}" + end + alias :to_text :to_js + + def errors + [] + end + + def persisted? + id.present? + end +end + +class GoodCustomer < Customer +end + +class Post < Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost) + extend ActiveModel::Naming + include ActiveModel::Conversion + extend ActiveModel::Translation + + alias_method :secret?, :secret + alias_method :persisted?, :persisted + + def initialize(*args) + super + @persisted = false + end + + attr_accessor :author + def author_attributes=(attributes); end + + attr_accessor :comments, :comment_ids + def comments_attributes=(attributes); end + + attr_accessor :tags + def tags_attributes=(attributes); end +end + +class Comment + extend ActiveModel::Naming + include ActiveModel::Conversion + + attr_reader :id + attr_reader :post_id + def initialize(id = nil, post_id = nil); @id, @post_id = id, post_id end + def to_key; id ? [id] : nil end + def save; @id = 1; @post_id = 1 end + def persisted?; @id.present? end + def to_param; @id.to_s; end + def name + @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}" + end + + attr_accessor :relevances + def relevances_attributes=(attributes); end + + attr_accessor :body +end + +class Tag + extend ActiveModel::Naming + include ActiveModel::Conversion + + attr_reader :id + attr_reader :post_id + def initialize(id = nil, post_id = nil); @id, @post_id = id, post_id end + def to_key; id ? [id] : nil end + def save; @id = 1; @post_id = 1 end + def persisted?; @id.present? end + def to_param; @id; end + def value + @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}" + end + + attr_accessor :relevances + def relevances_attributes=(attributes); end + +end + +class CommentRelevance + extend ActiveModel::Naming + include ActiveModel::Conversion + + attr_reader :id + attr_reader :comment_id + def initialize(id = nil, comment_id = nil); @id, @comment_id = id, comment_id end + def to_key; id ? [id] : nil end + def save; @id = 1; @comment_id = 1 end + def persisted?; @id.present? end + def to_param; @id; end + def value + @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}" + end +end + +class Sheep + extend ActiveModel::Naming + include ActiveModel::Conversion + + attr_reader :id + def to_key; id ? [id] : nil end + def save; @id = 1 end + def new_record?; @id.nil? end + def name + @id.nil? ? 'new sheep' : "sheep ##{@id}" + end +end + +class TagRelevance + extend ActiveModel::Naming + include ActiveModel::Conversion + + attr_reader :id + attr_reader :tag_id + def initialize(id = nil, tag_id = nil); @id, @tag_id = id, tag_id end + def to_key; id ? [id] : nil end + def save; @id = 1; @tag_id = 1 end + def persisted?; @id.present? end + def to_param; @id; end + def value + @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}" + end +end + +class Author < Comment + attr_accessor :post + def post_attributes=(attributes); end +end + +class HashBackedAuthor < Hash + extend ActiveModel::Naming + include ActiveModel::Conversion + + def persisted?; false; end + + def name + "hash backed author" + end +end + +module Blog + def self.use_relative_model_naming? + true + end + + class Post < Struct.new(:title, :id) + extend ActiveModel::Naming + include ActiveModel::Conversion + + def persisted? + id.present? + end + end +end + +class ArelLike + def to_ary + true + end + def each + a = Array.new(2) { |id| Comment.new(id + 1) } + a.each { |i| yield i } + end +end + +class Car < Struct.new(:color) +end diff --git a/actionpack/test/template/active_model_helper_test.rb b/actionview/test/template/active_model_helper_test.rb index 86bccdfade..86bccdfade 100644 --- a/actionpack/test/template/active_model_helper_test.rb +++ b/actionview/test/template/active_model_helper_test.rb diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb index 8d0ab7fd81..214a13654e 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionview/test/template/asset_tag_helper_test.rb @@ -48,6 +48,9 @@ class AssetTagHelperTest < ActionView::TestCase %(asset_path("style.min")) => %(/style.min), %(asset_path("style.min.css")) => %(/style.min.css), + %(asset_path("http://www.outside.com/image.jpg")) => %(http://www.outside.com/image.jpg), + %(asset_path("HTTP://www.outside.com/image.jpg")) => %(HTTP://www.outside.com/image.jpg), + %(asset_path("style", type: :stylesheet)) => %(/stylesheets/style.css), %(asset_path("xmlhr", type: :javascript)) => %(/javascripts/xmlhr.js), %(asset_path("xml.png", type: :image)) => %(/images/xml.png) @@ -445,8 +448,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 '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") + 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/atom_feed_helper_test.rb b/actionview/test/template/atom_feed_helper_test.rb index 63b5ac0fab..63b5ac0fab 100644 --- a/actionpack/test/template/atom_feed_helper_test.rb +++ b/actionview/test/template/atom_feed_helper_test.rb diff --git a/actionpack/test/template/capture_helper_test.rb b/actionview/test/template/capture_helper_test.rb index 938f1c3e54..938f1c3e54 100644 --- a/actionpack/test/template/capture_helper_test.rb +++ b/actionview/test/template/capture_helper_test.rb diff --git a/actionpack/test/template/compiled_templates_test.rb b/actionview/test/template/compiled_templates_test.rb index f5dc2fbb33..2336321f3e 100644 --- a/actionpack/test/template/compiled_templates_test.rb +++ b/actionview/test/template/compiled_templates_test.rb @@ -1,5 +1,4 @@ require 'abstract_unit' -require 'controller/fake_models' class CompiledTemplatesTest < ActiveSupport::TestCase def setup diff --git a/actionpack/test/template/date_helper_i18n_test.rb b/actionview/test/template/date_helper_i18n_test.rb index 21fca35185..21fca35185 100644 --- a/actionpack/test/template/date_helper_i18n_test.rb +++ b/actionview/test/template/date_helper_i18n_test.rb diff --git a/actionpack/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb index 242b56a1fd..242b56a1fd 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionview/test/template/date_helper_test.rb diff --git a/actionpack/test/template/debug_helper_test.rb b/actionview/test/template/debug_helper_test.rb index 42d06bd9ff..42d06bd9ff 100644 --- a/actionpack/test/template/debug_helper_test.rb +++ b/actionview/test/template/debug_helper_test.rb diff --git a/actionpack/test/template/dependency_tracker_test.rb b/actionview/test/template/dependency_tracker_test.rb index 8588925de3..7a9b4b26ac 100644 --- a/actionpack/test/template/dependency_tracker_test.rb +++ b/actionview/test/template/dependency_tracker_test.rb @@ -45,7 +45,7 @@ class DependencyTrackerTest < ActionView::TestCase end end -class ERBTrackerTest < MiniTest::Unit::TestCase +class ERBTrackerTest < Minitest::Test def make_tracker(name, template) ActionView::DependencyTracker::ERBTracker.new(name, template) end diff --git a/actionpack/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb index 06735c30d3..67e3775f28 100644 --- a/actionpack/test/template/digestor_test.rb +++ b/actionview/test/template/digestor_test.rb @@ -95,6 +95,31 @@ class TemplateDigestorTest < ActionView::TestCase end end + def test_recursion_in_renders + assert digest("level/recursion") # assert recursion is possible + assert_not_nil digest("level/recursion") # assert digest is stored + end + + def test_chaining_the_top_template_on_recursion + assert digest("level/recursion") # assert recursion is possible + + assert_digest_difference("level/recursion") do + change_template("level/recursion") + end + + assert_not_nil digest("level/recursion") # assert digest is stored + end + + def test_chaining_the_partial_template_on_recursion + assert digest("level/recursion") # assert recursion is possible + + assert_digest_difference("level/recursion") do + change_template("level/_recursion") + end + + assert_not_nil digest("level/recursion") # assert digest is stored + end + def test_dont_generate_a_digest_for_missing_templates assert_equal '', digest("nothing/there") end diff --git a/actionpack/test/template/erb/form_for_test.rb b/actionview/test/template/erb/form_for_test.rb index e722b40a9a..e722b40a9a 100644 --- a/actionpack/test/template/erb/form_for_test.rb +++ b/actionview/test/template/erb/form_for_test.rb diff --git a/actionpack/test/template/erb/helper.rb b/actionview/test/template/erb/helper.rb index a1973068d5..a1973068d5 100644 --- a/actionpack/test/template/erb/helper.rb +++ b/actionview/test/template/erb/helper.rb diff --git a/actionpack/test/template/erb/tag_helper_test.rb b/actionview/test/template/erb/tag_helper_test.rb index 84e328d8be..84e328d8be 100644 --- a/actionpack/test/template/erb/tag_helper_test.rb +++ b/actionview/test/template/erb/tag_helper_test.rb diff --git a/actionpack/test/template/erb_util_test.rb b/actionview/test/template/erb_util_test.rb index 3e5b029cea..3e5b029cea 100644 --- a/actionpack/test/template/erb_util_test.rb +++ b/actionview/test/template/erb_util_test.rb diff --git a/actionpack/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb index 2131f81396..bc9c21dfd3 100644 --- a/actionpack/test/template/form_collections_helper_test.rb +++ b/actionview/test/template/form_collections_helper_test.rb @@ -76,6 +76,14 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_select 'input[type=radio][value=false].special-radio#user_active_false' end + test 'collection radio accepts html options as the last element of array' do + collection = [[1, true, {class: 'foo'}], [0, false, {class: 'bar'}]] + with_collection_radio_buttons :user, :active, collection, :second, :first + + assert_select 'input[type=radio][value=true].foo#user_active_true' + assert_select 'input[type=radio][value=false].bar#user_active_false' + end + test 'collection radio does not wrap input inside the label' do with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s @@ -192,6 +200,14 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_select 'label[for=user_name_199]', '$1.99' end + test 'collection check boxes accepts html options as the last element of array' do + collection = [[1, 'Category 1', {class: 'foo'}], [2, 'Category 2', {class: 'bar'}]] + with_collection_check_boxes :user, :active, collection, :first, :second + + assert_select 'input[type=checkbox][value=1].foo' + assert_select 'input[type=checkbox][value=2].bar' + end + test 'collection check boxes accepts selected values as :checked option' do collection = (1..3).map{|i| [i, "Category #{i}"] } with_collection_check_boxes :user, :category_ids, collection, :first, :last, :checked => [1, 3] diff --git a/actionpack/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb index 1ff320224d..8cca43d7ca 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionview/test/template/form_helper_test.rb @@ -676,6 +676,13 @@ class FormHelperTest < ActionView::TestCase ) end + def test_text_area_with_nil_alternate_value + assert_dom_equal( + %{<textarea id="post_body" name="post[body]">\n</textarea>}, + text_area("post", "body", value: nil) + ) + end + def test_text_area_with_html_entities @post.body = "The HTML Entity for & is &" assert_dom_equal( @@ -702,6 +709,11 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal(expected, color_field("car", "color")) end + def test_color_field_with_value_attr + expected = %{<input id="car_color" name="car[color]" type="color" value="#00FF00" />} + assert_dom_equal(expected, color_field("car", "color", value: "#00FF00")) + end + def test_search_field expected = %{<input id="contact_notes_query" name="contact[notes_query]" type="search" />} assert_dom_equal(expected, search_field("contact", "notes_query")) @@ -732,6 +744,12 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal(expected, date_field("post", "written_on", min: min_value, max: max_value, step: step)) end + def test_date_field_with_value_attr + expected = %{<input id="post_written_on" name="post[written_on]" type="date" value="2013-06-29" />} + value = Date.new(2013,6,29) + assert_dom_equal(expected, date_field("post", "written_on", value: value)) + end + def test_date_field_with_timewithzone_value previous_time_zone, Time.zone = Time.zone, 'UTC' expected = %{<input id="post_written_on" name="post[written_on]" type="date" value="2004-06-15" />} @@ -802,6 +820,12 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal(expected, datetime_field("post", "written_on", min: min_value, max: max_value, step: step)) end + def test_datetime_field_with_value_attr + expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2013-06-29T13:37:00+00:00" />} + value = DateTime.new(2013,6,29,13,37) + assert_dom_equal(expected, datetime_field("post", "written_on", value: value)) + end + def test_datetime_field_with_timewithzone_value previous_time_zone, Time.zone = Time.zone, 'UTC' expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2004-06-15T15:30:45.000+0000" />} @@ -2899,18 +2923,6 @@ class FormHelperTest < ActionView::TestCase assert_equal "fields", output end - def test_form_builder_block_argument_deprecation - builder_class = Class.new(ActionView::Helpers::FormBuilder) do - def initialize(object_name, object, template, options, block) - super - end - end - - assert_deprecated(/Giving a block to FormBuilder is deprecated and has no effect anymore/) do - builder_class.new(:foo, nil, nil, {}, proc {}) - end - end - def test_form_for_only_instantiates_builder_once initialization_count = 0 builder_class = Class.new(ActionView::Helpers::FormBuilder) do diff --git a/actionpack/test/template/form_options_helper_i18n_test.rb b/actionview/test/template/form_options_helper_i18n_test.rb index 4972ea6511..4972ea6511 100644 --- a/actionpack/test/template/form_options_helper_i18n_test.rb +++ b/actionview/test/template/form_options_helper_i18n_test.rb diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb index 1715902927..3ec138b639 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionview/test/template/form_options_helper_test.rb @@ -302,6 +302,16 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_grouped_options_for_select_with_array_and_html_attributes + assert_dom_equal( + "<optgroup label=\"North America\" data-foo=\"bar\"><option value=\"US\">United States</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"Europe\" disabled=\"disabled\"><option value=\"GB\">Great Britain</option>\n<option value=\"Germany\">Germany</option></optgroup>", + grouped_options_for_select([ + ["North America", [['United States','US'],"Canada"], :data => { :foo => 'bar' }], + ["Europe", [["Great Britain","GB"], "Germany"], :disabled => 'disabled'] + ]) + ) + end + def test_grouped_options_for_select_with_optional_divider assert_dom_equal( "<optgroup label=\"----------\"><option value=\"US\">US</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"----------\"><option value=\"GB\">GB</option>\n<option value=\"Germany\">Germany</option></optgroup>", @@ -310,15 +320,6 @@ class FormOptionsHelperTest < ActionView::TestCase ) end - def test_grouped_options_for_select_with_selected_and_prompt_deprecated - assert_deprecated 'Passing the prompt to grouped_options_for_select as an argument is deprecated. Please use an options hash like `{ prompt: "Choose a product..." }`.' do - assert_dom_equal( - "<option value=\"\">Choose a product...</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option selected=\"selected\" value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>", - grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], "Cowboy Hat", "Choose a product...") - ) - end - end - def test_grouped_options_for_select_with_selected_and_prompt assert_dom_equal( "<option value=\"\">Choose a product...</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option selected=\"selected\" value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>", @@ -337,14 +338,6 @@ class FormOptionsHelperTest < ActionView::TestCase assert grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]]).html_safe? end - def test_grouped_options_for_select_with_prompt_returns_html_escaped_string_deprecated - ActiveSupport::Deprecation.silence do - assert_dom_equal( - "<option value=\"\"><Choose One></option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>", - grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], nil, '<Choose One>')) - end - end - def test_grouped_options_for_select_with_prompt_returns_html_escaped_string assert_dom_equal( "<option value=\"\"><Choose One></option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>", diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb index 70fc6a588b..22bf438a56 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionview/test/template/form_tag_helper_test.rb @@ -12,9 +12,10 @@ class FormTagHelperTest < ActionView::TestCase def hidden_fields(options = {}) method = options[:method] + enforce_utf8 = options.fetch(:enforce_utf8, true) txt = %{<div style="margin:0;padding:0;display:inline">} - txt << %{<input name="utf8" type="hidden" value="✓" />} + txt << %{<input name="utf8" type="hidden" value="✓" />} if enforce_utf8 if method && !%w(get post).include?(method.to_s) txt << %{<input name="_method" type="hidden" value="#{method}" />} end @@ -110,6 +111,20 @@ class FormTagHelperTest < ActionView::TestCase assert_dom_equal expected, actual end + def test_form_tag_enforce_utf8_true + actual = form_tag({}, { :enforce_utf8 => true }) + expected = whole_form("http://www.example.com", :enforce_utf8 => true) + assert_dom_equal expected, actual + assert actual.html_safe? + end + + def test_form_tag_enforce_utf8_false + actual = form_tag({}, { :enforce_utf8 => false }) + expected = whole_form("http://www.example.com", :enforce_utf8 => false) + assert_dom_equal expected, actual + assert actual.html_safe? + end + def test_form_tag_with_block_in_erb output_buffer = render_erb("<%= form_tag('http://www.example.com') do %>Hello world!<% end %>") diff --git a/actionpack/test/template/html-scanner/cdata_node_test.rb b/actionview/test/template/html-scanner/cdata_node_test.rb index 9b58174641..9b58174641 100644 --- a/actionpack/test/template/html-scanner/cdata_node_test.rb +++ b/actionview/test/template/html-scanner/cdata_node_test.rb diff --git a/actionpack/test/template/html-scanner/document_test.rb b/actionview/test/template/html-scanner/document_test.rb index 17f045d549..17f045d549 100644 --- a/actionpack/test/template/html-scanner/document_test.rb +++ b/actionview/test/template/html-scanner/document_test.rb diff --git a/actionpack/test/template/html-scanner/node_test.rb b/actionview/test/template/html-scanner/node_test.rb index 5b5d092036..5b5d092036 100644 --- a/actionpack/test/template/html-scanner/node_test.rb +++ b/actionview/test/template/html-scanner/node_test.rb diff --git a/actionpack/test/template/html-scanner/sanitizer_test.rb b/actionview/test/template/html-scanner/sanitizer_test.rb index b1c1b83807..b1c1b83807 100644 --- a/actionpack/test/template/html-scanner/sanitizer_test.rb +++ b/actionview/test/template/html-scanner/sanitizer_test.rb diff --git a/actionpack/test/template/html-scanner/tag_node_test.rb b/actionview/test/template/html-scanner/tag_node_test.rb index a29d2d43d7..a29d2d43d7 100644 --- a/actionpack/test/template/html-scanner/tag_node_test.rb +++ b/actionview/test/template/html-scanner/tag_node_test.rb diff --git a/actionpack/test/template/html-scanner/text_node_test.rb b/actionview/test/template/html-scanner/text_node_test.rb index cbcb9e78f0..cbcb9e78f0 100644 --- a/actionpack/test/template/html-scanner/text_node_test.rb +++ b/actionview/test/template/html-scanner/text_node_test.rb diff --git a/actionpack/test/template/html-scanner/tokenizer_test.rb b/actionview/test/template/html-scanner/tokenizer_test.rb index 1d59de23b6..1d59de23b6 100644 --- a/actionpack/test/template/html-scanner/tokenizer_test.rb +++ b/actionview/test/template/html-scanner/tokenizer_test.rb diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionview/test/template/javascript_helper_test.rb index de6a6eaab3..de6a6eaab3 100644 --- a/actionpack/test/template/javascript_helper_test.rb +++ b/actionview/test/template/javascript_helper_test.rb diff --git a/actionpack/test/template/log_subscriber_test.rb b/actionview/test/template/log_subscriber_test.rb index 7f4c84929f..7f4c84929f 100644 --- a/actionpack/test/template/log_subscriber_test.rb +++ b/actionview/test/template/log_subscriber_test.rb diff --git a/actionpack/test/template/lookup_context_test.rb b/actionview/test/template/lookup_context_test.rb index 073bd14783..073bd14783 100644 --- a/actionpack/test/template/lookup_context_test.rb +++ b/actionview/test/template/lookup_context_test.rb diff --git a/actionpack/test/template/number_helper_test.rb b/actionview/test/template/number_helper_test.rb index 6e640889d2..6e640889d2 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionview/test/template/number_helper_test.rb diff --git a/actionpack/test/template/output_buffer_test.rb b/actionview/test/template/output_buffer_test.rb index eb0df3d1ab..eb0df3d1ab 100644 --- a/actionpack/test/template/output_buffer_test.rb +++ b/actionview/test/template/output_buffer_test.rb diff --git a/actionpack/test/template/output_safety_helper_test.rb b/actionview/test/template/output_safety_helper_test.rb index 76c71c9e6d..76c71c9e6d 100644 --- a/actionpack/test/template/output_safety_helper_test.rb +++ b/actionview/test/template/output_safety_helper_test.rb diff --git a/actionpack/test/template/record_identifier_test.rb b/actionview/test/template/record_identifier_test.rb index 22038110a5..22038110a5 100644 --- a/actionpack/test/template/record_identifier_test.rb +++ b/actionview/test/template/record_identifier_test.rb diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionview/test/template/record_tag_helper_test.rb index ab84bccb56..ab84bccb56 100644 --- a/actionpack/test/template/record_tag_helper_test.rb +++ b/actionview/test/template/record_tag_helper_test.rb diff --git a/actionpack/test/template/render_test.rb b/actionview/test/template/render_test.rb index 81f3391fcd..928dfb092d 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionview/test/template/render_test.rb @@ -41,6 +41,11 @@ module RenderTestCases assert_match "<error>No Comment</error>", @view.render(:template => "comments/empty", :formats => [:xml]) end + def test_rendered_format_without_format + @view.render(:inline => "test") + assert_equal :html, @view.lookup_context.rendered_format + end + def test_render_partial_implicitly_use_format_of_the_rendered_template @view.lookup_context.formats = [:json] assert_equal "Hello world", @view.render(:template => "test/one", :formats => [:html]) @@ -376,6 +381,7 @@ module RenderTestCases def test_render_ignores_templates_with_malformed_template_handlers ActiveSupport::Deprecation.silence do %w(malformed malformed.erb malformed.html.erb malformed.en.html.erb).each do |name| + assert File.exists?(File.expand_path("#{FIXTURE_LOAD_PATH}/test/malformed/#{name}~")), "Malformed file (#{name}~) which should be ignored does not exists" assert_raises(ActionView::MissingTemplate) { @view.render(:file => "test/malformed/#{name}") } end end diff --git a/actionpack/test/template/resolver_patterns_test.rb b/actionview/test/template/resolver_patterns_test.rb index 97b1bad055..97b1bad055 100644 --- a/actionpack/test/template/resolver_patterns_test.rb +++ b/actionview/test/template/resolver_patterns_test.rb diff --git a/actionpack/test/template/sanitize_helper_test.rb b/actionview/test/template/sanitize_helper_test.rb index 12d5260a9d..12d5260a9d 100644 --- a/actionpack/test/template/sanitize_helper_test.rb +++ b/actionview/test/template/sanitize_helper_test.rb diff --git a/actionpack/test/template/streaming_render_test.rb b/actionview/test/template/streaming_render_test.rb index 520bf3a824..8a24d78e74 100644 --- a/actionpack/test/template/streaming_render_test.rb +++ b/actionview/test/template/streaming_render_test.rb @@ -1,6 +1,5 @@ # encoding: utf-8 require 'abstract_unit' -require 'controller/fake_models' class TestController < ActionController::Base end diff --git a/actionpack/test/template/tag_helper_test.rb b/actionview/test/template/tag_helper_test.rb index 9e711c6529..802da5d566 100644 --- a/actionpack/test/template/tag_helper_test.rb +++ b/actionview/test/template/tag_helper_test.rb @@ -30,8 +30,8 @@ class TagHelperTest < ActionView::TestCase end def test_tag_options_converts_boolean_option - assert_equal '<p disabled="disabled" itemscope="itemscope" multiple="multiple" readonly="readonly" />', - tag("p", :disabled => true, :itemscope => true, :multiple => true, :readonly => true) + assert_dom_equal '<p disabled="disabled" itemscope="itemscope" multiple="multiple" readonly="readonly" allowfullscreen="allowfullscreen" seamless="seamless" typemustmatch="typemustmatch" sortable="sortable" default="default" inert="inert" truespeed="truespeed" />', + tag("p", :disabled => true, :itemscope => true, :multiple => true, :readonly => true, :allowfullscreen => true, :seamless => true, :typemustmatch => true, :sortable => true, :default => true, :inert => true, :truespeed => true) end def test_content_tag diff --git a/actionpack/test/template/template_error_test.rb b/actionview/test/template/template_error_test.rb index 91424daeed..91424daeed 100644 --- a/actionpack/test/template/template_error_test.rb +++ b/actionview/test/template/template_error_test.rb diff --git a/actionpack/test/template/template_test.rb b/actionview/test/template/template_test.rb index c94508d678..c94508d678 100644 --- a/actionpack/test/template/template_test.rb +++ b/actionview/test/template/template_test.rb diff --git a/actionpack/test/template/test_case_test.rb b/actionview/test/template/test_case_test.rb index acd002ce73..4ee0930341 100644 --- a/actionpack/test/template/test_case_test.rb +++ b/actionview/test/template/test_case_test.rb @@ -1,5 +1,4 @@ require 'abstract_unit' -require 'controller/fake_controllers' module ActionView diff --git a/actionpack/test/template/test_test.rb b/actionview/test/template/test_test.rb index 108a674d95..108a674d95 100644 --- a/actionpack/test/template/test_test.rb +++ b/actionview/test/template/test_test.rb diff --git a/actionpack/test/template/testing/fixture_resolver_test.rb b/actionview/test/template/testing/fixture_resolver_test.rb index 9649f349cb..9649f349cb 100644 --- a/actionpack/test/template/testing/fixture_resolver_test.rb +++ b/actionview/test/template/testing/fixture_resolver_test.rb diff --git a/actionpack/test/template/testing/null_resolver_test.rb b/actionview/test/template/testing/null_resolver_test.rb index 55ec36e753..55ec36e753 100644 --- a/actionpack/test/template/testing/null_resolver_test.rb +++ b/actionview/test/template/testing/null_resolver_test.rb diff --git a/actionpack/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb index 1b2234f4e2..1b2234f4e2 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionview/test/template/text_helper_test.rb diff --git a/actionpack/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb index d496dbb35e..d496dbb35e 100644 --- a/actionpack/test/template/translation_helper_test.rb +++ b/actionview/test/template/translation_helper_test.rb diff --git a/actionpack/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb index f63f235a5c..851ea8796f 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionview/test/template/url_helper_test.rb @@ -1,6 +1,5 @@ # encoding: utf-8 require 'abstract_unit' -require 'controller/fake_controllers' class UrlHelperTest < ActiveSupport::TestCase @@ -309,6 +308,13 @@ class UrlHelperTest < ActiveSupport::TestCase link_to('/', class: "special") { content_tag(:span, 'Example site') } end + def test_link_tag_using_block_and_hash + assert_dom_equal( + %{<a href="/"><span>Example site</span></a>}, + link_to(url_hash) { content_tag(:span, 'Example site') } + ) + end + def test_link_tag_using_block_in_erb out = render_erb %{<%= link_to('/') do %>Example site<% end %>} assert_equal '<a href="/">Example site</a>', out @@ -337,8 +343,6 @@ class UrlHelperTest < ActiveSupport::TestCase assert_dom_equal %{<a href="/">Listing</a>}, link_to_unless(false, "Listing", url_hash) - assert_equal "Showing", link_to_unless(true, "Showing", url_hash) - assert_equal "<strong>Showing</strong>", link_to_unless(true, "Showing", url_hash) { |name| "<strong>#{name}</strong>".html_safe @@ -348,12 +352,16 @@ class UrlHelperTest < ActiveSupport::TestCase link_to_unless(true, "Showing", url_hash) { "test" } + + assert_equal %{<b>Showing</b>}, link_to_unless(true, "<b>Showing</b>", url_hash) + assert_equal %{<a href="/"><b>Showing</b></a>}, link_to_unless(false, "<b>Showing</b>", url_hash) + assert_equal %{<b>Showing</b>}, link_to_unless(true, "<b>Showing</b>".html_safe, url_hash) + assert_equal %{<a href="/"><b>Showing</b></a>}, link_to_unless(false, "<b>Showing</b>".html_safe, url_hash) end def test_link_to_if assert_equal "Showing", link_to_if(false, "Showing", url_hash) assert_dom_equal %{<a href="/">Listing</a>}, link_to_if(true, "Listing", url_hash) - assert_equal "Showing", link_to_if(false, "Showing", url_hash) end def request_for_url(url, opts = {}) diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 6fc34ecd60..3d3c61ed1c 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,17 @@ +* Fix has_secure_password. `password_confirmation` validations are triggered + even if no `password_confirmation` is set. + + *Vladimir Kiselev* + +* `inclusion` / `exclusion` validations with ranges will only use the faster + `Range#cover` for numerical ranges, and the more accurate `Range#include?` + for non-numerical ones. + + Fixes range validations like `:a..:f` that used to pass with values like `:be`. + Fixes #10593 + + *Charles Bergeron* + * Fix regression in has_secure_password. When a password is set, but a confirmation is an empty string, it would incorrectly save. diff --git a/activemodel/Rakefile b/activemodel/Rakefile index 45d1587ed6..407dda2ec3 100644 --- a/activemodel/Rakefile +++ b/activemodel/Rakefile @@ -13,9 +13,8 @@ end namespace :test do task :isolated do - ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) Dir.glob("#{dir}/test/**/*_test.rb").all? do |file| - sh(ruby, '-w', "-I#{dir}/lib", "-I#{dir}/test", file) + sh(Gem.ruby, '-w', "-I#{dir}/lib", "-I#{dir}/test", file) end or raise "Failures" end end @@ -28,7 +27,7 @@ Gem::PackageTask.new(spec) do |p| p.gem_spec = spec end -desc "Release to gemcutter" +desc "Release to rubygems" task :release => :package do require 'rake/gemcutter' Rake::Gemcutter::Tasks.new(spec).define diff --git a/activemodel/examples/validations.rb b/activemodel/examples/validations.rb index a56ec4db39..c94cd17e18 100644 --- a/activemodel/examples/validations.rb +++ b/activemodel/examples/validations.rb @@ -4,7 +4,7 @@ class Person include ActiveModel::Conversion include ActiveModel::Validations - validates_presence_of :name + validates :name, presence: true attr_accessor :name @@ -25,5 +25,5 @@ person1 = Person.new p person1.valid? # => false p person1.errors.messages # => {:name=>["can't be blank"]} -person2 = Person.new(:name => "matz") +person2 = Person.new(name: 'matz') p person2.valid? # => true diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index 3bd5531356..ef4f2514be 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -37,7 +37,6 @@ module ActiveModel autoload :ForbiddenAttributesProtection autoload :Lint autoload :Model - autoload :DeprecatedMassAssignmentSecurity autoload :Name, 'active_model/naming' autoload :Naming autoload :SecurePassword diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 98cde8ba59..f336c759d2 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -1,4 +1,5 @@ require 'thread_safe' +require 'mutex_m' module ActiveModel # Raised when an attribute is not defined. @@ -218,6 +219,16 @@ module ActiveModel end end + # Is +new_name+ an alias? + def attribute_alias?(new_name) + attribute_aliases.key? new_name.to_s + end + + # Returns the original name for the alias +name+ + def attribute_alias(name) + attribute_aliases[name.to_s] + end + # Declares the attributes that should be prefixed and suffixed by # ActiveModel::AttributeMethods. # @@ -322,9 +333,10 @@ module ActiveModel attribute_method_matchers_cache.clear end - # Returns true if the attribute methods defined have been generated. def generated_attribute_methods #:nodoc: - @generated_attribute_methods ||= Module.new.tap { |mod| include mod } + @generated_attribute_methods ||= Module.new { + extend Mutex_m + }.tap { |mod| include mod } end protected @@ -388,14 +400,6 @@ module ActiveModel AttributeMethodMatch = Struct.new(:target, :attr_name, :method_name) def initialize(options = {}) - if options[:prefix] == '' || options[:suffix] == '' - message = "Specifying an empty prefix/suffix for an attribute method is no longer " \ - "necessary. If the un-prefixed/suffixed version of the method has not been " \ - "defined when `define_attribute_methods` is called, it will be defined " \ - "automatically." - ActiveSupport::Deprecation.warn message - end - @prefix, @suffix = options.fetch(:prefix, ''), options.fetch(:suffix, '') @regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/ @method_missing_target = "#{@prefix}attribute#{@suffix}" diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index 8b09f8b203..377aa6ee27 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -135,7 +135,10 @@ module ActiveModel klass.define_singleton_method("after_#{callback}") do |*args, &block| options = args.extract_options! options[:prepend] = true - options[:if] = Array(options[:if]) << "value != false" + conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v| + v != false + } + options[:if] = Array(options[:if]) << conditional set_callback(:"#{callback}", :after, *(args << options), &block) end end diff --git a/activemodel/lib/active_model/deprecated_mass_assignment_security.rb b/activemodel/lib/active_model/deprecated_mass_assignment_security.rb deleted file mode 100644 index 1f409c87b9..0000000000 --- a/activemodel/lib/active_model/deprecated_mass_assignment_security.rb +++ /dev/null @@ -1,21 +0,0 @@ -module ActiveModel - module DeprecatedMassAssignmentSecurity # :nodoc: - extend ActiveSupport::Concern - - module ClassMethods # :nodoc: - def attr_protected(*args) - raise "`attr_protected` is extracted out of Rails into a gem. " \ - "Please use new recommended protection model for params" \ - "(strong_parameters) or add `protected_attributes` to your " \ - "Gemfile to use old one." - end - - def attr_accessible(*args) - raise "`attr_accessible` is extracted out of Rails into a gem. " \ - "Please use new recommended protection model for params" \ - "(strong_parameters) or add `protected_attributes` to your " \ - "Gemfile to use old one." - end - end - end -end diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index cafdb946c0..ea5ddf71de 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -142,23 +142,23 @@ module ActiveModel @changed_attributes ||= {} end - private + # Handle <tt>*_changed?</tt> for +method_missing+. + def attribute_changed?(attr) + changed_attributes.include?(attr) + end - # Handle <tt>*_changed?</tt> for +method_missing+. - def attribute_changed?(attr) - changed_attributes.include?(attr) - end + # Handle <tt>*_was</tt> for +method_missing+. + def attribute_was(attr) + attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr) + end + + private # Handle <tt>*_change</tt> for +method_missing+. def attribute_change(attr) [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr) end - # Handle <tt>*_was</tt> for +method_missing+. - def attribute_was(attr) - attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr) - end - # Handle <tt>*_will_change!</tt> for +method_missing+. def attribute_will_change!(attr) return if attribute_changed?(attr) diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 0d7efab04b..e608a649f8 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -50,7 +50,7 @@ module ActiveModel # # The above allows you to do: # - # p = Person.new + # person = Person.new # person.validate! # => ["can not be nil"] # person.errors.full_messages # => ["name can not be nil"] # # etc.. @@ -289,7 +289,7 @@ module ActiveModel # # => NameIsInvalid: name is invalid # # person.errors.messages # => {} - def add(attribute, message = nil, options = {}) + def add(attribute, message = :invalid, options = {}) message = normalize_message(attribute, message, options) if exception = options[:strict] exception = ActiveModel::StrictValidationFailed if exception == true @@ -331,7 +331,7 @@ module ActiveModel # # person.errors.add :name, :blank # person.errors.added? :name, :blank # => true - def added?(attribute, message = nil, options = {}) + def added?(attribute, message = :invalid, options = {}) message = normalize_message(attribute, message, options) self[attribute].include? message end @@ -437,8 +437,6 @@ module ActiveModel private def normalize_message(attribute, message, options) - message ||= :invalid - case message when Symbol generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS)) diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb index 62383a03e8..f048dda5c6 100644 --- a/activemodel/lib/active_model/model.rb +++ b/activemodel/lib/active_model/model.rb @@ -79,6 +79,8 @@ module ActiveModel params.each do |attr, value| self.public_send("#{attr}=", value) end if params + + super() end # Indicates if the model is persisted. Default is +false+. diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb index e553590671..cc9483e67b 100644 --- a/activemodel/lib/active_model/secure_password.rb +++ b/activemodel/lib/active_model/secure_password.rb @@ -15,12 +15,12 @@ module ActiveModel # argument. You can add more validations by hand if need be. # # If you don't need the confirmation validation, just don't set any - # value to the password_confirmation attribute and the the validation + # value to the password_confirmation attribute and the validation # will not be triggered. # - # You need to add bcrypt-ruby (~> 3.0.0) to Gemfile to use #has_secure_password: + # You need to add bcrypt-ruby (~> 3.1.0) to Gemfile to use #has_secure_password: # - # gem 'bcrypt-ruby', '~> 3.0.0' + # gem 'bcrypt-ruby', '~> 3.1.0' # # Example using Active Record (which automatically includes ActiveModel::SecurePassword): # @@ -44,7 +44,7 @@ module ActiveModel # This is to avoid ActiveModel (and by extension the entire framework) # being dependent on a binary library. begin - gem 'bcrypt-ruby', '~> 3.0.0' + gem 'bcrypt-ruby', '~> 3.1.0' require 'bcrypt' rescue LoadError $stderr.puts "You don't have bcrypt-ruby installed in your application. Please add it to your Gemfile and run bundle install" @@ -56,9 +56,9 @@ module ActiveModel include InstanceMethodsOnActivation if options.fetch(:validations, true) - validates_confirmation_of :password, if: lambda { |m| m.password.present? } + validates_confirmation_of :password, if: :should_confirm_password? validates_presence_of :password, on: :create - validates_presence_of :password_confirmation, if: lambda { |m| m.password.present? } + validates_presence_of :password_confirmation, if: :should_confirm_password? before_create { raise "Password digest missing on new record" if password_digest.blank? } end @@ -109,6 +109,12 @@ module ActiveModel def password_confirmation=(unencrypted_password) @password_confirmation = unencrypted_password end + + private + + def should_confirm_password? + password_confirmation && password.present? + end end end end diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb index 2803f69b6f..2864c2ba11 100644 --- a/activemodel/lib/active_model/serializers/xml.rb +++ b/activemodel/lib/active_model/serializers/xml.rb @@ -205,7 +205,7 @@ module ActiveModel Serializer.new(self, options).serialize(&block) end - # Sets the model +attributes+ from a JSON string. Returns +self+. + # Sets the model +attributes+ from an XML string. Returns +self+. # # class Person # include ActiveModel::Serializers::Xml diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 92206450d2..31c2245265 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -142,7 +142,9 @@ module ActiveModel if options.key?(:on) options = options.dup options[:if] = Array(options[:if]) - options[:if].unshift("validation_context == :#{options[:on]}") + options[:if].unshift lambda { |o| + o.validation_context == options[:on] + } end args << options set_callback(:validate, *args, &block) @@ -226,7 +228,6 @@ module ActiveModel # Person.validators_on(:name) # # => [ # # #<ActiveModel::Validations::PresenceValidator:0x007fe604914e60 @attributes=[:name], @options={}>, - # # #<ActiveModel::Validations::InclusionValidator:0x007fe603bb8780 @attributes=[:age], @options={in:0..99}> # # ] def validators_on(*attributes) attributes.flat_map do |attribute| diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb index cabb9482f2..fde53b9f89 100644 --- a/activemodel/lib/active_model/validations/callbacks.rb +++ b/activemodel/lib/active_model/validations/callbacks.rb @@ -58,7 +58,9 @@ module ActiveModel if options.is_a?(Hash) && options[:on] options[:if] = Array(options[:if]) options[:on] = Array(options[:on]) - options[:if].unshift("#{options[:on]}.include? self.validation_context") + options[:if].unshift lambda { |o| + options[:on].include? o.validation_context + } end set_callback(:validation, :before, *args, &block) end diff --git a/activemodel/lib/active_model/validations/clusivity.rb b/activemodel/lib/active_model/validations/clusivity.rb index 49df98d6c1..1c35cb7c35 100644 --- a/activemodel/lib/active_model/validations/clusivity.rb +++ b/activemodel/lib/active_model/validations/clusivity.rb @@ -15,15 +15,15 @@ module ActiveModel private def include?(record, value) - exclusions = if delimiter.respond_to?(:call) - delimiter.call(record) - elsif delimiter.respond_to?(:to_sym) - record.send(delimiter) - else - delimiter - end + members = if delimiter.respond_to?(:call) + delimiter.call(record) + elsif delimiter.respond_to?(:to_sym) + record.send(delimiter) + else + delimiter + end - exclusions.send(inclusion_method(exclusions), value) + members.send(inclusion_method(members), value) end def delimiter @@ -31,10 +31,11 @@ module ActiveModel end # In Ruby 1.9 <tt>Range#include?</tt> on non-numeric ranges checks all possible values in the - # range for equality, so it may be slow for large ranges. The new <tt>Range#cover?</tt> - # uses the previous logic of comparing a value with the range endpoints. + # range for equality, which is slower but more accurate. <tt>Range#cover?</tt> uses + # the previous logic of comparing a value with the range endpoints, which is fast + # but is only accurate on numeric ranges. def inclusion_method(enumerable) - enumerable.is_a?(Range) ? :cover? : :include? + (enumerable.is_a?(Range) && enumerable.first.is_a?(Numeric)) ? :cover? : :include? end end end diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb index 1cfd86efee..24337614c5 100644 --- a/activemodel/lib/active_model/validations/inclusion.rb +++ b/activemodel/lib/active_model/validations/inclusion.rb @@ -28,7 +28,7 @@ module ActiveModel # Configuration options: # * <tt>:in</tt> - An enumerable object of available items. This can be # supplied as a proc, lambda or symbol which returns an enumerable. If the - # enumerable is a range the test is performed with <tt>Range#cover?</tt>, + # enumerable is a numerical range the test is performed with <tt>Range#cover?</tt>, # otherwise with <tt>include?</tt>. # * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt> # * <tt>:message</tt> - Specifies a custom error message (default is: "is diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb index 25eb4860e3..e9cb5ccc96 100644 --- a/activemodel/test/cases/attribute_methods_test.rb +++ b/activemodel/test/cases/attribute_methods_test.rb @@ -202,17 +202,6 @@ class AttributeMethodsTest < ActiveModel::TestCase assert_equal 'bar', m.foo_test end - test 'explicitly specifying an empty prefix/suffix is deprecated' do - klass = Class.new(ModelWithAttributes) - - assert_deprecated { klass.attribute_method_suffix '' } - assert_deprecated { klass.attribute_method_prefix '' } - - klass.define_attribute_methods(:foo) - - assert_equal 'value of foo', klass.new.foo - end - test 'should not interfere with method_missing if the attr has a private/protected method' do m = ModelWithAttributes2.new m.attributes = { 'private_method' => '<3', 'protected_method' => 'O_o' } diff --git a/activemodel/test/cases/deprecated_mass_assignment_security_test.rb b/activemodel/test/cases/deprecated_mass_assignment_security_test.rb deleted file mode 100644 index c1fe8822cd..0000000000 --- a/activemodel/test/cases/deprecated_mass_assignment_security_test.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'cases/helper' -require 'models/project' - -class DeprecatedMassAssignmentSecurityTest < ActiveModel::TestCase - def test_attr_accessible_raise_error - assert_raise RuntimeError, /protected_attributes/ do - Project.attr_accessible :username - end - end - - def test_attr_protected_raise_error - assert_raise RuntimeError, /protected_attributes/ do - Project.attr_protected :username - end - end -end diff --git a/activemodel/test/cases/model_test.rb b/activemodel/test/cases/model_test.rb index 24e4ca91c6..ee0fa26546 100644 --- a/activemodel/test/cases/model_test.rb +++ b/activemodel/test/cases/model_test.rb @@ -3,7 +3,30 @@ require 'cases/helper' class ModelTest < ActiveModel::TestCase include ActiveModel::Lint::Tests + module DefaultValue + def self.included(klass) + klass.class_eval { attr_accessor :hello } + end + + def initialize(*args) + @attr ||= 'default value' + super + end + end + class BasicModel + include DefaultValue + include ActiveModel::Model + attr_accessor :attr + end + + class BasicModelWithReversedMixins + include ActiveModel::Model + include DefaultValue + attr_accessor :attr + end + + class SimpleModel include ActiveModel::Model attr_accessor :attr end @@ -14,14 +37,20 @@ class ModelTest < ActiveModel::TestCase def test_initialize_with_params object = BasicModel.new(attr: "value") - assert_equal object.attr, "value" + assert_equal "value", object.attr + end + + def test_initialize_with_params_and_mixins_reversed + object = BasicModelWithReversedMixins.new(attr: "value") + assert_equal "value", object.attr end def test_initialize_with_nil_or_empty_hash_params_does_not_explode assert_nothing_raised do BasicModel.new() - BasicModel.new nil + BasicModel.new(nil) BasicModel.new({}) + SimpleModel.new(attr: 'value') end end @@ -29,4 +58,18 @@ class ModelTest < ActiveModel::TestCase object = BasicModel.new(attr: "value") assert object.persisted? == false end + + def test_mixin_inclusion_chain + object = BasicModel.new + assert_equal 'default value', object.attr + end + + def test_mixin_initializer_when_args_exist + object = BasicModel.new(hello: 'world') + assert_equal 'world', object.hello + end + + def test_mixin_initializer_when_args_dont_exist + assert_raises(NoMethodError) { SimpleModel.new(hello: 'world') } + end end diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb index 0b900d934d..98e5c747d5 100644 --- a/activemodel/test/cases/secure_password_test.rb +++ b/activemodel/test/cases/secure_password_test.rb @@ -95,6 +95,11 @@ class SecurePasswordTest < ActiveModel::TestCase assert @user.valid?(:update), "user should be valid" end + test "password_confirmation validations will not be triggered if password_confirmation is not sent" do + @user.password = "password" + assert @user.valid?(:create) + end + test "will not save if confirmation is blank but password is not" do @user.password = "password" @user.password_confirmation = "" diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb index 901f42f29b..c4cfb0c255 100644 --- a/activemodel/test/cases/serializers/xml_serialization_test.rb +++ b/activemodel/test/cases/serializers/xml_serialization_test.rb @@ -130,7 +130,7 @@ class XmlSerializationTest < ActiveModel::TestCase end test "should serialize nil" do - assert_match %r{<pseudonyms nil=\"true\"/>}, @contact.to_xml(methods: :pseudonyms) + assert_match %r{<pseudonyms nil="true"/>}, @contact.to_xml(methods: :pseudonyms) end test "should serialize integer" do @@ -138,23 +138,23 @@ class XmlSerializationTest < ActiveModel::TestCase end test "should serialize datetime" do - assert_match %r{<created-at type=\"dateTime\">2006-08-01T00:00:00Z</created-at>}, @contact.to_xml + assert_match %r{<created-at type="dateTime">2006-08-01T00:00:00Z</created-at>}, @contact.to_xml end test "should serialize boolean" do - assert_match %r{<awesome type=\"boolean\">false</awesome>}, @contact.to_xml + assert_match %r{<awesome type="boolean">false</awesome>}, @contact.to_xml end test "should serialize array" do - assert_match %r{<social type=\"array\">\s*<social>twitter</social>\s*<social>github</social>\s*</social>}, @contact.to_xml(methods: :social) + assert_match %r{<social type="array">\s*<social>twitter</social>\s*<social>github</social>\s*</social>}, @contact.to_xml(methods: :social) end test "should serialize hash" do - assert_match %r{<network>\s*<git type=\"symbol\">github</git>\s*</network>}, @contact.to_xml(methods: :network) + assert_match %r{<network>\s*<git type="symbol">github</git>\s*</network>}, @contact.to_xml(methods: :network) end test "should serialize yaml" do - assert_match %r{<preferences type=\"yaml\">--- !ruby/struct:Customer(\s*)\nname: John\n</preferences>}, @contact.to_xml + assert_match %r{<preferences type="yaml">--- !ruby/struct:Customer(\s*)\nname: John\n</preferences>}, @contact.to_xml end test "should call proc on object" do diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb index ceec9dc256..01a373d85d 100644 --- a/activemodel/test/cases/validations/inclusion_validation_test.rb +++ b/activemodel/test/cases/validations/inclusion_validation_test.rb @@ -14,6 +14,7 @@ class InclusionValidationTest < ActiveModel::TestCase Topic.validates_inclusion_of(:title, in: 'aaa'..'bbb') assert Topic.new("title" => "bbc", "content" => "abc").invalid? assert Topic.new("title" => "aa", "content" => "abc").invalid? + assert Topic.new("title" => "aaab", "content" => "abc").invalid? assert Topic.new("title" => "aaa", "content" => "abc").valid? assert Topic.new("title" => "abc", "content" => "abc").valid? assert Topic.new("title" => "bbb", "content" => "abc").valid? diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index e5312d7d7c..61dba12b64 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,384 @@ +* Don't allow `quote_value` to be called without a column. + + Some adapters require column information to do their job properly. + By enforcing the provision of the column for this internal method + we ensure that those using adapters that require column information + will always get the proper behavior. + + *Ben Woosley* + +* When using optimistic locking, `update` was not passing the column to `quote_value` + to allow the connection adapter to properly determine how to quote the value. This was + affecting certain databases that use specific colmn types. + + Fixes: #6763 + + *Alfred Wong* + +* rescue from all exceptions in `ConnectionManagement#call` + + Fixes #11497 + + As `ActiveRecord::ConnectionAdapters::ConnectionManagement` middleware does + not rescue from Exception (but only from StandardError), the Connection + Pool quickly runs out of connections when multiple erroneous Requests come + in right after each other. + + Rescuing from all exceptions and not just StandardError, fixes this + behaviour. + + *Vipul A M* + +* `change_column` for PostgreSQL adapter respects the `:array` option. + + *Yves Senn* + +* Remove deprecation warning from `attribute_missing` for attributes that are columns. + + *Arun Agrawal* + +* Remove extra decrement of transaction deep level. + + Fixes: #4566 + + *Paul Nikitochkin* + +* Reset @column_defaults when assigning `locking_column`. + We had a potential problem. For example: + + class Post < ActiveRecord::Base + self.column_defaults # if we call this unintentionally before setting locking_column ... + self.locking_column = 'my_locking_column' + end + + Post.column_defaults["my_locking_column"] + => nil # expected value is 0 ! + + *kennyj* + +* Remove extra select and update queries on save/touch/destroy ActiveRecord model + with belongs to reflection with option `touch: true`. + + Fixes: #11288 + + *Paul Nikitochkin* + +* Remove deprecated nil-passing to the following `SchemaCache` methods: + `primary_keys`, `tables`, `columns` and `columns_hash`. + + *Yves Senn* + +* Remove deprecated block filter from `ActiveRecord::Migrator#migrate`. + + *Yves Senn* + +* Remove deprecated String constructor from `ActiveRecord::Migrator`. + + *Yves Senn* + +* Remove deprecated `scope` use without passing a callable object. + + *Arun Agrawal* + +* Remove deprecated `transaction_joinable=` in favor of `begin_transaction` + with `:joinable` option. + + *Arun Agrawal* + +* Remove deprecated `decrement_open_transactions`. + + *Arun Agrawal* + +* Remove deprecated `increment_open_transactions`. + + *Arun Agrawal* + +* Remove deprecated `PostgreSQLAdapter#outside_transaction?` + method. You can use `#transaction_open?` instead. + + *Yves Senn* + +* Remove deprecated `ActiveRecord::Fixtures.find_table_name` in favor of + `ActiveRecord::Fixtures.default_fixture_model_name`. + + *Vipul A M* + +* Removed deprecated `columns_for_remove` from `SchemaStatements`. + + *Neeraj Singh* + +* Remove deprecated `SchemaStatements#distinct`. + + *Francesco Rodriguez* + +* Move deprecated `ActiveRecord::TestCase` into the rails test + suite. The class is no longer public and is only used for internal + Rails tests. + + *Yves Senn* + +* Removed support for deprecated option `:restrict` for `:dependent` + in associations. + + *Neeraj Singh* + +* Removed support for deprecated `delete_sql` in associations. + + *Neeraj Singh* + +* Removed support for deprecated `insert_sql` in associations. + + *Neeraj Singh* + +* Removed support for deprecated `finder_sql` in associations. + + *Neeraj Singh* + +* Support array as root element in JSON fields. + + *Alexey Noskov & Francesco Rodriguez* + +* Removed support for deprecated `counter_sql` in associations. + + *Neeraj Singh* + +* Do not invoke callbacks when `delete_all` is called on collection. + + Method `delete_all` should not be invoking callbacks and this + feature was deprecated in Rails 4.0. This is being removed. + `delete_all` will continue to honor the `:dependent` option. However + if `:dependent` value is `:destroy` then the default deletion + strategy for that collection will be applied. + + User can also force a deletion strategy by passing parameter to + `delete_all`. For example you can do `@post.comments.delete_all(:nullify)` . + + *Neeraj Singh* + +* Calling default_scope without a proc will now raise `ArgumentError`. + + *Neeraj Singh* + +* Removed deprecated method `type_cast_code` from Column. + + *Neeraj Singh* + +* Removed deprecated options `delete_sql` and `insert_sql` from HABTM + association. + + Removed deprecated options `finder_sql` and `counter_sql` from + collection association. + + *Neeraj Singh* + +* Remove deprecated `ActiveRecord::Base#connection` method. + Make sure to access it via the class. + + *Yves Senn* + +* Remove deprecation warning for `auto_explain_threshold_in_seconds`. + + *Yves Senn* + +* Remove deprecated `:distinct` option from `Relation#count`. + + *Yves Senn* + +* Removed deprecated methods `partial_updates`, `partial_updates?` and + `partial_updates=`. + + *Neeraj Singh* + +* Removed deprecated method `scoped` + + *Neeraj Singh* + +* Removed deprecated method `default_scopes?` + + *Neeraj Singh* + +* Remove implicit join references that were deprecated in 4.0. + + Example: + + # before with implicit joins + Comment.where('posts.author_id' => 7) + + # after + Comment.references(:posts).where('posts.author_id' => 7) + + *Yves Senn* + +* Apply default scope when joining associations. For example: + + class Post < ActiveRecord::Base + default_scope -> { where published: true } + end + + class Comment + belongs_to :post + end + + When calling `Comment.joins(:post)`, we expect to receive only + comments on published posts, since that is the default scope for + posts. + + Before this change, the default scope from `Post` was not applied, + so we'd get comments on unpublished posts. + + *Jon Leighton* + +* Remove `activerecord-deprecated_finders` as a dependency + + *Łukasz Strzałkowski* + +* Remove Oracle / Sqlserver / Firebird database tasks that were deprecated in 4.0. + + *kennyj* + +* `find_each` now returns an `Enumerator` when called without a block, so that it + can be chained with other `Enumerable` methods. + + *Ben Woosley* + +* `ActiveRecord::Result.each` now returns an `Enumerator` when called without + a block, so that it can be chained with other `Enumerable` methods. + + *Ben Woosley* + +* Flatten merged join_values before building the joins. + + While joining_values special treatment is given to string values. + By flattening the array it ensures that string values are detected + as strings and not arrays. + + Fixes #10669. + + *Neeraj Singh and iwiznia* + +* Do not load all child records for inverse case. + + currently `post.comments.find(Comment.first.id)` would load all + comments for the given post to set the inverse association. + + This has a huge performance penalty. Because if post has 100k + records and all these 100k records would be loaded in memory + even though the comment id was supplied. + + Fix is to use in-memory records only if loaded? is true. Otherwise + load the records using full sql. + + Fixes #10509. + + *Neeraj Singh* + +* `inspect` on Active Record model classes does not initiate a + new connection. This means that calling `inspect`, when the + database is missing, will no longer raise an exception. + Fixes #10936. + + Example: + + Author.inspect # => "Author(no database connection)" + + *Yves Senn* + +* Handle single quotes in PostgreSQL default column values. + Fixes #10881. + + *Dylan Markow* + +* Log the sql that is actually sent to the database. + + If I have a query that produces sql + `WHERE "users"."name" = 'a b'` then in the log all the + whitespace is being squeezed. So the sql that is printed in the + log is `WHERE "users"."name" = 'a b'`. + + Do not squeeze whitespace out of sql queries. Fixes #10982. + + *Neeraj Singh* + +* Fixture setup does no longer depend on `ActiveRecord::Base.configurations`. + This is relevant when `ENV["DATABASE_URL"]` is used in place of a `database.yml`. + + *Yves Senn* + +* Fix mysql2 adapter raises the correct exception when executing a query on a + closed connection. + + *Yves Senn* + +* Ambiguous reflections are on :through relationships are no longer supported. + For example, you need to change this: + + class Author < ActiveRecord::Base + has_many :posts + has_many :taggings, :through => :posts + end + + class Post < ActiveRecord::Base + has_one :tagging + has_many :taggings + end + + class Tagging < ActiveRecord::Base + end + + To this: + + class Author < ActiveRecord::Base + has_many :posts + has_many :taggings, :through => :posts, :source => :tagging + end + + class Post < ActiveRecord::Base + has_one :tagging + has_many :taggings + end + + class Tagging < ActiveRecord::Base + end + + *Aaron Patterson* + +* Remove column restrictions for `count`, let the database raise if the SQL is + invalid. The previous behavior was untested and surprising for the user. + Fixes #5554. + + Example: + + User.select("name, username").count + # Before => SELECT count(*) FROM users + # After => ActiveRecord::StatementInvalid + + # you can still use `count(:all)` to perform a query unrelated to the + # selected columns + User.select("name, username").count(:all) # => SELECT count(*) FROM users + + *Yves Senn* + +* Rails now automatically detects inverse associations. If you do not set the + `:inverse_of` option on the association, then Active Record will guess the + inverse association based on heuristics. + + Note that automatic inverse detection only works on `has_many`, `has_one`, + and `belongs_to` associations. Extra options on the associations will + also prevent the association's inverse from being found automatically. + + The automatic guessing of the inverse association uses a heuristic based + on the name of the class, so it may not work for all associations, + especially the ones with non-standard names. + + You can turn off the automatic detection of inverse associations by setting + the `:inverse_of` option to `false` like so: + + class Taggable < ActiveRecord::Base + belongs_to :tag, inverse_of: false + end + + *John Wang* + * Fix `add_column` with `array` option when using PostgreSQL. Fixes #10432 *Adam Anderson* @@ -19,11 +400,11 @@ *Yves Senn* -* Fix bug where tiny types are incorectly coerced as booleand when the length is more than 1. +* Fix bug where tiny types are incorrectly coerced as boolean when the length is more than 1. Fixes #10620. - *Aaron Peterson* + *Aaron Patterson* * Also support extensions in PostgreSQL 9.1. This feature has been supported since 9.1. @@ -32,7 +413,7 @@ * Deprecate `ConnectionAdapters::SchemaStatements#distinct`, as it is no longer used by internals. - *Ben Woosley# + *Ben Woosley* * Fix pending migrations error when loading schema and `ActiveRecord::Base.table_name_prefix` is not blank. @@ -102,8 +483,8 @@ *Olek Janiszewski* -* fixes bug introduced by #3329. Now, when autosaving associations, - deletions happen before inserts and saves. This prevents a 'duplicate +* fixes bug introduced by #3329. Now, when autosaving associations, + deletions happen before inserts and saves. This prevents a 'duplicate unique value' database error that would occur if a record being created had the same value on a unique indexed field as that of a record being destroyed. diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 136bb1dfbc..cee1dd5aeb 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -58,11 +58,10 @@ end task "isolated_test_#{adapter}" do adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] puts [adapter, adapter_short].inspect - ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) (Dir["test/cases/**/*_test.rb"].reject { |x| x =~ /\/adapters\// } + Dir["test/cases/adapters/#{adapter_short}/**/*_test.rb"]).all? do |file| - sh(ruby, "-Itest", file) + sh(Gem.ruby, '-w' ,"-Itest", file) end or raise "Failures" end @@ -119,12 +118,9 @@ namespace :postgresql do %x( createdb -E UTF8 -T template0 #{config['arunit']['database']} ) %x( createdb -E UTF8 -T template0 #{config['arunit2']['database']} ) - # prepare hstore - version = %x( createdb --version ).strip.gsub(/(.*)(\d\.\d\.\d)$/, "\\2") - %w(arunit arunit2).each do |db| - if version < "9.1.0" - puts "Please prepare hstore data type. See http://www.postgresql.org/docs/9.0/static/hstore.html" - end + # notify about preparing hstore + if %x( createdb --version ).strip.gsub(/(.*)(\d\.\d\.\d)$/, "\\2") < "9.1.0" + puts "Please prepare hstore data type. See http://www.postgresql.org/docs/9.0/static/hstore.html" end end @@ -222,7 +218,7 @@ end # Publishing ------------------------------------------------------ -desc "Release to gemcutter" +desc "Release to rubygems" task :release => :package do require 'rake/gemcutter' Rake::Gemcutter::Tasks.new(spec).define diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index 337106cb92..9986ded904 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -25,5 +25,4 @@ Gem::Specification.new do |s| s.add_dependency 'activemodel', version s.add_dependency 'arel', '~> 4.0.0' - s.add_dependency 'activerecord-deprecated_finders', '~> 1.0.2' end diff --git a/activerecord/examples/performance.rb b/activerecord/examples/performance.rb index ad12f8597f..d3546ce948 100644 --- a/activerecord/examples/performance.rb +++ b/activerecord/examples/performance.rb @@ -5,12 +5,12 @@ require 'benchmark/ips' TIME = (ENV['BENCHMARK_TIME'] || 20).to_i RECORDS = (ENV['BENCHMARK_RECORDS'] || TIME*1000).to_i -conn = { :adapter => 'sqlite3', :database => ':memory:' } +conn = { adapter: 'sqlite3', database: ':memory:' } ActiveRecord::Base.establish_connection(conn) class User < ActiveRecord::Base - connection.create_table :users, :force => true do |t| + connection.create_table :users, force: true do |t| t.string :name, :email t.timestamps end @@ -19,7 +19,7 @@ class User < ActiveRecord::Base end class Exhibit < ActiveRecord::Base - connection.create_table :exhibits, :force => true do |t| + connection.create_table :exhibits, force: true do |t| t.belongs_to :user t.string :name t.text :notes @@ -43,6 +43,8 @@ class Exhibit < ActiveRecord::Base def self.feel(exhibits) exhibits.each { |e| e.feel } end end +def progress_bar(int); print "." if (int%100).zero? ; end + puts 'Generating data...' module ActiveRecord @@ -75,30 +77,32 @@ notes = ActiveRecord::Faker::LOREM.join ' ' today = Date.today puts "Inserting #{RECORDS} users and exhibits..." -RECORDS.times do +RECORDS.times do |record| user = User.create( - :created_at => today, - :name => ActiveRecord::Faker.name, - :email => ActiveRecord::Faker.email + created_at: today, + name: ActiveRecord::Faker.name, + email: ActiveRecord::Faker.email ) Exhibit.create( - :created_at => today, - :name => ActiveRecord::Faker.name, - :user => user, - :notes => notes + created_at: today, + name: ActiveRecord::Faker.name, + user: user, + notes: notes ) + progress_bar(record) end +puts "Done!\n" Benchmark.ips(TIME) do |x| ar_obj = Exhibit.find(1) - attrs = { :name => 'sam' } - attrs_first = { :name => 'sam' } - attrs_second = { :name => 'tom' } + attrs = { name: 'sam' } + attrs_first = { name: 'sam' } + attrs_second = { name: 'tom' } exhibit = { - :name => ActiveRecord::Faker.name, - :notes => notes, - :created_at => Date.today + name: ActiveRecord::Faker.name, + notes: notes, + created_at: Date.today } x.report("Model#id") do @@ -117,10 +121,18 @@ Benchmark.ips(TIME) do |x| Exhibit.first.look end + x.report 'Model.take' do + Exhibit.take + end + x.report("Model.all limit(100)") do Exhibit.look Exhibit.limit(100) end + x.report("Model.all take(100)") do + Exhibit.look Exhibit.take(100) + end + x.report "Model.all limit(100) with relationship" do Exhibit.feel Exhibit.limit(100).includes(:user) end @@ -167,6 +179,6 @@ Benchmark.ips(TIME) do |x| end x.report "AR.execute(query)" do - ActiveRecord::Base.connection.execute("Select * from exhibits where id = #{(rand * 1000 + 1).to_i}") + ActiveRecord::Base.connection.execute("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}") end end diff --git a/activerecord/examples/simple.rb b/activerecord/examples/simple.rb index c12f746992..4ed5d80eb2 100644 --- a/activerecord/examples/simple.rb +++ b/activerecord/examples/simple.rb @@ -1,14 +1,14 @@ -$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib" +require File.expand_path('../../../load_paths', __FILE__) require 'active_record' class Person < ActiveRecord::Base - establish_connection :adapter => 'sqlite3', :database => 'foobar.db' - connection.create_table table_name, :force => true do |t| + establish_connection adapter: 'sqlite3', database: 'foobar.db' + connection.create_table table_name, force: true do |t| t.string :name end end -bob = Person.create!(:name => 'bob') +bob = Person.create!(name: 'bob') puts Person.all.inspect bob.destroy puts Person.all.inspect diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 994cacb4f9..9d418dacaf 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -25,7 +25,6 @@ require 'active_support' require 'active_support/rails' require 'active_model' require 'arel' -require 'active_record/deprecated_finders' require 'active_record/version' @@ -152,7 +151,6 @@ module ActiveRecord autoload :OracleDatabaseTasks, 'active_record/tasks/oracle_database_tasks' end - autoload :TestCase autoload :TestFixtures, 'active_record/fixtures' def self.eager_load! diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index 9d1c12ec62..d075edc159 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -223,7 +223,8 @@ module ActiveRecord reader_method(name, class_name, mapping, allow_nil, constructor) writer_method(name, class_name, mapping, allow_nil, converter) - create_reflection(:composed_of, part_id, nil, options, self) + reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self) + Reflection.add_reflection self, part_id, reflection end private diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 3490057298..5ceda933f2 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -586,9 +586,10 @@ module ActiveRecord # belongs_to :tag, inverse_of: :taggings # end # - # If you do not set the +:inverse_of+ record, the association will do its - # best to match itself up with the correct inverse. Automatic +:inverse_of+ - # detection only works on +has_many+, +has_one+, and +belongs_to+ associations. + # If you do not set the <tt>:inverse_of</tt> record, the association will + # do its best to match itself up with the correct inverse. Automatic + # inverse detection only works on <tt>has_many</tt>, <tt>has_one</tt>, and + # <tt>belongs_to</tt> associations. # # Extra options on the associations, as defined in the # <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt> constant, will @@ -599,10 +600,10 @@ module ActiveRecord # especially the ones with non-standard names. # # You can turn off the automatic detection of inverse associations by setting - # the +:automatic_inverse_of+ option to +false+ like so: + # the <tt>:inverse_of</tt> option to <tt>false</tt> like so: # # class Taggable < ActiveRecord::Base - # belongs_to :tag, automatic_inverse_of: false + # belongs_to :tag, inverse_of: false # end # # == Nested \Associations @@ -1204,7 +1205,8 @@ module ActiveRecord # has_many :reports, -> { readonly } # has_many :subscribers, through: :subscriptions, source: :user def has_many(name, scope = nil, options = {}, &extension) - Builder::HasMany.build(self, name, scope, options, &extension) + reflection = Builder::HasMany.build(self, name, scope, options, &extension) + Reflection.add_reflection self, name, reflection end # Specifies a one-to-one association with another class. This method should only be used @@ -1307,7 +1309,8 @@ module ActiveRecord # has_one :club, through: :membership # has_one :primary_address, -> { where primary: true }, through: :addressables, source: :addressable def has_one(name, scope = nil, options = {}) - Builder::HasOne.build(self, name, scope, options) + reflection = Builder::HasOne.build(self, name, scope, options) + Reflection.add_reflection self, name, reflection end # Specifies a one-to-one association with another class. This method should only be used @@ -1419,7 +1422,8 @@ module ActiveRecord # belongs_to :company, touch: true # belongs_to :company, touch: :employees_last_updated_at def belongs_to(name, scope = nil, options = {}) - Builder::BelongsTo.build(self, name, scope, options) + reflection = Builder::BelongsTo.build(self, name, scope, options) + Reflection.add_reflection self, name, reflection end # Specifies a many-to-many relationship with another class. This associates two classes via an @@ -1556,7 +1560,8 @@ module ActiveRecord # has_and_belongs_to_many :categories, join_table: "prods_cats" # has_and_belongs_to_many :categories, -> { readonly } def has_and_belongs_to_many(name, scope = nil, options = {}, &extension) - Builder::HasAndBelongsToMany.build(self, name, scope, options, &extension) + reflection = Builder::HasAndBelongsToMany.build(self, name, scope, options, &extension) + Reflection.add_reflection self, name, reflection end end end diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 608a6af16c..67d24b35d1 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -84,11 +84,6 @@ module ActiveRecord target_scope.merge(association_scope) end - def scoped - ActiveSupport::Deprecation.warn "#scoped is deprecated. use #scope instead." - scope - end - # The scope for this association. # # Note that the association_scope is merged into the target_scope only when the @@ -122,11 +117,7 @@ module ActiveRecord # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the # through association's scope) def target_scope - all = klass.all - scope = AssociationRelation.new(klass, klass.arel_table, self) - scope.merge! all - scope.default_scoped = all.default_scoped? - scope + AssociationRelation.create(klass, klass.arel_table, self).merge!(klass.all) end # Loads the \target if needed and returns it. @@ -200,13 +191,14 @@ module ActiveRecord creation_attributes.each { |key, value| record[key] = value } end - # Should be true if there is a foreign key present on the owner which + # Returns true if there is a foreign key present on the owner which # references the target. This is used to determine whether we can load # the target if the owner is currently a new record (and therefore - # without a key). + # without a key). If the owner is a new record then foreign_key must + # be present in order to load target. # # Currently implemented by belongs_to (vanilla and polymorphic) and - # has_one/has_many :through associations which go through a belongs_to + # has_one/has_many :through associations which go through a belongs_to. def foreign_key_present? false end diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index aa5551fe0c..bdfafa5066 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -96,7 +96,7 @@ module ActiveRecord item = eval_scope(klass, scope_chain_item) if scope_chain_item == self.reflection.scope - scope.merge! item.except(:where, :includes) + scope.merge! item.except(:where, :includes, :bind) end scope.includes! item.includes_values @@ -119,7 +119,7 @@ module ActiveRecord # the owner klass.table_name else - reflection.table_name + super end end diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb index 6a3658f328..6b6a088806 100644 --- a/activerecord/lib/active_record/associations/builder/association.rb +++ b/activerecord/lib/active_record/associations/builder/association.rb @@ -2,7 +2,7 @@ # used by all associations. # # The hierarchy is defined as follows: -# Association +# Association # - SingularAssociation # - BelongsToAssociation # - HasOneAssociation @@ -14,11 +14,13 @@ module ActiveRecord::Associations::Builder class Association #:nodoc: class << self attr_accessor :valid_options + attr_accessor :extensions end self.valid_options = [:class_name, :foreign_key, :validate] + self.extensions = [] - attr_reader :model, :name, :scope, :options, :reflection + attr_reader :model, :name, :scope, :options def self.build(*args, &block) new(*args, &block).build @@ -48,15 +50,15 @@ module ActiveRecord::Associations::Builder @model.generated_feature_methods end - include Module.new { def build; end } - def build validate_options define_accessors configure_dependency if options[:dependent] - @reflection = model.create_reflection(macro, name, scope, options, model) - super # provides an extension point - @reflection + reflection = ActiveRecord::Reflection.create(macro, name, scope, options, model) + Association.extensions.each do |extension| + extension.build @model, reflection + end + reflection end def macro @@ -64,18 +66,18 @@ module ActiveRecord::Associations::Builder end def valid_options - Association.valid_options + Association.valid_options + Association.extensions.flat_map(&:valid_options) end def validate_options options.assert_valid_keys(valid_options) end - + # Defines the setter and getter methods for the association # class Post < ActiveRecord::Base # has_many :comments # end - # + # # Post.first.comments and Post.first.comments= methods are defined by this method... def define_accessors @@ -104,20 +106,8 @@ module ActiveRecord::Associations::Builder raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{options[:dependent]}" end - if options[:dependent] == :restrict - ActiveSupport::Deprecation.warn( - "The :restrict option is deprecated. Please use :restrict_with_exception instead, which " \ - "provides the same functionality." - ) - end - - mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 - def #{macro}_dependent_for_#{name} - association(:#{name}).handle_dependency - end - CODE - - model.before_destroy "#{macro}_dependent_for_#{name}" + n = name + model.before_destroy lambda { |o| o.association(n).handle_dependency } end def valid_dependent_options diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 63e9526436..81293e464d 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -19,82 +19,116 @@ module ActiveRecord::Associations::Builder reflection end - def add_counter_cache_callbacks(reflection) - cache_column = reflection.counter_cache_column - foreign_key = reflection.foreign_key + def valid_dependent_options + [:destroy, :delete] + end + + private - mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 - def belongs_to_counter_cache_after_create_for_#{name} - if record = #{name} - record.class.increment_counter(:#{cache_column}, record.id) + def add_counter_cache_methods(mixin) + return if mixin.method_defined? :belongs_to_counter_cache_after_create + + mixin.class_eval do + def belongs_to_counter_cache_after_create(association, reflection) + if record = send(association.name) + cache_column = reflection.counter_cache_column + record.class.increment_counter(cache_column, record.id) @_after_create_counter_called = true end end - def belongs_to_counter_cache_before_destroy_for_#{name} - unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == #{foreign_key.to_sym.inspect} - record = #{name} + def belongs_to_counter_cache_before_destroy(association, reflection) + foreign_key = reflection.foreign_key.to_sym + unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key + record = send association.name if record && !self.destroyed? - record.class.decrement_counter(:#{cache_column}, record.id) + cache_column = reflection.counter_cache_column + record.class.decrement_counter(cache_column, record.id) end end end - def belongs_to_counter_cache_after_update_for_#{name} + def belongs_to_counter_cache_after_update(association, reflection) + foreign_key = reflection.foreign_key + cache_column = reflection.counter_cache_column + if (@_after_create_counter_called ||= false) @_after_create_counter_called = false - elsif self.#{foreign_key}_changed? && !new_record? && defined?(#{name.to_s.camelize}) - model = #{name.to_s.camelize} - foreign_key_was = self.#{foreign_key}_was - foreign_key = self.#{foreign_key} + elsif attribute_changed?(foreign_key) && !new_record? && association.constructable? + model = reflection.klass + foreign_key_was = attribute_was foreign_key + foreign_key = attribute foreign_key if foreign_key && model.respond_to?(:increment_counter) - model.increment_counter(:#{cache_column}, foreign_key) + model.increment_counter(cache_column, foreign_key) end if foreign_key_was && model.respond_to?(:decrement_counter) - model.decrement_counter(:#{cache_column}, foreign_key_was) + model.decrement_counter(cache_column, foreign_key_was) end end end - CODE + end + end + + def add_counter_cache_callbacks(reflection) + cache_column = reflection.counter_cache_column + add_counter_cache_methods mixin + association = self + + model.after_create lambda { |record| + record.belongs_to_counter_cache_after_create(association, reflection) + } - model.after_create "belongs_to_counter_cache_after_create_for_#{name}" - model.before_destroy "belongs_to_counter_cache_before_destroy_for_#{name}" - model.after_update "belongs_to_counter_cache_after_update_for_#{name}" + model.before_destroy lambda { |record| + record.belongs_to_counter_cache_before_destroy(association, reflection) + } + + model.after_update lambda { |record| + record.belongs_to_counter_cache_after_update(association, reflection) + } klass = reflection.class_name.safe_constantize klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly) end - def add_touch_callbacks(reflection) - mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 - def belongs_to_touch_after_save_or_destroy_for_#{name} - foreign_key_field = #{reflection.foreign_key.inspect} - old_foreign_id = attribute_was(foreign_key_field) + def self.touch_record(o, foreign_key, name, touch) # :nodoc: + old_foreign_id = o.changed_attributes[foreign_key] - if old_foreign_id - klass = association(#{name.inspect}).klass - old_record = klass.find_by(klass.primary_key => old_foreign_id) + if old_foreign_id + klass = o.association(name).klass + old_record = klass.find_by(klass.primary_key => old_foreign_id) - if old_record - old_record.touch #{options[:touch].inspect if options[:touch] != true} - end - end - - record = #{name} - unless record.nil? || record.new_record? - record.touch #{options[:touch].inspect if options[:touch] != true} + if old_record + if touch != true + old_record.touch touch + else + old_record.touch end end - CODE - - model.after_save "belongs_to_touch_after_save_or_destroy_for_#{name}" - model.after_touch "belongs_to_touch_after_save_or_destroy_for_#{name}" - model.after_destroy "belongs_to_touch_after_save_or_destroy_for_#{name}" + end + + record = o.send name + unless record.nil? || record.new_record? + if touch != true + record.touch touch + else + record.touch + end + end end - def valid_dependent_options - [:destroy, :delete] + def add_touch_callbacks(reflection) + foreign_key = reflection.foreign_key + n = name + touch = options[:touch] + + callback = lambda { |record| + BelongsTo.touch_record(record, foreign_key, n, touch) + } + + model.after_save callback + model.after_touch callback + model.after_destroy callback end end end diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb index 9c6690b721..5eb11f98c8 100644 --- a/activerecord/lib/active_record/associations/builder/collection_association.rb +++ b/activerecord/lib/active_record/associations/builder/collection_association.rb @@ -8,7 +8,7 @@ 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, + super + [:table_name, :before_add, :after_add, :before_remove, :after_remove, :extend] end @@ -20,7 +20,6 @@ module ActiveRecord::Associations::Builder end def build - show_deprecation_warnings wrap_block_extension reflection = super CALLBACKS.each { |callback_name| define_callback(callback_name) } @@ -31,14 +30,6 @@ module ActiveRecord::Associations::Builder true end - def show_deprecation_warnings - [:finder_sql, :counter_sql].each do |name| - if options.include? name - ActiveSupport::Deprecation.warn("The :#{name} association option is deprecated. Please find an alternative (such as using scopes).") - end - end - end - def wrap_block_extension if block_extension @extension_module = mod = Module.new(&block_extension) diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb index bdac02b5bf..64ee24c7c6 100644 --- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb @@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder end def valid_options - super + [:join_table, :association_foreign_key, :delete_sql, :insert_sql] + super + [:join_table, :association_foreign_key] end def build @@ -14,16 +14,6 @@ module ActiveRecord::Associations::Builder reflection end - def show_deprecation_warnings - super - - [:delete_sql, :insert_sql].each do |name| - if options.include? name - ActiveSupport::Deprecation.warn("The :#{name} association option is deprecated. Please find an alternative (such as using has_many :through).") - end - end - end - def define_destroy_hook name = self.name model.send(:include, Module.new { diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb index 429def5455..a60cb4769a 100644 --- a/activerecord/lib/active_record/associations/builder/has_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_many.rb @@ -5,11 +5,11 @@ module ActiveRecord::Associations::Builder end def valid_options - super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :automatic_inverse_of, :counter_cache] + super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache] end def valid_dependent_options - [:destroy, :delete_all, :nullify, :restrict, :restrict_with_error, :restrict_with_exception] + [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception] end end end diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb index 0da564f402..2406437a07 100644 --- a/activerecord/lib/active_record/associations/builder/has_one.rb +++ b/activerecord/lib/active_record/associations/builder/has_one.rb @@ -19,7 +19,7 @@ module ActiveRecord::Associations::Builder end def valid_dependent_options - [:destroy, :delete, :nullify, :restrict, :restrict_with_error, :restrict_with_exception] + [:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception] end end end diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb index 96ccbeb8a3..7bd894d6ab 100644 --- a/activerecord/lib/active_record/associations/builder/singular_association.rb +++ b/activerecord/lib/active_record/associations/builder/singular_association.rb @@ -3,7 +3,7 @@ module ActiveRecord::Associations::Builder class SingularAssociation < Association #:nodoc: def valid_options - super + [:remote, :dependent, :counter_cache, :primary_key, :inverse_of, :automatic_inverse_of] + super + [:remote, :dependent, :counter_cache, :primary_key, :inverse_of] end def constructable? @@ -16,7 +16,7 @@ module ActiveRecord::Associations::Builder end # Defines the (build|create)_association methods for belongs_to or has_one association - + def define_constructors mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def build_#{name}(*args, &block) diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index efd7ecb97c..b4047b08bc 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -34,7 +34,7 @@ module ActiveRecord reload end - @proxy ||= CollectionProxy.new(klass, self) + @proxy ||= CollectionProxy.create(klass, self) end # Implements the writer method, e.g. foo.items= for Foo.has_many :items @@ -44,7 +44,7 @@ module ActiveRecord # Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items def ids_reader - if loaded? || options[:finder_sql] + if loaded? load_target.map do |record| record.send(reflection.association_primary_key) end @@ -79,9 +79,7 @@ module ActiveRecord if block_given? load_target.find(*args) { |*block_args| yield(*block_args) } else - if options[:finder_sql] - find_by_scan(*args) - elsif options[:inverse_of] + if options[:inverse_of] && loaded? args = args.flatten raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args.blank? @@ -153,11 +151,35 @@ module ActiveRecord end end - # Remove all records from this association. + # Removes all records from the association without calling callbacks + # on the associated records. It honors the `:dependent` option. However + # if the `:dependent` value is `:destroy` then in that case the default + # deletion strategy for the association is applied. + # + # You can force a particular deletion strategy by passing a parameter. + # + # Example: + # + # @author.books.delete_all(:nullify) + # @author.books.delete_all(:delete_all) # # See delete for more info. - def delete_all - delete(:all).tap do + def delete_all(dependent = nil) + if dependent.present? && ![:nullify, :delete_all].include?(dependent) + raise ArgumentError, "Valid values are :nullify or :delete_all" + end + + dependent = if dependent.present? + dependent + elsif options[:dependent] == :destroy + # since delete_all should not invoke callbacks so use the default deletion strategy + # for :destroy + reflection.is_a?(ActiveRecord::Reflection::ThroughReflection) ? :delete_all : :nullify + else + options[:dependent] + end + + delete(:all, dependent: dependent).tap do reset loaded! end @@ -173,36 +195,27 @@ module ActiveRecord end end - # Count all records using SQL. If the +:counter_sql+ or +:finder_sql+ option is set for the - # association, it will be used for the query. Otherwise, construct options and pass them with + # Count all records using SQL. Construct options and pass them with # scope to the target class's +count+. def count(column_name = nil, count_options = {}) column_name, count_options = nil, column_name if column_name.is_a?(Hash) - if options[:counter_sql] || options[:finder_sql] - unless count_options.blank? - raise ArgumentError, "If finder_sql/counter_sql is used then options cannot be passed" - end - - reflection.klass.count_by_sql(custom_counter_sql) - else - relation = scope - if association_scope.distinct_value - # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL. - column_name ||= reflection.klass.primary_key - relation = relation.distinct - end + relation = scope + if association_scope.distinct_value + # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL. + column_name ||= reflection.klass.primary_key + relation = relation.distinct + end - value = relation.count(column_name) + value = relation.count(column_name) - limit = options[:limit] - offset = options[:offset] + limit = options[:limit] + offset = options[:offset] - if limit || offset - [ [value - offset.to_i, 0].max, limit.to_i ].min - else - value - end + if limit || offset + [ [value - offset.to_i, 0].max, limit.to_i ].min + else + value end end @@ -214,18 +227,10 @@ module ActiveRecord # are actually removed from the database, that depends precisely on # +delete_records+. They are in any case removed from the collection. def delete(*records) - dependent = options[:dependent] + _options = records.extract_options! + dependent = _options[:dependent] || options[:dependent] if records.first == :all - - if dependent && dependent == :destroy - message = 'In Rails 4.1 delete_all on associations would not fire callbacks. ' \ - 'It means if the :dependent option is :destroy then the associated ' \ - 'records would be deleted without loading and invoking callbacks.' - - ActiveRecord::Base.logger ? ActiveRecord::Base.logger.warn(message) : $stderr.puts(message) - end - if loaded? || dependent == :destroy delete_or_destroy(load_target, dependent) else @@ -285,14 +290,14 @@ module ActiveRecord # Returns true if the collection is empty. # - # If the collection has been loaded or the <tt>:counter_sql</tt> option - # is provided, it is equivalent to <tt>collection.size.zero?</tt>. If the + # If the collection has been loaded + # it is equivalent to <tt>collection.size.zero?</tt>. If the # collection has not been loaded, it is equivalent to # <tt>collection.exists?</tt>. If the collection has not already been # loaded and you are going to fetch the records anyway it is better to # check <tt>collection.length.zero?</tt>. def empty? - if loaded? || options[:counter_sql] + if loaded? size.zero? else @target.blank? && !scope.exists? @@ -345,7 +350,6 @@ module ActiveRecord if record.new_record? include_in_memory?(record) else - load_target if options[:finder_sql] loaded? ? target.include?(record) : scope.exists?(record) end else @@ -390,31 +394,8 @@ module ActiveRecord private - def custom_counter_sql - if options[:counter_sql] - interpolate(options[:counter_sql]) - else - # replace the SELECT clause with COUNT(SELECTS), preserving any hints within /* ... */ - interpolate(options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) do - count_with = $2.to_s - count_with = '*' if count_with.blank? || count_with =~ /,/ || count_with =~ /\.\*/ - "SELECT #{$1}COUNT(#{count_with}) FROM" - end - end - end - - def custom_finder_sql - interpolate(options[:finder_sql]) - end - def find_target - records = - if options[:finder_sql] - reflection.klass.find_by_sql(custom_finder_sql) - else - scope.to_a - end - + records = scope.to_a records.each { |record| set_inverse_instance(record) } records end @@ -553,7 +534,6 @@ module ActiveRecord # Otherwise, go to the database only if none of the following are true: # * target already loaded # * owner is new record - # * custom :finder_sql exists # * target contains new or changed record(s) # * the first arg is an integer (which indicates the number of records to be returned) def fetch_first_or_last_using_find?(args) @@ -562,7 +542,6 @@ module ActiveRecord else !(loaded? || owner.new_record? || - options[:finder_sql] || target.any? { |record| record.new_record? || record.changed? } || args.first.kind_of?(Integer)) end @@ -579,7 +558,7 @@ module ActiveRecord end end - # If using a custom finder_sql or if the :inverse_of option has been + # If the :inverse_of option has been # specified, then #find scans the entire collection. def find_by_scan(*args) expects_array = args.first.kind_of?(Array) diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index e82c195335..6dc2da56d1 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -33,7 +33,6 @@ module ActiveRecord def initialize(klass, association) #:nodoc: @association = association super klass, klass.arel_table - self.default_scoped = true merge! association.scope(nullify: false) end @@ -418,8 +417,8 @@ module ActiveRecord # # Pet.find(1, 2, 3) # # => ActiveRecord::RecordNotFound - def delete_all - @association.delete_all + def delete_all(dependent = nil) + @association.delete_all(dependent) end # Deletes the records of the collection directly from the database @@ -727,7 +726,7 @@ module ActiveRecord end # Returns +true+ if the collection is empty. If the collection has been - # loaded or the <tt>:counter_sql</tt> option is provided, it is equivalent + # loaded it is equivalent # to <tt>collection.size.zero?</tt>. If the collection has not been loaded, # it is equivalent to <tt>collection.exists?</tt>. If the collection has # not already been loaded and you are going to fetch the records anyway it @@ -830,7 +829,7 @@ module ActiveRecord # person.pets.include?(Pet.find(20)) # => true # person.pets.include?(Pet.find(21)) # => false def include?(record) - @association.include?(record) + !!@association.include?(record) end def proxy_association diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index bb3e3db379..b2e6c708bf 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -18,16 +18,12 @@ module ActiveRecord end end - if options[:insert_sql] - owner.connection.insert(interpolate(options[:insert_sql], record)) - else - stmt = join_table.compile_insert( - join_table[reflection.foreign_key] => owner.id, - join_table[reflection.association_foreign_key] => record.id - ) + stmt = join_table.compile_insert( + join_table[reflection.foreign_key] => owner.id, + join_table[reflection.association_foreign_key] => record.id + ) - owner.class.connection.insert stmt - end + owner.class.connection.insert stmt record end @@ -39,22 +35,17 @@ module ActiveRecord end def delete_records(records, method) - if sql = options[:delete_sql] - records = load_target if records == :all - records.each { |record| owner.class.connection.delete(interpolate(sql, record)) } - else - relation = join_table - condition = relation[reflection.foreign_key].eq(owner.id) - - unless records == :all - condition = condition.and( - relation[reflection.association_foreign_key] - .in(records.map { |x| x.id }.compact) - ) - end - - owner.class.connection.delete(relation.where(condition).compile_delete) + relation = join_table + condition = relation[reflection.foreign_key].eq(owner.id) + + unless records == :all + condition = condition.and( + relation[reflection.association_foreign_key] + .in(records.map { |x| x.id }.compact) + ) end + + owner.class.connection.delete(relation.where(condition).compile_delete) end def invertible_for?(record) diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index cf8a589496..607ed0da46 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -9,7 +9,7 @@ module ActiveRecord def handle_dependency case options[:dependent] - when :restrict, :restrict_with_exception + when :restrict_with_exception raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty? when :restrict_with_error @@ -58,8 +58,6 @@ module ActiveRecord def count_records count = if has_cached_counter? owner.send(:read_attribute, cached_counter_attribute_name) - elsif options[:counter_sql] || options[:finder_sql] - reflection.klass.count_by_sql(custom_counter_sql) else scope.count end diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 920038a543..3ab1ea1ff4 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -6,7 +6,7 @@ module ActiveRecord def handle_dependency case options[:dependent] - when :restrict, :restrict_with_exception + when :restrict_with_exception raise ActiveRecord::DeleteRestrictionError.new(reflection.name) if load_target when :restrict_with_error diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index b81aecb4e5..58fc00d811 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -25,11 +25,8 @@ module ActiveRecord attr_reader :tables delegate :options, :through_reflection, :source_reflection, :chain, :to => :reflection - delegate :table, :table_name, :to => :parent, :prefix => :parent delegate :alias_tracker, :to => :join_dependency - alias :alias_suffix :parent_table_name - def initialize(reflection, join_dependency, parent = nil) reflection.check_validity! @@ -47,6 +44,9 @@ module ActiveRecord @tables = construct_tables.reverse end + def parent_table_name; parent.table_name; end + alias :alias_suffix :parent_table_name + def ==(other) other.class == self.class && other.reflection == reflection && @@ -64,15 +64,20 @@ module ActiveRecord end end - def join_to(manager) + def join_constraints + joins = [] tables = @tables.dup - foreign_table = parent_table + + foreign_table = parent.table foreign_klass = parent.base_klass + scope_chain_iter = reflection.scope_chain.reverse_each + # The chain starts with the target table, but we want to end with it here (makes # more sense in this context), so we reverse - chain.reverse.each_with_index do |reflection, i| + chain.reverse_each do |reflection| table = tables.shift + klass = reflection.klass case reflection.source_macro when :belongs_to @@ -80,11 +85,10 @@ module ActiveRecord foreign_key = reflection.foreign_key when :has_and_belongs_to_many # Join the join table first... - manager.from(join( + joins << join( table, table[reflection.foreign_key]. - eq(foreign_table[reflection.active_record_primary_key]) - )) + eq(foreign_table[reflection.active_record_primary_key])) foreign_table, table = table, tables.shift @@ -95,36 +99,39 @@ module ActiveRecord foreign_key = reflection.active_record_primary_key end - constraint = build_constraint(reflection, table, key, foreign_table, foreign_key) + constraint = build_constraint(klass, table, key, foreign_table, foreign_key) - scope_chain_items = scope_chain[i] + scope_chain_items = scope_chain_iter.next.map do |item| + if item.is_a?(Relation) + item + else + ActiveRecord::Relation.create(klass, table).instance_exec(self, &item) + end + end if reflection.type - scope_chain_items += [ - ActiveRecord::Relation.new(reflection.klass, table) + scope_chain_items << + ActiveRecord::Relation.create(klass, table) .where(reflection.type => foreign_klass.base_class.name) - ] end - constraint = scope_chain_items.inject(constraint) do |chain, item| - unless item.is_a?(Relation) - item = ActiveRecord::Relation.new(reflection.klass, table).instance_exec(self, &item) - end + scope_chain_items.concat [klass.send(:build_default_scope)].compact - if item.arel.constraints.empty? - chain - else - chain.and(item.arel.constraints) - end + rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right| + left.merge right + end + + if rel && !rel.arel.constraints.empty? + constraint = constraint.and rel.arel.constraints end - manager.from(join(table, constraint)) + joins << join(table, constraint) # The current table in this iteration becomes the foreign table in the next - foreign_table, foreign_klass = table, reflection.klass + foreign_table, foreign_klass = table, klass end - manager + joins end # Builds equality condition. @@ -142,13 +149,13 @@ module ActiveRecord # foreign_table #=> #<Arel::Table @name="physicians" ...> # foreign_key #=> id # - def build_constraint(reflection, table, key, foreign_table, foreign_key) + def build_constraint(klass, table, key, foreign_table, foreign_key) constraint = table[key].eq(foreign_table[foreign_key]) - if reflection.klass.finder_needs_type_condition? + if klass.finder_needs_type_condition? constraint = table.create_and([ constraint, - reflection.klass.send(:type_condition, table) + klass.send(:type_condition, table) ]) end @@ -167,11 +174,6 @@ module ActiveRecord def aliased_table_name table.table_alias || table.name end - - def scope_chain - @scope_chain ||= reflection.scope_chain.reverse - end - end end end diff --git a/activerecord/lib/active_record/associations/join_helper.rb b/activerecord/lib/active_record/associations/join_helper.rb index 5a41b40c8f..27b70edf1a 100644 --- a/activerecord/lib/active_record/associations/join_helper.rb +++ b/activerecord/lib/active_record/associations/join_helper.rb @@ -19,7 +19,7 @@ module ActiveRecord if reflection.source_macro == :has_and_belongs_to_many tables << alias_tracker.aliased_table_for( - (reflection.source_reflection || reflection).join_table, + reflection.source_reflection.join_table, table_alias_for(reflection, true) ) end diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index 82bf426b22..2317e34bc0 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -85,7 +85,7 @@ module ActiveRecord def initialize(records, associations, preload_scope = nil) @records = Array.wrap(records).compact.uniq @associations = Array.wrap(associations) - @preload_scope = preload_scope || Relation.new(nil, nil) + @preload_scope = preload_scope || Relation.create(nil, nil) end def run diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 82588905c6..d31f7432cd 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -98,7 +98,6 @@ module ActiveRecord def build_scope scope = klass.unscoped - scope.default_scoped = true values = reflection_scope.values preload_values = preload_scope.values @@ -113,7 +112,7 @@ module ActiveRecord scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name }) end - scope + klass.default_scoped.merge(scope) end end end diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index c4b50ab306..de06931845 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -41,21 +41,21 @@ module ActiveRecord end def through_scope - through_scope = through_reflection.klass.unscoped + scope = through_reflection.klass.unscoped if options[:source_type] - through_scope.where! reflection.foreign_type => options[:source_type] + scope.where! reflection.foreign_type => options[:source_type] else unless reflection_scope.where_values.empty? - through_scope.includes_values = Array(reflection_scope.values[:includes] || options[:source]) - through_scope.where_values = reflection_scope.values[:where] + scope.includes_values = Array(reflection_scope.values[:includes] || options[:source]) + scope.where_values = reflection_scope.values[:where] end - through_scope.references! reflection_scope.values[:references] - through_scope.order! reflection_scope.values[:order] if through_scope.eager_loading? + scope.references! reflection_scope.values[:references] + scope.order! reflection_scope.values[:order] if scope.eager_loading? end - through_scope + scope end end end diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb index 35f29b37a2..bd99997e7f 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -15,7 +15,7 @@ module ActiveRecord scope = super chain[1..-1].each do |reflection| scope.merge!( - reflection.klass.all.with_default_scope. + reflection.klass.all. except(:select, :create_with, :includes, :preload, :joins, :eager_load) ) end diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index 75377bba57..4f06955406 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -3,7 +3,6 @@ require 'active_model/forbidden_attributes_protection' module ActiveRecord module AttributeAssignment extend ActiveSupport::Concern - include ActiveModel::DeprecatedMassAssignmentSecurity include ActiveModel::ForbiddenAttributesProtection # Allows you to set all the attributes by passing in a hash of attributes with diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 609c6e8cab..208da2cb77 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/enumerable' +require 'mutex_m' module ActiveRecord # = Active Record Attribute Methods @@ -7,6 +8,7 @@ module ActiveRecord include ActiveModel::AttributeMethods included do + initialize_generated_modules include Read include Write include BeforeTypeCast @@ -17,27 +19,71 @@ module ActiveRecord include Serialization end + AttrNames = Module.new { + def self.set_name_cache(name, value) + const_name = "ATTR_#{name}" + unless const_defined? const_name + const_set const_name, value.dup.freeze + end + end + } + + class AttributeMethodCache + include Mutex_m + + def initialize + super + @module = Module.new + @method_cache = {} + end + + def [](name) + synchronize do + @method_cache.fetch(name) { + safe_name = name.unpack('h*').first + temp_method = "__temp__#{safe_name}" + ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name + @module.module_eval method_body(temp_method, safe_name), __FILE__, __LINE__ + @method_cache[name] = @module.instance_method temp_method + } + end + end + + private + def method_body; raise NotImplementedError; end + end + module ClassMethods + def inherited(child_class) #:nodoc: + child_class.initialize_generated_modules + super + end + + def initialize_generated_modules # :nodoc: + @generated_attribute_methods = Module.new { extend Mutex_m } + @attribute_methods_generated = false + include @generated_attribute_methods + end + # Generates all the attribute related methods for columns in the database # accessors, mutators and query methods. def define_attribute_methods # :nodoc: # Use a mutex; we don't want two thread simultaneously trying to define # attribute methods. - @attribute_methods_mutex.synchronize do - return if attribute_methods_generated? + generated_attribute_methods.synchronize do + return false if @attribute_methods_generated superclass.define_attribute_methods unless self == base_class super(column_names) @attribute_methods_generated = true end - end - - def attribute_methods_generated? # :nodoc: - @attribute_methods_generated ||= false + true end def undefine_attribute_methods # :nodoc: - super if attribute_methods_generated? - @attribute_methods_generated = false + generated_attribute_methods.synchronize do + super if @attribute_methods_generated + @attribute_methods_generated = false + end end # Raises a <tt>ActiveRecord::DangerousAttributeError</tt> exception when an @@ -119,9 +165,7 @@ module ActiveRecord # If we haven't generated any methods yet, generate them, then # see if we've created the method we're looking for. def method_missing(method, *args, &block) # :nodoc: - unless self.class.attribute_methods_generated? - self.class.define_attribute_methods - + if self.class.define_attribute_methods if respond_to_without_attributes?(method) send(method, *args, &block) else @@ -132,20 +176,6 @@ module ActiveRecord end end - def attribute_missing(match, *args, &block) # :nodoc: - if self.class.columns_hash[match.attr_name] - ActiveSupport::Deprecation.warn( - "The method `#{match.method_name}', matching the attribute `#{match.attr_name}' has " \ - "dispatched through method_missing. This shouldn't happen, because `#{match.attr_name}' " \ - "is a column of the table. If this error has happened through normal usage of Active " \ - "Record (rather than through your own code or external libraries), please report it as " \ - "a bug." - ) - end - - super - end - # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>, # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt> # which will all return +true+. It also define the attribute methods if they have @@ -164,7 +194,7 @@ module ActiveRecord # person.respond_to(:nothing) # => false def respond_to?(name, include_private = false) name = name.to_s - self.class.define_attribute_methods unless self.class.attribute_methods_generated? + self.class.define_attribute_methods result = super # If the result is false the answer is false. @@ -174,7 +204,7 @@ module ActiveRecord # For queries selecting a subset of columns, return false for unselected columns. # We check defined?(@attributes) not to issue warnings if called on objects that # have been allocated but not yet initialized. - if defined?(@attributes) && @attributes.present? && self.class.column_names.include?(name) + if defined?(@attributes) && @attributes.any? && self.class.column_names.include?(name) return has_attribute?(name) end @@ -229,7 +259,7 @@ module ActiveRecord # person = Person.create!(name: 'David Heinemeier Hansson ' * 3) # # person.attribute_for_inspect(:name) - # # => "\"David Heinemeier Hansson David Heinemeier Hansson D...\"" + # # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\"" # # person.attribute_for_inspect(:created_at) # # => "\"2012-10-22 00:15:07\"" @@ -237,7 +267,7 @@ module ActiveRecord value = read_attribute(attr_name) if value.is_a?(String) && value.length > 50 - "#{value[0..50]}...".inspect + "#{value[0, 50]}...".inspect elsif value.is_a?(Date) || value.is_a?(Time) %("#{value.to_s(:db)}") else diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 6315dd9549..dc2399643c 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -14,17 +14,6 @@ module ActiveRecord class_attribute :partial_writes, instance_writer: false self.partial_writes = true - - def self.partial_updates=(v); self.partial_writes = v; end - def self.partial_updates?; partial_writes?; end - def self.partial_updates; partial_writes; end - - ActiveSupport::Deprecation.deprecate_methods( - singleton_class, - :partial_updates= => :partial_writes=, - :partial_updates? => :partial_writes?, - :partial_updates => :partial_writes - ) end # Attempts to +save+ the record and clears changed attributes if successful. diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 506f5d75f9..1cf3aba41c 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -1,6 +1,38 @@ +require 'active_support/core_ext/module/method_transplanting' + module ActiveRecord module AttributeMethods module Read + ReaderMethodCache = Class.new(AttributeMethodCache) { + private + # We want to generate the methods via module_eval rather than + # define_method, because define_method is slower on dispatch. + # Evaluating many similar methods may use more memory as the instruction + # sequences are duplicated and cached (in MRI). define_method may + # be slower on dispatch, but if you're careful about the closure + # created, then define_method will consume much less memory. + # + # But sometimes the database might return columns with + # characters that are not allowed in normal method names (like + # 'my_column(omg)'. So to work around this we first define with + # the __temp__ identifier, and then use alias method to rename + # it to what we want. + # + # We are also defining a constant to hold the frozen string of + # the attribute name. Using a constant means that we do not have + # to allocate an object on each call to the attribute method. + # Making it frozen means that it doesn't get duped when used to + # key the @attributes_cache in read_attribute. + def method_body(method_name, const_name) + <<-EOMETHOD + def #{method_name} + name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name} + read_attribute(name) { |n| missing_attribute(n, caller) } + end + EOMETHOD + end + }.new + extend ActiveSupport::Concern ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date] @@ -32,30 +64,30 @@ module ActiveRecord protected - # We want to generate the methods via module_eval rather than - # define_method, because define_method is slower on dispatch and - # uses more memory (because it creates a closure). - # - # But sometimes the database might return columns with - # characters that are not allowed in normal method names (like - # 'my_column(omg)'. So to work around this we first define with - # the __temp__ identifier, and then use alias method to rename - # it to what we want. - # - # We are also defining a constant to hold the frozen string of - # the attribute name. Using a constant means that we do not have - # to allocate an object on each call to the attribute method. - # Making it frozen means that it doesn't get duped when used to - # key the @attributes_cache in read_attribute. - def define_method_attribute(name) - safe_name = name.unpack('h*').first - generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 - def __temp__#{safe_name} - read_attribute(AttrNames::ATTR_#{safe_name}) { |n| missing_attribute(n, caller) } + if Module.methods_transplantable? + def define_method_attribute(name) + method = ReaderMethodCache[name] + generated_attribute_methods.module_eval { define_method name, method } + end + else + def define_method_attribute(name) + safe_name = name.unpack('h*').first + temp_method = "__temp__#{safe_name}" + + ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name + + generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 + def #{temp_method} + name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} + read_attribute(name) { |n| missing_attribute(n, caller) } + end + STR + + generated_attribute_methods.module_eval do + alias_method name, temp_method + undef_method temp_method end - alias_method #{name.inspect}, :__temp__#{safe_name} - undef_method :__temp__#{safe_name} - STR + end end private diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 9caf73e627..1287de0d0d 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -56,7 +56,11 @@ module ActiveRecord end def type_cast(value) - value.unserialized_value @column.type_cast value.value + if value.state == :serialized + value.unserialized_value @column.type_cast value.value + else + value.unserialized_value + end end def type diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index cd33494cc3..c853fc0917 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -1,6 +1,21 @@ +require 'active_support/core_ext/module/method_transplanting' + module ActiveRecord module AttributeMethods module Write + WriterMethodCache = Class.new(AttributeMethodCache) { + private + + def method_body(method_name, const_name) + <<-EOMETHOD + def #{method_name}(value) + name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name} + write_attribute(name, value) + end + EOMETHOD + end + }.new + extend ActiveSupport::Concern included do @@ -10,17 +25,29 @@ module ActiveRecord module ClassMethods protected - # See define_method_attribute in read.rb for an explanation of - # this code. - def define_method_attribute=(name) - safe_name = name.unpack('h*').first - generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 - def __temp__#{safe_name}=(value) - write_attribute(AttrNames::ATTR_#{safe_name}, value) - end - alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= - undef_method :__temp__#{safe_name}= - STR + if Module.methods_transplantable? + # See define_method_attribute in read.rb for an explanation of + # this code. + def define_method_attribute=(name) + method = WriterMethodCache[name] + generated_attribute_methods.module_eval { + define_method "#{name}=", method + } + end + else + def define_method_attribute=(name) + safe_name = name.unpack('h*').first + ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name + + generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 + def __temp__#{safe_name}=(value) + name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} + write_attribute(name, value) + end + alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= + undef_method :__temp__#{safe_name}= + STR + end end end diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index a8a1847554..b30d1eb0a6 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -127,17 +127,17 @@ module ActiveRecord extend ActiveSupport::Concern module AssociationBuilderExtension #:nodoc: - def build + def self.build(model, reflection) model.send(:add_autosave_association_callbacks, reflection) - super + end + + def self.valid_options + [ :autosave ] end end included do - Associations::Builder::Association.class_eval do - self.valid_options << :autosave - include AssociationBuilderExtension - end + Associations::Builder::Association.extensions << AssociationBuilderExtension end module ClassMethods @@ -343,6 +343,7 @@ module ActiveRecord end records.each do |record| + next if record.destroyed? saved = true 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 816b397fcf..cfdcae7f63 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -332,11 +332,6 @@ module ActiveRecord end end - def clear_stale_cached_connections! # :nodoc: - reap - end - deprecate :clear_stale_cached_connections! => "Please use #reap instead" - # Check-out a database connection from the pool, indicating that you want # to use it. You should call #checkin when you no longer need this. # @@ -629,7 +624,7 @@ module ActiveRecord end response - rescue + rescue Exception ActiveRecord::Base.clear_active_connections! unless testing raise end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index c64b542286..97e1bc5379 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -18,8 +18,7 @@ module ActiveRecord end end - # Returns an array of record hashes with the column names as keys and - # column values as values. + # Returns an ActiveRecord::Result instance. def select_all(arel, name = nil, binds = []) select(to_sql(arel, binds), name, binds) end @@ -27,8 +26,7 @@ module ActiveRecord # Returns a record hash with the column names as keys and column values # as values. def select_one(arel, name = nil, binds = []) - result = select_all(arel, name, binds) - result.first if result + select_all(arel, name, binds).first end # Returns a single value from a record @@ -355,8 +353,7 @@ module ActiveRecord subselect end - # Returns an array of record hashes with the column names as keys and - # column values as values. + # Returns an ActiveRecord::Result instance. def select(sql, name = nil, binds = []) end undef_method :select @@ -377,14 +374,14 @@ module ActiveRecord update_sql(sql, name) end - def sql_for_insert(sql, pk, id_value, sequence_name, binds) - [sql, binds] - end - - def last_inserted_id(result) - row = result.rows.first - row && row.first - end + def sql_for_insert(sql, pk, id_value, sequence_name, binds) + [sql, binds] + end + + def last_inserted_id(result) + row = result.rows.first + row && row.first + end end end 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 41e07fbda9..e6b3c8ec9f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -20,6 +20,12 @@ module ActiveRecord attr_reader :query_cache, :query_cache_enabled + def initialize(*) + super + @query_cache = Hash.new { |h,sql| h[sql] = {} } + @query_cache_enabled = false + end + # Enable the query cache within the block. def cache old, @query_cache_enabled = @query_cache_enabled, true 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 c982d65d65..0be4b5cb19 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -25,6 +25,9 @@ module ActiveRecord end end + class ChangeColumnDefinition < Struct.new(:column, :type, :options) #:nodoc: + end + # Represents the schema of an SQL table in an abstract way. This class # provides methods for manipulating the schema representation. # diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 2a4eda3622..4b425494d0 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -214,8 +214,8 @@ module ActiveRecord # its block form to do so yourself: # # create_join_table :products, :categories do |t| - # t.index :products - # t.index :categories + # t.index :product_id + # t.index :category_id # end # # ====== Add a backend specific option to the generated SQL (MySQL) @@ -694,26 +694,6 @@ module ActiveRecord end end - def add_column_options!(sql, options) #:nodoc: - sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options) - # must explicitly check for :null to allow change_column to work on migrations - if options[:null] == false - sql << " NOT NULL" - end - if options[:auto_increment] == true - sql << " AUTO_INCREMENT" - end - end - - # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause. - # - # distinct("posts.id", ["posts.created_at desc"]) - # - def distinct(columns, order_by) - ActiveSupport::Deprecation.warn("#distinct is deprecated and shall be removed from future releases.") - "DISTINCT #{columns_for_distinct(columns, order_by)}" - end - # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT. # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax - they # require the order columns appear in the SELECT. @@ -823,12 +803,6 @@ module ActiveRecord index_name end - def columns_for_remove(table_name, *column_names) - ActiveSupport::Deprecation.warn("columns_for_remove is deprecated and will be removed in the future") - raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") if column_names.blank? - column_names.map {|column_name| quote_column_name(column_name) } - end - def rename_table_indexes(table_name, new_name) indexes(new_name).each do |index| generated_index_name = index_name(table_name, column: index.columns) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 26586f0974..b82d0fb872 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -95,8 +95,6 @@ module ActiveRecord @last_use = false @logger = logger @pool = pool - @query_cache = Hash.new { |h,sql| h[sql] = {} } - @query_cache_enabled = false @schema_cache = SchemaCache.new self @visitor = nil end @@ -116,6 +114,12 @@ module ActiveRecord send m, o end + def visit_AddColumn(o) + sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale) + sql = "ADD #{quote_column_name(o.name)} #{sql_type}" + add_column_options!(sql, column_options(o)) + end + private def visit_AlterTable(o) @@ -123,12 +127,6 @@ module ActiveRecord sql << o.adds.map { |col| visit_AddColumn col }.join(' ') end - def visit_AddColumn(o) - sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale) - sql = "ADD #{quote_column_name(o.name)} #{sql_type}" - add_column_options!(sql, column_options(o)) - end - def visit_ColumnDefinition(o) sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale) column_sql = "#{quote_column_name(o.name)} #{sql_type}" @@ -149,6 +147,8 @@ module ActiveRecord column_options[:null] = o.null unless o.null.nil? column_options[:default] = o.default unless o.default.nil? column_options[:column] = o + column_options[:first] = o.first + column_options[:after] = o.after column_options end @@ -164,9 +164,20 @@ module ActiveRecord @conn.type_to_sql type.to_sym, limit, precision, scale end - def add_column_options!(column_sql, column_options) - @conn.add_column_options! column_sql, column_options - column_sql + def add_column_options!(sql, options) + sql << " DEFAULT #{@conn.quote(options[:default], options[:column])}" if options_include_default?(options) + # must explicitly check for :null to allow change_column to work on migrations + if options[:null] == false + sql << " NOT NULL" + end + if options[:auto_increment] == true + sql << " AUTO_INCREMENT" + end + sql + end + + def options_include_default?(options) + options.include?(:default) && !(options[:null] == false && options[:default].nil?) end end @@ -294,8 +305,8 @@ module ActiveRecord # QUOTING ================================================== - # Returns a bind substitution value given a +column+ and list of current - # +binds+. + # Returns a bind substitution value given a bind +index+ and +column+ + # NOTE: The column param is currently being used by the sqlserver-adapter def substitute_at(column, index) Arel::Nodes::BindParam.new '?' end @@ -374,20 +385,6 @@ module ActiveRecord @transaction.number end - def increment_open_transactions - ActiveSupport::Deprecation.warn "#increment_open_transactions is deprecated and has no effect" - end - - def decrement_open_transactions - ActiveSupport::Deprecation.warn "#decrement_open_transactions is deprecated and has no effect" - end - - def transaction_joinable=(joinable) - message = "#transaction_joinable= is deprecated. Please pass the :joinable option to #begin_transaction instead." - ActiveSupport::Deprecation.warn message - @transaction.joinable = joinable - end - def create_savepoint end 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 d098ded77c..5b25b26164 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -4,17 +4,26 @@ module ActiveRecord module ConnectionAdapters class AbstractMysqlAdapter < AbstractAdapter class SchemaCreation < AbstractAdapter::SchemaCreation - private def visit_AddColumn(o) - add_column_position!(super, o) + add_column_position!(super, column_options(o)) + end + + private + def visit_ChangeColumnDefinition(o) + column = o.column + options = o.options + sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale]) + change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}" + add_column_options!(change_column_sql, options) + add_column_position!(change_column_sql, options) end - def add_column_position!(sql, column) - if column.first + def add_column_position!(sql, options) + if options[:first] sql << " FIRST" - elsif column.after - sql << " AFTER #{quote_column_name(column.after)}" + elsif options[:after] + sql << " AFTER #{quote_column_name(options[:after])}" end sql end @@ -661,10 +670,9 @@ module ActiveRecord end def add_column_sql(table_name, column_name, type, options = {}) - add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" - add_column_options!(add_column_sql, options) - add_column_position!(add_column_sql, options) - add_column_sql + td = create_table_definition table_name, options[:temporary], options[:options] + cd = td.new_column_definition(column_name, type, options) + schema_creation.visit_AddColumn cd end def change_column_sql(table_name, column_name, type, options = {}) @@ -678,14 +686,12 @@ module ActiveRecord options[:null] = column.null end - change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" - add_column_options!(change_column_sql, options) - add_column_position!(change_column_sql, options) - change_column_sql + options[:name] = column.name + schema_creation.accept ChangeColumnDefinition.new column, type, options end def rename_column_sql(table_name, column_name, new_column_name) - options = {} + options = { name: new_column_name } if column = columns(table_name).find { |c| c.name == column_name.to_s } options[:default] = column.default @@ -696,9 +702,7 @@ module ActiveRecord end current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"] - rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}" - add_column_options!(rename_column_sql, options) - rename_column_sql + schema_creation.accept ChangeColumnDefinition.new column, current_type, options end def remove_column_sql(table_name, column_name, type = nil, options = {}) diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 609ccc2ed2..cc02b313e1 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -107,30 +107,6 @@ module ActiveRecord end end - def type_cast_code(var_name) - message = "Column#type_cast_code is deprecated in favor of using Column#type_cast only, " \ - "and it is going to be removed in future Rails versions." - ActiveSupport::Deprecation.warn message - - klass = self.class.name - - case type - when :string, :text then var_name - when :integer then "#{klass}.value_to_integer(#{var_name})" - when :float then "#{var_name}.to_f" - when :decimal then "#{klass}.value_to_decimal(#{var_name})" - when :datetime, :timestamp then "#{klass}.string_to_time(#{var_name})" - when :time then "#{klass}.string_to_dummy_time(#{var_name})" - when :date then "#{klass}.value_to_date(#{var_name})" - when :binary then "#{klass}.binary_to_string(#{var_name})" - when :boolean then "#{klass}.value_to_boolean(#{var_name})" - 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})" - else var_name - end - end - # Returns the human name of the column name. # # ===== Examples diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 530a27d099..28c7cff1cc 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -1,6 +1,6 @@ require 'active_record/connection_adapters/abstract_mysql_adapter' -gem 'mysql2', '~> 0.3.10' +gem 'mysql2', '~> 0.3.13' require 'mysql2' module ActiveRecord @@ -213,9 +213,11 @@ module ActiveRecord # Executes the SQL statement in the context of this connection. def execute(sql, name = nil) - # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been - # made since we established the connection - @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone + if @connection + # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been + # made since we established the connection + @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone + end super end @@ -227,8 +229,7 @@ module ActiveRecord alias exec_without_stmt exec_query - # Returns an array of record hashes with the column names as keys and - # column values as values. + # Returns an ActiveRecord::Result instance. def select(sql, name = nil, binds = []) exec_query(sql, name) end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb index 9b5170f657..751655e61c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -218,13 +218,6 @@ module ActiveRecord execute "ROLLBACK" end - def outside_transaction? - message = "#outside_transaction? is deprecated. This method was only really used " \ - "internally, but you can use #transaction_open? instead." - ActiveSupport::Deprecation.warn message - @connection.transaction_status == PGconn::PQTRANS_IDLE - end - def create_savepoint execute("SAVEPOINT #{current_savepoint_name}") end 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 a651b6c32e..3fce8de1ba 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -384,8 +384,9 @@ module ActiveRecord def change_column(table_name, column_name, type, options = {}) clear_cache! quoted_table_name = quote_table_name(table_name) - - execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" + sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale]) + sql_type << "[]" if options[:array] + execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}" change_column_default(table_name, column_name, options[:default]) if options_include_default?(options) change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index d5a603cadc..98126249df 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -84,7 +84,7 @@ module ActiveRecord $1 # Character types when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m - $1 + $1.gsub(/''/, "'") # Binary data types when /\A'(.*)'::bytea\z/m $1 diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb index 1d7a22e831..e5c9f6f54a 100644 --- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb @@ -1,4 +1,3 @@ -require 'active_support/deprecation/reporting' module ActiveRecord module ConnectionAdapters @@ -16,13 +15,8 @@ module ActiveRecord prepare_default_proc end - def primary_keys(table_name = nil) - if table_name - @primary_keys[table_name] - else - ActiveSupport::Deprecation.warn('call primary_keys with a table name!') - @primary_keys.dup - end + def primary_keys(table_name) + @primary_keys[table_name] end # A cached lookup for table existence. @@ -41,34 +35,19 @@ module ActiveRecord end end - def tables(name = nil) - if name - @tables[name] - else - ActiveSupport::Deprecation.warn('call tables with a name!') - @tables.dup - end + def tables(name) + @tables[name] end # Get the columns for a table - def columns(table = nil) - if table - @columns[table] - else - ActiveSupport::Deprecation.warn('call columns with a table name!') - @columns.dup - end + def columns(table) + @columns[table] end # Get the columns for a table as a hash, key is the column name # value is the column object. - def columns_hash(table = nil) - if table - @columns_hash[table] - else - ActiveSupport::Deprecation.warn('call columns_hash with a table name!') - @columns_hash.dup - end + def columns_hash(table) + @columns_hash[table] end # Clears out internal caches diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index ba053700f2..e088021112 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -69,13 +69,10 @@ module ActiveRecord mattr_accessor :timestamped_migrations, instance_writer: false self.timestamped_migrations = true - ## - # :singleton-method: - # Disable implicit join references. This feature was deprecated with Rails 4. - # If you don't make use of implicit references but still see deprecation warnings - # you can disable the feature entirely. This will be the default with Rails 4.1. - mattr_accessor :disable_implicit_join_references, instance_writer: false - self.disable_implicit_join_references = false + def self.disable_implicit_join_references=(value) + ActiveSupport::Deprecation.warn("Implicit join references were removed with Rails 4.1." \ + "Make sure to remove this configuration because it does nothing.") + end class_attribute :default_connection_handler, instance_writer: false @@ -91,20 +88,8 @@ module ActiveRecord end module ClassMethods - def inherited(child_class) #:nodoc: - child_class.initialize_generated_modules - super - end - def initialize_generated_modules - @attribute_methods_mutex = Mutex.new - - # force attribute methods to be higher in inheritance hierarchy than other generated methods - generated_attribute_methods.const_set(:AttrNames, Module.new { - def self.const_missing(name) - const_set(name, [name.to_s.sub(/ATTR_/, '')].pack('h*').freeze) - end - }) + super generated_feature_methods end @@ -123,6 +108,8 @@ module ActiveRecord super elsif abstract_class? "#{super}(abstract)" + elsif !connected? + "#{super}(no database connection)" elsif table_exists? attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', ' "#{super}(#{attr_list})" @@ -153,13 +140,13 @@ module ActiveRecord else superclass.arel_engine end - end + end end private def relation #:nodoc: - relation = Relation.new(self, arel_table) + relation = Relation.create(self, arel_table) if finder_needs_type_condition? relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name) @@ -338,14 +325,6 @@ module ActiveRecord @readonly = true end - # Returns the connection currently associated with the class. This can - # also be used to "borrow" the connection to do database work that isn't - # easily done without going straight to SQL. - def connection - ActiveSupport::Deprecation.warn("#connection is deprecated in favour of accessing it via the class") - self.class.connection - end - def connection_handler self.class.connection_handler end diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index 81cca37939..e1faadf1ab 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -50,7 +50,7 @@ module ActiveRecord # ==== Parameters # # * +id+ - The id of the object you wish to update a counter on or an Array of ids. - # * +counters+ - An Array of Hashes containing the names of the fields + # * +counters+ - A Hash containing the names of the fields # to update as keys and the amount to update the field by as values. # # ==== Examples diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 33e19313a0..47d4f3637f 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -379,12 +379,6 @@ module ActiveRecord @@all_cached_fixtures = Hash.new { |h,k| h[k] = {} } - def self.find_table_name(fixture_set_name) # :nodoc: - ActiveSupport::Deprecation.warn( - "ActiveRecord::Fixtures.find_table_name is deprecated and shall be removed from future releases. Use ActiveRecord::Fixtures.default_fixture_model_name instead.") - default_fixture_model_name(fixture_set_name) - end - def self.default_fixture_model_name(fixture_set_name) # :nodoc: ActiveRecord::Base.pluralize_table_names ? fixture_set_name.singularize.camelize : @@ -841,8 +835,6 @@ module ActiveRecord end def setup_fixtures - return if ActiveRecord::Base.configurations.blank? - if pre_loaded_fixtures && !use_transactional_fixtures raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures' end @@ -875,8 +867,6 @@ module ActiveRecord end def teardown_fixtures - return if ActiveRecord::Base.configurations.blank? - # Rollback changes if a transaction is active. if run_in_transaction? @fixture_connections.each do |connection| diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 40976bc29e..e826762def 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -5,7 +5,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - # Determine whether to store the full constant name including namespace when using STI + # Determines whether to store the full constant name including namespace when using STI. class_attribute :store_full_sti_class, instance_writer: false self.store_full_sti_class = true end @@ -13,7 +13,7 @@ module ActiveRecord module ClassMethods # Determines if one of the attributes passed in is the inheritance column, # and if the inheritance column is attr accessible, it initializes an - # instance of the given subclass instead of the base class + # instance of the given subclass instead of the base class. def new(*args, &block) if abstract_class? || self == Base raise NotImplementedError, "#{self} is an abstract class and can not be instantiated." @@ -27,7 +27,8 @@ module ActiveRecord super end - # True if this isn't a concrete subclass needing a STI type condition. + # Returns +true+ if this does not need STI type condition. Returns + # +false+ if STI type condition needs to be applied. def descends_from_active_record? if self == Base false diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 209de78898..626fe40103 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -82,7 +82,7 @@ module ActiveRecord stmt = relation.where( relation.table[self.class.primary_key].eq(id).and( - relation.table[lock_col].eq(self.class.quote_value(previous_lock_value)) + relation.table[lock_col].eq(self.class.quote_value(previous_lock_value, column_for_attribute(lock_col))) ) ).arel.compile_update(arel_attributes_with_values_for_update(attribute_names)) @@ -138,6 +138,7 @@ module ActiveRecord # Set the column to use for optimistic locking. Defaults to +lock_version+. def locking_column=(value) + @column_defaults = nil @locking_column = value.to_s end diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index 61e5c120df..0358a36b14 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -41,7 +41,7 @@ module ActiveRecord return if IGNORE_PAYLOAD_NAMES.include?(payload[:name]) name = "#{payload[:name]} (#{event.duration.round(1)}ms)" - sql = payload[:sql].squeeze(' ') + sql = payload[:sql] binds = nil unless (payload[:binds] || []).empty? diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index e96c347f6f..19c6f8148b 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -32,7 +32,7 @@ module ActiveRecord class PendingMigrationError < ActiveRecordError#:nodoc: def initialize - super("Migrations are pending; run 'rake db:migrate RAILS_ENV=#{Rails.env}' to resolve this issue.") + super("Migrations are pending; run 'bin/rake db:migrate RAILS_ENV=#{::Rails.env}' to resolve this issue.") end end @@ -866,13 +866,7 @@ module ActiveRecord @direction = direction @target_version = target_version @migrated_versions = nil - - if Array(migrations).grep(String).empty? - @migrations = migrations - else - ActiveSupport::Deprecation.warn "instantiate this class with a list of migrations" - @migrations = self.class.migrations(migrations) - end + @migrations = migrations validate(@migrations) @@ -906,15 +900,7 @@ module ActiveRecord raise UnknownMigrationVersionError.new(@target_version) end - running = runnable - - if block_given? - message = "block argument to migrate is deprecated, please filter migrations before constructing the migrator" - ActiveSupport::Deprecation.warn message - running.select! { |m| yield m } - end - - running.each do |migration| + runnable.each do |migration| Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger begin diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index ac2d2f2712..23541d1d27 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -124,7 +124,7 @@ module ActiveRecord @quoted_table_name = nil @arel_table = nil @sequence_name = nil unless defined?(@explicit_sequence_name) && @explicit_sequence_name - @relation = Relation.new(self, arel_table) + @relation = Relation.create(self, arel_table) end # Returns a quoted version of the table name, used to construct SQL statements. @@ -253,19 +253,6 @@ module ActiveRecord @content_columns ||= columns.reject { |c| c.primary || c.name =~ /(_id|_count)$/ || c.name == inheritance_column } end - # Returns a hash of all the methods added to query each of the columns in the table with the name of the method as the key - # and true as the value. This makes it possible to do O(1) lookups in respond_to? to check if a given method for attribute - # is available. - def column_methods_hash #:nodoc: - @dynamic_methods_hash ||= column_names.each_with_object(Hash.new(false)) do |attr, methods| - attr_name = attr.to_s - methods[attr.to_sym] = attr_name - methods["#{attr}=".to_sym] = attr_name - methods["#{attr}?".to_sym] = attr_name - methods["#{attr}_before_type_cast".to_sym] = attr_name - end - end - # Resets all the cached information about columns, which will cause them # to be reloaded on the next request. # diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 8bdaeef924..e53e8553ad 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -230,6 +230,10 @@ module ActiveRecord # validates_presence_of :member # end # + # Note that if you do not specify the <tt>inverse_of</tt> option, then + # Active Record will try to automatically guess the inverse association + # based on heuristics. + # # For one-to-one nested associations, if you build the new (in-memory) # child object yourself before assignment, then this module will not # overwrite it, e.g.: @@ -302,14 +306,9 @@ module ActiveRecord attr_names.each do |association_name| if reflection = reflect_on_association(association_name) - reflection.options[:autosave] = true + reflection.autosave = true add_autosave_association_callbacks(reflection) - # Clear cached values of any inverse associations found in the - # reflection and prevent the reflection from finding inverses - # automatically in the future. - reflection.remove_automatic_inverse_of! - nested_attributes_options = self.nested_attributes_options.dup nested_attributes_options[association_name.to_sym] = options self.nested_attributes_options = nested_attributes_options diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index 3d85898c41..6bee4f38e7 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -1,15 +1,16 @@ module ActiveRecord module Querying - delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :to => :all - delegate :first_or_create, :first_or_create!, :first_or_initialize, :to => :all - delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, :to => :all - delegate :find_by, :find_by!, :to => :all - delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :all - delegate :find_each, :find_in_batches, :to => :all + delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, to: :all + delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all + delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all + delegate :find_by, :find_by!, to: :all + delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, to: :all + delegate :find_each, :find_in_batches, to: :all delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, - :having, :create_with, :uniq, :distinct, :references, :none, :unscope, :to => :all - delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :ids, :to => :all + :having, :create_with, :uniq, :distinct, :references, :none, :unscope, to: :all + delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all + delegate :pluck, :ids, to: :all # Executes a custom SQL query against your database and returns all the results. The results will # be returned as an array with columns requested encapsulated as attributes of the model you call diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 31a0ace864..269f9f975b 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -37,16 +37,21 @@ module ActiveRecord rake_tasks do require "active_record/base" - ActiveRecord::Tasks::DatabaseTasks.env = Rails.env - ActiveRecord::Tasks::DatabaseTasks.db_dir = Rails.application.config.paths["db"].first ActiveRecord::Tasks::DatabaseTasks.seed_loader = Rails.application - ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration - ActiveRecord::Tasks::DatabaseTasks.migrations_paths = Rails.application.paths['db/migrate'].to_a - ActiveRecord::Tasks::DatabaseTasks.fixtures_path = File.join Rails.root, 'test', 'fixtures' + ActiveRecord::Tasks::DatabaseTasks.env = Rails.env + + namespace :db do + task :load_config do + ActiveRecord::Tasks::DatabaseTasks.db_dir = Rails.application.config.paths["db"].first + ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration + ActiveRecord::Tasks::DatabaseTasks.migrations_paths = Rails.application.paths['db/migrate'].to_a + ActiveRecord::Tasks::DatabaseTasks.fixtures_path = File.join Rails.root, 'test', 'fixtures' - if defined?(APP_RAKEFILE) && engine = Rails::Engine.find(find_engine_path(APP_RAKEFILE)) - if engine.paths['db/migrate'].existent - ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths['db/migrate'].to_a + if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH) + if engine.paths['db/migrate'].existent + ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths['db/migrate'].to_a + end + end end end @@ -106,56 +111,6 @@ module ActiveRecord initializer "active_record.set_configs" do |app| ActiveSupport.on_load(:active_record) do - begin - old_behavior, ActiveSupport::Deprecation.behavior = ActiveSupport::Deprecation.behavior, :stderr - whitelist_attributes = app.config.active_record.delete(:whitelist_attributes) - - if respond_to?(:mass_assignment_sanitizer=) - mass_assignment_sanitizer = nil - else - mass_assignment_sanitizer = app.config.active_record.delete(:mass_assignment_sanitizer) - end - - unless whitelist_attributes.nil? && mass_assignment_sanitizer.nil? - ActiveSupport::Deprecation.warn <<-EOF.strip_heredoc, [] - Model based mass assignment security has been extracted - out of Rails into a gem. Please use the new recommended protection model for - params or add `protected_attributes` to your Gemfile to use the old one. - - To disable this message remove the `whitelist_attributes` option from your - `config/application.rb` file and any `mass_assignment_sanitizer` options - from your `config/environments/*.rb` files. - - See http://guides.rubyonrails.org/security.html#mass-assignment for more information. - EOF - end - - unless app.config.active_record.delete(:auto_explain_threshold_in_seconds).nil? - ActiveSupport::Deprecation.warn <<-EOF.strip_heredoc, [] - The Active Record auto explain feature has been removed. - - To disable this message remove the `active_record.auto_explain_threshold_in_seconds` - option from the `config/environments/*.rb` config file. - - See http://guides.rubyonrails.org/4_0_release_notes.html for more information. - EOF - end - - unless app.config.active_record.delete(:observers).nil? - ActiveSupport::Deprecation.warn <<-EOF.strip_heredoc, [] - Active Record Observers has been extracted out of Rails into a gem. - Please use callbacks or add `rails-observers` to your Gemfile to use observers. - - To disable this message remove the `observers` option from your - `config/application.rb` or from your initializers. - - See http://guides.rubyonrails.org/4_0_release_notes.html for more information. - EOF - end - ensure - ActiveSupport::Deprecation.behavior = old_behavior - end - app.config.active_record.each do |k,v| send "#{k}=", v end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index f69e9b2217..8a311039d5 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -12,7 +12,7 @@ db_namespace = namespace :db do end end - desc 'Create the database from DATABASE_URL or config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config)' + desc 'Create the database from DATABASE_URL or config/database.yml for the current Rails.env (use db:create:all to create all databases in the config)' task :create => [:load_config] do if ENV['DATABASE_URL'] ActiveRecord::Tasks::DatabaseTasks.create_database_url @@ -172,7 +172,7 @@ db_namespace = namespace :db do end end - desc 'Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the db first)' + desc 'Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the database first)' task :setup => ['db:schema:load_if_ruby', 'db:structure:load_if_sql', :seed] desc 'Load the seed data from db/seeds.rb' @@ -236,7 +236,7 @@ db_namespace = namespace :db do end namespace :schema do - desc 'Create a db/schema.rb file that can be portably used against any DB supported by AR' + desc 'Create a db/schema.rb file that is portable against any DB supported by AR' task :dump => [:environment, :load_config] do require 'active_record/schema_dumper' filename = ENV['SCHEMA'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, 'schema.rb') @@ -324,7 +324,7 @@ db_namespace = namespace :db do ActiveRecord::Schema.verbose = false db_namespace["schema:load"].invoke ensure - ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[Rails.env]) + ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env]) end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 27aa20b6c0..73d154e03e 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -5,7 +5,30 @@ module ActiveRecord included do class_attribute :reflections + class_attribute :aggregate_reflections self.reflections = {} + self.aggregate_reflections = {} + end + + def self.create(macro, name, scope, options, ar) + case macro + when :has_and_belongs_to_many + klass = AssociationReflection + when :has_many, :belongs_to, :has_one + klass = options[:through] ? ThroughReflection : AssociationReflection + when :composed_of + klass = AggregateReflection + end + + klass.new(macro, name, scope, options, ar) + end + + def self.add_reflection(ar, name, reflection) + if reflection.class == AggregateReflection + ar.aggregate_reflections = ar.aggregate_reflections.merge(name => reflection) + else + ar.reflections = ar.reflections.merge(name => reflection) + end end # \Reflection enables to interrogate Active Record classes and objects @@ -17,23 +40,9 @@ module ActiveRecord # MacroReflection class has info for AggregateReflection and AssociationReflection # classes. module ClassMethods - def create_reflection(macro, name, scope, options, active_record) - case macro - when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many - klass = options[:through] ? ThroughReflection : AssociationReflection - when :composed_of - klass = AggregateReflection - end - - reflection = klass.new(macro, name, scope, options, active_record) - - self.reflections = self.reflections.merge(name => reflection) - reflection - end - # Returns an array of AggregateReflection objects for all the aggregations in the class. def reflect_on_all_aggregations - reflections.values.grep(AggregateReflection) + aggregate_reflections.values end # Returns the AggregateReflection object for the named +aggregation+ (use the symbol). @@ -41,8 +50,7 @@ module ActiveRecord # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection # def reflect_on_aggregation(aggregation) - reflection = reflections[aggregation] - reflection if reflection.is_a?(AggregateReflection) + aggregate_reflections[aggregation] end # Returns an array of AssociationReflection objects for all the @@ -56,7 +64,7 @@ module ActiveRecord # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations # def reflect_on_all_associations(macro = nil) - association_reflections = reflections.values.grep(AssociationReflection) + association_reflections = reflections.values macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections end @@ -66,8 +74,7 @@ module ActiveRecord # Invoice.reflect_on_association(:line_items).macro # returns :has_many # def reflect_on_association(association) - reflection = reflections[association] - reflection if reflection.is_a?(AssociationReflection) + reflections[association] end # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled. @@ -118,6 +125,11 @@ module ActiveRecord name.to_s.pluralize : name.to_s end + def autosave=(autosave) + @automatic_inverse_of = false + @options[:autosave] = autosave + end + # Returns the class for the macro. # # <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class @@ -179,10 +191,14 @@ module ActiveRecord @klass ||= active_record.send(:compute_type, class_name) end - def initialize(*args) + attr_reader :type, :foreign_type + + def initialize(macro, name, scope, options, active_record) super @collection = [:has_many, :has_and_belongs_to_many].include?(macro) @automatic_inverse_of = nil + @type = options[:as] && "#{options[:as]}_type" + @foreign_type = options[:foreign_type] || "#{name}_type" end # Returns a new, unsaved instance of the associated class. +attributes+ will @@ -192,11 +208,11 @@ module ActiveRecord end def table_name - @table_name ||= klass.table_name + klass.table_name end def quoted_table_name - @quoted_table_name ||= klass.quoted_table_name + klass.quoted_table_name end def join_table @@ -207,16 +223,8 @@ module ActiveRecord @foreign_key ||= options[:foreign_key] || derive_foreign_key end - def foreign_type - @foreign_type ||= options[:foreign_type] || "#{name}_type" - end - - def type - @type ||= options[:as] && "#{options[:as]}_type" - end - def primary_key_column - @primary_key_column ||= klass.columns.find { |c| c.name == klass.primary_key } + klass.columns_hash[klass.primary_key] end def association_foreign_key @@ -240,14 +248,6 @@ module ActiveRecord end end - def columns(tbl_name) - @columns ||= klass.connection.columns(tbl_name) - end - - def reset_column_information - @columns = nil - end - def check_validity! check_validity_of_inverse! @@ -269,7 +269,7 @@ module ActiveRecord end def source_reflection - nil + self end # A chain of reflections from this one back to the owner. For more see the explanation in @@ -291,30 +291,13 @@ module ActiveRecord alias :source_macro :macro def has_inverse? - @options[:inverse_of] || find_inverse_of_automatically + inverse_name end def inverse_of - @inverse_of ||= if options[:inverse_of] - klass.reflect_on_association(options[:inverse_of]) - else - find_inverse_of_automatically - end - end + return unless inverse_name - # Clears the cached value of +@inverse_of+ on this object. This will - # not remove the :inverse_of option however, so future calls on the - # +inverse_of+ will have to recompute the inverse. - def clear_inverse_of_cache! - @inverse_of = nil - end - - # Removes the cached inverse association that was found automatically - # and prevents this object from finding the inverse association - # automatically in the future. - def remove_automatic_inverse_of! - @automatic_inverse_of = nil - options[:automatic_inverse_of] = false + @inverse_of ||= klass.reflect_on_association inverse_name end def polymorphic_inverse_of(associated_class) @@ -388,27 +371,28 @@ module ActiveRecord VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to] INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key] + protected + + def actual_source_reflection # FIXME: this is a horrible name + self + end + private - # Attempts to find the inverse association automatically. - # If it cannot find a suitable inverse association, it returns + # Attempts to find the inverse association name automatically. + # If it cannot find a suitable inverse association name, it returns # nil. - def find_inverse_of_automatically - if @automatic_inverse_of == false - nil - elsif @automatic_inverse_of.nil? - set_automatic_inverse_of - else - klass.reflect_on_association(@automatic_inverse_of) + def inverse_name + options.fetch(:inverse_of) do + if @automatic_inverse_of == false + nil + else + @automatic_inverse_of ||= automatic_inverse_of + end end end - # Sets the +@automatic_inverse_of+ instance variable, and returns - # either nil or the inverse association that it finds. - # - # This method caches the inverse association that is found so that - # future calls to +find_inverse_of_automatically+ have much less - # overhead. - def set_automatic_inverse_of + # returns either nil or the inverse association name that it finds. + def automatic_inverse_of if can_find_inverse_of_automatically?(self) inverse_name = active_record.name.downcase.to_sym @@ -421,16 +405,11 @@ module ActiveRecord end if valid_inverse_reflection?(reflection) - @automatic_inverse_of = inverse_name - reflection - else - @automatic_inverse_of = false - nil + return inverse_name end - else - @automatic_inverse_of = false - nil end + + false end # Checks if the inverse reflection that is returned from the @@ -442,22 +421,23 @@ module ActiveRecord # from calling +klass+, +reflection+ will already be set to false. def valid_inverse_reflection?(reflection) reflection && - klass.name == reflection.active_record.try(:name) && + klass.name == reflection.active_record.name && klass.primary_key == reflection.active_record_primary_key && can_find_inverse_of_automatically?(reflection) end # Checks to see if the reflection doesn't have any options that prevent # us from being able to guess the inverse automatically. First, the - # +automatic_inverse_of+ option cannot be set to false. Second, we must - # have +has_many+, +has_one+, +belongs_to+ associations. Third, we must - # not have options such as +:polymorphic+ or +:foreign_key+ which prevent us - # from correctly guessing the inverse association. + # <tt>inverse_of</tt> option cannot be set to false. Second, we must + # have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations. + # Third, we must not have options such as <tt>:polymorphic</tt> or + # <tt>:foreign_key</tt> which prevent us from correctly guessing the + # inverse association. # # Anything with a scope can additionally ruin our attempt at finding an # inverse, so we exclude reflections with scopes. def can_find_inverse_of_automatically?(reflection) - reflection.options[:automatic_inverse_of] != false && + reflection.options[:inverse_of] != false && VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) && !INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } && !reflection.scope @@ -494,6 +474,11 @@ module ActiveRecord delegate :foreign_key, :foreign_type, :association_foreign_key, :active_record_primary_key, :type, :to => :source_reflection + def initialize(macro, name, scope, options, active_record) + super + @source_reflection_name = options[:source] + end + # Returns the source of the through reflection. It checks both a singularized # and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>. # @@ -512,7 +497,7 @@ module ActiveRecord # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags"> # def source_reflection - @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first + through_reflection.klass.reflect_on_association(source_reflection_name) end # Returns the AssociationReflection object specified in the <tt>:through</tt> option @@ -528,7 +513,7 @@ module ActiveRecord # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @active_record=Post, @plural_name="taggings"> # def through_reflection - @through_reflection ||= active_record.reflect_on_association(options[:through]) + active_record.reflect_on_association(options[:through]) end # Returns an array of reflections which are involved in this association. Each item in the @@ -550,7 +535,9 @@ module ActiveRecord # def chain @chain ||= begin - chain = source_reflection.chain + through_reflection.chain + a = source_reflection.chain + b = through_reflection.chain + chain = a + b chain[0] = self # Use self so we don't lose the information from :source_type chain end @@ -601,7 +588,7 @@ module ActiveRecord # A through association is nested if there would be more than one join table def nested? - chain.length > 2 || through_reflection.macro == :has_and_belongs_to_many + chain.length > 2 || through_reflection.has_and_belongs_to_many? end # We want to use the klass from this reflection, rather than just delegate straight to @@ -610,12 +597,7 @@ module ActiveRecord def association_primary_key(klass = nil) # Get the "actual" source reflection if the immediate source reflection has a # source reflection itself - source_reflection = self.source_reflection - while source_reflection.source_reflection - source_reflection = source_reflection.source_reflection - end - - source_reflection.options[:primary_key] || primary_key(klass || self.klass) + actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass) end # Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form. @@ -630,7 +612,32 @@ module ActiveRecord # # => [:tag, :tags] # def source_reflection_names - @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym } + (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }.uniq + end + + def source_reflection_name # :nodoc: + return @source_reflection_name if @source_reflection_name + + names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq + names = names.find_all { |n| + through_reflection.klass.reflect_on_association(n) + } + + if names.length > 1 + example_options = options.dup + example_options[:source] = source_reflection_names.first + ActiveSupport::Deprecation.warn <<-eowarn +Ambiguous source reflection for through association. Please specify a :source +directive on your declaration like: + + class #{active_record.name} < ActiveRecord::Base + #{macro} :#{name}, #{example_options} + end + + eowarn + end + + @source_reflection_name = names.first end def source_options @@ -669,6 +676,12 @@ module ActiveRecord check_validity_of_inverse! end + protected + + def actual_source_reflection # FIXME: this is a horrible name + source_reflection.actual_source_reflection + end + private def derive_class_name # get the class_name of the belongs_to association of the through reflection diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index d54479edbb..4e86e905ed 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -17,17 +17,14 @@ module ActiveRecord include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation attr_reader :table, :klass, :loaded - attr_accessor :default_scoped alias :model :klass alias :loaded? :loaded - alias :default_scoped? :default_scoped def initialize(klass, table, values = {}) - @klass = klass - @table = table - @values = values - @loaded = false - @default_scoped = false + @klass = klass + @table = table + @values = values + @loaded = false end def initialize_copy(other) @@ -247,7 +244,7 @@ module ActiveRecord def empty? return @records.empty? if loaded? - c = count + c = count(:all) c.respond_to?(:zero?) ? c.zero? : c.empty? end @@ -313,7 +310,7 @@ module ActiveRecord stmt.table(table) stmt.key = table[primary_key] - if with_default_scope.joins_values.any? + if joins_values.any? @klass.connection.join_to_update(stmt, arel) else stmt.take(arel.limit) @@ -438,7 +435,7 @@ module ActiveRecord stmt = Arel::DeleteManager.new(arel.engine) stmt.from(table) - if with_default_scope.joins_values.any? + if joins_values.any? @klass.connection.join_to_delete(stmt, arel, table[primary_key]) else stmt.wheres = arel.constraints @@ -504,7 +501,22 @@ module ActiveRecord # 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) + @to_sql ||= begin + relation = self + connection = klass.connection + visitor = connection.visitor + + if eager_loading? + join_dependency = construct_join_dependency + relation = construct_relation_for_association_find(join_dependency) + end + + ast = relation.arel.ast + binds = relation.bind_values.dup + visitor.accept(ast) do + connection.quote(*binds.shift.reverse) + end + end end # Returns a hash of where conditions. @@ -512,12 +524,11 @@ module ActiveRecord # User.where(name: 'Oscar').where_values_hash # # => {name: "Oscar"} def where_values_hash - scope = with_default_scope - equalities = scope.where_values.grep(Arel::Nodes::Equality).find_all { |node| + equalities = where_values.grep(Arel::Nodes::Equality).find_all { |node| node.left.relation.name == table_name } - binds = Hash[scope.bind_values.find_all(&:first).map { |column, v| [column.name, v] }] + binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }] binds.merge!(Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]) Hash[equalities.map { |where| @@ -565,16 +576,6 @@ module ActiveRecord q.pp(self.to_a) end - def with_default_scope #:nodoc: - if default_scoped? && default_scope = klass.send(:build_default_scope) - default_scope = default_scope.merge(self) - default_scope.default_scoped = false - default_scope - else - self - end - end - # Returns true if relation is blank. def blank? to_a.blank? @@ -594,31 +595,23 @@ module ActiveRecord private def exec_queries - default_scoped = with_default_scope - - if default_scoped.equal?(self) - @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bind_values) - - preload = preload_values - preload += includes_values unless eager_loading? - preload.each do |associations| - ActiveRecord::Associations::Preloader.new(@records, associations).run - end + @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bind_values) - @records.each { |record| record.readonly! } if readonly_value - else - @records = default_scoped.to_a + preload = preload_values + preload += includes_values unless eager_loading? + preload.each do |associations| + ActiveRecord::Associations::Preloader.new(@records, associations).run end + @records.each { |record| record.readonly! } if readonly_value + @loaded = true @records end def references_eager_loaded_tables? joined_tables = arel.join_sources.map do |join| - if join.is_a?(Arel::Nodes::StringJoin) - tables_in_string(join.left) - else + unless join.is_a?(Arel::Nodes::StringJoin) [join.left.table_name, join.left.table_alias] end end @@ -627,41 +620,8 @@ module ActiveRecord # always convert table names to downcase as in Oracle quoted table names are in uppercase joined_tables = joined_tables.flatten.compact.map { |t| t.downcase }.uniq - string_tables = tables_in_string(to_sql) - - if (references_values - joined_tables).any? - true - elsif !ActiveRecord::Base.disable_implicit_join_references && - (string_tables - joined_tables).any? - ActiveSupport::Deprecation.warn( - "It looks like you are eager loading table(s) (one of: #{string_tables.join(', ')}) " \ - "that are referenced in a string SQL snippet. For example: \n" \ - "\n" \ - " Post.includes(:comments).where(\"comments.title = 'foo'\")\n" \ - "\n" \ - "Currently, Active Record recognizes the table in the string, and knows to JOIN the " \ - "comments table to the query, rather than loading comments in a separate query. " \ - "However, doing this without writing a full-blown SQL parser is inherently flawed. " \ - "Since we don't want to write an SQL parser, we are removing this functionality. " \ - "From now on, you must explicitly tell Active Record when you are referencing a table " \ - "from a string:\n" \ - "\n" \ - " Post.includes(:comments).where(\"comments.title = 'foo'\").references(:comments)\n" \ - "\n" \ - "If you don't rely on implicit join references you can disable the feature entirely " \ - "by setting `config.active_record.disable_implicit_join_references = true`." - ) - true - else - false - end - end - def tables_in_string(string) - return [] if string.blank? - # always convert table names to downcase as in Oracle quoted table names are in uppercase - # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries - string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_'] + (references_values - joined_tables).any? end end end diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index b921f2eddb..fd8496442e 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -11,7 +11,7 @@ module ActiveRecord # The #find_each method uses #find_in_batches with a batch size of 1000 (or as # specified by the +:batch_size+ option). # - # Person.all.find_each do |person| + # Person.find_each do |person| # person.do_awesome_stuff # end # @@ -19,47 +19,78 @@ module ActiveRecord # person.party_all_night! # end # - # You can also pass the +:start+ option to specify - # an offset to control the starting point. - def find_each(options = {}) - find_in_batches(options) do |records| - records.each { |record| yield record } - end - end - - # Yields each batch of records that was found by the find +options+ as - # an array. The size of each batch is set by the +:batch_size+ - # option; the default is 1000. + # If you do not provide a block to #find_each, it will return an Enumerator + # for chaining with other methods: + # + # Person.find_each.with_index do |person, index| + # person.award_trophy(index + 1) + # end + # + # ==== Options + # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000. + # * <tt>:start</tt> - Specifies the starting point for the batch processing. + # This is especially useful if you want multiple workers dealing with + # the same processing queue. You can make worker 1 handle all the records + # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond + # (by setting the +:start+ option on that worker). # - # You can control the starting point for the batch processing by - # supplying the +:start+ option. This is especially useful if you - # want multiple workers dealing with the same processing queue. You can - # make worker 1 handle all the records between id 0 and 10,000 and - # worker 2 handle from 10,000 and beyond (by setting the +:start+ - # option on that worker). + # # Let's process for a batch of 2000 records, skiping the first 2000 rows + # Person.find_each(start: 2000, batch_size: 2000) do |person| + # person.party_all_night! + # end # - # It's not possible to set the order. That is automatically set to + # NOTE: It's not possible to set the order. That is automatically set to # ascending on the primary key ("id ASC") to make the batch ordering # work. This also means that this method only works with integer-based - # primary keys. You can't set the limit either, that's used to control + # primary keys. + # + # NOTE: You can't set the limit either, that's used to control # the batch sizes. + def find_each(options = {}) + if block_given? + find_in_batches(options) do |records| + records.each { |record| yield record } + end + else + enum_for :find_each, options + end + end + + # Yields each batch of records that was found by the find +options+ as + # an array. # # Person.where("age > 21").find_in_batches do |group| # sleep(50) # Make sure it doesn't get too crowded in there! # group.each { |person| person.party_all_night! } # end # + # ==== Options + # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000. + # * <tt>:start</tt> - Specifies the starting point for the batch processing. + # This is especially useful if you want multiple workers dealing with + # the same processing queue. You can make worker 1 handle all the records + # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond + # (by setting the +:start+ option on that worker). + # # # Let's process the next 2000 records - # Person.all.find_in_batches(start: 2000, batch_size: 2000) do |group| + # Person.find_in_batches(start: 2000, batch_size: 2000) do |group| # group.each { |person| person.party_all_night! } # end + # + # NOTE: It's not possible to set the order. That is automatically set to + # ascending on the primary key ("id ASC") to make the batch ordering + # work. This also means that this method only works with integer-based + # primary keys. + # + # NOTE: You can't set the limit either, that's used to control + # the batch sizes. def find_in_batches(options = {}) options.assert_valid_keys(:start, :batch_size) relation = self - unless arel.orders.blank? && arel.taken.blank? - ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size") + if logger && (arel.orders.present? || arel.taken.present?) + logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size") end start = options.delete(:start) diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index ccb48247b7..52a538e5b5 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -91,20 +91,14 @@ module ActiveRecord # # Person.sum("2 * age") def calculate(operation, column_name, options = {}) - relation = with_default_scope - - if column_name.is_a?(Symbol) && attribute_aliases.key?(column_name.to_s) - column_name = attribute_aliases[column_name.to_s].to_sym + if column_name.is_a?(Symbol) && attribute_alias?(column_name) + column_name = attribute_alias(column_name) end - if relation.equal?(self) - if has_include?(column_name) - construct_relation_for_association_calculations.calculate(operation, column_name, options) - else - perform_calculation(operation, column_name, options) - end + if has_include?(column_name) + construct_relation_for_association_calculations.calculate(operation, column_name, options) else - relation.calculate(operation, column_name, options) + perform_calculation(operation, column_name, options) end end @@ -143,39 +137,32 @@ module ActiveRecord # def pluck(*column_names) column_names.map! do |column_name| - if column_name.is_a?(Symbol) - if attribute_aliases.key?(column_name.to_s) - column_name = attribute_aliases[column_name.to_s].to_sym - end - - if self.columns_hash.key?(column_name.to_s) - column_name = "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}" - end + if column_name.is_a?(Symbol) && attribute_alias?(column_name) + attribute_alias(column_name) + else + column_name.to_s end - - column_name end if has_include?(column_names.first) construct_relation_for_association_calculations.pluck(*column_names) else relation = spawn - relation.select_values = column_names + relation.select_values = column_names.map { |cn| + columns_hash.key?(cn) ? arel_table[cn] : cn + } result = klass.connection.select_all(relation.arel, nil, bind_values) columns = result.columns.map do |key| klass.column_types.fetch(key) { - result.column_types.fetch(key) { - Class.new { def type_cast(v); v; end }.new - } + result.column_types.fetch(key) { result.identity_type } } end result = result.map do |attributes| values = klass.initialize_attributes(attributes).values - columns.zip(values).map do |column, value| - column.type_cast(value) - end + iter = columns.each + values.map { |value| iter.next.type_cast value } end columns.one? ? result.map!(&:first) : result end @@ -200,22 +187,16 @@ module ActiveRecord # If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count) distinct = self.distinct_value - if options.has_key?(:distinct) - ActiveSupport::Deprecation.warn "The :distinct option for `Relation#count` is deprecated. " \ - "Please use `Relation#distinct` instead. (eg. `relation.distinct.count`)" - distinct = options[:distinct] - end if operation == "count" - column_name ||= (select_for_count || :all) + column_name ||= select_for_count unless arel.ast.grep(Arel::Nodes::OuterJoin).empty? distinct = true end column_name = primary_key if column_name == :all && distinct - - distinct = nil if column_name =~ /\s*DISTINCT\s+/i + distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i end if group_values.any? @@ -376,10 +357,12 @@ module ActiveRecord column ? column.type_cast(value) : value end + # TODO: refactor to allow non-string `select_values` (eg. Arel nodes). def select_for_count if select_values.present? - select = select_values.join(", ") - select if select !~ /[,*]/ + select_values.join(", ") + else + :all end end diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 8d6740246c..b6f80ac5c7 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -73,16 +73,8 @@ module ActiveRecord module ClassMethods # :nodoc: @@subclasses = ThreadSafe::Cache.new(:initial_capacity => 2) - def new(klass, *args) - relation = relation_class_for(klass).allocate - relation.__send__(:initialize, klass, *args) - relation - end - - # This doesn't have to be thread-safe. relation_class_for guarantees that this will only be - # called exactly once for a given const name. - def const_missing(name) - const_set(name, Class.new(self) { include ClassSpecificRelation }) + def create(klass, *args) + relation_class_for(klass).new(klass, *args) end private @@ -94,7 +86,13 @@ module ActiveRecord # This hash is keyed by klass.name to avoid memory leaks in development mode my_cache.compute_if_absent(klass_name) do # Cache#compute_if_absent guarantees that the block will only executed once for the given klass_name - const_get("#{name.gsub('::', '_')}_#{klass_name.gsub('::', '_')}", false) + subclass_name = "#{name.gsub('::', '_')}_#{klass_name.gsub('::', '_')}" + + if const_defined?(subclass_name) + const_get(subclass_name) + else + const_set(subclass_name, Class.new(self) { include ClassSpecificRelation }) + end end else ActiveRecord::Relation diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index f0edef58bf..2d3bd563ac 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -1,5 +1,7 @@ module ActiveRecord module FinderMethods + ONE_AS_ONE = '1 AS one' + # Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]). # If no record can be found for all of the listed ids, then RecordNotFound will be raised. If the primary key # is an integer, find by id coerces its arguments using +to_i+. @@ -11,9 +13,11 @@ module ActiveRecord # Person.find([1]) # returns an array for the object with ID = 1 # Person.where("administrator = 1").order("created_on DESC").find(1) # - # Note that returned records may not be in the same order as the ids you - # provide since database rows are unordered. Give an explicit <tt>order</tt> - # to ensure the results are sorted. + # <tt>ActiveRecord::RecordNotFound</tt> will be raised if one or more ids are not found. + # + # NOTE: The returned records may not be in the same order as the ids you + # provide since database rows are unordered. You'd need to provide an explicit <tt>order</tt> + # option if you want the results are sorted. # # ==== Find with lock # @@ -28,6 +32,34 @@ module ActiveRecord # person.visits += 1 # person.save! # end + # + # ==== Variations of +find+ + # + # Person.where(name: 'Spartacus', rating: 4) + # # returns a chainable list (which can be empty). + # + # Person.find_by(name: 'Spartacus', rating: 4) + # # returns the first item or nil. + # + # Person.where(name: 'Spartacus', rating: 4).first_or_initialize + # # returns the first item or returns a new instance (requires you call .save to persist against the database). + # + # Person.where(name: 'Spartacus', rating: 4).first_or_create + # # returns the first item or creates it and returns it, available since Rails 3.2.1. + # + # ==== Alternatives for +find+ + # + # Person.where(name: 'Spartacus', rating: 4).exists?(conditions = :none) + # # returns a boolean indicating if any record with the given conditions exist. + # + # Person.where(name: 'Spartacus', rating: 4).select("field1, field2, field3") + # # returns a chainable list of instances with only the mentioned fields. + # + # Person.where(name: 'Spartacus', rating: 4).ids + # # returns an Array of ids, available since Rails 3.2.1. + # + # Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2) + # # returns an Array of the required fields, available since Rails 3.1. def find(*args) if block_given? to_a.find { |*block_args| yield(*block_args) } @@ -79,13 +111,22 @@ module ActiveRecord # Person.where(["user_name = :u", { u: user_name }]).first # Person.order("created_on DESC").offset(5).first # Person.first(3) # returns the first three objects fetched by SELECT * FROM people LIMIT 3 + # + # ==== Rails 3 + # + # Person.first # SELECT "people".* FROM "people" LIMIT 1 + # + # NOTE: Rails 3 may not order this query by the primary key and the order + # will depend on the database implementation. In order to ensure that behavior, + # use <tt>User.order(:id).first</tt> instead. + # + # ==== Rails 4 + # + # Person.first # SELECT "people".* FROM "people" ORDER BY "people"."id" ASC LIMIT 1 + # def first(limit = nil) if limit - if order_values.empty? && primary_key - order(arel_table[primary_key].asc).limit(limit).to_a - else - limit(limit).to_a - end + find_first_with_limit(limit) else find_first end @@ -163,7 +204,7 @@ module ActiveRecord relation = construct_relation_for_association_find(construct_join_dependency) return false if ActiveRecord::NullRelation === relation - relation = relation.except(:select, :order).select("1 AS one").limit(1) + relation = relation.except(:select, :order).select(ONE_AS_ONE).limit(1) case conditions when Array, Hash @@ -172,7 +213,7 @@ module ActiveRecord relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none end - connection.select_value(relation, "#{name} Exists", relation.bind_values) + connection.select_value(relation.arel, "#{name} Exists", relation.bind_values) end # This method is called whenever no records are found with either a single @@ -197,7 +238,7 @@ module ActiveRecord raise RecordNotFound, error end - protected + private def find_with_associations join_dependency = construct_join_dependency @@ -205,7 +246,7 @@ module ActiveRecord if ActiveRecord::NullRelation === relation [] else - rows = connection.select_all(relation, 'SQL', relation.bind_values.dup) + rows = connection.select_all(relation.arel, 'SQL', relation.bind_values.dup) join_dependency.instantiate(rows) end end @@ -249,6 +290,12 @@ module ActiveRecord id_rows.map {|row| row[primary_key]} end + def using_limitable_reflections?(reflections) + reflections.none? { |r| r.collection? } + end + + protected + def find_with_ids(*ids) expects_array = ids.first.kind_of?(Array) return ids.first if expects_array && ids.first.empty? @@ -314,12 +361,15 @@ module ActiveRecord if loaded? @records.first else - @first ||= - if with_default_scope.order_values.empty? && primary_key - order(arel_table[primary_key].asc).limit(1).to_a.first - else - limit(1).to_a.first - end + @first ||= find_first_with_limit(1).first + end + end + + def find_first_with_limit(limit) + if order_values.empty? && primary_key + order(arel_table[primary_key].asc).limit(limit).to_a + else + limit(limit).to_a end end @@ -335,9 +385,5 @@ module ActiveRecord end end end - - def using_limitable_reflections?(reflections) - reflections.none? { |r| r.collection? } - end end end diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index c114ea0c0d..c08158d38b 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -22,7 +22,7 @@ module ActiveRecord # build a relation to merge in rather than directly merging # the values. def other - other = Relation.new(relation.klass, relation.table) + other = Relation.create(relation.klass, relation.table) hash.each { |k, v| if k == :joins if Hash === v @@ -42,10 +42,6 @@ module ActiveRecord attr_reader :relation, :values, :other def initialize(relation, other) - if other.default_scoped? && other.klass != relation.klass - other = other.with_default_scope - end - @relation = relation @values = other.values @other = other @@ -101,19 +97,35 @@ module ActiveRecord def merge_multi_values lhs_wheres = relation.where_values rhs_wheres = values[:where] || [] + lhs_binds = relation.bind_values rhs_binds = values[:bind] || [] removed, kept = partition_overwrites(lhs_wheres, rhs_wheres) - relation.where_values = kept + rhs_wheres - relation.bind_values = filter_binds(lhs_binds, removed) + rhs_binds + where_values = kept + rhs_wheres + bind_values = filter_binds(lhs_binds, removed) + rhs_binds + + conn = relation.klass.connection + bviter = bind_values.each.with_index + where_values.map! do |node| + if Arel::Nodes::Equality === node && Arel::Nodes::BindParam === node.right + (column, _), i = bviter.next + substitute = conn.substitute_at column, i + Arel::Nodes::Equality.new(node.left, substitute) + else + node + end + end + + relation.where_values = where_values + relation.bind_values = bind_values if values[:reordering] # override any order specified in the original relation relation.reorder! values[:order] elsif values[:order] - # merge in order_values from r + # merge in order_values from relation relation.order! values[:order] end diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index b7609c97b5..08ef899f68 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -1,15 +1,21 @@ module ActiveRecord class PredicateBuilder # :nodoc: + def self.resolve_column_aliases(klass, hash) + hash = hash.dup + hash.keys.grep(Symbol) do |key| + if klass.attribute_alias? key + hash[klass.attribute_alias(key)] = hash.delete key + end + end + hash + end + def self.build_from_hash(klass, attributes, default_table) queries = [] attributes.each do |column, value| table = default_table - if column.is_a?(Symbol) && klass.attribute_aliases.key?(column.to_s) - column = klass.attribute_aliases[column.to_s] - end - if value.is_a?(Hash) if value.empty? queries << '1=0' diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index ca1de2d4dc..d0c56ac3d0 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -100,6 +100,14 @@ module ActiveRecord # firing an additional query. This will often result in a # performance improvement over a simple +join+. # + # You can also specify multiple relationships, like this: + # + # users = User.includes(:address, :friends) + # + # Loading nested relationships is possible using a Hash: + # + # users = User.includes(:address, friends: [:address, :followers]) + # # === conditions # # If you want to add conditions to your included models you'll have @@ -280,16 +288,18 @@ module ActiveRecord args.flatten! validate_order_args args - references = args.reject { |arg| Arel::Node === arg } + references = args.grep(String) references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact! references!(references) if references.any? # if a symbol is given we prepend the quoted table name - args = args.map { |arg| - arg.is_a?(Symbol) ? "#{quoted_table_name}.#{arg} ASC" : arg + args.map! { |arg| + arg.is_a?(Symbol) ? + Arel::Nodes::Ascending.new(klass.arel_table[arg]) : + arg } - self.order_values = args + self.order_values + self.order_values = args.concat self.order_values self end @@ -795,14 +805,14 @@ module ActiveRecord # Returns the Arel object associated with the relation. def arel - @arel ||= with_default_scope.build_arel + @arel ||= build_arel end # Like #arel, but ignores the default scope of the model. def build_arel arel = Arel::SelectManager.new(table.engine, table) - build_joins(arel, joins_values) unless joins_values.empty? + build_joins(arel, joins_values.flatten) unless joins_values.empty? collapse_wheres(arel, (where_values - ['']).uniq) @@ -891,6 +901,7 @@ module ActiveRecord when String, Array [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))] when Hash + opts = PredicateBuilder.resolve_column_aliases klass, opts attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts) attributes.values.grep(ActiveRecord::Relation) do |rel| @@ -945,10 +956,11 @@ module ActiveRecord join_dependency.graft(*stashed_association_joins) - # FIXME: refactor this to build an AST - join_dependency.join_associations.each do |association| - association.join_to(manager) - end + joins = join_dependency.join_associations.map { |association| + association.join_constraints + }.flatten + + joins.each { |join| manager.from join } manager.join_sources.concat join_list diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index de784f9f57..2552cbd234 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -64,8 +64,7 @@ module ActiveRecord private def relation_with(values) # :nodoc: - result = Relation.new(klass, table, values) - result.default_scoped = default_scoped + result = Relation.create(klass, table, values) result.extend(*extending_values) if extending_values.any? result end diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb index bea195e9b8..253368ae5b 100644 --- a/activerecord/lib/active_record/result.rb +++ b/activerecord/lib/active_record/result.rb @@ -3,11 +3,36 @@ module ActiveRecord # This class encapsulates a Result returned from calling +exec_query+ on any # database connection adapter. For example: # - # x = ActiveRecord::Base.connection.exec_query('SELECT * FROM foo') - # x # => #<ActiveRecord::Result:0xdeadbeef> + # result = ActiveRecord::Base.connection.exec_query('SELECT id, title, body FROM posts') + # result # => #<ActiveRecord::Result:0xdeadbeef> + # + # # Get the column names of the result: + # result.columns + # # => ["id", "title", "body"] + # + # # Get the record values of the result: + # result.rows + # # => [[1, "title_1", "body_1"], + # [2, "title_2", "body_2"], + # ... + # ] + # + # # Get an array of hashes representing the result (column => value): + # result.to_hash + # # => [{"id" => 1, "title" => "title_1", "body" => "body_1"}, + # {"id" => 2, "title" => "title_2", "body" => "body_2"}, + # ... + # ] + # + # # ActiveRecord::Result also includes Enumerable. + # result.each do |row| + # puts row['title'] + " " + row['body'] + # end class Result include Enumerable + IDENTITY_TYPE = Class.new { def type_cast(v); v; end }.new # :nodoc: + attr_reader :columns, :rows, :column_types def initialize(columns, rows, column_types = {}) @@ -17,8 +42,16 @@ module ActiveRecord @column_types = column_types end + def identity_type # :nodoc: + IDENTITY_TYPE + end + def each - hash_rows.each { |row| yield row } + if block_given? + hash_rows.each { |row| yield row } + else + hash_rows.to_enum + end end def to_hash @@ -52,6 +85,7 @@ module ActiveRecord end private + def hash_rows @hash_rows ||= begin diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 0ed97b66d6..0b87ab9926 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -3,8 +3,8 @@ module ActiveRecord extend ActiveSupport::Concern module ClassMethods - def quote_value(value, column = nil) #:nodoc: - connection.quote(value,column) + def quote_value(value, column) #:nodoc: + connection.quote(value, column) end # Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>. @@ -86,6 +86,7 @@ module ActiveRecord # { address: Address.new("123 abc st.", "chicago") } # # => "address_street='123 abc st.' and address_city='chicago'" def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name) + attrs = PredicateBuilder.resolve_column_aliases self, attrs attrs = expand_hash_conditions_for_aggregates(attrs) table = Arel::Table.new(table_name, arel_engine).alias(default_table_name) diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index d37d33d552..a5d6aad3f0 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -8,14 +8,6 @@ module ActiveRecord class_attribute :default_scopes, instance_writer: false, instance_predicate: false self.default_scopes = [] - - def self.default_scopes? - ActiveSupport::Deprecation.warn( - "#default_scopes? is deprecated. Do something like #default_scopes.empty? instead." - ) - - !!self.default_scopes - end end module ClassMethods @@ -91,12 +83,11 @@ module ActiveRecord scope = Proc.new if block_given? if scope.is_a?(Relation) || !scope.respond_to?(:call) - ActiveSupport::Deprecation.warn( - "Calling #default_scope without a block is deprecated. For example instead " \ + raise ArgumentError, + "Support for calling #default_scope without a block is removed. For example instead " \ "of `default_scope where(color: 'red')`, please use " \ "`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \ "self.default_scope.)" - ) end self.default_scopes += [scope] diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index da73bead32..7c51aa6979 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -25,22 +25,18 @@ module ActiveRecord if current_scope current_scope.clone else - scope = relation - scope.default_scoped = true - scope + default_scoped end end + def default_scoped # :nodoc: + relation.merge(build_default_scope) + end + # Collects attributes from scopes that should be applied when creating # an AR instance for the particular class this is called on. def scope_attributes # :nodoc: - if current_scope - current_scope.scope_for_create - else - scope = relation - scope.default_scoped = true - scope.scope_for_create - end + all.scope_for_create end # Are there default attributes associated with this scope? @@ -145,19 +141,6 @@ module ActiveRecord def scope(name, body, &block) extension = Module.new(&block) if block - # Check body.is_a?(Relation) to prevent the relation actually being - # loaded by respond_to? - if body.is_a?(Relation) || !body.respond_to?(:call) - ActiveSupport::Deprecation.warn( - "Using #scope without passing a callable object is deprecated. For " \ - "example `scope :red, where(color: 'red')` should be changed to " \ - "`scope :red, -> { where(color: 'red') }`. There are numerous gotchas " \ - "in the former usage and it makes the implementation more complicated " \ - "and buggy. (If you prefer, you can just define a class method named " \ - "`self.red`.)" - ) - end - singleton_class.send(:define_method, name) do |*args| if body.respond_to?(:call) scope = all.scoping { body.call(*args) } diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 3e8b79c7a0..5ff594fdca 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -50,10 +50,6 @@ module ActiveRecord register_task(/postgresql/, ActiveRecord::Tasks::PostgreSQLDatabaseTasks) register_task(/sqlite/, ActiveRecord::Tasks::SQLiteDatabaseTasks) - register_task(/firebird/, ActiveRecord::Tasks::FirebirdDatabaseTasks) - register_task(/sqlserver/, ActiveRecord::Tasks::SqlserverDatabaseTasks) - register_task(/(oci|oracle)/, ActiveRecord::Tasks::OracleDatabaseTasks) - def current_config(options = {}) options.reverse_merge! :env => env if options.has_key?(:config) diff --git a/activerecord/lib/active_record/tasks/firebird_database_tasks.rb b/activerecord/lib/active_record/tasks/firebird_database_tasks.rb deleted file mode 100644 index 98014a38ea..0000000000 --- a/activerecord/lib/active_record/tasks/firebird_database_tasks.rb +++ /dev/null @@ -1,56 +0,0 @@ -module ActiveRecord - module Tasks # :nodoc: - class FirebirdDatabaseTasks # :nodoc: - delegate :connection, :establish_connection, to: ActiveRecord::Base - - def initialize(configuration) - ActiveSupport::Deprecation.warn "This database tasks were deprecated, because this tasks should be served by the 3rd party adapter." - @configuration = configuration - end - - def create - $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch' - end - - def drop - $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch' - end - - def purge - establish_connection(:test) - connection.recreate_database! - end - - def charset - $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch' - end - - def structure_dump(filename) - set_firebird_env(configuration) - db_string = firebird_db_string(configuration) - Kernel.system "isql -a #{db_string} > #{filename}" - end - - def structure_load(filename) - set_firebird_env(configuration) - db_string = firebird_db_string(configuration) - Kernel.system "isql -i #{filename} #{db_string}" - end - - private - - def set_firebird_env(config) - ENV['ISC_USER'] = config['username'].to_s if config['username'] - ENV['ISC_PASSWORD'] = config['password'].to_s if config['password'] - end - - def firebird_db_string(config) - FireRuby::Database.db_string_for(config.symbolize_keys) - end - - def configuration - @configuration - end - end - end -end diff --git a/activerecord/lib/active_record/tasks/oracle_database_tasks.rb b/activerecord/lib/active_record/tasks/oracle_database_tasks.rb deleted file mode 100644 index de3aa50e5e..0000000000 --- a/activerecord/lib/active_record/tasks/oracle_database_tasks.rb +++ /dev/null @@ -1,45 +0,0 @@ -module ActiveRecord - module Tasks # :nodoc: - class OracleDatabaseTasks # :nodoc: - delegate :connection, :establish_connection, to: ActiveRecord::Base - - def initialize(configuration) - ActiveSupport::Deprecation.warn "This database tasks were deprecated, because this tasks should be served by the 3rd party adapter." - @configuration = configuration - end - - def create - $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch' - end - - def drop - $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch' - end - - def purge - establish_connection(:test) - connection.structure_drop.split(";\n\n").each { |ddl| connection.execute(ddl) } - end - - def charset - $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch' - end - - def structure_dump(filename) - establish_connection(configuration) - File.open(filename, "w:utf-8") { |f| f << connection.structure_dump } - end - - def structure_load(filename) - establish_connection(configuration) - IO.read(filename).split(";\n\n").each { |ddl| connection.execute(ddl) } - end - - private - - def configuration - @configuration - end - end - end -end diff --git a/activerecord/lib/active_record/tasks/sqlserver_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlserver_database_tasks.rb deleted file mode 100644 index c718ee03a8..0000000000 --- a/activerecord/lib/active_record/tasks/sqlserver_database_tasks.rb +++ /dev/null @@ -1,48 +0,0 @@ -require 'shellwords' - -module ActiveRecord - module Tasks # :nodoc: - class SqlserverDatabaseTasks # :nodoc: - delegate :connection, :establish_connection, to: ActiveRecord::Base - - def initialize(configuration) - ActiveSupport::Deprecation.warn "This database tasks were deprecated, because this tasks should be served by the 3rd party adapter." - @configuration = configuration - end - - def create - $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch' - end - - def drop - $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch' - end - - def purge - test = configuration.deep_dup - test_database = test['database'] - test['database'] = 'master' - establish_connection(test) - connection.recreate_database!(test_database) - end - - def charset - $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch' - end - - def structure_dump(filename) - Kernel.system("smoscript -s #{configuration['host']} -d #{configuration['database']} -u #{configuration['username']} -p #{configuration['password']} -f #{filename} -A -U") - end - - def structure_load(filename) - Kernel.system("sqlcmd -S #{configuration['host']} -d #{configuration['database']} -U #{configuration['username']} -P #{configuration['password']} -i #{filename}") - end - - private - - def configuration - @configuration - end - end - end -end diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb deleted file mode 100644 index 1b4c473bfc..0000000000 --- a/activerecord/lib/active_record/test_case.rb +++ /dev/null @@ -1,96 +0,0 @@ -require 'active_support/test_case' - -ActiveSupport::Deprecation.warn('ActiveRecord::TestCase is deprecated, please use ActiveSupport::TestCase') -module ActiveRecord - # = Active Record Test Case - # - # Defines some test assertions to test against SQL queries. - class TestCase < ActiveSupport::TestCase #:nodoc: - def teardown - SQLCounter.clear_log - end - - def assert_date_from_db(expected, actual, message = nil) - # SybaseAdapter doesn't have a separate column type just for dates, - # so the time is in the string and incorrectly formatted - if current_adapter?(:SybaseAdapter) - assert_equal expected.to_s, actual.to_date.to_s, message - else - assert_equal expected.to_s, actual.to_s, message - end - end - - def assert_sql(*patterns_to_match) - SQLCounter.clear_log - yield - SQLCounter.log_all - ensure - failed_patterns = [] - patterns_to_match.each do |pattern| - failed_patterns << pattern unless SQLCounter.log_all.any?{ |sql| pattern === sql } - end - assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}" - end - - def assert_queries(num = 1, options = {}) - ignore_none = options.fetch(:ignore_none) { num == :any } - SQLCounter.clear_log - x = yield - the_log = ignore_none ? SQLCounter.log_all : SQLCounter.log - if num == :any - assert_operator the_log.size, :>=, 1, "1 or more queries expected, but none were executed." - else - mesg = "#{the_log.size} instead of #{num} queries were executed.#{the_log.size == 0 ? '' : "\nQueries:\n#{the_log.join("\n")}"}" - assert_equal num, the_log.size, mesg - end - x - end - - def assert_no_queries(&block) - assert_queries(0, :ignore_none => true, &block) - end - - end - - class SQLCounter - class << self - attr_accessor :ignored_sql, :log, :log_all - def clear_log; self.log = []; self.log_all = []; end - end - - self.clear_log - - 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, /^SHOW search_path/i] - sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im] - - [oracle_ignored, mysql_ignored, postgresql_ignored, sqlite3_ignored].each do |db_ignored_sql| - ignored_sql.concat db_ignored_sql - end - - attr_reader :ignore - - def initialize(ignore = Regexp.union(self.class.ignored_sql)) - @ignore = ignore - end - - def call(name, start, finish, message_id, values) - sql = values[:sql] - - # FIXME: this seems bad. we should probably have a better way to indicate - # the query was cached - return if 'CACHE' == values[:name] - - self.class.log_all << sql - self.class.log << sql unless ignore =~ sql - end - end - - ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new) -end diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index ae99cff35e..9253150c4f 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -10,9 +10,9 @@ module ActiveRecord # # config.active_record.record_timestamps = false # - # Timestamps are in the local timezone by default but you can use UTC by setting: + # Timestamps are in UTC by default but you can use the local timezone by setting: # - # config.active_record.default_timezone = :utc + # config.active_record.default_timezone = :local # # == Time Zone aware attributes # diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 77634b40bb..dcbf38a89f 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -245,7 +245,7 @@ module ActiveRecord if options.is_a?(Hash) && options[:on] assert_valid_transaction_action(options[:on]) options[:if] = Array(options[:if]) - fire_on = Array(options[:on]).map(&:to_sym) + fire_on = Array(options[:on]) options[:if] << "transaction_include_any_action?(#{fire_on})" end end @@ -288,25 +288,26 @@ module ActiveRecord clear_transaction_record_state end - # Call the after_commit callbacks + # 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 + # but call it after the commit of a destroyed object. def committed! #:nodoc: run_callbacks :commit if destroyed? || persisted? ensure clear_transaction_record_state end - # Call the after rollback callbacks. The restore_state argument indicates if the record + # Call the +after_rollback+ callbacks. The +force_restore_state+ argument indicates if the record # state should be rolled back to the beginning or just to the last savepoint. def rolledback!(force_restore_state = false) #:nodoc: run_callbacks :rollback ensure restore_transaction_record_state(force_restore_state) + clear_transaction_record_state end - # Add the record to the current transaction so that the :after_rollback and :after_commit callbacks + # Add the record to the current transaction so that the +after_rollback+ and +after_commit+ callbacks # can be called. def add_to_transaction if self.class.connection.add_transaction_record(self) @@ -360,8 +361,8 @@ module ActiveRecord # Restore the new record state and id of a record that was previously saved by a call to save_record_state. def restore_transaction_record_state(force = false) #:nodoc: unless @_start_transaction_state.empty? - @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 - if @_start_transaction_state[:level] < 1 || force + transaction_level = (@_start_transaction_state[:level] || 0) - 1 + if transaction_level < 1 || force restore_state = @_start_transaction_state was_frozen = restore_state[:frozen?] @attributes = @attributes.dup if @attributes.frozen? @@ -374,7 +375,6 @@ module ActiveRecord @attributes_cache.delete(self.class.primary_key) end @attributes.freeze if was_frozen - @_start_transaction_state.clear end end end diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 52e46e1ffe..b55af692d6 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -197,8 +197,8 @@ module ActiveRecord # will result in the default Rails exception page being shown), or you # can catch it and restart the transaction (e.g. by telling the user # that the title already exists, and asking him to re-enter the title). - # This technique is also known as optimistic concurrency control: - # http://en.wikipedia.org/wiki/Optimistic_concurrency_control. + # This technique is also known as + # {optimistic concurrency control}[http://en.wikipedia.org/wiki/Optimistic_concurrency_control]. # # The bundled ActiveRecord::ConnectionAdapters distinguish unique index # constraint errors from other types of database errors by throwing an diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index e28bb7b6ca..dd355e8d0c 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -173,6 +173,11 @@ module ActiveRecord end end end + + def test_select_all_always_return_activerecord_result + result = @connection.select_all "SELECT * FROM posts" + assert result.is_a?(ActiveRecord::Result) + end end class AdapterTestWithoutTransaction < ActiveRecord::TestCase diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb index 4cf4bc4c61..8eb9565963 100644 --- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb @@ -2,7 +2,7 @@ require "cases/helper" class Group < ActiveRecord::Base Group.table_name = 'group' - belongs_to :select, :class_name => 'Select' + belongs_to :select has_one :values end diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb index e76617b845..1a82308176 100644 --- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb @@ -2,7 +2,7 @@ require "cases/helper" class Group < ActiveRecord::Base Group.table_name = 'group' - belongs_to :select, :class_name => 'Select' + belongs_to :select has_one :values end diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index 61a3a2ba0f..e5e877f0b0 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -27,6 +27,18 @@ class PostgresqlArrayTest < ActiveRecord::TestCase assert @column.array end + def test_change_column_with_array + @connection.add_column :pg_arrays, :snippets, :string, array: true, default: [] + @connection.change_column :pg_arrays, :snippets, :text, array: true, default: "{}" + + PgArray.reset_column_information + column = PgArray.columns.find { |c| c.name == 'snippets' } + + assert_equal :text, column.type + assert_equal [], column.default + assert column.array + end + def test_type_cast_array assert @column diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index f45c7afcc0..adac1d3c13 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -96,5 +96,4 @@ class PostgresqlJSONTest < ActiveRecord::TestCase x.payload = ['v1', {'k2' => 'v2'}, 'v3'] assert x.save! 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 fb88ab7c09..8b017760b1 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -225,65 +225,26 @@ module ActiveRecord assert_equal "(number > 100)", index.where end - def test_distinct_zero_orders - assert_deprecated do - assert_equal "DISTINCT posts.id", - @connection.distinct("posts.id", []) - end - end - def test_columns_for_distinct_zero_orders assert_equal "posts.id", @connection.columns_for_distinct("posts.id", []) end - def test_distinct_one_order - assert_deprecated do - assert_equal "DISTINCT posts.id, posts.created_at AS alias_0", - @connection.distinct("posts.id", ["posts.created_at desc"]) - end - end - def test_columns_for_distinct_one_order assert_equal "posts.id, posts.created_at AS alias_0", @connection.columns_for_distinct("posts.id", ["posts.created_at desc"]) end - def test_distinct_few_orders - assert_deprecated do - assert_equal "DISTINCT posts.id, posts.created_at AS alias_0, posts.position AS alias_1", - @connection.distinct("posts.id", ["posts.created_at desc", "posts.position asc"]) - end - end - def test_columns_for_distinct_few_orders assert_equal "posts.id, posts.created_at AS alias_0, posts.position AS alias_1", @connection.columns_for_distinct("posts.id", ["posts.created_at desc", "posts.position asc"]) end - def test_distinct_blank_not_nil_orders - assert_deprecated do - assert_equal "DISTINCT posts.id, posts.created_at AS alias_0", - @connection.distinct("posts.id", ["posts.created_at desc", "", " "]) - end - end - def test_columns_for_distinct_blank_not_nil_orders assert_equal "posts.id, posts.created_at AS alias_0", @connection.columns_for_distinct("posts.id", ["posts.created_at desc", "", " "]) end - def test_distinct_with_arel_order - order = Object.new - def order.to_sql - "posts.created_at desc" - end - assert_deprecated do - assert_equal "DISTINCT posts.id, posts.created_at AS alias_0", - @connection.distinct("posts.id", [order]) - end - end - def test_columns_for_distinct_with_arel_order order = Object.new def order.to_sql @@ -293,13 +254,6 @@ module ActiveRecord @connection.columns_for_distinct("posts.id", [order]) end - def test_distinct_with_nulls - assert_deprecated do - assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls first"]) - assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls last"]) - end - end - def test_columns_for_distinct_with_nulls assert_equal "posts.title, posts.updater_id AS alias_0", @connection.columns_for_distinct("posts.title", ["posts.updater_id desc nulls first"]) assert_equal "posts.title, posts.updater_id AS alias_0", @connection.columns_for_distinct("posts.title", ["posts.updater_id desc nulls last"]) diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 95896971a8..a79f145e31 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -1,4 +1,4 @@ -require "cases/helper" +require 'cases/helper' require 'models/developer' require 'models/project' require 'models/company' @@ -14,6 +14,8 @@ require 'models/sponsor' require 'models/member' require 'models/essay' require 'models/toy' +require 'models/invoice' +require 'models/line_item' class BelongsToAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :developers, :projects, :topics, @@ -324,6 +326,45 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal 1, Topic.find(topic.id)[:replies_count] end + def test_belongs_to_with_touch_option_on_touch + line_item = LineItem.create! + Invoice.create!(line_items: [line_item]) + + assert_queries(1) { line_item.touch } + end + + def test_belongs_to_with_touch_option_on_touch_and_removed_parent + line_item = LineItem.create! + Invoice.create!(line_items: [line_item]) + + line_item.invoice = nil + + assert_queries(2) { line_item.touch } + end + + def test_belongs_to_with_touch_option_on_update + line_item = LineItem.create! + Invoice.create!(line_items: [line_item]) + + assert_queries(2) { line_item.update amount: 10 } + end + + def test_belongs_to_with_touch_option_on_destroy + line_item = LineItem.create! + Invoice.create!(line_items: [line_item]) + + assert_queries(2) { line_item.destroy } + end + + def test_belongs_to_with_touch_option_on_touch_and_reassigned_parent + line_item = LineItem.create! + Invoice.create!(line_items: [line_item]) + + line_item.invoice = Invoice.create! + + assert_queries(3) { line_item.touch } + end + def test_belongs_to_counter_after_update topic = Topic.create!(title: "37s") topic.replies.create!(title: "re: 37s", content: "rails") @@ -391,8 +432,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_dont_find_target_when_foreign_key_is_null tagging = taggings(:thinking_general) - queries = assert_sql { tagging.super_tag } - assert_equal 0, queries.length + assert_queries(0) { tagging.super_tag } end def test_field_name_same_as_foreign_key @@ -565,6 +605,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end def test_dependent_delete_and_destroy_with_belongs_to + AuthorAddress.destroyed_author_address_ids.clear + author_address = author_addresses(:david_address) author_address_extra = author_addresses(:david_address_extra) assert_equal [], AuthorAddress.destroyed_author_address_ids diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 4aa6567d85..28bf48f4fd 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -250,7 +250,8 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_nil Post.all.merge!(:includes => :author).find(posts(:authorless).id).author end - def test_nested_loading_with_no_associations + # Regression test for 21c75e5 + def test_nested_loading_does_not_raise_exception_when_association_does_not_exist assert_nothing_raised do Post.all.merge!(:includes => {:author => :author_addresss}).find(posts(:authorless).id) end @@ -345,9 +346,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_association_loading_with_belongs_to_and_conditions_string_with_unquoted_table_name assert_nothing_raised do - ActiveSupport::Deprecation.silence do - Comment.all.merge!(:includes => :post, :where => ['posts.id = ?',4]).to_a - end + Comment.includes(:post).references(:posts).where('posts.id = ?', 4) end end @@ -366,9 +365,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_association_loading_with_belongs_to_and_conditions_string_with_quoted_table_name quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id') assert_nothing_raised do - ActiveSupport::Deprecation.silence do - Comment.all.merge!(:includes => :post, :where => ["#{quoted_posts_id} = ?",4]).to_a - end + Comment.includes(:post).references(:posts).where("#{quoted_posts_id} = ?", 4) end end @@ -381,9 +378,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_association_loading_with_belongs_to_and_order_string_with_quoted_table_name quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id') assert_nothing_raised do - ActiveSupport::Deprecation.silence do - Comment.all.merge!(:includes => :post, :order => quoted_posts_id).to_a - end + Comment.includes(:post).references(:posts).order(quoted_posts_id) end end @@ -547,15 +542,11 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_many_and_limit_and_conditions_array_on_the_eagers - posts = ActiveSupport::Deprecation.silence do - Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => [ "authors.name = ?", 'David' ]).to_a - end + posts = Post.includes(:author, :comments).limit(2).references(:author).where("authors.name = ?", 'David') assert_equal 2, posts.size - count = ActiveSupport::Deprecation.silence do - Post.count(:include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ]) - end - assert_equal count, posts.size + count = Post.includes(:author, :comments).limit(2).references(:author).where("authors.name = ?", 'David').count + assert_equal posts.size, count end def test_eager_with_has_many_and_limit_and_high_offset diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 84bdca3a97..c63f48e370 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -65,19 +65,6 @@ class DeveloperWithSymbolsForKeys < ActiveRecord::Base :foreign_key => "developer_id" end -class DeveloperWithCounterSQL < ActiveRecord::Base - self.table_name = 'developers' - - ActiveSupport::Deprecation.silence do - has_and_belongs_to_many :projects, - :class_name => "DeveloperWithCounterSQL", - :join_table => "developers_projects", - :association_foreign_key => "project_id", - :foreign_key => "developer_id", - :counter_sql => proc { "SELECT COUNT(*) AS count_all FROM projects INNER JOIN developers_projects ON projects.id = developers_projects.project_id WHERE developers_projects.developer_id =#{id}" } - end -end - class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects, :parrots, :pirates, :parrots_pirates, :treasures, :price_estimates, :tags, :taggings @@ -364,31 +351,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal 0, david.projects(true).size end - def test_deleting_with_sql - david = Developer.find(1) - active_record = Project.find(1) - active_record.developers.reload - assert_equal 3, active_record.developers_by_sql.size - - active_record.developers_by_sql.delete(david) - assert_equal 2, active_record.developers_by_sql(true).size - end - - def test_deleting_array_with_sql - active_record = Project.find(1) - active_record.developers.reload - assert_equal 3, active_record.developers_by_sql.size - - active_record.developers_by_sql.delete(Developer.all) - assert_equal 0, active_record.developers_by_sql(true).size - end - - def test_deleting_all_with_sql - project = Project.find(1) - project.developers_by_sql.delete_all - assert_equal 0, project.developers_by_sql.size - end - def test_deleting_all david = Developer.find(1) david.projects.reload @@ -475,13 +437,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert george.treasures(true).empty? end - def test_deprecated_push_with_attributes_was_removed - jamis = developers(:jamis) - assert_raise(NoMethodError) do - jamis.projects.push_with_attributes(projects(:action_controller), :joined_on => Date.today) - end - end - def test_associations_with_conditions assert_equal 3, projects(:active_record).developers.size assert_equal 1, projects(:active_record).developers_named_david.size @@ -537,25 +492,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert ! project.developers.include?(developer) end - def test_find_in_association_with_custom_finder_sql - assert_equal developers(:david), projects(:active_record).developers_with_finder_sql.find(developers(:david).id), "SQL find" - - active_record = projects(:active_record) - active_record.developers_with_finder_sql.reload - assert_equal developers(:david), active_record.developers_with_finder_sql.find(developers(:david).id), "Ruby find" - end - - def test_find_in_association_with_custom_finder_sql_and_multiple_interpolations - # interpolate once: - assert_equal [developers(:david), developers(:jamis), developers(:poor_jamis)], projects(:active_record).developers_with_finder_sql, "first interpolation" - # interpolate again, for a different project id - assert_equal [developers(:david)], projects(:action_controller).developers_with_finder_sql, "second interpolation" - end - - def test_find_in_association_with_custom_finder_sql_and_string_id - assert_equal developers(:david), projects(:active_record).developers_with_finder_sql.find(developers(:david).id.to_s), "SQL find" - end - def test_find_with_merged_options assert_equal 1, projects(:active_record).limited_developers.size assert_equal 1, projects(:active_record).limited_developers.to_a.size @@ -791,21 +727,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal 2, david.projects.count end - def test_count_with_counter_sql - developer = DeveloperWithCounterSQL.create(:name => 'tekin') - developer.project_ids = [projects(:active_record).id] - developer.save - developer.reload - assert_equal 1, developer.projects.count - end - - unless current_adapter?(:PostgreSQLAdapter) - def test_count_with_finder_sql - assert_equal 3, projects(:active_record).developers_with_finder_sql.count - assert_equal 3, projects(:active_record).developers_with_multiline_finder_sql.count - end - end - def test_association_proxy_transaction_method_starts_transaction_in_association_class Post.expects(:transaction) Category.first.posts.transaction do @@ -845,18 +766,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert project.developers.include?(developer) end - test ":insert_sql is deprecated" do - klass = Class.new(ActiveRecord::Base) - def klass.name; 'Foo'; end - assert_deprecated { klass.has_and_belongs_to_many :posts, :insert_sql => 'lol' } - end - - test ":delete_sql is deprecated" do - klass = Class.new(ActiveRecord::Base) - def klass.name; 'Foo'; end - assert_deprecated { klass.has_and_belongs_to_many :posts, :delete_sql => 'lol' } - end - test "has and belongs to many associations on new records use null relations" do projects = Developer.new.projects assert_no_queries do diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 9f64ecd845..9c484a8bbe 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -23,79 +23,6 @@ require 'models/categorization' require 'models/minivan' require 'models/speedometer' -class HasManyAssociationsTestForCountWithFinderSql < ActiveRecord::TestCase - class Invoice < ActiveRecord::Base - ActiveSupport::Deprecation.silence do - has_many :custom_line_items, :class_name => 'LineItem', :finder_sql => "SELECT line_items.* from line_items" - end - end - def test_should_fail - assert_raise(ArgumentError) do - Invoice.create.custom_line_items.count(:conditions => {:amount => 0}) - end - end -end - -class HasManyAssociationsTestForCountWithCountSql < ActiveRecord::TestCase - class Invoice < ActiveRecord::Base - ActiveSupport::Deprecation.silence do - has_many :custom_line_items, :class_name => 'LineItem', :counter_sql => "SELECT COUNT(*) line_items.* from line_items" - end - end - def test_should_fail - assert_raise(ArgumentError) do - Invoice.create.custom_line_items.count(:conditions => {:amount => 0}) - end - end -end - -class HasManyAssociationsTestForCountWithVariousFinderSqls < ActiveRecord::TestCase - class Invoice < ActiveRecord::Base - ActiveSupport::Deprecation.silence do - has_many :custom_line_items, :class_name => 'LineItem', :finder_sql => "SELECT DISTINCT line_items.amount from line_items" - has_many :custom_full_line_items, :class_name => 'LineItem', :finder_sql => "SELECT line_items.invoice_id, line_items.amount from line_items" - has_many :custom_star_line_items, :class_name => 'LineItem', :finder_sql => "SELECT * from line_items" - has_many :custom_qualified_star_line_items, :class_name => 'LineItem', :finder_sql => "SELECT line_items.* from line_items" - end - end - - def test_should_count_distinct_results - invoice = Invoice.new - invoice.custom_line_items << LineItem.new(:amount => 0) - invoice.custom_line_items << LineItem.new(:amount => 0) - invoice.save! - - assert_equal 1, invoice.custom_line_items.count - end - - def test_should_count_results_with_multiple_fields - invoice = Invoice.new - invoice.custom_full_line_items << LineItem.new(:amount => 0) - invoice.custom_full_line_items << LineItem.new(:amount => 0) - invoice.save! - - assert_equal 2, invoice.custom_full_line_items.count - end - - def test_should_count_results_with_star - invoice = Invoice.new - invoice.custom_star_line_items << LineItem.new(:amount => 0) - invoice.custom_star_line_items << LineItem.new(:amount => 0) - invoice.save! - - assert_equal 2, invoice.custom_star_line_items.count - end - - def test_should_count_results_with_qualified_star - invoice = Invoice.new - invoice.custom_qualified_star_line_items << LineItem.new(:amount => 0) - invoice.custom_qualified_star_line_items << LineItem.new(:amount => 0) - invoice.save! - - assert_equal 2, invoice.custom_qualified_star_line_items.count - end -end - class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase fixtures :authors, :posts, :comments @@ -148,6 +75,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 'defaulty', bulb.name end + def test_do_not_call_callbacks_for_delete_all + bulb_count = Bulb.count + car = Car.create(:name => 'honda') + car.funky_bulbs.create! + assert_nothing_raised { car.reload.funky_bulbs.delete_all } + assert_equal bulb_count + 1, Bulb.count, "bulbs should have been deleted using :nullify strategey" + end + def test_building_the_associated_object_with_implicit_sti_base_class firm = DependentFirm.new company = firm.companies.build @@ -176,6 +111,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_raise(ActiveRecord::SubclassNotFound) { firm.companies.build(:type => "Account") } end + test "building the association with an array" do + speedometer = Speedometer.new(speedometer_id: "a") + data = [{name: "first"}, {name: "second"}] + speedometer.minivans.build(data) + + assert_equal 2, speedometer.minivans.size + assert speedometer.save + assert_equal ["first", "second"], speedometer.reload.minivans.map(&:name) + end + def test_association_keys_bypass_attribute_protection car = Car.create(:name => 'honda') @@ -347,37 +292,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal "Summit", Firm.all.merge!(:order => "id").first.clients_using_primary_key.first.name end - def test_finding_using_sql - firm = Firm.order("id").first - first_client = firm.clients_using_sql.first - assert_not_nil first_client - assert_equal "Microsoft", first_client.name - assert_equal 1, firm.clients_using_sql.size - assert_equal 1, Firm.order("id").first.clients_using_sql.size - end - - def test_finding_using_sql_take_into_account_only_uniq_ids - firm = Firm.order("id").first - client = firm.clients_using_sql.first - assert_equal client, firm.clients_using_sql.find(client.id, client.id) - assert_equal client, firm.clients_using_sql.find(client.id, client.id.to_s) - end - - def test_counting_using_sql - assert_equal 1, Firm.order("id").first.clients_using_counter_sql.size - assert Firm.order("id").first.clients_using_counter_sql.any? - assert_equal 0, Firm.order("id").first.clients_using_zero_counter_sql.size - assert !Firm.order("id").first.clients_using_zero_counter_sql.any? - end - - def test_counting_non_existant_items_using_sql - assert_equal 0, Firm.order("id").first.no_clients_using_counter_sql.size - end - - def test_counting_using_finder_sql - assert_equal 2, Firm.find(4).clients_using_sql.count - end - def test_belongs_to_sanity c = Client.new assert_nil c.firm @@ -405,22 +319,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find(2, 99) } end - def test_find_string_ids_when_using_finder_sql - firm = Firm.order("id").first - - client = firm.clients_using_finder_sql.find("2") - assert_kind_of Client, client - - client_ary = firm.clients_using_finder_sql.find(["2"]) - assert_kind_of Array, client_ary - assert_equal client, client_ary.first - - client_ary = firm.clients_using_finder_sql.find("2", "3") - assert_kind_of Array, client_ary - assert_equal 2, client_ary.size - assert client_ary.include?(client) - end - def test_find_all firm = Firm.all.merge!(:order => "id").first assert_equal 2, firm.clients.where("#{QUOTED_TYPE} = 'Client'").to_a.length @@ -920,18 +818,33 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm = companies(:first_firm) client_id = firm.dependent_clients_of_firm.first.id assert_equal 1, firm.dependent_clients_of_firm.size + assert_equal 1, Client.find_by_id(client_id).client_of - # :dependent means destroy is called on each client + # :nullify is called on each client firm.dependent_clients_of_firm.clear assert_equal 0, firm.dependent_clients_of_firm.size assert_equal 0, firm.dependent_clients_of_firm(true).size - assert_equal [client_id], Client.destroyed_client_ids[firm.id] + assert_equal [], Client.destroyed_client_ids[firm.id] # Should be destroyed since the association is dependent. + assert_nil Client.find_by_id(client_id).client_of + end + + def test_delete_all_with_option_delete_all + firm = companies(:first_firm) + client_id = firm.dependent_clients_of_firm.first.id + firm.dependent_clients_of_firm.delete_all(:delete_all) assert_nil Client.find_by_id(client_id) end + def test_delete_all_accepts_limited_parameters + firm = companies(:first_firm) + assert_raise(ArgumentError) do + firm.dependent_clients_of_firm.delete_all(:destroy) + end + end + def test_clearing_an_exclusively_dependent_association_collection firm = companies(:first_firm) client_id = firm.exclusively_dependent_clients_of_firm.first.id @@ -1179,21 +1092,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal num_accounts, Account.count end - def test_restrict - firm = RestrictedFirm.create!(:name => 'restrict') - firm.companies.create(:name => 'child') - - assert !firm.companies.empty? - assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy } - assert RestrictedFirm.exists?(:name => 'restrict') - assert firm.companies.exists?(:name => 'child') - end - - def test_restrict_is_deprecated - klass = Class.new(ActiveRecord::Base) - assert_deprecated { klass.has_many :posts, dependent: :restrict } - end - def test_restrict_with_exception firm = RestrictedWithExceptionFirm.create!(:name => 'restrict') firm.companies.create(:name => 'child') @@ -1220,14 +1118,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_included_in_collection - assert companies(:first_firm).clients.include?(Client.find(2)) + assert_equal true, companies(:first_firm).clients.include?(Client.find(2)) end def test_included_in_collection_for_new_records client = Client.create(:name => 'Persisted') assert_nil client.client_of - assert !Firm.new.clients_of_firm.include?(client), - 'includes a client that does not belong to any firm' + assert_equal false, Firm.new.clients_of_firm.include?(client), + 'includes a client that does not belong to any firm' end def test_adding_array_and_collection @@ -1254,7 +1152,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm.save firm.reload assert_equal 2, firm.clients.length - assert !firm.clients.include?(:first_client) + assert_equal false, firm.clients.include?(:first_client) end def test_replace_failure @@ -1315,24 +1213,44 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [readers(:michael_welcome).id], posts(:welcome).readers_with_person_ids end - def test_get_ids_for_unloaded_finder_sql_associations_loads_them - company = companies(:first_firm) - assert !company.clients_using_sql.loaded? - assert_equal [companies(:second_client).id], company.clients_using_sql_ids - assert company.clients_using_sql.loaded? - end - def test_get_ids_for_ordered_association assert_equal [companies(:second_client).id, companies(:first_client).id], companies(:first_firm).clients_ordered_by_name_ids end + def test_get_ids_for_association_on_new_record_does_not_try_to_find_records + Company.columns # Load schema information so we don't query below + Contract.columns # if running just this test. + + company = Company.new + assert_queries(0) do + company.contract_ids + end + + assert_equal [], company.contract_ids + end + + def test_set_ids_for_association_on_new_record_applies_association_correctly + contract_a = Contract.create! + contract_b = Contract.create! + Contract.create! # another contract + company = Company.new(:name => "Some Company") + + company.contract_ids = [contract_a.id, contract_b.id] + assert_equal [contract_a.id, contract_b.id], company.contract_ids + assert_equal [contract_a, contract_b], company.contracts + + company.save! + assert_equal company, contract_a.reload.company + assert_equal company, contract_b.reload.company + end + def test_assign_ids_ignoring_blanks firm = Firm.create!(:name => 'Apple') firm.client_ids = [companies(:first_client).id, nil, companies(:second_client).id, ''] firm.save! assert_equal 2, firm.clients(true).size - assert firm.clients.include?(companies(:second_client)) + assert_equal true, firm.clients.include?(companies(:second_client)) end def test_get_ids_for_through @@ -1366,7 +1284,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_no_queries do assert firm.clients.loaded? - assert firm.clients.include?(client) + assert_equal true, firm.clients.include?(client) end end @@ -1377,28 +1295,17 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm.reload assert ! firm.clients.loaded? assert_queries(1) do - assert firm.clients.include?(client) + assert_equal true, firm.clients.include?(client) end assert ! firm.clients.loaded? end - def test_include_loads_collection_if_target_uses_finder_sql - firm = companies(:first_firm) - client = firm.clients_using_sql.first - - firm.reload - assert ! firm.clients_using_sql.loaded? - assert firm.clients_using_sql.include?(client) - assert firm.clients_using_sql.loaded? - end - - def test_include_returns_false_for_non_matching_record_to_verify_scoping firm = companies(:first_firm) client = Client.create!(:name => 'Not Associated') assert ! firm.clients.loaded? - assert ! firm.clients.include?(client) + assert_equal false, firm.clients.include?(client) end def test_calling_first_or_last_on_association_should_not_load_association @@ -1613,7 +1520,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_include_method_in_has_many_association_should_return_true_for_instance_added_with_build post = Post.new comment = post.comments.build - assert post.comments.include?(comment) + assert_equal true, post.comments.include?(comment) end def test_load_target_respects_protected_attributes @@ -1737,16 +1644,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end end - test ":finder_sql is deprecated" do - klass = Class.new(ActiveRecord::Base) - assert_deprecated { klass.has_many :foo, :finder_sql => 'lol' } - end - - test ":counter_sql is deprecated" do - klass = Class.new(ActiveRecord::Base) - assert_deprecated { klass.has_many :foo, :counter_sql => 'lol' } - end - test "has many associations on new records use null relations" do post = Post.new 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 70c6b489aa..85296a5a83 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -5,6 +5,7 @@ require 'models/reference' require 'models/job' require 'models/reader' require 'models/comment' +require 'models/rating' require 'models/tag' require 'models/tagging' require 'models/author' @@ -57,6 +58,40 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert post.reload.people(true).include?(person) end + def test_delete_all_for_with_dependent_option_destroy + person = people(:david) + assert_equal 1, person.jobs_with_dependent_destroy.count + + assert_no_difference 'Job.count' do + assert_difference 'Reference.count', -1 do + person.reload.jobs_with_dependent_destroy.delete_all + end + end + end + + def test_delete_all_for_with_dependent_option_nullify + person = people(:david) + assert_equal 1, person.jobs_with_dependent_nullify.count + + assert_no_difference 'Job.count' do + assert_no_difference 'Reference.count' do + person.reload.jobs_with_dependent_nullify.delete_all + end + end + end + + def test_delete_all_for_with_dependent_option_delete_all + person = people(:david) + assert_equal 1, person.jobs_with_dependent_delete_all.count + + assert_no_difference 'Job.count' do + assert_difference 'Reference.count', -1 do + person.reload.jobs_with_dependent_delete_all.delete_all + end + end + end + + def test_associate_existing_record_twice_should_add_to_target_twice post = posts(:thinking) person = people(:david) @@ -582,6 +617,11 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_equal post.author.author_favorites, post.author_favorites end + def test_merge_join_association_with_has_many_through_association_proxy + author = authors(:mary) + assert_nothing_raised { author.comments.ratings.to_sql } + end + def test_has_many_association_through_a_has_many_association_with_nonstandard_primary_keys assert_equal 2, owners(:blackbeard).toys.count end @@ -607,7 +647,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase sarah = Person.create!(:first_name => 'Sarah', :primary_contact_id => people(:susan).id, :gender => 'F', :number1_fan_id => 1) john = Person.create!(:first_name => 'John', :primary_contact_id => sarah.id, :gender => 'M', :number1_fan_id => 1) assert_equal sarah.agents, [john] - assert_equal people(:susan).agents.map(&:agents).flatten, people(:susan).agents_of_agents + assert_equal people(:susan).agents.flat_map(&:agents), people(:susan).agents_of_agents end def test_associate_existing_with_nonstandard_primary_key_on_belongs_to diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 0e48fbca9c..4fdf9a9643 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -158,22 +158,6 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_nothing_raised { firm.destroy } end - def test_restrict - firm = RestrictedFirm.create!(:name => 'restrict') - firm.create_account(:credit_limit => 10) - - assert_not_nil firm.account - - assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy } - assert RestrictedFirm.exists?(:name => 'restrict') - assert firm.account.present? - end - - def test_restrict_is_deprecated - klass = Class.new(ActiveRecord::Base) - assert_deprecated { klass.has_one :post, dependent: :restrict } - end - def test_restrict_with_exception firm = RestrictedWithExceptionFirm.create!(:name => 'restrict') firm.create_account(:credit_limit => 10) diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb index 90c557e886..f2723f2e18 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -191,6 +191,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase end def test_preloading_has_one_through_on_belongs_to + MemberDetail.delete_all assert_not_nil @member.member_type @organization = organizations(:nsa) @member_detail = MemberDetail.new @@ -201,7 +202,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase end @new_detail = @member_details[0] assert @new_detail.send(:association, :member_type).loaded? - assert_not_nil assert_no_queries { @new_detail.member_type } + assert_no_queries { @new_detail.member_type } end def test_save_of_record_with_loaded_has_one_through diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb index 9baf94399a..de47a576c6 100644 --- a/activerecord/test/cases/associations/inner_join_association_test.rb +++ b/activerecord/test/cases/associations/inner_join_association_test.rb @@ -104,4 +104,12 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase assert !posts(:welcome).tags.empty? assert Post.joins(:misc_tags).where(:id => posts(:welcome).id).empty? end + + test "the default scope of the target is applied when joining associations" do + author = Author.create! name: "Jon" + author.categorizations.create! + author.categorizations.create! special: true + + assert_equal [author], Author.where(id: author).joins(:special_categorizations) + end end diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb index b1f0be3204..71cf1237e8 100644 --- a/activerecord/test/cases/associations/inverse_associations_test.rb +++ b/activerecord/test/cases/associations/inverse_associations_test.rb @@ -401,10 +401,22 @@ class InverseHasManyTests < ActiveRecord::TestCase assert_equal man.name, man.interests.find(interest.id).man.name, "The name of the man should match after the child name is changed" end + def test_find_on_child_instance_with_id_should_not_load_all_child_records + man = Man.create! + interest = Interest.create!(man: man) + + man.interests.find(interest.id) + refute man.interests.loaded? + end + def test_raise_record_not_found_error_when_invalid_ids_are_passed + # delete all interest records to ensure that hard coded invalid_id(s) + # are indeed invalid. + Interest.delete_all + man = Man.create! - invalid_id = 2394823094892348920348523452345 + invalid_id = 245324523 assert_raise(ActiveRecord::RecordNotFound) { man.interests.find(invalid_id) } invalid_ids = [8432342, 2390102913, 2453245234523452] diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb index 8d8ff2f952..c0659fddef 100644 --- a/activerecord/test/cases/attribute_methods/read_test.rb +++ b/activerecord/test/cases/attribute_methods/read_test.rb @@ -15,13 +15,6 @@ module ActiveRecord include ActiveRecord::AttributeMethods - def self.define_attribute_methods - # Created in the inherited/included hook for "proper" ARs - @attribute_methods_mutex ||= Mutex.new - - super - end - def self.column_names %w{ one two three } end @@ -56,9 +49,9 @@ module ActiveRecord end def test_attribute_methods_generated? - assert(!@klass.attribute_methods_generated?, 'attribute_methods_generated?') + assert_not @klass.method_defined?(:one) @klass.define_attribute_methods - assert(@klass.attribute_methods_generated?, 'attribute_methods_generated?') + assert @klass.method_defined?(:one) end end end diff --git a/activerecord/test/cases/attribute_methods/serialization_test.rb b/activerecord/test/cases/attribute_methods/serialization_test.rb new file mode 100644 index 0000000000..75de773961 --- /dev/null +++ b/activerecord/test/cases/attribute_methods/serialization_test.rb @@ -0,0 +1,29 @@ +require "cases/helper" + +module ActiveRecord + module AttributeMethods + class SerializationTest < ActiveSupport::TestCase + class FakeColumn < Struct.new(:name) + def type; :integer; end + def type_cast(s); "#{s}!"; end + end + + class NullCoder + def load(v); v; end + end + + def test_type_cast_serialized_value + value = Serialization::Attribute.new(NullCoder.new, "Hello world", :serialized) + type = Serialization::Type.new(FakeColumn.new) + assert_equal "Hello world!", type.type_cast(value) + end + + def test_type_cast_unserialized_value + value = Serialization::Attribute.new(nil, "Hello world", :unserialized) + type = Serialization::Type.new(FakeColumn.new) + type.type_cast(value) + assert_equal "Hello world", type.type_cast(value) + end + end + end +end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index ee0150558d..baba55ea43 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -32,7 +32,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase t.title = "The First Topic Now Has A Title With\nNewlines And More Than 50 Characters" assert_equal %("#{t.written_on.to_s(:db)}"), t.attribute_for_inspect(:written_on) - assert_equal '"The First Topic Now Has A Title With\nNewlines And M..."', t.attribute_for_inspect(:title) + assert_equal '"The First Topic Now Has A Title With\nNewlines And ..."', t.attribute_for_inspect(:title) end def test_attribute_present @@ -759,21 +759,6 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert subklass.method_defined?(:id), "subklass is missing id method" end - def test_dispatching_column_attributes_through_method_missing_deprecated - Topic.define_attribute_methods - - topic = Topic.new(:id => 5) - topic.id = 5 - - topic.method(:id).owner.send(:undef_method, :id) - - assert_deprecated do - assert_equal 5, topic.id - end - ensure - Topic.undefine_attribute_methods - end - def test_read_attribute_with_nil_should_not_asplode assert_equal nil, Topic.new.read_attribute(nil) end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 580aa96ecd..635278abc1 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -705,6 +705,13 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase ids.each { |id| assert_nil klass.find_by_id(id) } end + def test_should_not_resave_destroyed_association + @pirate.birds.create!(name: :parrot) + @pirate.birds.first.destroy + @pirate.save! + assert @pirate.reload.birds.empty? + end + def test_should_skip_validation_on_has_many_if_marked_for_destruction 2.times { |i| @pirate.birds.create!(:name => "birds_#{i}") } diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 395f28f280..3a1830a739 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require 'active_support/concurrency/latch' require 'models/post' require 'models/author' require 'models/topic' @@ -1458,21 +1459,20 @@ class BasicsTest < ActiveRecord::TestCase orig_handler = klass.connection_handler new_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new after_handler = nil - is_set = false + latch1 = ActiveSupport::Concurrency::Latch.new + latch2 = ActiveSupport::Concurrency::Latch.new t = Thread.new do klass.connection_handler = new_handler - is_set = true - Thread.stop + latch1.release + latch2.await after_handler = klass.connection_handler end - while(!is_set) - Thread.pass - end + latch1.await klass.connection_handler = orig_handler - t.wakeup + latch2.release t.join assert_equal after_handler, new_handler diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index e09fa95756..38c2560d69 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -26,6 +26,24 @@ class EachTest < ActiveRecord::TestCase end end + def test_each_should_return_an_enumerator_if_no_block_is_present + assert_queries(1) do + Post.find_each(:batch_size => 100000).with_index do |post, index| + assert_kind_of Post, post + assert_kind_of Integer, index + end + end + end + + def test_each_enumerator_should_execute_one_query_per_batch + assert_queries(@total + 1) do + Post.find_each(:batch_size => 1).with_index do |post, index| + assert_kind_of Post, post + assert_kind_of Integer, index + end + end + end + def test_each_should_raise_if_select_is_set_without_id assert_raise(RuntimeError) do Post.select(:title).find_each(:batch_size => 1) { |post| post } @@ -50,6 +68,16 @@ class EachTest < ActiveRecord::TestCase Post.order("title").find_each { |post| post } end + def test_logger_not_required + previous_logger = ActiveRecord::Base.logger + ActiveRecord::Base.logger = nil + assert_nothing_raised do + Post.limit(1).find_each { |post| post } + end + ensure + ActiveRecord::Base.logger = previous_logger + end + def test_find_in_batches_should_return_batches assert_queries(@total + 1) do Post.find_in_batches(:batch_size => 1) do |batch| diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index c8ac77984f..73a183f9b4 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -167,6 +167,15 @@ class CalculationsTest < ActiveRecord::TestCase assert_no_match(/OFFSET/, queries.first) end + def test_count_on_invalid_columns_raises + e = assert_raises(ActiveRecord::StatementInvalid) { + Account.select("credit_limit, firm_name").count + } + + assert_match "accounts", e.message + assert_match "credit_limit, firm_name", e.message + end + def test_should_group_by_summed_field_having_condition c = Account.group(:firm_id).having('sum(credit_limit) > 50').sum(:credit_limit) assert_nil c[1] @@ -342,16 +351,6 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal 5, Account.count(:firm_id) end - def test_count_distinct_option_is_deprecated - assert_deprecated do - assert_equal 4, Account.select(:credit_limit).count(distinct: true) - end - - assert_deprecated do - assert_equal 6, Account.select(:credit_limit).count(distinct: false) - end - end - def test_count_with_distinct assert_equal 4, Account.select(:credit_limit).distinct.count assert_equal 4, Account.select(:credit_limit).uniq.count diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb index fe1b40d884..df17732fff 100644 --- a/activerecord/test/cases/connection_management_test.rb +++ b/activerecord/test/cases/connection_management_test.rb @@ -80,9 +80,9 @@ module ActiveRecord end def test_connections_closed_if_exception - app = Class.new(App) { def call(env); raise; end }.new + app = Class.new(App) { def call(env); raise NotImplementedError; end }.new explosive = ConnectionManagement.new(app) - assert_raises(RuntimeError) { explosive.call(@env) } + assert_raises(NotImplementedError) { explosive.call(@env) } assert !ActiveRecord::Base.connection_handler.active_connections? end diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index e6af29282c..2da51ea015 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -118,6 +118,7 @@ module ActiveRecord connection = cs.first @pool.remove connection assert_respond_to t.join.value, :execute + connection.close end def test_reap_and_active @@ -329,7 +330,7 @@ module ActiveRecord end # make sure exceptions are thrown when establish_connection - # is called with a anonymous class + # is called with an anonymous class def test_anonymous_class_exception anonymous = Class.new(ActiveRecord::Base) handler = ActiveRecord::Base.connection_handler diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb index 61f9d4cdae..ee3d8a81c2 100644 --- a/activerecord/test/cases/counter_cache_test.rb +++ b/activerecord/test/cases/counter_cache_test.rb @@ -51,14 +51,9 @@ class CounterCacheTest < ActiveRecord::TestCase end end - test 'reset multiple association counters' do - Topic.increment_counter(:replies_count, @topic.id) - assert_difference '@topic.reload.replies_count', -1 do - Topic.reset_counters(@topic.id, :replies, :unique_replies) - end - - Topic.increment_counter(:unique_replies_count, @topic.id) - assert_difference '@topic.reload.unique_replies_count', -1 do + test 'reset multiple counters' do + Topic.update_counters @topic.id, replies_count: 1, unique_replies_count: 1 + assert_difference ['@topic.reload.replies_count', '@topic.reload.unique_replies_count'], -1 do Topic.reset_counters(@topic.id, :replies, :unique_replies) end end @@ -127,6 +122,12 @@ class CounterCacheTest < ActiveRecord::TestCase end end + test 'update multiple counters' do + assert_difference ['@topic.reload.replies_count', '@topic.reload.unique_replies_count'], 2 do + Topic.update_counters @topic.id, replies_count: 2, unique_replies_count: 2 + end + end + test "update other counters on parent destroy" do david, joanna = dog_lovers(:david, :joanna) joanna = joanna # squelch a warning diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb index e0cf4adf13..7e3d91e08c 100644 --- a/activerecord/test/cases/defaults_test.rb +++ b/activerecord/test/cases/defaults_test.rb @@ -39,6 +39,31 @@ class DefaultTest < ActiveRecord::TestCase end end +class DefaultStringsTest < ActiveRecord::TestCase + class DefaultString < ActiveRecord::Base; end + + setup do + @connection = ActiveRecord::Base.connection + @connection.create_table :default_strings do |t| + t.string :string_col, default: "Smith" + t.string :string_col_with_quotes, default: "O'Connor" + end + DefaultString.reset_column_information + end + + def test_default_strings + assert_equal "Smith", DefaultString.new.string_col + end + + def test_default_strings_containing_single_quotes + assert_equal "O'Connor", DefaultString.new.string_col_with_quotes + end + + teardown do + @connection.drop_table :default_strings + end +end + if current_adapter?(:MysqlAdapter, :Mysql2Adapter) class DefaultsTestWithoutTransactionalFixtures < ActiveRecord::TestCase # ActiveRecord::Base#create! (and #save and other related methods) will diff --git a/activerecord/test/cases/deprecated_dynamic_methods_test.rb b/activerecord/test/cases/deprecated_dynamic_methods_test.rb deleted file mode 100644 index 8e842d8758..0000000000 --- a/activerecord/test/cases/deprecated_dynamic_methods_test.rb +++ /dev/null @@ -1,592 +0,0 @@ -# This file should be deleted when activerecord-deprecated_finders is removed as -# a dependency. -# -# It is kept for now as there is some fairly nuanced behavior in the dynamic -# finders so it is useful to keep this around to guard against regressions if -# we need to change the code. - -require 'cases/helper' -require 'models/topic' -require 'models/reply' -require 'models/customer' -require 'models/post' -require 'models/company' -require 'models/author' -require 'models/category' -require 'models/comment' -require 'models/person' -require 'models/reader' - -class DeprecatedDynamicMethodsTest < ActiveRecord::TestCase - fixtures :topics, :customers, :companies, :accounts, :posts, :categories, :categories_posts, :authors, :people, :comments, :readers - - def setup - @deprecation_behavior = ActiveSupport::Deprecation.behavior - ActiveSupport::Deprecation.behavior = :silence - end - - def teardown - ActiveSupport::Deprecation.behavior = @deprecation_behavior - end - - def test_find_all_by_one_attribute - topics = Topic.find_all_by_content("Have a nice day") - assert_equal 2, topics.size - assert topics.include?(topics(:first)) - - assert_equal [], Topic.find_all_by_title("The First Topic!!") - end - - def test_find_all_by_one_attribute_which_is_a_symbol - topics = Topic.find_all_by_content("Have a nice day".to_sym) - assert_equal 2, topics.size - assert topics.include?(topics(:first)) - - assert_equal [], Topic.find_all_by_title("The First Topic!!") - end - - def test_find_all_by_one_attribute_that_is_an_aggregate - balance = customers(:david).balance - assert_kind_of Money, balance - found_customers = Customer.find_all_by_balance(balance) - assert_equal 1, found_customers.size - assert_equal customers(:david), found_customers.first - end - - def test_find_all_by_two_attributes_that_are_both_aggregates - balance = customers(:david).balance - address = customers(:david).address - assert_kind_of Money, balance - assert_kind_of Address, address - found_customers = Customer.find_all_by_balance_and_address(balance, address) - assert_equal 1, found_customers.size - assert_equal customers(:david), found_customers.first - end - - def test_find_all_by_two_attributes_with_one_being_an_aggregate - balance = customers(:david).balance - assert_kind_of Money, balance - found_customers = Customer.find_all_by_balance_and_name(balance, customers(:david).name) - assert_equal 1, found_customers.size - assert_equal customers(:david), found_customers.first - end - - def test_find_all_by_one_attribute_with_options - topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC") - assert_equal topics(:first), topics.last - - topics = Topic.find_all_by_content("Have a nice day", :order => "id") - assert_equal topics(:first), topics.first - end - - def test_find_all_by_array_attribute - assert_equal 2, Topic.find_all_by_title(["The First Topic", "The Second Topic of the day"]).size - end - - def test_find_all_by_boolean_attribute - topics = Topic.find_all_by_approved(false) - assert_equal 1, topics.size - assert topics.include?(topics(:first)) - - topics = Topic.find_all_by_approved(true) - assert_equal 3, topics.size - assert topics.include?(topics(:second)) - end - - def test_find_all_by_nil_and_not_nil_attributes - topics = Topic.find_all_by_last_read_and_author_name nil, "Mary" - assert_equal 1, topics.size - assert_equal "Mary", topics[0].author_name - end - - def test_find_or_create_from_one_attribute - number_of_companies = Company.count - sig38 = Company.find_or_create_by_name("38signals") - assert_equal number_of_companies + 1, Company.count - assert_equal sig38, Company.find_or_create_by_name("38signals") - assert sig38.persisted? - end - - def test_find_or_create_from_two_attributes - number_of_topics = Topic.count - another = Topic.find_or_create_by_title_and_author_name("Another topic","John") - assert_equal number_of_topics + 1, Topic.count - assert_equal another, Topic.find_or_create_by_title_and_author_name("Another topic", "John") - assert another.persisted? - end - - def test_find_or_create_from_one_attribute_bang - number_of_companies = Company.count - assert_raises(ActiveRecord::RecordInvalid) { Company.find_or_create_by_name!("") } - assert_equal number_of_companies, Company.count - sig38 = Company.find_or_create_by_name!("38signals") - assert_equal number_of_companies + 1, Company.count - assert_equal sig38, Company.find_or_create_by_name!("38signals") - assert sig38.persisted? - end - - def test_find_or_create_from_two_attributes_bang - number_of_companies = Company.count - assert_raises(ActiveRecord::RecordInvalid) { Company.find_or_create_by_name_and_firm_id!("", 17) } - assert_equal number_of_companies, Company.count - sig38 = Company.find_or_create_by_name_and_firm_id!("38signals", 17) - assert_equal number_of_companies + 1, Company.count - assert_equal sig38, Company.find_or_create_by_name_and_firm_id!("38signals", 17) - assert sig38.persisted? - assert_equal "38signals", sig38.name - assert_equal 17, sig38.firm_id - end - - def test_find_or_create_from_two_attributes_with_one_being_an_aggregate - number_of_customers = Customer.count - created_customer = Customer.find_or_create_by_balance_and_name(Money.new(123), "Elizabeth") - assert_equal number_of_customers + 1, Customer.count - assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123), "Elizabeth") - assert created_customer.persisted? - end - - def test_find_or_create_from_one_attribute_and_hash - number_of_companies = Company.count - sig38 = Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23}) - assert_equal number_of_companies + 1, Company.count - assert_equal sig38, Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23}) - assert sig38.persisted? - assert_equal "38signals", sig38.name - assert_equal 17, sig38.firm_id - assert_equal 23, sig38.client_of - end - - def test_find_or_create_from_two_attributes_and_hash - number_of_companies = Company.count - sig38 = Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23}) - assert_equal number_of_companies + 1, Company.count - assert_equal sig38, Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23}) - assert sig38.persisted? - assert_equal "38signals", sig38.name - assert_equal 17, sig38.firm_id - assert_equal 23, sig38.client_of - end - - def test_find_or_create_from_one_aggregate_attribute - number_of_customers = Customer.count - created_customer = Customer.find_or_create_by_balance(Money.new(123)) - assert_equal number_of_customers + 1, Customer.count - assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123)) - assert created_customer.persisted? - end - - def test_find_or_create_from_one_aggregate_attribute_and_hash - number_of_customers = Customer.count - balance = Money.new(123) - name = "Elizabeth" - created_customer = Customer.find_or_create_by_balance({:balance => balance, :name => name}) - assert_equal number_of_customers + 1, Customer.count - assert_equal created_customer, Customer.find_or_create_by_balance({:balance => balance, :name => name}) - assert created_customer.persisted? - assert_equal balance, created_customer.balance - assert_equal name, created_customer.name - end - - def test_find_or_initialize_from_one_attribute - sig38 = Company.find_or_initialize_by_name("38signals") - assert_equal "38signals", sig38.name - assert !sig38.persisted? - end - - def test_find_or_initialize_from_one_aggregate_attribute - new_customer = Customer.find_or_initialize_by_balance(Money.new(123)) - assert_equal 123, new_customer.balance.amount - assert !new_customer.persisted? - end - - def test_find_or_initialize_from_one_attribute_should_set_attribute - c = Company.find_or_initialize_by_name_and_rating("Fortune 1000", 1000) - assert_equal "Fortune 1000", c.name - assert_equal 1000, c.rating - assert c.valid? - assert !c.persisted? - end - - def test_find_or_create_from_one_attribute_should_set_attribute - c = Company.find_or_create_by_name_and_rating("Fortune 1000", 1000) - assert_equal "Fortune 1000", c.name - assert_equal 1000, c.rating - assert c.valid? - assert c.persisted? - end - - def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_set_the_hash - c = Company.find_or_initialize_by_rating(1000, {:name => "Fortune 1000"}) - assert_equal "Fortune 1000", c.name - assert_equal 1000, c.rating - assert c.valid? - assert !c.persisted? - end - - def test_find_or_create_from_one_attribute_should_set_attribute_even_when_set_the_hash - c = Company.find_or_create_by_rating(1000, {:name => "Fortune 1000"}) - assert_equal "Fortune 1000", c.name - assert_equal 1000, c.rating - assert c.valid? - assert c.persisted? - end - - def test_find_or_initialize_should_set_attributes_if_given_as_block - c = Company.find_or_initialize_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 } - assert_equal "Fortune 1000", c.name - assert_equal 1000.to_f, c.rating.to_f - assert c.valid? - assert !c.persisted? - end - - def test_find_or_create_should_set_attributes_if_given_as_block - c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 } - assert_equal "Fortune 1000", c.name - assert_equal 1000.to_f, c.rating.to_f - assert c.valid? - assert c.persisted? - end - - def test_find_or_create_should_work_with_block_on_first_call - class << Company - undef_method(:find_or_create_by_name) if method_defined?(:find_or_create_by_name) - end - c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 } - assert_equal "Fortune 1000", c.name - assert_equal 1000.to_f, c.rating.to_f - assert c.valid? - assert c.persisted? - end - - def test_find_or_initialize_from_two_attributes - another = Topic.find_or_initialize_by_title_and_author_name("Another topic","John") - assert_equal "Another topic", another.title - assert_equal "John", another.author_name - assert !another.persisted? - end - - def test_find_or_initialize_from_two_attributes_but_passing_only_one - assert_raise(ArgumentError) { Topic.find_or_initialize_by_title_and_author_name("Another topic") } - end - - def test_find_or_initialize_from_one_aggregate_attribute_and_one_not - new_customer = Customer.find_or_initialize_by_balance_and_name(Money.new(123), "Elizabeth") - assert_equal 123, new_customer.balance.amount - assert_equal "Elizabeth", new_customer.name - assert !new_customer.persisted? - end - - def test_find_or_initialize_from_one_attribute_and_hash - sig38 = Company.find_or_initialize_by_name({:name => "38signals", :firm_id => 17, :client_of => 23}) - assert_equal "38signals", sig38.name - assert_equal 17, sig38.firm_id - assert_equal 23, sig38.client_of - assert !sig38.persisted? - end - - def test_find_or_initialize_from_one_aggregate_attribute_and_hash - balance = Money.new(123) - name = "Elizabeth" - new_customer = Customer.find_or_initialize_by_balance({:balance => balance, :name => name}) - assert_equal balance, new_customer.balance - assert_equal name, new_customer.name - assert !new_customer.persisted? - end - - def test_find_last_by_one_attribute - assert_equal Topic.last, Topic.find_last_by_title(Topic.last.title) - assert_nil Topic.find_last_by_title("A title with no matches") - end - - def test_find_last_by_invalid_method_syntax - assert_raise(NoMethodError) { Topic.fail_to_find_last_by_title("The First Topic") } - assert_raise(NoMethodError) { Topic.find_last_by_title?("The First Topic") } - end - - def test_find_last_by_one_attribute_with_several_options - assert_equal accounts(:signals37), Account.order('id DESC').where('id != ?', 3).find_last_by_credit_limit(50) - end - - def test_find_last_by_one_missing_attribute - assert_raise(NoMethodError) { Topic.find_last_by_undertitle("The Last Topic!") } - end - - def test_find_last_by_two_attributes - topic = Topic.last - assert_equal topic, Topic.find_last_by_title_and_author_name(topic.title, topic.author_name) - assert_nil Topic.find_last_by_title_and_author_name(topic.title, "Anonymous") - end - - def test_find_last_with_limit_gives_same_result_when_loaded_and_unloaded - scope = Topic.limit(2) - unloaded_last = scope.last - loaded_last = scope.to_a.last - assert_equal loaded_last, unloaded_last - end - - def test_find_last_with_limit_and_offset_gives_same_result_when_loaded_and_unloaded - scope = Topic.offset(2).limit(2) - unloaded_last = scope.last - loaded_last = scope.to_a.last - assert_equal loaded_last, unloaded_last - end - - def test_find_last_with_offset_gives_same_result_when_loaded_and_unloaded - scope = Topic.offset(3) - unloaded_last = scope.last - loaded_last = scope.to_a.last - assert_equal loaded_last, unloaded_last - end - - def test_find_all_by_nil_attribute - topics = Topic.find_all_by_last_read nil - assert_equal 3, topics.size - assert topics.collect(&:last_read).all?(&:nil?) - end - - def test_forwarding_to_dynamic_finders - welcome = Post.find(1) - assert_equal 4, Category.find_all_by_type('SpecialCategory').size - assert_equal 0, welcome.categories.find_all_by_type('SpecialCategory').size - assert_equal 2, welcome.categories.find_all_by_type('Category').size - end - - def test_dynamic_find_all_should_respect_association_order - assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.where("type = 'Client'").to_a - assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.find_all_by_type('Client') - end - - def test_dynamic_find_all_should_respect_association_limit - assert_equal 1, companies(:first_firm).limited_clients.where("type = 'Client'").to_a.length - assert_equal 1, companies(:first_firm).limited_clients.find_all_by_type('Client').length - end - - def test_dynamic_find_all_limit_should_override_association_limit - assert_equal 2, companies(:first_firm).limited_clients.where("type = 'Client'").limit(9_000).to_a.length - assert_equal 2, companies(:first_firm).limited_clients.find_all_by_type('Client', :limit => 9_000).length - end - - def test_dynamic_find_last_without_specified_order - assert_equal companies(:second_client), companies(:first_firm).unsorted_clients.find_last_by_type('Client') - end - - def test_dynamic_find_or_create_from_two_attributes_using_an_association - author = authors(:david) - number_of_posts = Post.count - another = author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body") - assert_equal number_of_posts + 1, Post.count - assert_equal another, author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body") - assert another.persisted? - end - - def test_dynamic_find_all_should_respect_association_order_for_through - assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.where("comments.type = 'SpecialComment'").to_a - assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.find_all_by_type('SpecialComment') - end - - def test_dynamic_find_all_should_respect_association_limit_for_through - assert_equal 1, authors(:david).limited_comments.where("comments.type = 'SpecialComment'").to_a.length - assert_equal 1, authors(:david).limited_comments.find_all_by_type('SpecialComment').length - end - - def test_dynamic_find_all_order_should_override_association_limit_for_through - assert_equal 4, authors(:david).limited_comments.where("comments.type = 'SpecialComment'").limit(9_000).to_a.length - assert_equal 4, authors(:david).limited_comments.find_all_by_type('SpecialComment', :limit => 9_000).length - end - - def test_find_all_include_over_the_same_table_for_through - assert_equal 2, people(:michael).posts.includes(:people).to_a.length - end - - def test_find_or_create_by_resets_cached_counters - person = Person.create! :first_name => 'tenderlove' - post = Post.first - - assert_equal [], person.readers - assert_nil person.readers.find_by_post_id(post.id) - - person.readers.find_or_create_by_post_id(post.id) - - assert_equal 1, person.readers.count - assert_equal 1, person.readers.length - assert_equal post, person.readers.first.post - assert_equal person, person.readers.first.person - end - - def test_find_or_initialize - the_client = companies(:first_firm).clients.find_or_initialize_by_name("Yet another client") - assert_equal companies(:first_firm).id, the_client.firm_id - assert_equal "Yet another client", the_client.name - assert !the_client.persisted? - end - - def test_find_or_create_updates_size - number_of_clients = companies(:first_firm).clients.size - the_client = companies(:first_firm).clients.find_or_create_by_name("Yet another client") - assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size - assert_equal the_client, companies(:first_firm).clients.find_or_create_by_name("Yet another client") - assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size - end - - def test_find_or_initialize_updates_collection_size - number_of_clients = companies(:first_firm).clients_of_firm.size - companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client") - assert_equal number_of_clients + 1, companies(:first_firm).clients_of_firm.size - end - - def test_find_or_initialize_returns_the_instantiated_object - client = companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client") - assert_equal client, companies(:first_firm).clients_of_firm[-1] - end - - def test_find_or_initialize_only_instantiates_a_single_object - number_of_clients = Client.count - companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client").save! - companies(:first_firm).save! - assert_equal number_of_clients+1, Client.count - end - - def test_find_or_create_with_hash - post = authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody') - assert_equal post, authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody') - assert post.persisted? - end - - def test_find_or_create_with_one_attribute_followed_by_hash - post = authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody') - assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody') - assert post.persisted? - end - - def test_find_or_create_should_work_with_block - post = authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'} - assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'} - assert post.persisted? - end - - def test_forwarding_to_dynamic_finders_2 - welcome = Post.find(1) - assert_equal 4, Comment.find_all_by_type('Comment').size - assert_equal 2, welcome.comments.find_all_by_type('Comment').size - end - - def test_dynamic_find_all_by_attributes - authors = Author.all - - davids = authors.find_all_by_name('David') - assert_kind_of Array, davids - assert_equal [authors(:david)], davids - end - - def test_dynamic_find_or_initialize_by_attributes - authors = Author.all - - lifo = authors.find_or_initialize_by_name('Lifo') - assert_equal "Lifo", lifo.name - assert !lifo.persisted? - - assert_equal authors(:david), authors.find_or_initialize_by_name(:name => 'David') - end - - def test_dynamic_find_or_create_by_attributes - authors = Author.all - - lifo = authors.find_or_create_by_name('Lifo') - assert_equal "Lifo", lifo.name - assert lifo.persisted? - - assert_equal authors(:david), authors.find_or_create_by_name(:name => 'David') - end - - def test_dynamic_find_or_create_by_attributes_bang - authors = Author.all - - assert_raises(ActiveRecord::RecordInvalid) { authors.find_or_create_by_name!('') } - - lifo = authors.find_or_create_by_name!('Lifo') - assert_equal "Lifo", lifo.name - assert lifo.persisted? - - assert_equal authors(:david), authors.find_or_create_by_name!(:name => 'David') - end - - def test_finder_block - t = Topic.first - found = nil - Topic.find_by_id(t.id) { |f| found = f } - assert_equal t, found - end - - def test_finder_block_nothing_found - bad_id = Topic.maximum(:id) + 1 - assert_nil Topic.find_by_id(bad_id) { |f| raise } - end - - def test_find_returns_block_value - t = Topic.first - x = Topic.find_by_id(t.id) { |f| "hi mom!" } - assert_equal "hi mom!", x - end - - def test_dynamic_finder_with_invalid_params - assert_raise(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" } - end - - def test_find_by_one_attribute_with_order_option - assert_equal accounts(:signals37), Account.find_by_credit_limit(50, :order => 'id') - assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :order => 'id DESC') - end - - def test_dynamic_find_by_attributes_should_yield_found_object - david = authors(:david) - yielded_value = nil - Author.find_by_name(david.name) do |author| - yielded_value = author - end - assert_equal david, yielded_value - end -end - -class DynamicScopeTest < ActiveRecord::TestCase - fixtures :posts - - def setup - @test_klass = Class.new(Post) do - def self.name; "Post"; end - end - @deprecation_behavior = ActiveSupport::Deprecation.behavior - ActiveSupport::Deprecation.behavior = :silence - end - - def teardown - ActiveSupport::Deprecation.behavior = @deprecation_behavior - end - - def test_dynamic_scope - assert_equal @test_klass.scoped_by_author_id(1).find(1), @test_klass.find(1) - assert_equal @test_klass.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, @test_klass.all.merge!(:where => { :author_id => 1, :title => "Welcome to the weblog"}).first - end - - def test_dynamic_scope_should_create_methods_after_hitting_method_missing - assert @test_klass.methods.grep(/scoped_by_type/).blank? - @test_klass.scoped_by_type(nil) - assert @test_klass.methods.grep(/scoped_by_type/).present? - end - - def test_dynamic_scope_with_less_number_of_arguments - assert_raise(ArgumentError){ @test_klass.scoped_by_author_id_and_title(1) } - end -end - -class DynamicScopeMatchTest < ActiveRecord::TestCase - def test_scoped_by_no_match - assert_nil ActiveRecord::DynamicMatchers::Method.match(nil, "not_scoped_at_all") - end - - def test_scoped_by - model = stub(attribute_aliases: {}) - match = ActiveRecord::DynamicMatchers::Method.match(model, "scoped_by_age_and_sex_and_location") - assert_not_nil match - assert_equal %w(age sex location), match.attribute_names - end -end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 36b87033ae..b277ef0317 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -608,20 +608,6 @@ class DirtyTest < ActiveRecord::TestCase end end - test "partial_updates config attribute is deprecated" do - klass = Class.new(ActiveRecord::Base) - - assert klass.partial_writes? - assert_deprecated { assert klass.partial_updates? } - assert_deprecated { assert klass.partial_updates } - - assert_deprecated { klass.partial_updates = false } - - assert !klass.partial_writes? - assert_deprecated { assert !klass.partial_updates? } - assert_deprecated { assert !klass.partial_updates } - end - private def with_partial_writes(klass, on = true) old = klass.partial_writes? diff --git a/activerecord/test/cases/disconnected_test.rb b/activerecord/test/cases/disconnected_test.rb new file mode 100644 index 0000000000..1fecfd077e --- /dev/null +++ b/activerecord/test/cases/disconnected_test.rb @@ -0,0 +1,27 @@ +require "cases/helper" + +class TestRecord < ActiveRecord::Base +end + +class TestDisconnectedAdapter < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + def setup + skip "in-memory database mustn't disconnect" if in_memory_db? + @connection = ActiveRecord::Base.connection + end + + def teardown + return if in_memory_db? + spec = ActiveRecord::Base.connection_config + ActiveRecord::Base.establish_connection(spec) + end + + test "can't execute statements while disconnected" do + @connection.execute "SELECT count(*) from products" + @connection.disconnect! + assert_raises(ActiveRecord::StatementInvalid) do + @connection.execute "SELECT count(*) from products" + end + end +end diff --git a/activerecord/test/cases/finder_respond_to_test.rb b/activerecord/test/cases/finder_respond_to_test.rb index 9440cd429a..6101eb7b68 100644 --- a/activerecord/test/cases/finder_respond_to_test.rb +++ b/activerecord/test/cases/finder_respond_to_test.rb @@ -21,16 +21,6 @@ class FinderRespondToTest < ActiveRecord::TestCase assert_respond_to Topic, :find_by_title end - def test_should_respond_to_find_all_by_one_attribute - ensure_topic_method_is_not_cached(:find_all_by_title) - assert_respond_to Topic, :find_all_by_title - end - - def test_should_respond_to_find_all_by_two_attributes - ensure_topic_method_is_not_cached(:find_all_by_title_and_author_name) - assert_respond_to Topic, :find_all_by_title_and_author_name - end - def test_should_respond_to_find_by_two_attributes ensure_topic_method_is_not_cached(:find_by_title_and_author_name) assert_respond_to Topic, :find_by_title_and_author_name @@ -41,36 +31,6 @@ class FinderRespondToTest < ActiveRecord::TestCase assert_respond_to Topic, :find_by_heading end - def test_should_respond_to_find_or_initialize_from_one_attribute - ensure_topic_method_is_not_cached(:find_or_initialize_by_title) - assert_respond_to Topic, :find_or_initialize_by_title - end - - def test_should_respond_to_find_or_initialize_from_two_attributes - ensure_topic_method_is_not_cached(:find_or_initialize_by_title_and_author_name) - assert_respond_to Topic, :find_or_initialize_by_title_and_author_name - end - - def test_should_respond_to_find_or_create_from_one_attribute - ensure_topic_method_is_not_cached(:find_or_create_by_title) - assert_respond_to Topic, :find_or_create_by_title - end - - def test_should_respond_to_find_or_create_from_two_attributes - ensure_topic_method_is_not_cached(:find_or_create_by_title_and_author_name) - assert_respond_to Topic, :find_or_create_by_title_and_author_name - end - - def test_should_respond_to_find_or_create_from_one_attribute_bang - ensure_topic_method_is_not_cached(:find_or_create_by_title!) - assert_respond_to Topic, :find_or_create_by_title! - end - - def test_should_respond_to_find_or_create_from_two_attributes_bang - ensure_topic_method_is_not_cached(:find_or_create_by_title_and_author_name!) - assert_respond_to Topic, :find_or_create_by_title_and_author_name! - end - def test_should_not_respond_to_find_by_one_missing_attribute assert !Topic.respond_to?(:find_by_undertitle) end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 6f0de42aef..51a8a13d04 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -613,7 +613,7 @@ class FinderTest < ActiveRecord::TestCase def test_named_bind_with_postgresql_type_casts l = Proc.new { bind(":a::integer '2009-01-01'::date", :a => '10') } assert_nothing_raised(&l) - assert_equal "#{ActiveRecord::Base.quote_value('10')}::integer '2009-01-01'::date", l.call + assert_equal "#{ActiveRecord::Base.connection.quote('10')}::integer '2009-01-01'::date", l.call end def test_string_sanitation @@ -876,11 +876,4 @@ class FinderTest < ActiveRecord::TestCase ensure old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') end - - def with_active_record_default_timezone(zone) - old_zone, ActiveRecord::Base.default_timezone = ActiveRecord::Base.default_timezone, zone - yield - ensure - ActiveRecord::Base.default_timezone = old_zone - end end diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index f6cfee0cb8..2aa56ebc55 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -245,6 +245,22 @@ class FixturesTest < ActiveRecord::TestCase def test_serialized_fixtures assert_equal ["Green", "Red", "Orange"], traffic_lights(:uk).state end + + def test_fixtures_are_set_up_with_database_env_variable + ENV.stubs(:[]).with("DATABASE_URL").returns("sqlite3:///:memory:") + ActiveRecord::Base.stubs(:configurations).returns({}) + test_case = Class.new(ActiveRecord::TestCase) do + fixtures :accounts + + def test_fixtures + assert accounts(:signals37) + end + end + + result = test_case.new(:test_fixtures).run + + assert result.passed?, "Expected #{result.name} to pass:\n#{result}" + end end if Account.connection.respond_to?(:reset_pk_sequence!) @@ -477,10 +493,6 @@ class CustomConnectionFixturesTest < ActiveRecord::TestCase fixtures :courses self.use_transactional_fixtures = false - def test_connection_instance_method_deprecation - assert_deprecated { courses(:ruby).connection } - end - def test_leaky_destroy assert_nothing_raised { courses(:ruby) } courses(:ruby).destroy diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 7dbb6616f8..f96978aff8 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -124,7 +124,7 @@ module LogIntercepter def self.extended(base) base.logged = [] end - def log(sql, name, binds = [], &block) + def log(sql, name = 'SQL', binds = [], &block) if @intercepted @logged << [sql, name, binds] yield diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb index b0a7cda2f3..f5daca2fa8 100644 --- a/activerecord/test/cases/integration_test.rb +++ b/activerecord/test/cases/integration_test.rb @@ -32,6 +32,8 @@ class IntegrationTest < ActiveRecord::TestCase est_key = Developer.first.cache_key assert_equal utc_key, est_key + ensure + Time.zone = 'UTC' end def test_cache_key_format_for_existing_record_with_updated_at diff --git a/activerecord/test/cases/invalid_connection_test.rb b/activerecord/test/cases/invalid_connection_test.rb new file mode 100644 index 0000000000..f2d8f18ec7 --- /dev/null +++ b/activerecord/test/cases/invalid_connection_test.rb @@ -0,0 +1,22 @@ +require "cases/helper" + +class TestAdapterWithInvalidConnection < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + class Bird < ActiveRecord::Base + end + + def setup + # Can't just use current adapter; sqlite3 will create a database + # file on the fly. + Bird.establish_connection adapter: 'mysql', database: 'i_do_not_exist' + end + + def teardown + Bird.remove_connection + end + + test "inspect on Model class does not raise" do + assert_equal "#{Bird.name}(no database connection)", Bird.inspect + end +end diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index 77891b9156..dfa12cb97c 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -8,6 +8,7 @@ require 'models/legacy_thing' require 'models/reference' require 'models/string_key_object' require 'models/car' +require 'models/bulb' require 'models/engine' require 'models/wheel' require 'models/treasure' @@ -16,6 +17,7 @@ class LockWithoutDefault < ActiveRecord::Base; end class LockWithCustomColumnWithoutDefault < ActiveRecord::Base self.table_name = :lock_without_defaults_cust + self.column_defaults # to test @column_defaults caching. self.locking_column = :custom_lock_version end @@ -26,6 +28,18 @@ end class OptimisticLockingTest < ActiveRecord::TestCase fixtures :people, :legacy_things, :references, :string_key_objects, :peoples_treasures + def test_quote_value_passed_lock_col + p1 = Person.find(1) + assert_equal 0, p1.lock_version + + Person.expects(:quote_value).with(0, Person.columns_hash[Person.locking_column]).returns('0').once + + p1.first_name = 'anika2' + p1.save! + + assert_equal 1, p1.lock_version + end + def test_non_integer_lock_existing s1 = StringKeyObject.find("record1") s2 = StringKeyObject.find("record1") @@ -242,7 +256,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase car = Car.create! assert_difference 'car.wheels.count' do - car.wheels << Wheel.create! + car.wheels << Wheel.create! end assert_difference 'car.wheels.count', -1 do car.destroy diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index 57eac0c175..3bdc5a1302 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -56,6 +56,13 @@ class LogSubscriberTest < ActiveRecord::TestCase assert_equal 2, logger.debugs.length end + def test_sql_statements_are_not_squeezed + event = Struct.new(:duration, :payload) + logger = TestDebugLogSubscriber.new + logger.sql(event.new(0, sql: 'ruby rails')) + assert_match(/ruby rails/, logger.debugs.first) + end + def test_ignore_binds_payload_with_nil_column event = Struct.new(:duration, :payload) diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb index ec2926632c..aa606ac8bb 100644 --- a/activerecord/test/cases/migration/column_attributes_test.rb +++ b/activerecord/test/cases/migration/column_attributes_test.rb @@ -16,32 +16,23 @@ module ActiveRecord end def test_add_remove_single_field_using_string_arguments - assert_not TestModel.column_methods_hash.key?(:last_name) + assert_no_column TestModel, :last_name add_column 'test_models', 'last_name', :string - - TestModel.reset_column_information - - assert TestModel.column_methods_hash.key?(:last_name) + assert_column TestModel, :last_name remove_column 'test_models', 'last_name' - - TestModel.reset_column_information - assert_not TestModel.column_methods_hash.key?(:last_name) + assert_no_column TestModel, :last_name end def test_add_remove_single_field_using_symbol_arguments - assert_not TestModel.column_methods_hash.key?(:last_name) + assert_no_column TestModel, :last_name add_column :test_models, :last_name, :string - - TestModel.reset_column_information - assert TestModel.column_methods_hash.key?(:last_name) + assert_column TestModel, :last_name remove_column :test_models, :last_name - - TestModel.reset_column_information - assert_not TestModel.column_methods_hash.key?(:last_name) + assert_no_column TestModel, :last_name end def test_unabstracted_database_dependent_types @@ -168,26 +159,6 @@ module ActiveRecord assert_equal Date, bob.favorite_day.class end - # Oracle adapter stores Time or DateTime with timezone value already in _before_type_cast column - # therefore no timezone change is done afterwards when default timezone is changed - unless current_adapter?(:OracleAdapter) - # Test DateTime column and defaults, including timezone. - # FIXME: moment of truth may be Time on 64-bit platforms. - if bob.moment_of_truth.is_a?(DateTime) - - with_env_tz 'US/Eastern' do - bob.reload - assert_equal DateTime.local_offset, bob.moment_of_truth.offset - assert_not_equal 0, bob.moment_of_truth.offset - assert_not_equal "Z", bob.moment_of_truth.zone - # US/Eastern is -5 hours from GMT - assert_equal Rational(-5, 24), bob.moment_of_truth.offset - assert_match(/\A-05:00\Z/, bob.moment_of_truth.zone) - assert_equal DateTime::ITALY, bob.moment_of_truth.start - end - end - end - assert_instance_of TrueClass, bob.male? assert_kind_of BigDecimal, bob.wealth end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index e99312c245..ed080b2995 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -177,20 +177,18 @@ class MigrationTest < ActiveRecord::TestCase end def test_filtering_migrations - assert !Person.column_methods_hash.include?(:last_name) + assert_no_column Person, :last_name assert !Reminder.table_exists? name_filter = lambda { |migration| migration.name == "ValidPeopleHaveLastNames" } ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", &name_filter) - Person.reset_column_information - assert Person.column_methods_hash.include?(:last_name) + assert_column Person, :last_name assert_raise(ActiveRecord::StatementInvalid) { Reminder.first } ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", &name_filter) - Person.reset_column_information - assert !Person.column_methods_hash.include?(:last_name) + assert_no_column Person, :last_name assert_raise(ActiveRecord::StatementInvalid) { Reminder.first } end @@ -237,7 +235,7 @@ class MigrationTest < ActiveRecord::TestCase skip "not supported on #{ActiveRecord::Base.connection.class}" end - assert_not Person.column_methods_hash.include?(:last_name) + assert_no_column Person, :last_name migration = Class.new(ActiveRecord::Migration) { def version; 100 end @@ -253,8 +251,7 @@ class MigrationTest < ActiveRecord::TestCase assert_equal "An error has occurred, this and all later migrations canceled:\n\nSomething broke", e.message - Person.reset_column_information - assert_not Person.column_methods_hash.include?(:last_name), + assert_no_column Person, :last_name, "On error, the Migrator should revert schema changes but it did not." end @@ -263,7 +260,7 @@ class MigrationTest < ActiveRecord::TestCase skip "not supported on #{ActiveRecord::Base.connection.class}" end - assert_not Person.column_methods_hash.include?(:last_name) + assert_no_column Person, :last_name migration = Class.new(ActiveRecord::Migration) { def version; 100 end @@ -279,8 +276,7 @@ class MigrationTest < ActiveRecord::TestCase assert_equal "An error has occurred, this migration was canceled:\n\nSomething broke", e.message - Person.reset_column_information - assert_not Person.column_methods_hash.include?(:last_name), + assert_no_column Person, :last_name, "On error, the Migrator should revert schema changes but it did not." end @@ -289,7 +285,7 @@ class MigrationTest < ActiveRecord::TestCase skip "not supported on #{ActiveRecord::Base.connection.class}" end - assert_not Person.column_methods_hash.include?(:last_name) + assert_no_column Person, :last_name migration = Class.new(ActiveRecord::Migration) { self.disable_ddl_transaction! @@ -305,12 +301,11 @@ class MigrationTest < ActiveRecord::TestCase e = assert_raise(StandardError) { migrator.migrate } assert_equal "An error has occurred, all later migrations canceled:\n\nSomething broke", e.message - Person.reset_column_information - assert Person.column_methods_hash.include?(:last_name), + assert_column Person, :last_name, "without ddl transactions, the Migrator should not rollback on error but it did." ensure Person.reset_column_information - if Person.column_methods_hash.include?(:last_name) + if Person.column_names.include?('last_name') Person.connection.remove_column('people', 'last_name') end end diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb index b5a69c4a92..3f9854200d 100644 --- a/activerecord/test/cases/migrator_test.rb +++ b/activerecord/test/cases/migrator_test.rb @@ -91,12 +91,6 @@ module ActiveRecord assert_equal 'AddExpressions', migrations[0].name end - def test_deprecated_constructor - assert_deprecated do - ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid") - end - end - def test_relative_migrations list = Dir.chdir(MIGRATIONS_ROOT) do ActiveRecord::Migrator.migrations("valid/") diff --git a/activerecord/test/cases/multiparameter_attributes_test.rb b/activerecord/test/cases/multiparameter_attributes_test.rb index 1209f5460f..ce21760645 100644 --- a/activerecord/test/cases/multiparameter_attributes_test.rb +++ b/activerecord/test/cases/multiparameter_attributes_test.rb @@ -11,6 +11,10 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase Time.zone = nil end + def teardown + ActiveRecord::Base.default_timezone = :utc + end + def test_multiparameter_attributes_on_date attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "24" } topic = Topic.find(1) diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 6fe81e0d96..2f89699df7 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -797,26 +797,6 @@ module NestedAttributesOnACollectionAssociationTests end end - def test_validate_presence_of_parent_fails_without_inverse_of - Man.accepts_nested_attributes_for(:interests) - Man.reflect_on_association(:interests).options.delete(:inverse_of) - Man.reflect_on_association(:interests).clear_inverse_of_cache! - Interest.reflect_on_association(:man).options.delete(:inverse_of) - Interest.reflect_on_association(:man).clear_inverse_of_cache! - - repair_validations(Interest) do - Interest.validates_presence_of(:man) - assert_no_difference ['Man.count', 'Interest.count'] do - man = Man.create(:name => 'John', - :interests_attributes => [{:topic=>'Cars'}, {:topic=>'Sports'}]) - assert !man.errors[:"interests.man"].empty? - end - end - ensure - Man.reflect_on_association(:interests).options[:inverse_of] = :man - Interest.reflect_on_association(:man).options[:inverse_of] = :interests - end - def test_can_use_symbols_as_object_identifier @pirate.attributes = { :parrots_attributes => { :foo => { :name => 'Lovely Day' }, :bar => { :name => 'Blown Away' } } } assert_nothing_raised(NoMethodError) { @pirate.save! } diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb index a8a9b06ec4..626c6aeaf8 100644 --- a/activerecord/test/cases/pooled_connections_test.rb +++ b/activerecord/test/cases/pooled_connections_test.rb @@ -16,22 +16,6 @@ class PooledConnectionsTest < ActiveRecord::TestCase @per_test_teardown.each {|td| td.call } end - def checkout_connections - ActiveRecord::Base.establish_connection(@connection.merge({:pool => 2, :checkout_timeout => 0.3})) - @connections = [] - @timed_out = 0 - - 4.times do - Thread.new do - begin - @connections << ActiveRecord::Base.connection_pool.checkout - rescue ActiveRecord::ConnectionTimeoutError - @timed_out += 1 - end - end.join - end - end - # Will deadlock due to lack of Monitor timeouts in 1.9 def checkout_checkin_connections(pool_size, threads) ActiveRecord::Base.establish_connection(@connection.merge({:pool => pool_size, :checkout_timeout => 0.5})) diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index 3dd11ae89d..85fbe01416 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -53,50 +53,40 @@ module ActiveRecord end def test_quoted_time_utc - before = ActiveRecord::Base.default_timezone - ActiveRecord::Base.default_timezone = :utc - t = Time.now - assert_equal t.getutc.to_s(:db), @quoter.quoted_date(t) - ensure - ActiveRecord::Base.default_timezone = before + with_active_record_default_timezone :utc do + t = Time.now + assert_equal t.getutc.to_s(:db), @quoter.quoted_date(t) + end end def test_quoted_time_local - before = ActiveRecord::Base.default_timezone - ActiveRecord::Base.default_timezone = :local - t = Time.now - assert_equal t.getlocal.to_s(:db), @quoter.quoted_date(t) - ensure - ActiveRecord::Base.default_timezone = before + with_active_record_default_timezone :local do + t = Time.now + assert_equal t.getlocal.to_s(:db), @quoter.quoted_date(t) + end end def test_quoted_time_crazy - before = ActiveRecord::Base.default_timezone - ActiveRecord::Base.default_timezone = :asdfasdf - t = Time.now - assert_equal t.getlocal.to_s(:db), @quoter.quoted_date(t) - ensure - ActiveRecord::Base.default_timezone = before + with_active_record_default_timezone :asdfasdf do + t = Time.now + assert_equal t.getlocal.to_s(:db), @quoter.quoted_date(t) + end end def test_quoted_datetime_utc - before = ActiveRecord::Base.default_timezone - ActiveRecord::Base.default_timezone = :utc - t = DateTime.now - assert_equal t.getutc.to_s(:db), @quoter.quoted_date(t) - ensure - ActiveRecord::Base.default_timezone = before + with_active_record_default_timezone :utc do + t = DateTime.now + assert_equal t.getutc.to_s(:db), @quoter.quoted_date(t) + end end ### # DateTime doesn't define getlocal, so make sure it does nothing def test_quoted_datetime_local - before = ActiveRecord::Base.default_timezone - ActiveRecord::Base.default_timezone = :local - t = DateTime.now - assert_equal t.to_s(:db), @quoter.quoted_date(t) - ensure - ActiveRecord::Base.default_timezone = before + with_active_record_default_timezone :local do + t = DateTime.now + assert_equal t.to_s(:db), @quoter.quoted_date(t) + end end def test_quote_with_quoted_id diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index b5314bc9be..0e5c7df2cc 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -186,14 +186,6 @@ class ReflectionTest < ActiveRecord::TestCase ActiveRecord::Base.store_full_sti_class = true end - def test_reflection_of_all_associations - # FIXME these assertions bust a lot - assert_equal 39, Firm.reflect_on_all_associations.size - assert_equal 29, Firm.reflect_on_all_associations(:has_many).size - assert_equal 10, Firm.reflect_on_all_associations(:has_one).size - assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size - end - def test_reflection_should_not_raise_error_when_compared_to_other_object assert_nothing_raised { Firm.reflections[:clients] == Object.new } end @@ -260,8 +252,9 @@ class ReflectionTest < ActiveRecord::TestCase reflection = ActiveRecord::Reflection::AssociationReflection.new(:fuu, :edge, nil, {}, Author) assert_raises(ActiveRecord::UnknownPrimaryKey) { reflection.association_primary_key } - through = ActiveRecord::Reflection::ThroughReflection.new(:fuu, :edge, nil, {}, Author) - through.stubs(:source_reflection).returns(stub_everything(:options => {}, :class_name => 'Edge')) + through = Class.new(ActiveRecord::Reflection::ThroughReflection) { + define_method(:source_reflection) { reflection } + }.new(:fuu, :edge, nil, {}, Author) assert_raises(ActiveRecord::UnknownPrimaryKey) { through.association_primary_key } end diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index 55fd068d37..f99801c437 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -9,6 +9,9 @@ module ActiveRecord fixtures :posts, :comments, :authors class FakeKlass < Struct.new(:table_name, :name) + def self.connection + Post.connection + end end def test_construction @@ -108,6 +111,12 @@ module ActiveRecord assert_equal({}, relation.scope_for_create) end + def test_bad_constants_raise_errors + assert_raises(NameError) do + ActiveRecord::Relation::HelloWorld + end + end + def test_empty_eager_loading? relation = Relation.new FakeKlass, :b assert !relation.eager_loading? @@ -176,7 +185,7 @@ module ActiveRecord assert_equal ['foo = bar'], relation.where_values end - def test_relation_merging_with_merged_joins + def test_relation_merging_with_merged_joins_as_symbols special_comments_with_ratings = SpecialComment.joins(:ratings) posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings) assert_equal 3, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length @@ -190,12 +199,23 @@ module ActiveRecord assert_equal false, post.respond_to?(:title), "post should not respond_to?(:body) since invoking it raises exception" end + def test_relation_merging_with_merged_joins_as_strings + join_string = "LEFT OUTER JOIN #{Rating.quoted_table_name} ON #{SpecialComment.quoted_table_name}.id = #{Rating.quoted_table_name}.comment_id" + special_comments_with_ratings = SpecialComment.joins join_string + posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings) + assert_equal 3, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length + end + end class RelationMutationTest < ActiveSupport::TestCase class FakeKlass < Struct.new(:table_name, :name) - def quoted_table_name - %{"#{table_name}"} + def arel_table + Post.arel_table + end + + def connection + Post.connection end end @@ -217,7 +237,17 @@ module ActiveRecord test "#order! with symbol prepends the table name" do assert relation.order!(:name).equal?(relation) - assert_equal ['"posts".name ASC'], relation.order_values + node = relation.order_values.first + assert node.ascending? + assert_equal :name, node.expr.name + assert_equal "posts", node.expr.relation.name + end + + test "#order! on non-string does not attempt regexp match for references" do + obj = Object.new + obj.expects(:=~).never + assert relation.order!(obj) + assert_equal [obj], relation.order_values end test '#references!' do @@ -278,7 +308,7 @@ module ActiveRecord assert_equal({foo: 'bar'}, relation.create_with_value) end - test 'merge!' do + def test_merge! assert relation.merge!(where: :foo).equal?(relation) assert_equal [:foo], relation.where_values end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index e746ca2805..d0f8731b9a 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -368,7 +368,7 @@ class RelationTest < ActiveRecord::TestCase def test_respond_to_dynamic_finders relation = Topic.all - ["find_by_title", "find_by_title_and_author_name", "find_or_create_by_title", "find_or_initialize_by_title_and_author_name"].each do |method| + ["find_by_title", "find_by_title_and_author_name"].each do |method| assert_respond_to relation, method, "Topic.all should respond to #{method.inspect}" end end @@ -481,6 +481,14 @@ class RelationTest < ActiveRecord::TestCase assert_equal Post.find(1).last_comment, post.last_comment end + def test_to_sql_on_eager_join + expected = assert_sql { + Post.eager_load(:last_comment).order('comments.id DESC').to_a + }.first + actual = Post.eager_load(:last_comment).order('comments.id DESC').to_sql + assert_equal expected, actual + end + def test_loading_with_one_association_with_non_preload posts = Post.eager_load(:last_comment).order('comments.id DESC') post = posts.find { |p| p.id == 1 } @@ -1208,33 +1216,12 @@ class RelationTest < ActiveRecord::TestCase assert_equal "id", Post.all.primary_key end - def test_eager_loading_with_conditions_on_joins - scope = Post.includes(:comments) - - # This references the comments table, and so it should cause the comments to be eager - # loaded via a JOIN, rather than by subsequent queries. - scope = scope.joins( - Post.arel_table.create_join( - Post.arel_table, - Post.arel_table.create_on(Comment.arel_table[:id].eq(3)) - ) - ) - + def test_disable_implicit_join_references_is_deprecated assert_deprecated do - assert scope.eager_loading? + ActiveRecord::Base.disable_implicit_join_references = true end end - def test_turn_off_eager_loading_with_conditions_on_joins - original_value = ActiveRecord::Base.disable_implicit_join_references - ActiveRecord::Base.disable_implicit_join_references = true - - scope = Topic.where(author_email_address: 'my.example@gmail.com').includes(:replies) - assert_not scope.eager_loading? - ensure - ActiveRecord::Base.disable_implicit_join_references = original_value - end - def test_ordering_with_extra_spaces assert_equal authors(:david), Author.order('id DESC , name DESC').last end @@ -1569,4 +1556,21 @@ class RelationTest < ActiveRecord::TestCase merged = left.merge(right) assert_equal binds, merged.bind_values end + + def test_merging_reorders_bind_params + post = Post.first + id_column = Post.columns_hash['id'] + title_column = Post.columns_hash['title'] + + bv = Post.connection.substitute_at id_column, 0 + + right = Post.where(id: bv) + right.bind_values += [[id_column, post.id]] + + left = Post.where(title: bv) + left.bind_values += [[title_column, post.title]] + + merged = left.merge(right) + assert_equal post, merged.first + end end diff --git a/activerecord/test/cases/result_test.rb b/activerecord/test/cases/result_test.rb new file mode 100644 index 0000000000..b6c583dbf5 --- /dev/null +++ b/activerecord/test/cases/result_test.rb @@ -0,0 +1,32 @@ +require "cases/helper" + +module ActiveRecord + class ResultTest < ActiveRecord::TestCase + def result + Result.new(['col_1', 'col_2'], [ + ['row 1 col 1', 'row 1 col 2'], + ['row 2 col 1', 'row 2 col 2'] + ]) + end + + def test_to_hash_returns_row_hashes + assert_equal [ + {'col_1' => 'row 1 col 1', 'col_2' => 'row 1 col 2'}, + {'col_1' => 'row 2 col 1', 'col_2' => 'row 2 col 2'} + ], result.to_hash + end + + def test_each_with_block_returns_row_hashes + result.each do |row| + assert_equal ['col_1', 'col_2'], row.keys + end + end + + def test_each_without_block_returns_an_enumerator + result.each.with_index do |row, index| + assert_equal ['col_1', 'col_2'], row.keys + assert_kind_of Integer, index + end + end + end +end diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index 0f69443839..4bcc97ec44 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -56,7 +56,10 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_default_scoping_with_threads 2.times do - Thread.new { assert DeveloperOrderedBySalary.all.to_sql.include?('salary DESC') }.join + Thread.new { + assert DeveloperOrderedBySalary.all.to_sql.include?('salary DESC') + DeveloperOrderedBySalary.connection.close + }.join end end @@ -153,9 +156,8 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_order_to_unscope_reordering - expected = DeveloperOrderedBySalary.all.collect { |dev| [dev.name, dev.id] } - received = DeveloperOrderedBySalary.order('salary DESC, name ASC').reverse_order.unscope(:order).collect { |dev| [dev.name, dev.id] } - assert_equal expected, received + scope = DeveloperOrderedBySalary.order('salary DESC, name ASC').reverse_order.unscope(:order) + assert !(scope.to_sql =~ /order/i) end def test_unscope_reverse_order @@ -361,9 +363,11 @@ class DefaultScopingTest < ActiveRecord::TestCase threads << Thread.new do Thread.current[:long_default_scope] = true assert_equal 1, ThreadsafeDeveloper.all.to_a.count + ThreadsafeDeveloper.connection.close end threads << Thread.new do assert_equal 1, ThreadsafeDeveloper.all.to_a.count + ThreadsafeDeveloper.connection.close end threads.each(&:join) end diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index afe32af1d1..72c9787b84 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -60,11 +60,6 @@ class NamedScopingTest < ActiveRecord::TestCase assert Topic.approved.respond_to?(:length) end - def test_respond_to_respects_include_private_parameter - assert !Topic.approved.respond_to?(:tables_in_string) - assert Topic.approved.respond_to?(:tables_in_string, true) - end - def test_scopes_with_options_limit_finds_to_those_matching_the_criteria_specified assert !Topic.all.merge!(:where => {:approved => true}).to_a.empty? @@ -440,24 +435,13 @@ class NamedScopingTest < ActiveRecord::TestCase end end - def test_eager_scopes_are_deprecated - klass = Class.new(ActiveRecord::Base) - klass.table_name = 'posts' - - assert_deprecated do - klass.scope :welcome_2, klass.where(:id => posts(:welcome).id) - end - assert_equal [posts(:welcome).title], klass.welcome_2.map(&:title) - end - - def test_eager_default_scope_relations_are_deprecated + def test_eager_default_scope_relations_are_remove klass = Class.new(ActiveRecord::Base) klass.table_name = 'posts' - assert_deprecated do + assert_raises(ArgumentError) do klass.send(:default_scope, klass.where(:id => posts(:welcome).id)) end - assert_equal [posts(:welcome).title], klass.all.map(&:title) end def test_subclass_merges_scopes_properly diff --git a/activerecord/test/cases/tasks/firebird_rake_test.rb b/activerecord/test/cases/tasks/firebird_rake_test.rb deleted file mode 100644 index c54989ae34..0000000000 --- a/activerecord/test/cases/tasks/firebird_rake_test.rb +++ /dev/null @@ -1,100 +0,0 @@ -require 'cases/helper' - -unless defined?(FireRuby::Database) -module FireRuby - module Database; end -end -end - -module ActiveRecord - module FirebirdSetupper - def setup - @database = 'db.firebird' - @connection = stub :connection - @configuration = { - 'adapter' => 'firebird', - 'database' => @database - } - ActiveRecord::Base.stubs(:connection).returns(@connection) - ActiveRecord::Base.stubs(:establish_connection).returns(true) - - @tasks = Class.new(ActiveRecord::Tasks::FirebirdDatabaseTasks) do - def initialize(configuration) - ActiveSupport::Deprecation.silence { super } - end - end - ActiveRecord::Tasks::DatabaseTasks.stubs(:class_for_adapter).returns(@tasks) unless defined? ActiveRecord::ConnectionAdapters::FirebirdAdapter - end - end - - class FirebirdDBCreateTest < ActiveRecord::TestCase - include FirebirdSetupper - - def test_db_retrieves_create - message = capture(:stderr) do - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end - assert_match(/not supported/, message) - end - end - - class FirebirdDBDropTest < ActiveRecord::TestCase - include FirebirdSetupper - - def test_db_retrieves_drop - message = capture(:stderr) do - ActiveRecord::Tasks::DatabaseTasks.drop @configuration - end - assert_match(/not supported/, message) - end - end - - class FirebirdDBCharsetAndCollationTest < ActiveRecord::TestCase - include FirebirdSetupper - - def test_db_retrieves_collation - assert_raise NoMethodError do - ActiveRecord::Tasks::DatabaseTasks.collation @configuration - end - end - - def test_db_retrieves_charset - message = capture(:stderr) do - ActiveRecord::Tasks::DatabaseTasks.charset @configuration - end - assert_match(/not supported/, message) - end - end - - class FirebirdStructureDumpTest < ActiveRecord::TestCase - include FirebirdSetupper - - def setup - super - FireRuby::Database.stubs(:db_string_for).returns(@database) - end - - def test_structure_dump - filename = "filebird.sql" - Kernel.expects(:system).with("isql -a #{@database} > #{filename}") - - ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename) - end - end - - class FirebirdStructureLoadTest < ActiveRecord::TestCase - include FirebirdSetupper - - def setup - super - FireRuby::Database.stubs(:db_string_for).returns(@database) - end - - def test_structure_load - filename = "firebird.sql" - Kernel.expects(:system).with("isql -i #{filename} #{@database}") - - ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename) - end - end -end diff --git a/activerecord/test/cases/tasks/oracle_rake_test.rb b/activerecord/test/cases/tasks/oracle_rake_test.rb deleted file mode 100644 index 5f840febbc..0000000000 --- a/activerecord/test/cases/tasks/oracle_rake_test.rb +++ /dev/null @@ -1,93 +0,0 @@ -require 'cases/helper' - -module ActiveRecord - module OracleSetupper - def setup - @database = 'db.oracle' - @connection = stub :connection - @configuration = { - 'adapter' => 'oracle', - 'database' => @database - } - ActiveRecord::Base.stubs(:connection).returns(@connection) - ActiveRecord::Base.stubs(:establish_connection).returns(true) - - @tasks = Class.new(ActiveRecord::Tasks::OracleDatabaseTasks) do - def initialize(configuration) - ActiveSupport::Deprecation.silence { super } - end - end - ActiveRecord::Tasks::DatabaseTasks.stubs(:class_for_adapter).returns(@tasks) unless defined? ActiveRecord::ConnectionAdapters::OracleAdapter - end - end - - class OracleDBCreateTest < ActiveRecord::TestCase - include OracleSetupper - - def test_db_retrieves_create - message = capture(:stderr) do - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end - assert_match(/not supported/, message) - end - end - - class OracleDBDropTest < ActiveRecord::TestCase - include OracleSetupper - - def test_db_retrieves_drop - message = capture(:stderr) do - ActiveRecord::Tasks::DatabaseTasks.drop @configuration - end - assert_match(/not supported/, message) - end - end - - class OracleDBCharsetAndCollationTest < ActiveRecord::TestCase - include OracleSetupper - - def test_db_retrieves_collation - assert_raise NoMethodError do - ActiveRecord::Tasks::DatabaseTasks.collation @configuration - end - end - - def test_db_retrieves_charset - message = capture(:stderr) do - ActiveRecord::Tasks::DatabaseTasks.charset @configuration - end - assert_match(/not supported/, message) - end - end - - class OracleStructureDumpTest < ActiveRecord::TestCase - include OracleSetupper - - def setup - super - @connection.stubs(:structure_dump).returns("select sysdate from dual;") - end - - def test_structure_dump - filename = "oracle.sql" - ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename) - assert File.exists?(filename) - ensure - FileUtils.rm_f(filename) - end - end - - class OracleStructureLoadTest < ActiveRecord::TestCase - include OracleSetupper - - def test_structure_load - filename = "oracle.sql" - - open(filename, 'w') { |f| f.puts("select sysdate from dual;") } - @connection.stubs(:execute).with("select sysdate from dual;\n") - ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename) - ensure - FileUtils.rm_f(filename) - end - end -end diff --git a/activerecord/test/cases/tasks/sqlserver_rake_test.rb b/activerecord/test/cases/tasks/sqlserver_rake_test.rb deleted file mode 100644 index 0f1264b8ce..0000000000 --- a/activerecord/test/cases/tasks/sqlserver_rake_test.rb +++ /dev/null @@ -1,87 +0,0 @@ -require 'cases/helper' - -module ActiveRecord - module SqlserverSetupper - def setup - @database = 'db.sqlserver' - @connection = stub :connection - @configuration = { - 'adapter' => 'sqlserver', - 'database' => @database, - 'host' => 'localhost', - 'username' => 'username', - 'password' => 'password', - } - ActiveRecord::Base.stubs(:connection).returns(@connection) - ActiveRecord::Base.stubs(:establish_connection).returns(true) - - @tasks = Class.new(ActiveRecord::Tasks::SqlserverDatabaseTasks) do - def initialize(configuration) - ActiveSupport::Deprecation.silence { super } - end - end - ActiveRecord::Tasks::DatabaseTasks.stubs(:class_for_adapter).returns(@tasks) unless defined? ActiveRecord::ConnectionAdapters::SQLServerAdapter - end - end - - class SqlserverDBCreateTest < ActiveRecord::TestCase - include SqlserverSetupper - - def test_db_retrieves_create - message = capture(:stderr) do - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end - assert_match(/not supported/, message) - end - end - - class SqlserverDBDropTest < ActiveRecord::TestCase - include SqlserverSetupper - - def test_db_retrieves_drop - message = capture(:stderr) do - ActiveRecord::Tasks::DatabaseTasks.drop @configuration - end - assert_match(/not supported/, message) - end - end - - class SqlserverDBCharsetAndCollationTest < ActiveRecord::TestCase - include SqlserverSetupper - - def test_db_retrieves_collation - assert_raise NoMethodError do - ActiveRecord::Tasks::DatabaseTasks.collation @configuration - end - end - - def test_db_retrieves_charset - message = capture(:stderr) do - ActiveRecord::Tasks::DatabaseTasks.charset @configuration - end - assert_match(/not supported/, message) - end - end - - class SqlserverStructureDumpTest < ActiveRecord::TestCase - include SqlserverSetupper - - def test_structure_dump - filename = "sqlserver.sql" - Kernel.expects(:system).with("smoscript -s localhost -d #{@database} -u username -p password -f #{filename} -A -U") - - ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename) - end - end - - class SqlserverStructureLoadTest < ActiveRecord::TestCase - include SqlserverSetupper - - def test_structure_load - filename = "sqlserver.sql" - Kernel.expects(:system).with("sqlcmd -S localhost -d #{@database} -U username -P password -i #{filename}") - - ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename) - end - end -end diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb index f3f7054794..c33e5cdc00 100644 --- a/activerecord/test/cases/test_case.rb +++ b/activerecord/test/cases/test_case.rb @@ -1,9 +1,107 @@ -ActiveSupport::Deprecation.silence do - require 'active_record/test_case' -end +require 'active_support/test_case' + +module ActiveRecord + # = Active Record Test Case + # + # Defines some test assertions to test against SQL queries. + class TestCase < ActiveSupport::TestCase #:nodoc: + def teardown + SQLCounter.clear_log + end + + def assert_date_from_db(expected, actual, message = nil) + # SybaseAdapter doesn't have a separate column type just for dates, + # so the time is in the string and incorrectly formatted + if current_adapter?(:SybaseAdapter) + assert_equal expected.to_s, actual.to_date.to_s, message + else + assert_equal expected.to_s, actual.to_s, message + end + end + + def assert_sql(*patterns_to_match) + SQLCounter.clear_log + yield + SQLCounter.log_all + ensure + failed_patterns = [] + patterns_to_match.each do |pattern| + failed_patterns << pattern unless SQLCounter.log_all.any?{ |sql| pattern === sql } + end + assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}" + end + + def assert_queries(num = 1, options = {}) + ignore_none = options.fetch(:ignore_none) { num == :any } + SQLCounter.clear_log + x = yield + the_log = ignore_none ? SQLCounter.log_all : SQLCounter.log + if num == :any + assert_operator the_log.size, :>=, 1, "1 or more queries expected, but none were executed." + else + mesg = "#{the_log.size} instead of #{num} queries were executed.#{the_log.size == 0 ? '' : "\nQueries:\n#{the_log.join("\n")}"}" + assert_equal num, the_log.size, mesg + end + x + end + + def assert_no_queries(&block) + assert_queries(0, :ignore_none => true, &block) + end + + def assert_column(model, column_name, msg=nil) + assert has_column?(model, column_name), msg + end + + def assert_no_column(model, column_name, msg=nil) + assert_not has_column?(model, column_name), msg + end + + def has_column?(model, column_name) + model.reset_column_information + model.column_names.include?(column_name.to_s) + end + end -ActiveRecord::TestCase.class_eval do - def sqlite3? connection - connection.class.name.split('::').last == "SQLite3Adapter" + class SQLCounter + class << self + attr_accessor :ignored_sql, :log, :log_all + def clear_log; self.log = []; self.log_all = []; end + end + + self.clear_log + + 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, /^SHOW search_path/i] + sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im] + + [oracle_ignored, mysql_ignored, postgresql_ignored, sqlite3_ignored].each do |db_ignored_sql| + ignored_sql.concat db_ignored_sql + end + + attr_reader :ignore + + def initialize(ignore = Regexp.union(self.class.ignored_sql)) + @ignore = ignore + end + + def call(name, start, finish, message_id, values) + sql = values[:sql] + + # FIXME: this seems bad. we should probably have a better way to indicate + # the query was cached + return if 'CACHE' == values[:name] + + self.class.log_all << sql + self.class.log << sql unless ignore =~ sql + end end + + ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new) end diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 9485de88a6..5644a35385 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -182,9 +182,9 @@ class TransactionCallbacksTest < ActiveRecord::TestCase end def test_call_after_rollback_when_commit_fails - @first.class.connection.class.send(:alias_method, :real_method_commit_db_transaction, :commit_db_transaction) + @first.class.connection.singleton_class.send(:alias_method, :real_method_commit_db_transaction, :commit_db_transaction) begin - @first.class.connection.class.class_eval do + @first.class.connection.singleton_class.class_eval do def commit_db_transaction; raise "boom!"; end end @@ -194,8 +194,8 @@ class TransactionCallbacksTest < ActiveRecord::TestCase assert !@first.save rescue nil assert_equal [:after_rollback], @first.history ensure - @first.class.connection.class.send(:remove_method, :commit_db_transaction) - @first.class.connection.class.send(:alias_method, :commit_db_transaction, :real_method_commit_db_transaction) + @first.class.connection.singleton_class.send(:remove_method, :commit_db_transaction) + @first.class.connection.singleton_class.send(:alias_method, :commit_db_transaction, :real_method_commit_db_transaction) end end diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 6d66342fa5..f84088def3 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -117,6 +117,20 @@ class TransactionTest < ActiveRecord::TestCase assert !Topic.find(1).approved? end + def test_raising_exception_in_nested_transaction_restore_state_in_save + topic = Topic.new + + def topic.after_save_for_transaction + raise 'Make the transaction rollback' + end + + assert_raises(RuntimeError) do + Topic.transaction { topic.save } + end + + assert topic.new_record?, "#{topic.inspect} should be new record" + end + def test_update_should_rollback_on_failure author = Author.find(1) posts_count = author.posts.size @@ -410,16 +424,6 @@ class TransactionTest < ActiveRecord::TestCase assert !@second.destroyed?, 'not destroyed' end - if current_adapter?(:PostgreSQLAdapter) && defined?(PGconn::PQTRANS_IDLE) - def test_outside_transaction_works - assert assert_deprecated { Topic.connection.outside_transaction? } - Topic.connection.begin_db_transaction - assert assert_deprecated { !Topic.connection.outside_transaction? } - Topic.connection.rollback_db_transaction - assert assert_deprecated { Topic.connection.outside_transaction? } - end - end - def test_sqlite_add_column_in_transaction return true unless current_adapter?(:SQLite3Adapter) @@ -449,6 +453,11 @@ class TransactionTest < ActiveRecord::TestCase raise ActiveRecord::Rollback end end + + ensure + Topic.reset_column_information # reset the column information to get correct reading + Topic.connection.remove_column('topics', 'stuff') if Topic.column_names.include?('stuff') + Topic.reset_column_information # reset the column information again for other tests end def test_transactions_state_from_rollback @@ -536,22 +545,20 @@ if current_adapter?(:PostgreSQLAdapter) # This will cause transactions to overlap and fail unless they are performed on # separate database connections. def test_transaction_per_thread - assert_nothing_raised do - threads = (1..3).map do - Thread.new do - Topic.transaction do - topic = Topic.find(1) - topic.approved = !topic.approved? - topic.save! - topic.approved = !topic.approved? - topic.save! - end - Topic.connection.close + threads = 3.times.map do + Thread.new do + Topic.transaction do + topic = Topic.find(1) + topic.approved = !topic.approved? + assert topic.save! + topic.approved = !topic.approved? + assert topic.save! end + Topic.connection.close end - - threads.each { |t| t.join } end + + threads.each { |t| t.join } end # Test for dirty reads among simultaneous transactions. @@ -603,14 +610,5 @@ if current_adapter?(:PostgreSQLAdapter) assert_equal original_salary, Developer.find(1).salary end - - test "#transaction_joinable= is deprecated" do - Developer.transaction do - conn = Developer.connection - assert conn.current_transaction.joinable? - assert_deprecated { conn.transaction_joinable = false } - assert !conn.current_transaction.joinable? - end - end end end diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb index 7e92a2b127..602f633c45 100644 --- a/activerecord/test/cases/validations/association_validation_test.rb +++ b/activerecord/test/cases/validations/association_validation_test.rb @@ -10,29 +10,33 @@ require 'models/interest' class AssociationValidationTest < ActiveRecord::TestCase fixtures :topics, :owners - repair_validations(Topic, Reply, Owner) + repair_validations(Topic, Reply) def test_validates_size_of_association - assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 } - o = Owner.new('name' => 'nopets') - assert !o.save - assert o.errors[:pets].any? - o.pets.build('name' => 'apet') - assert o.valid? + repair_validations Owner do + assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 } + o = Owner.new('name' => 'nopets') + assert !o.save + assert o.errors[:pets].any? + o.pets.build('name' => 'apet') + assert o.valid? + end end def test_validates_size_of_association_using_within - assert_nothing_raised { Owner.validates_size_of :pets, :within => 1..2 } - o = Owner.new('name' => 'nopets') - assert !o.save - assert o.errors[:pets].any? - - o.pets.build('name' => 'apet') - assert o.valid? - - 2.times { o.pets.build('name' => 'apet') } - assert !o.save - assert o.errors[:pets].any? + repair_validations Owner do + assert_nothing_raised { Owner.validates_size_of :pets, :within => 1..2 } + o = Owner.new('name' => 'nopets') + assert !o.save + assert o.errors[:pets].any? + + o.pets.build('name' => 'apet') + assert o.valid? + + 2.times { o.pets.build('name' => 'apet') } + assert !o.save + assert o.errors[:pets].any? + end end def test_validates_associated_many @@ -91,12 +95,14 @@ class AssociationValidationTest < ActiveRecord::TestCase end def test_validates_size_of_association_utf8 - assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 } - o = Owner.new('name' => 'あいうえおかきくけこ') - assert !o.save - assert o.errors[:pets].any? - o.pets.build('name' => 'あいうえおかきくけこ') - assert o.valid? + repair_validations Owner do + assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 } + o = Owner.new('name' => 'あいうえおかきくけこ') + assert !o.save + assert o.errors[:pets].any? + o.pets.build('name' => 'あいうえおかきくけこ') + assert o.valid? + end end def test_validates_presence_of_belongs_to_association__parent_is_new_record diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index a96899ae10..7dad8041f3 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -8,12 +8,14 @@ class Author < ActiveRecord::Base has_many :posts_sorted_by_id_limited, -> { order('posts.id').limit(1) }, :class_name => "Post" 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_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 + has_many :comments, through: :posts do + def ratings + Rating.joins(:comment).merge(self) + end + end has_many :comments_containing_the_letter_e, :through => :posts, :source => :comments has_many :comments_with_order_and_conditions, -> { order('comments.body').where("comments.body like 'Thank%'") }, :through => :posts, :source => :comments has_many :comments_with_include, -> { includes(:post) }, :through => :posts, :source => :comments @@ -28,7 +30,6 @@ class Author < ActiveRecord::Base has_many :welcome_posts, -> { where(:title => 'Welcome to the weblog') }, :class_name => 'Post' has_many :comments_desc, -> { order('comments.id DESC') }, :through => :posts, :source => :comments - has_many :limited_comments, -> { limit(1) }, :through => :posts, :source => :comments has_many :funky_comments, :through => :posts, :source => :comments has_many :ordered_uniq_comments, -> { distinct.order('comments.id') }, :through => :posts, :source => :comments has_many :ordered_uniq_comments_desc, -> { distinct.order('comments.id DESC') }, :through => :posts, :source => :comments @@ -85,7 +86,7 @@ class Author < ActiveRecord::Base has_many :author_favorites has_many :favorite_authors, -> { order('name') }, :through => :author_favorites - has_many :taggings, :through => :posts + has_many :taggings, :through => :posts, :source => :taggings has_many :taggings_2, :through => :posts, :source => :tagging has_many :tags, :through => :posts has_many :post_categories, :through => :posts, :source => :categories diff --git a/activerecord/test/models/auto_id.rb b/activerecord/test/models/auto_id.rb index d720e2be5e..82c6544bd5 100644 --- a/activerecord/test/models/auto_id.rb +++ b/activerecord/test/models/auto_id.rb @@ -1,4 +1,4 @@ class AutoId < ActiveRecord::Base - def self.table_name () "auto_id_tests" end - def self.primary_key () "auto_id" end + self.table_name = "auto_id_tests" + self.primary_key = "auto_id" end diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb index 0109ef4f83..4361188e21 100644 --- a/activerecord/test/models/bulb.rb +++ b/activerecord/test/models/bulb.rb @@ -37,3 +37,9 @@ class CustomBulb < Bulb self.frickinawesome = true if name == 'Dude' end end + +class FunkyBulb < Bulb + before_destroy do + raise "before_destroy was called" + end +end diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb index ac42f444e1..6d257dbe7e 100644 --- a/activerecord/test/models/car.rb +++ b/activerecord/test/models/car.rb @@ -1,11 +1,9 @@ class Car < ActiveRecord::Base - has_many :bulbs + has_many :funky_bulbs, class_name: 'FunkyBulb', dependent: :destroy has_many :foo_bulbs, -> { where(:name => 'foo') }, :class_name => "Bulb" - has_many :frickinawesome_bulbs, -> { where :frickinawesome => true }, :class_name => "Bulb" has_one :bulb - has_one :frickinawesome_bulb, -> { where :frickinawesome => true }, :class_name => "Bulb" has_many :tyres has_many :engines, :dependent => :destroy diff --git a/activerecord/test/models/citation.rb b/activerecord/test/models/citation.rb index 545aa8110d..3d87eb795c 100644 --- a/activerecord/test/models/citation.rb +++ b/activerecord/test/models/citation.rb @@ -1,6 +1,3 @@ class Citation < ActiveRecord::Base belongs_to :reference_of, :class_name => "Book", :foreign_key => :book2_id - - belongs_to :book1, :class_name => "Book", :foreign_key => :book1_id - belongs_to :book2, :class_name => "Book", :foreign_key => :book2_id end diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb index 7d7c205041..566e0873f1 100644 --- a/activerecord/test/models/club.rb +++ b/activerecord/test/models/club.rb @@ -1,8 +1,7 @@ class Club < ActiveRecord::Base has_one :membership - has_many :memberships, :automatic_inverse_of => false + has_many :memberships, :inverse_of => false has_many :members, :through => :memberships - has_many :current_memberships has_one :sponsor has_one :sponsored_member, :through => :sponsor, :source => :sponsorable, :source_type => "Member" belongs_to :category diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index dcda62e71d..8104c607b5 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -35,13 +35,7 @@ module Namespaced end class Firm < Company - ActiveSupport::Deprecation.silence do - has_many :clients, -> { order "id" }, :dependent => :destroy, :counter_sql => - "SELECT COUNT(*) FROM companies WHERE firm_id = 1 " + - "AND (#{QUOTED_TYPE} = 'Client' OR #{QUOTED_TYPE} = 'SpecialClient' OR #{QUOTED_TYPE} = 'VerySpecialClient' )", - :before_remove => :log_before_remove, - :after_remove => :log_after_remove - end + has_many :clients, -> { order "id" }, :dependent => :destroy, :before_remove => :log_before_remove, :after_remove => :log_after_remove has_many :unsorted_clients, :class_name => "Client" has_many :unsorted_clients_with_symbol, :class_name => :Client has_many :clients_sorted_desc, -> { order "id DESC" }, :class_name => "Client" @@ -54,21 +48,7 @@ class Firm < Company has_many :clients_with_interpolated_conditions, ->(firm) { where "rating > #{firm.rating}" }, :class_name => "Client" has_many :clients_like_ms, -> { where("name = 'Microsoft'").order("id") }, :class_name => "Client" has_many :clients_like_ms_with_hash_conditions, -> { where(:name => 'Microsoft').order("id") }, :class_name => "Client" - ActiveSupport::Deprecation.silence do - has_many :clients_using_sql, :class_name => "Client", :finder_sql => proc { "SELECT * FROM companies WHERE client_of = #{id}" } - has_many :clients_using_counter_sql, :class_name => "Client", - :finder_sql => proc { "SELECT * FROM companies WHERE client_of = #{id} " }, - :counter_sql => proc { "SELECT COUNT(*) FROM companies WHERE client_of = #{id}" } - has_many :clients_using_zero_counter_sql, :class_name => "Client", - :finder_sql => proc { "SELECT * FROM companies WHERE client_of = #{id}" }, - :counter_sql => proc { "SELECT 0 FROM companies WHERE client_of = #{id}" } - has_many :no_clients_using_counter_sql, :class_name => "Client", - :finder_sql => 'SELECT * FROM companies WHERE client_of = 1000', - :counter_sql => 'SELECT COUNT(*) FROM companies WHERE client_of = 1000' - has_many :clients_using_finder_sql, :class_name => "Client", :finder_sql => 'SELECT * FROM companies WHERE 1=1' - end has_many :plain_clients, :class_name => 'Client' - has_many :readonly_clients, -> { readonly }, :class_name => 'Client' has_many :clients_using_primary_key, :class_name => 'Client', :primary_key => 'name', :foreign_key => 'firm_name' has_many :clients_using_primary_key_with_delete_all, :class_name => 'Client', @@ -114,13 +94,6 @@ class DependentFirm < Company has_one :company, :foreign_key => 'client_of', :dependent => :nullify end -class RestrictedFirm < Company - ActiveSupport::Deprecation.silence do - has_one :account, -> { order("id") }, :foreign_key => "firm_id", :dependent => :restrict - has_many :companies, -> { order("id") }, :foreign_key => 'client_of', :dependent => :restrict - end -end - class RestrictedWithExceptionFirm < Company has_one :account, -> { order("id") }, :foreign_key => "firm_id", :dependent => :restrict_with_exception has_many :companies, -> { order("id") }, :foreign_key => 'client_of', :dependent => :restrict_with_exception @@ -141,7 +114,7 @@ class Client < Company belongs_to :firm_with_primary_key_symbols, :class_name => "Firm", :primary_key => :name, :foreign_key => :firm_name belongs_to :readonly_firm, -> { readonly }, :class_name => "Firm", :foreign_key => "firm_id" belongs_to :bob_firm, -> { where :name => "Bob" }, :class_name => "Firm", :foreign_key => "client_of" - has_many :accounts, :through => :firm + has_many :accounts, :through => :firm, :source => :accounts belongs_to :account class RaisedOnSave < RuntimeError; end @@ -193,7 +166,6 @@ class ExclusivelyDependentFirm < Company has_one :account, :foreign_key => "firm_id", :dependent => :delete has_many :dependent_sanitized_conditional_clients_of_firm, -> { order("id").where("name = 'BigShot Inc.'") }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all has_many :dependent_conditional_clients_of_firm, -> { order("id").where("name = ?", 'BigShot Inc.') }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all - has_many :dependent_hash_conditional_clients_of_firm, -> { order("id").where(:name => 'BigShot Inc.') }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all end class SpecialClient < Client diff --git a/activerecord/test/models/company_in_module.rb b/activerecord/test/models/company_in_module.rb index 461bb0de09..38b0b6aafa 100644 --- a/activerecord/test/models/company_in_module.rb +++ b/activerecord/test/models/company_in_module.rb @@ -10,10 +10,6 @@ module MyApplication has_many :clients_sorted_desc, -> { order("id DESC") }, :class_name => "Client" has_many :clients_of_firm, -> { order "id" }, :foreign_key => "client_of", :class_name => "Client" has_many :clients_like_ms, -> { where("name = 'Microsoft'").order("id") }, :class_name => "Client" - ActiveSupport::Deprecation.silence do - has_many :clients_using_sql, :class_name => "Client", :finder_sql => 'SELECT * FROM companies WHERE client_of = #{id}' - end - has_one :account, :class_name => 'MyApplication::Billing::Account', :dependent => :destroy end diff --git a/activerecord/test/models/interest.rb b/activerecord/test/models/interest.rb index f772bb1c7f..d5d9226204 100644 --- a/activerecord/test/models/interest.rb +++ b/activerecord/test/models/interest.rb @@ -1,5 +1,5 @@ class Interest < ActiveRecord::Base - belongs_to :man, :inverse_of => :interests, :automatic_inverse_of => false + belongs_to :man, :inverse_of => :interests belongs_to :polymorphic_man, :polymorphic => true, :inverse_of => :polymorphic_interests belongs_to :zine, :inverse_of => :interests end diff --git a/activerecord/test/models/man.rb b/activerecord/test/models/man.rb index 49f002aa9a..4bff92dc98 100644 --- a/activerecord/test/models/man.rb +++ b/activerecord/test/models/man.rb @@ -1,7 +1,7 @@ class Man < ActiveRecord::Base has_one :face, :inverse_of => :man has_one :polymorphic_face, :class_name => 'Face', :as => :polymorphic_man, :inverse_of => :polymorphic_man - has_many :interests, :inverse_of => :man, :automatic_inverse_of => false + has_many :interests, :inverse_of => :man has_many :polymorphic_interests, :class_name => 'Interest', :as => :polymorphic_man, :inverse_of => :polymorphic_man # These are "broken" inverse_of associations for the purposes of testing has_one :dirty_face, :class_name => 'Face', :inverse_of => :dirty_man diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb index b81304b8e0..72095f9236 100644 --- a/activerecord/test/models/member.rb +++ b/activerecord/test/models/member.rb @@ -2,14 +2,13 @@ class Member < ActiveRecord::Base has_one :current_membership has_one :selected_membership has_one :membership - has_many :fellow_members, :through => :club, :source => :members has_one :club, :through => :current_membership has_one :selected_club, :through => :selected_membership, :source => :club has_one :favourite_club, -> { where "memberships.favourite = ?", true }, :through => :membership, :source => :club has_one :hairy_club, -> { where :clubs => {:name => "Moustache and Eyebrow Fancier Club"} }, :through => :membership, :source => :club has_one :sponsor, :as => :sponsorable has_one :sponsor_club, :through => :sponsor - has_one :member_detail, :automatic_inverse_of => false + has_one :member_detail, :inverse_of => false has_one :organization, :through => :member_detail belongs_to :member_type diff --git a/activerecord/test/models/member_detail.rb b/activerecord/test/models/member_detail.rb index a256c73c7e..9d253aa126 100644 --- a/activerecord/test/models/member_detail.rb +++ b/activerecord/test/models/member_detail.rb @@ -1,5 +1,5 @@ class MemberDetail < ActiveRecord::Base - belongs_to :member, :automatic_inverse_of => false + belongs_to :member, :inverse_of => false belongs_to :organization has_one :member_type, :through => :member diff --git a/activerecord/test/models/movie.rb b/activerecord/test/models/movie.rb index 6384b4c801..c441be2bef 100644 --- a/activerecord/test/models/movie.rb +++ b/activerecord/test/models/movie.rb @@ -1,5 +1,3 @@ class Movie < ActiveRecord::Base - def self.primary_key - "movieid" - end + self.primary_key = "movieid" end diff --git a/activerecord/test/models/parrot.rb b/activerecord/test/models/parrot.rb index c4ee2bd19d..e76e83f314 100644 --- a/activerecord/test/models/parrot.rb +++ b/activerecord/test/models/parrot.rb @@ -21,3 +21,9 @@ end class DeadParrot < Parrot belongs_to :killer, :class_name => 'Pirate' end + +class FunkyParrot < Parrot + before_destroy do + raise "before_destroy was called" + end +end diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 93a7a2073c..77564ffad4 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -122,7 +122,6 @@ class Post < ActiveRecord::Base has_many :secure_readers has_many :readers_with_person, -> { includes(:person) }, :class_name => "Reader" has_many :people, :through => :readers - has_many :secure_people, :through => :secure_readers has_many :single_people, :through => :readers has_many :people_with_callbacks, :source=>:person, :through => :readers, :before_add => lambda {|owner, reader| log(:added, :before, reader.first_name) }, diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb index f893754b9f..7f42a4b1f8 100644 --- a/activerecord/test/models/project.rb +++ b/activerecord/test/models/project.rb @@ -1,25 +1,11 @@ class Project < ActiveRecord::Base has_and_belongs_to_many :developers, -> { distinct.order 'developers.name desc, developers.id desc' } has_and_belongs_to_many :readonly_developers, -> { readonly }, :class_name => "Developer" - has_and_belongs_to_many :selected_developers, -> { distinct.select "developers.*" }, :class_name => "Developer" has_and_belongs_to_many :non_unique_developers, -> { order 'developers.name desc, developers.id desc' }, :class_name => 'Developer' has_and_belongs_to_many :limited_developers, -> { limit 1 }, :class_name => "Developer" has_and_belongs_to_many :developers_named_david, -> { where("name = 'David'").distinct }, :class_name => "Developer" has_and_belongs_to_many :developers_named_david_with_hash_conditions, -> { where(:name => 'David').distinct }, :class_name => "Developer" has_and_belongs_to_many :salaried_developers, -> { where "salary > 0" }, :class_name => "Developer" - - ActiveSupport::Deprecation.silence do - has_and_belongs_to_many :developers_with_finder_sql, :class_name => "Developer", :finder_sql => proc { "SELECT t.*, j.* FROM developers_projects j, developers t WHERE t.id = j.developer_id AND j.project_id = #{id} ORDER BY t.id" } - has_and_belongs_to_many :developers_with_multiline_finder_sql, :class_name => "Developer", :finder_sql => proc { - "SELECT - t.*, j.* - FROM - developers_projects j, - developers t WHERE t.id = j.developer_id AND j.project_id = #{id} ORDER BY t.id" - } - has_and_belongs_to_many :developers_by_sql, :class_name => "Developer", :delete_sql => proc { |record| "DELETE FROM developers_projects WHERE project_id = #{id} AND developer_id = #{record.id}" } - end - has_and_belongs_to_many :developers_with_callbacks, :class_name => "Developer", :before_add => Proc.new {|o, r| o.developers_log << "before_adding#{r.id || '<new>'}"}, :after_add => Proc.new {|o, r| o.developers_log << "after_adding#{r.id || '<new>'}"}, :before_remove => Proc.new {|o, r| o.developers_log << "before_removing#{r.id}"}, diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index 17035bf338..40c8e97fc2 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -34,7 +34,6 @@ class Topic < ActiveRecord::Base has_many :replies, :dependent => :destroy, :foreign_key => "parent_id" has_many :approved_replies, -> { approved }, class_name: 'Reply', foreign_key: "parent_id", counter_cache: 'replies_count' - has_many :replies_with_primary_key, :class_name => "Reply", :dependent => :destroy, :primary_key => "title", :foreign_key => "parent_title" has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id" has_many :silly_unique_replies, :dependent => :destroy, :foreign_key => "parent_id" diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index ae3fd811b3..961863207e 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,127 @@ +* Add `DateTime#to_s(:iso8601)` and `Date#to_s(:iso8601)` for consistency. + + *Andrew White* + +* Add `Time#to_s(:iso8601)` for easy conversion of times to the iso8601 format for easy Javascript date parsing. + + *DHH* + +* Improve `ActiveSupport::Cache::MemoryStore` cache size calculation. + The memory used by a key/entry pair is calculated via `#cached_size`: + + def cached_size(key, entry) + key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD + end + + The value of `PER_ENTRY_OVERHEAD` is 240 bytes based on an [empirical + estimation](https://gist.github.com/ssimeonov/6047200) for 64-bit MRI on + 1.9.3 and 2.0. GH#11512 + + *Simeon Simeonov* + +* Only raise `Module::DelegationError` if it's the source of the exception. + + Fixes #10559 + + *Andrew White* + +* Make `Time.at_with_coercion` retain the second fraction and return local time. + + Fixes #11350 + + *Neer Friedman*, *Andrew White* + +* Make `HashWithIndifferentAccess#select` always return the hash, even when + `Hash#select!` returns `nil`, to allow further chaining. + + *Marc Schütz* + +* Remove deprecated `String#encoding_aware?` core extensions (`core_ext/string/encoding`). + + *Arun Agrawal* + +* Remove deprecated `Module#local_constant_names` in favor of `Module#local_constants`. + + *Arun Agrawal* + +* Remove deprecated `DateTime.local_offset` in favor of `DateTime.civil_from_fromat`. + + *Arun Agrawal* + +* Remove deprecated `Logger` core extensions (`core_ext/logger.rb`). + + *Carlos Antonio da Silva* + +* Remove deprecated `Time#time_with_datetime_fallback`, `Time#utc_time` + and `Time#local_time` in favor of `Time#utc` and `Time#local`. + + *Vipul A M* + +* Remove deprecated `Hash#diff` with no replacement. + + If you're using it to compare hashes for the purpose of testing, please use + MiniTest's `assert_equal` instead. + + *Carlos Antonio da Silva* + +* Remove deprecated `Date#to_time_in_current_zone` in favor of `Date#in_time_zone`. + + *Vipul A M* + +* Remove deprecated `Proc#bind` with no replacement. + + *Carlos Antonio da Silva* + +* Remove deprecated `Array#uniq_by` and `Array#uniq_by!`, use native + `Array#uniq` and `Array#uniq!` instead. + + *Carlos Antonio da Silva* + +* Remove deprecated `ActiveSupport::BasicObject`, use `ActiveSupport::ProxyObject` instead. + + *Carlos Antonio da Silva* + +* Remove deprecated `BufferedLogger`. + + *Yves Senn* + +* Remove deprecated `assert_present` and `assert_blank` methods. + + *Yves Senn* + +* Fix return value from `BacktraceCleaner#noise` when the cleaner is configured + with multiple silencers. + + Fixes #11030 + + *Mark J. Titorenko* + +* `HashWithIndifferentAccess#select` now returns a `HashWithIndifferentAccess` + instance instead of a `Hash` instance. + + Fixes #10723 + + *Albert Llop* + +* Add `DateTime#usec` and `DateTime#nsec` so that `ActiveSupport::TimeWithZone` keeps + sub-second resolution when wrapping a `DateTime` value. + + Fixes #10855 + + *Andrew White* + +* Fix `ActiveSupport::Dependencies::Loadable#load_dependency` calling + `#blame_file!` on Exceptions that do not have the Blamable mixin + + *Andrew Kreiling* + +* Override `Time.at` to support the passing of Time-like values when called with a single argument. + + *Andrew White* + * Prevent side effects to hashes inside arrays when `Hash#with_indifferent_access` is called. + Fixes #10526 *Yves Senn* diff --git a/activesupport/Rakefile b/activesupport/Rakefile index f50225a9f0..5ba153662a 100644 --- a/activesupport/Rakefile +++ b/activesupport/Rakefile @@ -12,9 +12,8 @@ end namespace :test do task :isolated do - ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) Dir.glob("test/**/*_test.rb").all? do |file| - sh(ruby, '-Ilib:test', file) + sh(Gem.ruby, '-w', '-Ilib:test', file) end or raise "Failures" end end @@ -25,7 +24,7 @@ Gem::PackageTask.new(spec) do |p| p.gem_spec = spec end -desc "Release to gemcutter" +desc "Release to rubygems" task :release => :package do require 'rake/gemcutter' Rake::Gemcutter::Tasks.new(spec).define diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index ffa6ffda4f..5e1fe9e556 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -39,7 +39,6 @@ module ActiveSupport eager_autoload do autoload :BacktraceCleaner - autoload :BasicObject autoload :ProxyObject autoload :Benchmarkable autoload :Cache diff --git a/activesupport/lib/active_support/all.rb b/activesupport/lib/active_support/all.rb index f537818300..151008bbaa 100644 --- a/activesupport/lib/active_support/all.rb +++ b/activesupport/lib/active_support/all.rb @@ -1,3 +1,4 @@ require 'active_support' +require 'active_support/deprecation' require 'active_support/time' require 'active_support/core_ext' diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index 4b41e6247d..c88ae3e661 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -13,17 +13,17 @@ module ActiveSupport # can focus on the rest. # # bc = BacktraceCleaner.new - # bc.add_filter { |line| line.gsub(Rails.root, '') } - # bc.add_silencer { |line| line =~ /mongrel|rubygems/ } - # bc.clean(exception.backtrace) # will strip the Rails.root prefix and skip any lines from mongrel or rubygems + # bc.add_filter { |line| line.gsub(Rails.root, '') } # strip the Rails.root prefix + # bc.add_silencer { |line| line =~ /mongrel|rubygems/ } # skip any lines from mongrel or rubygems + # bc.clean(exception.backtrace) # perform the cleanup # # To reconfigure an existing BacktraceCleaner (like the default one in Rails) # and show as much data as possible, you can always call # <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the # backtrace to a pristine state. If you need to reconfigure an existing # BacktraceCleaner so that it does not filter or modify the paths of any lines - # of the backtrace, you can call BacktraceCleaner#remove_filters! These two - # methods will give you a completely untouched backtrace. + # of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!<tt> + # These two methods will give you a completely untouched backtrace. # # Inspired by the Quiet Backtrace gem by Thoughtbot. class BacktraceCleaner @@ -97,11 +97,7 @@ module ActiveSupport end def noise(backtrace) - @silencers.each do |s| - backtrace = backtrace.select { |line| s.call(line) } - end - - backtrace + backtrace - silence(backtrace) end end end diff --git a/activesupport/lib/active_support/basic_object.rb b/activesupport/lib/active_support/basic_object.rb deleted file mode 100644 index 91aac6db64..0000000000 --- a/activesupport/lib/active_support/basic_object.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'active_support/deprecation' -require 'active_support/proxy_object' - -module ActiveSupport - class BasicObject < ProxyObject # :nodoc: - def self.inherited(*) - ::ActiveSupport::Deprecation.warn 'ActiveSupport::BasicObject is deprecated! Use ActiveSupport::ProxyObject instead.' - super - end - end -end diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb deleted file mode 100644 index 1cd0c2f790..0000000000 --- a/activesupport/lib/active_support/buffered_logger.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'active_support/deprecation' -require 'active_support/logger' - -module ActiveSupport - class BufferedLogger < Logger - - def initialize(*args) - self.class._deprecation_warning - super - end - - def self.inherited(*) - _deprecation_warning - super - end - - def self._deprecation_warning - ::ActiveSupport::Deprecation.warn 'ActiveSupport::BufferedLogger is deprecated! Use ActiveSupport::Logger instead.' - end - end -end diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index b1ab5570a8..308a1b2cd9 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -228,7 +228,7 @@ module ActiveSupport # # Setting <tt>:race_condition_ttl</tt> is very useful in situations where # a cache entry is used very frequently and is under heavy load. If a - # cache expires and due to heavy load seven different processes will try + # cache expires and due to heavy load several different processes will try # to read data natively and then they all will try to write to cache. To # avoid that case the first process to find an expired cache entry will # bump the cache expiration time by the value set in <tt>:race_condition_ttl</tt>. @@ -408,7 +408,7 @@ module ActiveSupport options = merged_options(options) instrument(:exist?, name) do entry = read_entry(namespaced_key(name, options), options) - entry && !entry.expired? + (entry && !entry.expired?) || false end end diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 0c55aa8a32..472f23c1c5 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -22,11 +22,15 @@ module ActiveSupport extend Strategy::LocalCache end + # Deletes all items from the cache. In this case it deletes all the entries in the specified + # file store directory except for .gitkeep. Be careful which directory is specified in your + # config file when using +FileStore+ because everything in that directory will be deleted. def clear(options = nil) root_dirs = Dir.entries(cache_path).reject {|f| (EXCLUDED_DIRS + [".gitkeep"]).include?(f)} FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)}) end + # Premptively iterates through all stored keys and removes the ones which have expired. def cleanup(options = nil) options = merged_options(options) each_key(options) do |key| @@ -35,6 +39,8 @@ module ActiveSupport end end + # Increments an already existing integer value that is stored in the cache. + # If the key is not found nothing is done. def increment(name, amount = 1, options = nil) file_name = key_file_path(namespaced_key(name, options)) lock_file(file_name) do @@ -49,6 +55,8 @@ module ActiveSupport end end + # Decrements an already existing integer value that is stored in the cache. + # If the key is not found nothing is done. def decrement(name, amount = 1, options = nil) file_name = key_file_path(namespaced_key(name, options)) lock_file(file_name) do diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb index 4d26fb7e42..b979521c99 100644 --- a/activesupport/lib/active_support/cache/memory_store.rb +++ b/activesupport/lib/active_support/cache/memory_store.rb @@ -36,6 +36,7 @@ module ActiveSupport end end + # Premptively iterates through all stored keys and removes the ones which have expired. def cleanup(options = nil) options = merged_options(options) instrument(:cleanup, :size => @data.size) do @@ -122,6 +123,14 @@ module ActiveSupport end protected + + # See https://gist.github.com/ssimeonov/6047200 + PER_ENTRY_OVERHEAD = 240 + + def cached_size(key, entry) + key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD + end + def read_entry(key, options) # :nodoc: entry = @data[key] synchronize do @@ -139,8 +148,11 @@ module ActiveSupport synchronize do old_entry = @data[key] return false if @data.key?(key) && options[:unless_exist] - @cache_size -= old_entry.size if old_entry - @cache_size += entry.size + if old_entry + @cache_size -= (old_entry.size - entry.size) + else + @cache_size += cached_size(key, entry) + end @key_access[key] = Time.now.to_f @data[key] = entry prune(@max_size * 0.75, @max_prune_time) if @cache_size > @max_size @@ -152,7 +164,7 @@ module ActiveSupport synchronize do @key_access.delete(key) entry = @data.delete(key) - @cache_size -= entry.size if entry + @cache_size -= cached_size(key, entry) if entry !!entry end end diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 85b7669353..5c738572a8 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -1,5 +1,6 @@ require 'active_support/concern' require 'active_support/descendants_tracker' +require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/kernel/reporting' require 'active_support/core_ext/kernel/singleton_class' @@ -94,6 +95,15 @@ module ActiveSupport def halted_callback_hook(filter) end + module Conditionals # :nodoc: + class Value + def initialize(&block) + @block = block + end + def call(target, value); @block.call(value); end + end + end + module Filters Environment = Struct.new(:target, :halted, :value, :run_block) @@ -403,8 +413,8 @@ module ActiveSupport # the same after this point: # # Symbols:: Already methods. - # Strings:: class_eval'ed into methods. - # Procs:: define_method'ed into methods. + # Strings:: class_eval'd into methods. + # Procs:: using define_method compiled into methods. # Objects:: # a method is created that calls the before_foo method # on the object. @@ -415,6 +425,7 @@ module ActiveSupport when String l = eval "lambda { |value| #{filter} }" lambda { |target, value| target.instance_exec(value, &l) } + when Conditionals::Value then filter when ::Proc if filter.arity > 1 return lambda { |target, _, &block| @@ -532,14 +543,12 @@ module ActiveSupport @callbacks = nil @chain.delete_if { |c| callback.duplicates?(c) } end - end module ClassMethods - def normalize_callback_params(filters, block) # :nodoc: type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before - options = filters.last.is_a?(Hash) ? filters.pop : {} + options = filters.extract_options! filters.unshift(block) if block [type, filters, options.dup] end @@ -627,16 +636,16 @@ module ActiveSupport end # Remove all set callbacks for the given event. - def reset_callbacks(symbol) - callbacks = get_callbacks symbol + def reset_callbacks(name) + callbacks = get_callbacks name ActiveSupport::DescendantsTracker.descendants(self).each do |target| - chain = target.get_callbacks(symbol).dup + chain = target.get_callbacks(name).dup callbacks.each { |c| chain.delete(c) } - target.set_callbacks symbol, chain + target.set_callbacks name, chain end - self.set_callbacks symbol, callbacks.dup.clear + self.set_callbacks name, callbacks.dup.clear end # Define sets of events in the object lifecycle that support callbacks. @@ -648,10 +657,11 @@ module ActiveSupport # # * <tt>:terminator</tt> - Determines when a before filter will halt the # callback chain, preventing following callbacks from being called and - # the event from being triggered. This is a string to be eval'ed. The - # result of the callback is available in the +result+ variable. + # the event from being triggered. This should be a lambda to be executed. + # The current object and the return result of the callback will be called + # with the lambda. # - # define_callbacks :validate, terminator: 'result == false' + # define_callbacks :validate, terminator: ->(target, result) { result == false } # # In this example, if any before validate callbacks returns +false+, # other callbacks are not executed. Defaults to +false+, meaning no value @@ -706,18 +716,18 @@ module ActiveSupport # define_callbacks :save, scope: [:name] # # would call <tt>Audit#save</tt>. - def define_callbacks(*callbacks) - config = callbacks.last.is_a?(Hash) ? callbacks.pop : {} - if config.key?(:terminator) && String === config[:terminator] + def define_callbacks(*names) + options = names.extract_options! + if options.key?(:terminator) && String === options[:terminator] ActiveSupport::Deprecation.warn "String based terminators are deprecated, please use a lambda" - value = config[:terminator] - l = class_eval "lambda { |result| #{value} }", __FILE__, __LINE__ - config[:terminator] = lambda { |target, result| target.instance_exec(result, &l) } + value = options[:terminator] + line = class_eval "lambda { |result| #{value} }", __FILE__, __LINE__ + options[:terminator] = lambda { |target, result| target.instance_exec(result, &line) } end - callbacks.each do |callback| - class_attribute "_#{callback}_callbacks" - set_callbacks callback, CallbackChain.new(callback, config) + names.each do |name| + class_attribute "_#{name}_callbacks" + set_callbacks name, CallbackChain.new(name, options) end end diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb index b6ae86b583..b796d01dfd 100644 --- a/activesupport/lib/active_support/concern.rb +++ b/activesupport/lib/active_support/concern.rb @@ -105,25 +105,25 @@ module ActiveSupport end def self.extended(base) #:nodoc: - base.instance_variable_set("@_dependencies", []) + base.instance_variable_set(:@_dependencies, []) end def append_features(base) - if base.instance_variable_defined?("@_dependencies") - base.instance_variable_get("@_dependencies") << self + if base.instance_variable_defined?(:@_dependencies) + base.instance_variable_get(:@_dependencies) << self return false else return false if base < self @_dependencies.each { |dep| base.send(:include, dep) } super - base.extend const_get("ClassMethods") if const_defined?("ClassMethods") - base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block") + base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods) + base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block) end end def included(base = nil, &block) if base.nil? - raise MultipleIncludedBlocks if instance_variable_defined?("@_included_block") + raise MultipleIncludedBlocks if instance_variable_defined?(:@_included_block) @_included_block = block else diff --git a/activesupport/lib/active_support/core_ext.rb b/activesupport/lib/active_support/core_ext.rb index 998a59c618..199aa91020 100644 --- a/activesupport/lib/active_support/core_ext.rb +++ b/activesupport/lib/active_support/core_ext.rb @@ -1,4 +1,3 @@ Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].each do |path| - next if File.basename(path, '.rb') == 'logger' require path end diff --git a/activesupport/lib/active_support/core_ext/array.rb b/activesupport/lib/active_support/core_ext/array.rb index 79ba79192a..7d0c1e4c8d 100644 --- a/activesupport/lib/active_support/core_ext/array.rb +++ b/activesupport/lib/active_support/core_ext/array.rb @@ -1,6 +1,5 @@ require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/array/access' -require 'active_support/core_ext/array/uniq_by' require 'active_support/core_ext/array/conversions' require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/array/grouping' diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index 3807ee63b1..76ffd23ed1 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -82,23 +82,8 @@ class Array end end - # Converts a collection of elements into a formatted string by calling - # <tt>to_s</tt> on all elements and joining them. Having this model: - # - # class Blog < ActiveRecord::Base - # def to_s - # title - # end - # end - # - # Blog.all.map(&:title) #=> ["First Post", "Second Post", "Third post"] - # - # <tt>to_formatted_s</tt> shows us: - # - # Blog.all.to_formatted_s # => "First PostSecond PostThird Post" - # - # Adding in the <tt>:db</tt> argument as the format yields a comma separated - # id list: + # Extends <tt>Array#to_s</tt> to convert a collection of elements into a + # comma separated id list if <tt>:db</tt> argument is given as the format. # # Blog.all.to_formatted_s(:db) # => "1,2,3" def to_formatted_s(format = :default) diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb index dbddc7a7b4..37f007c751 100644 --- a/activesupport/lib/active_support/core_ext/array/grouping.rb +++ b/activesupport/lib/active_support/core_ext/array/grouping.rb @@ -31,9 +31,7 @@ class Array if block_given? collection.each_slice(number) { |slice| yield(slice) } else - groups = [] - collection.each_slice(number) { |group| groups << group } - groups + collection.each_slice(number).to_a end end @@ -86,13 +84,27 @@ class Array # [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]] # (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]] def split(value = nil, &block) - inject([[]]) do |results, element| - if block && block.call(element) || value == element - results << [] - else - results.last << element - end + if block + inject([[]]) do |results, element| + if block.call(element) + results << [] + else + results.last << element + end + results + end + else + results, arr = [[]], self + until arr.empty? + if (idx = index(value)) + results.last.concat(arr.shift(idx)) + arr.shift + results << [] + else + results.last.concat(arr.shift(arr.size)) + end + end results end end diff --git a/activesupport/lib/active_support/core_ext/array/uniq_by.rb b/activesupport/lib/active_support/core_ext/array/uniq_by.rb deleted file mode 100644 index 23573c97de..0000000000 --- a/activesupport/lib/active_support/core_ext/array/uniq_by.rb +++ /dev/null @@ -1,19 +0,0 @@ -class Array - # *DEPRECATED*: Use <tt>Array#uniq</tt> instead. - # - # Returns a unique array based on the criteria in the block. - # - # [1, 2, 3, 4].uniq_by { |i| i.odd? } # => [1, 2] - def uniq_by(&block) - ActiveSupport::Deprecation.warn 'uniq_by is deprecated. Use Array#uniq instead' - uniq(&block) - end - - # *DEPRECATED*: Use <tt>Array#uniq!</tt> instead. - # - # Same as +uniq_by+, but modifies +self+. - def uniq_by!(&block) - ActiveSupport::Deprecation.warn 'uniq_by! is deprecated. Use Array#uniq! instead' - uniq!(&block) - end -end diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index 6fa9967a28..83038f9da5 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -70,7 +70,6 @@ class Class # To opt out of both instance methods, pass <tt>instance_accessor: false</tt>. def class_attribute(*attrs) options = attrs.extract_options! - # double assignment is used to avoid "assigned but unused variable" warning instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true) instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true) instance_predicate = options.fetch(:instance_predicate, true) diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index 0637fe4929..6bc8f12176 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -12,7 +12,8 @@ class Date day_format = ActiveSupport::Inflector.ordinalize(date.day) date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007" }, - :rfc822 => '%e %b %Y' + :rfc822 => '%e %b %Y', + :iso8601 => lambda { |date| date.iso8601 } } # Ruby 1.9 has Date#to_time which converts to localtime only. @@ -34,6 +35,7 @@ class Date # date.to_formatted_s(:long) # => "November 10, 2007" # date.to_formatted_s(:long_ordinal) # => "November 10th, 2007" # date.to_formatted_s(:rfc822) # => "10 Nov 2007" + # date.to_formatted_s(:iso8601) # => "2007-11-10" # # == Adding your own time formats to to_formatted_s # You can add your own formats to the Date::DATE_FORMATS hash. diff --git a/activesupport/lib/active_support/core_ext/date/zones.rb b/activesupport/lib/active_support/core_ext/date/zones.rb index b4548671bf..538ed00406 100644 --- a/activesupport/lib/active_support/core_ext/date/zones.rb +++ b/activesupport/lib/active_support/core_ext/date/zones.rb @@ -2,21 +2,6 @@ require 'date' require 'active_support/core_ext/time/zones' class Date - # *DEPRECATED*: Use +Date#in_time_zone+ instead. - # - # Converts Date to a TimeWithZone in the current zone if <tt>Time.zone</tt> or - # <tt>Time.zone_default</tt> is set, otherwise converts Date to a Time via - # Date#to_time. - def to_time_in_current_zone - ActiveSupport::Deprecation.warn 'Date#to_time_in_current_zone is deprecated. Use Date#in_time_zone instead', caller - - if ::Time.zone - ::Time.zone.local(year, month, day) - else - to_time - end - end - # Converts Date to a TimeWithZone in the current zone if Time.zone or Time.zone_default # is set, otherwise converts Date to a Time via Date#to_time # diff --git a/activesupport/lib/active_support/core_ext/date_time/acts_like.rb b/activesupport/lib/active_support/core_ext/date_time/acts_like.rb index c79745c5aa..8fbbe0d3e9 100644 --- a/activesupport/lib/active_support/core_ext/date_time/acts_like.rb +++ b/activesupport/lib/active_support/core_ext/date_time/acts_like.rb @@ -1,3 +1,4 @@ +require 'date' require 'active_support/core_ext/object/acts_like' class DateTime 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 b718ae10c3..c288a1f9bd 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -1,15 +1,7 @@ require 'date' -require 'active_support/deprecation' class DateTime class << self - # *DEPRECATED*: Use +DateTime.civil_from_format+ directly. - def local_offset - ActiveSupport::Deprecation.warn 'DateTime.local_offset is deprecated. Use DateTime.civil_from_format directly.' - - ::Time.local(2012).utc_offset.to_r / 86400 - end - # Returns <tt>Time.zone.now.to_datetime</tt> when <tt>Time.zone</tt> or # <tt>config.time_zone</tt> are set, otherwise returns # <tt>Time.now.to_datetime</tt>. diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb index df07917d19..6ddfb72a0d 100644 --- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb @@ -19,6 +19,7 @@ class DateTime # datetime.to_formatted_s(:long) # => "December 04, 2007 00:00" # datetime.to_formatted_s(:long_ordinal) # => "December 4th, 2007 00:00" # datetime.to_formatted_s(:rfc822) # => "Tue, 04 Dec 2007 00:00:00 +0000" + # datetime.to_formatted_s(:iso8601) # => "2007-12-04T00:00:00+00:00" # # == Adding your own datetime formats to to_formatted_s # DateTime formats are shared with Time. You can add your own to the @@ -80,6 +81,16 @@ class DateTime seconds_since_unix_epoch.to_i end + # Returns the fraction of a second as microseconds + def usec + (sec_fraction * 1_000_000).to_i + end + + # Returns the fraction of a second as nanoseconds + def nsec + (sec_fraction * 1_000_000_000).to_i + end + private def offset_in_seconds diff --git a/activesupport/lib/active_support/core_ext/hash.rb b/activesupport/lib/active_support/core_ext/hash.rb index 501483498d..686f12c6da 100644 --- a/activesupport/lib/active_support/core_ext/hash.rb +++ b/activesupport/lib/active_support/core_ext/hash.rb @@ -1,6 +1,5 @@ require 'active_support/core_ext/hash/conversions' require 'active_support/core_ext/hash/deep_merge' -require 'active_support/core_ext/hash/diff' require 'active_support/core_ext/hash/except' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/hash/keys' diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 8930376ac8..fbf2877117 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -235,7 +235,6 @@ module ActiveSupport value.map! { |i| deep_to_h(i) } value.length > 1 ? value : value.first end - end end diff --git a/activesupport/lib/active_support/core_ext/hash/diff.rb b/activesupport/lib/active_support/core_ext/hash/diff.rb deleted file mode 100644 index 4359213380..0000000000 --- a/activesupport/lib/active_support/core_ext/hash/diff.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'active_support/deprecation' - -class Hash - # Returns a hash that represents the difference between two hashes. - # - # {1 => 2}.diff(1 => 2) # => {} - # {1 => 2}.diff(1 => 3) # => {1 => 2} - # {}.diff(1 => 2) # => {1 => 2} - # {1 => 2, 3 => 4}.diff(1 => 2) # => {3 => 4} - def diff(other) - ActiveSupport::Deprecation.warn "Hash#diff is no longer used inside of Rails, and is being deprecated with no replacement. If you're using it to compare hashes for the purpose of testing, please use MiniTest's assert_equal instead." - dup. - delete_if { |k, v| other[k] == v }. - merge!(other.dup.delete_if { |k, v| has_key?(k) }) - end -end diff --git a/activesupport/lib/active_support/core_ext/logger.rb b/activesupport/lib/active_support/core_ext/logger.rb deleted file mode 100644 index 34de766331..0000000000 --- a/activesupport/lib/active_support/core_ext/logger.rb +++ /dev/null @@ -1,67 +0,0 @@ -require 'active_support/core_ext/class/attribute_accessors' -require 'active_support/deprecation' -require 'active_support/logger_silence' - -ActiveSupport::Deprecation.warn 'this file is deprecated and will be removed' - -# Adds the 'around_level' method to Logger. -class Logger #:nodoc: - def self.define_around_helper(level) - module_eval <<-end_eval, __FILE__, __LINE__ + 1 - def around_#{level}(before_message, after_message) # def around_debug(before_message, after_message, &block) - self.#{level}(before_message) # self.debug(before_message) - return_value = yield(self) # return_value = yield(self) - self.#{level}(after_message) # self.debug(after_message) - return_value # return_value - end # end - end_eval - end - [:debug, :info, :error, :fatal].each {|level| define_around_helper(level) } -end - -require 'logger' - -# Extensions to the built-in Ruby logger. -# -# If you want to use the default log formatter as defined in the Ruby core, then you -# will need to set the formatter for the logger as in: -# -# logger.formatter = Formatter.new -# -# You can then specify the datetime format, for example: -# -# logger.datetime_format = "%Y-%m-%d" -# -# Note: This logger is deprecated in favor of ActiveSupport::Logger -class Logger - include LoggerSilence - - alias :old_datetime_format= :datetime_format= - # Logging date-time format (string passed to +strftime+). Ignored if the formatter - # does not respond to datetime_format=. - def datetime_format=(format) - formatter.datetime_format = format if formatter.respond_to?(:datetime_format=) - end - - alias :old_datetime_format :datetime_format - # Get the logging datetime format. Returns nil if the formatter does not support - # datetime formatting. - def datetime_format - formatter.datetime_format if formatter.respond_to?(:datetime_format) - end - - alias :old_initialize :initialize - # Overwrite initialize to set a default formatter. - def initialize(*args) - old_initialize(*args) - self.formatter = SimpleFormatter.new - end - - # Simple formatter which only displays the message. - class SimpleFormatter < Logger::Formatter - # This method is invoked when a log event occurs - def call(severity, timestamp, progname, msg) - "#{String === msg ? msg : msg.inspect}\n" - end - end -end diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 3dde87ac2e..bca3800344 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -1,7 +1,16 @@ class Module + # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+ + # option is not used. + class DelegationError < NoMethodError; end + # Provides a +delegate+ class method to easily expose contained objects' # public methods as your own. # + # ==== Options + # * <tt>:to</tt> - Specifies the target object + # * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix + # * <tt>:allow_nil</tt> - if set to true, prevents a +NoMethodError+ to be raised + # # The macro receives one or more method names (specified as symbols or # strings) and the name of the target object via the <tt>:to</tt> option # (also a symbol or string). @@ -176,19 +185,20 @@ class Module end # end EOS else - exception = %(raise "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") + exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") module_eval(<<-EOS, file, line - 2) - def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block) - _ = #{to} # _ = client - _.#{method}(#{definition}) # _.name(*args, &block) - rescue NoMethodError # rescue NoMethodError - if _.nil? # if _.nil? - #{exception} # # add helpful message to the exception - else # else - raise # raise - end # end - end # end + def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block) + _ = #{to} # _ = client + _.#{method}(#{definition}) # _.name(*args, &block) + rescue NoMethodError => e # rescue NoMethodError => e + location = "%s:%d:in `%s'" % [__FILE__, __LINE__ - 2, '#{method_prefix}#{method}'] # location = "%s:%d:in `%s'" % [__FILE__, __LINE__ - 2, 'customer_name'] + if _.nil? && e.backtrace.first == location # if _.nil? && e.backtrace.first == location + #{exception} # # add helpful message to the exception + else # else + raise # raise + end # end + end # end EOS end end diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb index 08e5f8a5c3..f1d26ef28f 100644 --- a/activesupport/lib/active_support/core_ext/module/introspection.rb +++ b/activesupport/lib/active_support/core_ext/module/introspection.rb @@ -59,20 +59,4 @@ class Module def local_constants #:nodoc: constants(false) end - - # *DEPRECATED*: Use +local_constants+ instead. - # - # Returns the names of the constants defined locally as strings. - # - # module M - # X = 1 - # end - # M.local_constant_names # => ["X"] - # - # This method is useful for forward compatibility, since Ruby 1.8 returns - # constant names as strings, whereas 1.9 returns them as symbols. - def local_constant_names - ActiveSupport::Deprecation.warn 'Module#local_constant_names is deprecated, use Module#local_constants instead' - local_constants.map { |c| c.to_s } - end end diff --git a/activesupport/lib/active_support/core_ext/module/method_transplanting.rb b/activesupport/lib/active_support/core_ext/module/method_transplanting.rb new file mode 100644 index 0000000000..b1097cc83b --- /dev/null +++ b/activesupport/lib/active_support/core_ext/module/method_transplanting.rb @@ -0,0 +1,11 @@ +class Module + ### + # TODO: remove this after 1.9 support is dropped + def methods_transplantable? # :nodoc: + x = Module.new { def foo; end } + Module.new { define_method :bar, x.instance_method(:foo) } + true + rescue TypeError + false + end +end diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index 534bbe3c42..48190e1e66 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -47,7 +47,7 @@ class Object end # Same as #try, but will raise a NoMethodError exception if the receiving is not nil and - # does not implemented the tried method. + # does not implement the tried method. def try!(*a, &b) if a.empty? && block_given? yield self diff --git a/activesupport/lib/active_support/core_ext/proc.rb b/activesupport/lib/active_support/core_ext/proc.rb deleted file mode 100644 index 166c3855a0..0000000000 --- a/activesupport/lib/active_support/core_ext/proc.rb +++ /dev/null @@ -1,17 +0,0 @@ -require "active_support/core_ext/kernel/singleton_class" -require "active_support/deprecation" - -class Proc #:nodoc: - def bind(object) - ActiveSupport::Deprecation.warn 'Proc#bind is deprecated and will be removed in future versions' - - block, time = self, Time.now - object.class_eval do - method_name = "__bind_#{time.to_i}_#{time.usec}" - define_method(method_name, &block) - method = instance_method(method_name) - remove_method(method_name) - method - end.bind(object) - end -end diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb index 8fa8157d65..ee3b6d2b3f 100644 --- a/activesupport/lib/active_support/core_ext/string/access.rb +++ b/activesupport/lib/active_support/core_ext/string/access.rb @@ -59,7 +59,7 @@ class String # str.from(0).to(-1) #=> "hello" # str.from(1).to(-2) #=> "ell" def to(position) - self[0..position] + self[0, position + 1] end # Returns the first character. If a limit is supplied, returns a substring diff --git a/activesupport/lib/active_support/core_ext/string/encoding.rb b/activesupport/lib/active_support/core_ext/string/encoding.rb deleted file mode 100644 index a583b914db..0000000000 --- a/activesupport/lib/active_support/core_ext/string/encoding.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'active_support/deprecation' - -class String - def encoding_aware? - ActiveSupport::Deprecation.warn 'String#encoding_aware? is deprecated' - true - 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 c62bb41416..a4cacaf789 100644 --- a/activesupport/lib/active_support/core_ext/string/filters.rb +++ b/activesupport/lib/active_support/core_ext/string/filters.rb @@ -41,8 +41,8 @@ class String def truncate(truncate_at, options = {}) return dup unless length > truncate_at - options[:omission] ||= '...' - length_with_room_for_omission = truncate_at - options[:omission].length + omission = options[:omission] || '...' + length_with_room_for_omission = truncate_at - omission.length stop = \ if options[:separator] rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission @@ -50,6 +50,6 @@ class String length_with_room_for_omission end - "#{self[0...stop]}#{options[:omission]}" + "#{self[0, stop]}#{omission}" end end diff --git a/activesupport/lib/active_support/core_ext/string/zones.rb b/activesupport/lib/active_support/core_ext/string/zones.rb index e3f20eee29..510c884c18 100644 --- a/activesupport/lib/active_support/core_ext/string/zones.rb +++ b/activesupport/lib/active_support/core_ext/string/zones.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/string/conversions' require 'active_support/core_ext/time/zones' class String diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 6388506e50..6e0af0db4d 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -3,7 +3,6 @@ require 'active_support/core_ext/time/conversions' require 'active_support/time_with_zone' require 'active_support/core_ext/time/zones' require 'active_support/core_ext/date_and_time/calculations' -require 'active_support/deprecation' class Time include DateAndTime::Calculations @@ -26,45 +25,27 @@ class Time end end - # *DEPRECATED*: Use +Time#utc+ or +Time#local+ instead. - # - # Returns a new Time if requested year can be accommodated by Ruby's Time class - # (i.e., if year is within either 1970..2038 or 1902..2038, depending on system architecture); - # otherwise returns a DateTime. - def time_with_datetime_fallback(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0, usec=0) - ActiveSupport::Deprecation.warn 'time_with_datetime_fallback is deprecated. Use Time#utc or Time#local instead', caller - time = ::Time.send(utc_or_local, year, month, day, hour, min, sec, usec) - - # This check is needed because Time.utc(y) returns a time object in the 2000s for 0 <= y <= 138. - if time.year == year - time - else - ::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec) - end - rescue - ::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec) + # Returns <tt>Time.zone.now</tt> when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise just returns <tt>Time.now</tt>. + def current + ::Time.zone ? ::Time.zone.now : ::Time.now end - # *DEPRECATED*: Use +Time#utc+ instead. - # - # Wraps class method +time_with_datetime_fallback+ with +utc_or_local+ set to <tt>:utc</tt>. - def utc_time(*args) - ActiveSupport::Deprecation.warn 'utc_time is deprecated. Use Time#utc instead', caller - time_with_datetime_fallback(:utc, *args) - end + # Layers additional behavior on Time.at so that ActiveSupport::TimeWithZone and DateTime + # instances can be used when called with a single argument + def at_with_coercion(*args) + return at_without_coercion(*args) if args.size != 1 - # *DEPRECATED*: Use +Time#local+ instead. - # - # Wraps class method +time_with_datetime_fallback+ with +utc_or_local+ set to <tt>:local</tt>. - def local_time(*args) - ActiveSupport::Deprecation.warn 'local_time is deprecated. Use Time#local instead', caller - time_with_datetime_fallback(:local, *args) - end + # Time.at can be called with a time or numerical value + time_or_number = args.first - # Returns <tt>Time.zone.now</tt> when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise just returns <tt>Time.now</tt>. - def current - ::Time.zone ? ::Time.zone.now : ::Time.now + if time_or_number.is_a?(ActiveSupport::TimeWithZone) || time_or_number.is_a?(DateTime) + at_without_coercion(time_or_number.to_f).getlocal + else + at_without_coercion(time_or_number) + end end + alias_method :at_without_coercion, :at + alias_method :at, :at_with_coercion end # Seconds since midnight: Time.now.seconds_since_midnight diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb index 48654eb1cc..9fd26156c7 100644 --- a/activesupport/lib/active_support/core_ext/time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/time/conversions.rb @@ -16,7 +16,8 @@ class Time :rfc822 => lambda { |time| offset_format = time.formatted_offset(false) time.strftime("%a, %d %b %Y %H:%M:%S #{offset_format}") - } + }, + :iso8601 => lambda { |time| time.iso8601 } } # Converts to a formatted string. See DATE_FORMATS for builtin formats. @@ -34,6 +35,7 @@ class Time # time.to_formatted_s(:long) # => "January 18, 2007 06:10" # time.to_formatted_s(:long_ordinal) # => "January 18th, 2007 06:10" # time.to_formatted_s(:rfc822) # => "Thu, 18 Jan 2007 06:10:17 -0600" + # time.to_formatted_s(:iso8601) # => "2007-01-18T06:10:17-06:00" # # == Adding your own time formats to +to_formatted_s+ # You can add your own formats to the Time::DATE_FORMATS hash. diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index fff4c776a9..73559bfe0e 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -213,7 +213,7 @@ module ActiveSupport #:nodoc: yield end rescue Exception => exception # errors from loading file - exception.blame_file! file + exception.blame_file! file if exception.respond_to? :blame_file! raise end @@ -416,7 +416,7 @@ module ActiveSupport #:nodoc: def load_file(path, const_paths = loadable_constants_for_path(path)) log_call path, const_paths const_paths = [const_paths].compact unless const_paths.is_a? Array - parent_paths = const_paths.collect { |const_path| const_path[/.*(?=::)/] || :Object } + parent_paths = const_paths.collect { |const_path| const_path[/.*(?=::)/] || ::Object } result = nil newly_defined_paths = new_constants_in(*parent_paths) do @@ -634,7 +634,7 @@ module ActiveSupport #:nodoc: when String then desc.sub(/^::/, '') when Symbol then desc.to_s when Module - desc.name.presence || + desc.name || raise(ArgumentError, "Anonymous modules have no name to be referenced by") else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}" end diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb index 6c15fffc0f..0281c9222e 100644 --- a/activesupport/lib/active_support/deprecation.rb +++ b/activesupport/lib/active_support/deprecation.rb @@ -25,11 +25,11 @@ module ActiveSupport include Reporting include MethodWrapper - # The version the deprecated behavior will be removed, by default. + # The version number in which the deprecated behavior will be removed, by default. attr_accessor :deprecation_horizon - # It accepts two parameters on initialization. The first is an version of library - # and the second is an library name + # It accepts two parameters on initialization. The first is a version of library + # and the second is a library name # # ActiveSupport::Deprecation.new('2.0', 'MyLibrary') def initialize(deprecation_horizon = '4.1', gem_name = 'Rails') diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 0a81a8393d..3da99872c0 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -224,9 +224,13 @@ module ActiveSupport undef :symbolize_keys! undef :deep_symbolize_keys! def symbolize_keys; to_hash.symbolize_keys! end - def deep_symbolize_keys; to_hash.deep_symbolize_keys end + def deep_symbolize_keys; to_hash.deep_symbolize_keys! end def to_options!; self end + def select(*args, &block) + dup.tap {|hash| hash.select!(*args, &block)} + end + # Convert to a regular hash with string keys. def to_hash _new_hash= {} diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 39648727fd..d063fcb49c 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -1,6 +1,5 @@ # encoding: utf-8 -require 'active_support/inflector/inflections' require 'active_support/inflections' module ActiveSupport @@ -73,7 +72,9 @@ module ActiveSupport else string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase } end - string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }.gsub('/', '::') + string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" } + string.gsub!('/', '::') + string end # Makes an underscored, lowercase form from the expression in the string. @@ -88,8 +89,7 @@ module ActiveSupport # # 'SSLError'.underscore.camelize # => "SslError" def underscore(camel_cased_word) - word = camel_cased_word.to_s.dup - word.gsub!('::', '/') + word = camel_cased_word.to_s.gsub('::', '/') word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" } word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2') word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') @@ -185,7 +185,7 @@ module ActiveSupport # # See also +demodulize+. def deconstantize(path) - path.to_s[0...(path.rindex('::') || 0)] # implementation based on the one in facets' Module#spacename + path.to_s[0, path.rindex('::') || 0] # implementation based on the one in facets' Module#spacename end # Creates a foreign key name from a class name. @@ -219,7 +219,12 @@ module ActiveSupport # unknown. def constantize(camel_cased_word) names = camel_cased_word.split('::') - names.shift if names.empty? || names.first.empty? + + # Trigger a builtin NameError exception including the ill-formed constant in the message. + Object.const_get(camel_cased_word) if names.empty? + + # Remove the first blank element in case of '::ClassName' notation. + names.shift if names.size > 1 && names.first.empty? names.inject(Object) do |constant, name| if constant == Object @@ -314,9 +319,14 @@ module ActiveSupport private # Mount a regular expression that will match part by part of the constant. - # For instance, Foo::Bar::Baz will generate Foo(::Bar(::Baz)?)? + # + # const_regexp("Foo::Bar::Baz") # => /Foo(::Bar(::Baz)?)?/ + # const_regexp("::") # => /::/ def const_regexp(camel_cased_word) #:nodoc: parts = camel_cased_word.split("::") + + return Regexp.escape(camel_cased_word) if parts.blank? + last = parts.pop parts.reverse.inject(last) do |acc, part| diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb index f9a98686d3..75f353f62c 100644 --- a/activesupport/lib/active_support/log_subscriber/test_helper.rb +++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb @@ -1,5 +1,5 @@ require 'active_support/log_subscriber' -require 'active_support/buffered_logger' +require 'active_support/logger' require 'active_support/notifications' module ActiveSupport diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index c45358bba9..b32aa75e59 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -143,8 +143,8 @@ module ActiveSupport # # == Default Queue # - # Notifications ships with a queue implementation that consumes and publish events - # to log subscribers in a thread. You can use any queue implementation you want. + # Notifications ships with a queue implementation that consumes and publishes events + # to all log subscribers. You can use any queue implementation you want. # module Notifications class << self diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb index 414960d2b1..54cff6d394 100644 --- a/activesupport/lib/active_support/number_helper.rb +++ b/activesupport/lib/active_support/number_helper.rb @@ -244,14 +244,14 @@ module ActiveSupport # # ==== Examples # - # number_to_percentage(100) # => 100.000% - # number_to_percentage('98') # => 98.000% - # number_to_percentage(100, precision: 0) # => 100% - # number_to_percentage(1000, delimiter: '.', separator: ,') # => 1.000,000% - # number_to_percentage(302.24398923423, precision: 5) # => 302.24399% - # number_to_percentage(1000, locale: :fr) # => 1 000,000% - # number_to_percentage('98a') # => 98a% - # number_to_percentage(100, format: '%n %') # => 100 % + # number_to_percentage(100) # => 100.000% + # number_to_percentage('98') # => 98.000% + # number_to_percentage(100, precision: 0) # => 100% + # number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000% + # number_to_percentage(302.24398923423, precision: 5) # => 302.24399% + # number_to_percentage(1000, locale: :fr) # => 1 000,000% + # number_to_percentage('98a') # => 98a% + # number_to_percentage(100, format: '%n %') # => 100 % def number_to_percentage(number, options = {}) return unless number options = options.symbolize_keys diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb index 9a038dfbca..a7eba91ac5 100644 --- a/activesupport/lib/active_support/rescuable.rb +++ b/activesupport/lib/active_support/rescuable.rb @@ -1,6 +1,5 @@ require 'active_support/concern' require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/proc' require 'active_support/core_ext/string/inflections' require 'active_support/core_ext/array/extract_options' diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb index 175f7ffe5a..76a591bc3b 100644 --- a/activesupport/lib/active_support/testing/assertions.rb +++ b/activesupport/lib/active_support/testing/assertions.rb @@ -92,36 +92,6 @@ module ActiveSupport def assert_no_difference(expression, message = nil, &block) assert_difference expression, 0, message, &block end - - # Test if an expression is blank. Passes if <tt>object.blank?</tt> - # is +true+. - # - # assert_blank [] # => true - # assert_blank [[]] # => [[]] is not blank - # - # An error message can be specified. - # - # assert_blank [], 'this should be blank' - def assert_blank(object, message=nil) - ActiveSupport::Deprecation.warn('"assert_blank" is deprecated. Please use "assert object.blank?" instead') - message ||= "#{object.inspect} is not blank" - assert object.blank?, message - end - - # Test if an expression is not blank. Passes if <tt>object.present?</tt> - # is +true+. - # - # assert_present({ data: 'x' }) # => true - # assert_present({}) # => {} is blank - # - # An error message can be specified. - # - # assert_present({ data: 'x' }, 'this should not be blank') - def assert_present(object, message=nil) - ActiveSupport::Deprecation.warn('"assert_present" is deprecated. Please use "assert object.present?" instead') - message ||= "#{object.inspect} is blank" - assert object.present?, message - end end end end diff --git a/activesupport/lib/active_support/testing/declarative.rb b/activesupport/lib/active_support/testing/declarative.rb index 508e37254a..1fa73caefa 100644 --- a/activesupport/lib/active_support/testing/declarative.rb +++ b/activesupport/lib/active_support/testing/declarative.rb @@ -19,9 +19,12 @@ module ActiveSupport end unless defined?(Spec) - # test "verify something" do - # ... - # end + # Helper to define a test method using a String. Under the hood, it replaces + # spaces with underscores and defines the test method. + # + # test "verify something" do + # ... + # end def test(name, &block) test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym defined = instance_method(test_name) rescue false diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb index a65148cf1f..33f2b8dc9b 100644 --- a/activesupport/lib/active_support/testing/setup_and_teardown.rb +++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb @@ -3,6 +3,19 @@ require 'active_support/callbacks' module ActiveSupport module Testing + # Adds support for +setup+ and +teardown+ callbacks. + # These callbacks serve as a replacement to overwriting the + # <tt>#setup</tt> and <tt>#teardown</tt> methods of your TestCase. + # + # class ExampleTest < ActiveSupport::TestCase + # setup do + # # ... + # end + # + # teardown do + # # ... + # end + # end module SetupAndTeardown extend ActiveSupport::Concern @@ -12,21 +25,23 @@ module ActiveSupport end module ClassMethods + # Add a callback, which runs before <tt>TestCase#setup</tt>. def setup(*args, &block) set_callback(:setup, :before, *args, &block) end + # Add a callback, which runs after <tt>TestCase#teardown</tt>. def teardown(*args, &block) set_callback(:teardown, :after, *args, &block) end end - def before_setup + def before_setup # :nodoc: super run_callbacks :setup end - def after_teardown + def after_teardown # :nodoc: run_callbacks :teardown super end diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 4a032b0ad0..95b9b8e5ae 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -292,7 +292,7 @@ module ActiveSupport end end - %w(year mon month day mday wday yday hour min sec to_date).each do |method_name| + %w(year mon month day mday wday yday hour min sec usec nsec to_date).each do |method_name| class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{method_name} # def month time.#{method_name} # time.month @@ -300,10 +300,6 @@ module ActiveSupport EOV end - def usec - time.respond_to?(:usec) ? time.usec : 0 - end - def to_a [time.sec, time.min, time.hour, time.day, time.mon, time.year, time.wday, time.yday, dst?, zone] end diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index dd17cb64f4..939bd7bfa8 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -8,7 +8,6 @@ ensure end require 'active_support/core_ext/kernel/reporting' -require 'active_support/core_ext/string/encoding' silence_warnings do Encoding.default_internal = "UTF-8" diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index ae6eaa4b60..df570d485a 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -327,8 +327,8 @@ module CacheStoreBehavior def test_exist @cache.write('foo', 'bar') - assert @cache.exist?('foo') - assert !@cache.exist?('bar') + assert_equal true, @cache.exist?('foo') + assert_equal false, @cache.exist?('bar') end def test_nil_exist @@ -713,8 +713,8 @@ end class MemoryStoreTest < ActiveSupport::TestCase def setup - @record_size = ActiveSupport::Cache::Entry.new("aaaaaaaaaa").size - @cache = ActiveSupport::Cache.lookup_store(:memory_store, :expires_in => 60, :size => @record_size * 10) + @record_size = ActiveSupport::Cache.lookup_store(:memory_store).send(:cached_size, 1, ActiveSupport::Cache::Entry.new("aaaaaaaaaa")) + @cache = ActiveSupport::Cache.lookup_store(:memory_store, :expires_in => 60, :size => @record_size * 10 + 1) end include CacheStoreBehavior @@ -764,6 +764,30 @@ class MemoryStoreTest < ActiveSupport::TestCase assert !@cache.exist?(1), "no entry" end + def test_prune_size_on_write_based_on_key_length + @cache.write(1, "aaaaaaaaaa") && sleep(0.001) + @cache.write(2, "bbbbbbbbbb") && sleep(0.001) + @cache.write(3, "cccccccccc") && sleep(0.001) + @cache.write(4, "dddddddddd") && sleep(0.001) + @cache.write(5, "eeeeeeeeee") && sleep(0.001) + @cache.write(6, "ffffffffff") && sleep(0.001) + @cache.write(7, "gggggggggg") && sleep(0.001) + @cache.write(8, "hhhhhhhhhh") && sleep(0.001) + @cache.write(9, "iiiiiiiiii") && sleep(0.001) + long_key = '*' * 2 * @record_size + @cache.write(long_key, "llllllllll") + assert @cache.exist?(long_key) + assert @cache.exist?(9) + assert @cache.exist?(8) + assert @cache.exist?(7) + assert @cache.exist?(6) + assert !@cache.exist?(5), "no entry" + assert !@cache.exist?(4), "no entry" + assert !@cache.exist?(3), "no entry" + assert !@cache.exist?(2), "no entry" + assert !@cache.exist?(1), "no entry" + end + def test_pruning_is_capped_at_a_max_time def @cache.delete_entry (*args) sleep(0.01) diff --git a/activesupport/test/clean_backtrace_test.rb b/activesupport/test/clean_backtrace_test.rb index b14950acb3..dd67a45cf6 100644 --- a/activesupport/test/clean_backtrace_test.rb +++ b/activesupport/test/clean_backtrace_test.rb @@ -36,6 +36,27 @@ class BacktraceCleanerSilencerTest < ActiveSupport::TestCase end end +class BacktraceCleanerMultipleSilencersTest < ActiveSupport::TestCase + def setup + @bc = ActiveSupport::BacktraceCleaner.new + @bc.add_silencer { |line| line =~ /mongrel/ } + @bc.add_silencer { |line| line =~ /yolo/ } + end + + test "backtrace should not contain lines that match the silencers" do + assert_equal \ + [ "/other/class.rb" ], + @bc.clean([ "/mongrel/class.rb", "/other/class.rb", "/mongrel/stuff.rb", "/other/yolo.rb" ]) + end + + test "backtrace should only contain lines that match the silencers" do + assert_equal \ + [ "/mongrel/class.rb", "/mongrel/stuff.rb", "/other/yolo.rb" ], + @bc.clean([ "/mongrel/class.rb", "/other/class.rb", "/mongrel/stuff.rb", "/other/yolo.rb" ], + :noise) + end +end + class BacktraceCleanerFilterAndSilencerTest < ActiveSupport::TestCase def setup @bc = ActiveSupport::BacktraceCleaner.new diff --git a/activesupport/test/concern_test.rb b/activesupport/test/concern_test.rb index 8e2c298fc6..a74ee880b2 100644 --- a/activesupport/test/concern_test.rb +++ b/activesupport/test/concern_test.rb @@ -56,10 +56,6 @@ class ConcernTest < ActiveSupport::TestCase @klass.send(:include, Baz) assert_equal "baz", @klass.new.baz assert @klass.included_modules.include?(ConcernTest::Baz) - - @klass.send(:include, Baz) - assert_equal "baz", @klass.new.baz - assert @klass.included_modules.include?(ConcernTest::Baz) end def test_class_methods_are_extended @@ -68,12 +64,6 @@ class ConcernTest < ActiveSupport::TestCase assert_equal ConcernTest::Baz::ClassMethods, (class << @klass; self.included_modules; end)[0] end - def test_instance_methods_are_included - @klass.send(:include, Baz) - assert_equal "baz", @klass.new.baz - assert @klass.included_modules.include?(ConcernTest::Baz) - end - def test_included_block_is_ran @klass.send(:include, Baz) assert_equal true, @klass.included_ran diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb index 9b62295c96..bbeb710a0c 100644 --- a/activesupport/test/constantize_test_cases.rb +++ b/activesupport/test/constantize_test_cases.rb @@ -34,8 +34,6 @@ module ConstantizeTestCases assert_equal Case::Dice, yield("Object::Case::Dice") assert_equal ConstantizeTestCases, yield("ConstantizeTestCases") assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases") - assert_equal Object, yield("") - assert_equal Object, yield("::") assert_raises(NameError) { yield("UnknownClass") } assert_raises(NameError) { yield("UnknownClass::Ace") } assert_raises(NameError) { yield("UnknownClass::Ace::Base") } @@ -45,6 +43,8 @@ module ConstantizeTestCases assert_raises(NameError) { yield("Ace::Base::ConstantizeTestCases") } assert_raises(NameError) { yield("Ace::Gas::Base") } assert_raises(NameError) { yield("Ace::Gas::ConstantizeTestCases") } + assert_raises(NameError) { yield("") } + assert_raises(NameError) { yield("::") } end def run_safe_constantize_tests_on @@ -58,8 +58,8 @@ module ConstantizeTestCases assert_equal Case::Dice, yield("Object::Case::Dice") assert_equal ConstantizeTestCases, yield("ConstantizeTestCases") assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases") - assert_equal Object, yield("") - assert_equal Object, yield("::") + assert_nil yield("") + assert_nil yield("::") assert_nil yield("UnknownClass") assert_nil yield("UnknownClass::Ace") assert_nil yield("UnknownClass::Ace::Base") diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb index 384064d81f..6cd0eb39b7 100644 --- a/activesupport/test/core_ext/array_ext_test.rb +++ b/activesupport/test/core_ext/array_ext_test.rb @@ -359,36 +359,6 @@ class ArrayExtractOptionsTests < ActiveSupport::TestCase end end -class ArrayUniqByTests < ActiveSupport::TestCase - def test_uniq_by - ActiveSupport::Deprecation.silence do - assert_equal [1,2], [1,2,3,4].uniq_by { |i| i.odd? } - assert_equal [1,2], [1,2,3,4].uniq_by(&:even?) - assert_equal((-5..0).to_a, (-5..5).to_a.uniq_by{ |i| i**2 }) - end - end - - def test_uniq_by! - a = [1,2,3,4] - ActiveSupport::Deprecation.silence do - a.uniq_by! { |i| i.odd? } - end - assert_equal [1,2], a - - a = [1,2,3,4] - ActiveSupport::Deprecation.silence do - a.uniq_by! { |i| i.even? } - end - assert_equal [1,2], a - - a = (-5..5).to_a - ActiveSupport::Deprecation.silence do - a.uniq_by! { |i| i**2 } - end - assert_equal((-5..0).to_a, a) - end -end - class ArrayWrapperTests < ActiveSupport::TestCase class FakeCollection def to_ary diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index ce8b6d5ad0..eab3aa7a6e 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -25,6 +25,7 @@ class DateExtCalculationsTest < ActiveSupport::TestCase assert_equal "February 21st, 2005", date.to_s(:long_ordinal) assert_equal "2005-02-21", date.to_s(:db) assert_equal "21 Feb 2005", date.to_s(:rfc822) + assert_equal "2005-02-21", date.to_s(:iso8601) end def test_readable_inspect @@ -367,10 +368,3 @@ class DateExtBehaviorTest < ActiveSupport::TestCase end end -class DateExtConversionsTest < ActiveSupport::TestCase - def test_to_time_in_current_zone_is_deprecated - assert_deprecated(/to_time_in_current_zone/) do - Date.new(2012,6,7).to_time_in_current_zone - end - end -end diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index c9ba91f706..0a40aeb96c 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -18,6 +18,12 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal "Mon, 21 Feb 2005 14:30:00 +0000", datetime.to_s(:rfc822) assert_equal "February 21st, 2005 14:30", datetime.to_s(:long_ordinal) assert_match(/^2005-02-21T14:30:00(Z|\+00:00)$/, datetime.to_s) + + with_env_tz "US/Central" do + assert_equal "2009-02-05T14:30:05-06:00", DateTime.civil(2009, 2, 5, 14, 30, 5, Rational(-21600, 86400)).to_s(:iso8601) + assert_equal "2008-06-09T04:05:01-05:00", DateTime.civil(2008, 6, 9, 4, 5, 1, Rational(-18000, 86400)).to_s(:iso8601) + assert_equal "2009-02-05T14:30:05+00:00", DateTime.civil(2009, 2, 5, 14, 30, 5).to_s(:iso8601) + end end def test_readable_inspect @@ -331,6 +337,16 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal 946684800, DateTime.civil(1999,12,31,19,0,0,Rational(-5,24)).to_i end + def test_usec + assert_equal 0, DateTime.civil(2000).usec + assert_equal 500000, DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)).usec + end + + def test_nsec + assert_equal 0, DateTime.civil(2000).nsec + assert_equal 500000000, DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)).nsec + end + protected def with_env_tz(new_tz = 'US/Eastern') old_tz, ENV['TZ'] = ENV['TZ'], new_tz diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 39bd0a2dd4..2d0c56bef5 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -480,6 +480,42 @@ class HashExtTest < ActiveSupport::TestCase assert_equal hash.delete('a'), nil end + def test_indifferent_select + hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select {|k,v| v == 1} + + assert_equal({ 'a' => 1 }, hash) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash + end + + def test_indifferent_select_returns_a_hash_when_unchanged + hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select {|k,v| true} + + assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash + end + + def test_indifferent_select_bang + indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings) + indifferent_strings.select! {|k,v| v == 1} + + assert_equal({ 'a' => 1 }, indifferent_strings) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings + end + + def test_indifferent_reject + hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject {|k,v| v != 1} + + assert_equal({ 'a' => 1 }, hash) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash + end + + def test_indifferent_reject_bang + indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings) + indifferent_strings.reject! {|k,v| v != 1} + + assert_equal({ 'a' => 1 }, indifferent_strings) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings + end + def test_indifferent_to_hash # Should convert to a Hash with String keys. assert_equal @strings, @mixed.with_indifferent_access.to_hash @@ -671,12 +707,6 @@ class HashExtTest < ActiveSupport::TestCase assert_equal expected, merged end - def test_diff - assert_deprecated do - assert_equal({ :a => 2 }, { :a => 2, :b => 5 }.diff({ :a => 1, :b => 5 })) - end - end - def test_slice original = { :a => 'x', :b => 'y', :c => 10 } expected = { :a => 'x', :b => 'y' } diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb index 8872611fb1..283b13ff8b 100644 --- a/activesupport/test/core_ext/module_test.rb +++ b/activesupport/test/core_ext/module_test.rb @@ -66,6 +66,23 @@ Tester = Struct.new(:client) do delegate :name, :to => :client, :prefix => false end +Product = Struct.new(:name) do + delegate :name, :to => :manufacturer, :prefix => true + delegate :name, :to => :type, :prefix => true + + def manufacturer + @manufacturer ||= begin + nil.unknown_method + end + end + + def type + @type ||= begin + nil.type_name + end + end +end + class ParameterSet delegate :[], :[]=, :to => :@params @@ -207,7 +224,7 @@ class ModuleTest < ActiveSupport::TestCase def test_delegation_without_allow_nil_and_nil_value david = Someone.new("David") - assert_raise(RuntimeError) { david.street } + assert_raise(Module::DelegationError) { david.street } end def test_delegation_to_method_that_exists_on_nil @@ -264,6 +281,16 @@ class ModuleTest < ActiveSupport::TestCase assert_equal [3], se.ints end + def test_delegation_doesnt_mask_nested_no_method_error_on_nil_receiver + product = Product.new('Widget') + + # Nested NoMethodError is a different name from the delegation + assert_raise(NoMethodError) { product.manufacturer_name } + + # Nested NoMethodError is the same name as the delegation + assert_raise(NoMethodError) { product.type_name } + end + def test_parent assert_equal Yz::Zy, Yz::Zy::Cd.parent assert_equal Yz, Yz::Zy.parent @@ -278,12 +305,6 @@ class ModuleTest < ActiveSupport::TestCase def test_local_constants assert_equal %w(Constant1 Constant3), Ab.local_constants.sort.map(&:to_s) end - - def test_local_constant_names - ActiveSupport::Deprecation.silence do - assert_equal %w(Constant1 Constant3), Ab.local_constant_names.sort.map(&:to_s) - end - end end module BarMethodAliaser diff --git a/activesupport/test/core_ext/proc_test.rb b/activesupport/test/core_ext/proc_test.rb deleted file mode 100644 index c4d5592196..0000000000 --- a/activesupport/test/core_ext/proc_test.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/proc' - -class ProcTests < ActiveSupport::TestCase - def test_bind_returns_method_with_changed_self - assert_deprecated do - block = Proc.new { self } - assert_equal self, block.call - bound_block = block.bind("hello") - assert_not_equal block, bound_block - assert_equal "hello", bound_block.call - end - end -end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 8f0ebc13ea..12126fb9ef 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -651,12 +651,6 @@ class OutputSafetyTest < ActiveSupport::TestCase assert_equal 'foo'.to_yaml, 'foo'.html_safe.to_yaml(:foo => 1) end - test 'knows whether it is encoding aware' do - assert_deprecated do - assert 'ruby'.encoding_aware? - end - end - test "call to_param returns a normal string" do string = @string.html_safe assert string.html_safe? diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 66631d1f5f..41a1df084e 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -521,6 +521,9 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase with_env_tz "US/Central" do assert_equal "Thu, 05 Feb 2009 14:30:05 -0600", Time.local(2009, 2, 5, 14, 30, 5).to_s(:rfc822) assert_equal "Mon, 09 Jun 2008 04:05:01 -0500", Time.local(2008, 6, 9, 4, 5, 1).to_s(:rfc822) + assert_equal "2009-02-05T14:30:05-06:00", Time.local(2009, 2, 5, 14, 30, 5).to_s(:iso8601) + assert_equal "2008-06-09T04:05:01-05:00", Time.local(2008, 6, 9, 4, 5, 1).to_s(:iso8601) + assert_equal "2009-02-05T14:30:05Z", Time.utc(2009, 2, 5, 14, 30, 5).to_s(:iso8601) end end @@ -590,58 +593,6 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal 29, Time.days_in_month(2) end - def test_time_with_datetime_fallback - ActiveSupport::Deprecation.silence do - assert_equal Time.time_with_datetime_fallback(:utc, 2005, 2, 21, 17, 44, 30), Time.utc(2005, 2, 21, 17, 44, 30) - assert_equal Time.time_with_datetime_fallback(:local, 2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30) - assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, 0) - assert_equal Time.time_with_datetime_fallback(:local, 2039, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 2039, 2, 21, 17, 44, 30) - assert_equal Time.time_with_datetime_fallback(:utc, 1900, 2, 21, 17, 44, 30), DateTime.civil(1900, 2, 21, 17, 44, 30, 0) - assert_equal Time.time_with_datetime_fallback(:utc, 2005), Time.utc(2005) - assert_equal Time.time_with_datetime_fallback(:utc, 2039), DateTime.civil(2039, 1, 1, 0, 0, 0, 0) - assert_equal Time.time_with_datetime_fallback(:utc, 2005, 2, 21, 17, 44, 30, 1), Time.utc(2005, 2, 21, 17, 44, 30, 1) #with usec - # This won't overflow on 64bit linux - unless time_is_64bits? - assert_equal Time.time_with_datetime_fallback(:local, 1900, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 1900, 2, 21, 17, 44, 30) - assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1), - DateTime.civil(2039, 2, 21, 17, 44, 30, 0, 0) - assert_equal ::Date::ITALY, Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1).start # use Ruby's default start value - end - silence_warnings do - 0.upto(138) do |year| - [:utc, :local].each do |format| - assert_equal year, Time.time_with_datetime_fallback(format, year).year - end - end - end - end - end - - def test_utc_time - ActiveSupport::Deprecation.silence do - assert_equal Time.utc_time(2005, 2, 21, 17, 44, 30), Time.utc(2005, 2, 21, 17, 44, 30) - assert_equal Time.utc_time(2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, 0) - assert_equal Time.utc_time(1901, 2, 21, 17, 44, 30), DateTime.civil(1901, 2, 21, 17, 44, 30, 0) - end - end - - def test_local_time - ActiveSupport::Deprecation.silence do - assert_equal Time.local_time(2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30) - assert_equal Time.local_time(2039, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 2039, 2, 21, 17, 44, 30) - - unless time_is_64bits? - assert_equal Time.local_time(1901, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 1901, 2, 21, 17, 44, 30) - end - end - end - - def test_time_with_datetime_fallback_deprecations - assert_deprecated(/time_with_datetime_fallback/) { Time.time_with_datetime_fallback(:utc, 2012, 6, 7) } - assert_deprecated(/utc_time/) { Time.utc_time(2012, 6, 7) } - assert_deprecated(/local_time/) { Time.local_time(2012, 6, 7) } - end - def test_last_month_on_31st assert_equal Time.local(2004, 2, 29), Time.local(2004, 3, 31).last_month end @@ -753,6 +704,82 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal(-1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone['UTC'] )) end + def test_at_with_datetime + assert_equal Time.utc(2000, 1, 1, 0, 0, 0), Time.at(DateTime.civil(2000, 1, 1, 0, 0, 0)) + + # Only test this if the underlying Time.at raises a TypeError + begin + Time.at_without_coercion(Time.now, 0) + rescue TypeError + assert_raise(TypeError) { assert_equal(Time.utc(2000, 1, 1, 0, 0, 0), Time.at(DateTime.civil(2000, 1, 1, 0, 0, 0), 0)) } + end + end + + def test_at_with_datetime_returns_local_time + with_env_tz 'US/Eastern' do + dt = DateTime.civil(2000, 1, 1, 0, 0, 0, '+0') + assert_equal Time.local(1999, 12, 31, 19, 0, 0), Time.at(dt) + assert_equal 'EST', Time.at(dt).zone + assert_equal(-18000, Time.at(dt).utc_offset) + + # Daylight savings + dt = DateTime.civil(2000, 7, 1, 1, 0, 0, '+1') + assert_equal Time.local(2000, 6, 30, 20, 0, 0), Time.at(dt) + assert_equal 'EDT', Time.at(dt).zone + assert_equal(-14400, Time.at(dt).utc_offset) + end + end + + def test_at_with_time_with_zone + assert_equal Time.utc(2000, 1, 1, 0, 0, 0), Time.at(ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC'])) + + # Only test this if the underlying Time.at raises a TypeError + begin + Time.at_without_coercion(Time.now, 0) + rescue TypeError + assert_raise(TypeError) { assert_equal(Time.utc(2000, 1, 1, 0, 0, 0), Time.at(ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC']), 0)) } + end + end + + def test_at_with_time_with_zone_returns_local_time + with_env_tz 'US/Eastern' do + twz = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['London']) + assert_equal Time.local(1999, 12, 31, 19, 0, 0), Time.at(twz) + assert_equal 'EST', Time.at(twz).zone + assert_equal(-18000, Time.at(twz).utc_offset) + + # Daylight savings + twz = ActiveSupport::TimeWithZone.new(Time.utc(2000, 7, 1, 0, 0, 0), ActiveSupport::TimeZone['London']) + assert_equal Time.local(2000, 6, 30, 20, 0, 0), Time.at(twz) + assert_equal 'EDT', Time.at(twz).zone + assert_equal(-14400, Time.at(twz).utc_offset) + end + end + + def test_at_with_time_microsecond_precision + assert_equal Time.at(Time.utc(2000, 1, 1, 0, 0, 0, 111)).to_f, Time.utc(2000, 1, 1, 0, 0, 0, 111).to_f + end + + def test_at_with_utc_time + with_env_tz 'US/Eastern' do + assert_equal Time.utc(2000), Time.at(Time.utc(2000)) + assert_equal 'UTC', Time.at(Time.utc(2000)).zone + assert_equal(0, Time.at(Time.utc(2000)).utc_offset) + end + end + + def test_at_with_local_time + with_env_tz 'US/Eastern' do + assert_equal Time.local(2000), Time.at(Time.local(2000)) + assert_equal 'EST', Time.at(Time.local(2000)).zone + assert_equal(-18000, Time.at(Time.local(2000)).utc_offset) + + assert_equal Time.local(2000, 7, 1), Time.at(Time.local(2000, 7, 1)) + assert_equal 'EDT', Time.at(Time.local(2000, 7, 1)).zone + assert_equal(-14400, Time.at(Time.local(2000, 7, 1)).utc_offset) + end + end + def test_eql? assert_equal true, Time.utc(2000).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']) ) assert_equal true, Time.utc(2000).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]) ) @@ -822,9 +849,6 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') end - def time_is_64bits? - Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1).is_a?(Time) - end end class TimeExtMarshalingTest < ActiveSupport::TestCase diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 3ce3297874..fab3d8ac38 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -80,6 +80,11 @@ class TimeWithZoneTest < ActiveSupport::TestCase ActiveSupport.use_standard_json_time_format = old end + def test_to_json_when_wrapping_a_date_time + twz = ActiveSupport::TimeWithZone.new(DateTime.civil(2000), @time_zone) + assert_equal '"1999-12-31T19:00:00.000-05:00"', ActiveSupport::JSON.encode(twz) + end + def test_nsec local = Time.local(2011,6,7,23,59,59,Rational(999999999, 1000)) with_zone = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], local) @@ -445,6 +450,16 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal 0, twz.usec end + def test_usec_returns_sec_fraction_when_datetime_is_wrapped + twz = ActiveSupport::TimeWithZone.new(DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)), @time_zone) + assert_equal 500000, twz.usec + end + + def test_nsec_returns_sec_fraction_when_datetime_is_wrapped + twz = ActiveSupport::TimeWithZone.new(DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)), @time_zone) + assert_equal 500000000, twz.nsec + end + def test_utc_to_local_conversion_saves_period_in_instance_variable assert_nil @twz.instance_variable_get('@period') @twz.time diff --git a/activesupport/test/dependencies/raises_exception_without_blame_file.rb b/activesupport/test/dependencies/raises_exception_without_blame_file.rb new file mode 100644 index 0000000000..4b2da6ff30 --- /dev/null +++ b/activesupport/test/dependencies/raises_exception_without_blame_file.rb @@ -0,0 +1,5 @@ +exception = Exception.new('I am not blamable!') +class << exception + undef_method(:blame_file!) +end +raise exception diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 4b1426bb2e..68b6cc6e8c 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -76,6 +76,14 @@ class DependenciesTest < ActiveSupport::TestCase end end + def test_dependency_which_raises_doesnt_blindly_call_blame_file! + with_loading do + filename = 'dependencies/raises_exception_without_blame_file' + + assert_raises(Exception) { require_dependency filename } + end + end + def test_warnings_should_be_enabled_on_first_load with_loading 'dependencies' do old_warnings, ActiveSupport::Dependencies.warnings_on_first_load = ActiveSupport::Dependencies.warnings_on_first_load, true diff --git a/activesupport/test/deprecation/basic_object_test.rb b/activesupport/test/deprecation/basic_object_test.rb deleted file mode 100644 index 4b5bed9eb1..0000000000 --- a/activesupport/test/deprecation/basic_object_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'abstract_unit' -require 'active_support/deprecation' -require 'active_support/basic_object' - - -class BasicObjectTest < ActiveSupport::TestCase - test 'BasicObject warns about deprecation when inherited from' do - warn = 'ActiveSupport::BasicObject is deprecated! Use ActiveSupport::ProxyObject instead.' - ActiveSupport::Deprecation.expects(:warn).with(warn).once - Class.new(ActiveSupport::BasicObject) - end -end
\ No newline at end of file diff --git a/activesupport/test/deprecation/buffered_logger_test.rb b/activesupport/test/deprecation/buffered_logger_test.rb deleted file mode 100644 index bf11a4732c..0000000000 --- a/activesupport/test/deprecation/buffered_logger_test.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'abstract_unit' -require 'active_support/buffered_logger' - -class BufferedLoggerTest < ActiveSupport::TestCase - - def test_can_be_subclassed - warn = 'ActiveSupport::BufferedLogger is deprecated! Use ActiveSupport::Logger instead.' - - ActiveSupport::Deprecation.expects(:warn).with(warn).once - - Class.new(ActiveSupport::BufferedLogger) - end - - def test_issues_deprecation_when_instantiated - warn = 'ActiveSupport::BufferedLogger is deprecated! Use ActiveSupport::Logger instead.' - - ActiveSupport::Deprecation.expects(:warn).with(warn).once - - ActiveSupport::BufferedLogger.new(STDOUT) - end - -end diff --git a/activesupport/test/load_paths_test.rb b/activesupport/test/load_paths_test.rb index 979e25bdf3..ac617a9fd8 100644 --- a/activesupport/test/load_paths_test.rb +++ b/activesupport/test/load_paths_test.rb @@ -10,7 +10,7 @@ class LoadPathsTest < ActiveSupport::TestCase } load_paths_count[File.expand_path('../../lib', __FILE__)] -= 1 - filtered = load_paths_count.select { |k, v| v > 1 } - assert filtered.empty?, filtered.inspect + load_paths_count.select! { |k, v| v > 1 } + assert load_paths_count.empty?, load_paths_count.inspect end end diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb index 68f9ec6c00..0c8cc1f883 100644 --- a/activesupport/test/test_test.rb +++ b/activesupport/test/test_test.rb @@ -87,54 +87,6 @@ class AssertDifferenceTest < ActiveSupport::TestCase end end -class AssertBlankTest < ActiveSupport::TestCase - BLANK = [ EmptyTrue.new, nil, false, '', ' ', " \n\t \r ", [], {} ] - NOT_BLANK = [ EmptyFalse.new, Object.new, true, 0, 1, 'x', [nil], { nil => 0 } ] - - def test_assert_blank_true - BLANK.each { |value| - assert_deprecated { assert_blank value } - } - end - - def test_assert_blank_false - NOT_BLANK.each { |v| - assert_deprecated { - begin - assert_blank v - fail 'should not get to here' - rescue Exception => e - assert_match(/is not blank/, e.message) - end - } - } - end -end - -class AssertPresentTest < ActiveSupport::TestCase - BLANK = [ EmptyTrue.new, nil, false, '', ' ', " \n\t \r ", [], {} ] - NOT_BLANK = [ EmptyFalse.new, Object.new, true, 0, 1, 'x', [nil], { nil => 0 } ] - - def test_assert_present_true - NOT_BLANK.each { |v| - assert_deprecated { assert_present v } - } - end - - def test_assert_present_false - BLANK.each { |v| - assert_deprecated { - begin - assert_present v - fail 'should not get to here' - rescue Exception => e - assert_match(/is blank/, e.message) - end - } - } - end -end - class AlsoDoingNothingTest < ActiveSupport::TestCase end diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb index ce91c443e1..e0f85f4e7c 100644 --- a/activesupport/test/transliterate_test.rb +++ b/activesupport/test/transliterate_test.rb @@ -3,7 +3,6 @@ require 'abstract_unit' require 'active_support/inflector/transliterate' class TransliterateTest < ActiveSupport::TestCase - def test_transliterate_should_not_change_ascii_chars (0..127).each do |byte| char = [byte].pack("U") @@ -24,12 +23,13 @@ class TransliterateTest < ActiveSupport::TestCase def test_transliterate_should_work_with_custom_i18n_rules_and_uncomposed_utf8 char = [117, 776].pack("U*") # "ü" as ASCII "u" plus COMBINING DIAERESIS I18n.backend.store_translations(:de, :i18n => {:transliterate => {:rule => {"ü" => "ue"}}}) - I18n.locale = :de + default_locale, I18n.locale = I18n.locale, :de assert_equal "ue", ActiveSupport::Inflector.transliterate(char) + ensure + I18n.locale = default_locale end def test_transliterate_should_allow_a_custom_replacement_char assert_equal "a*b", ActiveSupport::Inflector.transliterate("a索b", "*") end - end diff --git a/activesupport/test/xml_mini/jdom_engine_test.rb b/activesupport/test/xml_mini/jdom_engine_test.rb index 904ef7b208..ed4de8aba2 100644 --- a/activesupport/test/xml_mini/jdom_engine_test.rb +++ b/activesupport/test/xml_mini/jdom_engine_test.rb @@ -178,7 +178,7 @@ if RUBY_PLATFORM =~ /java/ private def assert_equal_rexml(xml) parsed_xml = XmlMini.parse(xml) - hash = XmlMini.with_backend('REXML') { parsed_xml } + hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } assert_equal(hash, parsed_xml) end end diff --git a/activesupport/test/xml_mini/libxml_engine_test.rb b/activesupport/test/xml_mini/libxml_engine_test.rb index e7cb350663..a8df2e1f7b 100644 --- a/activesupport/test/xml_mini/libxml_engine_test.rb +++ b/activesupport/test/xml_mini/libxml_engine_test.rb @@ -195,7 +195,8 @@ class LibxmlEngineTest < ActiveSupport::TestCase private def assert_equal_rexml(xml) parsed_xml = XmlMini.parse(xml) - hash = XmlMini.with_backend('REXML') { parsed_xml } + xml.rewind if xml.respond_to?(:rewind) + hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } assert_equal(hash, parsed_xml) end end diff --git a/activesupport/test/xml_mini/libxmlsax_engine_test.rb b/activesupport/test/xml_mini/libxmlsax_engine_test.rb index 07485911c9..d6d90639e2 100644 --- a/activesupport/test/xml_mini/libxmlsax_engine_test.rb +++ b/activesupport/test/xml_mini/libxmlsax_engine_test.rb @@ -186,7 +186,8 @@ class LibXMLSAXEngineTest < ActiveSupport::TestCase private def assert_equal_rexml(xml) parsed_xml = XmlMini.parse(xml) - hash = XmlMini.with_backend('REXML') { parsed_xml } + xml.rewind if xml.respond_to?(:rewind) + hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } assert_equal(hash, parsed_xml) end end diff --git a/activesupport/test/xml_mini/nokogiri_engine_test.rb b/activesupport/test/xml_mini/nokogiri_engine_test.rb index 937517786e..2e962576b5 100644 --- a/activesupport/test/xml_mini/nokogiri_engine_test.rb +++ b/activesupport/test/xml_mini/nokogiri_engine_test.rb @@ -208,7 +208,8 @@ class NokogiriEngineTest < ActiveSupport::TestCase private def assert_equal_rexml(xml) parsed_xml = XmlMini.parse(xml) - hash = XmlMini.with_backend('REXML') { parsed_xml } + xml.rewind if xml.respond_to?(:rewind) + hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } assert_equal(hash, parsed_xml) end end diff --git a/activesupport/test/xml_mini/nokogirisax_engine_test.rb b/activesupport/test/xml_mini/nokogirisax_engine_test.rb index 84a5c44a87..4f078f31e0 100644 --- a/activesupport/test/xml_mini/nokogirisax_engine_test.rb +++ b/activesupport/test/xml_mini/nokogirisax_engine_test.rb @@ -209,7 +209,8 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase private def assert_equal_rexml(xml) parsed_xml = XmlMini.parse(xml) - hash = XmlMini.with_backend('REXML') { parsed_xml } + xml.rewind if xml.respond_to?(:rewind) + hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } assert_equal(hash, parsed_xml) end end diff --git a/activesupport/test/xml_mini/rexml_engine_test.rb b/activesupport/test/xml_mini/rexml_engine_test.rb index 70a3b918fd..0c1f11803c 100644 --- a/activesupport/test/xml_mini/rexml_engine_test.rb +++ b/activesupport/test/xml_mini/rexml_engine_test.rb @@ -30,7 +30,8 @@ class REXMLEngineTest < ActiveSupport::TestCase private def assert_equal_rexml(xml) parsed_xml = XmlMini.parse(xml) - hash = XmlMini.with_backend('REXML') { parsed_xml } + xml.rewind if xml.respond_to?(:rewind) + hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } assert_equal(hash, parsed_xml) end end diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb index a025279e16..d992028323 100644 --- a/activesupport/test/xml_mini_test.rb +++ b/activesupport/test/xml_mini_test.rb @@ -106,7 +106,11 @@ module XmlMiniTest module Nokogiri end setup do - @xml = ActiveSupport::XmlMini + @xml, @default_backend = ActiveSupport::XmlMini, ActiveSupport::XmlMini.backend + end + + teardown do + ActiveSupport::XmlMini.backend = @default_backend end test "#with_backend should switch backend and then switch back" do @@ -135,7 +139,11 @@ module XmlMiniTest module LibXML end setup do - @xml = ActiveSupport::XmlMini + @xml, @default_backend = ActiveSupport::XmlMini, ActiveSupport::XmlMini.backend + end + + teardown do + ActiveSupport::XmlMini.backend = @default_backend end test "#with_backend should be thread-safe" do diff --git a/ci/travis.rb b/ci/travis.rb index 9029c3f41c..7e68993332 100755 --- a/ci/travis.rb +++ b/ci/travis.rb @@ -20,7 +20,8 @@ class Build 'am' => 'actionmailer', 'amo' => 'activemodel', 'as' => 'activesupport', - 'ar' => 'activerecord' + 'ar' => 'activerecord', + 'av' => 'actionview' } attr_reader :component, :options diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index 766f7f6f56..afa695d445 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1,3 +1,5 @@ -* No changes. +* Removed repetitive th tags. Instead of them added one th tag with a colspan attribute. + + *Sıtkı Bağdat* Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/guides/CHANGELOG.md) for previous changes. diff --git a/guides/assets/images/edge_badge.png b/guides/assets/images/edge_badge.png Binary files differindex a35dc9f8ee..a3c1843d1d 100644 --- a/guides/assets/images/edge_badge.png +++ b/guides/assets/images/edge_badge.png diff --git a/guides/assets/images/feature_tile.gif b/guides/assets/images/feature_tile.gif Binary files differindex 75469361db..5268ef8373 100644 --- a/guides/assets/images/feature_tile.gif +++ b/guides/assets/images/feature_tile.gif diff --git a/guides/assets/images/footer_tile.gif b/guides/assets/images/footer_tile.gif Binary files differindex bb33fc1ff0..3fe21a8275 100644 --- a/guides/assets/images/footer_tile.gif +++ b/guides/assets/images/footer_tile.gif diff --git a/guides/assets/images/fxn.png b/guides/assets/images/fxn.png Binary files differindex 9b531ee584..733d380cba 100644 --- a/guides/assets/images/fxn.png +++ b/guides/assets/images/fxn.png diff --git a/guides/assets/images/getting_started/challenge.png b/guides/assets/images/getting_started/challenge.png Binary files differindex 30be3d7028..4a30e49e6d 100644 --- a/guides/assets/images/getting_started/challenge.png +++ b/guides/assets/images/getting_started/challenge.png 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 differindex 500dfc2c02..6c78e52173 100644 --- a/guides/assets/images/getting_started/forbidden_attributes_for_new_post.png +++ b/guides/assets/images/getting_started/forbidden_attributes_for_new_post.png diff --git a/guides/assets/images/getting_started/new_post.png b/guides/assets/images/getting_started/new_post.png Binary files differindex b573cb164c..b20b0192d4 100644 --- a/guides/assets/images/getting_started/new_post.png +++ b/guides/assets/images/getting_started/new_post.png diff --git a/guides/assets/images/getting_started/routing_error_no_controller.png b/guides/assets/images/getting_started/routing_error_no_controller.png Binary files differindex 43ccd25252..35ee4f348f 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 1b8c0ea57e..1cbddfa0f1 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 75980432b2..f03db05fb8 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 d60a30465f..8fdd4c574a 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 f4b3eff9dc..7e72feee38 100644 --- a/guides/assets/images/getting_started/unknown_action_new_for_posts.png +++ b/guides/assets/images/getting_started/unknown_action_new_for_posts.png diff --git a/guides/assets/images/header_tile.gif b/guides/assets/images/header_tile.gif Binary files differindex e2c878d492..6b1af15eab 100644 --- a/guides/assets/images/header_tile.gif +++ b/guides/assets/images/header_tile.gif diff --git a/guides/assets/images/icons/callouts/11.png b/guides/assets/images/icons/callouts/11.png Binary files differindex 9244a1ac4b..3b7b9318e7 100644 --- a/guides/assets/images/icons/callouts/11.png +++ b/guides/assets/images/icons/callouts/11.png diff --git a/guides/assets/images/icons/callouts/12.png b/guides/assets/images/icons/callouts/12.png Binary files differindex ae56459f4c..7b95925e9d 100644 --- a/guides/assets/images/icons/callouts/12.png +++ b/guides/assets/images/icons/callouts/12.png diff --git a/guides/assets/images/icons/callouts/13.png b/guides/assets/images/icons/callouts/13.png Binary files differindex 1181f9f892..4b99fe8efc 100644 --- a/guides/assets/images/icons/callouts/13.png +++ b/guides/assets/images/icons/callouts/13.png diff --git a/guides/assets/images/icons/callouts/15.png b/guides/assets/images/icons/callouts/15.png Binary files differindex 39304de94f..70e4bba615 100644 --- a/guides/assets/images/icons/callouts/15.png +++ b/guides/assets/images/icons/callouts/15.png diff --git a/guides/assets/images/icons/caution.png b/guides/assets/images/icons/caution.png Binary files differindex 031e19c776..7227b54b32 100644 --- a/guides/assets/images/icons/caution.png +++ b/guides/assets/images/icons/caution.png diff --git a/guides/assets/images/icons/example.png b/guides/assets/images/icons/example.png Binary files differindex 1b0e482059..de23c0aa87 100644 --- a/guides/assets/images/icons/example.png +++ b/guides/assets/images/icons/example.png diff --git a/guides/assets/images/jaimeiniesta.jpg b/guides/assets/images/jaimeiniesta.jpg Binary files differdeleted file mode 100644 index 445f048d92..0000000000 --- a/guides/assets/images/jaimeiniesta.jpg +++ /dev/null diff --git a/guides/assets/images/radar.png b/guides/assets/images/radar.png Binary files differindex f61e08763f..421b62b623 100644 --- a/guides/assets/images/radar.png +++ b/guides/assets/images/radar.png diff --git a/guides/assets/images/rails4_features.png b/guides/assets/images/rails4_features.png Binary files differindex a979f02207..b3bd5ef69e 100644 --- a/guides/assets/images/rails4_features.png +++ b/guides/assets/images/rails4_features.png diff --git a/guides/assets/images/rails_guides_kindle_cover.jpg b/guides/assets/images/rails_guides_kindle_cover.jpg Binary files differindex 9eb16720a9..f068bd9a04 100644 --- a/guides/assets/images/rails_guides_kindle_cover.jpg +++ b/guides/assets/images/rails_guides_kindle_cover.jpg diff --git a/guides/assets/images/vijaydev.jpg b/guides/assets/images/vijaydev.jpg Binary files differindex e21d3cabfc..fe5e4f1cb4 100644 --- a/guides/assets/images/vijaydev.jpg +++ b/guides/assets/images/vijaydev.jpg diff --git a/guides/assets/javascripts/guides.js b/guides/assets/javascripts/guides.js index 7e494fb6d8..e4d25dfb21 100644 --- a/guides/assets/javascripts/guides.js +++ b/guides/assets/javascripts/guides.js @@ -1,57 +1,53 @@ -function guideMenu(){ - if (document.getElementById('guides').style.display == "none") { - document.getElementById('guides').style.display = "block"; - } else { - document.getElementById('guides').style.display = "none"; - } -} - -$.fn.selectGuide = function(guide){ +$.fn.selectGuide = function(guide) { $("select", this).val(guide); -} +}; -guidesIndex = { - bind: function(){ +var guidesIndex = { + bind: function() { var currentGuidePath = window.location.pathname; var currentGuide = currentGuidePath.substring(currentGuidePath.lastIndexOf("/")+1); $(".guides-index-small"). on("change", "select", guidesIndex.navigate). selectGuide(currentGuide); - $(".more-info-button:visible").click(function(e){ + $(document).on("click", ".more-info-button", function(e){ e.stopPropagation(); - if($(".more-info-links").is(":visible")){ + if ($(".more-info-links").is(":visible")) { $(".more-info-links").addClass("s-hidden").unwrap(); } else { $(".more-info-links").wrap("<div class='more-info-container'></div>").removeClass("s-hidden"); } - $(document).on("click", function(e){ - var $button = $(".more-info-button"); - var element; + }); + $("#guidesMenu").on("click", function(e) { + $("#guides").toggle(); + return false; + }); + $(document).on("click", function(e){ + e.stopPropagation(); + var $button = $(".more-info-button"); + var element; - // Cross browser find the element that had the event - if (e.target) element = e.target; - else if (e.srcElement) element = e.srcElement; + // Cross browser find the element that had the event + if (e.target) element = e.target; + else if (e.srcElement) element = e.srcElement; - // Defeat the older Safari bug: - // http://www.quirksmode.org/js/events_properties.html - if (element.nodeType == 3) element = element.parentNode; + // Defeat the older Safari bug: + // http://www.quirksmode.org/js/events_properties.html + if (element.nodeType === 3) element = element.parentNode; - var $element = $(element); + var $element = $(element); - var $container = $element.parents(".more-info-container"); + var $container = $element.parents(".more-info-container"); - // We've captured a click outside the popup - if($container.length == 0){ - $container = $button.next(".more-info-container"); - $container.find(".more-info-links").addClass("s-hidden").unwrap(); - $(document).off("click"); - } - }); + // We've captured a click outside the popup + if($container.length === 0){ + $container = $button.next(".more-info-container"); + $container.find(".more-info-links").addClass("s-hidden").unwrap(); + } }); }, navigate: function(e){ var $list = $(e.target); - url = $list.val(); + var url = $list.val(); window.location = url; } -} +}; diff --git a/guides/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css index dd029e6314..898f9ff05b 100644 --- a/guides/assets/stylesheets/main.css +++ b/guides/assets/stylesheets/main.css @@ -129,6 +129,7 @@ body { font-family: Helvetica, Arial, Sans-Serif; text-decoration: none; vertical-align: middle; + cursor: pointer; } .red-button:active { border-top: none; @@ -244,7 +245,7 @@ body { #subCol { position: absolute; z-index: 0; - top: 0; + top: 21px; right: 0; background: #FFF; padding: 1em 1.5em 1em 1.25em; diff --git a/guides/assets/stylesheets/print.css b/guides/assets/stylesheets/print.css index 628da105d4..bdc8ec948d 100644 --- a/guides/assets/stylesheets/print.css +++ b/guides/assets/stylesheets/print.css @@ -36,7 +36,7 @@ hr { } h1,h2,h3,h4,h5,h6 { font-family: "Helvetica Neue", Arial, "Lucida Grande", sans-serif; } -code { font:.9em "Courier New", Monaco, Courier, monospace; } +code { font:.9em "Courier New", Monaco, Courier, monospace; display:inline} img { float:left; margin:1.5em 1.5em 1.5em 0; } a img { border:none; } diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb index 2e604bec47..a8868a1877 100644 --- a/guides/bug_report_templates/active_record_gem.rb +++ b/guides/bug_report_templates/active_record_gem.rb @@ -1,5 +1,5 @@ # Activate the gem you are reporting the issue against. -gem 'activerecord', '3.2.13' +gem 'activerecord', '4.0.0' require 'active_record' require 'minitest/autorun' require 'logger' diff --git a/guides/code/getting_started/Gemfile b/guides/code/getting_started/Gemfile index acd2ed5160..3d57012901 100644 --- a/guides/code/getting_started/Gemfile +++ b/guides/code/getting_started/Gemfile @@ -31,7 +31,7 @@ end gem 'jbuilder', '~> 1.2' # To use ActiveModel has_secure_password -# gem 'bcrypt-ruby', '~> 3.0.0' +# gem 'bcrypt-ruby', '~> 3.1.0' # Use unicorn as the app server # gem 'unicorn' diff --git a/guides/code/getting_started/Gemfile.lock b/guides/code/getting_started/Gemfile.lock index 888a6b30e2..2d5c50ef5c 100644 --- a/guides/code/getting_started/Gemfile.lock +++ b/guides/code/getting_started/Gemfile.lock @@ -1,135 +1,107 @@ -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 +GEM + remote: https://rubygems.org/ specs: - actionmailer (4.0.0.beta) - actionpack (= 4.0.0.beta) + actionmailer (4.0.0) + actionpack (= 4.0.0) mail (~> 2.5.3) - actionpack (4.0.0.beta) - activesupport (= 4.0.0.beta) + actionpack (4.0.0) + activesupport (= 4.0.0) 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) + rack (~> 1.5.2) + rack-test (~> 0.6.2) + activemodel (4.0.0) + activesupport (= 4.0.0) 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) + activerecord (4.0.0) + activemodel (= 4.0.0) + activerecord-deprecated_finders (~> 1.0.2) + activesupport (= 4.0.0) + arel (~> 4.0.0) + activerecord-deprecated_finders (1.0.3) + activesupport (4.0.0) + i18n (~> 0.6, >= 0.6.4) + minitest (~> 4.2) 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) + tzinfo (~> 0.3.37) + arel (4.0.0) + atomic (1.1.10) builder (3.1.4) + coffee-rails (4.0.0) + coffee-script (>= 2.2.0) + railties (>= 4.0.0.beta, < 5.0) coffee-script (2.2.0) coffee-script-source execjs - coffee-script-source (1.4.0) + coffee-script-source (1.6.3) erubis (2.7.0) execjs (1.4.0) multi_json (~> 1.0) - hike (1.2.1) - i18n (0.6.1) - jbuilder (1.3.0) + hike (1.2.3) + i18n (0.6.4) + jbuilder (1.4.2) activesupport (>= 3.0.0) - jquery-rails (2.2.0) + multi_json (>= 1.2.0) + jquery-rails (3.0.2) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) - json (1.7.6) - mail (2.5.3) - i18n (>= 0.4.0) + json (1.8.0) + mail (2.5.4) mime-types (~> 1.16) treetop (~> 1.4.8) - mime-types (1.19) - minitest (4.4.0) - multi_json (1.5.0) + mime-types (1.23) + minitest (4.7.5) + multi_json (1.7.7) polyglot (0.3.3) - rack (1.4.4) + rack (1.5.2) rack-test (0.6.2) rack (>= 1.0) - rake (10.0.3) - rdoc (3.12) + rails (4.0.0) + actionmailer (= 4.0.0) + actionpack (= 4.0.0) + activerecord (= 4.0.0) + activesupport (= 4.0.0) + bundler (>= 1.3.0, < 2.0) + railties (= 4.0.0) + sprockets-rails (~> 2.0.0) + railties (4.0.0) + actionpack (= 4.0.0) + activesupport (= 4.0.0) + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rake (10.1.0) + rdoc (3.12.2) json (~> 1.4) - sass (3.2.5) - sprockets (2.8.2) + sass (3.2.9) + sass-rails (4.0.0) + railties (>= 4.0.0.beta, < 5.0) + sass (>= 3.1.10) + sprockets-rails (~> 2.0.0) + sdoc (0.3.20) + json (>= 1.1.3) + rdoc (~> 3.10) + sprockets (2.10.0) hike (~> 1.2) multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) + sprockets-rails (2.0.0) + actionpack (>= 3.0) + activesupport (>= 3.0) + sprockets (~> 2.8) sqlite3 (1.3.7) - thor (0.16.0) + thor (0.18.1) thread_safe (0.1.0) atomic - tilt (1.3.3) - treetop (1.4.12) + tilt (1.4.1) + treetop (1.4.14) polyglot polyglot (>= 0.3.1) - turbolinks (1.0.0) + turbolinks (1.2.0) coffee-rails - tzinfo (0.3.35) - uglifier (1.3.0) + tzinfo (0.3.37) + uglifier (2.1.1) execjs (>= 0.3.0) multi_json (~> 1.0, >= 1.0.2) @@ -137,14 +109,12 @@ PLATFORMS ruby DEPENDENCIES - activerecord-deprecated_finders! - arel! - coffee-rails! - jbuilder (~> 1.0.1) + coffee-rails + jbuilder (~> 1.2) jquery-rails - rails! - sass-rails! - sprockets-rails! + rails (= 4.0.0) + sass-rails + sdoc sqlite3 turbolinks uglifier (>= 1.0.3) diff --git a/guides/code/getting_started/app/controllers/comments_controller.rb b/guides/code/getting_started/app/controllers/comments_controller.rb index 0e3d2a6dde..b2d9bcdf7f 100644 --- a/guides/code/getting_started/app/controllers/comments_controller.rb +++ b/guides/code/getting_started/app/controllers/comments_controller.rb @@ -4,7 +4,7 @@ class CommentsController < ApplicationController def create @post = Post.find(params[:post_id]) - @comment = @post.comments.create(params[:comment].permit(:commenter, :body)) + @comment = @post.comments.create(comment_params) redirect_to post_path(@post) end @@ -14,4 +14,10 @@ class CommentsController < ApplicationController @comment.destroy redirect_to post_path(@post) end + + private + + def comment_params + params.require(:comment).permit(:commenter, :body) + end end diff --git a/guides/code/getting_started/app/controllers/posts_controller.rb b/guides/code/getting_started/app/controllers/posts_controller.rb index 6aa1409170..02689ad67b 100644 --- a/guides/code/getting_started/app/controllers/posts_controller.rb +++ b/guides/code/getting_started/app/controllers/posts_controller.rb @@ -17,7 +17,7 @@ class PostsController < ApplicationController def update @post = Post.find(params[:id]) - if @post.update(params[:post].permit(:title, :text)) + if @post.update(post_params) redirect_to action: :show, id: @post.id else render 'edit' @@ -29,7 +29,7 @@ class PostsController < ApplicationController end def create - @post = Post.new(params[:post].permit(:title, :text)) + @post = Post.new(post_params) if @post.save redirect_to action: :show, id: @post.id @@ -44,4 +44,10 @@ class PostsController < ApplicationController redirect_to action: :index end + + private + + def post_params + params.require(:post).permit(:title, :text) + end end 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 c9fb74af9c..f2f83585e1 100644 --- a/guides/code/getting_started/app/views/posts/_form.html.erb +++ b/guides/code/getting_started/app/views/posts/_form.html.erb @@ -1,6 +1,6 @@ <%= form_for @post do |f| %> <% if @post.errors.any? %> - <div id="errorExplanation"> + <div id="error_explanation"> <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2> <ul> @@ -14,12 +14,12 @@ <%= f.label :title %><br> <%= f.text_field :title %> </p> - + <p> <%= f.label :text %><br> <%= f.text_area :text %> </p> - + <p> <%= f.submit %> </p> 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 738e12d7dc..56be8dd3cc 100644 --- a/guides/code/getting_started/app/views/welcome/index.html.erb +++ b/guides/code/getting_started/app/views/welcome/index.html.erb @@ -1,3 +1,4 @@ <h1>Hello, Rails!</h1> <%= link_to "My Blog", controller: "posts" %> +<%= link_to "New Post", new_post_path %> diff --git a/guides/code/getting_started/config/environments/development.rb b/guides/code/getting_started/config/environments/development.rb index d169e9452c..7e5692b08b 100644 --- a/guides/code/getting_started/config/environments/development.rb +++ b/guides/code/getting_started/config/environments/development.rb @@ -22,7 +22,7 @@ Blog::Application.configure do # Only use best-standards-support built into browsers. config.action_dispatch.best_standards_support = :builtin - # Raise an error on page load if there are pending migrations + # Raise an error on page load if there are pending migrations. config.active_record.migration_error = :page_load # Debug mode disables concatenation and preprocessing of assets. diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md index 3c08f148cf..0f05cc6b85 100644 --- a/guides/source/2_3_release_notes.md +++ b/guides/source/2_3_release_notes.md @@ -40,7 +40,7 @@ Here's a summary of the rack-related changes: * `ActiveRecord::QueryCache` middleware is automatically inserted onto the middleware stack if `ActiveRecord` has been loaded. This middleware sets up and flushes the per-request Active Record query cache. * The Rails router and controller classes follow the Rack spec. You can call a controller directly with `SomeController.call(env)`. The router stores the routing parameters in `rack.routing_args`. * `ActionController::Request` inherits from `Rack::Request`. -* Instead of `config.action_controller.session = { :session_key => 'foo', ...` use `config.action_controller.session = { :key => 'foo', ...`. +* Instead of `config.action_controller.session = { :session_key => 'foo', ...` use `config.action_controller.session = { :key => 'foo', ...`. * Using the `ParamsParser` middleware preprocesses any XML, JSON, or YAML requests so they can be read normally with any `Rack::Request` object after it. ### Renewed Support for Rails Engines @@ -173,8 +173,8 @@ before_save :update_credit_rating, :if => :active, Rails now has a `:having` option on find (as well as on `has_many` and `has_and_belongs_to_many` associations) for filtering records in grouped finds. As those with heavy SQL backgrounds know, this allows filtering based on grouped results: ```ruby -developers = Developer.find(:all, :group => "salary", - :having => "sum(salary) > 10000", :select => "salary") +developers = Developer.find(:all, :group => "salary", + :having => "sum(salary) > 10000", :select => "salary") ``` * Lead Contributor: [Emilio Tagua](http://github.com/miloops) diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md index e036860de2..dc4d942671 100644 --- a/guides/source/3_2_release_notes.md +++ b/guides/source/3_2_release_notes.md @@ -240,11 +240,11 @@ Action Pack In the example above, Posts controller will no longer automatically look up for a posts layout. If you need this functionality you could either remove `layout "application"` from `ApplicationController` or explicitly set it to `nil` in `PostsController`. -* Deprecated `ActionController::UnknownAction` in favour of `AbstractController::ActionNotFound`. +* Deprecated `ActionController::UnknownAction` in favor of `AbstractController::ActionNotFound`. -* Deprecated `ActionController::DoubleRenderError` in favour of `AbstractController::DoubleRenderError`. +* Deprecated `ActionController::DoubleRenderError` in favor of `AbstractController::DoubleRenderError`. -* Deprecated `method_missing` in favour of `action_missing` for missing actions. +* Deprecated `method_missing` in favor of `action_missing` for missing actions. * Deprecated `ActionController#rescue_action`, `ActionController#initialize_template_class` and `ActionController#assign_shortcuts`. diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index b9dbe820c8..8be7a86d20 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -50,10 +50,47 @@ $ ruby /path/to/rails/railties/bin/rails new myapp --dev Major Features -------------- -TODO. Give a list and then talk about each of them briefly. We can point to relevant code commits or documentation from these sections. - -![Rails 4.0](images/rails4_features.png) - +[![Rails 4.0](images/rails4_features.png)](http://guides.rubyonrails.org/images/rails4_features.png) + +### Upgrade + + * **Ruby 1.9.3** ([commit](https://github.com/rails/rails/commit/a0380e808d3dbd2462df17f5d3b7fcd8bd812496)) - Ruby 2.0 preferred; 1.9.3+ required + * **[New deprecation policy](http://www.youtube.com/watch?v=z6YgD6tVPQs)** - Deprecated features are warnings in Rails 4.0 and will be removed in Rails 4.1. + * **ActionPack page and action caching** ([commit](https://github.com/rails/rails/commit/b0a7068564f0c95e7ef28fc39d0335ed17d93e90)) - Page and action caching are extracted to a separate gem. Page and action caching requires too much manual intervention (manually expiring caches when the underlying model objects are updated). Instead, use Russian doll caching. + * **ActiveRecord observers** ([commit](https://github.com/rails/rails/commit/ccecab3ba950a288b61a516bf9b6962e384aae0b)) - Observers are extracted to a separate gem. Observers are only needed for page and action caching, and can lead to spaghetti code. + * **ActiveRecord session store** ([commit](https://github.com/rails/rails/commit/0ffe19056c8e8b2f9ae9d487b896cad2ce9387ad)) - The ActiveRecord session store is extracted to a separate gem. Storing sessions in SQL is costly. Instead, use cookie sessions, memcache sessions, or a custom session store. + * **ActiveModel mass assignment protection** ([commit](https://github.com/rails/rails/commit/f8c9a4d3e88181cee644f91e1342bfe896ca64c6)) - Rails 3 mass assignment protection is deprecated. Instead, use strong parameters. + * **ActiveResource** ([commit](https://github.com/rails/rails/commit/f1637bf2bb00490203503fbd943b73406e043d1d)) - ActiveResource is extracted to a separate gem. ActiveResource was not widely used. + * **vendor/plugins removed** ([commit](https://github.com/rails/rails/commit/853de2bd9ac572735fa6cf59fcf827e485a231c3)) - Use a Gemfile to manage installed gems. + +### ActionPack + + * **Strong parameters** ([commit](https://github.com/rails/rails/commit/a8f6d5c6450a7fe058348a7f10a908352bb6c7fc)) - Only allow whitelisted parameters to update model objects (`params.permit(:title, :text)`). + * **Routing concerns** ([commit](https://github.com/rails/rails/commit/0dd24728a088fcb4ae616bb5d62734aca5276b1b)) - In the routing DSL, factor out common subroutes (`comments` from `/posts/1/comments` and `/videos/1/comments`). + * **ActionController::Live** ([commit](https://github.com/rails/rails/commit/af0a9f9eefaee3a8120cfd8d05cbc431af376da3)) - Stream JSON with `response.stream`. + * **Declarative ETags** ([commit](https://github.com/rails/rails/commit/ed5c938fa36995f06d4917d9543ba78ed506bb8d)) - Add controller-level etag additions that will be part of the action etag computation + * **[Russian doll caching](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works)** ([commit](https://github.com/rails/rails/commit/4154bf012d2bec2aae79e4a49aa94a70d3e91d49)) - Cache nested fragments of views. Each fragment expires based on a set of dependencies (a cache key). The cache key is usually a template version number and a model object. + * **Turbolinks** ([commit](https://github.com/rails/rails/commit/e35d8b18d0649c0ecc58f6b73df6b3c8d0c6bb74)) - Serve only one initial HTML page. When the user navigates to another page, use pushState to update the URL and use AJAX to update the title and body. + * **Decouple ActionView from ActionController** ([commit](https://github.com/rails/rails/commit/78b0934dd1bb84e8f093fb8ef95ca99b297b51cd)) - ActionView was decoupled from ActionPack and will be moved to a separated gem in Rails 4.1. + * **Do not depend on ActiveModel** ([commit](https://github.com/rails/rails/commit/166dbaa7526a96fdf046f093f25b0a134b277a68)) - ActionPack no longer depends on ActiveModel. + +### General + + * **ActiveModel::Model** ([commit](https://github.com/rails/rails/commit/3b822e91d1a6c4eab0064989bbd07aae3a6d0d08)) - `ActiveModel::Model`, a mixin to make normal Ruby objects to work with ActionPack out of box (ex. for `form_for`) + * **New scope API** ([commit](https://github.com/rails/rails/commit/50cbc03d18c5984347965a94027879623fc44cce)) - Scopes must always use callables. + * **Schema cache dump** ([commit](https://github.com/rails/rails/commit/5ca4fc95818047108e69e22d200e7a4a22969477)) - To improve Rails boot time, instead of loading the schema directly from the database, load the schema from a dump file. + * **Support for specifying transaction isolation level** ([commit](https://github.com/rails/rails/commit/392eeecc11a291e406db927a18b75f41b2658253)) - Choose whether repeatable reads or improved performance (less locking) is more important. + * **Dalli** ([commit](https://github.com/rails/rails/commit/82663306f428a5bbc90c511458432afb26d2f238)) - Use Dalli memcache client for the memcache store. + * **Notifications start & finish** ([commit](https://github.com/rails/rails/commit/f08f8750a512f741acb004d0cebe210c5f949f28)) - Active Support instrumentation reports start and finish notifications to subscribers. + * **Thread safe by default** ([commit](https://github.com/rails/rails/commit/5d416b907864d99af55ebaa400fff217e17570cd)) - Rails can run in threaded app servers without additional configuration. Note: Check that the gems you are using are threadsafe. + * **PATCH verb** ([commit](https://github.com/rails/rails/commit/eed9f2539e3ab5a68e798802f464b8e4e95e619e)) - In Rails, PATCH replaces PUT. PATCH is used for partial updates of resources. + +### Security + + * **match do not catch all** ([commit](https://github.com/rails/rails/commit/90d2802b71a6e89aedfe40564a37bd35f777e541)) - In the routing DSL, match requires the HTTP verb or verbs to be specified. + * **html entities escaped by default** ([commit](https://github.com/rails/rails/commit/5f189f41258b83d49012ec5a0678d827327e7543)) - Strings rendered in erb are escaped unless wrapped with `raw` or `html_safe` is called. + * **New security headers** ([commit](https://github.com/rails/rails/commit/6794e92b204572d75a07bd6413bdae6ae22d5a82)) - Rails sends the following headers with every HTTP request: `X-Frame-Options` (prevents clickjacking by forbidding the browser from embedding the page in a frame), `X-XSS-Protection` (asks the browser to halt script injection) and `X-Content-Type-Options` (prevents the browser from opening a jpeg as an exe). + Extraction of features to gems --------------------------- @@ -83,12 +120,16 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railt ### Notable changes -* New test locations `test/models`, `test/helpers`, `test/controllers`, and `test/mailers`. Corresponding rake tasks added as well. ([Pull Request](https://github.com/rails/rails/pull/7878)) +* New test locations `test/models`, `test/helpers`, `test/controllers`, and `test/mailers`. Corresponding rake tasks added as well. ([Pull Request](https://github.com/rails/rails/pull/7878)) * Your app's executables now live in the `bin/` directory. Run `rake rails:update:bin` to get `bin/bundle`, `bin/rails`, and `bin/rake`. * Threadsafe on by default +* Ability to use a custom builder by passing `--builder` (or `-b`) to + `rails new` has been removed. Consider using application templates + instead. ([Pull Request](https://github.com/rails/rails/pull/9401)) + ### Deprecations * `config.threadsafe!` is deprecated in favor of `config.eager_load` which provides a more fine grained control on what is eager loaded. @@ -111,10 +152,9 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ ### Notable changes -* Add `ActiveModel::ForbiddenAttributesProtection`, a simple module to protect attributes from mass assignment when non-permitted attributes are passed. +* Add `ActiveModel::ForbiddenAttributesProtection`, a simple module to protect attributes from mass assignment when non-permitted attributes are passed. -* Added `ActiveModel::Model`, a mixin to make Ruby objects work with - Action Pack out of box. +* Added `ActiveModel::Model`, a mixin to make Ruby objects work with Action Pack out of box. ### Deprecations @@ -125,27 +165,27 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ ### Notable changes -* Replace deprecated `memcache-client` gem with `dalli` in ActiveSupport::Cache::MemCacheStore. +* Replace deprecated `memcache-client` gem with `dalli` in ActiveSupport::Cache::MemCacheStore. -* Optimize ActiveSupport::Cache::Entry to reduce memory and processing overhead. +* Optimize ActiveSupport::Cache::Entry to reduce memory and processing overhead. -* Inflections can now be defined per locale. `singularize` and `pluralize` accept locale as an extra argument. +* Inflections can now be defined per locale. `singularize` and `pluralize` accept locale as an extra argument. -* `Object#try` will now return nil instead of raise a NoMethodError if the receiving object does not implement the method, but you can still get the old behavior by using the new `Object#try!`. +* `Object#try` will now return nil instead of raise a NoMethodError if the receiving object does not implement the method, but you can still get the old behavior by using the new `Object#try!`. ### Deprecations -* Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from MiniTest instead. +* Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from MiniTest instead. -* ActiveSupport::Benchmarkable#silence has been deprecated due to its lack of thread safety. It will be removed without replacement in Rails 4.1. +* ActiveSupport::Benchmarkable#silence has been deprecated due to its lack of thread safety. It will be removed without replacement in Rails 4.1. -* `ActiveSupport::JSON::Variable` is deprecated. Define your own `#as_json` and `#encode_json` methods for custom JSON string literals. +* `ActiveSupport::JSON::Variable` is deprecated. Define your own `#as_json` and `#encode_json` methods for custom JSON string literals. -* Deprecates the compatibility method Module#local_constant_names, use Module#local_constants instead (which returns symbols). +* Deprecates the compatibility method Module#local_constant_names, use Module#local_constants instead (which returns symbols). -* BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger from Ruby standard library. +* BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger from Ruby standard library. -* Deprecate `assert_present` and `assert_blank` in favor of `assert object.blank?` and `assert object.present?` +* Deprecate `assert_present` and `assert_blank` in favor of `assert object.blank?` and `assert object.present?` Action Pack ----------- @@ -166,7 +206,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ ### Notable changes -* Improve ways to write `change` migrations, making the old `up` & `down` methods no longer necessary. +* Improve ways to write `change` migrations, making the old `up` & `down` methods no longer necessary. * The methods `drop_table` and `remove_column` are now reversible, as long as the necessary information is given. The method `remove_column` used to accept multiple column names; instead use `remove_columns` (which is not revertible). @@ -179,36 +219,36 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ If migrating down, the given migration / block is run normally. See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#reverting-previous-migrations) -* Adds PostgreSQL array type support. Any datatype can be used to create an array column, with full migration and schema dumper support. +* Adds PostgreSQL array type support. Any datatype can be used to create an array column, with full migration and schema dumper support. -* Add `Relation#load` to explicitly load the record and return `self`. +* Add `Relation#load` to explicitly load the record and return `self`. -* `Model.all` now returns an `ActiveRecord::Relation`, rather than an array of records. Use `Relation#to_a` if you really want an array. In some specific cases, this may cause breakage when upgrading. +* `Model.all` now returns an `ActiveRecord::Relation`, rather than an array of records. Use `Relation#to_a` if you really want an array. In some specific cases, this may cause breakage when upgrading. -* Added `ActiveRecord::Migration.check_pending!` that raises an error if migrations are pending. +* Added `ActiveRecord::Migration.check_pending!` that raises an error if migrations are pending. -* Added custom coders support for `ActiveRecord::Store`. Now you can set your custom coder like this: +* Added custom coders support for `ActiveRecord::Store`. Now you can set your custom coder like this: store :settings, accessors: [ :color, :homepage ], coder: JSON -* `mysql` and `mysql2` connections will set `SQL_MODE=STRICT_ALL_TABLES` by default to avoid silent data loss. This can be disabled by specifying `strict: false` in your `database.yml`. +* `mysql` and `mysql2` connections will set `SQL_MODE=STRICT_ALL_TABLES` by default to avoid silent data loss. This can be disabled by specifying `strict: false` in your `database.yml`. -* Remove IdentityMap. +* Remove IdentityMap. -* Remove automatic execution of EXPLAIN queries. The option `active_record.auto_explain_threshold_in_seconds` is no longer used and should be removed. +* Remove automatic execution of EXPLAIN queries. The option `active_record.auto_explain_threshold_in_seconds` is no longer used and should be removed. -* Adds `ActiveRecord::NullRelation` and `ActiveRecord::Relation#none` implementing the null object pattern for the Relation class. +* Adds `ActiveRecord::NullRelation` and `ActiveRecord::Relation#none` implementing the null object pattern for the Relation class. -* Added `create_join_table` migration helper to create HABTM join tables. +* Added `create_join_table` migration helper to create HABTM join tables. -* Allows PostgreSQL hstore records to be created. +* Allows PostgreSQL hstore records to be created. ### Deprecations -* Deprecated the old-style hash based finder API. This means that methods which previously accepted "finder options" no longer do. +* Deprecated the old-style hash based finder API. This means that methods which previously accepted "finder options" no longer do. -* All dynamic methods except for `find_by_...` and `find_by_...!` are deprecated. Here's - how you can rewrite the code: +* All dynamic methods except for `find_by_...` and `find_by_...!` are deprecated. Here's + how you can rewrite the code: * `find_all_by_...` can be rewritten using `where(...)`. * `find_last_by_...` can be rewritten using `where(...).last`. diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb index a50961a0c7..0a0a958e30 100644 --- a/guides/source/_welcome.html.erb +++ b/guides/source/_welcome.html.erb @@ -10,10 +10,13 @@ </p> <% else %> <p> - These are the new guides for Rails 3.2 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>. + These are the new guides for Rails 4.0 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>. These guides are designed to make you immediately productive with Rails, and to help you understand how all of the pieces fit together. </p> <% end %> <p> + The guides for Rails 3.2.x are available at <a href="http://guides.rubyonrails.org/v3.2.14/">http://guides.rubyonrails.org/v3.2.14/</a>. +</p> +<p> The guides for Rails 2.3.x are available at <a href="http://guides.rubyonrails.org/v2.3.11/">http://guides.rubyonrails.org/v2.3.11/</a>. </p> diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 28939f307f..ecaee02cce 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -129,7 +129,7 @@ Note that the `params` hash is actually an instance of `ActiveSupport::HashWithI ### JSON parameters -If you're writing a web service application, you might find yourself more comfortable accepting parameters in JSON format. Rails will automatically convert your parameters into the `params` hash, which you can access as you would normally. +If you're writing a web service application, you might find yourself more comfortable accepting parameters in JSON format. If the "Content-Type" header of your request is set to "application/json", Rails will automatically convert your parameters into the `params` hash, which you can access as you would normally. So for example, if you are sending this JSON content: @@ -160,7 +160,7 @@ NOTE: Support for parsing XML parameters has been extracted into a gem named `ac The `params` hash will always contain the `:controller` and `:action` keys, but you should use the methods `controller_name` and `action_name` instead to access these values. Any other parameters defined by the routing, such as `:id` will also be available. As an example, consider a listing of clients where the list can show either active or inactive clients. We can add a route which captures the `:status` parameter in a "pretty" URL: ```ruby -match '/clients/:status' => 'clients#index', foo: 'bar' +get '/clients/:status' => 'clients#index', foo: 'bar' ``` In this case, when a user opens the URL `/clients/active`, `params[:status]` will be set to "active". When this route is used, `params[:foo]` will also be set to "bar" just like it was passed in the query string. In the same way `params[:action]` will contain "index". @@ -257,7 +257,7 @@ params.require(:log_entry).permit! ``` This will mark the `:log_entry` parameters hash and any subhash of it -permitted. Extreme care should be taken when using `permit!` as it +permitted. Extreme care should be taken when using `permit!` as it will allow all current and future model attributes to be mass-assigned. @@ -346,7 +346,7 @@ Your application has a session for each user in which you can store small amount All session stores use a cookie to store a unique ID for each session (you must use a cookie, Rails will not allow you to pass the session ID in the URL as this is less secure). -For most stores, this ID is used to look up the session data on the server, e.g. in a database table. There is one exception, and that is the default and recommended session store - the CookieStore - which stores all session data in the cookie itself (the ID is still available to you if you need it). This has the advantage of being very lightweight and it requires zero setup in a new application in order to use the session. The cookie data is cryptographically signed to make it tamper-proof, but it is not encrypted, so anyone with access to it can read its contents but not edit it (Rails will not accept it if it has been edited). +For most stores, this ID is used to look up the session data on the server, e.g. in a database table. There is one exception, and that is the default and recommended session store - the CookieStore - which stores all session data in the cookie itself (the ID is still available to you if you need it). This has the advantage of being very lightweight and it requires zero setup in a new application in order to use the session. The cookie data is cryptographically signed to make it tamper-proof. And it is also encrypted so anyone with access to it can't read its contents. (Rails will not accept it if it has been edited). The CookieStore can store around 4kB of data — much less than the others — but this is usually enough. Storing large amounts of data in the session is discouraged no matter which session store your application uses. You should especially avoid storing complex objects (anything other than basic Ruby objects, the most common example being model instances) in the session, as the server might not be able to reassemble them between requests, which will result in an error. @@ -410,7 +410,7 @@ class ApplicationController < ActionController::Base # logging out removes it. def current_user @_current_user ||= session[:current_user_id] && - User.find_by_id(session[:current_user_id]) + User.find_by(id: session[:current_user_id]) end end ``` @@ -907,6 +907,92 @@ Now the user can request to get a PDF version of a client just by adding ".pdf" GET /clients/1.pdf ``` +### Live Streaming of Arbitrary Data + +Rails allows you to stream more than just files. In fact, you can stream anything +you would like in a response object. The `ActionController::Live` module allows +you to create a persistent connection with a browser. Using this module, you will +be able to send arbitrary data to the browser at specific points in time. + +#### Incorporating Live Streaming + +Including `ActionController::Live` inside of your controller class will provide +all actions inside of the controller the ability to stream data. You can mix in +the module like so: + +```ruby +class MyController < ActionController::Base + include ActionController::Live + + def stream + response.headers['Content-Type'] = 'text/event-stream' + 100.times { + response.stream.write "hello world\n" + sleep 1 + } + ensure + response.stream.close + end +end +``` + +The above code will keep a persistent connection with the browser and send 100 +messages of `"hello world\n"`, each one second apart. + +There are a couple of things to notice in the above example. We need to make +sure to close the response stream. Forgetting to close the stream will leave +the socket open forever. We also have to set the content type to `text/event-stream` +before we write to the response stream. This is because headers cannot be written +after the response has been committed (when `response.committed` returns a truthy +value), which occurs when you `write` or `commit` the response stream. + +#### Example Usage + +Let's suppose that you were making a Karaoke machine and a user wants to get the +lyrics for a particular song. Each `Song` has a particular number of lines and +each line takes time `num_beats` to finish singing. + +If we wanted to return the lyrics in Karaoke fashion (only sending the line when +the singer has finished the previous line), then we could use `ActionController::Live` +as follows: + +```ruby +class LyricsController < ActionController::Base + include ActionController::Live + + def show + response.headers['Content-Type'] = 'text/event-stream' + song = Song.find(params[:id]) + + song.each do |line| + response.stream.write line.lyrics + sleep line.num_beats + end + ensure + response.stream.close + end +end +``` + +The above code sends the next line only after the singer has completed the previous +line. + +#### Streaming Considerations + +Streaming arbitrary data is an extremely powerful tool. As shown in the previous +examples, you can choose when and what to send across a response stream. However, +you should also note the following things: + +* Each response stream creates a new thread and copies over the thread local + variables from the original thread. Having too many thread local variables can + negatively impact performance. Similarly, a large number of threads can also + hinder performance. +* Failing to close the response stream will leave the corresponding socket open + forever. Make sure to call `close` whenever you are using a response stream. +* WEBrick servers buffer all responses, and so including `ActionController::Live` + will not work. You must use a web server which does not automatically buffer + responses. + Log Filtering ------------- diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index c351339117..bf34799eb3 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -216,6 +216,11 @@ Action Mailer makes it very easy to add attachments. attachments['filename.jpg'] = File.read('/path/to/filename.jpg') ``` + When the `mail` method will be triggered, it will send a multipart email with + an attachment, properly nested with the top level being `multipart/mixed` and + the first part being a `multipart/alternative` containing the plain text and + HTML email messages. + NOTE: Mail will automatically Base64 encode an attachment. If you want something different, encode your content and pass in the encoded content and encoding in a `Hash` to the `attachments` method. @@ -373,7 +378,7 @@ Just like with controller views, use `yield` to render the view inside the layout. You can also pass in a `layout: 'layout_name'` option to the render call inside -the format block to specify different layouts for different actions: +the format block to specify different layouts for different formats: ```ruby class UserMailer < ActionMailer::Base @@ -451,26 +456,6 @@ with the HTML and text versions setup as different parts. The order of the parts getting inserted is determined by the `:parts_order` inside of the `ActionMailer::Base.default` method. -### Sending Emails with Attachments - -Attachments can be added by using the `attachments` method: - -```ruby -class UserMailer < ActionMailer::Base - def welcome_email(user) - @user = user - @url = user_url(@user) - attachments['terms.pdf'] = File.read('/path/terms.pdf') - mail(to: @user.email, - subject: 'Please see the Terms and Conditions attached') - end -end -``` - -The above will send a multipart email with an attachment, properly nested with -the top level being `multipart/mixed` and the first part being a -`multipart/alternative` containing the plain text and HTML email messages. - ### Sending Emails with Dynamic Delivery Options If you wish to override the default delivery options (e.g. SMTP credentials) @@ -496,7 +481,7 @@ end There may be cases in which you want to skip the template rendering step and supply the email body as a string. You can achieve this using the `:body` -option. In such cases don't forget to add the `:content_type` option. Rails +option. In such cases don't forget to add the `:content_type` option. Rails will default to `text/plain` otherwise. ```ruby @@ -532,7 +517,7 @@ method. Here's an example: ```ruby class UserMailer < ActionMailer::Base def receive(email) - page = Page.find_by_address(email.to.first) + page = Page.find_by(address: email.to.first) page.emails.create( subject: email.subject, body: email.body @@ -623,7 +608,7 @@ files (environment.rb, production.rb, etc...) | Configuration | Description | |---------------|-------------| |`logger`|Generates information on the mailing run if available. Can be set to `nil` for no logging. Compatible with both Ruby's own `Logger` and `Log4r` loggers.| -|`smtp_settings`|Allows detailed configuration for `:smtp` delivery method:<ul><li>`:address` - Allows you to use a remote mail server. Just change it from its default "localhost" setting.</li><li>`:port` - On the off chance that your mail server doesn't run on port 25, you can change it.</li><li>`:domain` - If you need to specify a HELO domain, you can do it here.</li><li>`:user_name` - If your mail server requires authentication, set the username in this setting.</li><li>`:password` - If your mail server requires authentication, set the password in this setting.</li><li>`:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain`, `:login`, `:cram_md5`.</li><li>`:enable_starttls_auto` - Set this to `false` if there is a problem with your server certificate that you cannot resolve.</li></ul>| +|`smtp_settings`|Allows detailed configuration for `:smtp` delivery method:<ul><li>`:address` - Allows you to use a remote mail server. Just change it from its default "localhost" setting.</li><li>`:port` - On the off chance that your mail server doesn't run on port 25, you can change it.</li><li>`:domain` - If you need to specify a HELO domain, you can do it here.</li><li>`:user_name` - If your mail server requires authentication, set the username in this setting.</li><li>`:password` - If your mail server requires authentication, set the password in this setting.</li><li>`:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain`, `:login`, `:cram_md5`.</li><li>`:enable_starttls_auto` - Set this to `false` if there is a problem with your server certificate that you cannot resolve.</li></ul>| |`sendmail_settings`|Allows you to override options for the `:sendmail` delivery method.<ul><li>`:location` - The location of the sendmail executable. Defaults to `/usr/sbin/sendmail`.</li><li>`:arguments` - The command line arguments to be passed to sendmail. Defaults to `-i -t`.</li></ul>| |`raise_delivery_errors`|Whether or not errors should be raised if the email fails to be delivered. This only works if the external email server is configured for immediate delivery.| |`delivery_method`|Defines a delivery method. Possible values are `:smtp` (default), `:sendmail`, `:file` and `:test`.| @@ -649,7 +634,7 @@ config.action_mailer.delivery_method = :sendmail # } config.action_mailer.perform_deliveries = true config.action_mailer.raise_delivery_errors = true -config.action_mailer.default_options = {from: 'no-replay@example.com'} +config.action_mailer.default_options = {from: 'no-reply@example.com'} ``` ### Action Mailer Configuration for Gmail diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md index dea1ddef71..3542844f33 100644 --- a/guides/source/action_view_overview.md +++ b/guides/source/action_view_overview.md @@ -775,8 +775,8 @@ select_day(5) Returns a select tag with options for each of the hours 0 through 23 with the current hour selected. ```ruby -# Generates a select field for minutes that defaults to the minutes for the time provided -select_minute(Time.now + 6.hours) +# Generates a select field for hours that defaults to the hours for the time provided +select_hour(Time.now + 6.hours) ``` #### select_minute @@ -941,9 +941,9 @@ Creates a form and a scope around a specific model object that is used as a base ```html+erb <%= form_for @post do |f| %> <%= f.label :title, 'Title' %>: - <%= f.text_field :title %><br /> + <%= f.text_field :title %><br> <%= f.label :body, 'Body' %>: - <%= f.text_area :body %><br /> + <%= f.text_area :body %><br> <% end %> ``` @@ -1006,6 +1006,24 @@ text_field(:post, :title) # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" /> ``` +#### email_field + +Returns an input tag of the "email" type tailored for accessing a specified attribute. + +```ruby +email_field(:user, :email) +# => <input type="email" id="user_email" name="user[email]" value="#{@user.email}" /> +``` + +#### url_field + +Returns an input tag of the "url" type tailored for accessing a specified attribute. + +```ruby +url_field(:user, :url) +# => <input type="url" id="user_url" name="user[url]" value="#{@user.url}" /> +``` + ### FormOptionsHelper Provides a number of methods for turning different kinds of containers into a set of option tags. @@ -1090,7 +1108,7 @@ Example object structure for use with this method: ```ruby class Post < ActiveRecord::Base - has_and_belongs_to_many :author + has_and_belongs_to_many :authors end class Author < ActiveRecord::Base @@ -1230,6 +1248,14 @@ Return select and option tags for the given object and method, using `time_zone_ time_zone_select( "user", "time_zone") ``` +#### date_field + +Returns an input tag of the "date" type tailored for accessing a specified attribute. + +```ruby +date_field("user", "dob") +``` + ### FormTagHelper Provides a number of methods for creating form tags that doesn't rely on an Active Record object assigned to the template like FormHelper does. Instead, you provide the names and values manually. @@ -1364,6 +1390,33 @@ text_field_tag 'name' # => <input id="name" name="name" type="text" /> ``` +#### email_field_tag + +Creates a standard input field of email type. + +```ruby +email_field_tag 'email' +# => <input id="email" name="email" type="email" /> +``` + +#### url_field_tag + +Creates a standard input field of url type. + +```ruby +url_field_tag 'url' +# => <input id="url" name="url" type="url" /> +``` + +#### date_field_tag + +Creates a standard input field of date type. + +```ruby +date_field_tag "dob" +# => <input id="dob" name="dob" type="date" /> +``` + ### JavaScriptHelper Provides functionality for working with JavaScript in your views. @@ -1439,7 +1492,7 @@ number_to_human_size(1234567) # => 1.2 MB Formats a number as a percentage string. ```ruby -number_to_percentage(100, :precision => 0) # => 100% +number_to_percentage(100, precision: 0) # => 100% ``` #### number_to_phone diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index fc8fac4651..556c2544ff 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -62,9 +62,9 @@ may be necessary to write a lot of configuration code. This is particularly true for ORM frameworks in general. However, if you follow the conventions adopted by Rails, you'll need to write very little configuration (in some case no configuration at all) when creating Active Record models. The idea is that if -you configure your applications in the very same way most of the times then this -should be the default way. In this cases, explicit configuration would be needed -only in those cases where you can't follow the conventions for any reason. +you configure your applications in the very same way most of the time then this +should be the default way. Thus, explicit configuration would be needed +only in those cases where you can't follow the standard convention. ### Naming Conventions @@ -105,7 +105,7 @@ depending on the purpose of these columns. Migrations](migrations.html) to create your tables, this column will be automatically created. -There are also some optional column names that will create additional features +There are also some optional column names that will add additional features to Active Record instances: * `created_at` - Automatically gets set to the current date and time when the @@ -253,7 +253,7 @@ user = User.first ```ruby # return the first user named David -david = User.find_by_name('David') +david = User.find_by(name: 'David') ``` ```ruby @@ -270,7 +270,7 @@ Once an Active Record object has been retrieved, its attributes can be modified and it can be saved to the database. ```ruby -user = User.find_by_name('David') +user = User.find_by(name: 'David') user.name = 'Dave' user.save ``` @@ -279,7 +279,7 @@ A shorthand for this is to use a hash mapping attribute names to the desired value, like so: ```ruby -user = User.find_by_name('David') +user = User.find_by(name: 'David') user.update(name: 'Dave') ``` @@ -297,7 +297,7 @@ Likewise, once retrieved an Active Record object can be destroyed which removes it from the database. ```ruby -user = User.find_by_name('David') +user = User.find_by(name: 'David') user.destroy ``` @@ -343,7 +343,7 @@ Migrations Rails provides a domain-specific language for managing a database schema called migrations. Migrations are stored in files which are executed against any -database that Active Record support using `rake`. Here's a migration that +database that Active Record supports using `rake`. Here's a migration that creates a table: ```ruby diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md index bb42fab101..df1dd22971 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -49,8 +49,8 @@ The macro-style class methods can also receive a block. Consider using this styl class User < ActiveRecord::Base validates :login, :email, presence: true - before_create do |user| - user.name = user.login.capitalize if user.name.blank? + before_create do + self.name = login.capitalize if name.blank? end end ``` @@ -167,6 +167,7 @@ Additionally, the `after_find` callback is triggered by the following finder met * `all` * `first` * `find` +* `find_by` * `find_by_*` * `find_by_*!` * `find_by_sql` @@ -342,7 +343,7 @@ By using the `after_commit` callback we can account for this case. ```ruby class PictureFile < ActiveRecord::Base - after_commit :delete_picture_file_from_disk, :on => [:destroy] + after_commit :delete_picture_file_from_disk, on: [:destroy] def delete_picture_file_from_disk if File.exist?(filepath) diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index c4d69908ed..9252a248b2 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -58,6 +58,7 @@ The methods are: * `bind` * `create_with` +* `distinct` * `eager_load` * `extending` * `from` @@ -76,7 +77,6 @@ The methods are: * `reorder` * `reverse_order` * `select` -* `distinct` * `uniq` * `where` @@ -91,7 +91,7 @@ The primary operation of `Model.find(options)` can be summarized as: ### Retrieving a Single Object -Active Record provides five different ways of retrieving a single object. +Active Record provides several different ways of retrieving a single object. #### Using a Primary Key @@ -514,7 +514,13 @@ SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5)) Post.where.not(author: author) ``` -In other words, this query can be generated by calling `where` with no argument, then immediately chain with `not` passing `where` conditions. +In other words, this query can be generated by calling `where` with no argument, +then immediately chain with `not` passing `where` conditions. This will generate +SQL code like this: + +```sql +SELECT * FROM posts WHERE (author_id != 1) +``` Ordering -------- @@ -687,6 +693,10 @@ The SQL that would be executed: ```sql SELECT * FROM posts WHERE id > 10 LIMIT 20 + +# Original query without `except` +SELECT * FROM posts WHERE id > 10 ORDER BY id asc LIMIT 20 + ``` ### `unscope` @@ -707,7 +717,7 @@ Post.order('id DESC').limit(20).unscope(:order, :limit) = Post.all You can additionally unscope specific where clauses. For example: ```ruby -Post.where(:id => 10).limit(1).unscope(where: :id, :limit).order('id DESC') = Post.order('id DESC') +Post.where(id: 10).limit(1).unscope({ where: :id }, :limit).order('id DESC') = Post.order('id DESC') ``` ### `only` @@ -722,6 +732,10 @@ The SQL that would be executed: ```sql SELECT * FROM posts WHERE id > 10 ORDER BY id DESC + +# Original query without `only` +SELECT "posts".* FROM "posts" WHERE (id > 10) ORDER BY id desc LIMIT 20 + ``` ### `reorder` @@ -1202,9 +1216,7 @@ class User < ActiveRecord::Base scope :active, -> { where state: 'active' } scope :inactive, -> { where state: 'inactive' } end -``` -```ruby User.active.inactive # => SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'inactive' ``` @@ -1230,7 +1242,7 @@ One important caveat is that `default_scope` will be overridden by ```ruby class User < ActiveRecord::Base - default_scope { where state: 'pending' } + default_scope { where state: 'pending' } scope :active, -> { where state: 'active' } scope :inactive, -> { where state: 'inactive' } end diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index 621d2222ff..d95b587e78 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -120,8 +120,8 @@ database only if the object is valid: * `update!` The bang versions (e.g. `save!`) raise an exception if the record is invalid. -The non-bang versions don't: `save` and `update` return `false`, -`create` and `update` just return the objects. +The non-bang versions don't, `save` and `update` return `false`, +`create` just returns the object. ### Skipping Validations @@ -243,7 +243,7 @@ line of code you can add the same kind of validation to several attributes. All of them accept the `:on` and `:message` options, which define when the validation should be run and what message should be added to the `errors` collection if it fails, respectively. The `:on` option takes one of the values -`:save` (the default), `:create` or `:update`. There is a default error +`:save` (the default), `:create` or `:update`. There is a default error message for each one of the validation helpers. These messages are used when the `:message` option isn't specified. Let's take a look at each one of the available helpers. @@ -357,7 +357,7 @@ given regular expression, which is specified using the `:with` option. ```ruby class Product < ActiveRecord::Base validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/, - message: "Only letters allowed" } + message: "only allows letters" } end ``` @@ -677,13 +677,13 @@ class GoodnessValidator def initialize(person) @person = person end - + def validate if some_complex_condition_involving_ivars_and_private_methods? @person.errors[:base] << "This person is evil" end end - + # … end ``` @@ -736,8 +736,8 @@ class Topic < ActiveRecord::Base validates :title, length: { is: 5 }, allow_blank: true end -Topic.create("title" => "").valid? # => true -Topic.create("title" => nil).valid? # => true +Topic.create(title: "").valid? # => true +Topic.create(title: nil).valid? # => true ``` ### `:message` diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index c012ded888..1915252122 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -96,12 +96,13 @@ INFO: The predicate for strings uses the Unicode-aware character class `[:space: WARNING: Note that numbers are not mentioned. In particular, 0 and 0.0 are **not** blank. -For example, this method from `ActionDispatch::Session::AbstractStore` uses `blank?` for checking whether a session key is present: +For example, this method from `ActionController::HttpAuthentication::Token::ControllerMethods` uses `blank?` for checking whether a token is present: ```ruby -def ensure_session_key! - if @key.blank? - raise ArgumentError, 'A key is required...' +def authenticate(controller, &login_procedure) + token, options = token_and_options(controller.request) + unless token.blank? + login_procedure.call(token, options) end end ``` @@ -418,6 +419,14 @@ TIP: Since `with_options` forwards calls to its receiver they can be nested. Eac NOTE: Defined in `active_support/core_ext/object/with_options.rb`. +### JSON support + +Active Support provides a better implemention of `to_json` than the `json` gem ordinarily provides for Ruby objects. This is because some classes, like `Hash` and `OrderedHash` needs special handling in order to provide a proper JSON representation. + +Active Support also provides an implementation of `as_json` for the `Process::Status` class. + +NOTE: Defined in `active_support/core_ext/object/to_json.rb`. + ### Instance Variables Active Support provides several methods to ease access to instance variables. @@ -439,6 +448,22 @@ C.new(0, 1).instance_values # => {"x" => 0, "y" => 1} NOTE: Defined in `active_support/core_ext/object/instance_variables.rb`. +#### `instance_variable_names` + +The method `instance_variable_names` returns an array. Each name includes the "@" sign. + +```ruby +class C + def initialize(x, y) + @x, @y = x, y + end +end + +C.new(0, 1).instance_variable_names # => ["@x", "@y"] +``` + +NOTE: Defined in `active_support/core_ext/object/instance_variables.rb`. + ### Silencing Warnings, Streams, and Exceptions The methods `silence_warnings` and `enable_warnings` change the value of `$VERBOSE` accordingly for the duration of their block, and reset it afterwards: @@ -1038,7 +1063,7 @@ For convenience `class_attribute` also defines an instance predicate which is th When `:instance_reader` is `false`, the instance predicate returns a `NoMethodError` just like the reader method. -If you do not want the instance predicate, pass `instance_predicate: false` and it will not be defined. +If you do not want the instance predicate, pass `instance_predicate: false` and it will not be defined. NOTE: Defined in `active_support/core_ext/class/attribute.rb` @@ -1423,7 +1448,7 @@ The method `pluralize` returns the plural of its receiver: As the previous example shows, Active Support knows some irregular plurals and uncountable nouns. Built-in rules can be extended in `config/initializers/inflections.rb`. That file is generated by the `rails` command and has instructions in comments. -`pluralize` can also take an optional `count` parameter. If `count == 1` the singular form will be returned. For any other value of `count` the plural form will be returned: +`pluralize` can also take an optional `count` parameter. If `count == 1` the singular form will be returned. For any other value of `count` the plural form will be returned: ```ruby "dude".pluralize(0) # => "dudes" @@ -1963,7 +1988,7 @@ Produce a string representation of a number in human-readable words: 1234567890123456.to_s(:human) # => "1.23 Quadrillion" ``` -NOTE: Defined in `active_support/core_ext/numeric/formatting.rb`. +NOTE: Defined in `active_support/core_ext/numeric/conversions.rb`. Extensions to `Integer` ----------------------- @@ -2011,8 +2036,33 @@ NOTE: Defined in `active_support/core_ext/integer/inflections.rb`. Extensions to `BigDecimal` -------------------------- +### `to_s` -... +The method `to_s` is aliased to `to_formatted_s`. This provides a convenient way to display a BigDecimal value in floating-point notation: + +```ruby +BigDecimal.new(5.00, 6).to_s # => "5.0" +``` + +### `to_formatted_s` + +The method `to_formatted_s` provides a default specifier of "F". This means that a simple call to `to_formatted_s` or `to_s` will result in floating point representation instead of engineering notation: + +```ruby +BigDecimal.new(5.00, 6).to_formatted_s # => "5.0" +``` + +and that symbol specifiers are also supported: + +```ruby +BigDecimal.new(5.00, 6).to_formatted_s(:db) # => "5.0" +``` + +Engineering notation is still supported: + +```ruby +BigDecimal.new(5.00, 6).to_formatted_s("e") # => "0.5E1" +``` Extensions to `Enumerable` -------------------------- @@ -2383,7 +2433,7 @@ dup[1][2] = 4 array[1][2] == nil # => true ``` -NOTE: Defined in `active_support/core_ext/array/deep_dup.rb`. +NOTE: Defined in `active_support/core_ext/object/deep_dup.rb`. ### Grouping @@ -2609,45 +2659,7 @@ hash[:b][:e] == nil # => true hash[:b][:d] == [3, 4] # => true ``` -NOTE: Defined in `active_support/core_ext/hash/deep_dup.rb`. - -### Diffing - -The method `diff` returns a hash that represents a diff of the receiver and the argument with the following logic: - -* Pairs `key`, `value` that exist in both hashes do not belong to the diff hash. - -* If both hashes have `key`, but with different values, the pair in the receiver wins. - -* The rest is just merged. - -```ruby -{a: 1}.diff(a: 1) -# => {}, first rule - -{a: 1}.diff(a: 2) -# => {:a=>1}, second rule - -{a: 1}.diff(b: 2) -# => {:a=>1, :b=>2}, third rule - -{a: 1, b: 2, c: 3}.diff(b: 1, c: 3, d: 4) -# => {:a=>1, :b=>2, :d=>4}, all rules - -{}.diff({}) # => {} -{a: 1}.diff({}) # => {:a=>1} -{}.diff(a: 1) # => {:a=>1} -``` - -An important property of this diff hash is that you can retrieve the original hash by applying `diff` twice: - -```ruby -hash.diff(hash2).diff(hash2) == hash -``` - -Diffing hashes may be useful for error messages related to expected option hashes for example. - -NOTE: Defined in `active_support/core_ext/hash/diff.rb`. +NOTE: Defined in `active_support/core_ext/object/deep_dup.rb`. ### Working with Keys @@ -3782,13 +3794,13 @@ def default_helper_module! module_path = module_name.underscore helper module_path rescue MissingSourceFile => e - raise e unless e.is_missing? "#{module_path}_helper" + raise e unless e.is_missing? "helpers/#{module_path}_helper" rescue NameError => e raise e unless e.missing_name? "#{module_name}Helper" end ``` -NOTE: Defined in `active_support/core_ext/name_error.rb`. +NOTE: Defined in `actionpack/lib/abstract_controller/helpers.rb`. Extensions to `LoadError` ------------------------- @@ -3811,4 +3823,4 @@ rescue NameError => e end ``` -NOTE: Defined in `active_support/core_ext/load_error.rb`. +NOTE: Defined in `actionpack/lib/abstract_controller/helpers.rb`. diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md index 38dbfd3152..969596f470 100644 --- a/guides/source/active_support_instrumentation.md +++ b/guides/source/active_support_instrumentation.md @@ -39,7 +39,7 @@ Action Controller ```ruby { - key: 'posts/1-dasboard-view' + key: 'posts/1-dashboard-view' } ``` @@ -51,7 +51,7 @@ Action Controller ```ruby { - key: 'posts/1-dasboard-view' + key: 'posts/1-dashboard-view' } ``` @@ -63,7 +63,7 @@ Action Controller ```ruby { - key: 'posts/1-dasboard-view' + key: 'posts/1-dashboard-view' } ``` @@ -75,7 +75,7 @@ Action Controller ```ruby { - key: 'posts/1-dasboard-view' + key: 'posts/1-dashboard-view' } ``` diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md index d0499878da..7e056d970c 100644 --- a/guides/source/api_documentation_guidelines.md +++ b/guides/source/api_documentation_guidelines.md @@ -25,7 +25,7 @@ Write in present tense: "Returns a hash that...", rather than "Returned a hash t Start comments in upper case. Follow regular punctuation rules: ```ruby -# Declares an attribute reader backed by an internally-named +# Declares an attribute reader backed by an internally-named # instance variable. def attr_internal_reader(*attrs) ... @@ -57,7 +57,7 @@ Use two spaces to indent chunks of code--that is, for markup purposes, two space Short docs do not need an explicit "Examples" label to introduce snippets; they just follow paragraphs: ```ruby -# Converts a collection of elements into a formatted string by +# Converts a collection of elements into a formatted string by # calling +to_s+ on all elements and joining them. # # Blog.all.to_formatted_s # => "First PostSecond PostThird Post" @@ -141,7 +141,7 @@ class Array end ``` -WARNING: Using a pair of `+...+` for fixed-width font only works with **words**; that is: anything matching `\A\w+\z`. For anything else use `<tt>...</tt>`, notably symbols, setters, inline snippets, etc. +WARNING: Using a pair of `+...+` for fixed-width font only works with **words**; that is: anything matching `\A\w+\z`. For anything else use `<tt>...</tt>`, notably symbols, setters, inline snippets, etc. ### Regular Font diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 3b3707d9e5..6c4d7fe255 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -69,12 +69,12 @@ Rails' old strategy was to append a date-based query string to every asset linke The query string strategy has several disadvantages: -1. **Not all caches will reliably cache content where the filename only differs by query parameters**<br /> +1. **Not all caches will reliably cache content where the filename only differs by query parameters**<br> [Steve Souders recommends](http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/), "...avoiding a querystring for cacheable resources". He found that in this case 5-20% of requests will not be cached. Query strings in particular do not work at all with some CDNs for cache invalidation. -2. **The file name can change between nodes in multi-server environments.**<br /> +2. **The file name can change between nodes in multi-server environments.**<br> The default query string in Rails 2.x is based on the modification time of the files. When assets are deployed to a cluster, there is no guarantee that the timestamps will be the same, resulting in different values being used depending on which server handles the request. -3. **Too much cache invalidation**<br /> +3. **Too much cache invalidation**<br> When static assets are deployed with each new release of code, the mtime(time of last modification) of _all_ these files changes, forcing all remote clients to fetch them again, even when the content of those assets has not changed. Fingerprinting fixes these problems by avoiding query strings, and by ensuring that filenames are consistent based on their content. @@ -419,17 +419,6 @@ The rake task is: $ RAILS_ENV=production bundle exec rake assets:precompile ``` -For faster asset precompiles, you can partially load your application by setting -`config.assets.initialize_on_precompile` to false in `config/application.rb`, though in that case templates -cannot see application objects or methods. **Heroku requires this to be false.** - -WARNING: If you set `config.assets.initialize_on_precompile` to false, be sure to -test `rake assets:precompile` locally before deploying. It may expose bugs where -your assets reference application objects or methods, since those are still -in scope in development mode regardless of the value of this flag. Changing this flag also affects -engines. Engines can define assets for precompilation as well. Since the complete environment is not loaded, -engines (or other gems) will not be loaded, which can cause missing assets. - Capistrano (v2.15.1 and above) includes a recipe to handle this in deployment. Add the following line to `Capfile`: ```ruby @@ -570,16 +559,8 @@ In `config/environments/development.rb`, place the following line: config.assets.prefix = "/dev-assets" ``` -You will also need this in application.rb: - -```ruby -config.assets.initialize_on_precompile = false -``` - The `prefix` change makes Rails use a different URL for serving assets in development mode, and pass all requests to Sprockets. The prefix is still set to `/assets` in the production environment. Without this change, the application would serve the precompiled assets from `public/assets` in development, and you would not see any local changes until you compile assets again. -The `initialize_on_precompile` change tells the precompile task to run without invoking Rails. This is because the precompile task runs in production mode by default, and will attempt to connect to your specified production database. Please note that you cannot have code in pipeline files that relies on Rails resources (such as the database) when compiling locally with this option. - You will also need to ensure that any compressors or minifiers are available on your development system. In practice, this will allow you to precompile locally, have those files in your working tree, and commit those files to source control when needed. Development mode will work as expected. @@ -625,7 +606,7 @@ Customizing the Pipeline ### CSS Compression -There is currently one option for compressing CSS, YUI. The [YUI CSS compressor](http://developer.yahoo.com/yui/compressor/css.html) provides minification. +There is currently one option for compressing CSS, YUI. The [YUI CSS compressor](http://yui.github.io/yuicompressor/css.html) provides minification. The following line enables YUI compression, and requires the `yui-compressor` gem. @@ -639,7 +620,7 @@ The `config.assets.compress` must be set to `true` to enable CSS compression. Possible options for JavaScript compression are `:closure`, `:uglifier` and `:yui`. These require the use of the `closure-compiler`, `uglifier` or `yui-compressor` gems, respectively. -The default Gemfile includes [uglifier](https://github.com/lautis/uglifier). This gem wraps [UglifierJS](https://github.com/mishoo/UglifyJS) (written for NodeJS) in Ruby. It compresses your code by removing white space. It also includes other optimizations such as changing your `if` and `else` statements to ternary operators where possible. +The default Gemfile includes [uglifier](https://github.com/lautis/uglifier). This gem wraps [UglifyJS](https://github.com/mishoo/UglifyJS) (written for NodeJS) in Ruby. It compresses your code by removing white space and comments, shortening local variable names, and performing other micro-optimizations such as changing `if` and `else` statements to ternary operators where possible. The following line invokes `uglifier` for JavaScript compression. diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 16a5241319..e6a66f3fa1 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -721,6 +721,7 @@ When you declare a `belongs_to` association, the declaring class automatically g * `association=(associate)` * `build_association(attributes = {})` * `create_association(attributes = {})` +* `create_association!(attributes = {})` In all of these methods, `association` is replaced with the symbol passed as the first argument to `belongs_to`. For example, given the declaration: @@ -737,6 +738,7 @@ customer customer= build_customer create_customer +create_customer! ``` NOTE: When initializing a new `has_one` or `belongs_to` association you must use the `build_` prefix to build the association, rather than the `association.build` method that would be used for `has_many` or `has_and_belongs_to_many` associations. To create one, use the `create_` prefix. @@ -777,6 +779,10 @@ The `create_association` method returns a new object of the associated type. Thi customer_name: "John Doe") ``` +##### `create_association!(attributes = {})` + +Does the same as `create_association` above, but raises `ActiveRecord::RecordInvalid` if the record is invalid. + #### Options for `belongs_to` @@ -947,7 +953,7 @@ end ##### `includes` -You can use the `includes` method let you specify second-order associations that should be eager-loaded when this association is used. For example, consider these models: +You can use the `includes` method to specify second-order associations that should be eager-loaded when this association is used. For example, consider these models: ```ruby class LineItem < ActiveRecord::Base @@ -1019,6 +1025,7 @@ When you declare a `has_one` association, the declaring class automatically gain * `association=(associate)` * `build_association(attributes = {})` * `create_association(attributes = {})` +* `create_association!(attributes = {})` In all of these methods, `association` is replaced with the symbol passed as the first argument to `has_one`. For example, given the declaration: @@ -1035,6 +1042,7 @@ account account= build_account create_account +create_account! ``` NOTE: When initializing a new `has_one` or `belongs_to` association you must use the `build_` prefix to build the association, rather than the `association.build` method that would be used for `has_many` or `has_and_belongs_to_many` associations. To create one, use the `create_` prefix. @@ -1073,6 +1081,10 @@ The `create_association` method returns a new object of the associated type. Thi @account = @supplier.create_account(terms: "Net 30") ``` +##### `create_association!(attributes = {})` + +Does the same as `create_association` above, but raises `ActiveRecord::RecordInvalid` if the record is invalid. + #### Options for `has_one` While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_one` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options: @@ -1285,6 +1297,7 @@ When you declare a `has_many` association, the declaring class automatically gai * `collection.exists?(...)` * `collection.build(attributes = {}, ...)` * `collection.create(attributes = {})` +* `collection.create!(attributes = {})` In all of these methods, `collection` is replaced with the symbol passed as the first argument to `has_many`, and `collection_singular` is replaced with the singularized version of that symbol. For example, given the declaration: @@ -1312,6 +1325,7 @@ orders.where(...) orders.exists?(...) orders.build(attributes = {}, ...) orders.create(attributes = {}) +orders.create!(attributes = {}) ``` ##### `collection(force_reload = false)` @@ -1427,6 +1441,10 @@ The `collection.create` method returns a new object of the associated type. This order_number: "A12345") ``` +##### `collection.create!(attributes = {})` + +Does the same as `collection.create` above, but raises `ActiveRecord::RecordInvalid` if the record is invalid. + #### Options for `has_many` While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_many` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options: @@ -1511,6 +1529,20 @@ end By convention, Rails assumes that the column used to hold the primary key of the association is `id`. You can override this and explicitly specify the primary key with the `:primary_key` option. +Let's say that `users` table has `id` as the primary_key but it also has +`guid` column. And the requirement is that `todos` table should hold +`guid` column value and not `id` value. This can be achieved like this + +```ruby +class User < ActiveRecord::Base + has_many :todos, primary_key: :guid +end +``` + +Now if we execute `@user.todos.create` then `@todo` record will have +`user_id` value as the `guid` value of `@user`. + + ##### `:source` The `:source` option specifies the source association name for a `has_many :through` association. You only need to use this option if the name of the source association cannot be automatically inferred from the association name. @@ -1678,7 +1710,7 @@ person.posts.inspect # => [#<Post id: 5, name: "a1">, #<Post id: 5, name: "a1">] Reading.all.inspect # => [#<Reading id: 12, person_id: 5, post_id: 5>, #<Reading id: 13, person_id: 5, post_id: 5>] ``` -In the above case there are two readings and `person.posts` brings out both of +In the above case there are two readings and `person.posts` brings out both of them even though these records are pointing to the same post. Now let's set `distinct`: @@ -1697,24 +1729,24 @@ person.posts.inspect # => [#<Post id: 7, name: "a1">] Reading.all.inspect # => [#<Reading id: 16, person_id: 7, post_id: 7>, #<Reading id: 17, person_id: 7, post_id: 7>] ``` -In the above case there are still two readings. However `person.posts` shows +In the above case there are still two readings. However `person.posts` shows only one post because the collection loads only unique records. -If you want to make sure that, upon insertion, all of the records in the -persisted association are distinct (so that you can be sure that when you -inspect the association that you will never find duplicate records), you should -add a unique index on the table itself. For example, if you have a table named -``person_posts`` and you want to make sure all the posts are unique, you could +If you want to make sure that, upon insertion, all of the records in the +persisted association are distinct (so that you can be sure that when you +inspect the association that you will never find duplicate records), you should +add a unique index on the table itself. For example, if you have a table named +`person_posts` and you want to make sure all the posts are unique, you could add the following in a migration: ```ruby -add_index :person_posts, :post, :unique => true +add_index :person_posts, :post, unique: true ``` -Note that checking for uniqueness using something like ``include?`` is subject -to race conditions. Do not attempt to use ``include?`` to enforce distinctness -in an association. For instance, using the post example from above, the -following code would be racy because multiple users could be attempting this +Note that checking for uniqueness using something like `include?` is subject +to race conditions. Do not attempt to use `include?` to enforce distinctness +in an association. For instance, using the post example from above, the +following code would be racy because multiple users could be attempting this at the same time: ```ruby @@ -1754,6 +1786,7 @@ When you declare a `has_and_belongs_to_many` association, the declaring class au * `collection.exists?(...)` * `collection.build(attributes = {})` * `collection.create(attributes = {})` +* `collection.create!(attributes = {})` In all of these methods, `collection` is replaced with the symbol passed as the first argument to `has_and_belongs_to_many`, and `collection_singular` is replaced with the singularized version of that symbol. For example, given the declaration: @@ -1781,6 +1814,7 @@ assemblies.where(...) assemblies.exists?(...) assemblies.build(attributes = {}, ...) assemblies.create(attributes = {}) +assemblies.create!(attributes = {}) ``` ##### Additional Column Methods @@ -1900,6 +1934,10 @@ The `collection.create` method returns a new object of the associated type. This @assembly = @part.assemblies.create({assembly_name: "Transmission housing"}) ``` +##### `collection.create!(attributes = {})` + +Does the same as `collection.create`, but raises `ActiveRecord::RecordInvalid` if the record is invalid. + #### Options for `has_and_belongs_to_many` While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_and_belongs_to_many` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options: @@ -1928,7 +1966,7 @@ TIP: The `:foreign_key` and `:association_foreign_key` options are useful when s ```ruby class User < ActiveRecord::Base - has_and_belongs_to_many :friends, + has_and_belongs_to_many :friends, class_name: "User", foreign_key: "this_user_id", association_foreign_key: "other_user_id" @@ -2133,7 +2171,7 @@ You're not limited to the functionality that Rails automatically builds into ass class Customer < ActiveRecord::Base has_many :orders do def find_by_order_prefix(order_number) - find_by_region_id(order_number[0..2]) + find_by(region_id: order_number[0..2]) end end end diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index 456abaf612..1e196b0e42 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -104,6 +104,15 @@ This method generates a cache key that depends on all products and can be used i All available products: <% end %> ``` + +If you want to cache a fragment under certain condition you can use `cache_if` or `cache_unless` + +```erb +<% cache_if (condition, cache_key_for_products) do %> + All available products: +<% end %> +``` + You can also use an Active Record model as the cache key: ```erb @@ -236,7 +245,7 @@ config.cache_store = :ehcache_store When initializing the cache, you may use the `:ehcache_config` option to specify the Ehcache config file to use (where the default is "ehcache.xml" in your Rails config directory), and the :cache_name option to provide a custom name for your cache (the default is rails_cache). -In addition to the standard `:expires_in` option, the `write` method on this cache can also accept the additional `:unless_exist` option, which will cause the cache store to use Ehcache's `putIfAbsent` method instead of `put`, and therefore will not overwrite an existing entry. Additionally, the `write` method supports all of the properties exposed by the [Ehcache Element class](http://ehcache.org/apidocs/net/sf/ehcache/Element.html) , including: +In addition to the standard `:expires_in` option, the `write` method on this cache can also accept the additional `:unless_exist` option, which will cause the cache store to use Ehcache's `putIfAbsent` method instead of `put`, and therefore will not overwrite an existing entry. Additionally, the `write` method supports all of the properties exposed by the [Ehcache Element class](http://ehcache.org/apidocs/net/sf/ehcache/Element.html) , including: | Property | Argument Type | Description | | --------------------------- | ------------------- | ----------------------------------------------------------- | diff --git a/guides/source/command_line.md b/guides/source/command_line.md index e0b44bbf93..5f98326c57 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -27,6 +27,8 @@ There are a few commands that are absolutely critical to your everyday usage of * `rails dbconsole` * `rails new app_name` +All commands can run with ```-h or --help``` to list more information. + Let's create a simple Rails application to step through each of these commands in context. ### `rails new` @@ -64,7 +66,7 @@ With no further work, `rails server` will run our new shiny Rails app: $ cd commandsapp $ rails server => Booting WEBrick -=> Rails 3.2.3 application starting in development on http://0.0.0.0:3000 +=> Rails 4.0.0 application starting in development on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server [2012-05-28 00:39:41] INFO WEBrick 1.3.1 @@ -220,7 +222,7 @@ We will set up a simple resource called "HighScore" that will keep track of our ```bash $ rails generate scaffold HighScore game:string score:integer invoke active_record - create db/migrate/20120528060026_create_high_scores.rb + create db/migrate/20130717151933_create_high_scores.rb create app/models/high_score.rb invoke test_unit create test/models/high_score_test.rb @@ -242,18 +244,21 @@ $ rails generate scaffold HighScore game:string score:integer create app/helpers/high_scores_helper.rb invoke test_unit create test/helpers/high_scores_helper_test.rb + invoke jbuilder + create app/views/high_scores/index.json.jbuilder + create app/views/high_scores/show.json.jbuilder invoke assets invoke coffee create app/assets/javascripts/high_scores.js.coffee invoke scss create app/assets/stylesheets/high_scores.css.scss invoke scss - create app/assets/stylesheets/scaffolds.css.scss + identical app/assets/stylesheets/scaffolds.css.scss ``` The generator checks that there exist the directories for models, controllers, helpers, layouts, functional and unit tests, stylesheets, creates the views, controller, model and database migration for HighScore (creating the `high_scores` table and fields), takes care of the route for the **resource**, and new tests for everything. -The migration requires that we **migrate**, that is, run some Ruby code (living in that `20120528060026_create_high_scores.rb`) to modify the schema of our database. Which database? The sqlite3 database that Rails will create for you when we run the `rake db:migrate` command. We'll talk more about Rake in-depth in a little while. +The migration requires that we **migrate**, that is, run some Ruby code (living in that `20130717151933_create_high_scores.rb`) to modify the schema of our database. Which database? The sqlite3 database that Rails will create for you when we run the `rake db:migrate` command. We'll talk more about Rake in-depth in a little while. ```bash $ rake db:migrate @@ -289,7 +294,7 @@ If you wish to test out some code without changing any data, you can do that by ```bash $ rails console --sandbox -Loading development environment in sandbox (Rails 3.2.3) +Loading development environment in sandbox (Rails 4.0.0) Any modifications you make will be rolled back on exit irb(main):001:0> ``` @@ -348,6 +353,9 @@ Rake is Ruby Make, a standalone Ruby utility that replaces the Unix utility 'mak You can get a list of Rake tasks available to you, which will often depend on your current directory, by typing `rake --tasks`. Each task has a description, and should help you find the thing you need. +To get the full backtrace for running rake task you can pass the option +```--trace``` to command line, for example ```rake db:create --trace```. + ```bash $ rake --tasks rake about # List versions of all Rails frameworks and the environment @@ -361,6 +369,7 @@ 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) rake tmp:create # Creates tmp directories for sessions, cache, sockets, and pids ``` +INFO: You can also use ```rake -T``` to get the list of tasks. ### `about` @@ -372,13 +381,13 @@ About your application's environment Ruby version 1.9.3 (x86_64-linux) RubyGems version 1.3.6 Rack version 1.3 -Rails version 4.0.0.beta +Rails version 4.0.0 JavaScript Runtime Node.js (V8) -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 +Active Record version 4.0.0 +Action Pack version 4.0.0 +Action Mailer version 4.0.0 +Active Support version 4.0.0 +Middleware Rack::Sendfile, ActionDispatch::Static, Rack::Lock, #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ffd131a7c88>, 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 @@ -468,12 +477,13 @@ Rails comes with a test suite called `Test::Unit`. Rails owes its stability to t The `Rails.root/tmp` directory is, like the *nix /tmp directory, the holding place for temporary files like sessions (if you're using a file store for files), process id files, and cached actions. -The `tmp:` namespaced tasks will help you clear the `Rails.root/tmp` directory: +The `tmp:` namespaced tasks will help you clear and create the `Rails.root/tmp` directory: * `rake tmp:cache:clear` clears `tmp/cache`. * `rake tmp:sessions:clear` clears `tmp/sessions`. * `rake tmp:sockets:clear` clears `tmp/sockets`. * `rake tmp:clear` clears all the three: cache, sessions and sockets. +* `rake tmp:create` creates tmp directories for sessions, cache, sockets, and pids. ### Miscellaneous diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 9e40165d15..2f5444c763 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -30,10 +30,10 @@ Configuring Rails Components In general, the work of configuring Rails means configuring the components of Rails, as well as configuring Rails itself. The configuration file `config/application.rb` and environment-specific configuration files (such as `config/environments/production.rb`) allow you to specify the various settings that you want to pass down to all of the components. -For example, the default `config/application.rb` file includes this setting: +For example, the `config/application.rb` file includes this setting: ```ruby -config.filter_parameters += [:password] +config.autoload_paths += %W(#{config.root}/extras) ``` This is a setting for Rails itself. If you want to pass settings to individual Rails components, you can do so via the same `config` object in `config/application.rb`: @@ -97,10 +97,14 @@ These configuration methods are to be called on a `Rails::Railtie` object, such * `config.file_watcher` the class used to detect file updates in the filesystem when `config.reload_classes_only_on_change` is true. Must conform to `ActiveSupport::FileUpdateChecker` API. -* `config.filter_parameters` used for filtering out the parameters that you don't want shown in the logs, such as passwords or credit card numbers. +* `config.filter_parameters` used for filtering out the parameters that +you don't want shown in the logs, such as passwords or credit card +numbers. New applications filter out passwords by adding the following `config.filter_parameters+=[:password]` in `config/initializers/filter_parameter_logging.rb`. * `config.force_ssl` forces all requests to be under HTTPS protocol by using `ActionDispatch::SSL` middleware. +* `config.log_formatter` defines the formatter of the Rails logger. This option defaults to a instance of `ActiveSupport::Logger::SimpleFormatter` for all modes except production, where it defaults to `Logger::Formatter`. + * `config.log_level` defines the verbosity of the Rails logger. This option defaults to `:debug` for all modes except production, where it defaults to `:info`. * `config.log_tags` accepts a list of methods that respond to `request` object. This makes it easy to tag log lines with debug information like subdomain and request id — both very helpful in debugging multi-user production applications. @@ -131,7 +135,8 @@ These configuration methods are to be called on a `Rails::Railtie` object, such ### Configuring Assets -* `config.assets.enabled` a flag that controls whether the asset pipeline is enabled. It is explicitly initialized in `config/application.rb`. +* `config.assets.enabled` a flag that controls whether the asset +pipeline is enabled. It is set to true by default. * `config.assets.compress` a flag that enables the compression of compiled assets. It is explicitly set to true in `config/production.rb`. @@ -193,7 +198,7 @@ Every Rails application comes with a standard set of middleware which it uses in * `Rack::Lock` wraps the app in mutex so it can only be called by a single thread at a time. Only enabled when `config.cache_classes` is `false`. * `ActiveSupport::Cache::Strategy::LocalCache` serves as a basic memory backed cache. This cache is not thread safe and is intended only for serving as a temporary memory cache for a single thread. * `Rack::Runtime` sets an `X-Runtime` header, containing the time (in seconds) taken to execute the request. -* `Rails::Rack::Logger` notifies the logs that the request has began. After request is complete, flushes all the logs. +* `Rails::Rack::Logger` notifies the logs that the request has begun. After request is complete, flushes all the logs. * `ActionDispatch::ShowExceptions` rescues any exception returned by the application and renders nice exception pages if the request is local or if `config.consider_all_requests_local` is set to `true`. If `config.action_dispatch.show_exceptions` is set to `false`, exceptions will be raised regardless. * `ActionDispatch::RequestId` makes a unique X-Request-Id header available to the response and enables the `ActionDispatch::Request#uuid` method. * `ActionDispatch::RemoteIp` checks for IP spoofing attacks and gets valid `client_ip` from request headers. Configurable with the `config.action_dispatch.ip_spoofing_check`, and `config.action_dispatch.trusted_proxies` options. @@ -421,13 +426,13 @@ There are a few configuration options available in Active Support: ### Configuring a Database -Just about every Rails application will interact with a database. The database to use is specified in a configuration file called `config/database.yml`. If you open this file in a new Rails application, you'll see a default database configured to use SQLite3. The file contains sections for three different environments in which Rails can run by default: +Just about every Rails application will interact with a database. The database to use is specified in a configuration file called `config/database.yml`. If you open this file in a new Rails application, you'll see a default database configured to use SQLite3. The file contains sections for three different environments in which Rails can run by default: * The `development` environment is used on your development/local computer as you interact manually with the application. * The `test` environment is used when running automated tests. * The `production` environment is used when you deploy your application for the world to use. -TIP: You don't have to update the database configurations manually. If you look at the options of the application generator, you will see that one of the options is named `--database`. This option allows you to choose an adapter from a list of the most used relational databases. You can even run the generator repeatedly: `cd .. && rails new blog --database=mysql`. When you confirm the overwriting of the `config/database.yml` file, your application will be configured for MySQL instead of SQLite. Detailed examples of the common database connections are below. +TIP: You don't have to update the database configurations manually. If you look at the options of the application generator, you will see that one of the options is named `--database`. This option allows you to choose an adapter from a list of the most used relational databases. You can even run the generator repeatedly: `cd .. && rails new blog --database=mysql`. When you confirm the overwriting of the `config/database.yml` file, your application will be configured for MySQL instead of SQLite. Detailed examples of the common database connections are below. #### Configuring an SQLite3 Database @@ -527,7 +532,7 @@ By default Rails ships with three environments: "development", "test", and "prod 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. +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 @@ -600,7 +605,7 @@ end The `initializer` method takes three arguments with the first being the name for the initializer and the second being an options hash (not shown here) and the third being a block. The `:before` key in the options hash can be specified to specify which initializer this new initializer must run before, and the `:after` key will specify which initializer to run this initializer _after_. -Initializers defined using the `initializer` method will be ran in the order they are defined in, with the exception of ones that use the `:before` or `:after` methods. +Initializers defined using the `initializer` method will be run in the order they are defined in, with the exception of ones that use the `:before` or `:after` methods. WARNING: You may put your initializer before or after any other initializer in the chain, as long as it is logical. Say you have 4 initializers called "one" through "four" (defined in that order) and you define "four" to go _before_ "four" but _after_ "three", that just isn't logical and Rails will not be able to determine your initializer order. @@ -702,7 +707,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `ensure_autoload_once_paths_as_subset` Ensures that the `config.autoload_once_paths` only contains paths from `config.autoload_paths`. If it contains extra paths, then an exception will be raised. -* `add_to_prepare_blocks` The block for every `config.to_prepare` call in the application, a railtie or engine is added to the `to_prepare` callbacks for Action Dispatch which will be ran per request in development, or before the first request in production. +* `add_to_prepare_blocks` The block for every `config.to_prepare` call in the application, a railtie or engine is added to the `to_prepare` callbacks for Action Dispatch which will be run per request in development, or before the first request in production. * `add_builtin_route` If the application is running under the development environment then this will append the route for `rails/info/properties` to the application routes. This route provides the detailed information such as Rails and Ruby version for `public/index.html` in a default Rails application. diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 0bfa646b81..2f8a178376 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -184,7 +184,9 @@ Ruby on Rails has two main sets of documentation: the guides help you in learnin You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing it up to date with the latest edge Rails. To get involved in the translation of Rails guides, please see [Translating Rails Guides](https://wiki.github.com/rails/docrails/translating-rails-guides). -If you're confident about your changes, you can push them directly yourself via [docrails](https://github.com/rails/docrails). Docrails is a branch with an **open commit policy** and public write access. Commits to docrails are still reviewed, but this happens after they are pushed. Docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation. +You can either ask for commit bit if you'd like to contribute to [Docrails](http://github.com/rails/docrails) regularly (Please contact anyone from [the core team](http://rubyonrails.org/core)), or else open a pull request to [Rails](http://github.com/rails/rails) itself. + +Docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation. If you are unsure of the documentation changes, you can create an issue in the [Rails](https://github.com/rails/rails/issues) issues tracker on GitHub. @@ -232,8 +234,8 @@ workflow with the [rails-dev-box](https://github.com/rails/rails-dev-box). As a compromise, test what your code obviously affects, and if the change is not in railties run the whole test suite of the affected component. If all is -green that's enough to propose your contribution. We have [Travis CI](https -://travis-ci.org/) as a safety net for catching unexpected breakages +green that's enough to propose your contribution. We have [Travis CI](https://travis-ci.org/rails/rails) +as a safety net for catching unexpected breakages elsewhere. TIP: Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of Rails will generally not be accepted. @@ -262,7 +264,7 @@ The CHANGELOG is an important part of every release. It keeps the list of change You should add an entry to the CHANGELOG of the framework that you modified if you're adding or removing a feature, committing a bug fix or adding deprecation notices. Refactorings and documentation changes generally should not go to the CHANGELOG. -A CHANGELOG entry should summarize what was changed and should end with author's name. You can use multiple lines if you need more space and you can attach code examples indented with 4 spaces. If a change is related to a specific issue, you should attach issue's number. Here is an example CHANGELOG entry: +A CHANGELOG entry should summarize what was changed and should end with author's name and it should go on top of a CHANGELOG. You can use multiple lines if you need more space and you can attach code examples indented with 4 spaces. If a change is related to a specific issue, you should attach issue's number. Here is an example CHANGELOG entry: ``` * Summary of a change that briefly describes what was changed. You can use multiple diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index 70055c1d7d..50ee934b87 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -209,6 +209,37 @@ logger.tagged("BCX", "Jason") { logger.info "Stuff" } # Logs " logger.tagged("BCX") { logger.tagged("Jason") { logger.info "Stuff" } } # Logs "[BCX] [Jason] Stuff" ``` +### Impact of Logs on Performance +Logging will always have a small impact on performance of your rails app, + particularly when logging to disk.However, there are a few subtleties: + +Using the `:debug` level will have a greater performance penalty than `:fatal`, + as a far greater number of strings are being evaluated and written to the + log output (e.g. disk). + +Another potential pitfall is that if you have many calls to `Logger` like this + in your code: + +```ruby +logger.debug "Person attributes hash: #{@person.attributes.inspect}" +``` + +In the above example, There will be a performance impact even if the allowed +output level doesn't include debug. The reason is that Ruby has to evaluate +these strings, which includes instantiating the somewhat heavy `String` object +and interpolating the variables, and which takes time. +Therefore, it's recommended to pass blocks to the logger methods, as these are +only evaluated if the output level is the same or included in the allowed level +(i.e. lazy loading). The same code rewritten would be: + +```ruby +logger.debug {"Person attibutes hash: #{@person.attributes.inspect}"} +``` + +The contents of the block, and therefore the string interpolation, is only +evaluated if debug is enabled. This performance savings is only really +noticeable with large amounts of logging, but it's a good practice to employ. + Debugging with the `debugger` gem --------------------------------- @@ -248,7 +279,7 @@ Make sure you have started your web server with the option `--debugger`: ```bash $ rails server --debugger => Booting WEBrick -=> Rails 3.2.13 application starting on http://0.0.0.0:3000 +=> Rails 4.0.0 application starting on http://0.0.0.0:3000 => Debugger enabled ... ``` @@ -301,7 +332,7 @@ This command shows you where you are in the code by printing 10 lines centered a 7 8 respond_to do |format| 9 format.html # index.html.erb - 10 format.json { render :json => @posts } + 10 format.json { render json: @posts } ``` If you repeat the `list` command, this time using just `l`, the next ten lines of the file will be printed out. @@ -337,7 +368,7 @@ On the other hand, to see the previous ten lines you should type `list-` (or `l- 7 8 respond_to do |format| 9 format.html # index.html.erb - 10 format.json { render :json => @posts } + 10 format.json { render json: @posts } ``` This way you can move inside the file, being able to see the code above and over the line you added the `debugger`. @@ -504,7 +535,7 @@ TIP: You can use the debugger while using `rails console`. Just remember to `req ``` $ rails console -Loading development environment (Rails 3.2.13) +Loading development environment (Rails 4.0.0) >> require "debugger" => [] >> author = Author.first @@ -648,7 +679,7 @@ In this section, you will learn how to find and fix such leaks by using tool suc [Valgrind](http://valgrind.org/) is a Linux-only application for detecting C-based memory leaks and race conditions. -There are Valgrind tools that can automatically detect many memory management and threading bugs, and profile your programs in detail. For example, a C extension in the interpreter calls `malloc()` but is doesn't properly call `free()`, this memory won't be available until the app terminates. +There are Valgrind tools that can automatically detect many memory management and threading bugs, and profile your programs in detail. For example, if a C extension in the interpreter calls `malloc()` but doesn't properly call `free()`, this memory won't be available until the app terminates. For further information on how to install Valgrind and use with Ruby, refer to [Valgrind and Ruby](http://blog.evanweaver.com/articles/2008/02/05/valgrind-and-ruby/) by Evan Weaver. @@ -661,6 +692,8 @@ There are some Rails plugins to help you to find errors and debug your applicati * [Query Trace](https://github.com/ntalbott/query_trace/tree/master) Adds query origin tracing to your logs. * [Query Reviewer](https://github.com/nesquena/query_reviewer) This rails plugin not only runs "EXPLAIN" before each of your select queries in development, but provides a small DIV in the rendered output of each page with the summary of warnings for each query that it analyzed. * [Exception Notifier](https://github.com/smartinez87/exception_notification/tree/master) Provides a mailer object and a default set of templates for sending email notifications when errors occur in a Rails application. +* [Better Errors](https://github.com/charliesome/better_errors) Replaces the standard Rails error page with a new one containing more contextual information, like source code and variable inspection. +* [RailsPanel](https://github.com/dejan/rails_panel) Chrome extension for Rails development that will end your tailing of development.log. Have all information about your Rails app requests in the browser - in the Developer Tools panel. Provides insight to db/rendering/total times, parameter list, rendered views and more. References ---------- diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index 5647a4c1b7..ec25e09222 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -57,9 +57,24 @@ If you are on Fedora or CentOS, you can run $ sudo yum install libxml2 libxml2-devel libxslt libxslt-devel ``` +If you are running Arch Linux, you're done with: + +```bash +$ sudo pacman -S libxml2 libxslt +``` + +On FreeBSD, you just have to run: + +```bash +# pkg_add -r libxml2 libxslt +``` + +Alternatively, you can install the `textproc/libxml2` and `textproc/libxslt` +ports. + If you have any problems with these libraries, you can install them manually by compiling the source code. Just follow the instructions at the [Red Hat/CentOS section of the Nokogiri tutorials](http://nokogiri.org/tutorials/installing_nokogiri.html#red_hat__centos) . -Also, SQLite3 and its development files for the `sqlite3-ruby` gem — in Ubuntu you're done with just +Also, SQLite3 and its development files for the `sqlite3` gem — in Ubuntu you're done with just ```bash $ sudo apt-get install sqlite3 libsqlite3-dev @@ -71,6 +86,20 @@ And if you are on Fedora or CentOS, you're done with $ sudo yum install sqlite3 sqlite3-devel ``` +If you are on Arch Linux, you will need to run: + +```bash +$ sudo pacman -S sqlite +``` + +For FreeBSD users, you're done with: + +```bash +# pkg_add -r sqlite3 +``` + +Or compile the `databases/sqlite3` port. + Get a recent version of [Bundler](http://gembundler.com/) ```bash @@ -137,6 +166,25 @@ $ sudo yum install mysql-server mysql-devel $ sudo yum install postgresql-server postgresql-devel ``` +If you are running Arch Linux, MySQL isn't supported anymore so you will need to +use MariaDB instead (see [this announcement](https://www.archlinux.org/news/mariadb-replaces-mysql-in-repositories/)): + +```bash +$ sudo pacman -S mariadb libmariadbclient mariadb-clients +$ sudo pacman -S postgresql postgresql-libs +``` + +FreeBSD users will have to run the following: + +```bash +# pkg_add -r mysql56-client mysql56-server +# pkg_add -r postgresql92-client postgresql92-server +``` + +Or install them through ports (they are located under the `databases` folder). +If you run into troubles during the installation of MySQL, please see +[the MySQL documentation](http://dev.mysql.com/doc/refman/5.1/en/freebsd-installation.html). + After that, run: ```bash diff --git a/guides/source/engines.md b/guides/source/engines.md index bc66ed256e..a77be917a2 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -346,7 +346,7 @@ Next, the partial that this line will render needs to exist. Create a new direct <h3>New comment</h3> <%= form_for [@post, @post.comments.build] do |f| %> <p> - <%= f.label :text %><br /> + <%= f.label :text %><br> <%= f.text_area :text %> </p> <%= f.submit %> @@ -393,10 +393,15 @@ The form will be making a `POST` request to `/posts/:post_id/comments`, which wi ```ruby def create @post = Post.find(params[:post_id]) - @comment = @post.comments.create(params[:comment]) + @comment = @post.comments.create(comment_params) flash[:notice] = "Comment has been created!" redirect_to posts_path end + +private +def comment_params + params.require(:comment).permit(:text) +end ``` This is the final part required to get the new comment form working. Displaying the comments however, is not quite right yet. If you were to create a comment right now you would see this error: @@ -515,7 +520,7 @@ First, the `author_name` text field needs to be added to the `app/views/blorgh/p ```html+erb <div class="field"> - <%= f.label :author_name %><br /> + <%= f.label :author_name %><br> <%= f.text_field :author_name %> </div> ``` diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md index a4dab39d55..11e8db9e88 100644 --- a/guides/source/form_helpers.md +++ b/guides/source/form_helpers.md @@ -553,7 +553,7 @@ outputs (with actual option values omitted for brevity) which results in a `params` hash like ```ruby -{:person => {'birth_date(1i)' => '2008', 'birth_date(2i)' => '11', 'birth_date(3i)' => '22'}} +{'person' => {'birth_date(1i)' => '2008', 'birth_date(2i)' => '11', 'birth_date(3i)' => '22'}} ``` When this is passed to `Person.new` (or `update`), Active Record spots that these parameters should all be used to construct the `birth_date` attribute and uses the suffixed information to determine in which order it should pass these parameters to functions such as `Date.civil`. @@ -568,7 +568,7 @@ NOTE: In many cases the built-in date pickers are clumsy as they do not aid the ### Individual Components -Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component `select_year`, `select_month`, `select_day`, `select_hour`, `select_minute`, `select_second`. These helpers are fairly straightforward. By default they will generate an input field named after the time component (for example "year" for `select_year`, "month" for `select_month` etc.) although this can be overridden with the `:field_name` option. The `:prefix` option works in the same way that it does for `select_date` and `select_time` and has the same default value. +Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component `select_year`, `select_month`, `select_day`, `select_hour`, `select_minute`, `select_second`. These helpers are fairly straightforward. By default they will generate an input field named after the time component (for example "year" for `select_year`, "month" for `select_month` etc.) although this can be overridden with the `:field_name` option. The `:prefix` option works in the same way that it does for `select_date` and `select_time` and has the same default value. The first parameter specifies which value should be selected and can either be an instance of a Date, Time or DateTime, in which case the relevant component will be extracted, or a numerical value. For example @@ -830,7 +830,7 @@ Many apps grow beyond simple forms editing a single object. For example when cre ### Configuring the Model -Active Record provides model level support via the `accepts_nested_attributes_for` method: +Active Record provides model level support via the `accepts_nested_attributes_for` method: ```ruby class Person < ActiveRecord::Base @@ -881,19 +881,19 @@ end ```ruby { - :person => { - :name => 'John Doe', - :addresses_attributes => { - '0' => { - :kind => 'Home', - :street => '221b Baker Street', - }, - '1' => { - :kind => 'Office', - :street => '31 Spooner Street' - } - } + 'person' => { + 'name' => 'John Doe', + 'addresses_attributes' => { + '0' => { + 'kind' => 'Home', + 'street' => '221b Baker Street' + }, + '1' => { + 'kind' => 'Office', + 'street' => '31 Spooner Street' + } } + } } ``` @@ -973,4 +973,4 @@ As a convenience you can instead pass the symbol `:all_blank` which will create ### Adding Fields on the Fly -Rather than rendering multiple sets of fields ahead of time you may wish to add them only when a user clicks on an 'Add new child' button. Rails does not provide any builtin support for this. When generating new sets of fields you must ensure the the key of the associated array is unique - the current javascript date (milliseconds after the epoch) is a common choice. +Rather than rendering multiple sets of fields ahead of time you may wish to add them only when a user clicks on an 'Add new child' button. Rails does not provide any builtin support for this. When generating new sets of fields you must ensure the key of the associated array is unique - the current javascript date (milliseconds after the epoch) is a common choice. diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 1b30f4b728..58bf8bbe90 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -135,10 +135,10 @@ application. Most of the work in this tutorial will happen in the `app/` folder, | ----------- | ------- | |app/|Contains the controllers, models, views, helpers, mailers and assets for your application. You'll focus on this folder for the remainder of this guide.| |bin/|Contains the rails script that starts your app and can contain other scripts you use to deploy or run your application.| -|config/|Configure your application's runtime rules, routes, database, and more. This is covered in more detail in [Configuring Rails Applications](configuring.html)| +|config/|Configure your application's runtime rules, routes, database, and more. This is covered in more detail in [Configuring Rails Applications](configuring.html)| |config.ru|Rack configuration for Rack based servers used to start the application.| |db/|Contains your current database schema, as well as the database migrations.| -|Gemfile<br />Gemfile.lock|These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see [the Bundler website](http://gembundler.com) | +|Gemfile<br>Gemfile.lock|These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see [the Bundler website](http://gembundler.com) | |lib/|Extended modules for your application.| |log/|Application log files.| |public/|The only folder seen to the world as-is. Contains the static files and compiled assets.| @@ -230,16 +230,16 @@ Blog::Application.routes.draw do # first created -> highest priority. # ... # You can have the root of your site routed with "root" - # root to: "welcome#index" + # root "welcome#index" ``` -This is your application's _routing file_ which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. This file contains many sample routes on commented lines, and one of them actually shows you how to connect the root of your site to a specific controller and action. Find the line beginning with `root :to` and uncomment it. It should look something like the following: +This is your application's _routing file_ which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. This file contains many sample routes on commented lines, and one of them actually shows you how to connect the root of your site to a specific controller and action. Find the line beginning with `root` and uncomment it. It should look something like the following: ```ruby -root to: "welcome#index" +root "welcome#index" ``` -The `root to: "welcome#index"` tells Rails to map requests to the root of the application to the welcome controller's index action and `get "welcome/index"` tells Rails to map requests to <http://localhost:3000/welcome/index> to the welcome controller's index action. This was created earlier when you ran the controller generator (`rails generate controller welcome index`). +The `root "welcome#index"` tells Rails to map requests to the root of the application to the welcome controller's index action and `get "welcome/index"` tells Rails to map requests to <http://localhost:3000/welcome/index> to the welcome controller's index action. This was created earlier when you ran the controller generator (`rails generate controller welcome index`). If you navigate to <http://localhost:3000> in your browser, you'll see the `Hello, Rails!` message you put into `app/views/welcome/index.html.erb`, indicating that this new route is indeed going to `WelcomeController`'s `index` action and is rendering the view correctly. @@ -252,23 +252,26 @@ Now that you've seen how to create a controller, an action and a view, let's cre In the Blog application, you will now create a new _resource_. A resource is the term used for a collection of similar objects, such as posts, people or animals. You can create, read, update and destroy items for a resource and these operations are referred to as _CRUD_ operations. -Rails provides a `resources` method which can be used to declare a -standard REST resource. Here's how `config/routes.rb` will look like. +Rails provides a `resources` method which can be used to declare a standard REST resource. +Here's what `config/routes.rb` should look like after the _post resource_ is declared. ```ruby Blog::Application.routes.draw do resources :posts - root to: "welcome#index" + root "welcome#index" end ``` -If you run `rake routes`, you'll see that all the routes for the -standard RESTful actions. +If you run `rake routes`, you'll see that it has defined routes for all the +standard RESTful actions. The meaning of the prefix column (and other columns) +will be seen later, but for now notice that Rails has inferred the +singular form `post` and makes meaningful use of the distinction. ```bash $ rake routes + Prefix Verb URI Pattern Controller#Action posts GET /posts(.:format) posts#index POST /posts(.:format) posts#create new_post GET /posts/new(.:format) posts#new @@ -288,7 +291,7 @@ It will look a little basic for now, but that's ok. We'll look at improving the ### Laying down the ground work -The first thing that you are going to need to create a new post within the application is a place to do that. A great place for that would be at `/posts/new`. With the route already defined, requests can now be made to `/posts/new` in the application. Navigate to <http://localhost:3000/posts/new> and you'll see a routing error: +The first thing that you are going to need to create a new post within the application is a place to do that. A great place for that would be at `/posts/new`. With the route already defined, requests can now be made to `/posts/new` in the application. Navigate to <http://localhost:3000/posts/new> and you'll see a routing error: ![Another routing error, uninitialized constant PostsController](images/getting_started/routing_error_no_controller.png) @@ -394,9 +397,27 @@ Edit the `form_for` line inside `app/views/posts/new.html.erb` to look like this <%= form_for :post, url: posts_path do |f| %> ``` -In this example, the `posts_path` helper is passed to the `:url` option. What Rails will do with this is that it will point the form to the `create` action of the current controller, the `PostsController`, and will send a `POST` request to that route. - -By using the `post` method rather than the `get` method, Rails will define a route that will only respond to POST methods. The POST method is the typical method used by forms all over the web. +In this example, the `posts_path` helper is passed to the `:url` option. +To see what Rails will do with this, we look back at the output of +`rake routes`: +```bash +$ rake routes + Prefix Verb URI Pattern Controller#Action + posts GET /posts(.:format) posts#index + POST /posts(.:format) posts#create + new_post GET /posts/new(.:format) posts#new +edit_post GET /posts/:id/edit(.:format) posts#edit + post GET /posts/:id(.:format) posts#show + PATCH /posts/:id(.:format) posts#update + PUT /posts/:id(.:format) posts#update + DELETE /posts/:id(.:format) posts#destroy + root / welcome#index +``` +The `posts_path` helper tells Rails to point the form +to the URI Pattern associated with the `posts` prefix; and +the form will (by default) send a `POST` request +to that route. This is associated with the +`create` action of the current controller, the `PostsController`. With the form and its associated route defined, you will be able to fill in the form and then click the submit button to begin the process of creating a new post, so go ahead and do that. When you submit the form, you should see a familiar error: @@ -532,19 +553,18 @@ and change the `create` action to look like this: ```ruby def create @post = Post.new(params[:post]) - + @post.save - redirect_to @post + redirect_to @post end ``` Here's what's going on: every Rails model can be initialized with its respective attributes, which are automatically mapped to the respective -database columns. In the first line we do just that (remember that -`params[:post]` contains the attributes we're interested in). Then, -`@post.save` is responsible for saving the model in the database. -Finally, we redirect the user to the `show` action, -which we'll define later. +database columns. In the first line we do just that +(remember that `params[:post]` contains the attributes we're interested in). +Then, `@post.save` is responsible for saving the model in the database. +Finally, we redirect the user to the `show` action, which we'll define later. TIP: As we'll see later, `@post.save` returns a boolean indicating whether the model was saved or not. @@ -553,14 +573,16 @@ whether the model was saved or not. If you submit the form again now, Rails will complain about not finding the `show` action. That's not very useful though, so let's add the -`show` action before proceeding. +`show` action before proceeding. + +First we need to add a new `route` in `config/routes.rb`. ```ruby post GET /posts/:id(.:format) posts#show ``` The special syntax `:id` tells rails that this route expects an `:id` -parameter, which in our case will be the id of the post. +parameter, which in our case will be the id of the post. As we did before, we need to add the `show` action in `app/controllers/posts_controller.rb` and its respective view. @@ -605,11 +627,16 @@ we want to accept in our controllers. In this case, we want to allow the look like this: ``` - def create - @post = Post.new(params[:post].permit(:title, :text)) +def create + @post = Post.new(post_params) - @post.save - redirect_to @post + @post.save + redirect_to @post +end + +private + def post_params + params.require(:post).permit(:title, :text) end ``` @@ -619,9 +646,14 @@ Visit <http://localhost:3000/posts/new> and give it a try! ![Show action for posts](images/getting_started/show_action_for_posts.png) +TIP: Note that `def post_params` is private. This new approach prevents an attacker from +setting the model's attributes by manipulating the hash passed to the model. +For more information, refer to +[this blog post about Strong Parameters](http://weblog.rubyonrails.org/2012/3/21/strong-parameters/). + ### Listing all posts -We still need a way to list all our posts, so let's do that. +We still need a way to list all our posts, so let's do that. We'll use a specific route from `config/routes.rb`: ```ruby @@ -714,7 +746,7 @@ TIP: In development mode (which is what you're working in by default), Rails reloads your application with every browser request, so there's no need to stop and restart the web server when a change is made. -### Allowing the update of fields +### Adding Some Validation The model file, `app/models/post.rb` is about as simple as it can get: @@ -729,8 +761,6 @@ your Rails models for free, including basic database CRUD (Create, Read, Update, Destroy) operations, data validation, as well as sophisticated search support and the ability to relate multiple models to one another. -### Adding Some Validation - Rails includes methods to help you validate the data that you send to models. Open the `app/models/post.rb` file and edit it: @@ -742,7 +772,7 @@ 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, +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) @@ -760,14 +790,19 @@ def new end def create - @post = Post.new(params[:post].permit(:title, :text)) + @post = Post.new(post_params) if @post.save - redirect_to @post + redirect_to @post else render 'new' end end + +private + def post_params + params.require(:post).permit(:title, :text) + end ``` The `new` action is now creating a new instance variable called `@post`, and @@ -786,7 +821,7 @@ something went wrong. To do that, you'll modify ```html+erb <%= form_for :post, url: posts_path do |f| %> <% if @post.errors.any? %> - <div id="errorExplanation"> + <div id="error_explanation"> <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2> <ul> @@ -855,7 +890,7 @@ it look as follows: <%= form_for :post, url: post_path(@post.id), method: :patch do |f| %> <% if @post.errors.any? %> - <div id="errorExplanation"> + <div id="error_explanation"> <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2> <ul> @@ -898,12 +933,17 @@ Next we need to create the `update` action in `app/controllers/posts_controller. def update @post = Post.find(params[:id]) - if @post.update(params[:post].permit(:title, :text)) + if @post.update(post_params) redirect_to @post else render 'edit' end end + +private + def post_params + params.require(:post).permit(:title, :text) + end ``` The new method, `update`, is used when you want to update a record @@ -911,6 +951,8 @@ that already exists, and it accepts a hash containing the attributes that you want to update. As before, if there was an error updating the post we want to show the form back to the user. +We reuse the `post_params` method that we defined earlier for the create action. + TIP: You don't need to pass all attributes to `update`. For example, if you'd call `@post.update(title: 'A new title')` Rails would only update the `title` attribute, leaving all other @@ -925,15 +967,14 @@ appear next to the "Show" link: <tr> <th>Title</th> <th>Text</th> - <th></th> - <th></th> + <th colspan="2"></th> </tr> <% @posts.each do |post| %> <tr> <td><%= post.title %></td> <td><%= post.text %></td> - <td><%= link_to 'Show', post_path(post) %></td> + <td><%= link_to 'Show', post %></td> <td><%= link_to 'Edit', edit_post_path(post) %></td> </tr> <% end %> @@ -971,7 +1012,7 @@ content: ```html+erb <%= form_for @post do |f| %> <% if @post.errors.any? %> - <div id="errorExplanation"> + <div id="error_explanation"> <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2> <ul> @@ -998,9 +1039,14 @@ content: ``` Everything except for the `form_for` declaration remained the same. -How `form_for` can figure out the right `action` and `method` attributes -when building the form will be explained in just a moment. For now, let's update the -`app/views/posts/new.html.erb` view to use this new partial, rewriting it +The reason we can use this shorter, simpler `form_for` declaration +to stand in for either of the other forms is that `@post` is a *resource* +corresponding to a full set of RESTful routes, and Rails is able to infer +which URI and method to use. +For more information about this use of `form_for`, see +[Resource-oriented style](//api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for-label-Resource-oriented+style). + +Now, let's update the `app/views/posts/new.html.erb` view to use this new partial, rewriting it completely: ```html+erb @@ -1056,7 +1102,7 @@ You can call `destroy` on Active Record objects when you want to delete them from the database. Note that we don't need to add a view for this action since we're redirecting to the `index` action. -Finally, add a 'destroy' link to your `index` action template +Finally, add a 'Destroy' link to your `index` action template (`app/views/posts/index.html.erb`) to wrap everything together. @@ -1066,9 +1112,7 @@ together. <tr> <th>Title</th> <th>Text</th> - <th></th> - <th></th> - <th></th> + <th colspan="3"></th> </tr> <% @posts.each do |post| %> @@ -1084,8 +1128,8 @@ together. </table> ``` -Here we're using `link_to` in a different way. We pass the named route as the first argument, -and then the final two keys as another argument. The `:method` and `:'data-confirm'` +Here we're using `link_to` in a different way. We pass the named route as the second argument, +and then the options as another argument. The `:method` and `:'data-confirm'` options are used as HTML5 attributes so that when the link is clicked, Rails will first show a confirm dialog to the user, and then submit the link with method `delete`. This is done via the JavaScript file `jquery_ujs` which is automatically included @@ -1095,7 +1139,7 @@ generated the application. Without this file, the confirmation dialog box wouldn ![Confirm Dialog](images/getting_started/confirm_dialog.png) Congratulations, you can now create, show, list, update and destroy -posts. +posts. TIP: In general, Rails encourages the use of resources objects in place of declaring routes manually. @@ -1273,11 +1317,11 @@ So first, we'll wire up the Post show template <h2>Add a comment:</h2> <%= form_for([@post, @post.comments.build]) do |f| %> <p> - <%= f.label :commenter %><br /> + <%= f.label :commenter %><br> <%= f.text_field :commenter %> </p> <p> - <%= f.label :body %><br /> + <%= f.label :body %><br> <%= f.text_area :body %> </p> <p> @@ -1299,9 +1343,14 @@ 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].permit(:commenter, :body)) + @comment = @post.comments.create(comment_params) redirect_to post_path(@post) end + + private + def comment_params + params.require(:comment).permit(:commenter, :body) + end end ``` @@ -1348,11 +1397,11 @@ template. This is where we want the comment to show, so let's add that to the <h2>Add a comment:</h2> <%= form_for([@post, @post.comments.build]) do |f| %> <p> - <%= f.label :commenter %><br /> + <%= f.label :commenter %><br> <%= f.text_field :commenter %> </p> <p> - <%= f.label :body %><br /> + <%= f.label :body %><br> <%= f.text_area :body %> </p> <p> @@ -1414,11 +1463,11 @@ following: <h2>Add a comment:</h2> <%= form_for([@post, @post.comments.build]) do |f| %> <p> - <%= f.label :commenter %><br /> + <%= f.label :commenter %><br> <%= f.text_field :commenter %> </p> <p> - <%= f.label :body %><br /> + <%= f.label :body %><br> <%= f.text_area :body %> </p> <p> @@ -1444,11 +1493,11 @@ create a file `app/views/comments/_form.html.erb` containing: ```html+erb <%= form_for([@post, @post.comments.build]) do |f| %> <p> - <%= f.label :commenter %><br /> + <%= f.label :commenter %><br> <%= f.text_field :commenter %> </p> <p> - <%= f.label :body %><br /> + <%= f.label :body %><br> <%= f.text_area :body %> </p> <p> @@ -1523,10 +1572,9 @@ controller (`app/controllers/comments_controller.rb`): ```ruby class CommentsController < ApplicationController - def create @post = Post.find(params[:post_id]) - @comment = @post.comments.create(params[:comment]) + @comment = @post.comments.create(comment_params) redirect_to post_path(@post) end @@ -1537,6 +1585,10 @@ class CommentsController < ApplicationController redirect_to post_path(@post) end + private + def comment_params + params.require(:comment).permit(:commenter, :body) + end end ``` @@ -1655,7 +1707,7 @@ Two very common sources of data that are not UTF-8: in the browser. This also applies to your i18n translation files. Most editors that do not already default to UTF-8 (such as some versions of Dreamweaver) offer a way to change the default to UTF-8. Do so. -* Your database. Rails defaults to converting data from your database into UTF-8 at +* Your database: Rails defaults to converting data from your database into UTF-8 at the boundary. However, if your database is not using UTF-8 internally, it may not be able to store all characters that your users enter. For instance, if your database is using Latin-1 internally, and your user enters a Russian, Hebrew, or Japanese diff --git a/guides/source/i18n.md b/guides/source/i18n.md index 65a85efa69..a20e82931d 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -21,6 +21,11 @@ This guide will walk you through the I18n API and contains a tutorial on how to After reading this guide, you will know: +* How I18n works in Ruby on Rails +* How to correctly use I18n into a RESTful application in various ways +* How to use I18n to translate ActiveRecord errors or ActionMailer E-mail subjects +* Some other tools to go further with the translation process of your application + -------------------------------------------------------------------------------- NOTE: The Ruby I18n framework provides you with all necessary means for internationalization/localization of your Rails application. You may, however, use any of various plugins and extensions available, which add additional functionality or features. See the Rails [I18n Wiki](http://rails-i18n.org/wiki) for more information. @@ -132,7 +137,7 @@ If you want to translate your Rails application to a **single language other tha However, you would probably like to **provide support for more locales** in your application. In such case, you need to set and pass the locale between requests. -WARNING: You may be tempted to store the chosen locale in a _session_ or a <em>cookie</em>, however **do not do this**. The locale should be transparent and a part of the URL. This way you won't break people's basic assumptions about the web itself: if you send a URL to a friend, they should see the same page and content as you. A fancy word for this would be that you're being [<em>RESTful</em>](http://en.wikipedia.org/wiki/Representational_State_Transfer. Read more about the RESTful approach in [Stefan Tilkov's articles](http://www.infoq.com/articles/rest-introduction). Sometimes there are exceptions to this rule and those are discussed below. +WARNING: You may be tempted to store the chosen locale in a _session_ or a <em>cookie</em>, however **do not do this**. The locale should be transparent and a part of the URL. This way you won't break people's basic assumptions about the web itself: if you send a URL to a friend, they should see the same page and content as you. A fancy word for this would be that you're being [<em>RESTful</em>](http://en.wikipedia.org/wiki/Representational_State_Transfer). Read more about the RESTful approach in [Stefan Tilkov's articles](http://www.infoq.com/articles/rest-introduction). Sometimes there are exceptions to this rule and those are discussed below. The _setting part_ is easy. You can set the locale in a `before_action` in the `ApplicationController` like this: @@ -174,7 +179,7 @@ end # in your /etc/hosts file to try this out locally def extract_locale_from_tld parsed_locale = request.host.split('.').last - I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil + I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil end ``` @@ -253,7 +258,7 @@ You would probably need to map URLs like these: ```ruby # config/routes.rb -match '/:locale' => 'dashboard#index' +get '/:locale' => 'dashboard#index' ``` Do take special care about the **order of your routes**, so this route declaration does not "eat" other ones. (You may want to add it directly before the `root :to` declaration.) @@ -283,7 +288,7 @@ def extract_locale_from_accept_language_header end ``` -Of course, in a production environment you would need much more robust code, and could use a plugin such as Iain Hecker's [http_accept_language](https://github.com/iain/http_accept_language/tree/master or even Rack middleware such as Ryan Tomayko's [locale](https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/locale.rb). +Of course, in a production environment you would need much more robust code, and could use a plugin such as Iain Hecker's [http_accept_language](https://github.com/iain/http_accept_language/tree/master) or even Rack middleware such as Ryan Tomayko's [locale](https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/locale.rb). #### Using GeoIP (or Similar) Database @@ -865,11 +870,11 @@ Rails uses fixed strings and other localizations, such as format strings and oth #### Action View Helper Methods -* `distance_of_time_in_words` translates and pluralizes its result and interpolates the number of seconds, minutes, hours, and so on. See [datetime.distance_in_words](https://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L51) translations. +* `distance_of_time_in_words` translates and pluralizes its result and interpolates the number of seconds, minutes, hours, and so on. See [datetime.distance_in_words](https://github.com/rails/rails/blob/master/actionview/lib/action_view/locale/en.yml#L51) translations. -* `datetime_select` and `select_month` use translated month names for populating the resulting select tag. See [date.month_names](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L15) for translations. `datetime_select` also looks up the order option from [date.order](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L18) (unless you pass the option explicitly). All date selection helpers translate the prompt using the translations in the [datetime.prompts](https://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L83) scope if applicable. +* `datetime_select` and `select_month` use translated month names for populating the resulting select tag. See [date.month_names](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L15) for translations. `datetime_select` also looks up the order option from [date.order](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L18) (unless you pass the option explicitly). All date selection helpers translate the prompt using the translations in the [datetime.prompts](https://github.com/rails/rails/blob/master/actionview/lib/action_view/locale/en.yml#L83) scope if applicable. -* The `number_to_currency`, `number_with_precision`, `number_to_percentage`, `number_with_delimiter`, and `number_to_human_size` helpers use the number format settings located in the [number](https://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L2) scope. +* The `number_to_currency`, `number_with_precision`, `number_to_percentage`, `number_with_delimiter`, and `number_to_human_size` helpers use the number format settings located in the [number](https://github.com/rails/rails/blob/master/actionview/lib/action_view/locale/en.yml#L2) scope. #### Active Model Methods diff --git a/guides/source/index.html.erb b/guides/source/index.html.erb index a8e4525c67..57c224c165 100644 --- a/guides/source/index.html.erb +++ b/guides/source/index.html.erb @@ -19,7 +19,7 @@ Ruby on Rails Guides <h3><%= section['name'] %></h3> <dl> <% section['documents'].each do |document| %> - <%= guide(document['name'], document['url'], :work_in_progress => document['work_in_progress']) do %> + <%= guide(document['name'], document['url'], work_in_progress: document['work_in_progress']) do %> <p><%= document['description'] %></p> <% end %> <% end %> diff --git a/guides/source/initialization.md b/guides/source/initialization.md index 9fcd530183..26259408b4 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -7,14 +7,17 @@ as of Rails 4. It is an extremely in-depth guide and recommended for advanced Ra After reading this guide, you will know: * How to use `rails server`. +* The timeline of Rails' initialization sequence. +* Where different files are required by the boot sequence. +* How the Rails::Server interface is defined and used. -------------------------------------------------------------------------------- This guide goes through every method call that is required to boot up the Ruby on Rails stack for a default Rails 4 application, explaining each part in detail along the way. For this -guide, we will be focusing on what happens when you execute +rails -server+ to boot your app. +guide, we will be focusing on what happens when you execute `rails server` +to boot your app. NOTE: Paths in this guide are relative to Rails or a Rails application unless otherwise specified. @@ -26,7 +29,7 @@ quickly. Launch! ------- -Now we finally boot and initialize the app. It all starts with your app's +Let's start to boot and initialize the app. It all begins with your app's `bin/rails` executable. A Rails application is usually started by running `rails console` or `rails server`. @@ -36,8 +39,8 @@ This file is as follows: ```ruby #!/usr/bin/env ruby -APP_PATH = File.expand_path('../../config/application', __FILE__) -require File.expand_path('../../config/boot', __FILE__) +APP_PATH = File.expand_path('../../config/application', __FILE__) +require File.expand_path('../../config/boot', __FILE__) require 'rails/commands' ``` @@ -57,7 +60,8 @@ require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) In a standard Rails application, there's a `Gemfile` which declares all dependencies of the application. `config/boot.rb` sets `ENV['BUNDLE_GEMFILE']` to the location of this file. If the Gemfile -exists, `bundler/setup` is then required. +exists, then `bundler/setup` is required. The require is used by Bundler to +configure the load path for your Gemfile's dependencies. A standard Rails application depends on several gems, specifically: @@ -251,9 +255,9 @@ set earlier) is required. ### `config/application` -When `require APP_PATH` is executed, `config/application.rb` is loaded. -This file exists in your app and it's free for you to change based -on your needs. +When `require APP_PATH` is executed, `config/application.rb` is loaded (recall +that `APP_PATH` is defined in `bin/rails`). This file exists in your application +and it's free for you to change based on your needs. ### `Rails::Server#start` @@ -346,7 +350,7 @@ end The interesting part for a Rails app is the last line, `server.run`. Here we encounter the `wrapped_app` method again, which this time we're going to explore more (even though it was executed before, and -thus memorized by now). +thus memoized by now). ```ruby @wrapped_app ||= build_app app @@ -373,7 +377,7 @@ The `options[:config]` value defaults to `config.ru` which contains this: ```ruby # This file is used by Rack-based servers to start the application. -require ::File.expand_path('../config/environment', __FILE__) +require ::File.expand_path('../config/environment', __FILE__) run <%= app_const %> ``` @@ -388,7 +392,7 @@ app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app", The `initialize` method of `Rack::Builder` will take the block here and execute it within an instance of `Rack::Builder`. This is where the majority of the initialization process of Rails happens. The `require` line for `config/environment.rb` in `config.ru` is the first to run: ```ruby -require ::File.expand_path('../config/environment', __FILE__) +require ::File.expand_path('../config/environment', __FILE__) ``` ### `config/environment.rb` @@ -443,7 +447,9 @@ I18n and Rails configuration are all being defined here. ### Back to `config/environment.rb` -When `config/application.rb` has finished loading Rails, and defined +The rest of `config/application.rb` defines the configuration for the +`Rails::Application` which will be used once the application is fully +initialized. When `config/application.rb` has finished loading Rails and defined the application namespace, we go back to `config/environment.rb`, where the application is initialized. For example, if the application was called `Blog`, here we would find `Blog::Application.initialize!`, which is @@ -471,6 +477,13 @@ traverses all the class ancestors looking for an `initializers` method, sorting them and running them. For example, the `Engine` class will make all the engines available by providing the `initializers` method. +The `Rails::Application` class, as defined in `railties/lib/rails/application.rb` +defines `bootstrap`, `railtie`, and `finisher` initializers. The `bootstrap` initializers +prepare the application (like initializing the logger) while the `finisher` +initializers (like building the middleware stack) are run last. The `railtie` +initializers are the initializers which have been defined on the `Rails::Application` +itself and are run between the `bootstrap` and `finishers`. + After this is done we go back to `Rack::Server` ### Rack: lib/rack/server.rb @@ -546,7 +559,7 @@ def self.run(app, options={}) else server.register('/', Rack::Handler::Mongrel.new(app)) end - yield server if block_given? + yield server if block_given? server.run.join end ``` diff --git a/guides/source/kindle/toc.html.erb b/guides/source/kindle/toc.html.erb index e013797dee..f310edd3a1 100644 --- a/guides/source/kindle/toc.html.erb +++ b/guides/source/kindle/toc.html.erb @@ -20,5 +20,5 @@ Ruby on Rails Guides <ul> <li><a href="credits.html">Credits</a></li> <li><a href="copyright.html">Copyright & License</a></li> -<ul> +</ul> </div> diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb index ef2bdf0753..71d3c5638b 100644 --- a/guides/source/layout.html.erb +++ b/guides/source/layout.html.erb @@ -48,7 +48,7 @@ <ul class="nav"> <li><a class="nav-item" href="index.html">Home</a></li> <li class="guides-index guides-index-large"> - <a href="index.html" onclick="guideMenu(); return false;" id="guidesMenu" class="guides-index-item nav-item">Guides Index</a> + <a href="index.html" id="guidesMenu" class="guides-index-item nav-item">Guides Index</a> <div id="guides" class="clearfix" style="display: none;"> <hr /> <% ['L', 'R'].each do |position| %> @@ -101,10 +101,12 @@ You're encouraged to help improve the quality of this guide. </p> <p> - If you see any typos or factual errors you are confident to - patch, please clone <%= link_to 'docrails', 'https://github.com/rails/docrails' %> - and push the change yourself. That branch of Rails has public write access. - Commits are still reviewed, but that happens after you've submitted your + If you see any typos or factual errors that you are confident to fix, + you can push the fix to <%= link_to 'docrails', 'https://github.com/rails/docrails' %> (Ask + the <%= link_to 'Rails core team', 'http://rubyonrails.org/core' %> for push access). + If you choose to open a pull request, please do it in <%= link_to 'Rails', 'https://github.com/rails/rails' %> + and not in the <%= link_to 'docrails', 'https://github.com/rails/docrails' %> repository. + Commits made to docrails are still reviewed, but that happens after you've submitted your contribution. <%= link_to 'docrails', 'https://github.com/rails/docrails' %> is cross-merged with master periodically. </p> @@ -141,7 +143,7 @@ <script type="text/javascript" src="javascripts/syntaxhighlighter/shBrushSql.js"></script> <script type="text/javascript" src="javascripts/syntaxhighlighter/shBrushPlain.js"></script> <script type="text/javascript"> - SyntaxHighlighter.all() + SyntaxHighlighter.all(); $(guidesIndex.bind); </script> </body> diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index 1ab841b137..5b6e5387ff 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -88,7 +88,7 @@ If we want to display the properties of all the books in our view, we can do so <% end %> </table> -<br /> +<br> <%= link_to "New book", new_book_path %> ``` @@ -592,7 +592,7 @@ def index end def show - @book = Book.find_by_id(params[:id]) + @book = Book.find_by(id: params[:id]) if @book.nil? render action: "index" end @@ -607,7 +607,7 @@ def index end def show - @book = Book.find_by_id(params[:id]) + @book = Book.find_by(id: params[:id]) if @book.nil? redirect_to action: :index end @@ -626,10 +626,10 @@ def index end def show - @book = Book.find_by_id(params[:id]) + @book = Book.find_by(id: params[:id]) if @book.nil? @books = Book.all - flash[:alert] = "Your book was not found" + flash.now[:alert] = "Your book was not found" render "index" end end @@ -1026,7 +1026,7 @@ You can also pass local variables into partials, making them even more powerful ```html+erb <%= form_for(zone) do |f| %> <p> - <b>Zone name</b><br /> + <b>Zone name</b><br> <%= f.text_field :name %> </p> <p> diff --git a/guides/source/migrations.md b/guides/source/migrations.md index 550f8fdc3c..e6d1e71f5e 100644 --- a/guides/source/migrations.md +++ b/guides/source/migrations.md @@ -314,7 +314,7 @@ will produce a migration that looks like this class AddDetailsToProducts < ActiveRecord::Migration def change add_column :products, :price, precision: 5, scale: 2 - add_reference :products, :user, polymorphic: true, index: true + add_reference :products, :supplier, polymorphic: true, index: true end end ``` @@ -366,34 +366,32 @@ create_join_table :products, :categories which creates a `categories_products` table with two columns called `category_id` and `product_id`. These columns have the option `:null` set to -`false` by default. - -You can pass the option `:table_name` with you want to customize the table -name. For example, +`false` by default. This can be overridden by specifying the `:column_options` +option. ```ruby -create_join_table :products, :categories, table_name: :categorization +create_join_table :products, :categories, column_options: {null: true} ``` -will create a `categorization` table. +will create the `product_id` and `category_id` with the `:null` option as +`true`. -By default, `create_join_table` will create two columns with no options, but -you can specify these options using the `:column_options` option. For example, +You can pass the option `:table_name` when you want to customize the table +name. For example, ```ruby -create_join_table :products, :categories, column_options: {null: true} +create_join_table :products, :categories, table_name: :categorization ``` -will create the `product_id` and `category_id` with the `:null` option as -`true`. +will create a `categorization` table. `create_join_table` also accepts a block, which you can use to add indices (which are not created by default) or additional columns: ```ruby create_join_table :products, :categories do |t| - t.index :products - t.index :categories + t.index :product_id + t.index :category_id end ``` @@ -448,7 +446,7 @@ definitions: * `create_table` * `create_join_table` * `drop_table` (must supply a block) -* `drop_join_table` (must supply a block) +* `drop_join_table` (must supply a block) * `remove_timestamps` * `rename_column` * `rename_index` @@ -843,7 +841,6 @@ class AddFlagToProduct < ActiveRecord::Migration reversible do |dir| dir.up { Product.update_all flag: false } end - Product.update_all flag: false end end ``` @@ -877,7 +874,7 @@ end # app/models/product.rb class Product < ActiveRecord::Base - validates :flag, inclusion: { in: [true, false] } + validates :flag, inclusion: { in: [true, false] } validates :fuzz, presence: true end ``` diff --git a/guides/source/plugins.md b/guides/source/plugins.md index 695f25f8a9..9077e424c8 100644 --- a/guides/source/plugins.md +++ b/guides/source/plugins.md @@ -68,7 +68,7 @@ In this example you will add a method to String named `to_squawk`. To begin, cre require 'test_helper' -class CoreExtTest < Test::Unit::TestCase +class CoreExtTest < ActiveSupport::TestCase def test_to_squawk_prepends_the_word_squawk assert_equal "squawk! Hello World", "Hello World".to_squawk end @@ -136,7 +136,7 @@ To begin, set up your files so that you have: require 'test_helper' -class ActsAsYaffleTest < Test::Unit::TestCase +class ActsAsYaffleTest < ActiveSupport::TestCase end ``` @@ -173,7 +173,7 @@ To start out, write a failing test that shows the behavior you'd like: require 'test_helper' -class ActsAsYaffleTest < Test::Unit::TestCase +class ActsAsYaffleTest < ActiveSupport::TestCase def test_a_hickwalls_yaffle_text_field_should_be_last_squawk assert_equal "last_squawk", Hickwall.yaffle_text_field @@ -321,7 +321,7 @@ To start out, write a failing test that shows the behavior you'd like: # yaffle/test/acts_as_yaffle_test.rb require 'test_helper' -class ActsAsYaffleTest < Test::Unit::TestCase +class ActsAsYaffleTest < ActiveSupport::TestCase def test_a_hickwalls_yaffle_text_field_should_be_last_squawk assert_equal "last_squawk", Hickwall.yaffle_text_field diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md index b548eaede8..0c70f379be 100644 --- a/guides/source/rails_application_templates.md +++ b/guides/source/rails_application_templates.md @@ -91,7 +91,7 @@ Adds a line inside the `Application` class for `config/application.rb`. If `options[:env]` is specified, the line is appended to the corresponding file in `config/environments`. ```ruby -environment 'config.action_mailer.default_url_options = {host: 'http://yourwebsite.example.com'}, env: 'production' +environment 'config.action_mailer.default_url_options = {host: "http://yourwebsite.example.com"}', env: 'production' ``` A block can be used in place of the `data` argument. @@ -227,3 +227,22 @@ git :init git add: "." git commit: "-a -m 'Initial commit'" ``` + +Advanced Usage +-------------- + +The application template is evaluated in the context of a +`Rails::Generators::AppGenerator` instance. It uses the `apply` action +provided by +[Thor](https://github.com/erikhuda/thor/blob/master/lib/thor/actions.rb#L207). +This means you can extend and change the instance to match your needs. + +For example by overwriting the `source_paths` method to contain the +location of your template. Now methods like `copy_file` will accept +relative paths to your template's location. + +```ruby +def source_paths + [File.expand_path(File.dirname(__FILE__))] +end +``` diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index de8f3f483f..642c70fd9d 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -5,7 +5,6 @@ This guide covers Rails integration with Rack and interfacing with other Rack co After reading this guide, you will know: -* How to create Rails Metal applications. * How to use Rack Middlewares in your Rails applications. * Action Pack's internal Middleware stack. * How to define a custom Middleware stack. @@ -82,7 +81,7 @@ To use `rackup` instead of Rails' `rails server`, you can put the following insi ```ruby # Rails.root/config.ru -require ::File.expand_path('../config/environment', __FILE__) +require ::File.expand_path('../config/environment', __FILE__) use Rack::Debugger use Rack::ContentLength @@ -119,6 +118,7 @@ $ rake middleware For a freshly generated Rails application, this might produce something like: ```ruby +use Rack::Sendfile use ActionDispatch::Static use Rack::Lock use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x000000029a0838> @@ -131,6 +131,7 @@ use ActionDispatch::DebugExceptions use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks +use ActiveRecord::Migration::CheckPending use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies @@ -272,6 +273,10 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol * Runs the prepare callbacks before serving the request. + **`ActiveRecord::Migration::CheckPending`** + +* Checks pending migrations and raises `ActiveRecord::PendingMigrationError` if any migrations are pending. + **`ActiveRecord::ConnectionAdapters::ConnectionManagement`** * Cleans active connections after each request, unless the `rack.test` key in the request environment is set to `true`. @@ -321,7 +326,7 @@ The following shows how to replace use `Rack::Builder` instead of the Rails supp config.middleware.clear ``` -<br /> +<br> <strong>Add a `config.ru` file to `Rails.root`</strong> ```ruby diff --git a/guides/source/routing.md b/guides/source/routing.md index c26a827172..76c4c25108 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -36,7 +36,7 @@ the request is dispatched to the `patients` controller's `show` action with `{ i ### Generating Paths and URLs from Code -You can also generate paths and URLs. If the route above is modified to be: +You can also generate paths and URLs. If the route above is modified to be: ```ruby get '/patients/:id', to: 'patients#show', as: 'patient' @@ -171,6 +171,12 @@ A singular resourceful route generates these helpers: As with plural resources, the same helpers ending in `_url` will also include the host, port and path prefix. +WARNING: A [long-standing bug](https://github.com/rails/rails/issues/1769) prevents `form_for` from working automatically with singular resources. As a workaround, specify the URL for the form directly, like so: + +```ruby +form_for @geocoder, url: geocoder_path do |f| +``` + ### Controller Namespaces and Routing You may wish to organize groups of controllers under a namespace. Most commonly, you might group a number of administrative controllers under an `Admin::` namespace. You would place these controllers under the `app/controllers/admin` directory, and you can group them together in your router: @@ -767,11 +773,11 @@ You can also reuse dynamic segments from the match in the path to redirect to: get '/stories/:name', to: redirect('/posts/%{name}') ``` -You can also provide a block to redirect, which receives the params and the request object: +You can also provide a block to redirect, which receives the symbolized path parameters and the request object: ```ruby -get '/stories/:name', to: redirect {|params, req| "/posts/#{params[:name].pluralize}" } -get '/stories', to: redirect {|p, req| "/posts/#{req.subdomain}" } +get '/stories/:name', to: redirect {|path_params, req| "/posts/#{path_params[:name].pluralize}" } +get '/stories', to: redirect {|path_params, req| "/posts/#{req.subdomain}" } ``` Please note that this redirection is a 301 "Moved Permanently" redirect. Keep in mind that some web browsers or proxy servers will cache this type of redirect, making the old page inaccessible. @@ -803,7 +809,7 @@ You should put the `root` route at the top of the file, because it is the most p NOTE: The `root` route only routes `GET` requests to the action. -You can also use root inside namespaces and scopes as well. For example: +You can also use root inside namespaces and scopes as well. For example: ```ruby namespace :admin do diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md index d5d1ee0a38..5564b0648b 100644 --- a/guides/source/ruby_on_rails_guides_guidelines.md +++ b/guides/source/ruby_on_rails_guides_guidelines.md @@ -63,7 +63,7 @@ Those guidelines apply also to guides. HTML Guides ----------- -Before generating the guides, make sure that you have the latest version of Bundler installed on your system. As of this writing, you must install Bundler 1.3.5 on your device. +Before generating the guides, make sure that you have the latest version of Bundler installed on your system. As of this writing, you must install Bundler 1.3.5 on your device. To install the latest version of Bundler, simply run the `gem install bundler` command diff --git a/guides/source/security.md b/guides/source/security.md index f04129acdb..ad0546810d 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -346,13 +346,13 @@ Intranet and administration interfaces are popular attack targets, because they In 2007 there was the first tailor-made trojan which stole information from an Intranet, namely the "Monster for employers" web site of Monster.com, an online recruitment web application. Tailor-made Trojans are very rare, so far, and the risk is quite low, but it is certainly a possibility and an example of how the security of the client host is important, too. However, the highest threat to Intranet and Admin applications are XSS and CSRF.
-**XSS** If your application re-displays malicious user input from the extranet, the application will be vulnerable to XSS. User names, comments, spam reports, order addresses are just a few uncommon examples, where there can be XSS. +**XSS** If your application re-displays malicious user input from the extranet, the application will be vulnerable to XSS. User names, comments, spam reports, order addresses are just a few uncommon examples, where there can be XSS. Having one single place in the admin interface or Intranet, where the input has not been sanitized, makes the entire application vulnerable. Possible exploits include stealing the privileged administrator's cookie, injecting an iframe to steal the administrator's password or installing malicious software through browser security holes to take over the administrator's computer. Refer to the Injection section for countermeasures against XSS. It is _recommended to use the SafeErb plugin_ also in an Intranet or administration interface. -**CSRF** Cross-Site Reference Forgery (CSRF) is a gigantic attack method, it allows the attacker to do everything the administrator or Intranet user may do. As you have already seen above how CSRF works, here are a few examples of what attackers can do in the Intranet or admin interface. +**CSRF** Cross-Site Reference Forgery (CSRF) is a gigantic attack method, it allows the attacker to do everything the administrator or Intranet user may do. As you have already seen above how CSRF works, here are a few examples of what attackers can do in the Intranet or admin interface. A real-world example is a [router reconfiguration by CSRF](http://www.h-online.com/security/Symantec-reports-first-active-attack-on-a-DSL-router--/news/102352). The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had his credentials stolen. diff --git a/guides/source/testing.md b/guides/source/testing.md index b02d0b663c..62c9835fa4 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -64,7 +64,7 @@ YAML-formatted fixtures are a very human-friendly way to describe your sample da Here's a sample YAML fixture file: ```yaml -# lo & behold! I am a YAML comment! +# lo & behold! I am a YAML comment! david: name: David Heinemeier Hansson birthday: 1979-10-15 @@ -92,7 +92,7 @@ user_<%= n %>: #### Fixtures in Action -Rails by default automatically loads all fixtures from the `test/fixtures` folder for your unit and functional test. Loading involves three steps: +Rails by default automatically loads all fixtures from the `test/fixtures` folder for your models and controllers test. Loading involves three steps: * Remove any existing data from the table corresponding to the fixture * Load the fixture data into the table @@ -116,7 +116,7 @@ email(david.girlfriend.email, david.location_tonight) Unit Testing your Models ------------------------ -In Rails, unit tests are what you write to test your models. +In Rails, models tests are what you write to test your models. For this guide we will be using Rails _scaffolding_. It will create the model, a migration, controller and views for the new resource in a single operation. It will also create a full test suite following Rails best practices. I will be using examples from this generated code and will be supplementing it with additional examples where necessary. @@ -159,7 +159,7 @@ class PostTest < ActiveSupport::TestCase The `PostTest` class defines a _test case_ because it inherits from `ActiveSupport::TestCase`. `PostTest` thus has all the methods available from `ActiveSupport::TestCase`. You'll see those methods a little later in this guide. -Any method defined within a class inherited from `MiniTest::Unit::TestCase` +Any method defined within a class inherited from `MiniTest::Unit::TestCase` (which is the superclass of `ActiveSupport::TestCase`) that begins with `test` (case sensitive) is simply called a test. So, `test_password`, `test_valid_password` and `testValidPassword` all are legal test names and are run automatically when the test case is run. Rails adds a `test` method that takes a test name and a block. It generates a normal `MiniTest::Unit` test with method names prefixed with `test_`. So, @@ -396,7 +396,7 @@ Rails adds some custom assertions of its own to the `test/unit` framework: | `assert_no_difference(expressions, message = nil, &block)` | Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.| | `assert_recognizes(expected_options, path, extras={}, message=nil)` | Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.| | `assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)` | Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.| -| `assert_response(type, message = nil)` | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range| +| `assert_response(type, message = nil)` | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range| | `assert_redirected_to(options = {}, message=nil)` | Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on.| | `assert_template(expected = nil, message=nil)` | Asserts that the request was rendered with the appropriate template file.| @@ -622,11 +622,11 @@ The `assert_select` assertion is quite powerful. For more advanced usage, refer There are more assertions that are primarily used in testing views: -| Assertion | Purpose | -| ---------------------------------------------------------- | ------- | -| `assert_select_email` | Allows you to make assertions on the body of an e-mail. | -| `assert_select_encoded` | Allows you to make assertions on encoded HTML. It does this by un-encoding the contents of each element and then calling the block with all the un-encoded elements.| -| `css_select(selector)` or `css_select(element, selector)` | Returns an array of all the elements selected by the _selector_. In the second variant it first matches the base _element_ and tries to match the _selector_ expression on any of its children. If there are no matches both variants return an empty array.| +| Assertion | Purpose | +| --------------------------------------------------------- | ------- | +| `assert_select_email` | Allows you to make assertions on the body of an e-mail. | +| `assert_select_encoded` | Allows you to make assertions on encoded HTML. It does this by un-encoding the contents of each element and then calling the block with all the un-encoded elements.| +| `css_select(selector)` or `css_select(element, selector)` | Returns an array of all the elements selected by the _selector_. In the second variant it first matches the base _element_ and tries to match the _selector_ expression on any of its children. If there are no matches both variants return an empty array.| Here's an example of using `assert_select_email`: @@ -767,8 +767,8 @@ Rake Tasks for Running your Tests You don't need to set up and run your tests by hand on a test-by-test basis. Rails comes with a number of commands to help in testing. The table below lists all commands that come along in the default Rakefile when you initiate a Rails project. -| Tasks | Description | -| ------------------------ | ----------- | +| Tasks | Description | +| ----------------------- | ----------- | | `rake test` | Runs all unit, functional and integration tests. You can also simply run `rake test` as Rails will run all the tests by default| | `rake test:controllers` | Runs all the controller tests from `test/controllers`| | `rake test:functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional`| @@ -778,13 +778,6 @@ You don't need to set up and run your tests by hand on a test-by-test basis. Rai | `rake test:models` | Runs all the model tests from `test/models`| | `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit`| -There're also some test commands which you can initiate by running rake tasks: - -| Tasks | Description | -| ------------------------ | ----------- | -| `rake test` | Runs all unit, functional and integration tests. You can also simply run `rake` as the _test_ target is the default.| -| `rake test:recent` | Tests recent changes| -| `rake test:uncommitted` | Runs all the tests which are uncommitted. Supports Subversion and Git| Brief Note About `MiniTest` ----------------------------- diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 5a1594102a..656e6d9956 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -61,7 +61,7 @@ end ```ruby class UsersController < ApplicationController def update_name - # Change needed; form_for will try to use a non-existant PATCH route. + # Change needed; form_for will try to use a non-existent PATCH route. end end ``` @@ -69,6 +69,10 @@ end If the action is not being used in a public API and you are free to change the HTTP method, you can update your route to use `patch` instead of `put`: +`PUT` requests to `/users/:id` in Rails 4 get routed to `update` as they are +today. So, if you have an API that gets real PUT requests it is going to work. +The router also routes `PATCH` requests to `/users/:id` to the `update` action. + ```ruby resources :users do patch :update_name, on: :member @@ -127,7 +131,15 @@ The following changes are meant for upgrading your application to Rails 4.0. ### Gemfile -Rails 4.0 removed the `assets` group from Gemfile. You'd need to remove that line from your Gemfile when upgrading. +Rails 4.0 removed the `assets` group from Gemfile. You'd need to remove that +line from your Gemfile when upgrading. You should also update your application +file (in `config/application.rb`): + +```ruby +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(:default, Rails.env) +``` ### vendor/plugins @@ -139,11 +151,16 @@ Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must rep * The `delete` method in collection associations can now receive `Fixnum` or `String` arguments as record ids, besides records, pretty much like the `destroy` method does. Previously it raised `ActiveRecord::AssociationTypeMismatch` for such arguments. From Rails 4.0 on `delete` automatically tries to find the records matching the given ids before deleting them. +* In Rails 4.0 when a column or a table is renamed the related indexes are also renamed. If you have migrations which rename the indexes, they are no longer needed. + * Rails 4.0 has changed how orders get stacked in `ActiveRecord::Relation`. In previous versions of Rails, the new order was applied after the previously defined order. But this is no longer true. Check [Active Record Query guide](active_record_querying.html#ordering) for more information. * Rails 4.0 has changed `serialized_attributes` and `attr_readonly` to class methods only. You shouldn't use instance methods since it's now deprecated. You should change them to use class methods, e.g. `self.serialized_attributes` to `self.class.serialized_attributes`. -* Rails 4.0 has removed `attr_accessible` and `attr_protected` feature in favor of Strong Parameters. You can use the [Protected Attributes gem](https://github.com/rails/protected_attributes) to a smoothly upgrade path. +* Rails 4.0 has removed `attr_accessible` and `attr_protected` feature in favor of Strong Parameters. You can use the [Protected Attributes gem](https://github.com/rails/protected_attributes) for a smooth upgrade path. + +* If you are not using Protected Attributes, you can remove any options related to +this gem such as `whitelist_attributes` or `mass_assignment_sanitizer` options. * Rails 4.0 requires that scopes use a callable object such as a Proc or lambda: @@ -190,16 +207,6 @@ If you are relying on the ability for external applications or Javascript to be * Rails 4.0 encrypts the contents of cookie-based sessions if `secret_key_base` has been set. Rails 3.x signed, but did not encrypt, the contents of cookie-based session. Signed cookies are "secure" in that they are verified to have been generated by your app and are tamper-proof. However, the contents can be viewed by end users, and encrypting the contents eliminates this caveat/concern without a significant performance penalty. -As described above, existing signed cookies generated with Rails 3.x will be transparently upgraded if you leave your existing `secret_token` in place and add the new `secret_key_base`. - -```ruby - # config/initializers/secret_token.rb - Myapp::Application.config.secret_token = 'existing secret token' - Myapp::Application.config.secret_key_base = 'new secret key base' -``` - -The same caveats apply here, too. You should wait to set `secret_key_base` until you have 100% of your userbase on Rails 4.x and are reasonably sure you will not need to rollback to Rails 3.x. You should also take care to make sure you are not relying on the ability to decode signed cookies generated by your app in external applications or Javascript before upgrading. - Please read [Pull Request #9978](https://github.com/rails/rails/pull/9978) for details on the move to encrypted session cookies. * Rails 4.0 removed the `ActionController::Base.asset_path` option. Use the assets pipeline feature. @@ -214,6 +221,11 @@ Please read [Pull Request #9978](https://github.com/rails/rails/pull/9978) for d * Rails 4.0 deprecates the `dom_id` and `dom_class` methods in controllers (they are fine in views). You will need to include the `ActionView::RecordIdentifier` module in controllers requiring this feature. +* Rails 4.0 deprecates the `:confirm` option for the `link_to` helper. You should +instead rely on a data attribute (e.g. `data: { confirm: 'Are you sure?' }`). +This deprecation also concerns the helpers based on this one (such as `link_to_if` +or `link_to_unless`). + * Rails 4.0 changed how `assert_generates`, `assert_recognizes`, and `assert_routing` work. Now all these assertions raise `Assertion` instead of `ActionController::RoutingError`. * Rails 4.0 raises an `ArgumentError` if clashing named routes are defined. This can be triggered by explicitly defined named routes or by the `resources` method. Here are two examples that clash with routes named `example_path`: @@ -301,10 +313,16 @@ Active Record Observer and Action Controller Sweeper have been extracted to the ### sprockets-rails * `assets:precompile:primary` has been removed. Use `assets:precompile` instead. +* The `config.assets.compress` option should be changed to +`config.assets.js_compressor` like so for instance: + +```ruby +config.assets.js_compressor = :uglifier +``` ### sass-rails -* `asset_url` with two arguments is deprecated. For example: `asset-url("rails.png", image)` becomes `asset-url("rails.png")` +* `asset-url` with two arguments is deprecated. For example: `asset-url("rails.png", image)` becomes `asset-url("rails.png")` Upgrading from Rails 3.1 to Rails 3.2 ------------------------------------- diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index ddefaf6ff8..bd0c796673 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -278,9 +278,7 @@ The index view (`app/views/users/index.html.erb`) contains: <b>Users</b> <ul id="users"> -<% @users.each do |user| %> - <%= render user %> -<% end %> +<%= render @users %> </ul> <br> @@ -394,4 +392,4 @@ Here are some helpful links to help you learn even more: * [jquery-ujs list of external articles](https://github.com/rails/jquery-ujs/wiki/External-articles) * [Rails 3 Remote Links and Forms: A Definitive Guide](http://www.alfajango.com/blog/rails-3-remote-links-and-forms/) * [Railscasts: Unobtrusive JavaScript](http://railscasts.com/episodes/205-unobtrusive-javascript) -* [Railscasts: Turbolinks](http://railscasts.com/episodes/390-turbolinks)
\ No newline at end of file +* [Railscasts: Turbolinks](http://railscasts.com/episodes/390-turbolinks) diff --git a/install.rb b/install.rb index 77c3e6048a..bff8fee934 100644 --- a/install.rb +++ b/install.rb @@ -5,7 +5,7 @@ if version.nil? exit(64) end -%w( activesupport activemodel activerecord actionpack actionmailer railties ).each do |framework| +%w( activesupport activemodel activerecord actionpack actionview actionmailer railties ).each do |framework| puts "Installing #{framework}..." `cd #{framework} && gem build #{framework}.gemspec && gem install #{framework}-#{version}.gem --no-ri --no-rdoc && rm #{framework}-#{version}.gem` end diff --git a/rails.gemspec b/rails.gemspec index 1993467455..b426faf0e8 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -20,10 +20,11 @@ Gem::Specification.new do |s| s.add_dependency 'activesupport', version s.add_dependency 'actionpack', version + s.add_dependency 'actionview', version s.add_dependency 'activerecord', version s.add_dependency 'actionmailer', version s.add_dependency 'railties', version s.add_dependency 'bundler', '>= 1.3.0', '< 2.0' - s.add_dependency 'sprockets-rails', '~> 2.0.0.rc4' + s.add_dependency 'sprockets-rails', '~> 2.0.0' end diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 23a7cf6ca3..30328a0c19 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,4 +1,61 @@ -* Clearing autoloaded constants triggers routes reloading [Fixes #10685]. +* Changed stylesheet load order in the stylesheet manifest generator. + Fixes #11639. + + *Pawel Janiak* + +* Added generated unit test for generator generator using new + `test:generators` rake task. + + *Josef Šimánek* + +* Removed `update:application_controller` rake task. + + *Josef Šimánek* + +* Fix `rake environment` to do not eager load modules + + *Paul Nikitochkin* + +* Fix `rake notes` to look into `*.sass` files + + *Yuri Artemev* + +* Removed deprecated `Rails.application.railties.engines`. + + *Arun Agrawal* + +* Removed deprecated threadsafe! from Rails Config. + + *Paul Nikitochkin* + +* Remove deprecated `ActiveRecord::Generators::ActiveModel#update_attributes` in + favor of `ActiveRecord::Generators::ActiveModel#update` + + *Vipul A M* + +* Remove deprecated `config.whiny_nils` option + + *Vipul A M* + +* Rename `commands/plugin_new.rb` to `commands/plugin.rb` and fix references + + *Richard Schneeman* + +* Fix `rails plugin --help` command. + + *Richard Schneeman* + +* Omit turbolinks configuration completely on skip_javascript generator option. + + *Nikita Fedyashev* + +* Removed deprecated rake tasks for running tests: `rake test:uncommitted` and + `rake test:recent`. + + *John Wang* + +* Clearing autoloaded constants triggers routes reloading. + Fixes #10685. *Xavier Noria* @@ -13,4 +70,8 @@ *John Wang* +* Changes repetitive th tags to use colspan attribute in `index.html.erb` template. + + *Sıtkı Bağdat* + Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/railties/CHANGELOG.md) for previous changes. diff --git a/railties/RDOC_MAIN.rdoc b/railties/RDOC_MAIN.rdoc index cadf0fb43e..eccdee7b07 100644 --- a/railties/RDOC_MAIN.rdoc +++ b/railties/RDOC_MAIN.rdoc @@ -18,7 +18,7 @@ you to present the data from database rows as objects and embellish these data o with business logic methods. Although most \Rails models are backed by a database, models can also be ordinary Ruby classes, or Ruby classes that implement a set of interfaces as provided by the ActiveModel module. You can read more about Active Record in its -{README}[link:/activerecord/README.rdoc]. +{README}[link:files/activerecord/README_rdoc.html]. The Controller layer is responsible for handling incoming HTTP requests and providing a suitable response. Usually this means returning \HTML, but \Rails controllers can also @@ -29,7 +29,7 @@ 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 independent. Each of these packages can be used independently outside of \Rails. You -can read more about Action Pack in its {README}[link:/actionpack/README.rdoc]. +can read more about Action Pack in its {README}[link:files/actionpack/README_rdoc.html]. == Getting Started @@ -55,7 +55,7 @@ can read more about Action Pack in its {README}[link:/actionpack/README.rdoc]. 5. Follow the guidelines to start developing your application. You may find the following resources handy: -* The README file created within your application. +* The \README file created within your application. * {Getting Started with \Rails}[http://guides.rubyonrails.org/getting_started.html]. * {Ruby on \Rails Tutorial}[http://ruby.railstutorial.org/ruby-on-rails-tutorial-book]. * {Ruby on \Rails Guides}[http://guides.rubyonrails.org]. diff --git a/railties/Rakefile b/railties/Rakefile index 4789f41ad4..a899d069b5 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -1,10 +1,6 @@ require 'rake/testtask' require 'rubygems/package_task' -require 'date' -require 'rbconfig' - - task :default => :test desc "Run all unit tests" @@ -35,15 +31,6 @@ Rake::TestTask.new('test:regular') do |t| t.verbose = true end -# Update spinoffs ------------------------------------------------------------------- - -desc "Updates application README to the latest version Railties README" -task :update_readme do - readme = "lib/rails/generators/rails/app/templates/README" - rm readme - cp "./README.rdoc", readme -end - # Generate GEM ---------------------------------------------------------------------------- spec = eval(File.read('railties.gemspec')) @@ -54,7 +41,7 @@ end # Publishing ------------------------------------------------------- -desc "Release to gemcutter" +desc "Release to rubygems" task :release => :package do require 'rake/gemcutter' Rake::Gemcutter::Tasks.new(spec).define diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index bb98bbe5bf..ea82327365 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -3,12 +3,13 @@ require 'rails/ruby_version_check' require 'pathname' require 'active_support' +require 'active_support/dependencies/autoload' require 'active_support/core_ext/kernel/reporting' +require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/array/extract_options' require 'rails/application' require 'rails/version' -require 'rails/deprecation' require 'active_support/railtie' require 'action_dispatch/railtie' @@ -20,26 +21,22 @@ silence_warnings do end module Rails - autoload :Info, 'rails/info' - autoload :InfoController, 'rails/info_controller' - autoload :WelcomeController, 'rails/welcome_controller' + extend ActiveSupport::Autoload + + autoload :Info + autoload :InfoController + autoload :WelcomeController class << self attr_accessor :application, :cache, :logger + delegate :initialize!, :initialized?, to: :application + # The Configuration instance used to configure the Rails environment def configuration application.config end - def initialize! - application.initialize! - end - - def initialized? - application.initialized? - end - def backtrace_cleaner @backtrace_cleaner ||= begin # Relies on Active Support, so we have to lazy load to postpone definition until AS has been loaded @@ -76,7 +73,7 @@ module Rails env = Rails.env groups.unshift(:default, env) groups.concat ENV["RAILS_GROUPS"].to_s.split(",") - groups.concat hash.map { |k,v| k if v.map(&:to_s).include?(env) } + groups.concat hash.map { |k, v| k if v.map(&:to_s).include?(env) } groups.compact! groups.uniq! groups diff --git a/railties/lib/rails/api/task.rb b/railties/lib/rails/api/task.rb index c829873da4..3e32576040 100644 --- a/railties/lib/rails/api/task.rb +++ b/railties/lib/rails/api/task.rb @@ -7,7 +7,6 @@ module Rails 'activesupport' => { :include => %w( README.rdoc - CHANGELOG.md lib/active_support/**/*.rb ), :exclude => 'lib/active_support/vendor/*' @@ -16,16 +15,13 @@ module Rails 'activerecord' => { :include => %w( README.rdoc - CHANGELOG.md lib/active_record/**/*.rb - ), - :exclude => 'lib/active_record/vendor/*' + ) }, 'activemodel' => { :include => %w( README.rdoc - CHANGELOG.md lib/active_model/**/*.rb ) }, @@ -33,29 +29,30 @@ module Rails 'actionpack' => { :include => %w( README.rdoc - CHANGELOG.md lib/abstract_controller/**/*.rb lib/action_controller/**/*.rb lib/action_dispatch/**/*.rb + ) + }, + + 'actionview' => { + :include => %w( + README.rdoc lib/action_view/**/*.rb ), - :exclude => 'lib/action_controller/vendor/*' + :exclude => 'lib/action_view/vendor/*' }, 'actionmailer' => { :include => %w( README.rdoc - CHANGELOG.md lib/action_mailer/**/*.rb - ), - :exclude => 'lib/action_mailer/vendor/*' + ) }, 'railties' => { :include => %w( README.rdoc - CHANGELOG.md - MIT-LICENSE lib/**/*.rb ), :exclude => 'lib/rails/generators/rails/**/templates/**/*.rb' @@ -135,12 +132,20 @@ module Rails def api_dir 'doc/rdoc' end + end + class EdgeTask < RepoTask def rails_version "master@#{`git rev-parse HEAD`[0, 7]}" end end + class StableTask < RepoTask + def rails_version + File.read('RAILS_VERSION').strip + end + end + class AppTask < Task def component_root_dir(gem_name) $:.grep(%r{#{gem_name}[\w.-]*/lib\z}).first[0..-5] diff --git a/railties/lib/rails/app_rails_loader.rb b/railties/lib/rails/app_rails_loader.rb index 4a17803f1c..fbb83fa10e 100644 --- a/railties/lib/rails/app_rails_loader.rb +++ b/railties/lib/rails/app_rails_loader.rb @@ -2,7 +2,7 @@ require 'pathname' module Rails module AppRailsLoader - RUBY = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"] + RUBY = Gem.ruby EXECUTABLES = ['bin/rails', 'script/rails'] BUNDLER_WARNING = <<EOS Looks like your app's ./bin/rails is a stub that was generated by Bundler. diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 914e4393c4..4faaf5ef1e 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -51,21 +51,49 @@ module Rails # 10) Run config.before_eager_load and eager_load! if eager_load is true # 11) Run config.after_initialize callbacks # + # == Multiple Applications + # + # If you decide to define multiple applications, then the first application + # that is initialized will be set to +Rails.application+, unless you override + # it with a different application. + # + # To create a new application, you can instantiate a new instance of a class + # that has already been created: + # + # class Application < Rails::Application + # end + # + # first_application = Application.new + # second_application = Application.new(config: first_application.config) + # + # In the above example, the configuration from the first application was used + # to initialize the second application. You can also use the +initialize_copy+ + # on one of the applications to create a copy of the application which shares + # the configuration. + # + # If you decide to define rake tasks, runners, or initializers in an + # application other than +Rails.application+, then you must run those + # these manually. class Application < Engine - 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' + autoload :Bootstrap, 'rails/application/bootstrap' + autoload :Configuration, 'rails/application/configuration' + autoload :DefaultMiddlewareStack, 'rails/application/default_middleware_stack' + autoload :Finisher, 'rails/application/finisher' + autoload :Railties, 'rails/engine/railties' + autoload :RoutesReloader, 'rails/application/routes_reloader' class << self def inherited(base) - raise "You cannot have more than one Rails::Application" if Rails.application super - Rails.application = base.instance - Rails.application.add_lib_to_load_path! - ActiveSupport.run_load_hooks(:before_configuration, base.instance) + Rails.application ||= base.instance end + + # Makes the +new+ method public. + # + # Note that Rails::Application inherits from Rails::Engine, which + # inherits from Rails::Railtie and the +new+ method on Rails::Railtie is + # private + public :new end attr_accessor :assets, :sandbox @@ -74,14 +102,28 @@ module Rails delegate :default_url_options, :default_url_options=, to: :routes - def initialize - super + INITIAL_VARIABLES = [:config, :railties, :routes_reloader, :reloaders, + :routes, :helpers, :app_env_config] # :nodoc: + + def initialize(initial_variable_values = {}, &block) + super() @initialized = false @reloaders = [] @routes_reloader = nil @app_env_config = nil @ordered_railties = nil @railties = nil + + add_lib_to_load_path! + ActiveSupport.run_load_hooks(:before_configuration, self) + + initial_variable_values.each do |variable_name, value| + if INITIAL_VARIABLES.include?(variable_name) + instance_variable_set("@#{variable_name}", value) + end + end + + instance_eval(&block) if block_given? end # Returns true if the application is initialized. @@ -102,7 +144,6 @@ module Rails routes_reloader.reload! end - # Return the application's KeyGenerator def key_generator # number of iterations selected based on consultation with the google security @@ -119,32 +160,9 @@ module Rails # Stores some of the Rails initial environment parameters which # will be used by middlewares and engines to configure themselves. - # Currently stores: - # - # * "action_dispatch.parameter_filter" => config.filter_parameters - # * "action_dispatch.redirect_filter" => config.filter_redirect - # * "action_dispatch.secret_token" => config.secret_token - # * "action_dispatch.secret_key_base" => config.secret_key_base - # * "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions - # * "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local - # * "action_dispatch.logger" => Rails.logger - # * "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner - # * "action_dispatch.key_generator" => key_generator - # * "action_dispatch.http_auth_salt" => config.action_dispatch.http_auth_salt - # * "action_dispatch.signed_cookie_salt" => config.action_dispatch.signed_cookie_salt - # * "action_dispatch.encrypted_cookie_salt" => config.action_dispatch.encrypted_cookie_salt - # * "action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt - # def env_config @app_env_config ||= begin - if config.secret_key_base.blank? - ActiveSupport::Deprecation.warn "You didn't set config.secret_key_base. " + - "Read the upgrade documentation to learn more about this new config option." - - if config.secret_token.blank? - raise "You must set config.secret_key_base in your app's config." - end - end + validate_secret_key_config! super.merge({ "action_dispatch.parameter_filter" => config.filter_parameters, @@ -164,6 +182,30 @@ module Rails end end + # If you try to define a set of rake tasks on the instance, these will get + # passed up to the rake tasks defined on the application's class. + def rake_tasks(&block) + self.class.rake_tasks(&block) + end + + # Sends the initializers to the +initializer+ method defined in the + # Rails::Initializable module. Each Rails::Application class has its own + # set of initializers, as defined by the Initializable module. + def initializer(name, opts={}, &block) + self.class.initializer(name, opts, &block) + end + + # Sends any runner called in the instance of a new application up + # to the +runner+ method defined in Rails::Railtie. + def runner(&blk) + self.class.runner(&blk) + end + + # Sends the +isolate_namespace+ method up to the class method. + def isolate_namespace(mod) + self.class.isolate_namespace(mod) + end + ## Rails internal API # This method is called just after an application inherits from Rails::Application, @@ -181,7 +223,9 @@ module Rails # you need to load files in lib/ during the application configuration as well. def add_lib_to_load_path! #:nodoc: path = File.join config.root, 'lib' - $LOAD_PATH.unshift(path) if File.exists?(path) + if File.exists?(path) && !$LOAD_PATH.include?(path) + $LOAD_PATH.unshift(path) + end end def require_environment! #:nodoc: @@ -207,9 +251,7 @@ module Rails end # Initialize the application passing the given group. By default, the - # group is :default but sprockets precompilation passes group equals - # to assets if initialize_on_precompile is false to avoid booting the - # whole app. + # group is :default def initialize!(group=:default) #:nodoc: raise "Application has been already initialized." if @initialized run_initializers(group, self) @@ -227,6 +269,10 @@ module Rails @config ||= Application::Configuration.new(find_root_with_flag("config.ru", Dir.pwd)) end + def config=(configuration) #:nodoc: + @config = configuration + end + def to_app #:nodoc: self end @@ -243,9 +289,9 @@ module Rails railties.each { |r| r.run_tasks_blocks(app) } super require "rails/tasks" - config = self.config task :environment do - config.eager_load = false + ActiveSupport.on_load(:before_initialize) { config.eager_load = false } + require_environment! end end @@ -300,96 +346,9 @@ module Rails initializers end - def reload_dependencies? #:nodoc: - config.reload_classes_only_on_change != true || reloaders.map(&:updated?).any? - end - def default_middleware_stack #:nodoc: - ActionDispatch::MiddlewareStack.new.tap do |middleware| - app = self - - if rack_cache = load_rack_cache - require "action_dispatch/http/rack_cache" - middleware.use ::Rack::Cache, rack_cache - end - - if config.force_ssl - middleware.use ::ActionDispatch::SSL, config.ssl_options - end - - if config.action_dispatch.x_sendfile_header.present? - middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header - end - - if config.serve_static_assets - middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control - end - - middleware.use ::Rack::Lock unless allow_concurrency? - middleware.use ::Rack::Runtime - middleware.use ::Rack::MethodOverride - middleware.use ::ActionDispatch::RequestId - - # Must come after Rack::MethodOverride to properly log overridden methods - middleware.use ::Rails::Rack::Logger, config.log_tags - middleware.use ::ActionDispatch::ShowExceptions, show_exceptions_app - middleware.use ::ActionDispatch::DebugExceptions, app - middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies - - unless config.cache_classes - middleware.use ::ActionDispatch::Reloader, lambda { app.reload_dependencies? } - end - - middleware.use ::ActionDispatch::Callbacks - middleware.use ::ActionDispatch::Cookies - - if config.session_store - if config.force_ssl && !config.session_options.key?(:secure) - config.session_options[:secure] = true - end - middleware.use config.session_store, config.session_options - middleware.use ::ActionDispatch::Flash - end - - middleware.use ::ActionDispatch::ParamsParser - middleware.use ::Rack::Head - middleware.use ::Rack::ConditionalGet - middleware.use ::Rack::ETag, "no-cache" - end - end - - def allow_concurrency? - if config.allow_concurrency.nil? - config.cache_classes - else - config.allow_concurrency - end - end - - def load_rack_cache - rack_cache = config.action_dispatch.rack_cache - return unless rack_cache - - begin - require 'rack/cache' - rescue LoadError => error - error.message << ' Be sure to add rack-cache to your Gemfile' - raise - end - - if rack_cache == true - { - metastore: "rails:/", - entitystore: "rails:/", - verbose: false - } - else - rack_cache - end - end - - def show_exceptions_app - config.exceptions_app || ActionDispatch::PublicExceptions.new(Rails.public_path) + default_stack = DefaultMiddlewareStack.new(self, config, paths) + default_stack.build_stack end def build_original_fullpath(env) #:nodoc: @@ -403,5 +362,11 @@ module Rails "#{script_name}#{path_info}" end end + + def validate_secret_key_config! #:nodoc: + if config.secret_key_base.blank? && config.secret_token.blank? + raise "You must set config.secret_key_base in your app's config." + end + end end end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 31fc80e544..7332444ab9 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -61,7 +61,6 @@ module Rails @assets.cache_store = [ :file_store, "#{root}/tmp/cache/assets/#{Rails.env}/" ] @assets.js_compressor = nil @assets.css_compressor = nil - @assets.initialize_on_precompile = true @assets.logger = nil end @@ -88,16 +87,6 @@ module Rails end end - def threadsafe! - message = "config.threadsafe! is deprecated. Rails applications " \ - "behave by default as thread safe in production as long as config.cache_classes and " \ - "config.eager_load are set to true" - ActiveSupport::Deprecation.warn message - @cache_classes = true - @eager_load = true - self - end - # Loads and returns the configuration of the database. def database_configuration yaml = paths["config/database"].first @@ -151,9 +140,6 @@ module Rails end end - def whiny_nils=(*) - ActiveSupport::Deprecation.warn "config.whiny_nils option is deprecated and no longer works" - end end end end diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb new file mode 100644 index 0000000000..570ff02c83 --- /dev/null +++ b/railties/lib/rails/application/default_middleware_stack.rb @@ -0,0 +1,99 @@ +module Rails + class Application + class DefaultMiddlewareStack + attr_reader :config, :paths, :app + + def initialize(app, config, paths) + @app = app + @config = config + @paths = paths + end + + def build_stack + ActionDispatch::MiddlewareStack.new.tap do |middleware| + if rack_cache = load_rack_cache + require "action_dispatch/http/rack_cache" + middleware.use ::Rack::Cache, rack_cache + end + + if config.force_ssl + middleware.use ::ActionDispatch::SSL, config.ssl_options + end + + middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header + + if config.serve_static_assets + middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control + end + + middleware.use ::Rack::Lock unless allow_concurrency? + middleware.use ::Rack::Runtime + middleware.use ::Rack::MethodOverride + middleware.use ::ActionDispatch::RequestId + + # Must come after Rack::MethodOverride to properly log overridden methods + middleware.use ::Rails::Rack::Logger, config.log_tags + middleware.use ::ActionDispatch::ShowExceptions, show_exceptions_app + middleware.use ::ActionDispatch::DebugExceptions, app + middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies + + unless config.cache_classes + middleware.use ::ActionDispatch::Reloader, lambda { reload_dependencies? } + end + + middleware.use ::ActionDispatch::Callbacks + middleware.use ::ActionDispatch::Cookies + + if config.session_store + if config.force_ssl && !config.session_options.key?(:secure) + config.session_options[:secure] = true + end + middleware.use config.session_store, config.session_options + middleware.use ::ActionDispatch::Flash + end + + middleware.use ::ActionDispatch::ParamsParser + middleware.use ::Rack::Head + middleware.use ::Rack::ConditionalGet + middleware.use ::Rack::ETag, "no-cache" + end + end + + private + + def reload_dependencies? + config.reload_classes_only_on_change != true || app.reloaders.map(&:updated?).any? + end + + def allow_concurrency? + config.allow_concurrency.nil? ? config.cache_classes : config.allow_concurrency + end + + def load_rack_cache + rack_cache = config.action_dispatch.rack_cache + return unless rack_cache + + begin + require 'rack/cache' + rescue LoadError => error + error.message << ' Be sure to add rack-cache to your Gemfile' + raise + end + + if rack_cache == true + { + metastore: "rails:/", + entitystore: "rails:/", + verbose: false + } + else + rack_cache + end + end + + def show_exceptions_app + config.exceptions_app || ActionDispatch::PublicExceptions.new(Rails.public_path) + end + end + end +end diff --git a/railties/lib/rails/cli.rb b/railties/lib/rails/cli.rb index e5341ac436..20313a2608 100644 --- a/railties/lib/rails/cli.rb +++ b/railties/lib/rails/cli.rb @@ -10,7 +10,7 @@ Signal.trap("INT") { puts; exit(1) } if ARGV.first == 'plugin' ARGV.shift - require 'rails/commands/plugin_new' + require 'rails/commands/plugin' else require 'rails/commands/application' end diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb index e8c42b149b..f32bf772a5 100644 --- a/railties/lib/rails/commands.rb +++ b/railties/lib/rails/commands.rb @@ -5,109 +5,13 @@ aliases = { "d" => "destroy", "c" => "console", "s" => "server", - "t" => "test", "db" => "dbconsole", "r" => "runner" } -help_message = <<-EOT -Usage: rails COMMAND [ARGS] - -The most common rails commands are: - generate Generate new code (short-cut alias: "g") - console Start the Rails console (short-cut alias: "c") - server Start the Rails server (short-cut alias: "s") - test Running the test file (short-cut alias: "t") - dbconsole Start a console for the database specified in config/database.yml - (short-cut alias: "db") - new Create a new Rails application. "rails new my_app" creates a - new application called MyApp in "./my_app" - -In addition to those, there are: - application Generate the Rails application code - destroy Undo code generated with "generate" (short-cut alias: "d") - plugin new Generates skeleton for developing a Rails plugin - runner Run a piece of code in the application environment (short-cut alias: "r") - -All commands can be run with -h (or --help) for more information. -EOT - - command = ARGV.shift command = aliases[command] || command -case command -when 'generate', 'destroy', 'plugin' - require 'rails/generators' - - if command == 'plugin' && ARGV.first == 'new' - require "rails/commands/plugin_new" - else - require APP_PATH - Rails.application.require_environment! - - Rails.application.load_generators - - require "rails/commands/#{command}" - end - -when 'console' - require 'rails/commands/console' - options = Rails::Console.parse_arguments(ARGV) - - # RAILS_ENV needs to be set before config/application is required - ENV['RAILS_ENV'] = options[:environment] if options[:environment] - - # shift ARGV so IRB doesn't freak - ARGV.shift if ARGV.first && ARGV.first[0] != '-' - - require APP_PATH - Rails.application.require_environment! - Rails::Console.start(Rails.application, options) - -when 'server' - # Change to the application's path if there is no config.ru file in current directory. - # This allows us to run `rails server` from other directories, but still get - # the main config.ru and properly set the tmp directory. - Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exists?(File.expand_path("config.ru")) - - require 'rails/commands/server' - Rails::Server.new.tap do |server| - # We need to require application after the server sets environment, - # otherwise the --environment option given to the server won't propagate. - require APP_PATH - Dir.chdir(Rails.application.root) - server.start - end - -when 'dbconsole' - require 'rails/commands/dbconsole' - Rails::DBConsole.start - -when 'application', 'runner' - require "rails/commands/#{command}" - -when 'new' - if %w(-h --help).include?(ARGV.first) - require 'rails/commands/application' - else - puts "Can't initialize a new Rails application within the directory of another, please change to a non-Rails directory first.\n" - puts "Type 'rails' for help." - exit(1) - end - -when '--version', '-v' - ARGV.unshift '--version' - require 'rails/commands/application' - -when '-h', '--help' - puts help_message +require 'rails/commands/commands_tasks' -else - puts "Error: Command '#{command}' not recognized" - if %x{rake #{command} --dry-run 2>&1 } && $?.success? - puts "Did you mean: `$ rake #{command}` ?\n\n" - end - puts help_message - exit(1) -end +Rails::CommandsTasks.new(ARGV).run_command!(command) diff --git a/railties/lib/rails/commands/application.rb b/railties/lib/rails/commands/application.rb index 2ff29418c6..678697f09b 100644 --- a/railties/lib/rails/commands/application.rb +++ b/railties/lib/rails/commands/application.rb @@ -1,30 +1,3 @@ -require 'rails/version' - -if ['--version', '-v'].include?(ARGV.first) - puts "Rails #{Rails::VERSION::STRING}" - exit(0) -end - -if ARGV.first != "new" - ARGV[0] = "--help" -else - ARGV.shift - 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 - require 'rails/generators' require 'rails/generators/rails/app/app_generator' @@ -40,4 +13,5 @@ module Rails end end +Rails::Generators::AppPreparer.new(ARGV).prepare! Rails::Generators::AppGenerator.start diff --git a/railties/lib/rails/commands/commands_tasks.rb b/railties/lib/rails/commands/commands_tasks.rb new file mode 100644 index 0000000000..11524c4ef5 --- /dev/null +++ b/railties/lib/rails/commands/commands_tasks.rb @@ -0,0 +1,170 @@ +module Rails + # This is a class which takes in a rails command and initiates the appropriate + # initiation sequence. + # + # Warning: This class mutates ARGV because some commands require manipulating + # it before they are run. + class CommandsTasks # :nodoc: + attr_reader :argv + + HELP_MESSAGE = <<-EOT +Usage: rails COMMAND [ARGS] + +The most common rails commands are: + generate Generate new code (short-cut alias: "g") + console Start the Rails console (short-cut alias: "c") + server Start the Rails server (short-cut alias: "s") + dbconsole Start a console for the database specified in config/database.yml + (short-cut alias: "db") + new Create a new Rails application. "rails new my_app" creates a + new application called MyApp in "./my_app" + +In addition to those, there are: + application Generate the Rails application code + destroy Undo code generated with "generate" (short-cut alias: "d") + plugin new Generates skeleton for developing a Rails plugin + runner Run a piece of code in the application environment (short-cut alias: "r") + +All commands can be run with -h (or --help) for more information. +EOT + + COMMAND_WHITELIST = %(plugin generate destroy console server dbconsole application runner new version help) + + def initialize(argv) + @argv = argv + end + + def run_command!(command) + command = parse_command(command) + if COMMAND_WHITELIST.include?(command) + send(command) + else + write_error_message(command) + end + end + + def plugin + require_command!("plugin") + end + + def generate + generate_or_destroy(:generate) + end + + def console + require_command!("console") + options = Rails::Console.parse_arguments(argv) + + # RAILS_ENV needs to be set before config/application is required + ENV['RAILS_ENV'] = options[:environment] if options[:environment] + + # shift ARGV so IRB doesn't freak + shift_argv! + + require_application_and_environment! + Rails::Console.start(Rails.application, options) + end + + def server + set_application_directory! + require_command!("server") + + Rails::Server.new.tap do |server| + # We need to require application after the server sets environment, + # otherwise the --environment option given to the server won't propagate. + require APP_PATH + Dir.chdir(Rails.application.root) + server.start + end + end + + def dbconsole + require_command!("dbconsole") + Rails::DBConsole.start + end + + def application + require_command!("application") + end + + def runner + require_command!("runner") + end + + def new + if %w(-h --help).include?(argv.first) + require_command!("application") + else + exit_with_initialization_warning! + end + end + + def version + argv.unshift '--version' + require_command!("application") + end + + def help + write_help_message + end + + private + + def exit_with_initialization_warning! + puts "Can't initialize a new Rails application within the directory of another, please change to a non-Rails directory first.\n" + puts "Type 'rails' for help." + exit(1) + end + + def shift_argv! + argv.shift if argv.first && argv.first[0] != '-' + end + + def require_command!(command) + require "rails/commands/#{command}" + end + + def generate_or_destroy(command) + require 'rails/generators' + require_application_and_environment! + Rails.application.load_generators + require "rails/commands/#{command}" + end + + # Change to the application's path if there is no config.ru file in current directory. + # This allows us to run `rails server` from other directories, but still get + # the main config.ru and properly set the tmp directory. + def set_application_directory! + Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exists?(File.expand_path("config.ru")) + end + + def require_application_and_environment! + require APP_PATH + Rails.application.require_environment! + end + + def write_help_message + puts HELP_MESSAGE + end + + def write_error_message(command) + puts "Error: Command '#{command}' not recognized" + if %x{rake #{command} --dry-run 2>&1 } && $?.success? + puts "Did you mean: `$ rake #{command}` ?\n\n" + end + write_help_message + exit(1) + end + + def parse_command(command) + case command + when '--version', '-v' + 'version' + when '--help', '-h' + 'help' + else + command + end + end + end +end diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb index 5914c9e4ae..3e4cc787c4 100644 --- a/railties/lib/rails/commands/dbconsole.rb +++ b/railties/lib/rails/commands/dbconsole.rb @@ -44,7 +44,7 @@ module Rails find_cmd_and_exec(['mysql', 'mysql5'], *args) - when "postgresql", "postgres" + when "postgresql", "postgres", "postgis" ENV['PGUSER'] = config["username"] if config["username"] ENV['PGHOST'] = config["host"] if config["host"] ENV['PGPORT'] = config["port"].to_s if config["port"] diff --git a/railties/lib/rails/commands/plugin.rb b/railties/lib/rails/commands/plugin.rb new file mode 100644 index 0000000000..837fe0ec10 --- /dev/null +++ b/railties/lib/rails/commands/plugin.rb @@ -0,0 +1,9 @@ +if ARGV.first != "new" + ARGV[0] = "--help" +else + ARGV.shift +end + +require 'rails/generators' +require 'rails/generators/rails/plugin/plugin_generator' +Rails::Generators::PluginGenerator.start diff --git a/railties/lib/rails/commands/plugin_new.rb b/railties/lib/rails/commands/plugin_new.rb deleted file mode 100644 index 4d7bf3c9f3..0000000000 --- a/railties/lib/rails/commands/plugin_new.rb +++ /dev/null @@ -1,9 +0,0 @@ -if ARGV.first != "new" - ARGV[0] = "--help" -else - ARGV.shift -end - -require 'rails/generators' -require 'rails/generators/rails/plugin_new/plugin_new_generator' -Rails::Generators::PluginNewGenerator.start
\ No newline at end of file diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb index c4622d6a2d..2b77d5d387 100644 --- a/railties/lib/rails/commands/runner.rb +++ b/railties/lib/rails/commands/runner.rb @@ -48,7 +48,7 @@ if code_or_file.nil? exit 1 elsif File.exist?(code_or_file) $0 = code_or_file - eval(File.read(code_or_file), nil, code_or_file) + Kernel.load code_or_file else eval(code_or_file) end diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 15d13d5f28..c694513960 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -1,4 +1,3 @@ -require 'active_support/deprecation' require 'active_support/ordered_options' require 'active_support/core_ext/object' require 'rails/paths' diff --git a/railties/lib/rails/console/helpers.rb b/railties/lib/rails/console/helpers.rb index 230d3d9d04..b775f1ff8d 100644 --- a/railties/lib/rails/console/helpers.rb +++ b/railties/lib/rails/console/helpers.rb @@ -1,9 +1,15 @@ module Rails module ConsoleMethods + # Gets the helper methods available to the controller. + # + # This method assumes an +ApplicationController+ exists, and it extends +ActionController::Base+ def helper @helper ||= ApplicationController.helpers end + # Gets a new instance of a controller object. + # + # This method assumes an +ApplicationController+ exists, and it extends +ActionController::Base+ def controller @controller ||= ApplicationController.new end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 8000fc3b1e..be8af5c46c 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -351,8 +351,13 @@ module Rails Rails::Railtie::Configuration.eager_load_namespaces << base base.called_from = begin - # Remove the line number from backtraces making sure we don't leave anything behind - call_stack = caller.map { |p| p.sub(/:\d+.*/, '') } + call_stack = if Kernel.respond_to?(:caller_locations) + caller_locations.map(&:path) + else + # Remove the line number from backtraces making sure we don't leave anything behind + caller.map { |p| p.sub(/:\d+.*/, '') } + end + File.dirname(call_stack.detect { |p| p !~ %r[railties[\w.-]*/lib/rails|rack[\w.-]*/lib/rack] }) end end diff --git a/railties/lib/rails/engine/railties.rb b/railties/lib/rails/engine/railties.rb index 1081700cd0..595256f36a 100644 --- a/railties/lib/rails/engine/railties.rb +++ b/railties/lib/rails/engine/railties.rb @@ -9,10 +9,6 @@ module Rails ::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 @@ -25,5 +21,3 @@ module Rails end end end - -ActiveSupport::Deprecation.deprecate_methods(Rails::Engine::Railties, :engines) diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 4b767ea0c6..6b34db3e3f 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -225,7 +225,7 @@ module Rails rails = groups.delete("rails") rails.map! { |n| n.sub(/^rails:/, '') } rails.delete("app") - rails.delete("plugin_new") + rails.delete("plugin") print_list("rails", rails) hidden_namespaces.each { |n| groups.delete(n.to_s) } diff --git a/railties/lib/rails/generators/active_model.rb b/railties/lib/rails/generators/active_model.rb index e5373704d7..6183944bb0 100644 --- a/railties/lib/rails/generators/active_model.rb +++ b/railties/lib/rails/generators/active_model.rb @@ -1,5 +1,3 @@ -require 'active_support/deprecation' - module Rails module Generators # ActiveModel is a class to be implemented by each ORM to allow Rails to @@ -65,12 +63,6 @@ module Rails "#{name}.update(#{params})" end - def update_attributes(*args) # :nodoc: - ActiveSupport::Deprecation.warn("Calling '@orm_instance.update_attributes' " \ - "is deprecated, please use '@orm_instance.update' instead.") - update(*args) - end - # POST create # PATCH/PUT update def errors diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb index 1799e823b6..4a40ba654d 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb @@ -21,8 +21,13 @@ <%%= f.label :password_confirmation %><br> <%%= f.password_field :password_confirmation %> <% else -%> + <% if attribute.reference? -%> + <%%= f.label :<%= attribute.column_name %> %><br> + <%%= f.<%= attribute.field_type %> :<%= attribute.column_name %> %> + <% else -%> <%%= f.label :<%= attribute.name %> %><br> <%%= f.<%= attribute.field_type %> :<%= attribute.name %> %> + <% end -%> <% end -%> </div> <% end -%> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb index 9d778642f2..814d6fdb0e 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb @@ -6,9 +6,7 @@ <% attributes.reject(&:password_digest?).each do |attribute| -%> <th><%= attribute.human_name %></th> <% end -%> - <th></th> - <th></th> - <th></th> + <th colspan="3"></th> </tr> </thead> diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb index cd69a017dd..3566f96f5e 100644 --- a/railties/lib/rails/generators/migration.rb +++ b/railties/lib/rails/generators/migration.rb @@ -1,15 +1,14 @@ +require 'active_support/concern' + module Rails module Generators # Holds common methods for migrations. It assumes that migrations has the # [0-9]*_name format and can be used by another frameworks (like Sequel) # just by implementing the next migration version method. module Migration + extend ActiveSupport::Concern attr_reader :migration_number, :migration_file_name, :migration_class_name - def self.included(base) #:nodoc: - base.extend ClassMethods - end - module ClassMethods def migration_lookup_at(dirname) #:nodoc: Dir.glob("#{dirname}/[0-9]*_*.rb") diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index d48dcf9ef3..9fb8ea6955 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -300,5 +300,67 @@ module Rails defined?(::AppBuilder) ? ::AppBuilder : Rails::AppBuilder end end + + # This class handles preparation of the arguments before the AppGenerator is + # called. The class provides version or help information if they were + # requested, and also constructs the railsrc file (used for extra configuration + # options). + # + # This class should be called before the AppGenerator is required and started + # since it configures and mutates ARGV correctly. + class AppPreparer # :nodoc + attr_reader :argv + + def initialize(argv = ARGV) + @argv = argv + end + + def prepare! + handle_version_request!(argv.first) + unless handle_invalid_command!(argv.first) + argv.shift + handle_rails_rc! + end + end + + private + + def handle_version_request!(argument) + if ['--version', '-v'].include?(argv.first) + require 'rails/version' + puts "Rails #{Rails::VERSION::STRING}" + exit(0) + end + end + + def handle_invalid_command!(argument) + if argument != "new" + argv[0] = "--help" + end + end + + def handle_rails_rc! + unless argv.delete("--no-rc") + insert_railsrc(railsrc) + end + end + + def railsrc + if (customrc = argv.index{ |x| x.include?("--rc=") }) + File.expand_path(argv.delete_at(customrc).gsub(/--rc=/, "")) + else + File.join(File.expand_path("~"), '.railsrc') + end + end + + def insert_railsrc(railsrc) + 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 end end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 577ff651e5..adc83353b4 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -16,7 +16,7 @@ group :doc do end # Use ActiveModel has_secure_password -# gem 'bcrypt-ruby', '~> 3.0.0' +# gem 'bcrypt-ruby', '~> 3.1.0' # Use unicorn as the app server # gem 'unicorn' diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css index 3192ec897b..a443db3401 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css @@ -5,9 +5,11 @@ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. * - * You're free to add application-wide styles to this file and they'll appear at the top of the - * compiled file, but it's generally better to create a new file per style scope. + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any styles + * defined in the other CSS/SCSS files in this directory. It is generally better to create a new + * file per style scope. * - *= require_self *= require_tree . + *= require_self */ diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt index d87c7b7268..c3d1578818 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt @@ -2,8 +2,13 @@ <html> <head> <title><%= camelized %></title> + <%- if options[:skip_javascript] -%> + <%%= stylesheet_link_tag "application", media: "all" %> + <%%= javascript_include_tag "application" %> + <%- else -%> <%%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %> <%%= javascript_include_tag "application", "data-turbolinks-track" => true %> + <%- end -%> <%%= csrf_meta_tags %> </head> <body> 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 91253d1508..cff570a631 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -20,7 +20,7 @@ Rails.application.configure do config.active_support.deprecation = :log <%- unless options.skip_active_record? -%> - # Raise an error on page load if there are pending migrations + # Raise an error on page load if there are pending migrations. config.active_record.migration_error = :page_load <%- end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb index 3dfb724164..3f66539d54 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb @@ -39,7 +39,7 @@ Rails.application.routes.draw do # get 'recent', on: :collection # end # end - + # Example resource route with concerns: # concern :toggleable do # post 'toggle' diff --git a/railties/lib/rails/generators/rails/controller/USAGE b/railties/lib/rails/generators/rails/controller/USAGE index 64239ad599..de33900e0a 100644 --- a/railties/lib/rails/generators/rails/controller/USAGE +++ b/railties/lib/rails/generators/rails/controller/USAGE @@ -16,3 +16,4 @@ Example: Test: test/controllers/credit_cards_controller_test.rb Views: app/views/credit_cards/debit.html.erb [...] Helper: app/helpers/credit_cards_helper.rb + Test: test/helpers/credit_cards_helper_test.rb diff --git a/railties/lib/rails/generators/rails/generator/USAGE b/railties/lib/rails/generators/rails/generator/USAGE index d28eb3d7d8..799383050c 100644 --- a/railties/lib/rails/generators/rails/generator/USAGE +++ b/railties/lib/rails/generators/rails/generator/USAGE @@ -10,3 +10,4 @@ Example: lib/generators/awesome/awesome_generator.rb lib/generators/awesome/USAGE lib/generators/awesome/templates/ + test/lib/generators/awesome_generator_test.rb diff --git a/railties/lib/rails/generators/rails/generator/generator_generator.rb b/railties/lib/rails/generators/rails/generator/generator_generator.rb index 9a7a516b5b..15d88f06ac 100644 --- a/railties/lib/rails/generators/rails/generator/generator_generator.rb +++ b/railties/lib/rails/generators/rails/generator/generator_generator.rb @@ -10,6 +10,8 @@ module Rails directory '.', generator_dir end + hook_for :test_framework + protected def generator_dir diff --git a/railties/lib/rails/generators/rails/plugin_new/USAGE b/railties/lib/rails/generators/rails/plugin/USAGE index 9a7bf9f396..9a7bf9f396 100644 --- a/railties/lib/rails/generators/rails/plugin_new/USAGE +++ b/railties/lib/rails/generators/rails/plugin/USAGE diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb index 850c9d5c0d..13f5472ede 100644 --- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb +++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb @@ -154,7 +154,7 @@ task default: :test end module Generators - class PluginNewGenerator < AppBase # :nodoc: + class PluginGenerator < AppBase # :nodoc: add_shared_options_for "plugin" alias_method :plugin_path, :app_path diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec index 5fdf0e1554..5fdf0e1554 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec +++ b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile index 3f2b78f2fd..3f2b78f2fd 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile +++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/MIT-LICENSE b/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE index d7a9109894..d7a9109894 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/MIT-LICENSE +++ b/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/README.rdoc b/railties/lib/rails/generators/rails/plugin/templates/README.rdoc index 301d647731..301d647731 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/README.rdoc +++ b/railties/lib/rails/generators/rails/plugin/templates/README.rdoc diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile b/railties/lib/rails/generators/rails/plugin/templates/Rakefile index 0ba899176c..0ba899176c 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile +++ b/railties/lib/rails/generators/rails/plugin/templates/Rakefile diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/app/controllers/%name%/application_controller.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%name%/application_controller.rb.tt index 448ad7f989..448ad7f989 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/app/controllers/%name%/application_controller.rb.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%name%/application_controller.rb.tt diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/app/helpers/%name%/application_helper.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%name%/application_helper.rb.tt index 40ae9f52c2..40ae9f52c2 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/app/helpers/%name%/application_helper.rb.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%name%/application_helper.rb.tt diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/app/models/.empty_directory b/railties/lib/rails/generators/rails/plugin/templates/app/mailers/.empty_directory index e69de29bb2..e69de29bb2 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/app/models/.empty_directory +++ b/railties/lib/rails/generators/rails/plugin/templates/app/mailers/.empty_directory diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/models/.empty_directory b/railties/lib/rails/generators/rails/plugin/templates/app/models/.empty_directory new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/app/models/.empty_directory diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/%name%/application.html.erb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%name%/application.html.erb.tt index 1d380420b4..1d380420b4 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/%name%/application.html.erb.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%name%/application.html.erb.tt diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/bin/rails.tt b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt index c8de9f3e0f..c8de9f3e0f 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/bin/rails.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/config/routes.rb b/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb index 8e158d5831..8e158d5831 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/config/routes.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/gitignore b/railties/lib/rails/generators/rails/plugin/templates/gitignore index 086d87818a..086d87818a 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/gitignore +++ b/railties/lib/rails/generators/rails/plugin/templates/gitignore diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%name%.rb index 40c074cced..40c074cced 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%name%.rb diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%/engine.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/engine.rb index 967668fe66..967668fe66 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%/engine.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/engine.rb diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%/version.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/version.rb index ef07ef2e19..ef07ef2e19 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%/version.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/version.rb diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/lib/tasks/%name%_tasks.rake b/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%name%_tasks.rake index 7121f5ae23..7121f5ae23 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/lib/tasks/%name%_tasks.rake +++ b/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%name%_tasks.rake diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb index 310c975262..310c975262 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb index ef360470a3..ef360470a3 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js b/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js index 5bc2e1c8b5..5bc2e1c8b5 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/routes.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb index 730ee31c3d..730ee31c3d 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/rails/routes.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/stylesheets.css b/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css index 3192ec897b..a443db3401 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/rails/stylesheets.css +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css @@ -5,9 +5,11 @@ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. * - * You're free to add application-wide styles to this file and they'll appear at the top of the - * compiled file, but it's generally better to create a new file per style scope. + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any styles + * defined in the other CSS/SCSS files in this directory. It is generally better to create a new + * file per style scope. * - *= require_self *= require_tree . + *= require_self */ diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/test/%name%_test.rb b/railties/lib/rails/generators/rails/plugin/templates/test/%name%_test.rb index 0a8bbd4aaf..0a8bbd4aaf 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/test/%name%_test.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/test/%name%_test.rb diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb b/railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb index 824caecb24..824caecb24 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb index 1e26a313cd..1e26a313cd 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/test/test_helper.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb 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 73e89086a5..0e69aa101f 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb @@ -59,9 +59,9 @@ class <%= controller_class_name %>Controller < ApplicationController # Only allow a trusted parameter "white list" through. def <%= "#{singular_table_name}_params" %> <%- if attributes_names.empty? -%> - params[<%= ":#{singular_table_name}" %>] + params[:<%= singular_table_name %>] <%- else -%> - params.require(<%= ":#{singular_table_name}" %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>) + params.require(:<%= singular_table_name %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>) <%- end -%> end end diff --git a/railties/lib/rails/generators/test_unit/generator/generator_generator.rb b/railties/lib/rails/generators/test_unit/generator/generator_generator.rb new file mode 100644 index 0000000000..d7307398ce --- /dev/null +++ b/railties/lib/rails/generators/test_unit/generator/generator_generator.rb @@ -0,0 +1,26 @@ +require 'rails/generators/test_unit' + +module TestUnit # :nodoc: + module Generators # :nodoc: + class GeneratorGenerator < Base # :nodoc: + check_class_collision suffix: "GeneratorTest" + + class_option :namespace, type: :boolean, default: true, + desc: "Namespace generator under lib/generators/name" + + def create_generator_files + template 'generator_test.rb', File.join('test/lib/generators', class_path, "#{file_name}_generator_test.rb") + end + + protected + + def generator_path + if options[:namespace] + File.join("generators", regular_class_path, file_name, "#{file_name}_generator") + else + File.join("generators", regular_class_path, "#{file_name}_generator") + end + end + end + end +end diff --git a/railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb b/railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb new file mode 100644 index 0000000000..a7f1fc4fba --- /dev/null +++ b/railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb @@ -0,0 +1,16 @@ +require 'test_helper' +require '<%= generator_path %>' + +<% module_namespacing do -%> +class <%= class_name %>GeneratorTest < Rails::Generators::TestCase + tests <%= class_name %>Generator + destination Rails.root.join('tmp/generators') + setup :prepare_destination + + # test "generator runs without errors" do + # assert_nothing_raised do + # run_generator ["arguments"] + # end + # end +end +<% end -%> diff --git a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml index 90a92e6982..f19e9d1d87 100644 --- a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml +++ b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml @@ -1,4 +1,4 @@ -# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html <% unless attributes.empty? -%> <% %w(one two).each do |name| %> <%= name %>: diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb index f06ce659c5..5b69605b51 100644 --- a/railties/lib/rails/info.rb +++ b/railties/lib/rails/info.rb @@ -61,8 +61,10 @@ module Rails end end - # The Ruby version and platform, e.g. "1.8.2 (powerpc-darwin8.2.0)". - property 'Ruby version', "#{RUBY_VERSION} (#{RUBY_PLATFORM})" + # The Ruby version and platform, e.g. "2.0.0-p247 (x86_64-darwin12.4.0)". + property 'Ruby version' do + "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_PLATFORM})" + end # The RubyGems version, if it's installed. property 'RubyGems version' do diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb index 2cbb0a435c..290634290f 100644 --- a/railties/lib/rails/source_annotation_extractor.rb +++ b/railties/lib/rails/source_annotation_extractor.rb @@ -82,7 +82,7 @@ class SourceAnnotationExtractor case item when /\.(builder|rb|coffee|rake)$/ /#\s*(#{tag}):?\s*(.*)$/ - when /\.(css|scss|js)$/ + when /\.(css|scss|sass|js)$/ /\/\/\s*(#{tag}):?\s*(.*)$/ when /\.erb$/ /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/ diff --git a/railties/lib/rails/tasks/engine.rake b/railties/lib/rails/tasks/engine.rake index 70370be3f5..16ad1bfc84 100644 --- a/railties/lib/rails/tasks/engine.rake +++ b/railties/lib/rails/tasks/engine.rake @@ -26,7 +26,7 @@ namespace :db do desc "Display status of migrations" app_task "migrate:status" - desc 'Create the database from config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config)' + desc 'Create the database from config/database.yml for the current Rails.env (use db:create:all to create all databases in the config)' app_task "create" app_task "create:all" diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index 2116330b45..bd4e7c33e0 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -1,6 +1,6 @@ namespace :rails do - desc "Update configs and some other initially generated files (or use just update:configs, update:bin, or update:application_controller)" - task update: [ "update:configs", "update:bin", "update:application_controller" ] + desc "Update configs and some other initially generated files (or use just update:configs or update:bin)" + task update: [ "update:configs", "update:bin" ] desc "Applies the template supplied by LOCATION=(/path/to/template) or URL" task :template do @@ -47,7 +47,7 @@ namespace :rails do gen = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: Rails.root File.exists?(Rails.root.join("config", "application.rb")) ? - gen.send(:app_const) : gen.send(:valid_app_const?) + gen.send(:app_const) : gen.send(:valid_const?) gen end end @@ -62,15 +62,5 @@ namespace :rails do task :bin do invoke_from_app_generator :create_bin_files end - - # desc "Rename application.rb to application_controller.rb" - task :application_controller do - old_style = Rails.root + '/app/controllers/application.rb' - new_style = Rails.root + '/app/controllers/application_controller.rb' - if File.exists?(old_style) && !File.exists?(new_style) - FileUtils.mv(old_style, new_style) - puts "#{old_style} has been renamed to #{new_style}, update your SCM as necessary" - end - end end end diff --git a/railties/lib/rails/templates/rails/welcome/index.html.erb b/railties/lib/rails/templates/rails/welcome/index.html.erb index 4c4c80ecda..eb620caa00 100644 --- a/railties/lib/rails/templates/rails/welcome/index.html.erb +++ b/railties/lib/rails/templates/rails/welcome/index.html.erb @@ -232,8 +232,8 @@ </li> <li> - <h2>Create your database</h2> - <p>Run <code>rake db:create</code> to create your database. If you're not using SQLite (the default), edit <span class="filename">config/database.yml</span> with your username and password.</p> + <h2>Configure your database</h2> + <p>If you're not using SQLite (the default), edit <span class="filename">config/database.yml</span> with your username and password.</p> </li> </ol> </div> diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index 739e4ad992..46f7466551 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -6,6 +6,7 @@ require 'active_support/testing/autorun' require 'active_support/test_case' require 'action_controller/test_case' require 'action_dispatch/testing/integration' +require 'rails/generators/test_case' # Config Rails backtrace in tests. require 'rails/backtrace_cleaner' diff --git a/railties/lib/rails/test_unit/sub_test_task.rb b/railties/lib/rails/test_unit/sub_test_task.rb index a463380e2d..d9bffba4d7 100644 --- a/railties/lib/rails/test_unit/sub_test_task.rb +++ b/railties/lib/rails/test_unit/sub_test_task.rb @@ -2,13 +2,53 @@ require 'rake/testtask' module Rails class TestTask < Rake::TestTask # :nodoc: all + # A utility class which is used primarily in "rails/test_unit/testing.rake" + # to help define rake tasks corresponding to <tt>rake test</tt>. + # + # This class takes a TestInfo class and defines the appropriate rake task + # based on the information, then invokes it. + class TestCreator + def initialize(info) + @info = info + end + + def invoke_rake_task + if @info.files.any? + create_and_run_single_test + reset_application_tasks + else + Rake::Task[ENV['TEST'] ? 'test:single' : 'test:run'].invoke + end + end + + private + + def create_and_run_single_test + Rails::TestTask.new('test:single') { |t| + t.test_files = @info.files + } + ENV['TESTOPTS'] ||= @info.opts + Rake::Task['test:single'].invoke + end + + def reset_application_tasks + Rake.application.top_level_tasks.replace @info.tasks + end + end + + # This is a utility class used by the <tt>TestTask::TestCreator</tt> class. + # This class takes a set of test tasks and checks to see if they correspond + # to test files (or can be transformed into test files). Calling <tt>files</tt> + # provides the set of test files and is used when initializing tests after + # a call to <tt>rake test</tt>. class TestInfo def initialize(tasks) @tasks = tasks + @files = nil end def files - @tasks.map { |task| + @files ||= @tasks.map { |task| [task, translate(task)].find { |file| test_file?(file) } }.compact end @@ -53,8 +93,9 @@ module Rails end end - def self.test_info(tasks) - TestInfo.new tasks + def self.test_creator(tasks) + info = TestInfo.new(tasks) + TestCreator.new(info) end def initialize(name = :test) diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake index 9263b9b81d..547846c833 100644 --- a/railties/lib/rails/test_unit/testing.rake +++ b/railties/lib/rails/test_unit/testing.rake @@ -1,65 +1,12 @@ require 'rbconfig' require 'rake/testtask' require 'rails/test_unit/sub_test_task' -require 'active_support/deprecation' - -TEST_CHANGES_SINCE = Time.now - 600 - -# Look up tests for recently modified sources. -def recent_tests(source_pattern, test_path, touched_since = 10.minutes.ago) - FileList[source_pattern].map do |path| - if File.mtime(path) > touched_since - tests = [] - source_dir = File.dirname(path).split("/") - source_file = File.basename(path, '.rb') - - # Support subdirs in app/models and app/controllers - modified_test_path = source_dir.length > 2 ? "#{test_path}/" << source_dir[1..source_dir.length].join('/') : test_path - - # For modified files in app/ run the tests for it. ex. /test/controllers/account_controller.rb - test = "#{modified_test_path}/#{source_file}_test.rb" - tests.push test if File.exist?(test) - - # For modified files in app, run tests in subdirs too. ex. /test/controllers/account/*_test.rb - test = "#{modified_test_path}/#{File.basename(path, '.rb').sub("_controller","")}" - FileList["#{test}/*_test.rb"].each { |f| tests.push f } if File.exist?(test) - - return tests - - end - end.flatten.compact -end - - -# Recreated here from Active Support because :uncommitted needs it before Rails is available -module Kernel - remove_method :silence_stderr # Removing old method to prevent method redefined warning - def silence_stderr - old_stderr = STDERR.dup - STDERR.reopen(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null') - STDERR.sync = true - yield - ensure - STDERR.reopen(old_stderr) - end -end task default: :test desc 'Runs test:units, test:functionals, test:integration together' task :test do - info = Rails::TestTask.test_info Rake.application.top_level_tasks - if info.files.any? - Rails::TestTask.new('test:single') { |t| - t.test_files = info.files - } - ENV['TESTOPTS'] ||= info.opts - Rake.application.top_level_tasks.replace info.tasks - - Rake::Task['test:single'].invoke - else - Rake::Task[ENV['TEST'] ? 'test:single' : 'test:run'].invoke - end + Rails::TestTask.test_creator(Rake.application.top_level_tasks).invoke_rake_task end namespace :test do @@ -67,7 +14,7 @@ namespace :test do # Placeholder task for other Railtie and plugins to enhance. See Active Record for an example. end - task :run => ['test:units', 'test:functionals', 'test:integration'] + task :run => ['test:units', 'test:functionals', 'test:generators', 'test:integration'] # Inspired by: http://ngauthier.com/2012/02/quick-tests-with-bash.html desc "Run tests quickly by merging all types and not resetting db" @@ -80,45 +27,6 @@ namespace :test do task :db => %w[db:test:prepare test:all] end - # Display deprecation message - task :deprecated do - ActiveSupport::Deprecation.warn "`rake #{ARGV.first}` is deprecated with no replacement." - end - - Rake::TestTask.new(recent: ["test:deprecated", "test:prepare"]) do |t| - since = TEST_CHANGES_SINCE - touched = FileList['test/**/*_test.rb'].select { |path| File.mtime(path) > since } + - recent_tests('app/models/**/*.rb', 'test/models', since) + - recent_tests('app/models/**/*.rb', 'test/unit', since) + - recent_tests('app/controllers/**/*.rb', 'test/controllers', since) + - recent_tests('app/controllers/**/*.rb', 'test/functional', since) - - t.test_files = touched.uniq - end - Rake::Task['test:recent'].comment = "Deprecated; Test recent changes" - - Rake::TestTask.new(uncommitted: ["test:deprecated", "test:prepare"]) do |t| - def t.file_list - if File.directory?(".svn") - changed_since_checkin = silence_stderr { `svn status` }.split.map { |path| path.chomp[7 .. -1] } - elsif system "git rev-parse --git-dir 2>&1 >/dev/null" - changed_since_checkin = silence_stderr { `git ls-files --modified --others --exclude-standard` }.split.map { |path| path.chomp } - else - abort "Not a Subversion or Git checkout." - end - - models = changed_since_checkin.select { |path| path =~ /app[\\\/]models[\\\/].*\.rb$/ } - controllers = changed_since_checkin.select { |path| path =~ /app[\\\/]controllers[\\\/].*\.rb$/ } - - unit_tests = models.map { |model| "test/models/#{File.basename(model, '.rb')}_test.rb" } + - models.map { |model| "test/unit/#{File.basename(model, '.rb')}_test.rb" } + - functional_tests = controllers.map { |controller| "test/controllers/#{File.basename(controller, '.rb')}_test.rb" } + - controllers.map { |controller| "test/functional/#{File.basename(controller, '.rb')}_test.rb" } - (unit_tests + functional_tests).uniq.select { |file| File.exist?(file) } - end - end - Rake::Task['test:uncommitted'].comment = "Deprecated; Test changes since last checkin (only Subversion and Git)" - Rails::TestTask.new(single: "test:prepare") ["models", "helpers", "controllers", "mailers", "integration"].each do |name| @@ -127,6 +35,10 @@ namespace :test do end end + Rails::TestTask.new(generators: "test:prepare") do |t| + t.pattern = "test/lib/generators/**/*_test.rb" + end + Rails::TestTask.new(units: "test:prepare") do |t| t.pattern = 'test/{models,helpers,unit}/**/*_test.rb' end diff --git a/railties/test/application/asset_debugging_test.rb b/railties/test/application/asset_debugging_test.rb index b3b40448c0..9a571fac3a 100644 --- a/railties/test/application/asset_debugging_test.rb +++ b/railties/test/application/asset_debugging_test.rb @@ -14,7 +14,7 @@ module ApplicationTests app_file "app/views/posts/index.html.erb", "<%= javascript_include_tag 'application' %>" app_file "config/routes.rb", <<-RUBY - AppTemplate::Application.routes.draw do + Rails.application.routes.draw do get '/posts', to: "posts#index" end RUBY diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb index 34432eac3a..4de8fcaa38 100644 --- a/railties/test/application/assets_test.rb +++ b/railties/test/application/assets_test.rb @@ -45,7 +45,7 @@ module ApplicationTests app_file "app/assets/javascripts/demo.js.erb", "a = <%= image_path('rails.png').inspect %>;" app_file 'config/routes.rb', <<-RUBY - AppTemplate::Application.routes.draw do + Rails.application.routes.draw do get '*path', to: lambda { |env| [200, { "Content-Type" => "text/html" }, ["Not an asset"]] } end RUBY @@ -91,7 +91,7 @@ module ApplicationTests class UsersController < ApplicationController; end eoruby app_file "app/models/user.rb", <<-eoruby - class User < ActiveRecord::Base; end + class User < ActiveRecord::Base; raise 'should not be reached'; end eoruby ENV['RAILS_ENV'] = 'production' @@ -165,6 +165,29 @@ module ApplicationTests assert_file_exists("#{app_path}/public/assets/something-*.js") end + test 'precompile use assets defined in app env config' do + add_to_env_config 'production', 'config.assets.precompile = [ "something.js" ]' + + app_file 'app/assets/javascripts/something.js.erb', 'alert();' + + precompile! 'RAILS_ENV=production' + + assert_file_exists("#{app_path}/public/assets/something-*.js") + end + + test 'precompile use assets defined in app config and reassigned in app env config' do + add_to_config 'config.assets.precompile = [ "something.js" ]' + add_to_env_config 'production', 'config.assets.precompile += [ "another.js" ]' + + app_file 'app/assets/javascripts/something.js.erb', 'alert();' + app_file 'app/assets/javascripts/another.js.erb', 'alert();' + + precompile! 'RAILS_ENV=production' + + assert_file_exists("#{app_path}/public/assets/something-*.js") + assert_file_exists("#{app_path}/public/assets/another-*.js") + end + test "asset pipeline should use a Sprockets::Index when config.assets.digest is true" do add_to_config "config.assets.digest = true" add_to_config "config.action_controller.perform_caching = false" @@ -313,7 +336,7 @@ module ApplicationTests app_file "app/assets/javascripts/demo.js.erb", "<%= :alert %>();" app_file "config/routes.rb", <<-RUBY - AppTemplate::Application.routes.draw do + Rails.application.routes.draw do get '/omg', :to => "omg#index" end RUBY @@ -376,18 +399,6 @@ module ApplicationTests assert_equal "Post;\n", File.read(Dir["#{app_path}/public/assets/application-*.js"].first) end - test "assets can't access model information when precompiling if not initializing the app" do - app_file "app/models/post.rb", "class Post; end" - app_file "app/assets/javascripts/application.js", "//= require_tree ." - app_file "app/assets/javascripts/xmlhr.js.erb", "<%= defined?(Post) || :NoPost %>" - - add_to_config "config.assets.digest = false" - add_to_config "config.assets.initialize_on_precompile = false" - - precompile! - assert_equal "NoPost;\n", File.read(Dir["#{app_path}/public/assets/application-*.js"].first) - end - test "initialization on the assets group should set assets_dir" do require "#{app_path}/config/application" Rails.application.initialize!(:assets) @@ -475,7 +486,7 @@ module ApplicationTests app_file "app/views/posts/index.html.erb", "<%= javascript_include_tag 'application' %>" app_file "config/routes.rb", <<-RUBY - AppTemplate::Application.routes.draw do + Rails.application.routes.draw do get '/posts', :to => "posts#index" end RUBY diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index bc794e1602..83104acf3c 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -41,7 +41,7 @@ module ApplicationTests test "allows me to configure default url options for ActionMailer" do app_file "config/environments/development.rb", <<-RUBY - AppTemplate::Application.configure do + Rails.application.configure do config.action_mailer.default_url_options = { :host => "test.rails" } end RUBY @@ -52,7 +52,7 @@ module ApplicationTests test "does not include url helpers as action methods" do app_file "config/routes.rb", <<-RUBY - AppTemplate::Application.routes.draw do + Rails.application.routes.draw do get "/foo", :to => lambda { |env| [200, {}, []] }, :as => :foo end RUBY @@ -115,7 +115,7 @@ module ApplicationTests RUBY app_file "config/routes.rb", <<-RUBY - AppTemplate::Application.routes.draw do + Rails.application.routes.draw do get "/:controller(/:action)" end RUBY @@ -182,7 +182,7 @@ module ApplicationTests end require "#{app_path}/config/environment" ActiveRecord::Base.connection.drop_table("posts") # force drop posts table for test. - assert ActiveRecord::Base.connection.schema_cache.tables["posts"] + assert ActiveRecord::Base.connection.schema_cache.tables("posts") end test "expire schema cache dump" do @@ -192,7 +192,7 @@ module ApplicationTests end silence_warnings { require "#{app_path}/config/environment" - assert !ActiveRecord::Base.connection.schema_cache.tables["posts"] + assert !ActiveRecord::Base.connection.schema_cache.tables("posts") } end diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb index 17d0b10b70..97df073ec7 100644 --- a/railties/test/application/initializers/i18n_test.rb +++ b/railties/test/application/initializers/i18n_test.rb @@ -84,7 +84,7 @@ en: RUBY app_file 'config/routes.rb', <<-RUBY - AppTemplate::Application.routes.draw do + Rails.application.routes.draw do get '/i18n', :to => lambda { |env| [200, {}, [Foo.instance_variable_get('@foo')]] } end RUBY @@ -108,7 +108,7 @@ en: YAML app_file 'config/routes.rb', <<-RUBY - AppTemplate::Application.routes.draw do + Rails.application.routes.draw do get '/i18n', :to => lambda { |env| [200, {}, [I18n.t(:foo)]] } end RUBY diff --git a/railties/test/application/initializers/load_path_test.rb b/railties/test/application/initializers/load_path_test.rb index 0c66213caa..b36628ee37 100644 --- a/railties/test/application/initializers/load_path_test.rb +++ b/railties/test/application/initializers/load_path_test.rb @@ -75,7 +75,7 @@ module ApplicationTests $initialize_test_set_from_env = nil app_file "config/environments/development.rb", <<-RUBY $initialize_test_set_from_env = 'success' - AppTemplate::Application.configure do + Rails.application.configure do config.cache_classes = true config.time_zone = "Brasilia" end @@ -89,8 +89,8 @@ module ApplicationTests require "#{app_path}/config/environment" assert_equal "success", $initialize_test_set_from_env - assert AppTemplate::Application.config.cache_classes - assert_equal "Brasilia", AppTemplate::Application.config.time_zone + assert Rails.application.config.cache_classes + assert_equal "Brasilia", Rails.application.config.time_zone end end end diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb index 17926ac444..4f30f30f95 100644 --- a/railties/test/application/loading_test.rb +++ b/railties/test/application/loading_test.rb @@ -36,7 +36,7 @@ class LoadingTest < ActiveSupport::TestCase test "models without table do not panic on scope definitions when loaded" do app_file "app/models/user.rb", <<-MODEL class User < ActiveRecord::Base - default_scope where(published: true) + default_scope { where(published: true) } end MODEL @@ -213,7 +213,7 @@ class LoadingTest < ActiveSupport::TestCase app_file 'config/routes.rb', <<-RUBY $counter ||= 1 $counter *= 2 - AppTemplate::Application.routes.draw do + Rails.application.routes.draw do get '/c', to: lambda { |env| User; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] } end RUBY diff --git a/railties/test/application/middleware/cache_test.rb b/railties/test/application/middleware/cache_test.rb index b8e0c9be60..b4db840e68 100644 --- a/railties/test/application/middleware/cache_test.rb +++ b/railties/test/application/middleware/cache_test.rb @@ -45,7 +45,7 @@ module ApplicationTests RUBY app_file 'config/routes.rb', <<-RUBY - AppTemplate::Application.routes.draw do + Rails.application.routes.draw do get ':controller(/:action)' end RUBY diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index d8076c7151..31a35a09bb 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -19,7 +19,7 @@ module ApplicationTests end test "default middleware stack" do - add_to_config "config.action_dispatch.x_sendfile_header = 'X-Sendfile'" + add_to_config "config.active_record.migration_error = :page_load" boot! @@ -37,6 +37,7 @@ module ApplicationTests "ActionDispatch::RemoteIp", "ActionDispatch::Reloader", "ActionDispatch::Callbacks", + "ActiveRecord::Migration::CheckPending", "ActiveRecord::ConnectionAdapters::ConnectionManagement", "ActiveRecord::QueryCache", "ActionDispatch::Cookies", @@ -49,12 +50,6 @@ module ApplicationTests ], middleware end - test "Rack::Sendfile is not included by default" do - boot! - - assert !middleware.include?("Rack::Sendfile"), "Rack::Sendfile is not included in the default stack unless you set config.action_dispatch.x_sendfile_header" - end - test "Rack::Cache is not included by default" do boot! @@ -69,6 +64,14 @@ module ApplicationTests assert_equal "Rack::Cache", middleware.first end + test "ActiveRecord::Migration::CheckPending is present when active_record.migration_error is set to :page_load" do + add_to_config "config.active_record.migration_error = :page_load" + + boot! + + assert middleware.include?("ActiveRecord::Migration::CheckPending") + end + test "ActionDispatch::SSL is present when force_ssl is set" do add_to_config "config.force_ssl = true" boot! @@ -80,7 +83,7 @@ module ApplicationTests add_to_config "config.ssl_options = { host: 'example.com' }" boot! - assert_equal AppTemplate::Application.middleware.first.args, [{host: 'example.com'}] + assert_equal Rails.application.middleware.first.args, [{host: 'example.com'}] end test "removing Active Record omits its middleware" do @@ -88,6 +91,7 @@ module ApplicationTests boot! assert !middleware.include?("ActiveRecord::ConnectionAdapters::ConnectionManagement") assert !middleware.include?("ActiveRecord::QueryCache") + assert !middleware.include?("ActiveRecord::Migration::CheckPending") end test "removes lock if cache classes is set" do @@ -135,7 +139,7 @@ module ApplicationTests end test "insert middleware after" do - add_to_config "config.middleware.insert_after ActionDispatch::Static, Rack::Config" + add_to_config "config.middleware.insert_after Rack::Sendfile, Rack::Config" boot! assert_equal "Rack::Config", middleware.second end @@ -143,16 +147,16 @@ module ApplicationTests test "Rails.cache does not respond to middleware" do add_to_config "config.cache_store = :memory_store" boot! - assert_equal "Rack::Runtime", middleware.third + assert_equal "Rack::Runtime", middleware.fourth end test "Rails.cache does respond to middleware" do boot! - assert_equal "Rack::Runtime", middleware.fourth + assert_equal "Rack::Runtime", middleware.fifth end test "insert middleware before" do - add_to_config "config.middleware.insert_before ActionDispatch::Static, Rack::Config" + add_to_config "config.middleware.insert_before Rack::Sendfile, Rack::Config" boot! assert_equal "Rack::Config", middleware.first end @@ -217,7 +221,7 @@ module ApplicationTests end def middleware - AppTemplate::Application.middleware.map(&:klass).map(&:name) + Rails.application.middleware.map(&:klass).map(&:name) end end end diff --git a/railties/test/application/multiple_applications_test.rb b/railties/test/application/multiple_applications_test.rb new file mode 100644 index 0000000000..03c343c475 --- /dev/null +++ b/railties/test/application/multiple_applications_test.rb @@ -0,0 +1,148 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + class MultipleApplicationsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app(initializers: true) + boot_rails + require "#{rails_root}/config/environment" + end + + def teardown + teardown_app + end + + def test_cloning_an_application_makes_a_shallow_copy_of_config + clone = Rails.application.clone + + assert_equal Rails.application.config, clone.config, "The cloned application should get a copy of the config" + assert_equal Rails.application.config.secret_key_base, clone.config.secret_key_base, "The base secret key on the config should be the same" + end + + def test_initialization_of_multiple_copies_of_same_application + application1 = AppTemplate::Application.new + application2 = AppTemplate::Application.new + + assert_not_equal Rails.application.object_id, application1.object_id, "New applications should not be the same as the original application" + assert_not_equal Rails.application.object_id, application2.object_id, "New applications should not be the same as the original application" + end + + def test_initialization_of_application_with_previous_config + application1 = AppTemplate::Application.new(config: Rails.application.config) + application2 = AppTemplate::Application.new + + assert_equal Rails.application.config, application1.config, "Creating a new application while setting an initial config should result in the same config" + assert_not_equal Rails.application.config, application2.config, "New applications without setting an initial config should not have the same config" + end + + def test_initialization_of_application_with_previous_railties + application1 = AppTemplate::Application.new(railties: Rails.application.railties) + application2 = AppTemplate::Application.new + + assert_equal Rails.application.railties, application1.railties + assert_not_equal Rails.application.railties, application2.railties + end + + def test_initialize_new_application_with_all_previous_initialization_variables + application1 = AppTemplate::Application.new( + config: Rails.application.config, + railties: Rails.application.railties, + routes_reloader: Rails.application.routes_reloader, + reloaders: Rails.application.reloaders, + routes: Rails.application.routes, + helpers: Rails.application.helpers, + app_env_config: Rails.application.env_config + ) + + assert_equal Rails.application.config, application1.config + assert_equal Rails.application.railties, application1.railties + assert_equal Rails.application.routes_reloader, application1.routes_reloader + assert_equal Rails.application.reloaders, application1.reloaders + assert_equal Rails.application.routes, application1.routes + assert_equal Rails.application.helpers, application1.helpers + assert_equal Rails.application.env_config, application1.env_config + end + + def test_rake_tasks_defined_on_different_applications_go_to_the_same_class + $run_count = 0 + + application1 = AppTemplate::Application.new + application1.rake_tasks do + $run_count += 1 + end + + application2 = AppTemplate::Application.new + application2.rake_tasks do + $run_count += 1 + end + + require "#{app_path}/config/environment" + + assert_equal 0, $run_count, "The count should stay at zero without any calls to the rake tasks" + require 'rake' + require 'rake/testtask' + require 'rdoc/task' + Rails.application.load_tasks + assert_equal 2, $run_count, "Calling a rake task should result in two increments to the count" + end + + def test_multiple_applications_can_be_initialized + assert_nothing_raised { AppTemplate::Application.new } + end + + def test_initializers_run_on_different_applications_go_to_the_same_class + application1 = AppTemplate::Application.new + $run_count = 0 + + AppTemplate::Application.initializer :init0 do + $run_count += 1 + end + + application1.initializer :init1 do + $run_count += 1 + end + + AppTemplate::Application.new.initializer :init2 do + $run_count += 1 + end + + assert_equal 0, $run_count, "Without loading the initializers, the count should be 0" + + # Set config.eager_load to false so that a eager_load warning doesn't pop up + AppTemplate::Application.new { config.eager_load = false }.initialize! + + assert_equal 3, $run_count, "There should have been three initializers that incremented the count" + end + + def test_runners_run_on_different_applications_go_to_the_same_class + $run_count = 0 + AppTemplate::Application.runner { $run_count += 1 } + AppTemplate::Application.new.runner { $run_count += 1 } + + assert_equal 0, $run_count, "Without loading the runners, the count should be 0" + Rails.application.load_runner + assert_equal 2, $run_count, "There should have been two runners that increment the count" + end + + def test_isolate_namespace_on_an_application + assert_nil Rails.application.railtie_namespace, "Before isolating namespace, the railtie namespace should be nil" + Rails.application.isolate_namespace(AppTemplate) + assert_equal Rails.application.railtie_namespace, AppTemplate, "After isolating namespace, we should have a namespace" + end + + def test_inserting_configuration_into_application + app = AppTemplate::Application.new(config: Rails.application.config) + new_config = Rails::Application::Configuration.new("root_of_application") + new_config.secret_key_base = "some_secret_key_dude" + app.config.secret_key_base = "a_different_secret_key" + + assert_equal "a_different_secret_key", app.config.secret_key_base, "The configuration's secret key should be set." + app.config = new_config + assert_equal "some_secret_key_dude", app.config.secret_key_base, "The configuration's secret key should have changed." + assert_equal "root_of_application", app.config.root, "The root should have changed to the new config's root." + assert_equal new_config, app.config, "The application's config should have changed to the new config." + end + end +end diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb index 3508f4225a..9a92c5f6ff 100644 --- a/railties/test/application/rake/notes_test.rb +++ b/railties/test/application/rake/notes_test.rb @@ -24,6 +24,7 @@ module ApplicationTests app_file "app/assets/javascripts/application.js", "// TODO: note in js" app_file "app/assets/stylesheets/application.css", "// TODO: note in css" app_file "app/assets/stylesheets/application.css.scss", "// TODO: note in scss" + app_file "app/assets/stylesheets/application.css.sass", "// TODO: note in sass" app_file "app/controllers/application_controller.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in ruby" app_file "lib/tasks/task.rake", "# TODO: note in rake" @@ -46,9 +47,10 @@ module ApplicationTests assert_match(/note in js/, output) assert_match(/note in css/, output) assert_match(/note in scss/, output) + assert_match(/note in sass/, output) assert_match(/note in rake/, output) - assert_equal 9, lines.size + assert_equal 10, lines.size lines.each do |line| assert_equal 4, line[0].size diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index dc2684ae0d..c1cb1c1eba 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -9,7 +9,6 @@ module ApplicationTests def setup build_app boot_rails - FileUtils.rm_rf("#{app_path}/config/environments") end def teardown @@ -30,7 +29,7 @@ module ApplicationTests app_file "config/environment.rb", <<-RUBY SuperMiddleware = Struct.new(:app) - AppTemplate::Application.configure do + Rails.application.configure do config.middleware.use SuperMiddleware end @@ -56,10 +55,8 @@ module ApplicationTests assert_match "Doing something...", output end - def test_does_not_explode_when_accessing_a_model_with_eager_load + def test_does_not_explode_when_accessing_a_model add_to_config <<-RUBY - config.eager_load = true - rake_tasks do task do_nothing: :environment do Hello.new.world @@ -67,82 +64,48 @@ module ApplicationTests end RUBY - app_file "app/models/hello.rb", <<-RUBY - class Hello - def world - puts "Hello world" + app_file 'app/models/hello.rb', <<-RUBY + class Hello + def world + puts 'Hello world' + end end - end RUBY - output = Dir.chdir(app_path){ `rake do_nothing` } - assert_match "Hello world", output + output = Dir.chdir(app_path) { `rake do_nothing` } + assert_match 'Hello world', output end - def test_should_not_eager_load_model_path_for_rake + def test_should_not_eager_load_model_for_rake add_to_config <<-RUBY - config.eager_load = true - rake_tasks do task do_nothing: :environment do end end RUBY - app_file "app/models/hello.rb", <<-RUBY - raise 'should not be pre-required for rake even `eager_load=true`' + add_to_env_config 'production', <<-RUBY + config.eager_load = true RUBY - Dir.chdir(app_path){ `rake do_nothing` } - end - - def test_code_statistics_sanity - assert_match "Code LOC: 5 Test LOC: 0 Code to Test Ratio: 1:0.0", - Dir.chdir(app_path){ `rake stats` } - end - - def test_rake_test_uncommitted_always_find_git_in_parent_dir - return "FIXME :'(" - app_name = File.basename(app_path) - app_dir = File.dirname(app_path) - moved_app_name = app_name + '_moved' - - Dir.chdir(app_dir) do - # Go from "./app/" to "./app/app_moved" - FileUtils.mv(app_name, moved_app_name) - FileUtils.mkdir(app_name) - FileUtils.mv(moved_app_name, app_name) - # Initialize the git repository and start the test. - Dir.chdir(app_name) do - `git init` - Dir.chdir(moved_app_name){ `rake db:migrate` } - silence_stderr { Dir.chdir(moved_app_name) { `rake test:uncommitted` } } - assert_equal 0, $?.exitstatus - end - end - end + app_file 'app/models/hello.rb', <<-RUBY + raise 'should not be pre-required for rake even eager_load=true' + RUBY - def test_rake_test_uncommitted_fails_with_no_scm - Dir.chdir(app_path){ `rake db:migrate` } Dir.chdir(app_path) do - silence_stderr { `rake test:uncommitted` } - assert_equal 1, $?.exitstatus + assert system('rake do_nothing RAILS_ENV=production'), + 'should not be pre-required for rake even eager_load=true' end end - def test_rake_test_deprecation_messages - Dir.chdir(app_path){ `rails generate scaffold user name:string` } - Dir.chdir(app_path){ `rake db:migrate` } - - %w(recent uncommitted).each do |test_suit_name| - output = Dir.chdir(app_path) { `rake test:#{test_suit_name} 2>&1` } - assert_match(/DEPRECATION WARNING: `rake test:#{test_suit_name}` is deprecated/, output) - end + def test_code_statistics_sanity + assert_match "Code LOC: 5 Test LOC: 0 Code to Test Ratio: 1:0.0", + Dir.chdir(app_path){ `rake stats` } end def test_rake_routes_calls_the_route_inspector app_file "config/routes.rb", <<-RUBY - AppTemplate::Application.routes.draw do + Rails.application.routes.draw do get '/cart', to: 'cart#show' end RUBY @@ -153,7 +116,7 @@ module ApplicationTests def test_rake_routes_with_controller_environment app_file "config/routes.rb", <<-RUBY - AppTemplate::Application.routes.draw do + Rails.application.routes.draw do get '/cart', to: 'cart#show' get '/basketball', to: 'basketball#index' end @@ -166,7 +129,7 @@ module ApplicationTests def test_rake_routes_displays_message_when_no_routes_are_defined app_file "config/routes.rb", <<-RUBY - AppTemplate::Application.routes.draw do + Rails.application.routes.draw do end RUBY diff --git a/railties/test/application/rendering_test.rb b/railties/test/application/rendering_test.rb index 588d64dde9..b01febd768 100644 --- a/railties/test/application/rendering_test.rb +++ b/railties/test/application/rendering_test.rb @@ -17,7 +17,7 @@ module ApplicationTests test "Unknown format falls back to HTML template" do app_file 'config/routes.rb', <<-RUBY - AppTemplate::Application.routes.draw do + Rails.application.routes.draw do get 'pages/:id', to: 'pages#show' end RUBY diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb index 1a4e2d4123..8576a2b738 100644 --- a/railties/test/application/routing_test.rb +++ b/railties/test/application/routing_test.rb @@ -372,6 +372,51 @@ module ApplicationTests end end + test 'named routes are cleared when reloading' do + app('development') + + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render text: "foo" + end + end + RUBY + + controller :bar, <<-RUBY + class BarController < ApplicationController + def index + render text: "bar" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':locale/foo', to: 'foo#index', as: 'foo' + end + RUBY + + get '/en/foo' + assert_equal 'foo', last_response.body + assert_equal '/en/foo', Rails.application.routes.url_helpers.foo_path(:locale => 'en') + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':locale/bar', to: 'bar#index', as: 'foo' + end + RUBY + + Rails.application.reload_routes! + + get '/en/foo' + assert_equal 404, last_response.status + + get '/en/bar' + assert_equal 'bar', last_response.body + assert_equal '/en/bar', Rails.application.routes.url_helpers.foo_path(:locale => 'en') + end + test 'resource routing with irregular inflection' do app_file 'config/initializers/inflection.rb', <<-RUBY ActiveSupport::Inflector.inflections do |inflect| diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index e992534938..42b6275932 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -53,9 +53,10 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_assets run_generator - assert_file "app/views/layouts/application.html.erb", /stylesheet_link_tag\s+"application"/ - assert_file "app/views/layouts/application.html.erb", /javascript_include_tag\s+"application"/ - assert_file "app/assets/stylesheets/application.css" + + assert_file("app/views/layouts/application.html.erb", /stylesheet_link_tag\s+"application", media: "all", "data-turbolinks-track" => true/) + assert_file("app/views/layouts/application.html.erb", /javascript_include_tag\s+"application", "data-turbolinks-track" => true/) + assert_file("app/assets/stylesheets/application.css") end def test_invalid_application_name_raises_an_error @@ -295,6 +296,10 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "app/assets/javascripts/application.js" do |contents| assert_no_match %r{^//=\s+require\s}, contents end + assert_file "app/views/layouts/application.html.erb" do |contents| + assert_match(/stylesheet_link_tag\s+"application", media: "all" %>/, contents) + assert_match(/javascript_include_tag\s+"application" \%>/, contents) + end assert_file "Gemfile" do |content| assert_match(/coffee-rails/, content) end diff --git a/railties/test/generators/generator_generator_test.rb b/railties/test/generators/generator_generator_test.rb index f4c975fc18..dcfeaaa8e0 100644 --- a/railties/test/generators/generator_generator_test.rb +++ b/railties/test/generators/generator_generator_test.rb @@ -16,6 +16,9 @@ class GeneratorGeneratorTest < Rails::Generators::TestCase assert_file "lib/generators/awesome/awesome_generator.rb", /class AwesomeGenerator < Rails::Generators::NamedBase/ + assert_file "test/lib/generators/awesome_generator_test.rb", + /class AwesomeGeneratorTest < Rails::Generators::TestCase/, + /require 'generators\/awesome\/awesome_generator'/ end def test_namespaced_generator_skeleton @@ -29,6 +32,9 @@ class GeneratorGeneratorTest < Rails::Generators::TestCase assert_file "lib/generators/rails/awesome/awesome_generator.rb", /class Rails::AwesomeGenerator < Rails::Generators::NamedBase/ + assert_file "test/lib/generators/rails/awesome_generator_test.rb", + /class Rails::AwesomeGeneratorTest < Rails::Generators::TestCase/, + /require 'generators\/rails\/awesome\/awesome_generator'/ end def test_generator_skeleton_is_created_without_file_name_namespace @@ -42,6 +48,9 @@ class GeneratorGeneratorTest < Rails::Generators::TestCase assert_file "lib/generators/awesome_generator.rb", /class AwesomeGenerator < Rails::Generators::NamedBase/ + assert_file "test/lib/generators/awesome_generator_test.rb", + /class AwesomeGeneratorTest < Rails::Generators::TestCase/, + /require 'generators\/awesome_generator'/ end def test_namespaced_generator_skeleton_without_file_name_namespace @@ -55,5 +64,8 @@ class GeneratorGeneratorTest < Rails::Generators::TestCase assert_file "lib/generators/rails/awesome_generator.rb", /class Rails::AwesomeGenerator < Rails::Generators::NamedBase/ + assert_file "test/lib/generators/rails/awesome_generator_test.rb", + /class Rails::AwesomeGeneratorTest < Rails::Generators::TestCase/, + /require 'generators\/rails\/awesome_generator'/ end end diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index 32c7612a8f..068eb66bc6 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'rails/generators/rails/plugin_new/plugin_new_generator' +require 'rails/generators/rails/plugin/plugin_generator' require 'generators/shared_generator_tests' DEFAULT_PLUGIN_FILES = %w( @@ -18,7 +18,7 @@ DEFAULT_PLUGIN_FILES = %w( test/dummy ) -class PluginNewGeneratorTest < Rails::Generators::TestCase +class PluginGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper destination File.join(Rails.root, "tmp/bukkits") arguments [destination_root] diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index d5ad978986..4b837da483 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -286,6 +286,30 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end end + def test_scaffold_generator_belongs_to + run_generator ["account", "name", "currency:belongs_to"] + + assert_file "app/models/account.rb", /belongs_to :currency/ + + assert_migration "db/migrate/create_accounts.rb" do |m| + assert_method :change, m do |up| + assert_match(/t\.string :name/, up) + assert_match(/t\.belongs_to :currency/, up) + end + end + + assert_file "app/controllers/accounts_controller.rb" do |content| + assert_instance_method :account_params, content do |m| + assert_match(/permit\(:name, :currency_id\)/, m) + end + end + + assert_file "app/views/accounts/_form.html.erb" do |content| + assert_match(/<%= f\.text_field :name %>/, content) + assert_match(/<%= f\.text_field :currency_id %>/, content) + end + end + def test_scaffold_generator_password_digest run_generator ["user", "name", "password:digest"] diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index 361784f509..5130b285a9 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -106,7 +106,7 @@ class GeneratorsTest < Rails::Generators::TestCase def test_rails_generators_help_does_not_include_app_nor_plugin_new output = capture(:stdout){ Rails::Generators.help } assert_no_match(/app/, output) - assert_no_match(/plugin_new/, output) + assert_no_match(/[^:]plugin/, output) end def test_rails_generators_with_others_information diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 68d96bae94..a3295a6e69 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -163,7 +163,7 @@ module TestHelpers RUBY app_file 'config/routes.rb', <<-RUBY - AppTemplate::Application.routes.draw do + Rails.application.routes.draw do get ':controller(/:action)' end RUBY diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 05e9c4ab6e..d095535abd 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -345,7 +345,7 @@ YAML #{RAILS_FRAMEWORK_ROOT}/activesupport/lib/active_support/locale/en.yml #{RAILS_FRAMEWORK_ROOT}/activemodel/lib/active_model/locale/en.yml #{RAILS_FRAMEWORK_ROOT}/activerecord/lib/active_record/locale/en.yml - #{RAILS_FRAMEWORK_ROOT}/actionpack/lib/action_view/locale/en.yml + #{RAILS_FRAMEWORK_ROOT}/actionview/lib/action_view/locale/en.yml #{@plugin.path}/config/locales/en.yml #{app_path}/config/locales/en.yml #{app_path}/app/locales/en.yml @@ -1236,12 +1236,6 @@ 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 c94937f964..0ef2ff2e2e 100644 --- a/railties/test/railties/mounted_engine_test.rb +++ b/railties/test/railties/mounted_engine_test.rb @@ -16,7 +16,7 @@ module ApplicationTests @metrics_plugin = engine "metrics" app_file 'config/routes.rb', <<-RUBY - AppTemplate::Application.routes.draw do + Rails.application.routes.draw do mount Weblog::Engine, :at => '/', :as => 'weblog' resources :posts get "/engine_route" => "application_generating#engine_route" diff --git a/railties/test/test_info_test.rb b/railties/test/test_info_test.rb index d5463c11de..b9c3a9c0c7 100644 --- a/railties/test/test_info_test.rb +++ b/railties/test/test_info_test.rb @@ -48,6 +48,7 @@ module Rails assert_equal ['test'], info.tasks end + private def new_test_info(tasks) Class.new(TestTask::TestInfo) { def task_defined?(task) diff --git a/tasks/release.rb b/tasks/release.rb index 0c22f812fc..66da67bfd0 100644 --- a/tasks/release.rb +++ b/tasks/release.rb @@ -1,4 +1,4 @@ -FRAMEWORKS = %w( activesupport activemodel activerecord actionpack actionmailer railties ) +FRAMEWORKS = %w( activesupport activemodel activerecord actionpack actionview actionmailer railties ) root = File.expand_path('../../', __FILE__) version = File.read("#{root}/RAILS_VERSION").strip diff --git a/tools/profile b/tools/profile index 665efe049d..fbea67492b 100755 --- a/tools/profile +++ b/tools/profile @@ -4,7 +4,6 @@ ENV['NO_RELOAD'] ||= '1' ENV['RAILS_ENV'] ||= 'development' -Gem.source_index require 'benchmark' module RequireProfiler |