diff options
117 files changed, 1186 insertions, 619 deletions
@@ -12,9 +12,6 @@ gem 'jquery-rails', '~> 2.2.0', github: 'rails/jquery-rails' gem 'turbolinks' gem 'coffee-rails', github: 'rails/coffee-rails' -# TODO: Release thor -gem 'thor', github: 'wycats/thor', branch: 'master' - gem 'activerecord-deprecated_finders', github: 'rails/activerecord-deprecated_finders', branch: 'master' # Needed for compiling the ActionDispatch::Journey parser diff --git a/README.rdoc b/README.rdoc index 91a5f27add..bb9c418e0b 100644 --- a/README.rdoc +++ b/README.rdoc @@ -27,7 +27,7 @@ and render view templates in order to generate the appropriate HTTP response. In Rails, the Controller and View layers are handled together by Action Pack. These two layers are bundled in a single package due to their heavy interdependence. -This is unlike the relationship between Active Record and Action Pack which are +This is unlike the relationship between Active Record and Action Pack, which are independent. Each of these packages can be used independently outside of Rails. You can read more about Action Pack in its {README}[link:/rails/rails/blob/master/actionpack/README.rdoc]. diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index 43bfa536c1..7e57fb39f3 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,6 +1,23 @@ ## Rails 4.0.0 (unreleased) ## -* Eager loading made to use relation's in_clause_length instead of host's one. +* Allow passing interpolations to `#default_i18n_subject`, e.g.: + + # config/locales/en.yml + en: + user_mailer: + welcome: + subject: 'Hello, %{username}' + + # app/mailers/user_mailer.rb + class UserMailer < ActionMailer::Base + def welcome(user) + mail(subject: default_i18n_subject(username: user.name)) + end + end + + *Olek Janiszewski* + +* Eager loading made to use relation's `in_clause_length` instead of host's one. Fix #8474 *Boris Staal* diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 9ba606c045..c4d2950abd 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -726,9 +726,10 @@ module ActionMailer # Translates the +subject+ using Rails I18n class under <tt>[mailer_scope, action_name]</tt> scope. # If it does not find a translation for the +subject+ under the specified scope it will default to a # humanized version of the <tt>action_name</tt>. - def default_i18n_subject #:nodoc: + # If the subject has interpolations, you can pass them through the +interpolations+ parameter. + def default_i18n_subject(interpolations = {}) mailer_scope = self.class.mailer_name.tr('/', '.') - I18n.t(:subject, scope: [mailer_scope, action_name], default: action_name.humanize) + I18n.t(:subject, interpolations.merge(scope: [mailer_scope, action_name], default: action_name.humanize)) end def collect_responses(headers) #:nodoc: diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index b06c465380..b9c56c540d 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -209,6 +209,12 @@ class BaseTest < ActiveSupport::TestCase assert_equal "New Subject!", email.subject end + test 'default subject can have interpolations' do + I18n.backend.store_translations('en', base_mailer: {with_subject_interpolations: {subject: 'Will the real %{rapper_or_impersonator} please stand up?'}}) + email = BaseMailer.with_subject_interpolations + assert_equal 'Will the real Slim Shady please stand up?', email.subject + end + test "translations are scoped properly" do I18n.backend.store_translations('en', base_mailer: {email_with_translations: {greet_user: "Hello %{name}!"}}) email = BaseMailer.email_with_translations diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb index 8fca6177bd..504ca36483 100644 --- a/actionmailer/test/mailers/base_mailer.rb +++ b/actionmailer/test/mailers/base_mailer.rb @@ -123,4 +123,8 @@ class BaseMailer < ActionMailer::Base mail(:template_name => "welcome") nil end + + def with_subject_interpolations + mail(subject: default_i18n_subject(rapper_or_impersonator: 'Slim Shady'), body: '') + end end diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index f00fc46b2e..70a1975ee9 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |s| s.add_dependency 'activesupport', version s.add_dependency 'builder', '~> 3.1.0' - s.add_dependency 'rack', '~> 1.4.3' + s.add_dependency 'rack', '~> 1.5.0' s.add_dependency 'rack-test', '~> 0.6.1' s.add_dependency 'erubis', '~> 2.7.0' diff --git a/actionpack/lib/abstract_controller/translation.rb b/actionpack/lib/abstract_controller/translation.rb index b6c484d188..db48022b9f 100644 --- a/actionpack/lib/abstract_controller/translation.rb +++ b/actionpack/lib/abstract_controller/translation.rb @@ -1,5 +1,13 @@ module AbstractController module Translation + # Delegates to <tt>I18n.translate</tt>. Also aliased as <tt>t</tt>. + # + # When the given key starts with a period, it will be scoped by the current + # controller and action. So if you call <tt>translate(".foo")</tt> from + # <tt>PeopleController#index</tt>, it will convert the call to + # <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive + # to translate many keys within the same controller / action and gives you a + # simple framework for scoping them consistently. def translate(*args) key = args.first if key.is_a?(String) && (key[0] == '.') @@ -11,6 +19,7 @@ module AbstractController end alias :t :translate + # Delegates to <tt>I18n.localize</tt>. Also aliased as <tt>l</tt>. def localize(*args) I18n.localize(*args) end diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index f41df1730b..77b173979e 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -102,15 +102,16 @@ module ActionController #:nodoc: # This is the method that defines the application behavior when a request is found to be unverified. def handle_unverified_request - request.session = NullSessionHash.new + request.session = NullSessionHash.new(request.env) request.env['action_dispatch.request.flash_hash'] = nil request.env['rack.session.options'] = { skip: true } request.env['action_dispatch.cookies'] = NullCookieJar.build(request) end class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc: - def initialize - super(nil, nil) + def initialize(env) + super(nil, env) + @data = {} @loaded = true end diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index d028c7d8c4..7e720ca6f5 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -1,6 +1,7 @@ require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/array/wrap' require 'active_support/rescuable' +require 'action_dispatch/http/upload' module ActionController # Raised when a required parameter is missing. @@ -190,8 +191,9 @@ module ActionController # # +:name+ passes it is a key of +params+ whose associated value is of type # +String+, +Symbol+, +NilClass+, +Numeric+, +TrueClass+, +FalseClass+, - # +Date+, +Time+, +DateTime+, +StringIO+, or +IO+. Otherwise, the key +:name+ - # is filtered out. + # +Date+, +Time+, +DateTime+, +StringIO+, +IO+, or + # +ActionDispatch::Http::UploadedFile+. Otherwise, the key +:name+ is + # filtered out. # # You may declare that the parameter should be an array of permitted scalars # by mapping it to an empty array: @@ -371,6 +373,7 @@ module ActionController # DateTimes are Dates, we document the type but avoid the redundant check. StringIO, IO, + ActionDispatch::Http::UploadedFile, ] def permitted_scalar?(value) diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index d8206b573d..e9cf4372e4 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -235,18 +235,39 @@ module ActionController end end + # Methods #destroy and #load! are overridden to avoid calling methods on the + # @store object, which does not exist for the TestSession class. class TestSession < Rack::Session::Abstract::SessionHash #:nodoc: DEFAULT_OPTIONS = Rack::Session::Abstract::ID::DEFAULT_OPTIONS def initialize(session = {}) super(nil, nil) - replace(session.stringify_keys) + @id = SecureRandom.hex(16) + @data = stringify_keys(session) @loaded = true end def exists? true end + + def keys + @data.keys + end + + def values + @data.values + end + + def destroy + clear + end + + private + + def load! + @id + end end # Superclass for ActionController functional tests. Functional tests allow you to diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 25edd196c3..446862aad0 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -55,7 +55,7 @@ module ActionDispatch # should really prevent that from happening def encode_params(params) if params.is_a?(String) - return params.force_encoding("UTF-8").encode! + return params.force_encoding(Encoding::UTF_8).encode! elsif !params.is_a?(Hash) return params end diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 91cf4784db..e7e8905d7e 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -137,6 +137,7 @@ module ActionDispatch # :nodoc: @committed end + # Sets the HTTP status code. def status=(status) @status = Rack::Utils.status_code(status) end @@ -145,16 +146,24 @@ module ActionDispatch # :nodoc: @content_type = content_type.to_s end - # The response code of the request + # The response code of the request. def response_code @status end - # Returns a String to ensure compatibility with Net::HTTPResponse + # Returns a string to ensure compatibility with <tt>Net::HTTPResponse</tt>. def code @status.to_s end + # Returns the corresponding message for the current HTTP status code: + # + # response.status = 200 + # response.message # => "OK" + # + # response.status = 404 + # response.message # => "Not Found" + # def message Rack::Utils::HTTP_STATUS_CODES[@status] end @@ -172,6 +181,8 @@ module ActionDispatch # :nodoc: stream.to_path end + # Returns the content of the response as a string. This contains the contents + # of any calls to <tt>render</tt>. def body strings = [] each { |part| strings << part.to_s } @@ -180,6 +191,7 @@ module ActionDispatch # :nodoc: EMPTY = " " + # Allows you to manually set or override the response body. def body=(body) @blank = true if body == EMPTY diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index 8a97248eb3..67cb7fbcb5 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -70,7 +70,7 @@ module ActionDispatch def encode_filename(filename) # Encode the filename in the utf8 encoding, unless it is nil - filename.force_encoding("UTF-8").encode! if filename + filename.force_encoding(Encoding::UTF_8).encode! if filename end end diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 6ecbb03784..0f02d230d4 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -145,6 +145,10 @@ module ActionDispatch @cookies[name.to_s] end + def fetch(name, *args, &block) + @cookies.fetch(name.to_s, *args, &block) + end + def key?(name) @cookies.key?(name.to_s) end diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index 7c12590c49..84df55fd5a 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -26,7 +26,7 @@ module ActionDispatch def generate_sid sid = SecureRandom.hex(16) - sid.encode!('UTF-8') + sid.encode!(Encoding::UTF_8) sid end diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb index a05a23d953..7bc812fd22 100644 --- a/actionpack/lib/action_dispatch/request/session.rb +++ b/actionpack/lib/action_dispatch/request/session.rb @@ -63,6 +63,10 @@ module ActionDispatch @exists = nil # we haven't checked yet end + def id + options[:id] + end + def options Options.find @env end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index c72310cca3..705314f8ab 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -122,14 +122,12 @@ module ActionDispatch end def helper_names - self.module.instance_methods.map(&:to_s) + @helpers.map(&:to_s) end def clear! @helpers.each do |helper| - @module.module_eval do - remove_possible_method helper - end + @module.remove_possible_method helper end @routes.clear @@ -185,8 +183,8 @@ module ActionDispatch # foo_url(bar, baz, bang, sort_by: 'baz') # def define_url_helper(route, name, options) + @module.remove_possible_method name @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1 - remove_possible_method :#{name} def #{name}(*args) if #{optimize_helper?(route)} && args.size == #{route.required_parts.size} && !args.last.is_a?(Hash) && optimize_routes_generation? options = #{options.inspect} diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 11743e36f2..5b3a2cae7c 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -182,6 +182,8 @@ module ActionView # width="30" and height="45", and "50" becomes width="50" and height="50". # <tt>:size</tt> will be ignored if the value is not in the correct format. # + # ==== Examples + # # image_tag("icon") # # => <img alt="Icon" src="/assets/icon" /> # image_tag("icon.png") diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 10748aacf4..da8a9dafe4 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -1040,14 +1040,38 @@ module ActionView end class FormBuilder + # Wraps ActionView::Helpers::DateHelper#date_select for form builders: + # + # <%= form_for @person do |f| %> + # <%= f.date_select :birth_date %> + # <%= f.submit %> + # <% end %> + # + # Please refer to the documentation of the base helper for details. def date_select(method, options = {}, html_options = {}) @template.date_select(@object_name, method, objectify_options(options), html_options) end + # Wraps ActionView::Helpers::DateHelper#time_select for form builders: + # + # <%= form_for @race do |f| %> + # <%= f.time_select :average_lap %> + # <%= f.submit %> + # <% end %> + # + # Please refer to the documentation of the base helper for details. def time_select(method, options = {}, html_options = {}) @template.time_select(@object_name, method, objectify_options(options), html_options) end + # Wraps ActionView::Helpers::DateHelper#datetime_select for form builders: + # + # <%= form_for @person do |f| %> + # <%= f.time_select :last_request_at %> + # <%= f.submit %> + # <% end %> + # + # Please refer to the documentation of the base helper for details. def datetime_select(method, options = {}, html_options = {}) @template.datetime_select(@object_name, method, objectify_options(options), html_options) end diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index ae7bfd1ec6..89c24956a3 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -756,26 +756,74 @@ module ActionView end class FormBuilder + # Wraps ActionView::Helpers::FormOptionsHelper#select for form builders: + # + # <%= form_for @post do |f| %> + # <%= f.select :person_id, Person.all.collect {|p| [ p.name, p.id ] }, { include_blank: true }) %> + # <%= f.submit %> + # <% end %> + # + # Please refer to the documentation of the base helper for details. def select(method, choices, options = {}, html_options = {}) @template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options)) end + # Wraps ActionView::Helpers::FormOptionsHelper#collection_select for form builders: + # + # <%= form_for @post do |f| %> + # <%= f.collection_select :person_id, Author.all, :id, :name_with_initial, prompt: true %> + # <%= f.submit %> + # <% end %> + # + # Please refer to the documentation of the base helper for details. def collection_select(method, collection, value_method, text_method, options = {}, html_options = {}) @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)) end + # Wraps ActionView::Helpers::FormOptionsHelper#grouped_collection_select for form builders: + # + # <%= form_for @city do |f| %> + # <%= f.grouped_collection_select :country_id, :country_id, @continents, :countries, :name, :id, :name %> + # <%= f.submit %> + # <% end %> + # + # Please refer to the documentation of the base helper for details. def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {}) @template.grouped_collection_select(@object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_options.merge(html_options)) end + # Wraps ActionView::Helpers::FormOptionsHelper#time_zone_select for form builders: + # + # <%= form_for @user do |f| %> + # <%= f.time_zone_select :time_zone, nil, include_blank: true %> + # <%= f.submit %> + # <% end %> + # + # Please refer to the documentation of the base helper for details. def time_zone_select(method, priority_zones = nil, options = {}, html_options = {}) @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options)) end + # Wraps ActionView::Helpers::FormOptionsHelper#collection_check_boxes for form builders: + # + # <%= form_for @post do |f| %> + # <%= f.collection_check_boxes :author_ids, Author.all, :id, :name_with_initial %> + # <%= f.submit %> + # <% end %> + # + # Please refer to the documentation of the base helper for details. def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block) @template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options), &block) end + # Wraps ActionView::Helpers::FormOptionsHelper#collection_radio_buttons for form builders: + # + # <%= form_for @post do |f| %> + # <%= f.collection_radio_buttons :author_id, Author.all, :id, :name_with_initial %> + # <%= f.submit %> + # <% end %> + # + # Please refer to the documentation of the base helper for details. def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block) @template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options), &block) end diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index cfdd7c77d8..878d3e0eda 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -13,8 +13,8 @@ module ActionView "'" => "\\'" } - JS_ESCAPE_MAP["\342\200\250".force_encoding('UTF-8').encode!] = '
' - JS_ESCAPE_MAP["\342\200\251".force_encoding('UTF-8').encode!] = '
' + JS_ESCAPE_MAP["\342\200\250".force_encoding(Encoding::UTF_8).encode!] = '
' + JS_ESCAPE_MAP["\342\200\251".force_encoding(Encoding::UTF_8).encode!] = '
' # Escapes carriage returns and single and double quotes for JavaScript segments. # diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index b927c69260..f73d14c79b 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -80,8 +80,7 @@ module ActionView # problems with converting the user's data to # the <tt>default_internal</tt>. # - # To do so, simply raise the raise +WrongEncodingError+ - # as follows: + # To do so, simply raise +WrongEncodingError+ as follows: # # raise WrongEncodingError.new( # problematic_string, diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb index b479f991bc..a89d51221e 100644 --- a/actionpack/lib/action_view/template/error.rb +++ b/actionpack/lib/action_view/template/error.rb @@ -17,7 +17,7 @@ module ActionView end def message - @string.force_encoding("BINARY") + @string.force_encoding(Encoding::ASCII_8BIT) "Your template was not saved as valid #{@encoding}. Please " \ "either specify #{@encoding} as the encoding for your template " \ "in your text editor, or mark the template with its " \ diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb index afbbece90f..5aaafc15c1 100644 --- a/actionpack/lib/action_view/template/handlers/erb.rb +++ b/actionpack/lib/action_view/template/handlers/erb.rb @@ -81,7 +81,7 @@ module ActionView # wrong, we can still find an encoding tag # (<%# encoding %>) inside the String using a regular # expression - template_source = template.source.dup.force_encoding("BINARY") + template_source = template.source.dup.force_encoding(Encoding::ASCII_8BIT) erb = template_source.gsub(ENCODING_TAG, '') encoding = $2 diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index bbcd289886..7157bccfb3 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -38,6 +38,8 @@ end ActiveSupport::Dependencies.hook! +Thread.abort_on_exception = true + # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb index 734662ae11..aadb142660 100644 --- a/actionpack/test/controller/parameters/parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -1,4 +1,5 @@ require 'abstract_unit' +require 'action_dispatch/http/upload' require 'action_controller/metal/strong_parameters' class ParametersPermitTest < ActiveSupport::TestCase @@ -31,7 +32,7 @@ class ParametersPermitTest < ActiveSupport::TestCase values += [0, 1.0, 2**128, BigDecimal.new(1)] values += [true, false] values += [Date.today, Time.now, DateTime.now] - values += [StringIO.new] + values += [STDOUT, StringIO.new, ActionDispatch::Http::UploadedFile.new(tempfile: __FILE__)] values.each do |value| params = ActionController::Parameters.new(id: value) diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index ffa91d63c4..66d2cade22 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -160,6 +160,36 @@ class CookiesTest < ActionController::TestCase @request.host = "www.nextangle.com" end + def test_fetch + x = Object.new + assert_not request.cookie_jar.key?('zzzzzz') + assert_equal x, request.cookie_jar.fetch('zzzzzz', x) + assert_not request.cookie_jar.key?('zzzzzz') + end + + def test_fetch_exists + x = Object.new + request.cookie_jar['foo'] = 'bar' + assert_equal 'bar', request.cookie_jar.fetch('foo', x) + end + + def test_fetch_block + x = Object.new + assert_not request.cookie_jar.key?('zzzzzz') + assert_equal x, request.cookie_jar.fetch('zzzzzz') { x } + end + + def test_key_is_to_s + request.cookie_jar['foo'] = 'bar' + assert_equal 'bar', request.cookie_jar.fetch(:foo) + end + + def test_fetch_type_error + assert_raises(KeyError) do + request.cookie_jar.fetch(:omglolwut) + end + end + def test_each request.cookie_jar['foo'] = :bar list = [] @@ -211,13 +241,13 @@ class CookiesTest < ActionController::TestCase def test_setting_cookie_for_fourteen_days get :authenticate_for_fourteen_days - assert_cookie_header "user_name=david; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT" + assert_cookie_header "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000" assert_equal({"user_name" => "david"}, @response.cookies) end def test_setting_cookie_for_fourteen_days_with_symbols get :authenticate_for_fourteen_days_with_symbols - assert_cookie_header "user_name=david; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT" + assert_cookie_header "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000" assert_equal({"user_name" => "david"}, @response.cookies) end @@ -250,7 +280,7 @@ class CookiesTest < ActionController::TestCase def test_multiple_cookies get :set_multiple_cookies assert_equal 2, @response.cookies.size - assert_cookie_header "user_name=david; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT\nlogin=XJ-122; path=/" + assert_cookie_header "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000\nlogin=XJ-122; path=/" assert_equal({"login" => "XJ-122", "user_name" => "david"}, @response.cookies) end @@ -261,14 +291,14 @@ class CookiesTest < ActionController::TestCase def test_expiring_cookie request.cookies[:user_name] = 'Joe' get :logout - assert_cookie_header "user_name=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT" + assert_cookie_header "user_name=; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000" assert_equal({"user_name" => nil}, @response.cookies) end def test_delete_cookie_with_path request.cookies[:user_name] = 'Joe' get :delete_cookie_with_path - assert_cookie_header "user_name=; path=/beaten; expires=Thu, 01-Jan-1970 00:00:00 GMT" + assert_cookie_header "user_name=; path=/beaten; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000" end def test_delete_unexisting_cookie @@ -330,7 +360,7 @@ class CookiesTest < ActionController::TestCase def test_delete_and_set_cookie request.cookies[:user_name] = 'Joe' get :delete_and_set_cookie - assert_cookie_header "user_name=david; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT" + assert_cookie_header "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000" assert_equal({"user_name" => "david"}, @response.cookies) end @@ -435,7 +465,7 @@ class CookiesTest < ActionController::TestCase request.cookies[:user_name] = 'Joe' get :delete_cookie_with_domain assert_response :success - assert_cookie_header "user_name=; domain=.nextangle.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT" + assert_cookie_header "user_name=; domain=.nextangle.com; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000" end def test_cookie_with_all_domain_option_and_tld_length @@ -462,7 +492,7 @@ class CookiesTest < ActionController::TestCase request.cookies[:user_name] = 'Joe' get :delete_cookie_with_domain_and_tld assert_response :success - assert_cookie_header "user_name=; domain=.nextangle.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT" + assert_cookie_header "user_name=; domain=.nextangle.com; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000" end def test_cookie_with_several_preset_domains_using_one_of_these_domains @@ -491,7 +521,7 @@ class CookiesTest < ActionController::TestCase request.cookies[:user_name] = 'Joe' get :delete_cookie_with_domains assert_response :success - assert_cookie_header "user_name=; domain=example2.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT" + assert_cookie_header "user_name=; domain=example2.com; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000" end def test_deletings_cookie_with_several_preset_domains_using_other_domain @@ -499,7 +529,7 @@ class CookiesTest < ActionController::TestCase request.cookies[:user_name] = 'Joe' get :delete_cookie_with_domains assert_response :success - assert_cookie_header "user_name=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT" + assert_cookie_header "user_name=; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000" end def test_cookies_hash_is_indifferent_access diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 185a9e9b18..2fbe7358f9 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -35,7 +35,7 @@ class ResponseTest < ActiveSupport::TestCase end def test_response_body_encoding - body = ["hello".encode('utf-8')] + body = ["hello".encode(Encoding::UTF_8)] response = ActionDispatch::Response.new 200, {}, body assert_equal Encoding::UTF_8, response.body.encoding end @@ -129,7 +129,7 @@ class ResponseTest < ActiveSupport::TestCase @response.set_cookie("login", :value => "foo&bar", :path => "/", :expires => Time.utc(2005, 10, 10,5)) status, headers, body = @response.to_a - assert_equal "user_name=david; path=/\nlogin=foo%26bar; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT", headers["Set-Cookie"] + assert_equal "user_name=david; path=/\nlogin=foo%26bar; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000", headers["Set-Cookie"] assert_equal({"login" => "foo&bar", "user_name" => "david"}, @response.cookies) @response.delete_cookie("login") diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index 1677dee524..d8bf22dec8 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -276,7 +276,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest # First request accesses the session time = Time.local(2008, 4, 24) Time.stubs(:now).returns(time) - expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d-%b-%Y %H:%M:%S GMT") + expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d %b %Y %H:%M:%S -0000") cookies[SessionKey] = SignedBar @@ -290,7 +290,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest # Second request does not access the session time = Time.local(2008, 4, 25) Time.stubs(:now).returns(time) - expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d-%b-%Y %H:%M:%S GMT") + expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d %b %Y %H:%M:%S -0000") get '/no_session_access' assert_response :success diff --git a/actionpack/test/dispatch/session/test_session_test.rb b/actionpack/test/dispatch/session/test_session_test.rb index 904398f563..d30461a623 100644 --- a/actionpack/test/dispatch/session/test_session_test.rb +++ b/actionpack/test/dispatch/session/test_session_test.rb @@ -2,8 +2,8 @@ require 'abstract_unit' require 'stringio' class ActionController::TestSessionTest < ActiveSupport::TestCase - def test_ctor_allows_setting - session = ActionController::TestSession.new({:one => 'one', :two => 'two'}) + def test_initialize_with_values + session = ActionController::TestSession.new(one: 'one', two: 'two') assert_equal('one', session[:one]) assert_equal('two', session[:two]) end @@ -23,15 +23,21 @@ class ActionController::TestSessionTest < ActiveSupport::TestCase end def test_calling_update_with_params_passes_to_attributes - session = ActionController::TestSession.new() + session = ActionController::TestSession.new session.update('key' => 'value') assert_equal('value', session[:key]) end - def test_clear_emptys_session - session = ActionController::TestSession.new({:one => 'one', :two => 'two'}) + def test_clear_empties_session + session = ActionController::TestSession.new(one: 'one', two: 'two') session.clear assert_nil(session[:one]) assert_nil(session[:two]) end + + def test_keys_and_values + session = ActionController::TestSession.new(one: '1', two: '2') + assert_equal %w(one two), session.keys + assert_equal %w(1 2), session.values + end end diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb index 96aafb868b..3db862c810 100644 --- a/actionpack/test/dispatch/test_request_test.rb +++ b/actionpack/test/dispatch/test_request_test.rb @@ -18,7 +18,7 @@ class TestRequestTest < ActiveSupport::TestCase assert_equal "0.0.0.0", env.delete("REMOTE_ADDR") assert_equal "Rails Testing", env.delete("HTTP_USER_AGENT") - assert_equal [1, 1], env.delete("rack.version") + assert_equal [1, 2], env.delete("rack.version") assert_equal "", env.delete("rack.input").string assert_kind_of StringIO, env.delete("rack.errors") assert_equal true, env.delete("rack.multithread") diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb index 56919dc592..1eed8adb62 100644 --- a/actionpack/test/template/javascript_helper_test.rb +++ b/actionpack/test/template/javascript_helper_test.rb @@ -27,8 +27,8 @@ class JavaScriptHelperTest < ActionView::TestCase assert_equal %(This \\"thing\\" is really\\n netos\\'), escape_javascript(%(This "thing" is really\n netos')) assert_equal %(backslash\\\\test), escape_javascript( %(backslash\\test) ) assert_equal %(dont <\\/close> tags), escape_javascript(%(dont </close> tags)) - assert_equal %(unicode 
 newline), escape_javascript(%(unicode \342\200\250 newline).force_encoding('UTF-8').encode!) - assert_equal %(unicode 
 newline), escape_javascript(%(unicode \342\200\251 newline).force_encoding('UTF-8').encode!) + assert_equal %(unicode 
 newline), escape_javascript(%(unicode \342\200\250 newline).force_encoding(Encoding::UTF_8).encode!) + assert_equal %(unicode 
 newline), escape_javascript(%(unicode \342\200\251 newline).force_encoding(Encoding::UTF_8).encode!) assert_equal %(dont <\\/close> tags), j(%(dont </close> tags)) end diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index 412d13bb2b..1b2234f4e2 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -95,8 +95,8 @@ class TextHelperTest < ActionView::TestCase end def test_truncate_multibyte - assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding('UTF-8'), - truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8'), :length => 10) + assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding(Encoding::UTF_8), + truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding(Encoding::UTF_8), :length => 10) end def test_truncate_does_not_modify_the_options_hash @@ -293,7 +293,7 @@ class TextHelperTest < ActionView::TestCase end def test_excerpt_with_utf8 - assert_equal("...\357\254\203ciency could not be...".force_encoding('UTF-8'), excerpt("That's why e\357\254\203ciency could not be helped".force_encoding('UTF-8'), 'could', :radius => 8)) + assert_equal("...\357\254\203ciency could not be...".force_encoding(Encoding::UTF_8), excerpt("That's why e\357\254\203ciency could not be helped".force_encoding(Encoding::UTF_8), 'could', :radius => 8)) end def test_excerpt_does_not_modify_the_options_hash diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb index 49d8706ac2..38ba3cc152 100644 --- a/activemodel/test/cases/naming_test.rb +++ b/activemodel/test/cases/naming_test.rb @@ -29,6 +29,14 @@ class NamingTest < ActiveModel::TestCase assert_equal 'Track back', @model_name.human end + def test_route_key + assert_equal 'post_track_backs', @model_name.route_key + end + + def test_param_key + assert_equal 'post_track_back', @model_name.param_key + end + def test_i18n_key assert_equal :"post/track_back", @model_name.i18n_key end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index f1cca0ad76..c6a6e44724 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,87 @@ ## Rails 4.0.0 (unreleased) ## +* Relation#merge now only overwrites where values on the LHS of the + merge. Consider: + + left = Person.where(age: [13, 14, 15]) + right = Person.where(age: [13, 14]).where(age: [14, 15]) + + `left` results in the following SQL: + + WHERE age IN (13, 14, 15) + + `right` results in the following SQL: + + WHERE age IN (13, 14) AND age IN (14, 15) + + Previously, `left.merge(right)` would result in all but the last + condition being removed: + + WHERE age IN (14, 15) + + Now it results in the LHS condition(s) for `age` being removed, but + the RHS remains as it is: + + WHERE age IN (13, 14) AND age IN (14, 15) + + *Jon Leighton* + +* Fix handling of dirty time zone aware attributes + + Previously, when `time_zone_aware_attributes` were enabled, after + changing a datetime or timestamp attribute and then changing it back + to the original value, `changed_attributes` still tracked the + attribute as changed. This caused `[attribute]_changed?` and + `changed?` methods to return true incorrectly. + + Example: + + in_time_zone 'Paris' do + order = Order.new + original_time = Time.local(2012, 10, 10) + order.shipped_at = original_time + order.save + order.changed? # => false + + # changing value + order.shipped_at = Time.local(2013, 1, 1) + order.changed? # => true + + # reverting to original value + order.shipped_at = original_time + order.changed? # => false, used to return true + end + + *Lilibeth De La Cruz* + +* When `#count` is used in conjunction with `#uniq` we perform `count(:distinct => true)`. + Fix #6865. + + Example: + + relation.uniq.count # => SELECT COUNT(DISTINCT *) + + *Yves Senn + Kaspar Schiess* + +* PostgreSQL ranges type support. Includes: int4range, int8range, + numrange, tsrange, tstzrange, daterange + + Ranges can be created with inclusive and exclusive bounds. + + Example: + + create_table :Room do |t| + t.daterange :availability + end + + Room.create(availability: (Date.today..Float::INFINITY)) + Room.first.availability # => Wed, 19 Sep 2012..Infinity + + One thing to note: Range class does not support exclusive lower + bound. + + *Alexander Grebennik* + * Added a state instance variable to each transaction. Will allow other objects to know whether a transaction has been committed or rolled back. diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 16a46a59d1..06bdabfced 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -191,7 +191,7 @@ module ActiveRecord # * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt> # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt> # * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt> - # <tt>Project#milestones.delete(milestone), Project#milestones.destroy(mileston), Project#milestones.find(milestone_id),</tt> + # <tt>Project#milestones.delete(milestone), Project#milestones.destroy(milestone), Project#milestones.find(milestone_id),</tt> # <tt>Project#milestones.build, Project#milestones.create</tt> # * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt> # <tt>Project#categories.delete(category1), Project#categories.destroy(category1)</tt> diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 300f67959d..c5fb1fe2c7 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -15,7 +15,6 @@ module ActiveRecord def scope scope = klass.unscoped - scope.merge! eval_scope(klass, reflection.scope) if reflection.scope scope.extending! Array(options[:extend]) add_constraints(scope) end @@ -59,7 +58,7 @@ module ActiveRecord if reflection.source_macro == :belongs_to if reflection.options[:polymorphic] - key = reflection.association_primary_key(klass) + key = reflection.association_primary_key(self.klass) else key = reflection.association_primary_key end @@ -92,8 +91,13 @@ module ActiveRecord # Exclude the scope of the association itself, because that # was already merged in the #scope method. - (scope_chain[i] - [self.reflection.scope]).each do |scope_chain_item| - item = eval_scope(reflection.klass, scope_chain_item) + scope_chain[i].each do |scope_chain_item| + klass = i == 0 ? self.klass : reflection.klass + item = eval_scope(klass, scope_chain_item) + + if scope_chain_item == self.reflection.scope + scope.merge! item.except(:where, :includes) + end scope.includes! item.includes_values scope.where_values += item.where_values diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 7701001da9..41b5a6e926 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -33,17 +33,12 @@ module ActiveRecord def define_method_attribute=(attr_name) if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name]) method_body, line = <<-EOV, __LINE__ + 1 - def #{attr_name}=(original_time) - original_time = nil if original_time.blank? - time = original_time - unless time.acts_like?(:time) - time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time - end - time = time.in_time_zone rescue nil if time - changed = read_attribute(:#{attr_name}) != time - write_attribute(:#{attr_name}, original_time) - #{attr_name}_will_change! if changed - @attributes_cache["#{attr_name}"] = time + def #{attr_name}=(time) + time_with_zone = time.respond_to?(:in_time_zone) ? time.in_time_zone : nil + previous_time = attribute_changed?("#{attr_name}") ? changed_attributes["#{attr_name}"] : read_attribute(:#{attr_name}) + write_attribute(:#{attr_name}, time) + #{attr_name}_will_change! if previous_time != time_with_zone + @attributes_cache["#{attr_name}"] = time_with_zone end EOV generated_attribute_methods.module_eval(method_body, __FILE__, line) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index bf5793d454..e262401da6 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -162,8 +162,8 @@ module ActiveRecord #:nodoc: # # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects # by simple queries without turning to SQL. They work by appending the name of an attribute - # to <tt>find_by_</tt> # like <tt>Person.find_by_user_name</tt>. - # Instead of writing # <tt>Person.where(user_name: user_name).first</tt>, you just do + # to <tt>find_by_</tt> like <tt>Person.find_by_user_name</tt>. + # Instead of writing <tt>Person.where(user_name: user_name).first</tt>, you just do # <tt>Person.find_by_user_name(user_name)</tt>. # # It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 3675184193..847d9da6e6 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -237,7 +237,7 @@ module ActiveRecord @spec = spec @checkout_timeout = spec.config[:checkout_timeout] || 5 - @dead_connection_timeout = spec.config[:dead_connection_timeout] + @dead_connection_timeout = spec.config[:dead_connection_timeout] || 5 @reaper = Reaper.new self, spec.config[:reaping_frequency] @reaper.run diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 60a9eee7c7..d18b9c991f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -93,6 +93,18 @@ module ActiveRecord quote_column_name(table_name) end + # Override to return the quoted table name for assignment. Defaults to + # table quoting. + # + # This works for mysql and mysql2 where table.column can be used to + # resolve ambiguity. + # + # We override this in the sqlite and postgresql adapters to use only + # the column name (as per syntax requirements). + def quote_table_name_for_assignment(table, attr) + quote_table_name("#{table}.#{attr}") + end + def quoted_true "'t'" end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb index 9d6111b51e..fd5eaab9c9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -47,6 +47,9 @@ module ActiveRecord value.to_s when Date, DateTime, Time "'#{value.to_s(:db)}'" + when Range + # infinity dumps as Infinity, which causes uninitialized constant error + value.inspect.gsub('Infinity', '::Float::INFINITY') else value.inspect end diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index fb28ecb6cf..747331f3a1 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -126,7 +126,6 @@ module ActiveRecord when :hstore then "#{klass}.string_to_hstore(#{var_name})" when :inet, :cidr then "#{klass}.string_to_cidr(#{var_name})" when :json then "#{klass}.string_to_json(#{var_name})" - when :intrange then "#{klass}.string_to_intrange(#{var_name})" else var_name end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb index f7d734a2f1..3d8f0b575c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb @@ -62,6 +62,12 @@ module ActiveRecord "{#{casted_values.join(',')}}" end + def range_to_string(object) + from = object.begin.respond_to?(:infinite?) && object.begin.infinite? ? '' : object.begin + to = object.end.respond_to?(:infinite?) && object.end.infinite? ? '' : object.end + "[#{from},#{to}#{object.exclude_end? ? ')' : ']'}" + end + def string_to_json(string) if String === string ActiveSupport::JSON.decode(string) @@ -92,36 +98,6 @@ module ActiveRecord parse_pg_array(string).map{|val| oid.type_cast val} end - def string_to_intrange(string) - if string.nil? - nil - elsif "empty" == string - (nil..nil) - elsif String === string && (matches = /^(\(|\[)([0-9]+),(\s?)([0-9]+)(\)|\])$/i.match(string)) - lower_bound = ("(" == matches[1] ? (matches[2].to_i + 1) : matches[2].to_i) - upper_bound = (")" == matches[5] ? (matches[4].to_i - 1) : matches[4].to_i) - (lower_bound..upper_bound) - else - string - end - end - - def intrange_to_string(object) - if object.nil? - nil - elsif Range === object - if [object.first, object.last].all? { |el| Integer === el } - "[#{object.first.to_i},#{object.exclude_end? ? object.last.to_i : object.last.to_i + 1})" - elsif [object.first, object.last].all? { |el| NilClass === el } - "empty" - else - nil - end - else - object - end - end - private HstorePair = begin diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 02c295983f..d90b9283ef 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -78,6 +78,64 @@ module ActiveRecord end end + class Range < Type + attr_reader :subtype + def initialize(subtype) + @subtype = subtype + end + + def exctract_bounds(value) + from, to = value[1..-2].split(',') + { + from: (value[1] == ',' || from == '-infinity') ? infinity(:negative => true) : from, + to: (value[-2] == ',' || to == 'infinity') ? infinity : to, + exclude_start: (value[0] == '('), + exclude_end: (value[-1] == ')') + } + end + + def infinity(options = {}) + ::Float::INFINITY * (options[:negative] ? -1 : 1) + end + + def infinity?(value) + value.respond_to?(:infinite?) && value.infinite? + end + + def to_integer(value) + infinity?(value) ? value : value.to_i + end + + def type_cast(value) + return if value.nil? || value == 'empty' + return value if value.is_a?(::Range) + + extracted = exctract_bounds(value) + + case @subtype + when :date + from = ConnectionAdapters::Column.value_to_date(extracted[:from]) + from -= 1.day if extracted[:exclude_start] + to = ConnectionAdapters::Column.value_to_date(extracted[:to]) + when :decimal + from = BigDecimal.new(extracted[:from].to_s) + # FIXME: add exclude start for ::Range, same for timestamp ranges + to = BigDecimal.new(extracted[:to].to_s) + when :time + from = ConnectionAdapters::Column.string_to_time(extracted[:from]) + to = ConnectionAdapters::Column.string_to_time(extracted[:to]) + when :integer + from = to_integer(extracted[:from]) rescue value ? 1 : 0 + from -= 1 if extracted[:exclude_start] + to = to_integer(extracted[:to]) rescue value ? 1 : 0 + else + return value + end + + ::Range.new(from, to, extracted[:exclude_end]) + end + end + class Integer < Type def type_cast(value) return if value.nil? @@ -168,14 +226,6 @@ module ActiveRecord end end - class IntRange < Type - def type_cast(value) - return if value.nil? - - ConnectionAdapters::PostgreSQLColumn.string_to_intrange value - end - end - class TypeMap def initialize @mapping = {} @@ -241,6 +291,13 @@ module ActiveRecord alias_type 'int8', 'int2' alias_type 'oid', 'int2' + register_type 'daterange', OID::Range.new(:date) + register_type 'numrange', OID::Range.new(:decimal) + register_type 'tsrange', OID::Range.new(:time) + register_type 'int4range', OID::Range.new(:integer) + alias_type 'tstzrange', 'tsrange' + alias_type 'int8range', 'int4range' + register_type 'numeric', OID::Decimal.new register_type 'text', OID::Identity.new alias_type 'varchar', 'text' @@ -278,9 +335,6 @@ module ActiveRecord register_type 'json', OID::Json.new register_type 'ltree', OID::Identity.new - register_type 'int4range', OID::IntRange.new - alias_type 'int8range', 'int4range' - register_type 'cidr', OID::Cidr.new alias_type 'inet', 'cidr' end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index c2fcef94da..47e2e3928f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -19,6 +19,12 @@ module ActiveRecord return super unless column case value + when Range + if /range$/ =~ column.sql_type + "'#{PostgreSQLColumn.range_to_string(value)}'::#{column.sql_type}" + else + super + end when Array if column.array "'#{PostgreSQLColumn.array_to_string(value, column, self)}'" @@ -31,11 +37,6 @@ module ActiveRecord when 'json' then super(PostgreSQLColumn.json_to_string(value), column) else super end - when Range - case column.sql_type - when 'int4range', 'int8range' then super(PostgreSQLColumn.intrange_to_string(value), column) - else super - end when IPAddr case column.sql_type when 'inet', 'cidr' then super(PostgreSQLColumn.cidr_to_string(value), column) @@ -74,6 +75,9 @@ module ActiveRecord return super(value, column) unless column case value + when Range + return super(value, column) unless /range$/ =~ column.sql_type + PostgreSQLColumn.range_to_string(value) when NilClass if column.array && array_member 'NULL' @@ -94,11 +98,6 @@ module ActiveRecord when 'json' then PostgreSQLColumn.json_to_string(value) else super(value, column) end - when Range - case column.sql_type - when 'int4range', 'int8range' then PostgreSQLColumn.intrange_to_string(value) - else super(value, column) - end when IPAddr return super(value, column) unless ['inet','cidr'].include? column.sql_type PostgreSQLColumn.cidr_to_string(value) @@ -131,6 +130,10 @@ module ActiveRecord end end + def quote_table_name_for_assignment(table, attr) + quote_column_name(attr) + end + # Quotes column names for use in SQL queries. def quote_column_name(name) #:nodoc: PGconn.quote_ident(name.to_s) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index 8c68576bdc..73ca2c8e61 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -417,14 +417,6 @@ module ActiveRecord when 0..6; "timestamp(#{precision})" else raise(ActiveRecordError, "No timestamp type has precision of #{precision}. The allowed range of precision is from 0 to 6") end - when 'intrange' - return 'int4range' unless limit - - case limit - when 1..4; 'int4range' - when 5..8; 'int8range' - else raise(ActiveRecordError, "No range type has byte size #{limit}. Use a numeric with precision 0 instead.") - end else super end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index b1b0467379..209553b26e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -77,6 +77,8 @@ module ActiveRecord return default unless default case default + when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m + $1 # Numeric types when /\A\(?(-?\d+(\.\d*)?\)?)\z/ $1 @@ -117,9 +119,6 @@ module ActiveRecord # JSON when /\A'(.*)'::json\z/ $1 - # int4range, int8range - when /\A'(.*)'::int(4|8)range\z/ - $1 # Object identifier types when /\A-?\d+\z/ $1 @@ -220,12 +219,11 @@ module ActiveRecord # JSON type when 'json' :json - # int4range, int8range types - when 'int4range', 'int8range' - :intrange # Small and big integer types when /^(?:small|big)int$/ :integer + when /(num|date|tstz|ts|int4|int8)range$/ + field_type.to_sym # Pass through all types that are not specific to PostgreSQL. else super @@ -276,6 +274,30 @@ module ActiveRecord column(args[0], 'tsvector', options) end + def int4range(name, options = {}) + column(name, 'int4range', options) + end + + def int8range(name, options = {}) + column(name, 'int8range', options) + end + + def tsrange(name, options = {}) + column(name, 'tsrange', options) + end + + def tstzrange(name, options = {}) + column(name, 'tstzrange', options) + end + + def numrange(name, options = {}) + column(name, 'numrange', options) + end + + def daterange(name, options = {}) + column(name, 'daterange', options) + end + def hstore(name, options = {}) column(name, 'hstore', options) end @@ -304,10 +326,6 @@ module ActiveRecord column(name, 'json', options) end - def intrange(name, options = {}) - column(name, 'intrange', options) - end - def column(name, type = nil, options = {}) super column = self[name] @@ -339,6 +357,12 @@ module ActiveRecord timestamp: { name: "timestamp" }, time: { name: "time" }, date: { name: "date" }, + daterange: { name: "daterange" }, + numrange: { name: "numrange" }, + tsrange: { name: "tsrange" }, + tstzrange: { name: "tstzrange" }, + int4range: { name: "int4range" }, + int8range: { name: "int8range" }, binary: { name: "bytea" }, boolean: { name: "boolean" }, xml: { name: "xml" }, @@ -349,7 +373,6 @@ module ActiveRecord macaddr: { name: "macaddr" }, uuid: { name: "uuid" }, json: { name: "json" }, - intrange: { name: "int4range" }, ltree: { name: "ltree" } } @@ -552,6 +575,11 @@ module ActiveRecord true end + # Range datatypes weren't introduced until PostgreSQL 9.2 + def supports_ranges? + postgresql_version >= 90200 + end + # Returns the configured supported identifier length supported by PostgreSQL def table_alias_length @table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 11e8197293..7bead4bde9 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -229,6 +229,10 @@ module ActiveRecord @connection.class.quote(s) end + def quote_table_name_for_assignment(table, attr) + quote_column_name(attr) + end + def quote_column_name(name) #:nodoc: %Q("#{name.to_s.gsub('"', '""')}") end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 5a4ef5991d..2958d08210 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -363,11 +363,11 @@ module ActiveRecord # # first: # name: Smurf - # *DEFAULTS + # <<: *DEFAULTS # # second: # name: Fraggle - # *DEFAULTS + # <<: *DEFAULTS # # Any fixture labeled "DEFAULTS" is safely ignored. class FixtureSet diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 67339c05e5..823595a128 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -342,7 +342,7 @@ module ActiveRecord end def call(env) - ActiveRecord::Base.logger.quietly do + ActiveRecord::Base.logger.silence do ActiveRecord::Migration.check_pending! end @app.call(env) diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 3f154bd1cc..f10c290755 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -192,7 +192,8 @@ module ActiveRecord def perform_calculation(operation, column_name, options = {}) operation = operation.to_s.downcase - distinct = options[:distinct] + # If #count is used in conjuction with #uniq it is considered distinct. (eg. relation.uniq.count) + distinct = options[:distinct] || self.uniq_value if operation == "count" column_name ||= (select_for_count || :all) diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index 59226d316e..eb23e92fb8 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/hash/keys' +require "set" module ActiveRecord class Relation @@ -105,25 +106,26 @@ module ActiveRecord end def merged_wheres - if values[:where] - merged_wheres = relation.where_values + values[:where] - - unless relation.where_values.empty? - # Remove equalities with duplicated left-hand. Last one wins. - seen = {} - merged_wheres = merged_wheres.reverse.reject { |w| - nuke = false - if w.respond_to?(:operator) && w.operator == :== - nuke = seen[w.left] - seen[w.left] = true - end - nuke - }.reverse - end + values[:where] ||= [] - merged_wheres + if values[:where].empty? || relation.where_values.empty? + relation.where_values + values[:where] else - relation.where_values + # Remove equalities from the existing relation with a LHS which is + # present in the relation being merged in. + + seen = Set.new + values[:where].each { |w| + if w.respond_to?(:operator) && w.operator == :== + seen << w.left + end + } + + relation.where_values.reject { |w| + w.respond_to?(:operator) && + w.operator == :== && + seen.include?(w.left) + } + values[:where] end end end diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 883d25d80b..537ebbef28 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -58,7 +58,7 @@ module ActiveRecord key else key = key.to_s - key.split('.').first.to_sym if key.include?('.') + key.split('.').first if key.include?('.') end end.compact end @@ -66,7 +66,7 @@ module ActiveRecord private def self.build(attribute, value) case value - when Array, ActiveRecord::Associations::CollectionProxy + when Array values = value.to_a.map {|x| x.is_a?(Base) ? x.id : x} ranges, values = values.partition {|v| v.is_a?(Range)} diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 46c0d6206f..42849d6bc9 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -764,6 +764,11 @@ module ActiveRecord [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))] when Hash attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts) + + attributes.values.grep(ActiveRecord::Relation) do |rel| + self.bind_values += rel.bind_values + end + PredicateBuilder.build_from_hash(klass, attributes, table) else [opts] diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 2dad1dc177..3c5b871e99 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -33,10 +33,10 @@ module ActiveRecord # Accepts an array, hash, or string of SQL conditions and sanitizes # them into a valid SQL fragment for a SET clause. # { name: nil, group_id: 4 } returns "name = NULL , group_id='4'" - def sanitize_sql_for_assignment(assignments) + def sanitize_sql_for_assignment(assignments, default_table_name = self.table_name) case assignments when Array; sanitize_sql_array(assignments) - when Hash; sanitize_sql_hash_for_assignment(assignments) + when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name) else assignments end end @@ -98,9 +98,9 @@ module ActiveRecord # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause. # { status: nil, group_id: 1 } # # => "status = NULL , group_id = 1" - def sanitize_sql_hash_for_assignment(attrs) + def sanitize_sql_hash_for_assignment(attrs, table) attrs.map do |attr, value| - "#{connection.quote_column_name(attr)} = #{quote_bound_value(value)}" + "#{connection.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value)}" end.join(', ') end diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index 2254be8612..7351ed9013 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -3,6 +3,9 @@ require "cases/helper" class PostgresqlArray < ActiveRecord::Base end +class PostgresqlRange < ActiveRecord::Base +end + class PostgresqlTsvector < ActiveRecord::Base end @@ -43,7 +46,106 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase @connection.execute("INSERT INTO postgresql_arrays (id, commission_by_quarter, nicknames) VALUES (1, '{35000,21000,18000,17000}', '{foo,bar,baz}')") @first_array = PostgresqlArray.find(1) + @connection.execute <<_SQL if @connection.supports_ranges? + INSERT INTO postgresql_ranges ( + date_range, + num_range, + ts_range, + tstz_range, + int4_range, + int8_range + ) VALUES ( + '[''2012-01-02'', ''2012-01-04'']', + '[0.1, 0.2]', + '[''2010-01-01 14:30'', ''2011-01-01 14:30'']', + '[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']', + '[1, 10]', + '[10, 100]' + ) +_SQL + + @connection.execute <<_SQL if @connection.supports_ranges? + INSERT INTO postgresql_ranges ( + date_range, + num_range, + ts_range, + tstz_range, + int4_range, + int8_range + ) VALUES ( + '(''2012-01-02'', ''2012-01-04'')', + '[0.1, 0.2)', + '[''2010-01-01 14:30'', ''2011-01-01 14:30'')', + '[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'')', + '(1, 10)', + '(10, 100)' + ) +_SQL + + @connection.execute <<_SQL if @connection.supports_ranges? + INSERT INTO postgresql_ranges ( + date_range, + num_range, + ts_range, + tstz_range, + int4_range, + int8_range + ) VALUES ( + '(''2012-01-02'',]', + '[0.1,]', + '[''2010-01-01 14:30'',]', + '[''2010-01-01 14:30:00+05'',]', + '(1,]', + '(10,]' + ) +_SQL + + @connection.execute <<_SQL if @connection.supports_ranges? + INSERT INTO postgresql_ranges ( + date_range, + num_range, + ts_range, + tstz_range, + int4_range, + int8_range + ) VALUES ( + '[,]', + '[,]', + '[,]', + '[,]', + '[,]', + '[,]' + ) +_SQL + + @connection.execute <<_SQL if @connection.supports_ranges? + INSERT INTO postgresql_ranges ( + date_range, + num_range, + ts_range, + tstz_range, + int4_range, + int8_range + ) VALUES ( + '(''2012-01-02'', ''2012-01-02'')', + '(0.1, 0.1)', + '(''2010-01-01 14:30'', ''2010-01-01 14:30'')', + '(''2010-01-01 14:30:00+05'', ''2010-01-01 06:30:00-03'')', + '(1, 1)', + '(10, 10)' + ) +_SQL + + if @connection.supports_ranges? + @first_range = PostgresqlRange.find(1) + @second_range = PostgresqlRange.find(2) + @third_range = PostgresqlRange.find(3) + @fourth_range = PostgresqlRange.find(4) + @empty_range = PostgresqlRange.find(5) + end + @connection.execute("INSERT INTO postgresql_tsvectors (id, text_vector) VALUES (1, ' ''text'' ''vector'' ')") + @first_tsvector = PostgresqlTsvector.find(1) @connection.execute("INSERT INTO postgresql_moneys (id, wealth) VALUES (1, '567.89'::money)") @@ -82,6 +184,16 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase assert_equal :text, @first_array.column_for_attribute(:nicknames).type end + def test_data_type_of_range_types + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + assert_equal :daterange, @first_range.column_for_attribute(:date_range).type + assert_equal :numrange, @first_range.column_for_attribute(:num_range).type + assert_equal :tsrange, @first_range.column_for_attribute(:ts_range).type + assert_equal :tstzrange, @first_range.column_for_attribute(:tstz_range).type + assert_equal :int4range, @first_range.column_for_attribute(:int4_range).type + assert_equal :int8range, @first_range.column_for_attribute(:int8_range).type + end + def test_data_type_of_tsvector_types assert_equal :tsvector, @first_tsvector.column_for_attribute(:text_vector).type end @@ -128,11 +240,201 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase assert_equal "'text' 'vector'", @first_tsvector.text_vector end + def test_int4range_values + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + assert_equal 1...11, @first_range.int4_range + assert_equal 2...10, @second_range.int4_range + assert_equal 2...Float::INFINITY, @third_range.int4_range + assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int4_range) + assert_equal nil, @empty_range.int4_range + end + + def test_int8range_values + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + assert_equal 10...101, @first_range.int8_range + assert_equal 11...100, @second_range.int8_range + assert_equal 11...Float::INFINITY, @third_range.int8_range + assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int8_range) + assert_equal nil, @empty_range.int8_range + end + + def test_daterange_values + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + assert_equal Date.new(2012, 1, 2)...Date.new(2012, 1, 5), @first_range.date_range + assert_equal Date.new(2012, 1, 3)...Date.new(2012, 1, 4), @second_range.date_range + assert_equal Date.new(2012, 1, 3)...Float::INFINITY, @third_range.date_range + assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.date_range) + assert_equal nil, @empty_range.date_range + end + + def test_numrange_values + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + assert_equal BigDecimal.new('0.1')..BigDecimal.new('0.2'), @first_range.num_range + assert_equal BigDecimal.new('0.1')...BigDecimal.new('0.2'), @second_range.num_range + assert_equal BigDecimal.new('0.1')...BigDecimal.new('Infinity'), @third_range.num_range + assert_equal BigDecimal.new('-Infinity')...BigDecimal.new('Infinity'), @fourth_range.num_range + assert_equal nil, @empty_range.num_range + end + + def test_tsrange_values + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + tz = ::ActiveRecord::Base.default_timezone + assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)..Time.send(tz, 2011, 1, 1, 14, 30, 0), @first_range.ts_range + assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 1, 1, 14, 30, 0), @second_range.ts_range + assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)...Float::INFINITY, @third_range.ts_range + assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.ts_range) + assert_equal nil, @empty_range.ts_range + end + + def test_tstzrange_values + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + assert_equal Time.parse('2010-01-01 09:30:00 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), @first_range.tstz_range + assert_equal Time.parse('2010-01-01 09:30:00 UTC')...Time.parse('2011-01-01 17:30:00 UTC'), @second_range.tstz_range + assert_equal Time.parse('2010-01-01 09:30:00 UTC')...Float::INFINITY, @third_range.tstz_range + assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.tstz_range) + assert_equal nil, @empty_range.tstz_range + end + def test_money_values assert_equal 567.89, @first_money.wealth assert_equal(-567.89, @second_money.wealth) end + def test_create_tstzrange + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + tstzrange = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2011-02-02 14:30:00 CDT') + range = PostgresqlRange.new(:tstz_range => tstzrange) + assert range.save + assert range.reload + assert_equal range.tstz_range, tstzrange + assert_equal range.tstz_range, Time.parse('2010-01-01 13:30:00 UTC')...Time.parse('2011-02-02 19:30:00 UTC') + end + + def test_update_tstzrange + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + new_tstzrange = Time.parse('2010-01-01 14:30:00 CDT')...Time.parse('2011-02-02 14:30:00 CET') + assert @first_range.tstz_range = new_tstzrange + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.tstz_range, new_tstzrange + assert @first_range.tstz_range = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2010-01-01 13:30:00 +0000') + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.tstz_range, nil + end + + def test_create_tsrange + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + tz = ::ActiveRecord::Base.default_timezone + tsrange = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0) + range = PostgresqlRange.new(:ts_range => tsrange) + assert range.save + assert range.reload + assert_equal range.ts_range, tsrange + end + + def test_update_tsrange + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + tz = ::ActiveRecord::Base.default_timezone + new_tsrange = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0) + assert @first_range.ts_range = new_tsrange + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.ts_range, new_tsrange + assert @first_range.ts_range = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0) + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.ts_range, nil + end + + def test_create_numrange + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + numrange = BigDecimal.new('0.5')...BigDecimal.new('1') + range = PostgresqlRange.new(:num_range => numrange) + assert range.save + assert range.reload + assert_equal range.num_range, numrange + end + + def test_update_numrange + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + new_numrange = BigDecimal.new('0.5')...BigDecimal.new('1') + assert @first_range.num_range = new_numrange + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.num_range, new_numrange + assert @first_range.num_range = BigDecimal.new('0.5')...BigDecimal.new('0.5') + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.num_range, nil + end + + def test_create_daterange + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + daterange = Range.new(Date.new(2012, 1, 1), Date.new(2013, 1, 1), true) + range = PostgresqlRange.new(:date_range => daterange) + assert range.save + assert range.reload + assert_equal range.date_range, daterange + end + + def test_update_daterange + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + new_daterange = Date.new(2012, 2, 3)...Date.new(2012, 2, 10) + assert @first_range.date_range = new_daterange + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.date_range, new_daterange + assert @first_range.date_range = Date.new(2012, 2, 3)...Date.new(2012, 2, 3) + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.date_range, nil + end + + def test_create_int4range + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + int4range = Range.new(3, 50, true) + range = PostgresqlRange.new(:int4_range => int4range) + assert range.save + assert range.reload + assert_equal range.int4_range, int4range + end + + def test_update_int4range + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + new_int4range = 6...10 + assert @first_range.int4_range = new_int4range + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.int4_range, new_int4range + assert @first_range.int4_range = 3...3 + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.int4_range, nil + end + + def test_create_int8range + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + int8range = Range.new(30, 50, true) + range = PostgresqlRange.new(:int8_range => int8range) + assert range.save + assert range.reload + assert_equal range.int8_range, int8range + end + + def test_update_int8range + skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? + new_int8range = 60000...10000000 + assert @first_range.int8_range = new_int8range + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.int8_range, new_int8range + assert @first_range.int8_range = 39999...39999 + assert @first_range.save + assert @first_range.reload + assert_equal @first_range.int8_range, nil + end + def test_update_tsvector new_text_vector = "'new' 'text' 'vector'" assert @first_tsvector.text_vector = new_text_vector diff --git a/activerecord/test/cases/adapters/postgresql/intrange_test.rb b/activerecord/test/cases/adapters/postgresql/intrange_test.rb deleted file mode 100644 index 5f6a64619d..0000000000 --- a/activerecord/test/cases/adapters/postgresql/intrange_test.rb +++ /dev/null @@ -1,106 +0,0 @@ -# encoding: utf-8 - -require "cases/helper" -require 'active_record/base' -require 'active_record/connection_adapters/postgresql_adapter' - -class PostgresqlIntrangesTest < ActiveRecord::TestCase - class IntRangeDataType < ActiveRecord::Base - self.table_name = 'intrange_data_type' - end - - def setup - @connection = ActiveRecord::Base.connection - begin - @connection.transaction do - @connection.create_table('intrange_data_type') do |t| - t.intrange 'int_range', :default => (1..10) - t.intrange 'long_int_range', :limit => 8, :default => (1..100) - end - end - rescue ActiveRecord::StatementInvalid - return skip "do not test on PG without ranges" - end - @int_range_column = IntRangeDataType.columns.find { |c| c.name == 'int_range' } - @long_int_range_column = IntRangeDataType.columns.find { |c| c.name == 'long_int_range' } - end - - def teardown - @connection.execute 'drop table if exists intrange_data_type' - end - - def test_columns - assert_equal :intrange, @int_range_column.type - assert_equal :intrange, @long_int_range_column.type - end - - def test_type_cast_intrange - assert @int_range_column - assert_equal(true, @int_range_column.has_default?) - assert_equal((1..10), @int_range_column.default) - assert_equal("int4range", @int_range_column.sql_type) - - data = "[1,10)" - hash = @int_range_column.class.string_to_intrange data - assert_equal((1..9), hash) - assert_equal((1..9), @int_range_column.type_cast(data)) - - assert_equal((nil..nil), @int_range_column.type_cast("empty")) - assert_equal((1..5), @int_range_column.type_cast('[1,5]')) - assert_equal((2..4), @int_range_column.type_cast('(1,5)')) - assert_equal((2..39), @int_range_column.type_cast('[2,40)')) - assert_equal((10..20), @int_range_column.type_cast('(9,20]')) - end - - def test_type_cast_long_intrange - assert @long_int_range_column - assert_equal(true, @long_int_range_column.has_default?) - assert_equal((1..100), @long_int_range_column.default) - assert_equal("int8range", @long_int_range_column.sql_type) - end - - def test_rewrite - @connection.execute "insert into intrange_data_type (int_range) VALUES ('(1, 6)')" - x = IntRangeDataType.first - x.int_range = (1..100) - assert x.save! - end - - def test_select - @connection.execute "insert into intrange_data_type (int_range) VALUES ('(1, 4]')" - x = IntRangeDataType.first - assert_equal((2..4), x.int_range) - end - - def test_empty_range - @connection.execute %q|insert into intrange_data_type (int_range) VALUES('empty')| - x = IntRangeDataType.first - assert_equal((nil..nil), x.int_range) - end - - def test_rewrite_to_nil - @connection.execute %q|insert into intrange_data_type (int_range) VALUES('(1, 4]')| - x = IntRangeDataType.first - x.int_range = nil - assert x.save! - assert_equal(nil, x.int_range) - end - - def test_invalid_intrange - assert IntRangeDataType.create!(int_range: ('a'..'d')) - x = IntRangeDataType.first - assert_equal(nil, x.int_range) - end - - def test_save_empty_range - assert IntRangeDataType.create!(int_range: (nil..nil)) - x = IntRangeDataType.first - assert_equal((nil..nil), x.int_range) - end - - def test_save_invalid_data - assert_raises(ActiveRecord::StatementInvalid) do - IntRangeDataType.create!(int_range: "empty1") - end - end -end diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index 2ba9143cd5..a7b2764fc1 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -17,7 +17,7 @@ module ActiveRecord @conn.extend(Module.new { def logger; end }) column = Struct.new(:type, :name).new(:string, "foo") binary = SecureRandom.hex - expected = binary.dup.encode!('utf-8') + expected = binary.dup.encode!(Encoding::UTF_8) assert_equal expected, @conn.type_cast(binary, column) end diff --git a/activerecord/test/cases/associations/habtm_join_table_test.rb b/activerecord/test/cases/associations/habtm_join_table_test.rb deleted file mode 100644 index fe2b82f2c1..0000000000 --- a/activerecord/test/cases/associations/habtm_join_table_test.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'cases/helper' - -class MyReader < ActiveRecord::Base - has_and_belongs_to_many :my_books -end - -class MyBook < ActiveRecord::Base - has_and_belongs_to_many :my_readers -end - -class HabtmJoinTableTest < ActiveRecord::TestCase - def setup - ActiveRecord::Base.connection.create_table :my_books, :force => true do |t| - t.string :name - end - assert ActiveRecord::Base.connection.table_exists?(:my_books) - - ActiveRecord::Base.connection.create_table :my_readers, :force => true do |t| - t.string :name - end - assert ActiveRecord::Base.connection.table_exists?(:my_readers) - - ActiveRecord::Base.connection.create_table :my_books_my_readers, :force => true do |t| - t.integer :my_book_id - t.integer :my_reader_id - end - assert ActiveRecord::Base.connection.table_exists?(:my_books_my_readers) - end - - def teardown - ActiveRecord::Base.connection.drop_table :my_books - ActiveRecord::Base.connection.drop_table :my_readers - ActiveRecord::Base.connection.drop_table :my_books_my_readers - end -end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index d42630e1b7..fd6d531645 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -19,6 +19,7 @@ require 'models/line_item' require 'models/car' require 'models/bulb' require 'models/engine' +require 'models/categorization' class HasManyAssociationsTestForCountWithFinderSql < ActiveRecord::TestCase class Invoice < ActiveRecord::Base @@ -108,7 +109,8 @@ end class HasManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :categories, :companies, :developers, :projects, :developers_projects, :topics, :authors, :comments, - :people, :posts, :readers, :taggings, :cars, :essays + :people, :posts, :readers, :taggings, :cars, :essays, + :categorizations def setup Client.destroyed_client_ids.clear @@ -1729,4 +1731,20 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal "lifo", post.comments_with_extend_2.author assert_equal "hello", post.comments_with_extend_2.greeting end + + test "delete record with complex joins" do + david = authors(:david) + + post = david.posts.first + post.type = 'PostWithSpecialCategorization' + post.save + + categorization = post.categorizations.first + categorization.special = true + categorization.save + + assert_not_equal [], david.posts_with_special_categorizations + david.posts_with_special_categorizations = [] + assert_equal [], david.posts_with_special_categorizations + end end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index af91fb2920..67d18f313a 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -894,4 +894,11 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end end + test "has many through with default scope on the target" do + person = people(:michael) + assert_equal [posts(:thinking)], person.first_posts + + readers(:michael_authorless).update(first_post_id: 1) + assert_equal [posts(:thinking)], person.reload.first_posts + end end diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb index 25d2896ab0..9a486cf8b8 100644 --- a/activerecord/test/cases/binary_test.rb +++ b/activerecord/test/cases/binary_test.rb @@ -23,7 +23,7 @@ unless current_adapter?(:SybaseAdapter, :DB2Adapter, :FirebirdAdapter) # Mysql adapter doesn't properly encode things, so we have to do it if current_adapter?(:MysqlAdapter) - name.force_encoding('UTF-8') + name.force_encoding(Encoding::UTF_8) end assert_equal 'いただきます!', name end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index b7622705bf..be49e948fc 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -341,6 +341,10 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal 5, Account.count(:firm_id) end + def test_count_with_uniq + assert_equal 4, Account.select(:credit_limit).uniq.count + end + def test_count_with_column_and_options_parameter assert_equal 2, Account.where("credit_limit = 50 AND firm_id IS NOT NULL").count(:firm_id) end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 4ac82f6880..c7d2ba6073 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -79,6 +79,8 @@ class DirtyTest < ActiveRecord::TestCase assert pirate.created_on_changed? assert_kind_of ActiveSupport::TimeWithZone, pirate.created_on_was assert_equal old_created_on, pirate.created_on_was + pirate.created_on = old_created_on + assert !pirate.created_on_changed? end end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 5ffb32e809..7dbb6616f8 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -15,6 +15,8 @@ require 'support/connection' # TODO: Move all these random hacks into the ARTest namespace and into the support/ dir +Thread.abort_on_exception = true + # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index f69a248491..c43c7601a2 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -8,7 +8,20 @@ require 'models/edge' module ActiveRecord class WhereTest < ActiveRecord::TestCase - fixtures :posts, :edges + fixtures :posts, :edges, :authors + + def test_where_copies_bind_params + author = authors(:david) + posts = author.posts.where('posts.id != 1') + joined = Post.where(id: posts) + + assert_operator joined.length, :>, 0 + + joined.each { |post| + assert_equal author, post.author + assert_not_equal 1, post.id + } + end def test_belongs_to_shallow_where author = Author.new diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 64f1c9f6cc..379c0c0758 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -1488,4 +1488,17 @@ class RelationTest < ActiveRecord::TestCase Array.send(:remove_method, :__omg__) end end + + test "merge collapses wheres from the LHS only" do + left = Post.where(title: "omg").where(comments_count: 1) + right = Post.where(title: "wtf").where(title: "bbq") + + expected = [left.where_values[1]] + right.where_values + merged = left.merge(right) + + assert_equal expected, merged.where_values + assert !merged.to_sql.include?("omg") + assert merged.to_sql.include?("wtf") + assert merged.to_sql.include?("bbq") + end end diff --git a/activerecord/test/fixtures/readers.yml b/activerecord/test/fixtures/readers.yml index 8a6076655b..14b883f041 100644 --- a/activerecord/test/fixtures/readers.yml +++ b/activerecord/test/fixtures/readers.yml @@ -2,8 +2,10 @@ michael_welcome: id: 1 post_id: 1 person_id: 1 + first_post_id: 2 michael_authorless: id: 2 post_id: 3 - person_id: 1
