diff options
68 files changed, 718 insertions, 187 deletions
@@ -1,4 +1,3 @@ -require 'sdoc' require 'net/http' $:.unshift File.expand_path('..', __FILE__) diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index 13795f0dd8..287550db42 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -61,7 +61,7 @@ module AbstractController # impossible to skip a callback defined using an anonymous proc # using #skip_action_callback def skip_action_callback(*names) - ActiveSupport::Deprecation.warn('`skip_action_callback` is deprecated and will be removed in the next major version of Rails. Please use skip_before_action, skip_after_action or skip_around_action instead.') + ActiveSupport::Deprecation.warn('`skip_action_callback` is deprecated and will be removed in Rails 5.1. Please use skip_before_action, skip_after_action or skip_around_action instead.') skip_before_action(*names, raise: false) skip_after_action(*names, raise: false) skip_around_action(*names, raise: false) diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index 7db8d13e24..27b3eb4e58 100644 --- a/actionpack/lib/action_controller/metal/live.rb +++ b/actionpack/lib/action_controller/metal/live.rb @@ -36,8 +36,9 @@ module ActionController extend ActiveSupport::Concern module ClassMethods - def make_response!(request) - if request.env["HTTP_VERSION"] == "HTTP/1.0" + def make_response!(response) + request = response.request + if request.get_header("HTTP_VERSION") == "HTTP/1.0" super else Live::Response.new.tap do |res| @@ -293,8 +294,8 @@ module ActionController response.close if response end - def set_response!(request) - @_response = self.class.make_response! request + def set_response!(response) + @_response = self.class.make_response! response end end end diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb index b2b3b4283f..ac37b00010 100644 --- a/actionpack/lib/action_controller/metal/testing.rb +++ b/actionpack/lib/action_controller/metal/testing.rb @@ -4,9 +4,6 @@ module ActionController # Behavior specific to functional tests module Functional # :nodoc: - def set_response!(request) - end - def recycle! @_url_options = nil self.formats = nil diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 380e9d29b4..698719377c 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -509,20 +509,19 @@ module ActionController end end - @controller.request = @request - @controller.response = @response - @request.fetch_header("SCRIPT_NAME") do |k| @request.set_header k, @controller.config.relative_url_root end @controller.recycle! - @controller.process(action) + @controller.dispatch(action, @request, @response) + @request = @controller.request + @response = @controller.response @request.delete_header 'HTTP_COOKIE' if @request.have_cookie_jar? - unless @response.committed? + unless @request.cookie_jar.committed? @request.cookie_jar.write(@response) self.cookies.update(@request.cookie_jar.instance_variable_get(:@cookies)) end diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index c54efb6541..f0127aa276 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -149,7 +149,6 @@ module ActionDispatch # :nodoc: self.body, self.status = body, status - @blank = false @cv = new_cond @committed = false @sending = false @@ -287,12 +286,8 @@ module ActionDispatch # :nodoc: @stream.write string end - EMPTY = " " - # Allows you to manually set or override the response body. def body=(body) - @blank = true if body == EMPTY - if body.respond_to?(:to_path) @stream = body else diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 7e59bb68cf..790f9ea5d2 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -131,35 +131,35 @@ module ActionDispatch # Performs a GET request, following any subsequent redirect. # See +request_via_redirect+ for more information. def get_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`get_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn('`get_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') request_via_redirect(:get, path, *args) end # Performs a POST request, following any subsequent redirect. # See +request_via_redirect+ for more information. def post_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`post_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn('`post_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') request_via_redirect(:post, path, *args) end # Performs a PATCH request, following any subsequent redirect. # See +request_via_redirect+ for more information. def patch_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`patch_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn('`patch_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') request_via_redirect(:patch, path, *args) end # Performs a PUT request, following any subsequent redirect. # See +request_via_redirect+ for more information. def put_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`put_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn('`put_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') request_via_redirect(:put, path, *args) end # Performs a DELETE request, following any subsequent redirect. # See +request_via_redirect+ for more information. def delete_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`delete_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn('`delete_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') request_via_redirect(:delete, path, *args) end end diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 40c97abd35..e50373a0cc 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -75,7 +75,7 @@ class TestCaseTest < ActionController::TestCase end def test_headers - render plain: request.headers.env.to_json + render plain: ::JSON.dump(request.headers.env) end def test_html_output @@ -139,7 +139,7 @@ XML def delete_cookie cookies.delete("foo") - head :ok + render plain: 'ok' end def test_without_body diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index c010b7ce91..65314184c8 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,7 @@ +* Allow `date_select` helper selected option to accept hash like the default options. + + *Lecky Lao* + * Collection input propagates input's `id` to the label's `for` attribute when using html options as the last element of collection. diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb index 312e41ee48..233e613e97 100644 --- a/actionview/lib/action_view/helpers/date_helper.rb +++ b/actionview/lib/action_view/helpers/date_helper.rb @@ -845,7 +845,12 @@ module ActionView private %w( sec min hour day month year ).each do |method| define_method(method) do - @datetime.kind_of?(Numeric) ? @datetime : @datetime.send(method) if @datetime + case @datetime + when Hash then @datetime[method.to_sym] + when Numeric then @datetime + when nil then nil + else @datetime.send(method) + end end end diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb index dde1ef22ac..4c4d2c4457 100644 --- a/actionview/lib/action_view/helpers/translation_helper.rb +++ b/actionview/lib/action_view/helpers/translation_helper.rb @@ -90,7 +90,7 @@ module ActionView keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope]) title = "translation missing: #{keys.join('.')}" - interpolations = options.except(:default) + interpolations = options.except(:default, :scope) if interpolations.any? title << ", " << interpolations.map { |k, v| "#{k}: #{ERB::Util.html_escape(v)}" }.join(', ') end diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb index 9212420ec9..c4234a71c3 100644 --- a/actionview/test/template/date_helper_test.rb +++ b/actionview/test/template/date_helper_test.rb @@ -1559,6 +1559,26 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, date_select("post", "written_on", :selected => Date.new(2004, 07, 10)) end + def test_date_select_with_selected_in_hash + @post = Post.new + @post.written_on = Date.new(2004, 6, 15) + + expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} + expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} + expected << "</select>\n" + + expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} + expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7" selected="selected">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} + expected << "</select>\n" + + expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} + expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10" selected="selected">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} + + expected << "</select>\n" + + assert_dom_equal expected, date_select("post", "written_on", :selected => {day: 10, month: 07, year: 2004}) + end + def test_date_select_with_selected_nil @post = Post.new @post.written_on = Date.new(2004, 6, 15) diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb index 41f31f1582..e540bf27d9 100644 --- a/actionview/test/template/form_helper_test.rb +++ b/actionview/test/template/form_helper_test.rb @@ -1539,9 +1539,10 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_requires_block - assert_raises(ArgumentError) do - form_for(:post, @post, html: { id: 'create-post' }) + error = assert_raises(ArgumentError) do + form_for(@post, html: { id: 'create-post' }) end + assert_equal "Missing block", error.message end def test_form_for_requires_arguments diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb index 261576bead..631bceadd8 100644 --- a/actionview/test/template/translation_helper_test.rb +++ b/actionview/test/template/translation_helper_test.rb @@ -66,6 +66,14 @@ class TranslationHelperTest < ActiveSupport::TestCase assert translate(:"translations.missing").html_safe? end + def test_returns_missing_translation_message_does_filters_out_i18n_options + expected = '<span class="translation_missing" title="translation missing: en.translations.missing, year: 2015">Missing</span>' + assert_equal expected, translate(:"translations.missing", year: '2015', default: []) + + expected = '<span class="translation_missing" title="translation missing: en.scoped.translations.missing, year: 2015">Missing</span>' + assert_equal expected, translate(:"translations.missing", year: '2015', scope: %i(scoped)) + end + def test_raises_missing_translation_message_with_raise_config_option ActionView::Base.raise_on_missing_translations = true diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 5e523d2aa6..82d0c91c58 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,70 @@ +* Added `ActiveRecord::Relation#left_outer_joins`. + + Example: + User.left_outer_joins(:posts) + => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON + "posts"."user_id" = "users"."id" + + *Florian Thomas* + +* Support passing an array to `order` for SQL parameter sanitization. + + *Aaron Suggs* + +* Avoid disabling errors on the PostgreSQL connection when enabling the + standard_conforming_strings setting. Errors were previously disabled because + the setting wasn't writable in Postgres 8.1 and didn't exist in earlier + versions. Now Rails only supports Postgres 8.2+ we're fine to assume the + setting exists. Disabling errors caused problems when using a connection + pooling tool like PgBouncer because it's not guaranteed to have the same + connection between calls to `execute` and it could leave the connection + with errors disabled. + + Fixes #22101. + + *Harry Marr* + +* Set `scope.reordering_value` to `true` if :reordering values are specified. + + Fixes #21886. + + *Hiroaki Izu* + +* Add support for bidirectional destroy dependencies. + + Fixes #13609. + + Example: + + class Content < ActiveRecord::Base + has_one :position, dependent: :destroy + end + + class Position < ActiveRecord::Base + belongs_to :content, dependent: :destroy + end + + *Seb Jacobs* + +* Includes HABTM returns correct size now. It's caused by the join dependency + only instantiates one HABTM object because the join table hasn't a primary key. + + Fixes #16032. + + Examples: + + before: + + Project.first.salaried_developers.size # => 3 + Project.includes(:salaried_developers).first.salaried_developers.size # => 1 + + after: + + Project.first.salaried_developers.size # => 3 + Project.includes(:salaried_developers).first.salaried_developers.size # => 3 + + *Bigxiang* + * Add option to index errors in nested attributes For models which have nested attributes, errors within those models will diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index d4f079e346..b806a2f832 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -149,7 +149,7 @@ module ActiveRecord class HasOneThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly #:nodoc: end - # This error is raised when trying to eager load a poloymorphic association using a JOIN. + # This error is raised when trying to eager load a polymorphic association using a JOIN. # Eager loading polymorphic associations is only possible with # {ActiveRecord::Relation#preload}[rdoc-ref:QueryMethods#preload]. class EagerLoadPolymorphicError < ActiveRecordError diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb index a5c9f1666e..b888148841 100644 --- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb @@ -62,13 +62,13 @@ module ActiveRecord::Associations::Builder # :nodoc: end def self.add_left_association(name, options) - belongs_to name, options + belongs_to name, required: false, **options self.left_reflection = _reflect_on_association(name) end def self.add_right_association(name, options) rhs_name = name.to_s.singularize.to_sym - belongs_to rhs_name, options + belongs_to rhs_name, required: false, **options self.right_reflection = _reflect_on_association(rhs_name) end diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index ca473beea3..9f183c3e7e 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -103,9 +103,14 @@ module ActiveRecord join_root.drop(1).map!(&:reflection) end - def join_constraints(outer_joins) + def join_constraints(outer_joins, join_type) joins = join_root.children.flat_map { |child| - make_inner_joins join_root, child + + if join_type == Arel::Nodes::OuterJoin + make_left_outer_joins join_root, child + else + make_inner_joins join_root, child + end } joins.concat outer_joins.flat_map { |oj| @@ -150,7 +155,8 @@ module ActiveRecord message_bus.instrument('instantiation.active_record', payload) do result_set.each { |row_hash| - parent = parents[row_hash[primary_key]] ||= join_root.instantiate(row_hash, column_aliases) + parent_key = primary_key ? row_hash[primary_key] : row_hash + parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases) construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases) } end @@ -175,6 +181,14 @@ module ActiveRecord [info] + child.children.flat_map { |c| make_outer_joins(child, c) } end + def make_left_outer_joins(parent, child) + tables = child.tables + join_type = Arel::Nodes::OuterJoin + info = make_constraints parent, child, tables, join_type + + [info] + child.children.flat_map { |c| make_left_outer_joins(child, c) } + end + def make_inner_joins(parent, child) tables = child.tables join_type = Arel::Nodes::InnerJoin diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 29dd0643d6..e11a5cfb8a 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -134,7 +134,14 @@ module ActiveRecord else scope.joins!(reflection_scope.joins_values) end - scope.order! preload_values[:order] || values[:order] + + if order_values = preload_values[:order] || values[:order] + scope.order!(order_values) + end + + if preload_values[:reordering] || values[:reordering] + scope.reordering_value = true + end if preload_values[:readonly] || values[:readonly] scope.readonly! diff --git a/activerecord/lib/active_record/associations/preloader/collection_association.rb b/activerecord/lib/active_record/associations/preloader/collection_association.rb index 5adffcd831..9939280fa4 100644 --- a/activerecord/lib/active_record/associations/preloader/collection_association.rb +++ b/activerecord/lib/active_record/associations/preloader/collection_association.rb @@ -2,13 +2,8 @@ module ActiveRecord module Associations class Preloader class CollectionAssociation < Association #:nodoc: - private - def build_scope - super.order(preload_scope.values[:order] || reflection_scope.values[:order]) - end - def preload(preloader) associated_records_by_owner(preloader).each do |owner, records| association = owner.association(reflection.name) @@ -17,7 +12,6 @@ module ActiveRecord records.each { |record| association.set_inverse_instance(record) } end end - end end end diff --git a/activerecord/lib/active_record/associations/preloader/has_one.rb b/activerecord/lib/active_record/associations/preloader/has_one.rb index 24728e9f01..c4add621ca 100644 --- a/activerecord/lib/active_record/associations/preloader/has_one.rb +++ b/activerecord/lib/active_record/associations/preloader/has_one.rb @@ -2,7 +2,6 @@ module ActiveRecord module Associations class Preloader class HasOne < SingularAssociation #:nodoc: - def association_key_name reflection.foreign_key end @@ -10,13 +9,6 @@ module ActiveRecord def owner_key_name reflection.active_record_primary_key end - - private - - def build_scope - super.order(preload_scope.values[:order] || reflection_scope.values[:order]) - end - end end end diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index 56aa23b173..24aa47bb51 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -84,7 +84,9 @@ module ActiveRecord end scope.references! reflection_scope.values[:references] - scope = scope.order reflection_scope.values[:order] if scope.eager_loading? + if scope.eager_loading? && order_values = reflection_scope.values[:order] + scope = scope.order(order_values) + end end scope diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index bfedc4af3f..cfd8cbda67 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -292,10 +292,15 @@ module ActiveRecord end def destroy #:nodoc: + @_destroy_callback_already_called ||= false + return if @_destroy_callback_already_called + @_destroy_callback_already_called = true _run_destroy_callbacks { super } rescue RecordNotDestroyed => e @_association_destroy_exception = e false + ensure + @_destroy_callback_already_called = false end def touch(*) #:nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 0af7d99a4b..6200cc8d29 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -19,12 +19,6 @@ require 'ipaddr' module ActiveRecord module ConnectionHandling # :nodoc: - VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout, - :client_encoding, :options, :application_name, :fallback_application_name, - :keepalives, :keepalives_idle, :keepalives_interval, :keepalives_count, - :tty, :sslmode, :requiressl, :sslcompression, :sslcert, :sslkey, - :sslrootcert, :sslcrl, :requirepeer, :krbsrvname, :gsslib, :service] - # Establishes a connection to the database that's used by all Active Record objects def postgresql_connection(config) conn_params = config.symbolize_keys @@ -36,7 +30,8 @@ module ActiveRecord conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database] # Forward only valid config params to PGconn.connect. - conn_params.keep_if { |k, _| VALID_CONN_PARAMS.include?(k) } + valid_conn_param_keys = PGconn.conndefaults_hash.keys + [:requiressl] + conn_params.slice!(*valid_conn_param_keys) # The postgres drivers don't allow the creation of an unconnected PGconn object, # so just pass a nil connection object for the time being. @@ -281,12 +276,8 @@ module ActiveRecord true end - # Enable standard-conforming strings if available. def set_standard_conforming_strings - old, self.client_min_messages = client_min_messages, 'panic' - execute('SET standard_conforming_strings = on', 'SCHEMA') rescue nil - ensure - self.client_min_messages = old + execute('SET standard_conforming_strings = on', 'SCHEMA') end def supports_ddl_transactions? @@ -669,7 +660,7 @@ module ActiveRecord self.client_min_messages = @config[:min_messages] || 'warning' self.schema_search_path = @config[:schema_search_path] || @config[:schema_order] - # Use standard-conforming strings if available so we don't have to do the E'...' dance. + # Use standard-conforming strings so we don't have to do the E'...' dance. set_standard_conforming_strings # If using Active Record's time zone support configure the connection to return diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 17e7c828b9..deb7d7d06d 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -875,9 +875,7 @@ module ActiveRecord self.pre_loaded_fixtures = false self.config = ActiveRecord::Base - self.fixture_class_names = Hash.new do |h, fixture_set_name| - h[fixture_set_name] = ActiveRecord::FixtureSet.default_fixture_model_name(fixture_set_name, self.config) - end + self.fixture_class_names = {} silence_warnings do define_singleton_method :use_transactional_tests do diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index c26842014d..589c70db0d 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -55,7 +55,7 @@ module ActiveRecord subclass = subclass_from_attributes(attrs) end - if subclass + if subclass && subclass != self subclass.new(*args, &block) else super @@ -167,17 +167,23 @@ module ActiveRecord end def find_sti_class(type_name) - if store_full_sti_class - ActiveSupport::Dependencies.constantize(type_name) - else - compute_type(type_name) + subclass = begin + if store_full_sti_class + ActiveSupport::Dependencies.constantize(type_name) + else + compute_type(type_name) + end + rescue NameError + raise SubclassNotFound, + "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \ + "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \ + "Please rename this column if you didn't intend it to be used for storing the inheritance class " \ + "or overwrite #{name}.inheritance_column to use another column for that information." + end + unless subclass == self || descendants.include?(subclass) + raise SubclassNotFound, "Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}" end - rescue NameError - raise SubclassNotFound, - "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " + - "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " + - "Please rename this column if you didn't intend it to be used for storing the inheritance class " + - "or overwrite #{name}.inheritance_column to use another column for that information." + subclass end def type_condition(table = arel_table) @@ -199,15 +205,7 @@ module ActiveRecord subclass_name = attrs.with_indifferent_access[inheritance_column] if subclass_name.present? - subclass = find_sti_class(subclass_name) - - if subclass.name != self.name - unless descendants.include?(subclass) - raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}") - end - - subclass - end + find_sti_class(subclass_name) end end end diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index 87a1988f2f..7ab2108de0 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -7,7 +7,7 @@ module ActiveRecord delegate :find_by, :find_by!, to: :all delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, to: :all delegate :find_each, :find_in_batches, :in_batches, to: :all - delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :or, + delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :left_outer_joins, :or, :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :uniq, :distinct, :references, :none, :unscope, to: :all delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index d940ac244a..b6f3695856 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -34,7 +34,7 @@ db_namespace = namespace :db do end end - # desc "Empty the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV it defaults to purging the development and test databases." + # desc "Empty the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:purge:all to purge all databases in the config). Without RAILS_ENV it defaults to purging the development and test databases." task :purge => [:load_config] do ActiveRecord::Tasks::DatabaseTasks.purge_current end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 392b462aa9..ad7ea199d6 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -4,7 +4,7 @@ module ActiveRecord # = Active Record \Relation class Relation MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group, - :order, :joins, :references, + :order, :joins, :left_outer_joins, :references, :extending, :unscope] SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering, diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index f5afc1000d..27e7948b10 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -428,6 +428,25 @@ module ActiveRecord self end + # Performs a left outer joins on +args+: + # + # User.left_outer_joins(:posts) + # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id" + # + def left_outer_joins(*args) + check_if_method_has_arguments!(:left_outer_joins, args) + + args.compact! + args.flatten! + + spawn.left_outer_joins!(*args) + end + + def left_outer_joins!(*args) # :nodoc: + self.left_outer_joins_values += args + self + end + # Returns a new relation, which is the result of filtering the current relation # according to the conditions in the arguments. # @@ -878,6 +897,7 @@ module ActiveRecord arel = Arel::SelectManager.new(table) build_joins(arel, joins_values.flatten) unless joins_values.empty? + build_left_outer_joins(arel, left_outer_joins_values.flatten) unless left_outer_joins_values.empty? arel.where(where_clause.ast) unless where_clause.empty? arel.having(having_clause.ast) unless having_clause.empty? @@ -937,6 +957,19 @@ module ActiveRecord end end + def build_left_outer_joins(manager, outer_joins) + buckets = outer_joins.group_by do |join| + case join + when Hash, Symbol, Array + :association_join + else + raise ArgumentError, 'only Hash, Symbol and Array are allowed' + end + end + + build_join_query(manager, buckets, Arel::Nodes::OuterJoin) + end + def build_joins(manager, joins) buckets = joins.group_by do |join| case join @@ -952,6 +985,11 @@ module ActiveRecord raise 'unknown class: %s' % join.class.name end end + + build_join_query(manager, buckets, Arel::Nodes::InnerJoin) + end + + def build_join_query(manager, buckets, join_type) buckets.default = [] association_joins = buckets[:association_join] @@ -967,7 +1005,7 @@ module ActiveRecord join_list ) - join_infos = join_dependency.join_constraints stashed_association_joins + join_infos = join_dependency.join_constraints stashed_association_joins, join_type join_infos.each do |info| info.joins.each { |join| manager.from(join) } @@ -1045,6 +1083,13 @@ module ActiveRecord end def preprocess_order_args(order_args) + order_args.map! do |arg| + if arg.is_a?(Array) && arg.first.to_s.include?('?') + klass.send(:sanitize_sql, arg) + else + arg + end + end order_args.flatten! validate_order_args(order_args) diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb index 2e18f609fd..a0f3c31e78 100644 --- a/activerecord/test/cases/adapters/mysql/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/schema_test.rb @@ -14,6 +14,7 @@ module ActiveRecord @db_name = db @omgpost = Class.new(ActiveRecord::Base) do + self.inheritance_column = :disabled self.table_name = "#{db}.#{table}" def self.name; 'Post'; end end diff --git a/activerecord/test/cases/adapters/mysql/sp_test.rb b/activerecord/test/cases/adapters/mysql/sp_test.rb index 579c3273c6..7849248dcc 100644 --- a/activerecord/test/cases/adapters/mysql/sp_test.rb +++ b/activerecord/test/cases/adapters/mysql/sp_test.rb @@ -7,23 +7,24 @@ class MysqlStoredProcedureTest < ActiveRecord::MysqlTestCase def setup @connection = ActiveRecord::Base.connection + unless ActiveRecord::Base.connection.version >= '5.6.0' || Mysql.const_defined?(:CLIENT_MULTI_RESULTS) + skip("no stored procedure support") + end end # Test that MySQL allows multiple results for stored procedures # # In MySQL 5.6, CLIENT_MULTI_RESULTS is enabled by default. # http://dev.mysql.com/doc/refman/5.6/en/call.html - if ActiveRecord::Base.connection.version >= '5.6.0' || Mysql.const_defined?(:CLIENT_MULTI_RESULTS) - def test_multi_results - rows = @connection.select_rows('CALL ten();') - assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}" - assert @connection.active?, "Bad connection use by 'MysqlAdapter.select_rows'" - end + def test_multi_results + rows = @connection.select_rows('CALL ten();') + assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}" + assert @connection.active?, "Bad connection use by 'MysqlAdapter.select_rows'" + end - def test_multi_results_from_find_by_sql - topics = Topic.find_by_sql 'CALL topics(3);' - assert_equal 3, topics.size - assert @connection.active?, "Bad connection use by 'MysqlAdapter.select'" - end + def test_multi_results_from_find_by_sql + topics = Topic.find_by_sql 'CALL topics(3);' + assert_equal 3, topics.size + assert @connection.active?, "Bad connection use by 'MysqlAdapter.select'" end end diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb index faf2acb9cb..1ebdca661c 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb @@ -14,6 +14,7 @@ module ActiveRecord @db_name = db @omgpost = Class.new(ActiveRecord::Base) do + self.inheritance_column = :disabled self.table_name = "#{db}.#{table}" def self.name; 'Post'; end end diff --git a/activerecord/test/cases/adapters/mysql2/sp_test.rb b/activerecord/test/cases/adapters/mysql2/sp_test.rb index 8b12945f24..cdaa2cca44 100644 --- a/activerecord/test/cases/adapters/mysql2/sp_test.rb +++ b/activerecord/test/cases/adapters/mysql2/sp_test.rb @@ -7,23 +7,24 @@ class Mysql2StoredProcedureTest < ActiveRecord::Mysql2TestCase def setup @connection = ActiveRecord::Base.connection + unless ActiveRecord::Base.connection.version >= '5.6.0' + skip("no stored procedure support") + end end # Test that MySQL allows multiple results for stored procedures # # In MySQL 5.6, CLIENT_MULTI_RESULTS is enabled by default. # http://dev.mysql.com/doc/refman/5.6/en/call.html - if ActiveRecord::Base.connection.version >= '5.6.0' - def test_multi_results - rows = @connection.select_rows('CALL ten();') - assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}" - assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select_rows'" - end + def test_multi_results + rows = @connection.select_rows('CALL ten();') + assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}" + assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select_rows'" + end - def test_multi_results_from_find_by_sql - topics = Topic.find_by_sql 'CALL topics(3);' - assert_equal 3, topics.size - assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select'" - end + def test_multi_results_from_find_by_sql + topics = Topic.find_by_sql 'CALL topics(3);' + assert_equal 3, topics.size + assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select'" end end diff --git a/activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb b/activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb new file mode 100644 index 0000000000..2b867965ba --- /dev/null +++ b/activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb @@ -0,0 +1,41 @@ +require 'cases/helper' +require 'models/content' + +class BidirectionalDestroyDependenciesTest < ActiveRecord::TestCase + fixtures :content, :content_positions + + def setup + Content.destroyed_ids.clear + ContentPosition.destroyed_ids.clear + end + + def test_bidirectional_dependence_when_destroying_item_with_belongs_to_association + content_position = ContentPosition.find(1) + content = content_position.content + assert_not_nil content + + content_position.destroy + + assert_equal [content_position.id], ContentPosition.destroyed_ids + assert_equal [content.id], Content.destroyed_ids + end + + def test_bidirectional_dependence_when_destroying_item_with_has_one_association + content = Content.find(1) + content_position = content.content_position + assert_not_nil content_position + + content.destroy + + assert_equal [content.id], Content.destroyed_ids + assert_equal [content_position.id], ContentPosition.destroyed_ids + end + + def test_bidirectional_dependence_when_destroying_item_with_has_one_association_fails_first_time + content = ContentWhichRequiresTwoDestroyCalls.find(1) + + 2.times { content.destroy } + + assert_equal content.destroyed?, true + end +end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index bc1bff79d3..0c09713971 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -1179,6 +1179,24 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal 1, mary.unique_categorized_post_ids.length end + def test_preloading_has_one_using_reorder + klass = Class.new(ActiveRecord::Base) do + def self.name; "TempAuthor"; end + self.table_name = "authors" + has_one :post, class_name: "PostWithDefaultScope", foreign_key: :author_id + has_one :reorderd_post, -> { reorder(title: :desc) }, class_name: "PostWithDefaultScope", foreign_key: :author_id + end + + author = klass.first + # PRECONDITION: make sure ordering results in different results + assert_not_equal author.post, author.reorderd_post + + preloaded_reorderd_post = klass.preload(:reorderd_post).first.reorderd_post + + assert_equal author.reorderd_post, preloaded_reorderd_post + assert_equal Post.order(title: :desc).first.title, preloaded_reorderd_post.title + end + def test_preloading_polymorphic_with_custom_foreign_type sponsor = sponsors(:moustache_club_sponsor_for_groucho) groucho = members(:groucho) @@ -1203,12 +1221,6 @@ class EagerAssociationTest < ActiveRecord::TestCase end end - def test_join_eager_with_nil_order_should_generate_valid_sql - assert_nothing_raised(ActiveRecord::StatementInvalid) do - Post.includes(:comments).order(nil).where(:comments => {:body => "Thank you for the welcome"}).first - end - end - def test_deep_including_through_habtm # warm up habtm cache posts = Post.all.merge!(:includes => {:categories => :categorizations}, :order => "posts.id").to_a diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 20af436e02..ccb062f991 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -957,4 +957,29 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase projects = ProjectUnscopingDavidDefaultScope.includes(:developers).where(id: project.id) assert_equal 1, projects.first.developers.size end + + def test_preloaded_associations_size + assert_equal Project.first.salaried_developers.size, + Project.preload(:salaried_developers).first.salaried_developers.size + + assert_equal Project.includes(:salaried_developers).references(:salaried_developers).first.salaried_developers.size, + Project.preload(:salaried_developers).first.salaried_developers.size + + # Nested HATBM + first_project = Developer.first.projects.first + preloaded_first_project = + Developer.preload(projects: :salaried_developers). + first. + projects. + detect { |p| p.id == first_project.id } + + assert preloaded_first_project.salaried_developers.loaded?, true + assert_equal first_project.salaried_developers.size, preloaded_first_project.salaried_developers.size + end + + def test_has_and_belongs_to_many_is_useable_with_belongs_to_required_by_default + assert_difference "Project.first.developers_required_by_default.size", 1 do + Project.first.developers_required_by_default.create!(name: "Sean", salary: 50000) + end + 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 cf730e4fe7..226ecf5447 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -1111,10 +1111,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_with_default_scope_on_the_target person = people(:michael) - assert_equal [posts(:thinking)], person.first_posts + assert_equal [posts(:thinking).id], person.first_posts.map(&:id) readers(:michael_authorless).update(first_post_id: 1) - assert_equal [posts(:thinking)], person.reload.first_posts + assert_equal [posts(:thinking).id], person.reload.first_posts.map(&:id) end def test_has_many_through_with_includes_in_through_association_scope diff --git a/activerecord/test/cases/associations/left_outer_join_association_test.rb b/activerecord/test/cases/associations/left_outer_join_association_test.rb new file mode 100644 index 0000000000..4be9264f81 --- /dev/null +++ b/activerecord/test/cases/associations/left_outer_join_association_test.rb @@ -0,0 +1,74 @@ +require "cases/helper" +require 'models/post' +require 'models/comment' +require 'models/author' +require 'models/essay' +require 'models/categorization' +require 'models/person' + +class LeftOuterJoinAssociationTest < ActiveRecord::TestCase + fixtures :authors, :essays, :posts, :comments, :categorizations, :people + + def test_construct_finder_sql_applies_aliases_tables_on_association_conditions + result = Author.left_outer_joins(:thinking_posts, :welcome_posts).to_a + assert_equal authors(:david), result.first + end + + def test_construct_finder_sql_does_not_table_name_collide_on_duplicate_associations + assert_nothing_raised do + queries = capture_sql do + Person.left_outer_joins(:agents => {:agents => :agents}) + .left_outer_joins(:agents => {:agents => {:primary_contact => :agents}}).to_a + end + assert queries.any? { |sql| /agents_people_4/i =~ sql } + end + end + + def test_construct_finder_sql_executes_a_left_outer_join + assert_not_equal Author.count, Author.joins(:posts).count + assert_equal Author.count, Author.left_outer_joins(:posts).count + end + + def test_construct_finder_sql_ignores_empty_left_outer_joins_hash + queries = capture_sql { Author.left_outer_joins({}) } + assert queries.none? { |sql| /LEFT OUTER JOIN/i =~ sql } + end + + def test_construct_finder_sql_ignores_empty_left_outer_joins_array + queries = capture_sql { Author.left_outer_joins([]) } + assert queries.none? { |sql| /LEFT OUTER JOIN/i =~ sql } + end + + def test_left_outer_joins_forbids_to_use_string_as_argument + assert_raise(ArgumentError){ Author.left_outer_joins('LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"').to_a } + end + + def test_join_conditions_added_to_join_clause + queries = capture_sql { Author.left_outer_joins(:essays).to_a } + assert queries.any? { |sql| /writer_type.*?=.*?(Author|\?|\$1)/i =~ sql } + assert queries.none? { |sql| /WHERE/i =~ sql } + end + + def test_find_with_sti_join + scope = Post.left_outer_joins(:special_comments).where(:id => posts(:sti_comments).id) + + # The join should match SpecialComment and its subclasses only + assert scope.where("comments.type" => "Comment").empty? + assert !scope.where("comments.type" => "SpecialComment").empty? + assert !scope.where("comments.type" => "SubSpecialComment").empty? + end + + def test_does_not_override_select + authors = Author.select("authors.name, #{%{(authors.author_address_id || ' ' || authors.author_address_extra_id) as addr_id}}").left_outer_joins(:posts) + assert authors.any? + assert authors.first.respond_to?(:addr_id) + end + + test "the default scope of the target is applied when joining associations" do + author = Author.create! name: "Jon" + author.categorizations.create! + author.categorizations.create! special: true + + assert_equal [author], Author.where(id: author).left_outer_joins(:special_categorizations) + end +end diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index 9cb70ee239..da65336305 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -161,7 +161,7 @@ class EachTest < ActiveRecord::TestCase end # posts.first will be ordered using id only. Title order scope should not apply here assert_not_equal first_post, posts.first - assert_equal posts(:welcome), posts.first + assert_equal posts(:welcome).id, posts.first.id end def test_find_in_batches_should_not_ignore_the_default_scope_if_it_is_other_then_order diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index a0eaa66e94..f30ed4fcc8 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -955,3 +955,17 @@ class FixturesWithAbstractBelongsTo < ActiveRecord::TestCase assert_equal pirates(:blackbeard), doubloons(:blackbeards_doubloon).pirate end end + +class FixtureClassNamesTest < ActiveRecord::TestCase + def setup + @saved_cache = self.fixture_class_names.dup + end + + def teardown + self.fixture_class_names.replace(@saved_cache) + end + + test "fixture_class_names returns nil for unregistered identifier" do + assert_nil self.fixture_class_names['unregistered_identifier'] + end +end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 7521f0573a..cd23c1b3e1 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -297,6 +297,11 @@ class RelationTest < ActiveRecord::TestCase assert_equal 3, tags.length end + def test_finding_with_sanitized_order + query = Tag.order(["field(id, ?)", [1,3,2]]).to_sql + assert_match(/field\(id, 1,3,2\)/, query) + end + def test_finding_with_order_limit_and_offset entrants = Entrant.order("id ASC").limit(2).offset(1) diff --git a/activerecord/test/fixtures/content.yml b/activerecord/test/fixtures/content.yml new file mode 100644 index 0000000000..0d12ee03dc --- /dev/null +++ b/activerecord/test/fixtures/content.yml @@ -0,0 +1,3 @@ +content: + id: 1 + title: How to use Rails diff --git a/activerecord/test/fixtures/content_positions.yml b/activerecord/test/fixtures/content_positions.yml new file mode 100644 index 0000000000..9e85773f8e --- /dev/null +++ b/activerecord/test/fixtures/content_positions.yml @@ -0,0 +1,3 @@ +content_positions: + id: 1 + content_id: 1 diff --git a/activerecord/test/models/content.rb b/activerecord/test/models/content.rb new file mode 100644 index 0000000000..140e1dfc78 --- /dev/null +++ b/activerecord/test/models/content.rb @@ -0,0 +1,40 @@ +class Content < ActiveRecord::Base + self.table_name = 'content' + has_one :content_position, dependent: :destroy + + def self.destroyed_ids + @destroyed_ids ||= [] + end + + before_destroy do |object| + Content.destroyed_ids << object.id + end +end + +class ContentWhichRequiresTwoDestroyCalls < ActiveRecord::Base + self.table_name = 'content' + has_one :content_position, foreign_key: 'content_id', dependent: :destroy + + after_initialize do + @destroy_count = 0 + end + + before_destroy do + @destroy_count += 1 + if @destroy_count == 1 + throw :abort + end + end +end + +class ContentPosition < ActiveRecord::Base + belongs_to :content, dependent: :destroy + + def self.destroyed_ids + @destroyed_ids ||= [] + end + + before_destroy do |object| + ContentPosition.destroyed_ids << object.id + end +end diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 81a18188d4..23cebe2602 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -185,6 +185,7 @@ class SubStiPost < StiPost end class FirstPost < ActiveRecord::Base + self.inheritance_column = :disabled self.table_name = 'posts' default_scope { where(:id => 1) } @@ -193,6 +194,7 @@ class FirstPost < ActiveRecord::Base end class PostWithDefaultInclude < ActiveRecord::Base + self.inheritance_column = :disabled self.table_name = 'posts' default_scope { includes(:comments) } has_many :comments, :foreign_key => :post_id @@ -204,6 +206,7 @@ class PostWithSpecialCategorization < Post end class PostWithDefaultScope < ActiveRecord::Base + self.inheritance_column = :disabled self.table_name = 'posts' default_scope { order(:title) } end @@ -225,11 +228,13 @@ class PostWithIncludesDefaultScope < ActiveRecord::Base end class SpecialPostWithDefaultScope < ActiveRecord::Base + self.inheritance_column = :disabled self.table_name = 'posts' default_scope { where(:id => [1, 5,6]) } end class PostThatLoadsCommentsInAnAfterSaveHook < ActiveRecord::Base + self.inheritance_column = :disabled self.table_name = 'posts' has_many :comments, class_name: "CommentThatAutomaticallyAltersPostBody", foreign_key: :post_id @@ -239,6 +244,7 @@ class PostThatLoadsCommentsInAnAfterSaveHook < ActiveRecord::Base end class PostWithAfterCreateCallback < ActiveRecord::Base + self.inheritance_column = :disabled self.table_name = 'posts' has_many :comments, foreign_key: :post_id @@ -248,6 +254,7 @@ class PostWithAfterCreateCallback < ActiveRecord::Base end class PostWithCommentWithDefaultScopeReferencesAssociation < ActiveRecord::Base + self.inheritance_column = :disabled self.table_name = 'posts' has_many :comment_with_default_scope_references_associations, foreign_key: :post_id has_one :first_comment, class_name: "CommentWithDefaultScopeReferencesAssociation", foreign_key: :post_id diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb index b034e0e267..efa8246f1e 100644 --- a/activerecord/test/models/project.rb +++ b/activerecord/test/models/project.rb @@ -15,6 +15,14 @@ class Project < ActiveRecord::Base belongs_to :firm has_one :lead_developer, through: :firm, inverse_of: :contracted_projects + begin + previous_value, ActiveRecord::Base.belongs_to_required_by_default = + ActiveRecord::Base.belongs_to_required_by_default, true + has_and_belongs_to_many :developers_required_by_default, class_name: "Developer" + ensure + ActiveRecord::Base.belongs_to_required_by_default = previous_value + end + attr_accessor :developers_log after_initialize :set_developers_log diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index ca1757acd0..66a1f5aa8a 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -207,6 +207,14 @@ ActiveRecord::Schema.define do add_index :companies, [:firm_id, :type], name: "company_partial_index", where: "rating > 10" add_index :companies, :name, name: 'company_name_index', using: :btree + create_table :content, force: true do |t| + t.string :title + end + + create_table :content_positions, force: true do |t| + t.integer :content_id + end + create_table :vegetables, force: true do |t| t.string :name t.integer :seller_id diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 3705fc57fc..7c0f3eae80 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,9 @@ +* `HashWithIndifferentAccess.new` respects the default value or proc on objects + that respond to `#to_hash`. `.new_from_hash_copying_default` simply invokes `.new`. + All calls to `.new_from_hash_copying_default` are replaced with `.new`. + + *Gordon Chan* + * Change Integer#year to return a Fixnum instead of a Float to improve consistency. diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 3996f583c2..d0e53eaf05 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -538,7 +538,7 @@ module ActiveSupport end def instrument(operation, key, options = nil) - log { "Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}" } + log { "Cache #{operation}: #{namespaced_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" } payload = { :key => key } payload.merge!(options) if options.is_a?(Hash) diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb index 28cb3e2a3b..6df7b4121b 100644 --- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb +++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb @@ -6,7 +6,7 @@ class Hash # # { a: 1 }.with_indifferent_access['a'] # => 1 def with_indifferent_access - ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default(self) + ActiveSupport::HashWithIndifferentAccess.new(self) end # Called when object is nested under an object that receives diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 0371f760b0..4ff35a45a1 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -59,6 +59,10 @@ module ActiveSupport if constructor.respond_to?(:to_hash) super() update(constructor) + + hash = constructor.to_hash + self.default = hash.default if hash.default + self.default_proc = hash.default_proc if hash.default_proc else super(constructor) end @@ -73,11 +77,12 @@ module ActiveSupport end def self.new_from_hash_copying_default(hash) - hash = hash.to_hash - new(hash).tap do |new_hash| - new_hash.default = hash.default - new_hash.default_proc = hash.default_proc if hash.default_proc - end + ActiveSupport::Deprecation.warn(<<-MSG.squish) + `ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default` + has been deprecated, and will be removed in Rails 5.1. The behavior of + this method is now identical to the behavior of `.new`. + MSG + new(hash) end def self.[](*args) @@ -206,7 +211,7 @@ module ActiveSupport # hash['a'] = nil # hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1} def reverse_merge(other_hash) - super(self.class.new_from_hash_copying_default(other_hash)) + super(self.class.new(other_hash)) end # Same semantics as +reverse_merge+ but modifies the receiver in-place. @@ -219,7 +224,7 @@ module ActiveSupport # h = { "a" => 100, "b" => 200 } # h.replace({ "c" => 300, "d" => 400 }) # => {"c"=>300, "d"=>400} def replace(other_hash) - super(self.class.new_from_hash_copying_default(other_hash)) + super(self.class.new(other_hash)) end # Removes the specified key from the hash. diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 910c1f91a5..79cc748cf5 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -284,8 +284,8 @@ module ActiveSupport # the current object's time and the +other+ time. # # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' - # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28 EST -05:00 - # now - 1000 # => Sun, 02 Nov 2014 01:09:48 EST -05:00 + # now = Time.zone.now # => Mon, 03 Nov 2014 00:26:28 EST -05:00 + # now - 1000 # => Mon, 03 Nov 2014 00:09:48 EST -05:00 # # If subtracting a Duration of variable length (i.e., years, months, days), # move backward from #time, otherwise move backward from #utc, for accuracy @@ -294,8 +294,8 @@ module ActiveSupport # For instance, a time - 24.hours will go subtract exactly 24 hours, while a # time - 1.day will subtract 23-25 hours, depending on the day. # - # now - 24.hours # => Sat, 01 Nov 2014 02:26:28 EDT -04:00 - # now - 1.day # => Sat, 01 Nov 2014 01:26:28 EDT -04:00 + # now - 24.hours # => Sun, 02 Nov 2014 01:26:28 EDT -04:00 + # now - 1.day # => Sun, 02 Nov 2014 00:26:28 EDT -04:00 def -(other) if other.acts_like?(:time) to_time - other.to_time @@ -307,10 +307,48 @@ module ActiveSupport end end + # Subtracts an interval of time from the current object's time and returns + # the result as a new TimeWithZone object. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # now = Time.zone.now # => Mon, 03 Nov 2014 00:26:28 EST -05:00 + # now.ago(1000) # => Mon, 03 Nov 2014 00:09:48 EST -05:00 + # + # If we're subtracting a Duration of variable length (i.e., years, months, + # days), move backward from #time, otherwise move backward from #utc, for + # accuracy when moving across DST boundaries. + # + # For instance, <tt>time.ago(24.hours)</tt> will move back exactly 24 hours, + # while <tt>time.ago(1.day)</tt> will move back 23-25 hours, depending on + # the day. + # + # now.ago(24.hours) # => Sun, 02 Nov 2014 01:26:28 EDT -04:00 + # now.ago(1.day) # => Sun, 02 Nov 2014 00:26:28 EDT -04:00 def ago(other) since(-other) end + # Uses Date to provide precise Time calculations for years, months, and days + # according to the proleptic Gregorian calendar. The result is returned as a + # new TimeWithZone object. + # + # The +options+ parameter takes a hash with any of these keys: + # <tt>:years</tt>, <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>, + # <tt>:hours</tt>, <tt>:minutes</tt>, <tt>:seconds</tt>. + # + # If advancing by a value of variable length (i.e., years, weeks, months, + # days), move forward from #time, otherwise move forward from #utc, for + # accuracy when moving across DST boundaries. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28 EDT -04:00 + # now.advance(seconds: 1) # => Sun, 02 Nov 2014 01:26:29 EDT -04:00 + # now.advance(minutes: 1) # => Sun, 02 Nov 2014 01:27:28 EDT -04:00 + # now.advance(hours: 1) # => Sun, 02 Nov 2014 01:26:28 EST -05:00 + # now.advance(days: 1) # => Mon, 03 Nov 2014 01:26:28 EST -05:00 + # now.advance(weeks: 1) # => Sun, 09 Nov 2014 01:26:28 EST -05:00 + # now.advance(months: 1) # => Tue, 02 Dec 2014 01:26:28 EST -05:00 + # now.advance(years: 1) # => Mon, 02 Nov 2015 01:26:28 EST -05:00 def advance(options) # If we're advancing a value of variable length (i.e., years, weeks, months, days), advance from #time, # otherwise advance from #utc, for accuracy when moving across DST boundaries diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index fb52613a23..854fcce4ef 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -1086,6 +1086,19 @@ class CacheStoreLoggerTest < ActiveSupport::TestCase assert @buffer.string.present? end + def test_log_with_string_namespace + @cache.fetch('foo', {namespace: 'string_namespace'}) { 'bar' } + assert_match %r{string_namespace:foo}, @buffer.string + end + + def test_log_with_proc_namespace + proc = Proc.new do + "proc_namespace" + end + @cache.fetch('foo', {:namespace => proc}) { 'bar' } + assert_match %r{proc_namespace:foo}, @buffer.string + end + def test_mute_logging @cache.mute { @cache.fetch('foo') { 'bar' } } assert @buffer.string.blank? diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 4624338df1..265416dce7 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -1042,7 +1042,25 @@ class HashExtTest < ActiveSupport::TestCase hash = Hash.new hash.default_proc = proc { |h, k| raise "walrus" } - assert_nothing_raised { HashWithIndifferentAccess.new_from_hash_copying_default(hash) } + assert_deprecated { HashWithIndifferentAccess.new_from_hash_copying_default(hash) } + end + + def test_new_with_to_hash_conversion_copies_default + normal_hash = Hash.new(3) + normal_hash[:a] = 1 + + hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash)) + assert_equal 1, hash[:a] + assert_equal 3, hash[:b] + end + + def test_new_with_to_hash_conversion_copies_default_proc + normal_hash = Hash.new { 1 + 2 } + normal_hash[:a] = 1 + + hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash)) + assert_equal 1, hash[:a] + assert_equal 3, hash[:b] end end diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 8ea0f383c0..b7773ea65a 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -69,6 +69,7 @@ The methods are: * `having` * `includes` * `joins` +* `left_outer_joins` * `limit` * `lock` * `none` @@ -80,7 +81,7 @@ The methods are: * `reorder` * `reverse_order` * `select` -* `uniq` +* `distinct` * `where` All of the above methods return an instance of `ActiveRecord::Relation`. @@ -935,25 +936,30 @@ end Joining Tables -------------- -Active Record provides a finder method called `joins` for specifying `JOIN` clauses on the resulting SQL. There are multiple ways to use the `joins` method. +Active Record provides two finder methods for specifying `JOIN` clauses on the +resulting SQL: `joins` and `left_outer_joins`. +While `joins` should be used for `INNER JOIN` or custom queries, +`left_outer_joins` is used for queries using `LEFT OUTER JOIN`. + +### `joins` -### Using a String SQL Fragment +There are multiple ways to use the `joins` method. + +#### Using a String SQL Fragment You can just supply the raw SQL specifying the `JOIN` clause to `joins`: ```ruby -Client.joins('LEFT OUTER JOIN addresses ON addresses.client_id = clients.id') +Author.joins("INNER JOIN posts ON posts.author_id = author.id AND posts.published = 't'") ``` This will result in the following SQL: ```sql -SELECT clients.* FROM clients LEFT OUTER JOIN addresses ON addresses.client_id = clients.id +SELECT clients.* FROM clients INNER JOIN posts ON posts.author_id = author.id AND posts.published = 't' ``` -### Using Array/Hash of Named Associations - -WARNING: This method only works with `INNER JOIN`. +#### Using Array/Hash of Named Associations Active Record lets you use the names of the [associations](association_basics.html) defined on the model as a shortcut for specifying `JOIN` clauses for those associations when using the `joins` method. @@ -986,7 +992,7 @@ end Now all of the following will produce the expected join queries using `INNER JOIN`: -#### Joining a Single Association +##### Joining a Single Association ```ruby Category.joins(:articles) @@ -999,7 +1005,7 @@ SELECT categories.* FROM categories INNER JOIN articles ON articles.category_id = categories.id ``` -Or, in English: "return a Category object for all categories with articles". Note that you will see duplicate categories if more than one article has the same category. If you want unique categories, you can use `Category.joins(:articles).uniq`. +Or, in English: "return a Category object for all categories with articles". Note that you will see duplicate categories if more than one article has the same category. If you want unique categories, you can use `Category.joins(:articles).distinct`. #### Joining Multiple Associations @@ -1017,7 +1023,7 @@ SELECT articles.* FROM articles Or, in English: "return all articles that have a category and at least one comment". Note again that articles with multiple comments will show up multiple times. -#### Joining Nested Associations (Single Level) +##### Joining Nested Associations (Single Level) ```ruby Article.joins(comments: :guest) @@ -1033,7 +1039,7 @@ SELECT articles.* FROM articles Or, in English: "return all articles that have a comment made by a guest." -#### Joining Nested Associations (Multiple Level) +##### Joining Nested Associations (Multiple Level) ```ruby Category.joins(articles: [{ comments: :guest }, :tags]) @@ -1049,7 +1055,7 @@ SELECT categories.* FROM categories INNER JOIN tags ON tags.article_id = articles.id ``` -### Specifying Conditions on the Joined Tables +#### Specifying Conditions on the Joined Tables You can specify conditions on the joined tables using the regular [Array](#array-conditions) and [String](#pure-string-conditions) conditions. [Hash conditions](#hash-conditions) provide a special syntax for specifying conditions for the joined tables: @@ -1067,6 +1073,26 @@ Client.joins(:orders).where(orders: { created_at: time_range }) This will find all clients who have orders that were created yesterday, again using a `BETWEEN` SQL expression. +### `left_outer_joins` + +If you want to select a set of records whether or not they have associated +records you can use the `left_outer_joins` method. + +```ruby +Author.left_outer_joins(:posts).uniq.select('authors.*, COUNT(posts.*) AS posts_count').group('authors.id') +``` + +Which produces: + +```sql +SELECT DISTINCT authors.*, COUNT(posts.*) AS posts_count FROM "authors" +LEFT OUTER JOIN posts ON posts.author_id = authors.id GROUP BY authors.id +``` + +Which means: "return all authors with their count of posts, whether or not they +have any posts at all" + + Eager Loading Associations -------------------------- diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md index e5a560edd0..f495acbf68 100644 --- a/guides/source/active_support_instrumentation.md +++ b/guides/source/active_support_instrumentation.md @@ -232,6 +232,7 @@ Active Record | `:sql` | SQL statement | | `:name` | Name of the operation | | `:connection_id` | `self.object_id` | +| `:binds` | Bind parameters | INFO. The adapters will add their own data as well. diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 999c533fb3..74cd9bdc7b 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -2163,7 +2163,7 @@ You can use any of the standard [querying methods](active_record_querying.html) * `order` * `readonly` * `select` -* `uniq` +* `distinct` ##### `where` @@ -2239,9 +2239,9 @@ If you use the `readonly` method, then the associated objects will be read-only The `select` method lets you override the SQL `SELECT` clause that is used to retrieve data about the associated objects. By default, Rails retrieves all columns. -##### `uniq` +##### `distinct` -Use the `uniq` method to remove duplicates from the collection. +Use the `distinct` method to remove duplicates from the collection. #### When are Objects Saved? diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 9337dd6407..f43b73cb9d 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,7 @@ +* Allow rake:stats to account for rake tasks in lib/tasks + + *Kevin Deisz* + * Added javascript to update the URL on mailer previews with the currently selected email format. Reloading the page now keeps you on your selected format rather than going back to the default html version. diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb index fd352dc9b7..8e9097e1ef 100644 --- a/railties/lib/rails/code_statistics.rb +++ b/railties/lib/rails/code_statistics.rb @@ -33,7 +33,7 @@ class CodeStatistics #:nodoc: Hash[@pairs.map{|pair| [pair.first, calculate_directory_statistics(pair.last)]}] end - def calculate_directory_statistics(directory, pattern = /.*\.(rb|js|coffee)$/) + def calculate_directory_statistics(directory, pattern = /.*\.(rb|js|coffee|rake)$/) stats = CodeStatisticsCalculator.new Dir.foreach(directory) do |file_name| diff --git a/railties/lib/rails/code_statistics_calculator.rb b/railties/lib/rails/code_statistics_calculator.rb index a142236dbe..fad13e8517 100644 --- a/railties/lib/rails/code_statistics_calculator.rb +++ b/railties/lib/rails/code_statistics_calculator.rb @@ -25,6 +25,7 @@ class CodeStatisticsCalculator #:nodoc: } PATTERNS[:minitest] = PATTERNS[:rb].merge method: /^\s*(def|test)\s+['"_a-z]/ + PATTERNS[:rake] = PATTERNS[:rb] def initialize(lines = 0, code_lines = 0, classes = 0, methods = 0) @lines = lines diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 0f44f4694e..56c9d3e354 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -162,7 +162,8 @@ module Rails def database_gemfile_entry return [] if options[:skip_active_record] - GemfileEntry.version gem_for_database, nil, + gem_name, gem_version = gem_for_database + GemfileEntry.version gem_name, gem_version, "Use #{options[:database]} as the database for Active Record" end @@ -202,6 +203,16 @@ module Rails def self.path(name, path, comment = nil) new(name, nil, comment, path: path) end + + def version + version = super + + if version.is_a?(Array) + version.join("', '") + else + version + end + end end def rails_gemfile_entry @@ -230,16 +241,16 @@ module Rails def gem_for_database # %w( mysql oracle postgresql sqlite3 frontbase ibm_db sqlserver jdbcmysql jdbcsqlite3 jdbcpostgresql ) case options[:database] - when "oracle" then "ruby-oci8" - when "postgresql" then "pg" - when "frontbase" then "ruby-frontbase" - when "mysql" then "mysql2" - when "sqlserver" then "activerecord-sqlserver-adapter" - when "jdbcmysql" then "activerecord-jdbcmysql-adapter" - when "jdbcsqlite3" then "activerecord-jdbcsqlite3-adapter" - when "jdbcpostgresql" then "activerecord-jdbcpostgresql-adapter" - when "jdbc" then "activerecord-jdbc-adapter" - else options[:database] + when "oracle" then ["ruby-oci8", nil] + when "postgresql" then ["pg", ["~> 0.18"]] + when "frontbase" then ["ruby-frontbase", nil] + when "mysql" then ["mysql2", [">= 0.3.18", "< 0.5"]] + when "sqlserver" then ["activerecord-sqlserver-adapter", nil] + when "jdbcmysql" then ["activerecord-jdbcmysql-adapter", nil] + when "jdbcsqlite3" then ["activerecord-jdbcsqlite3-adapter", nil] + when "jdbcpostgresql" then ["activerecord-jdbcpostgresql-adapter", nil] + when "jdbc" then ["activerecord-jdbc-adapter", nil] + else [options[:database], nil] end end diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt index 75ea52828e..8bb4440f55 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt @@ -1,23 +1,24 @@ <!DOCTYPE html> <html> -<head> - <title><%= camelized %></title> - <%- if options[:skip_javascript] -%> - <%%= stylesheet_link_tag 'application', media: 'all' %> - <%- else -%> - <%- if gemfile_entries.any? { |m| m.name == 'turbolinks' } -%> - <%%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> - <%%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> + <head> + <title><%= camelized %></title> + <%- if options[:skip_javascript] -%> + <%%= stylesheet_link_tag 'application', media: 'all' %> <%- else -%> - <%%= stylesheet_link_tag 'application', media: 'all' %> - <%%= javascript_include_tag 'application' %> + <%- if gemfile_entries.any? { |m| m.name == 'turbolinks' } -%> + <%%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> + <%%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> + <%- else -%> + <%%= stylesheet_link_tag 'application', media: 'all' %> + <%%= javascript_include_tag 'application' %> + <%- end -%> <%- end -%> - <%- end -%> - <%%= csrf_meta_tags %> -</head> -<body> + <%%= csrf_meta_tags %> + </head> -<%%= yield %> + <body> -</body> + <%%= yield %> + + </body> </html> diff --git a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec index f8ece4fe73..ff242adbab 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec +++ b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec @@ -22,6 +22,6 @@ Gem::Specification.new do |s| <%= '# ' if options.dev? || options.edge? -%>s.add_dependency "rails", "~> <%= Rails::VERSION::STRING %>" <% unless options[:skip_active_record] -%> - s.add_development_dependency "<%= gem_for_database %>" + s.add_development_dependency "<%= gem_for_database[0] %>" <% end -%> end diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile index 2c91c6a0ea..f085a2577a 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/Gemfile +++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile @@ -11,7 +11,7 @@ gemspec <% if options[:skip_gemspec] -%> group :development do - gem '<%= gem_for_database %>' + gem '<%= gem_for_database[0] %>' end <% else -%> # Declare any dependencies that are still in development here instead of in diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake index 735c36eb3a..a919d36939 100644 --- a/railties/lib/rails/tasks/statistics.rake +++ b/railties/lib/rails/tasks/statistics.rake @@ -9,6 +9,7 @@ STATS_DIRECTORIES = [ %w(Mailers app/mailers), %w(Javascripts app/assets/javascripts), %w(Libraries lib/), + %w(Tasks lib/tasks), %w(APIs app/apis), %w(Controller\ tests test/controllers), %w(Helper\ tests test/helpers), diff --git a/railties/test/code_statistics_calculator_test.rb b/railties/test/code_statistics_calculator_test.rb index 46445a001a..cecc3908b3 100644 --- a/railties/test/code_statistics_calculator_test.rb +++ b/railties/test/code_statistics_calculator_test.rb @@ -299,6 +299,22 @@ class Animal assert_equal 0, @code_statistics_calculator.methods end + test 'count rake tasks' do + code = <<-'CODE' + task :test_task do + puts 'foo' + end + + CODE + + @code_statistics_calculator.add_by_io(StringIO.new(code), :rake) + + assert_equal 4, @code_statistics_calculator.lines + assert_equal 3, @code_statistics_calculator.code_lines + assert_equal 0, @code_statistics_calculator.classes + assert_equal 0, @code_statistics_calculator.methods + end + private def temp_file(name, content) dir = File.expand_path '../fixtures/tmp', __FILE__ diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index e5f10a89d3..446fef562b 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -264,7 +264,7 @@ class AppGeneratorTest < Rails::Generators::TestCase if defined?(JRUBY_VERSION) assert_gem "activerecord-jdbcmysql-adapter" else - assert_gem "mysql2" + assert_gem "mysql2", "'>= 0.3.18', '< 0.5'" end end @@ -279,7 +279,7 @@ class AppGeneratorTest < Rails::Generators::TestCase if defined?(JRUBY_VERSION) assert_gem "activerecord-jdbcpostgresql-adapter" else - assert_gem "pg" + assert_gem "pg", "'~> 0.18'" end end @@ -686,7 +686,11 @@ class AppGeneratorTest < Rails::Generators::TestCase capture(:stdout) { generator.send(*args, &block) } end - def assert_gem(gem) - assert_file "Gemfile", /^\s*gem\s+["']#{gem}["']$*/ + def assert_gem(gem, constraint = nil) + if constraint + assert_file "Gemfile", /^\s*gem\s+["']#{gem}["'], #{constraint}$*/ + else + assert_file "Gemfile", /^\s*gem\s+["']#{gem}["']$*/ + end end end |