\ No newline at end of file + person_id: 1 + first_post_id: 3 diff --git a/activerecord/test/models/admin/user.rb b/activerecord/test/models/admin/user.rb index 467f3ccd39..024fede266 100644 --- a/activerecord/test/models/admin/user.rb +++ b/activerecord/test/models/admin/user.rb @@ -1,10 +1,24 @@ class Admin::User < ActiveRecord::Base + class Coder + def initialize(default = {}) + @default = default + end + + def dump(o) + ActiveSupport::JSON.encode(o || @default) + end + + def load(s) + s.present? ? ActiveSupport::JSON.decode(s) : @default.clone + end + end + belongs_to :account store :settings, :accessors => [ :color, :homepage ] store_accessor :settings, :favorite_food store :preferences, :accessors => [ :remember_login ] - store :json_data, :accessors => [ :height, :weight ], :coder => JSON - store :json_data_empty, :accessors => [ :is_a_good_guy ], :coder => JSON + store :json_data, :accessors => [ :height, :weight ], :coder => Coder.new + store :json_data_empty, :accessors => [ :is_a_good_guy ], :coder => Coder.new def phone_number read_store_attribute(:settings, :phone_number).gsub(/(\d{3})(\d{3})(\d{4})/,'(\1) \2-\3') diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index 6935cfb0ea..8423411474 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -9,17 +9,8 @@ class Author < ActiveRecord::Base has_many :posts_with_categories, -> { includes(:categories) }, :class_name => "Post" has_many :posts_with_comments_and_categories, -> { includes(:comments, :categories).order("posts.id") }, :class_name => "Post" has_many :posts_containing_the_letter_a, :class_name => "Post" - has_many :posts_with_extension, :class_name => "Post" do #, :extend => ProxyTestExtension - def testing_proxy_owner - proxy_owner - end - def testing_proxy_reflection - proxy_reflection - end - def testing_proxy_target - proxy_target - end - end + has_many :posts_with_special_categorizations, :class_name => 'PostWithSpecialCategorization' + has_many :posts_with_extension, :class_name => "Post" has_one :post_about_thinking, -> { where("posts.title like '%thinking%'") }, :class_name => 'Post' has_one :post_about_thinking_with_last_comment, -> { where("posts.title like '%thinking%'").includes(:last_comment) }, :class_name => 'Post' has_many :comments, :through => :posts diff --git a/activerecord/test/models/category.rb b/activerecord/test/models/category.rb index f8c8ebb70c..7da39a8e33 100644 --- a/activerecord/test/models/category.rb +++ b/activerecord/test/models/category.rb @@ -31,9 +31,4 @@ class Category < ActiveRecord::Base end class SpecialCategory < Category - - def self.what_are_you - 'a special category...' - end - end diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb index 4b2015fe01..ede5fbd0c6 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -29,16 +29,10 @@ class Comment < ActiveRecord::Base end class SpecialComment < Comment - def self.what_are_you - 'a special comment...' - end end class SubSpecialComment < SpecialComment end class VerySpecialComment < Comment - def self.what_are_you - 'a very special comment...' - end end diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index c602ca5eac..fa717ef8d6 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -15,6 +15,7 @@ class Person < ActiveRecord::Base has_many :fixed_bad_references, -> { where :favourite => true }, :class_name => 'BadReference' has_one :favourite_reference, -> { where 'favourite=?', true }, :class_name => 'Reference' has_many :posts_with_comments_sorted_by_comment_id, -> { includes(:comments).order('comments.id') }, :through => :readers, :source => :post + has_many :first_posts, -> { where(id: [1, 2]) }, through: :readers has_many :jobs, :through => :references has_many :jobs_with_dependent_destroy, :source => :job, :through => :references, :dependent => :destroy diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 603f1f2555..93a7a2073c 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -16,11 +16,7 @@ class Post < ActiveRecord::Base scope :limit_by, lambda {|l| limit(l) } - belongs_to :author do - def greeting - "hello" - end - end + belongs_to :author belongs_to :author_with_posts, -> { includes(:posts) }, :class_name => "Author", :foreign_key => :author_id belongs_to :author_with_address, -> { includes(:author_address) }, :class_name => "Author", :foreign_key => :author_id @@ -168,18 +164,6 @@ class SubStiPost < StiPost self.table_name = Post.table_name end -ActiveSupport::Deprecation.silence do - class DeprecatedPostWithComment < ActiveRecord::Base - self.table_name = 'posts' - default_scope where("posts.comments_count > 0").order("posts.comments_count ASC") - end -end - -class PostForAuthor < ActiveRecord::Base - self.table_name = 'posts' - cattr_accessor :selected_author -end - class FirstPost < ActiveRecord::Base self.table_name = 'posts' default_scope { where(:id => 1) } @@ -194,6 +178,11 @@ class PostWithDefaultInclude < ActiveRecord::Base has_many :comments, :foreign_key => :post_id end +class PostWithSpecialCategorization < Post + has_many :categorizations, :foreign_key => :post_id + default_scope { where(:type => 'PostWithSpecialCategorization').joins(:categorizations).where(:categorizations => { :special => true }) } +end + class PostWithDefaultScope < ActiveRecord::Base self.table_name = 'posts' default_scope { order(:title) } diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb index af3ec4be83..90273adafc 100644 --- a/activerecord/test/models/project.rb +++ b/activerecord/test/models/project.rb @@ -40,7 +40,4 @@ class Project < ActiveRecord::Base end class SpecialProject < Project - def hello_world - "hello there!" - end end diff --git a/activerecord/test/models/reader.rb b/activerecord/test/models/reader.rb index f8fb9c573e..3a6b7fad34 100644 --- a/activerecord/test/models/reader.rb +++ b/activerecord/test/models/reader.rb @@ -2,6 +2,7 @@ class Reader < ActiveRecord::Base belongs_to :post belongs_to :person, :inverse_of => :readers belongs_to :single_person, :class_name => 'Person', :foreign_key => :person_id, :inverse_of => :reader + belongs_to :first_post, -> { where(id: [2, 3]) } end class SecureReader < ActiveRecord::Base diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index f7f4cebc5a..17035bf338 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -108,6 +108,7 @@ class ImportantTopic < Topic end class BlankTopic < Topic + # declared here to make sure that dynamic finder with a bang can find a model that responds to `blank?` def blank? true end diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index ae13f2cd8a..83b50030bd 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -1,7 +1,7 @@ ActiveRecord::Schema.define do - %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings postgresql_uuids postgresql_ltrees - postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent postgresql_json_data_type postgresql_intrange_data_type).each do |table_name| + %w(postgresql_ranges postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings postgresql_uuids postgresql_ltrees + postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent postgresql_json_data_type).each do |table_name| execute "DROP TABLE IF EXISTS #{quote_table_name table_name}" end @@ -73,6 +73,18 @@ _SQL ); _SQL + execute <<_SQL if supports_ranges? + CREATE TABLE postgresql_ranges ( + id SERIAL PRIMARY KEY, + date_range daterange, + num_range numrange, + ts_range tsrange, + tstz_range tstzrange, + int4_range int4range, + int8_range int8range + ); +_SQL + execute <<_SQL CREATE TABLE postgresql_tsvectors ( id SERIAL PRIMARY KEY, @@ -106,16 +118,6 @@ _SQL ); _SQL end - - if 't' == select_value("select 'int4range'=ANY(select typname from pg_type)") - execute <<_SQL - CREATE TABLE postgresql_intrange_data_type ( - id SERIAL PRIMARY KEY, - int_range int4range, - int_long_range int8range - ); -_SQL - end execute <<_SQL CREATE TABLE postgresql_moneys ( diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 46219c53db..cd9835259a 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -567,6 +567,7 @@ ActiveRecord::Schema.define do t.integer :post_id, :null => false t.integer :person_id, :null => false t.boolean :skimmer, :default => false + t.integer :first_post_id end create_table :references, :force => true do |t| diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 712db2c75a..c7dbfc95dc 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -158,7 +158,7 @@ module ActiveSupport # characters properly. def escape_key(key) key = key.to_s.dup - key = key.force_encoding("BINARY") + key = key.force_encoding(Encoding::ASCII_8BIT) key = key.gsub(ESCAPE_KEY_CHARS){ |match| "%#{match.getbyte(0).to_s(16).upcase}" } key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250 key diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb index 4e4852a5e6..1d3682eaf2 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -43,7 +43,7 @@ class DateTime # Returns a new DateTime where one or more of the elements have been changed # according to the +options+ parameter. The time options (<tt>:hour</tt>, - # <tt>:minute</tt>, <tt>:sec</tt>) reset cascadingly, so if only the hour is + # <tt>:min</tt>, <tt>:sec</tt>) reset cascadingly, so if only the hour is # passed, then minute and sec is set to 0. If the hour and minute is passed, # then sec is set to 0. The +options+ parameter takes a hash with any of these # keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, diff --git a/activesupport/lib/active_support/proxy_object.rb b/activesupport/lib/active_support/proxy_object.rb index a2bdf1d790..20a0fd8e62 100644 --- a/activesupport/lib/active_support/proxy_object.rb +++ b/activesupport/lib/active_support/proxy_object.rb @@ -5,7 +5,7 @@ module ActiveSupport undef_method :== undef_method :equal? - # Let ActiveSupport::BasicObject at least raise exceptions. + # Let ActiveSupport::ProxyObject at least raise exceptions. def raise(*args) ::Object.send(:raise, *args) end diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index 90e50f235b..dd17cb64f4 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -21,5 +21,7 @@ require 'empty_bool' ENV['NO_RELOAD'] = '1' require 'active_support' +Thread.abort_on_exception = true + # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index f74b7b9917..24e62cc2b9 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -245,6 +245,10 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end end + def test_acts_like_date + assert DateTime.new.acts_like_date? + end + def test_acts_like_time assert DateTime.new.acts_like_time? end diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb index 1583c1fa32..b8951de402 100644 --- a/activesupport/test/core_ext/kernel_test.rb +++ b/activesupport/test/core_ext/kernel_test.rb @@ -38,6 +38,18 @@ class KernelTest < ActiveSupport::TestCase # Skip if we can't STDERR.tell end + def test_quietly + old_stdout_position, old_stderr_position = STDOUT.tell, STDERR.tell + quietly do + puts 'see me, feel me' + STDERR.puts 'touch me, heal me' + end + assert_equal old_stdout_position, STDOUT.tell + assert_equal old_stderr_position, STDERR.tell + rescue Errno::ESPIPE + # Skip if we can't STDERR.tell + end + def test_silence_stderr_with_return_value assert_equal 1, silence_stderr { 1 } end diff --git a/activesupport/test/core_ext/object_and_class_ext_test.rb b/activesupport/test/core_ext/object_and_class_ext_test.rb index ec7dd6d4fb..8d748791e3 100644 --- a/activesupport/test/core_ext/object_and_class_ext_test.rb +++ b/activesupport/test/core_ext/object_and_class_ext_test.rb @@ -120,6 +120,10 @@ class ObjectTryTest < ActiveSupport::TestCase assert_raise(NoMethodError) { @string.try!(method, 'llo', 'y') } end + def test_try_only_block_bang + assert_equal @string.reverse, @string.try! { |s| s.reverse } + end + def test_valid_method assert_equal 5, @string.try(:size) end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index db1cf14abf..68918b040f 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -87,6 +87,12 @@ class StringInflectionsTest < ActiveSupport::TestCase assert_equal('capital', 'Capital'.camelize(:lower)) end + def test_dasherize + UnderscoresToDashes.each do |underscored, dasherized| + assert_equal(dasherized, underscored.dasherize) + end + end + def test_underscore CamelToUnderscore.each do |camel, underscore| assert_equal(underscore, camel.underscore) @@ -263,8 +269,8 @@ class StringInflectionsTest < ActiveSupport::TestCase end def test_truncate_multibyte - assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding('UTF-8'), - "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8').truncate(10) + assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding(Encoding::UTF_8), + "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding(Encoding::UTF_8).truncate(10) end def test_truncate_should_not_be_html_safe @@ -355,22 +361,24 @@ class StringBehaviourTest < ActiveSupport::TestCase end class CoreExtStringMultibyteTest < ActiveSupport::TestCase - UNICODE_STRING = 'こにちわ' - ASCII_STRING = 'ohayo' - BYTE_STRING = "\270\236\010\210\245" + UTF8_STRING = 'こにちわ' + ASCII_STRING = 'ohayo'.encode('US-ASCII') + EUC_JP_STRING = 'さよなら'.encode('EUC-JP') + INVALID_UTF8_STRING = "\270\236\010\210\245" def test_core_ext_adds_mb_chars - assert_respond_to UNICODE_STRING, :mb_chars + assert_respond_to UTF8_STRING, :mb_chars end def test_string_should_recognize_utf8_strings - assert UNICODE_STRING.is_utf8? + assert UTF8_STRING.is_utf8? assert ASCII_STRING.is_utf8? - assert !BYTE_STRING.is_utf8? + assert !EUC_JP_STRING.is_utf8? + assert !INVALID_UTF8_STRING.is_utf8? end def test_mb_chars_returns_instance_of_proxy_class - assert_kind_of ActiveSupport::Multibyte.proxy_class, UNICODE_STRING.mb_chars + assert_kind_of ActiveSupport::Multibyte.proxy_class, UTF8_STRING.mb_chars end end diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index c1a468ec86..9616e42f44 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -75,6 +75,11 @@ class DeprecationTest < ActiveSupport::TestCase end end + def test_deprecate_object + deprecated_object = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(Object.new, ':bomb:') + assert_deprecated(/:bomb:/) { deprecated_object.to_s } + end + def test_nil_behavior_is_ignored ActiveSupport::Deprecation.behavior = nil assert_deprecated(/foo=nil/) { @dtc.partially } @@ -139,6 +144,7 @@ class DeprecationTest < ActiveSupport::TestCase def test_deprecated_constant_proxy assert_not_deprecated { Deprecatee::B::C } assert_deprecated('Deprecatee::A') { assert_equal Deprecatee::B::C, Deprecatee::A } + assert_not_deprecated { assert_equal Deprecatee::B::C.class, Deprecatee::A.class } end def test_assert_deprecation_without_match diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb index 3f8d09c18e..e099e47e0e 100644 --- a/activesupport/test/rescuable_test.rb +++ b/activesupport/test/rescuable_test.rb @@ -21,7 +21,7 @@ class Stargate rescue_from WraithAttack, :with => :sos - rescue_from NuclearExplosion do + rescue_from 'NuclearExplosion' do @result = 'alldead' end @@ -102,5 +102,4 @@ class RescuableTest < ActiveSupport::TestCase result = @cool_stargate.send(:rescue_handlers).collect {|e| e.first} assert_equal expected, result end - end diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index 52571fab60..a678dd9d90 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -7,7 +7,6 @@ Highlights in Rails 4.0: * Strong Parameters * Turbolinks * Russian Doll Caching -* Asynchronous Mailers These release notes cover only the major changes. To know about various bug fixes and changes, please refer to the change logs or check out the [list of commits](https://github.com/rails/rails/commits/master) in the main Rails repository on GitHub. @@ -150,7 +149,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ Action Pack ----------- -Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railties/CHANGELOG.md) for detailed changes. +Please refer to the [Changelog](https://github.com/rails/rails/blob/master/actionpack/CHANGELOG.md) for detailed changes. ### Notable changes @@ -162,7 +161,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railt Active Record ------------- -Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railties/CHANGELOG.md) for detailed changes. +Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activerecord/CHANGELOG.md) for detailed changes. ### Notable changes diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index cc80334af3..7260a48c8c 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -58,7 +58,7 @@ Parameters You will probably want to access data sent in by the user or other parameters in your controller actions. There are two kinds of parameters possible in a web application. The first are parameters that are sent as part of the URL, called query string parameters. The query string is everything after "?" in the URL. The second type of parameter is usually referred to as POST data. This information usually comes from an HTML form which has been filled in by the user. It's called POST data because it can only be sent as part of an HTTP POST request. Rails does not make any distinction between query string parameters and POST parameters, and both are available in the `params` hash in your controller: ```ruby -class ClientsController < ActionController::Base +class ClientsController < ApplicationController # This action uses query string parameters because it gets run # by an HTTP GET request, but this does not make any difference # to the way in which the parameters are accessed. The URL for @@ -479,7 +479,7 @@ In addition to "before" filters, you can also run filters after an action has be For example, in a website where changes have an approval workflow an administrator could be able to preview them easily, just apply them within a transaction: ```ruby -class ChangesController < ActionController::Base +class ChangesController < ApplicationController around_action :wrap_in_transaction, only: :show private diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index 3a9a4e73a6..513ae1272f 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -576,6 +576,8 @@ end In the test we send the email and store the returned object in the `email` variable. We then ensure that it was sent (the first assert), then, in the second batch of assertions, we ensure that the email does indeed contain what we expect. +NOTE: The `ActionMailer::Base.deliveries` array is only reset automatically in `ActionMailer::TestCase` tests. If you want to have a clean slate outside Action Mailer tests, you can reset it manually with: `ActionMailer::Base.deliveries.clear` + Intercepting Emails ------------------- There are situations where you need to edit an email before it's delivered. Fortunately Action Mailer provides hooks to intercept every email. You can register an interceptor to make modifications to mail messages right before they are handed to the delivery agents. diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index 062bcd49f4..69d7333e6f 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -1,6 +1,6 @@ Active Record Basics ==================== - + This guide is an introduction to Active Record. After reading this guide, you will know: diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md index 3747b00b82..516457bcd3 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -157,7 +157,6 @@ The following methods trigger callbacks: * `save!` * `save(validate: false)` * `toggle!` -* `update` * `update_attribute` * `update` * `update!` diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index fffa31927d..e939606c88 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -37,9 +37,9 @@ You should use the defaults for all new applications unless you have a specific ### Main Features -The first feature of the pipeline is to concatenate assets. This is important in a production environment, because it can reduce the number of requests that a browser must make to render a web page. Web browsers are limited in the number of requests that they can make in parallel, so fewer requests can mean faster loading for your application. +The first feature of the pipeline is to concatenate assets. This is important in a production environment, because it can reduce the number of requests that a browser makes to render a web page. Web browsers are limited in the number of requests that they can make in parallel, so fewer requests can mean faster loading for your application. -Rails 2.x introduced the ability to concatenate JavaScript and CSS assets by placing `:cache => true` at the end of the `javascript_include_tag` and `stylesheet_link_tag` methods. But this technique has some limitations. For example, it cannot generate the caches in advance, and it is not able to transparently include assets provided by third-party libraries. +Rails 2.x introduced the ability to concatenate JavaScript and CSS assets by placing `cache: true` at the end of the `javascript_include_tag` and `stylesheet_link_tag` methods. But this technique has some limitations. For example, it cannot generate the caches in advance, and it is not able to transparently include assets provided by third-party libraries. Starting with version 3.1, Rails defaults to concatenating all JavaScript files into one master `.js` file and all CSS files into one master `.css` file. As you'll learn later in this guide, you can customize this strategy to group files any way you like. In production, Rails inserts an MD5 fingerprint into each filename so that the file is cached by the web browser. You can invalidate the cache by altering this fingerprint, which happens automatically whenever you change the file contents. @@ -369,8 +369,8 @@ If any of the files in the manifest have changed between requests, the server re Debug mode can also be enabled in the Rails helper methods: ```erb -<%= stylesheet_link_tag "application", :debug => true %> -<%= javascript_include_tag "application", :debug => true %> +<%= stylesheet_link_tag "application", debug: true %> +<%= javascript_include_tag "application", debug: true %> ``` The `:debug` option is redundant if debug mode is on. @@ -445,7 +445,7 @@ NOTE. If you are precompiling your assets locally, you can use `bundle install - The default matcher for compiling files includes `application.js`, `application.css` and all non-JS/CSS files (this will include all image assets automatically): ```ruby -[ Proc.new{ |path| !%w(.js .css).include?(File.extname(path)) }, /application.(css|js)$/ ] +[ Proc.new { |path| !%w(.js .css).include?(File.extname(path)) }, /application.(css|js)$/ ] ``` NOTE. The matcher (and other members of the precompile array; see below) is applied to final compiled file names. This means that anything that compiles to JS/CSS is excluded, as well as raw JS/CSS files; for example, `.coffee` and `.scss` files are **not** automatically included as they compile to JS/CSS. @@ -460,7 +460,7 @@ Or you can opt to precompile all assets with something like this: ```ruby # config/environments/production.rb -config.assets.precompile << Proc.new { |path| +config.assets.precompile << Proc.new do |path| if path =~ /\.(css|js)\z/ full_path = Rails.application.assets.resolve(path).to_path app_assets_path = Rails.root.join('app', 'assets').to_path @@ -474,7 +474,7 @@ config.assets.precompile << Proc.new { |path| else false end -} +end ``` NOTE. Always specify an expected compiled filename that ends with js or css, even if you want to add Sass or CoffeeScript files to the precompile array. @@ -502,14 +502,14 @@ For Apache: ```apache # The Expires* directives requires the Apache module `mod_expires` to be enabled. -<LocationMatch "^/assets/.*$"> +<Location /assets/> # Use of ETag is discouraged when Last-Modified is present Header unset ETag FileETag None # RFC says only cache for 1 year ExpiresActive On ExpiresDefault "access plus 1 year" -</LocationMatch> +</Location> ``` For nginx: @@ -663,7 +663,7 @@ class Transformer end ``` -To enable this, pass a `new` object to the config option in `application.rb`: +To enable this, pass a new object to the config option in `application.rb`: ```ruby config.assets.css_compressor = Transformer.new diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index 0228d463cf..e52264f296 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -85,6 +85,51 @@ This fragment is then available to all actions in the `ProductsController` using ```ruby expire_fragment('all_available_products') ``` +If you want to avoid expiring the fragment manually, whenever an action updates a product, you can define a helper method: + +```ruby +module ProductsHelper + def cache_key_for_products + count = Product.count + max_updated_at = Product.maximum(:updated_at).try(:utc).try(:to_s, :number) + "products/all-#{count}-#{max_updated_at}" + end +end +``` + +This method generates a cache key that depends on all products and can be used in the view: + +```ruby +<% cache(cache_key_for_products) do %> + All available products: +<% end %> +``` +You can also use an Active Record model as the cache key: + +```ruby +<% Product.all.each do |p| %> + <% cache(p) do %> + <%= link_to p.name, product_url(p) %> + <% end %> +<% end %> +``` + +Behind the scenes, a method called `cache_key` will be invoked on the model and it returns a string like `products/23-20130109142513`. The cache key includes the model name, the id and finally the updated_at timestamp. Thus it will automatically generate a new fragment when the product is updated because the key changes. + +You can also combine the two schemes which is called "Russian Doll Caching": + +```ruby +<% cache(cache_key_for_products) do %> + All available products: + <% Product.all.each do |p| %> + <% cache(p) do %> + <%= link_to p.name, product_url(p) %> + <% end %> + <% end %> +<% end %> +``` + +It's called "Russian Doll Caching" because it nests multiple fragments. The advantage is that if a single product is updated, all the other inner fragments can be reused when regenerating the outer fragment. ### SQL Caching @@ -93,7 +138,7 @@ Query caching is a Rails feature that caches the result set returned by each que For example: ```ruby -class ProductsController < ActionController +class ProductsController < ApplicationController def index # Run a find query diff --git a/guides/source/configuring.md b/guides/source/configuring.md index fc1d0d7530..c1f31fd2b0 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -303,7 +303,7 @@ The schema dumper adds one additional configuration option: * `config.action_controller.permit_all_parameters` sets all the parameters for mass assignment to be permitted by default. The default value is `false`. -* `config.action_controller.raise_on_unpermitted_parameters` enables raising an exception if parameters that are not explicitly permitted are found. The default value is `true` in development and test environments, `false` otherwise. +* `config.action_controller.action_on_unpermitted_params` enables logging or raising an exception if parameters that are not explicitly permitted are found. Set to `:log` or `:raise` to enable. The default value is `:log` in development and test environments, and `false` in all other environments. ### Configuring Action Dispatch @@ -345,34 +345,6 @@ The schema dumper adds one additional configuration option: * `config.action_view.erb_trim_mode` gives the trim mode to be used by ERB. It defaults to `'-'`. See the [ERB documentation](http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/) for more information. -* `config.action_view.javascript_expansions` is a hash containing expansions that can be used for the JavaScript include tag. By default, this is defined as: - - ```ruby - config.action_view.javascript_expansions = { :defaults => %w(jquery jquery_ujs) } - ``` - - However, you may add to this by defining others: - - ```ruby - config.action_view.javascript_expansions[:prototype] = [ - 'prototype', 'effects', 'dragdrop', 'controls' - ] - ``` - - And can reference in the view with the following code: - - ```ruby - <%= javascript_include_tag :prototype %> - ``` - -* `config.action_view.stylesheet_expansions` works in much the same way as `javascript_expansions`, but has no default key. Keys defined for this hash can be referenced in the view like such: - - ```ruby - <%= stylesheet_link_tag :special %> - ``` - -* `config.action_view.cache_asset_ids` With the cache enabled, the asset tag helper methods will make fewer expensive file system calls (the default implementation checks the file system timestamp). However this prevents you from modifying any asset files while the server is running. - * `config.action_view.embed_authenticity_token_in_remote_forms` allows you to set the default behavior for `authenticity_token` in forms with `:remote => true`. By default it's set to false, which means that remote forms will not include `authenticity_token`, which is helpful when you're fragment-caching the form. Remote forms get the authenticity from the `meta` tag, so embedding is unnecessary unless you support browsers without JavaScript. In such case you can either pass `:authenticity_token => true` as a form option or set this config setting to `true` * `config.action_view.prefix_partial_path_with_controller_namespace` determines whether or not partials are looked up from a subdirectory in templates rendered from namespaced controllers. For example, consider a controller named `Admin::PostsController` which renders this template: @@ -681,10 +653,6 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `action_dispatch.configure` Configures the `ActionDispatch::Http::URL.tld_length` to be set to the value of `config.action_dispatch.tld_length`. -* `action_view.cache_asset_ids` Sets `ActionView::Helpers::AssetTagHelper::AssetPaths.cache_asset_ids` to `false` when Active Support loads, but only if `config.cache_classes` is too. - -* `action_view.javascript_expansions` Registers the expansions set up by `config.action_view.javascript_expansions` and `config.action_view.stylesheet_expansions` to be recognized by Action View and therefore usable in the views. - * `action_view.set_configs` Sets up Action View by using the settings in `config.action_view` by `send`'ing the method names as setters to `ActionView::Base` and passing the values through. * `action_controller.logger` Sets `ActionController::Base.logger` — if it's not already set — to `Rails.logger`. diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index db43d62fcf..6493c1e1ec 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -174,6 +174,20 @@ $ cd activerecord $ bundle exec rake postgresql:build_databases ``` +It is possible to build databases for both PostgreSQL and MySQL with + +```bash +$ cd activerecord +$ bundle exec rake db:create +``` + +You can cleanup the databases using + +```bash +$ cd activerecord +$ bundle exec rake db:drop +``` + NOTE: Using the rake task to create the test databases ensures they have the correct character set and collation. NOTE: You'll see the following warning (or localized warning) during activating HStore extension in PostgreSQL 9.1.x or earlier: "WARNING: => is deprecated as an operator". diff --git a/guides/source/engines.md b/guides/source/engines.md index f35233993c..459aa8d57e 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -261,9 +261,9 @@ end This helps prevent conflicts with any other engine or application that may have a post resource as well. -Finally, two files that are the assets for this resource are generated, `app/assets/javascripts/blorgh/posts.js` and `app/assets/javascripts/blorgh/posts.css`. You'll see how to use these a little later. +Finally, two files that are the assets for this resource are generated, `app/assets/javascripts/blorgh/posts.js` and `app/assets/stylesheets/blorgh/posts.css`. You'll see how to use these a little later. -By default, the scaffold styling is not applied to the engine as the engine's layout file, `app/views/blorgh/application.html.erb` doesn't load it. To make this apply, insert this line into the `<head>` tag of this layout: +By default, the scaffold styling is not applied to the engine as the engine's layout file, `app/views/layouts/blorgh/application.html.erb` doesn't load it. To make this apply, insert this line into the `<head>` tag of this layout: ```erb <%= stylesheet_link_tag "scaffold" %> @@ -288,7 +288,7 @@ Now people will only need to go to the root of the engine to see all the posts, ### Generating a comments resource -Now that the engine can to create new blog posts, it only makes sense to add commenting functionality as well. To do get this, you'll need to generate a comment model, a comment controller and then modify the posts scaffold to display comments and allow people to create new ones. +Now that the engine can create new blog posts, it only makes sense to add commenting functionality as well. To do this, you'll need to generate a comment model, a comment controller and then modify the posts scaffold to display comments and allow people to create new ones. Run the model generator and tell it to generate a `Comment` model, with the related table having two columns: a `post_id` integer and `text` text column. @@ -470,7 +470,7 @@ If you have multiple engines that need migrations copied over, use `railties:ins $ rake railties:install:migrations ``` -This command, when run for the first time will copy over all the migrations from the engine. When run the next time, it will only copy over migrations that haven't been copied over already. The first run for this command will output something such as this: +This command, when run for the first time, will copy over all the migrations from the engine. When run the next time, it will only copy over migrations that haven't been copied over already. The first run for this command will output something such as this: ```bash Copied migration [timestamp_1]_create_blorgh_posts.rb from blorgh @@ -754,10 +754,9 @@ end #### Implementing Decorator Pattern Using ActiveSupport::Concern -Using `Class#class_eval` is great for simple adjustments, but for more complex class modifications, you might want to consider using [`ActiveSupport::Concern`](http://edgeapi.rubyonrails.org/classes/ActiveSupport/Concern.html) helps manage load order of interlinked dependencies at run time allowing you to significantly modularize your code. +Using `Class#class_eval` is great for simple adjustments, but for more complex class modifications, you might want to consider using [`ActiveSupport::Concern`](http://edgeapi.rubyonrails.org/classes/ActiveSupport/Concern.html). ActiveSupport::Concern manages load order of interlinked dependent modules and classes at run time allowing you to significantly modularize your code. -**Adding** `Post#time_since_created`<br/> -**Overriding** `Post#summary` +**Adding** `Post#time_since_created` and **Overriding** `Post#summary` ```ruby # MyApp/app/models/blorgh/post.rb @@ -790,7 +789,7 @@ module Blorgh::Concerns::Models::Post extend ActiveSupport::Concern # 'included do' causes the included code to be evaluated in the - # conext where it is included (post.rb), rather than be + # context where it is included (post.rb), rather than be # executed in the module's context (blorgh/concerns/models/post). included do attr_accessor :author_name @@ -800,9 +799,9 @@ module Blorgh::Concerns::Models::Post private - def set_author - self.author = User.find_or_create_by(name: author_name) - end + def set_author + self.author = User.find_or_create_by(name: author_name) + end end def summary @@ -840,7 +839,7 @@ Try this now by creating a new file at `app/views/blorgh/posts/index.html.erb` a ### Routes -Routes inside an engine are, by default, isolated from the application. This is done by the `isolate_namespace` call inside the `Engine` class. This essentially means that the application and its engines can have identically named routes, and that they will not clash. +Routes inside an engine are, by default, isolated from the application. This is done by the `isolate_namespace` call inside the `Engine` class. This essentially means that the application and its engines can have identically named routes and they will not clash. Routes inside an engine are drawn on the `Engine` class within `config/routes.rb`, like this: diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index 7c26512046..339008ab9e 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -695,72 +695,6 @@ To include `http://example.com/main.js`: <%= javascript_include_tag "http://example.com/main.js" %> ``` -If the application does not use the asset pipeline, the `:defaults` option loads jQuery by default: - -```erb -<%= javascript_include_tag :defaults %> -``` - -Outputting `script` tags such as this: - -```html -<script src="/javascripts/jquery.js"></script> -<script src="/javascripts/jquery_ujs.js"></script> -``` - -These two files for jQuery, `jquery.js` and `jquery_ujs.js` must be placed inside `public/javascripts` if the application doesn't use the asset pipeline. These files can be downloaded from the [jquery-rails repository on GitHub](https://github.com/indirect/jquery-rails/tree/master/vendor/assets/javascripts) - -WARNING: If you are using the asset pipeline, this tag will render a `script` tag for an asset called `defaults.js`, which would not exist in your application unless you've explicitly created it. - -And you can in any case override the `:defaults` expansion in `config/application.rb`: - -```ruby -config.action_view.javascript_expansions[:defaults] = %w(foo.js bar.js) -``` - -You can also define new defaults: - -```ruby -config.action_view.javascript_expansions[:projects] = %w(projects.js tickets.js) -``` - -And use them by referencing them exactly like `:defaults`: - -```erb -<%= javascript_include_tag :projects %> -``` - -When using `:defaults`, if an `application.js` file exists in `public/javascripts` it will be included as well at the end. - -Also, if the asset pipeline is disabled, the `:all` expansion loads every JavaScript file in `public/javascripts`: - -```erb -<%= javascript_include_tag :all %> -``` - -Note that your defaults of choice will be included first, so they will be available to all subsequently included files. - -You can supply the `:recursive` option to load files in subfolders of `public/javascripts` as well: - -```erb -<%= javascript_include_tag :all, recursive: true %> -``` - -If you're loading multiple JavaScript files, you can create a better user experience by combining multiple files into a single download. To make this happen in production, specify `cache: true` in your `javascript_include_tag`: - -```erb -<%= javascript_include_tag "main", "columns", cache: true %> -``` - -By default, the combined file will be delivered as `javascripts/all.js`. You can specify a location for the cached asset file instead: - -```erb -<%= javascript_include_tag "main", "columns", - cache: "cache/main/display" %> -``` - -You can even use dynamic paths such as `cache/#{current_site}/main/display`. - #### Linking to CSS Files with the `stylesheet_link_tag` The `stylesheet_link_tag` helper returns an HTML `<link>` tag for each source provided. @@ -797,33 +731,6 @@ By default, the `stylesheet_link_tag` creates links with `media="screen" rel="st <%= stylesheet_link_tag "main_print", media: "print" %> ``` -If the asset pipeline is disabled, the `all` option links every CSS file in `public/stylesheets`: - -```erb -<%= stylesheet_link_tag :all %> -``` - -You can supply the `:recursive` option to link files in subfolders of `public/stylesheets` as well: - -```erb -<%= stylesheet_link_tag :all, recursive: true %> -``` - -If you're loading multiple CSS files, you can create a better user experience by combining multiple files into a single download. To make this happen in production, specify `cache: true` in your `stylesheet_link_tag`: - -```erb -<%= stylesheet_link_tag "main", "columns", cache: true %> -``` - -By default, the combined file will be delivered as `stylesheets/all.css`. You can specify a location for the cached asset file instead: - -```erb -<%= stylesheet_link_tag "main", "columns", - cache: "cache/main/display" %> -``` - -You can even use dynamic paths such as `cache/#{current_site}/main/display`. - #### Linking to Images with the `image_tag` The `image_tag` helper builds an HTML `<img />` tag to the specified file. By default, files are loaded from `public/images`. diff --git a/guides/source/migrations.md b/guides/source/migrations.md index 617e01bd15..cefbc3b829 100644 --- a/guides/source/migrations.md +++ b/guides/source/migrations.md @@ -474,7 +474,7 @@ class ExampleMigration < ActiveRecord::Migration t.references :category end - #add a foreign key + # add a foreign key execute <<-SQL ALTER TABLE products ADD CONSTRAINT fk_products_categories @@ -1011,7 +1011,7 @@ with foreign key constraints in the database. Although Active Record does not provide any tools for working directly with such features, the `execute` method can be used to execute arbitrary SQL. You -could also use some plugin like +could also use some gem like [foreigner](https://github.com/matthuhiggins/foreigner) which add foreign key support to Active Record (including support for dumping foreign keys in `db/schema.rb`). diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md index 93d8e8dfcd..b90b3bb5fc 100644 --- a/guides/source/nested_model_forms.md +++ b/guides/source/nested_model_forms.md @@ -98,7 +98,7 @@ A nested model form will _only_ be built if the associated object(s) exist. This Consider the following typical RESTful controller which will prepare a new Person instance and its `address` and `projects` associations before rendering the `new` template: ```ruby -class PeopleController < ActionController:Base +class PeopleController < ApplicationController def new @person = Person.new @person.built_address diff --git a/guides/source/security.md b/guides/source/security.md index 3706a61431..769bd130be 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -9,7 +9,6 @@ After reading this guide, you will know: * The concept of sessions in Rails, what to put in there and popular attack methods. * How just visiting a site can be a security problem (with CSRF). * What you have to pay attention to when working with files or providing an administration interface. -* The Rails-specific mass assignment problem. * How to manage users: Logging in and out and attack methods on all layers. * And the most popular injection attack methods. diff --git a/guides/source/testing.md b/guides/source/testing.md index 09d6d2d5ee..39a44794a7 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -943,7 +943,7 @@ Cheers! This is the right time to understand a little more about writing tests for your mailers. The line `ActionMailer::Base.delivery_method = :test` in `config/environments/test.rb` sets the delivery method to test mode so that email will not actually be delivered (useful to avoid spamming your users while testing) but instead it will be appended to an array (`ActionMailer::Base.deliveries`). -However often in unit tests, mails will not actually be sent, simply constructed, as in the example above, where the precise content of the email is checked against what it should be. +This way, emails are not actually sent, simply constructed. The precise content of the email can then be checked against what is expected, as in the example above. ### Functional Testing diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index a7ca531123..03ef770352 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -51,7 +51,7 @@ with an id of `results`. Rails provides quite a bit of built-in support for building web pages with this technique. You rarely have to write this code yourself. The rest of this guide -will show you how Rails can help you write web sites in this manner, but it's +will show you how Rails can help you write websites in this way, but it's all built on top of this fairly simple technique. Unobtrusive JavaScript diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 3b0f282143..c6ffe30ad3 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,5 +1,19 @@ ## Rails 4.0.0 (unreleased) ## +* Fixes database.yml when creating a new rails application with '.' + Fix #8304 + + *Jeremy W. Rowe* + +* Deprecate the `eager_load_paths` configuration and alias it to `autoload_paths`. + Since the default in Rails 4.0 is to run in 'threadsafe' mode we need to eager + load all of the paths in `autoload_paths`. This may have unintended consequences + if you have added 'lib' to `autoload_paths` such as loading unneeded code or + code intended only for development and/or test environments. If this applies to + your application you should thoroughly check what is being eager loaded. + + *Andrew White* + * Restore Rails::Engine::Railties#engines with deprecation to ensure compatibility with gems such as Thinking Sphinx Fix #8551 diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 46a6485c44..8a6d1f34aa 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -34,9 +34,8 @@ module Rails # == Configuration # # Besides the +Railtie+ configuration which is shared across the application, in a - # <tt>Rails::Engine</tt> you can access <tt>autoload_paths</tt>, <tt>eager_load_paths</tt> - # and <tt>autoload_once_paths</tt>, which, differently from a <tt>Railtie</tt>, are scoped to - # the current engine. + # <tt>Rails::Engine</tt> you can access <tt>autoload_paths</tt> and <tt>autoload_once_paths</tt>, + # which, differently from a <tt>Railtie</tt>, are scoped to the current engine. # # class MyEngine < Rails::Engine # # Add a load path for this specific Engine @@ -456,9 +455,9 @@ module Rails end # Eager load the application by loading all ruby - # files inside eager_load paths. + # files inside autoload_paths. def eager_load! - config.eager_load_paths.each do |load_path| + config.autoload_paths.each do |load_path| matcher = /\A#{Regexp.escape(load_path)}\/(.*)\.rb\Z/ Dir.glob("#{load_path}/**/*.rb").sort.each do |file| require_dependency file.sub(matcher, '\1') @@ -558,7 +557,6 @@ module Rails # Freeze so future modifications will fail rather than do nothing mysteriously config.autoload_paths.freeze - config.eager_load_paths.freeze config.autoload_once_paths.freeze end @@ -671,7 +669,7 @@ module Rails end def _all_autoload_paths #:nodoc: - @_all_autoload_paths ||= (config.autoload_paths + config.eager_load_paths + config.autoload_once_paths).uniq + @_all_autoload_paths ||= (config.autoload_paths + config.autoload_once_paths).uniq end def _all_load_paths #:nodoc: diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index 10d1821709..2b23d8be38 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -4,7 +4,7 @@ module Rails class Engine class Configuration < ::Rails::Railtie::Configuration attr_reader :root - attr_writer :middleware, :eager_load_paths, :autoload_once_paths, :autoload_paths + attr_writer :middleware, :autoload_once_paths, :autoload_paths def initialize(root=nil) super() @@ -39,16 +39,16 @@ module Rails @paths ||= begin paths = Rails::Paths::Root.new(@root) - paths.add "app", eager_load: true, glob: "*" + paths.add "app", autoload: true, glob: "*" paths.add "app/assets", glob: "*" - paths.add "app/controllers", eager_load: true - paths.add "app/helpers", eager_load: true - paths.add "app/models", eager_load: true - paths.add "app/mailers", eager_load: true + paths.add "app/controllers", autoload: true + paths.add "app/helpers", autoload: true + paths.add "app/models", autoload: true + paths.add "app/mailers", autoload: true paths.add "app/views" - paths.add "app/controllers/concerns", eager_load: true - paths.add "app/models/concerns", eager_load: true + paths.add "app/controllers/concerns", autoload: true + paths.add "app/models/concerns", autoload: true paths.add "lib", load_path: true paths.add "lib/assets", glob: "*" @@ -76,7 +76,13 @@ module Rails end def eager_load_paths - @eager_load_paths ||= paths.eager_load + ActiveSupport::Deprecation.warn "eager_load_paths is deprecated and all autoload_paths are now eagerly loaded." + autoload_paths + end + + def eager_load_paths=(paths) + ActiveSupport::Deprecation.warn "eager_load_paths is deprecated and all autoload_paths are now eagerly loaded." + self.autoload_paths = paths end def autoload_once_paths diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index e22be40381..b2d1be9b51 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -236,7 +236,7 @@ module Rails end def app_name - @app_name ||= defined_app_const_base? ? defined_app_name : File.basename(destination_root) + @app_name ||= (defined_app_const_base? ? defined_app_name : File.basename(destination_root)).tr(".", "_") end def defined_app_name diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index de6795eda2..80ba144441 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -5,13 +5,13 @@ module Rails # paths by a Hash like API. It requires you to give a physical path on initialization. # # root = Root.new "/rails" - # root.add "app/controllers", eager_load: true + # root.add "app/controllers", autoload: true # # The command above creates a new root object and add "app/controllers" as a path. # This means we can get a <tt>Rails::Paths::Path</tt> object back like below: # # path = root["app/controllers"] - # path.eager_load? # => true + # path.autoload? # => true # path.is_a?(Rails::Paths::Path) # => true # # The +Path+ object is simply an enumerable and allows you to easily add extra paths: @@ -30,7 +30,7 @@ module Rails # root["config/routes"].inspect # => ["config/routes.rb"] # # The +add+ method accepts the following options as arguments: - # eager_load, autoload, autoload_once and glob. + # autoload, autoload_once and glob. # # Finally, the +Path+ object also provides a few helpers: # @@ -85,7 +85,8 @@ module Rails end def eager_load - filter_by(:eager_load?) + ActiveSupport::Deprecation.warn "eager_load is deprecated and all autoload_paths are now eagerly loaded." + filter_by(:autoload?) end def autoload_paths @@ -124,9 +125,13 @@ module Rails @glob = options[:glob] options[:autoload_once] ? autoload_once! : skip_autoload_once! - options[:eager_load] ? eager_load! : skip_eager_load! options[:autoload] ? autoload! : skip_autoload! options[:load_path] ? load_path! : skip_load_path! + + if !options.key?(:autoload) && options.key?(:eager_load) + ActiveSupport::Deprecation.warn "the :eager_load option is deprecated and all :autoload paths are now eagerly loaded." + options[:eager_load] ? autoload! : skip_autoload! + end end def children @@ -143,22 +148,37 @@ module Rails expanded.last end - %w(autoload_once eager_load autoload load_path).each do |m| + %w(autoload_once autoload load_path).each do |m| class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def #{m}! # def eager_load! - @#{m} = true # @eager_load = true + def #{m}! # def autoload! + @#{m} = true # @autoload = true end # end # - def skip_#{m}! # def skip_eager_load! - @#{m} = false # @eager_load = false + def skip_#{m}! # def skip_autoload! + @#{m} = false # @autoload = false end # end # - def #{m}? # def eager_load? - @#{m} # @eager_load + def #{m}? # def autoload? + @#{m} # @autoload end # end RUBY end + def eager_load! + ActiveSupport::Deprecation.warn "eager_load paths are deprecated and all autoload paths are now eagerly loaded." + autoload! + end + + def skip_eager_load! + ActiveSupport::Deprecation.warn "eager_load paths are deprecated and all autoload paths are now eagerly loaded." + skip_autoload! + end + + def eager_load? + ActiveSupport::Deprecation.warn "eager_load paths are deprecated and all autoload paths are now eagerly loaded." + autoload? + end + def each(&block) @paths.each(&block) end diff --git a/railties/railties.gemspec b/railties/railties.gemspec index e39430560f..a55bf012da 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -27,6 +27,6 @@ Gem::Specification.new do |s| s.add_dependency 'actionpack', version s.add_dependency 'rake', '>= 0.8.7' - s.add_dependency 'thor', '>= 0.15.4', '< 2.0' + s.add_dependency 'thor', '>= 0.17.0', '< 2.0' s.add_dependency 'rdoc', '~> 3.4' end diff --git a/railties/test/application/initializers/load_path_test.rb b/railties/test/application/initializers/load_path_test.rb index 31811e7f92..595f58bd91 100644 --- a/railties/test/application/initializers/load_path_test.rb +++ b/railties/test/application/initializers/load_path_test.rb @@ -64,7 +64,7 @@ module ApplicationTests add_to_config <<-RUBY config.root = "#{app_path}" - config.eager_load_paths << "#{app_path}/lib" + config.autoload_paths << "#{app_path}/lib" RUBY require "#{app_path}/config/environment" diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb index 4029984ce9..f6a77a0d17 100644 --- a/railties/test/application/paths_test.rb +++ b/railties/test/application/paths_test.rb @@ -54,11 +54,11 @@ module ApplicationTests assert_equal root("app", "controllers"), @paths["app/controllers"].expanded.first end - test "booting up Rails yields a list of paths that are eager" do - eager_load = @paths.eager_load - assert eager_load.include?(root("app/controllers")) - assert eager_load.include?(root("app/helpers")) - assert eager_load.include?(root("app/models")) + test "booting up Rails yields a list of paths that will be eager loaded" do + autoload_paths = @paths.autoload_paths + assert autoload_paths.include?(root("app/controllers")) + assert autoload_paths.include?(root("app/helpers")) + assert autoload_paths.include?(root("app/models")) end test "environments has a glob equal to the current environment" do diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index ee93dc49cd..b2deeb011d 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -164,6 +164,11 @@ class AppGeneratorTest < Rails::Generators::TestCase end end + def test_config_database_app_name_with_period + run_generator [File.join(destination_root, "common.usage.com"), "-d", "postgresql"] + assert_file "common.usage.com/config/database.yml", /common_usage_com/ + end + def test_config_postgresql_database run_generator([destination_root, "-d", "postgresql"]) assert_file "config/database.yml", /postgresql/ diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb index 12f18b9dbf..6f860469fd 100644 --- a/railties/test/paths_test.rb +++ b/railties/test/paths_test.rb @@ -135,56 +135,48 @@ class PathsTest < ActiveSupport::TestCase assert_equal 2, @root.autoload_once.size end - test "it is possible to mark a path as eager loaded" do + test "marking a path as eager loaded is deprecated" do @root["app"] = "/app" - @root["app"].eager_load! - assert @root["app"].eager_load? - assert @root.eager_load.include?(@root["app"].to_a.first) + assert_deprecated{ @root["app"].eager_load! } + assert_deprecated{ assert @root["app"].eager_load? } + assert_deprecated{ assert @root.eager_load.include?(@root["app"].to_a.first) } end - test "it is possible to skip a path from eager loading" do + test "skipping a path from eager loading is deprecated" do @root["app"] = "/app" - @root["app"].eager_load! - assert @root["app"].eager_load? + assert_deprecated{ @root["app"].eager_load! } + assert_deprecated{ assert @root["app"].eager_load? } - @root["app"].skip_eager_load! - assert !@root["app"].eager_load? - assert !@root.eager_load.include?(@root["app"].to_a.first) + assert_deprecated{ @root["app"].skip_eager_load! } + assert_deprecated{ assert !@root["app"].eager_load? } + assert_deprecated{ assert !@root.eager_load.include?(@root["app"].to_a.first) } end - test "it is possible to add a path without assignment and mark it as eager" do - @root.add "app", with: "/app", eager_load: true - assert @root["app"].eager_load? - assert @root.eager_load.include?("/app") + test "adding a path with eager_load option is deprecated" do + assert_deprecated{ @root.add "app", with: "/app", eager_load: true } + assert_deprecated{ assert @root["app"].eager_load? } + assert_deprecated{ assert @root.eager_load.include?("/app") } end - test "it is possible to add multiple paths without assignment and mark them as eager" do - @root.add "app", with: ["/app", "/app2"], eager_load: true - assert @root["app"].eager_load? - assert @root.eager_load.include?("/app") - assert @root.eager_load.include?("/app2") - end - - test "it is possible to create a path without assignment and mark it both as eager and load once" do - @root.add "app", with: "/app", eager_load: true, autoload_once: true - assert @root["app"].eager_load? - assert @root["app"].autoload_once? - assert @root.eager_load.include?("/app") - assert @root.autoload_once.include?("/app") + test "adding multiple paths with eager_load option is deprecated" do + assert_deprecated{ @root.add "app", with: ["/app", "/app2"], eager_load: true } + assert_deprecated{ assert @root["app"].eager_load? } + assert_deprecated{ assert @root.eager_load.include?("/app") } + assert_deprecated{ assert @root.eager_load.include?("/app2") } end test "making a path eager more than once only includes it once in @root.eager_paths" do @root["app"] = "/app" - @root["app"].eager_load! - @root["app"].eager_load! - assert_equal 1, @root.eager_load.select {|p| p == @root["app"].expanded.first }.size + assert_deprecated{ @root["app"].eager_load! } + assert_deprecated{ @root["app"].eager_load! } + assert_deprecated{ assert_equal 1, @root.eager_load.select {|p| p == @root["app"].expanded.first }.size } end test "paths added to a eager_load path should be added to the eager_load collection" do @root["app"] = "/app" - @root["app"].eager_load! + assert_deprecated{ @root["app"].eager_load! } @root["app"] << "/app2" - assert_equal 2, @root.eager_load.size + assert_deprecated{ assert_equal 2, @root.eager_load.size } end test "it should be possible to add a path's default glob" do |