diff options
50 files changed, 1004 insertions, 317 deletions
diff --git a/Gemfile.lock b/Gemfile.lock index eb14330c2b..0763efc001 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -162,7 +162,7 @@ GEM coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.10.0) + coffee-script-source (1.12.2) concurrent-ruby (1.0.2) connection_pool (2.2.0) cookiejar (0.3.3) diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 85f2501d42..c639178776 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -3,6 +3,7 @@ require "active_support/core_ext/hash/conversions" require "active_support/core_ext/object/to_query" require "active_support/core_ext/module/anonymous" require "active_support/core_ext/hash/keys" +require "active_support/testing/constant_lookup" require "action_controller/template_assertions" require "rails-dom-testing" diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index e7ea267211..6c632b8e75 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -595,6 +595,16 @@ module ActionView # # Where <tt>@document = Document.find(params[:id])</tt>. # + # When using labels +form_with+ requires setting the id on the field being + # labelled: + # + # <%= form_with(model: @post) do |form| %> + # <%= form.label :title %> + # <%= form.text_field :title, id: :post_title %> + # <% end %> + # + # See +label+ for more on how the +for+ attribute is derived. + # # === Mixing with other form helpers # # While +form_with+ uses a FormBuilder object it's possible to mix and @@ -690,6 +700,8 @@ module ActionView # form_with(**options.merge(builder: LabellingFormBuilder), &block) # end def form_with(model: nil, scope: nil, url: nil, format: nil, **options) + options[:skip_default_ids] = true + if model url ||= polymorphic_path(model, format: format) @@ -986,6 +998,16 @@ module ActionView # or model is yielded, so any generated field names are prefixed with # either the passed scope or the scope inferred from the <tt>:model</tt>. # + # When using labels +fields+ requires setting the id on the field being + # labelled: + # + # <%= fields :comment do |fields| %> + # <%= fields.label :body %> + # <%= fields.text_field :body, id: :comment_body %> + # <% end %> + # + # See +label+ for more on how the +for+ attribute is derived. + # # === Mixing with other form helpers # # While +form_with+ uses a FormBuilder object it's possible to mix and @@ -1003,7 +1025,8 @@ module ActionView # to work with an object as a base, like # FormOptionHelper#collection_select and DateHelper#datetime_select. def fields(scope = nil, model: nil, **options, &block) - # TODO: Remove when ids and classes are no longer output by default. + options[:skip_default_ids] = true + if model scope ||= model_name_from_record_or_class(model).param_key end @@ -1469,7 +1492,7 @@ module ActionView private def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: false, skip_enforcing_utf8: false, **options) - html_options = options.except(:index, :include_id, :builder).merge(html) + html_options = options.except(:index, :include_id, :skip_default_ids, :builder).merge(html) html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted? html_options[:enforce_utf8] = !skip_enforcing_utf8 @@ -1603,7 +1626,7 @@ module ActionView def initialize(object_name, object, template, options) @nested_child_index = {} @object_name, @object, @template, @options = object_name, object, template, options - @default_options = @options ? @options.slice(:index, :namespace) : {} + @default_options = @options ? @options.slice(:index, :namespace, :skip_default_ids) : {} convert_to_legacy_options(@options) @@ -1910,6 +1933,8 @@ module ActionView # See the docs for the <tt>ActionView::FormHelper.fields</tt> helper method. def fields(scope = nil, model: nil, **options, &block) + options[:skip_default_ids] = true + convert_to_legacy_options(options) fields_for(scope || model, model, **options, &block) @@ -2268,10 +2293,6 @@ module ActionView if options.key?(:skip_id) options[:include_id] = !options.delete(:skip_id) end - - if options.key?(:local) - options[:remote] = !options.delete(:local) - end end end end diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index 7bd473507b..ffc52569f2 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -461,7 +461,7 @@ module ActionView end # Creates a button element that defines a <tt>submit</tt> button, - # <tt>reset</tt>button or a generic button which can be used in + # <tt>reset</tt> button or a generic button which can be used in # JavaScript, for example. You can use the button tag as a regular # submit tag but it isn't supported in legacy browsers. However, # the button tag does allow for richer labels such as images and emphasis, diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb index 75b898c3e9..9e80f0b2ee 100644 --- a/actionview/lib/action_view/helpers/number_helper.rb +++ b/actionview/lib/action_view/helpers/number_helper.rb @@ -171,6 +171,9 @@ module ActionView # to ","). # * <tt>:separator</tt> - Sets the separator between the # fractional and integer digits (defaults to "."). + # * <tt>:delimiter_pattern</tt> - Sets a custom regular expression used for + # deriving the placement of delimiter. Helpful when using currency formats + # like INR. # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when # the argument is invalid. # @@ -187,6 +190,9 @@ module ActionView # number_with_delimiter(98765432.98, delimiter: " ", separator: ",") # # => 98 765 432,98 # + # number_with_delimiter("123456.78", + # delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/) # => "1,23,456.78" + # # number_with_delimiter("112a", raise: true) # => raise InvalidNumberError def number_with_delimiter(number, options = {}) delegate_number_helper_method(:number_to_delimited, number, options) diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb index cf8a6d6028..b8c446cbed 100644 --- a/actionview/lib/action_view/helpers/tags/base.rb +++ b/actionview/lib/action_view/helpers/tags/base.rb @@ -13,6 +13,7 @@ module ActionView @object_name.sub!(/\[\]$/, "") || @object_name.sub!(/\[\]\]$/, "]") @object = retrieve_object(options.delete(:object)) + @skip_default_ids = options.delete(:skip_default_ids) @options = options @auto_index = Regexp.last_match ? retrieve_autoindex(Regexp.last_match.pre_match) : nil end @@ -81,9 +82,12 @@ module ActionView def add_default_name_and_id(options) index = name_and_id_index(options) options["name"] = options.fetch("name") { tag_name(options["multiple"], index) } - options["id"] = options.fetch("id") { tag_id(index) } - if namespace = options.delete("namespace") - options["id"] = options["id"] ? "#{namespace}_#{options['id']}" : namespace + + unless skip_default_ids? + options["id"] = options.fetch("id") { tag_id(index) } + if namespace = options.delete("namespace") + options["id"] = options["id"] ? "#{namespace}_#{options['id']}" : namespace + end end end @@ -154,6 +158,10 @@ module ActionView def name_and_id_index(options) options.key?("index") ? options.delete("index") || "" : @auto_index end + + def skip_default_ids? + @skip_default_ids + end end end end diff --git a/actionview/lib/action_view/helpers/tags/label.rb b/actionview/lib/action_view/helpers/tags/label.rb index b31d5fda66..cab15ae201 100644 --- a/actionview/lib/action_view/helpers/tags/label.rb +++ b/actionview/lib/action_view/helpers/tags/label.rb @@ -73,6 +73,10 @@ module ActionView def render_component(builder) builder.translation end + + def skip_default_ids? + false # The id is used as the `for` attribute. + end end end end diff --git a/actionview/test/template/form_helper/form_with_test.rb b/actionview/test/template/form_helper/form_with_test.rb index c80a2f61b9..c078b47e14 100644 --- a/actionview/test/template/form_helper/form_with_test.rb +++ b/actionview/test/template/form_helper/form_with_test.rb @@ -116,6 +116,16 @@ class FormWithActsLikeFormTagTest < FormWithTest assert_dom_equal expected, output_buffer end + + def test_form_with_with_block_in_erb_and_local_true + output_buffer = render_erb("<%= form_with(url: 'http://www.example.com', local: true) do %>Hello world!<% end %>") + + expected = whole_form("http://www.example.com", local: true) do + "Hello world!" + end + + assert_dom_equal expected, output_buffer + end end class FormWithActsLikeFormForTest < FormWithTest @@ -301,10 +311,10 @@ class FormWithActsLikeFormForTest < FormWithTest expected = whole_form("/posts/123", "create-post", method: "patch") do "<label for='post_title'>The Title</label>" + - "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + + "<input name='post[title]' type='text' value='Hello World' />" + + "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + - "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + + "<input name='post[secret]' checked='checked' type='checkbox' value='1' />" + "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />" + "<button name='button' type='submit'>Create post</button>" + "<button name='button' type='submit'><span>Create post</span></button>" @@ -322,9 +332,9 @@ class FormWithActsLikeFormForTest < FormWithTest expected = whole_form("/posts") do "<input type='hidden' name='post[active]' value='' />" + - "<input id='post_active_true' name='post[active]' type='radio' value='true' />" + + "<input name='post[active]' type='radio' value='true' />" + "<label for='post_active_true'>true</label>" + - "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" + + "<input checked='checked' name='post[active]' type='radio' value='false' />" + "<label for='post_active_false'>false</label>" end @@ -345,10 +355,10 @@ class FormWithActsLikeFormForTest < FormWithTest expected = whole_form("/posts") do "<input type='hidden' name='post[active]' value='' />" + "<label for='post_active_true'>" + - "<input id='post_active_true' name='post[active]' type='radio' value='true' />" + + "<input name='post[active]' type='radio' value='true' />" + "true</label>" + "<label for='post_active_false'>" + - "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" + + "<input checked='checked' name='post[active]' type='radio' value='false' />" + "false</label>" end @@ -371,12 +381,12 @@ class FormWithActsLikeFormForTest < FormWithTest expected = whole_form("/posts") do "<input type='hidden' name='post[active]' value='' />" + "<label for='post_active_true'>" + - "<input id='post_active_true' name='post[active]' type='radio' value='true' />" + + "<input name='post[active]' type='radio' value='true' />" + "true</label>" + "<label for='post_active_false'>" + - "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" + + "<input checked='checked' name='post[active]' type='radio' value='false' />" + "false</label>" + - "<input id='post_id' name='post[id]' type='hidden' value='1' />" + "<input name='post[id]' type='hidden' value='1' />" end assert_dom_equal expected, output_buffer @@ -392,9 +402,9 @@ class FormWithActsLikeFormForTest < FormWithTest expected = whole_form("/posts") do "<input type='hidden' name='post[1][active]' value='' />" + - "<input id='post_1_active_true' name='post[1][active]' type='radio' value='true' />" + + "<input name='post[1][active]' type='radio' value='true' />" + "<label for='post_1_active_true'>true</label>" + - "<input checked='checked' id='post_1_active_false' name='post[1][active]' type='radio' value='false' />" + + "<input checked='checked' name='post[1][active]' type='radio' value='false' />" + "<label for='post_1_active_false'>false</label>" end @@ -411,11 +421,11 @@ class FormWithActsLikeFormForTest < FormWithTest expected = whole_form("/posts") do "<input name='post[tag_ids][]' type='hidden' value='' />" + - "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" + + "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' />" + "<label for='post_tag_ids_1'>Tag 1</label>" + - "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" + + "<input name='post[tag_ids][]' type='checkbox' value='2' />" + "<label for='post_tag_ids_2'>Tag 2</label>" + - "<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" + + "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' />" + "<label for='post_tag_ids_3'>Tag 3</label>" end @@ -436,13 +446,13 @@ class FormWithActsLikeFormForTest < FormWithTest expected = whole_form("/posts") do "<input name='post[tag_ids][]' type='hidden' value='' />" + "<label for='post_tag_ids_1'>" + - "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" + + "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' />" + "Tag 1</label>" + "<label for='post_tag_ids_2'>" + - "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" + + "<input name='post[tag_ids][]' type='checkbox' value='2' />" + "Tag 2</label>" + "<label for='post_tag_ids_3'>" + - "<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" + + "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' />" + "Tag 3</label>" end @@ -466,15 +476,15 @@ class FormWithActsLikeFormForTest < FormWithTest expected = whole_form("/posts") do "<input name='post[tag_ids][]' type='hidden' value='' />" + "<label for='post_tag_ids_1'>" + - "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" + + "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' />" + "Tag 1</label>" + "<label for='post_tag_ids_2'>" + - "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" + + "<input name='post[tag_ids][]' type='checkbox' value='2' />" + "Tag 2</label>" + "<label for='post_tag_ids_3'>" + - "<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" + + "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' />" + "Tag 3</label>" + - "<input id='post_id' name='post[id]' type='hidden' value='1' />" + "<input name='post[id]' type='hidden' value='1' />" end assert_dom_equal expected, output_buffer @@ -491,7 +501,7 @@ class FormWithActsLikeFormForTest < FormWithTest expected = whole_form("/posts") do "<input name='post[1][tag_ids][]' type='hidden' value='' />" + - "<input checked='checked' id='post_1_tag_ids_1' name='post[1][tag_ids][]' type='checkbox' value='1' />" + + "<input checked='checked' name='post[1][tag_ids][]' type='checkbox' value='1' />" + "<label for='post_1_tag_ids_1'>Tag 1</label>" end @@ -506,7 +516,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", "create-post", method: "patch", multipart: true) do - "<input name='post[file]' type='file' id='post_file' />" + "<input name='post[file]' type='file' />" end assert_dom_equal expected, output_buffer @@ -522,7 +532,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch", multipart: true) do - "<input name='post[comment][file]' type='file' id='post_comment_file' />" + "<input name='post[comment][file]' type='file' />" end assert_dom_equal expected, output_buffer @@ -561,7 +571,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/44", method: "patch") do - "<input name='post[title]' type='text' id='post_title' value='And his name will be forty and four.' />" + + "<input name='post[title]' type='text' value='And his name will be forty and four.' />" + "<input name='commit' data-disable-with='Edit post' type='submit' value='Edit post' />" end @@ -579,10 +589,10 @@ class FormWithActsLikeFormForTest < FormWithTest expected = whole_form("/posts/123", "create-post", method: "patch") do "<label for='other_name_title' class='post_title'>Title</label>" + - "<input name='other_name[title]' id='other_name_title' value='Hello World' type='text' />" + - "<textarea name='other_name[body]' id='other_name_body'>\nBack to the hill and over it again!</textarea>" + + "<input name='other_name[title]' value='Hello World' type='text' />" + + "<textarea name='other_name[body]'>\nBack to the hill and over it again!</textarea>" + "<input name='other_name[secret]' value='0' type='hidden' />" + - "<input name='other_name[secret]' checked='checked' id='other_name_secret' value='1' type='checkbox' />" + + "<input name='other_name[secret]' checked='checked' value='1' type='checkbox' />" + "<input name='commit' value='Create post' data-disable-with='Create post' type='submit' />" end @@ -609,10 +619,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/", "create-post", method: "delete") do - "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + + "<input name='post[title]' type='text' value='Hello World' />" + + "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + - "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + "<input name='post[secret]' checked='checked' type='checkbox' value='1' />" end assert_dom_equal expected, output_buffer @@ -626,10 +636,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/", "create-post", method: "delete") do - "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + + "<input name='post[title]' type='text' value='Hello World' />" + + "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + - "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + "<input name='post[secret]' checked='checked' type='checkbox' value='1' />" end assert_dom_equal expected, output_buffer @@ -643,7 +653,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/search", "search-post", method: "get") do - "<input name='post[title]' type='search' id='post_title' />" + "<input name='post[title]' type='search' />" end assert_dom_equal expected, output_buffer @@ -657,10 +667,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/", "create-post", method: "patch") do - "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + + "<input name='post[title]' type='text' value='Hello World' />" + + "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + - "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + "<input name='post[secret]' checked='checked' type='checkbox' value='1' />" end assert_dom_equal expected, output_buffer @@ -672,7 +682,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/", skip_enforcing_utf8: true) do - "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + "<input name='post[title]' type='text' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -684,7 +694,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/", skip_enforcing_utf8: false) do - "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + "<input name='post[title]' type='text' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -698,10 +708,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/", "create-post") do - "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + + "<input name='post[title]' type='text' value='Hello World' />" + + "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + - "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + "<input name='post[secret]' checked='checked' type='checkbox' value='1' />" end assert_dom_equal expected, output_buffer @@ -717,10 +727,10 @@ class FormWithActsLikeFormForTest < FormWithTest expected = whole_form("/posts/123", method: "patch") do "<label for='post_123_title'>Title</label>" + - "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" + - "<textarea name='post[123][body]' id='post_123_body'>\nBack to the hill and over it again!</textarea>" + + "<input name='post[123][title]' type='text' value='Hello World' />" + + "<textarea name='post[123][body]'>\nBack to the hill and over it again!</textarea>" + "<input name='post[123][secret]' type='hidden' value='0' />" + - "<input name='post[123][secret]' checked='checked' type='checkbox' id='post_123_secret' value='1' />" + "<input name='post[123][secret]' checked='checked' type='checkbox' value='1' />" end assert_dom_equal expected, output_buffer @@ -734,10 +744,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<input name='post[][title]' type='text' id='post__title' value='Hello World' />" + - "<textarea name='post[][body]' id='post__body'>\nBack to the hill and over it again!</textarea>" + + "<input name='post[][title]' type='text' value='Hello World' />" + + "<textarea name='post[][body]'>\nBack to the hill and over it again!</textarea>" + "<input name='post[][secret]' type='hidden' value='0' />" + - "<input name='post[][secret]' checked='checked' type='checkbox' id='post__secret' value='1' />" + "<input name='post[][secret]' checked='checked' type='checkbox' value='1' />" end assert_dom_equal expected, output_buffer @@ -752,7 +762,7 @@ class FormWithActsLikeFormForTest < FormWithTest expected = whole_form("/posts/123", method: "patch") do "<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" + - "<div class='field_with_errors'><input name='post[author_name]' type='text' id='post_author_name' value='' /></div>" + + "<div class='field_with_errors'><input name='post[author_name]' type='text' value='' /></div>" + "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />" end @@ -770,7 +780,7 @@ class FormWithActsLikeFormForTest < FormWithTest expected = whole_form("/posts/123", method: "patch") do "<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" + - "<div class='field_with_errors'><input name='post[author_name]' type='text' id='post_author_name' value='' /></div>" + + "<div class='field_with_errors'><input name='post[author_name]' type='text' value='' /></div>" + "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />" end @@ -800,10 +810,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", "namespace_edit_post_123", "edit_post", method: "patch") do - "<input name='post[title]' type='text' id='namespace_post_title' value='Hello World' />" + - "<textarea name='post[body]' id='namespace_post_body'>\nBack to the hill and over it again!</textarea>" + + "<input name='post[title]' type='text' value='Hello World' />" + + "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + - "<input name='post[secret]' checked='checked' type='checkbox' id='namespace_post_secret' value='1' />" + "<input name='post[secret]' checked='checked' type='checkbox' value='1' />" end assert_dom_equal expected, output_buffer @@ -877,7 +887,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<input name='post[comment][body]' type='text' id='post_comment_body' value='Hello World' />" + "<input name='post[comment][body]' type='text' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -897,7 +907,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form do - "<input name='posts[post][0][comment][1][name]' type='text' id='posts_post_0_comment_1_name' value='comment #1' />" + "<input name='posts[post][0][comment][1][name]' type='text' value='comment #1' />" end assert_dom_equal expected, output_buffer @@ -912,8 +922,8 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" + - "<input name='post[123][comment][][name]' type='text' id='post_123_comment__name' value='new comment' />" + "<input name='post[123][title]' type='text' value='Hello World' />" + + "<input name='post[123][comment][][name]' type='text' value='new comment' />" end assert_dom_equal expected, output_buffer @@ -928,8 +938,8 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<input name='post[1][title]' type='text' id='post_1_title' value='Hello World' />" + - "<input name='post[1][comment][1][name]' type='text' id='post_1_comment_1_name' value='new comment' />" + "<input name='post[1][title]' type='text' value='Hello World' />" + + "<input name='post[1][comment][1][name]' type='text' value='new comment' />" end assert_dom_equal expected, output_buffer @@ -943,7 +953,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<input name='post[1][comment][title]' type='text' id='post_1_comment_title' value='Hello World' />" + "<input name='post[1][comment][title]' type='text' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -957,7 +967,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<input name='post[1][comment][5][title]' type='text' id='post_1_comment_5_title' value='Hello World' />" + "<input name='post[1][comment][5][title]' type='text' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -971,7 +981,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<input name='post[123][comment][title]' type='text' id='post_123_comment_title' value='Hello World' />" + "<input name='post[123][comment][title]' type='text' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -985,7 +995,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<input name='post[comment][5][title]' type='radio' id='post_comment_5_title_hello' value='hello' />" + "<input name='post[comment][5][title]' type='radio' value='hello' />" end assert_dom_equal expected, output_buffer @@ -999,7 +1009,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<input name='post[123][comment][123][title]' type='text' id='post_123_comment_123_title' value='Hello World' />" + "<input name='post[123][comment][123][title]' type='text' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -1019,9 +1029,9 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<input name='post[123][comment][5][title]' type='text' id='post_123_comment_5_title' value='Hello World' />" + "<input name='post[123][comment][5][title]' type='text' value='Hello World' />" end + whole_form("/posts/123", method: "patch") do - "<input name='post[1][comment][123][title]' type='text' id='post_1_comment_123_title' value='Hello World' />" + "<input name='post[1][comment][123][title]' type='text' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -1038,8 +1048,8 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="new author" />' + '<input name="post[title]" type="text" value="Hello World" />' + + '<input name="post[author_attributes][name]" type="text" value="new author" />' end assert_dom_equal expected, output_buffer @@ -1065,9 +1075,9 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + - '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + '<input name="post[title]" type="text" value="Hello World" />' + + '<input name="post[author_attributes][name]" type="text" value="author #321" />' + + '<input name="post[author_attributes][id]" type="hidden" value="321" />' end assert_dom_equal expected, output_buffer @@ -1084,9 +1094,9 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + - '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + '<input name="post[title]" type="text" value="Hello World" />' + + '<input name="post[author_attributes][name]" type="text" value="author #321" />' + + '<input name="post[author_attributes][id]" type="hidden" value="321" />' end assert_dom_equal expected, output_buffer @@ -1103,8 +1113,8 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + '<input name="post[title]" type="text" value="Hello World" />' + + '<input name="post[author_attributes][name]" type="text" value="author #321" />' end assert_dom_equal expected, output_buffer @@ -1121,8 +1131,8 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + '<input name="post[title]" type="text" value="Hello World" />' + + '<input name="post[author_attributes][name]" type="text" value="author #321" />' end assert_dom_equal expected, output_buffer @@ -1139,9 +1149,9 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + - '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + '<input name="post[title]" type="text" value="Hello World" />' + + '<input name="post[author_attributes][name]" type="text" value="author #321" />' + + '<input name="post[author_attributes][id]" type="hidden" value="321" />' end assert_dom_equal expected, output_buffer @@ -1159,9 +1169,9 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + - '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + '<input name="post[title]" type="text" value="Hello World" />' + + '<input name="post[author_attributes][id]" type="hidden" value="321" />' + + '<input name="post[author_attributes][name]" type="text" value="author #321" />' end assert_dom_equal expected, output_buffer @@ -1180,11 +1190,11 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + - '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + '<input name="post[title]" type="text" value="Hello World" />' + + '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + + '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' + + '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + + '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />' end assert_dom_equal expected, output_buffer @@ -1207,11 +1217,11 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + - '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + '<input name="post[title]" type="text" value="Hello World" />' + + '<input name="post[author_attributes][name]" type="text" value="author #321" />' + + '<input name="post[author_attributes][id]" type="hidden" value="321" />' + + '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + + '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' end assert_dom_equal expected, output_buffer @@ -1234,10 +1244,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + '<input name="post[title]" type="text" value="Hello World" />' + + '<input name="post[author_attributes][name]" type="text" value="author #321" />' + + '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + + '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' end assert_dom_equal expected, output_buffer @@ -1260,11 +1270,11 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + - '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + '<input name="post[title]" type="text" value="Hello World" />' + + '<input name="post[author_attributes][name]" type="text" value="author #321" />' + + '<input name="post[author_attributes][id]" type="hidden" value="321" />' + + '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + + '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' end assert_dom_equal expected, output_buffer @@ -1283,11 +1293,11 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + - '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + '<input name="post[title]" type="text" value="Hello World" />' + + '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + + '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' + + '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + + '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />' end assert_dom_equal expected, output_buffer @@ -1307,11 +1317,11 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + - '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + '<input name="post[title]" type="text" value="Hello World" />' + + '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' + + '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + + '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />' + + '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' end assert_dom_equal expected, output_buffer @@ -1330,9 +1340,9 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="new comment" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="new comment" />' + '<input name="post[title]" type="text" value="Hello World" />' + + '<input name="post[comments_attributes][0][name]" type="text" value="new comment" />' + + '<input name="post[comments_attributes][1][name]" type="text" value="new comment" />' end assert_dom_equal expected, output_buffer @@ -1351,10 +1361,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="new comment" />' + '<input name="post[title]" type="text" value="Hello World" />' + + '<input name="post[comments_attributes][0][name]" type="text" value="comment #321" />' + + '<input name="post[comments_attributes][0][id]" type="hidden" value="321" />' + + '<input name="post[comments_attributes][1][name]" type="text" value="new comment" />' end assert_dom_equal expected, output_buffer @@ -1369,7 +1379,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input name="post[title]" type="text" value="Hello World" />' end assert_dom_equal expected, output_buffer @@ -1386,11 +1396,11 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + - '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + '<input name="post[title]" type="text" value="Hello World" />' + + '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + + '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' + + '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + + '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />' end assert_dom_equal expected, output_buffer @@ -1407,11 +1417,11 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + - '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + '<input name="post[title]" type="text" value="Hello World" />' + + '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + + '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' + + '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + + '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />' end assert_dom_equal expected, output_buffer @@ -1442,11 +1452,11 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + - '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + '<input name="post[title]" type="text" value="Hello World" />' + + '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + + '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' + + '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + + '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />' end assert_dom_equal expected, output_buffer @@ -1465,10 +1475,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="new comment" />' + '<input name="post[title]" type="text" value="Hello World" />' + + '<input name="post[comments_attributes][0][name]" type="text" value="comment #321" />' + + '<input name="post[comments_attributes][0][id]" type="hidden" value="321" />' + + '<input name="post[comments_attributes][1][name]" type="text" value="new comment" />' end assert_dom_equal expected, output_buffer @@ -1485,8 +1495,8 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' + - '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />' + '<input name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' + + '<input name="post[comments_attributes][abc][id]" type="hidden" value="321" />' end assert_dom_equal expected, output_buffer @@ -1502,8 +1512,8 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' + - '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />' + '<input name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' + + '<input name="post[comments_attributes][abc][id]" type="hidden" value="321" />' end assert_dom_equal expected, output_buffer @@ -1525,8 +1535,8 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' + - '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />' + '<input name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' + + '<input name="post[comments_attributes][abc][id]" type="hidden" value="321" />' end assert_dom_equal expected, output_buffer @@ -1611,18 +1621,18 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' + - '<input id="post_comments_attributes_0_relevances_attributes_0_value" name="post[comments_attributes][0][relevances_attributes][0][value]" type="text" value="commentrelevance #314" />' + - '<input id="post_comments_attributes_0_relevances_attributes_0_id" name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + - '<input id="post_tags_attributes_0_value" name="post[tags_attributes][0][value]" type="text" value="tag #123" />' + - '<input id="post_tags_attributes_0_relevances_attributes_0_value" name="post[tags_attributes][0][relevances_attributes][0][value]" type="text" value="tagrelevance #3141" />' + - '<input id="post_tags_attributes_0_relevances_attributes_0_id" name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' + - '<input id="post_tags_attributes_0_id" name="post[tags_attributes][0][id]" type="hidden" value="123" />' + - '<input id="post_tags_attributes_1_value" name="post[tags_attributes][1][value]" type="text" value="tag #456" />' + - '<input id="post_tags_attributes_1_relevances_attributes_0_value" name="post[tags_attributes][1][relevances_attributes][0][value]" type="text" value="tagrelevance #31415" />' + - '<input id="post_tags_attributes_1_relevances_attributes_0_id" name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' + - '<input id="post_tags_attributes_1_id" name="post[tags_attributes][1][id]" type="hidden" value="456" />' + '<input name="post[comments_attributes][0][name]" type="text" value="comment #321" />' + + '<input name="post[comments_attributes][0][relevances_attributes][0][value]" type="text" value="commentrelevance #314" />' + + '<input name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' + + '<input name="post[comments_attributes][0][id]" type="hidden" value="321" />' + + '<input name="post[tags_attributes][0][value]" type="text" value="tag #123" />' + + '<input name="post[tags_attributes][0][relevances_attributes][0][value]" type="text" value="tagrelevance #3141" />' + + '<input name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' + + '<input name="post[tags_attributes][0][id]" type="hidden" value="123" />' + + '<input name="post[tags_attributes][1][value]" type="text" value="tag #456" />' + + '<input name="post[tags_attributes][1][relevances_attributes][0][value]" type="text" value="tagrelevance #31415" />' + + '<input name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' + + '<input name="post[tags_attributes][1][id]" type="hidden" value="456" />' end assert_dom_equal expected, output_buffer @@ -1638,7 +1648,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="hash backed author" />' + '<input name="post[author_attributes][name]" type="text" value="hash backed author" />' end assert_dom_equal expected, output_buffer @@ -1652,10 +1662,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = - "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + + "<input name='post[title]' type='text' value='Hello World' />" + + "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + - "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + "<input name='post[secret]' checked='checked' type='checkbox' value='1' />" assert_dom_equal expected, output_buffer end @@ -1668,10 +1678,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = - "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" + - "<textarea name='post[123][body]' id='post_123_body'>\nBack to the hill and over it again!</textarea>" + + "<input name='post[123][title]' type='text' value='Hello World' />" + + "<textarea name='post[123][body]'>\nBack to the hill and over it again!</textarea>" + "<input name='post[123][secret]' type='hidden' value='0' />" + - "<input name='post[123][secret]' checked='checked' type='checkbox' id='post_123_secret' value='1' />" + "<input name='post[123][secret]' checked='checked' type='checkbox' value='1' />" assert_dom_equal expected, output_buffer end @@ -1684,10 +1694,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = - "<input name='post[][title]' type='text' id='post__title' value='Hello World' />" + - "<textarea name='post[][body]' id='post__body'>\nBack to the hill and over it again!</textarea>" + + "<input name='post[][title]' type='text' value='Hello World' />" + + "<textarea name='post[][body]'>\nBack to the hill and over it again!</textarea>" + "<input name='post[][secret]' type='hidden' value='0' />" + - "<input name='post[][secret]' checked='checked' type='checkbox' id='post__secret' value='1' />" + "<input name='post[][secret]' checked='checked' type='checkbox' value='1' />" assert_dom_equal expected, output_buffer end @@ -1700,10 +1710,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = - "<input name='post[abc][title]' type='text' id='post_abc_title' value='Hello World' />" + - "<textarea name='post[abc][body]' id='post_abc_body'>\nBack to the hill and over it again!</textarea>" + + "<input name='post[abc][title]' type='text' value='Hello World' />" + + "<textarea name='post[abc][body]'>\nBack to the hill and over it again!</textarea>" + "<input name='post[abc][secret]' type='hidden' value='0' />" + - "<input name='post[abc][secret]' checked='checked' type='checkbox' id='post_abc_secret' value='1' />" + "<input name='post[abc][secret]' checked='checked' type='checkbox' value='1' />" assert_dom_equal expected, output_buffer end @@ -1716,10 +1726,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = - "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + + "<input name='post[title]' type='text' value='Hello World' />" + + "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + - "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + "<input name='post[secret]' checked='checked' type='checkbox' value='1' />" assert_dom_equal expected, output_buffer end @@ -1732,10 +1742,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = - "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + + "<input name='post[title]' type='text' value='Hello World' />" + + "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + - "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + "<input name='post[secret]' checked='checked' type='checkbox' value='1' />" assert_dom_equal expected, output_buffer end @@ -1747,7 +1757,7 @@ class FormWithActsLikeFormForTest < FormWithTest end assert_dom_equal "<label for=\"author_post_title\">Title</label>" + - "<input name='author[post][title]' type='text' id='author_post_title' value='Hello World' />", + "<input name='author[post][title]' type='text' value='Hello World' />", output_buffer end @@ -1758,7 +1768,7 @@ class FormWithActsLikeFormForTest < FormWithTest end assert_dom_equal "<label for=\"author_post_1_title\">Title</label>" + - "<input name='author[post][1][title]' type='text' id='author_post_1_title' value='Hello World' />", + "<input name='author[post][1][title]' type='text' value='Hello World' />", output_buffer end @@ -1777,10 +1787,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", "create-post", method: "patch") do - "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + + "<input name='post[title]' type='text' value='Hello World' />" + + "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + "<input name='parent_post[secret]' type='hidden' value='0' />" + - "<input name='parent_post[secret]' checked='checked' type='checkbox' id='parent_post_secret' value='1' />" + "<input name='parent_post[secret]' checked='checked' type='checkbox' value='1' />" end assert_dom_equal expected, output_buffer @@ -1797,9 +1807,9 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", "create-post", method: "patch") do - "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + - "<input name='post[comment][name]' type='text' id='post_comment_name' value='new comment' />" + "<input name='post[title]' type='text' value='Hello World' />" + + "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + + "<input name='post[comment][name]' type='text' value='new comment' />" end assert_dom_equal expected, output_buffer @@ -1813,7 +1823,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<input name='post[category][name]' type='text' id='post_category_name' />" + "<input name='post[category][name]' type='text' />" end assert_dom_equal expected, output_buffer @@ -1837,9 +1847,9 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" + - "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea><br/>" + - "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>" + "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>" + + "<label for='body'>Body:</label> <textarea name='post[body]'>\nBack to the hill and over it again!</textarea><br/>" + + "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' value='1' /><br/>" end assert_dom_equal expected, output_buffer @@ -1856,9 +1866,9 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" + - "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea><br/>" + - "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>" + "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>" + + "<label for='body'>Body:</label> <textarea name='post[body]'>\nBack to the hill and over it again!</textarea><br/>" + + "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' value='1' /><br/>" end assert_dom_equal expected, output_buffer @@ -1875,7 +1885,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" + "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>" end assert_dom_equal expected, output_buffer @@ -1890,7 +1900,7 @@ class FormWithActsLikeFormForTest < FormWithTest concat f.text_field(:title) end - expected = "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" + expected = "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>" assert_dom_equal expected, output_buffer end @@ -1902,7 +1912,7 @@ class FormWithActsLikeFormForTest < FormWithTest concat f.text_field(:title) end - expected = "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" + expected = "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>" assert_dom_equal expected, output_buffer end @@ -1915,9 +1925,9 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = - "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" + - "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea><br/>" + - "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>" + "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>" + + "<label for='body'>Body:</label> <textarea name='post[body]'>\nBack to the hill and over it again!</textarea><br/>" + + "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' value='1' /><br/>" assert_dom_equal expected, output_buffer end diff --git a/activejob/test/helper.rb b/activejob/test/helper.rb index 758506b3c0..776f7788de 100644 --- a/activejob/test/helper.rb +++ b/activejob/test/helper.rb @@ -5,6 +5,7 @@ ActiveSupport.halt_callback_chains_on_return_false = false GlobalID.app = "aj" @adapter = ENV["AJ_ADAPTER"] || "inline" +puts "Using #{@adapter}" if ENV["AJ_INTEGRATION_TESTS"] require "support/integration/helper" diff --git a/activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb b/activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb index facea12704..f783d286c5 100644 --- a/activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb +++ b/activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb @@ -1,7 +1,7 @@ module ActiveModel module Type - module Helpers - class AcceptsMultiparameterTime < Module # :nodoc: + module Helpers # :nodoc: all + class AcceptsMultiparameterTime < Module def initialize(defaults: {}) define_method(:cast) do |value| if value.is_a?(Hash) diff --git a/activemodel/lib/active_model/type/helpers/mutable.rb b/activemodel/lib/active_model/type/helpers/mutable.rb index 4dddbe4e5e..f3a17a1698 100644 --- a/activemodel/lib/active_model/type/helpers/mutable.rb +++ b/activemodel/lib/active_model/type/helpers/mutable.rb @@ -1,7 +1,7 @@ module ActiveModel module Type - module Helpers - module Mutable # :nodoc: + module Helpers # :nodoc: all + module Mutable def cast(value) deserialize(serialize(value)) end diff --git a/activemodel/lib/active_model/type/helpers/numeric.rb b/activemodel/lib/active_model/type/helpers/numeric.rb index 98533f8771..275822b738 100644 --- a/activemodel/lib/active_model/type/helpers/numeric.rb +++ b/activemodel/lib/active_model/type/helpers/numeric.rb @@ -1,7 +1,7 @@ module ActiveModel module Type - module Helpers - module Numeric # :nodoc: + module Helpers # :nodoc: all + module Numeric def cast(value) value = \ case value diff --git a/activemodel/lib/active_model/type/helpers/time_value.rb b/activemodel/lib/active_model/type/helpers/time_value.rb index 721f9543ed..e57a52104b 100644 --- a/activemodel/lib/active_model/type/helpers/time_value.rb +++ b/activemodel/lib/active_model/type/helpers/time_value.rb @@ -2,8 +2,8 @@ require "active_support/core_ext/time/zones" module ActiveModel module Type - module Helpers - module TimeValue # :nodoc: + module Helpers # :nodoc: all + module TimeValue def serialize(value) value = apply_seconds_precision(value) diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index e868d20fc8..e6ba06301d 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require "yaml" class ErrorsTest < ActiveModel::TestCase class Person diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 7401e8b26c..eac54e7566 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,24 @@ +* Notifications see frozen SQL string. + + Fixes #23774 + + *Richard Monette* + +* RuntimeErrors are no longer translated to ActiveRecord::StatementInvalid. + + *Richard Monette* + +* Change the schema cache format to use YAML instead of Marshal. + + *Kir Shatrov* + +* Support index length and order options using both string and symbol + column names. + + Fixes #27243. + + *Ryuta Kamizono* + * Raise `ActiveRecord::RangeError` when values that executed are out of range. *Ryuta Kamizono* diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index faccd1d641..947796eea0 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -10,9 +10,9 @@ module ActiveRecord def to_sql(arel, binds = []) if arel.respond_to?(:ast) collected = visitor.accept(arel.ast, collector) - collected.compile(binds, self) + collected.compile(binds, self).freeze else - arel + arel.dup.freeze end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 237367c8b3..284529b46e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -598,7 +598,12 @@ module ActiveRecord def translate_exception(exception, message) # override in derived class - ActiveRecord::StatementInvalid.new(message) + case exception + when RuntimeError + exception + else + ActiveRecord::StatementInvalid.new(message) + end end def without_prepared_statement?(binds) diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 02d546209d..61cd7ae4cc 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -40,6 +40,28 @@ module ActiveRecord Base.human_attribute_name(@name) end + def init_with(coder) + @name = coder["name"] + @table_name = coder["table_name"] + @sql_type_metadata = coder["sql_type_metadata"] + @null = coder["null"] + @default = coder["default"] + @default_function = coder["default_function"] + @collation = coder["collation"] + @comment = coder["comment"] + end + + def encode_with(coder) + coder["name"] = @name + coder["table_name"] = @table_name + coder["sql_type_metadata"] = @sql_type_metadata + coder["null"] = @null + coder["default"] = @default + coder["default_function"] = @default_function + coder["collation"] = @collation + coder["comment"] = @comment + end + def ==(other) other.is_a?(Column) && attributes_for_hash == other.attributes_for_hash diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb index 8219f132c3..3a319c4029 100644 --- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb @@ -21,6 +21,22 @@ module ActiveRecord @data_sources = @data_sources.dup end + def encode_with(coder) + coder["columns"] = @columns + coder["columns_hash"] = @columns_hash + coder["primary_keys"] = @primary_keys + coder["data_sources"] = @data_sources + coder["version"] = ActiveRecord::Migrator.current_version + end + + def init_with(coder) + @columns = coder["columns"] + @columns_hash = coder["columns_hash"] + @primary_keys = coder["primary_keys"] + @data_sources = coder["data_sources"] + @version = coder["version"] + end + def primary_keys(table_name) @primary_keys[table_name] ||= data_source_exists?(table_name) ? connection.primary_key(table_name) : nil end diff --git a/activerecord/lib/active_record/fixture_set/file.rb b/activerecord/lib/active_record/fixture_set/file.rb index 5ba354d758..6cf2e01179 100644 --- a/activerecord/lib/active_record/fixture_set/file.rb +++ b/activerecord/lib/active_record/fixture_set/file.rb @@ -66,10 +66,13 @@ module ActiveRecord # Validate our unmarshalled data. def validate(data) unless Hash === data || YAML::Omap === data - raise Fixture::FormatError, "fixture is not a hash" + raise Fixture::FormatError, "fixture is not a hash: #{@file}" end - raise Fixture::FormatError unless data.all? { |name, row| Hash === row } + invalid = data.reject { |_, row| Hash === row } + if invalid.any? + raise Fixture::FormatError, "fixture key is not a hash: #{@file}, keys: #{invalid.keys.inspect}" + end data end end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 7ce10df6d4..2701c5bca9 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -82,15 +82,15 @@ module ActiveRecord if config.active_record.delete(:use_schema_cache_dump) config.after_initialize do |app| ActiveSupport.on_load(:active_record) do - filename = File.join(app.config.paths["db"].first, "schema_cache.dump") + filename = File.join(app.config.paths["db"].first, "schema_cache.yml") if File.file?(filename) - cache = Marshal.load File.binread filename + cache = YAML.load(File.read(filename)) if cache.version == ActiveRecord::Migrator.current_version self.connection.schema_cache = cache self.connection_pool.schema_cache = cache.dup else - warn "Ignoring db/schema_cache.dump because it has expired. The current schema version is #{ActiveRecord::Migrator.current_version}, but the one in the cache is #{cache.version}." + warn "Ignoring db/schema_cache.yml because it has expired. The current schema version is #{ActiveRecord::Migrator.current_version}, but the one in the cache is #{cache.version}." end end end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 46235ab922..25d79a6c7d 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -265,19 +265,19 @@ db_namespace = namespace :db do end namespace :cache do - desc "Creates a db/schema_cache.dump file." + desc "Creates a db/schema_cache.yml file." task dump: [:environment, :load_config] do - con = ActiveRecord::Base.connection - filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump") + conn = ActiveRecord::Base.connection + filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.yml") - con.schema_cache.clear! - con.data_sources.each { |table| con.schema_cache.add(table) } - open(filename, "wb") { |f| f.write(Marshal.dump(con.schema_cache)) } + conn.schema_cache.clear! + conn.data_sources.each { |table| conn.schema_cache.add(table) } + open(filename, "wb") { |f| f.write(YAML.dump(conn.schema_cache)) } end - desc "Clears a db/schema_cache.dump file." + desc "Clears a db/schema_cache.yml file." task clear: [:environment, :load_config] do - filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump") + filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.yml") rm_f filename, verbose: false end end diff --git a/activerecord/test/assets/schema_dump_5_1.yml b/activerecord/test/assets/schema_dump_5_1.yml new file mode 100644 index 0000000000..f37977daf2 --- /dev/null +++ b/activerecord/test/assets/schema_dump_5_1.yml @@ -0,0 +1,345 @@ +--- !ruby/object:ActiveRecord::ConnectionAdapters::SchemaCache +columns: + posts: + - &1 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: id + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: INTEGER + type: :integer + limit: + precision: + scale: + 'null': false + default: + default_function: + collation: + comment: + - &2 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: author_id + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: integer + type: :integer + limit: + precision: + scale: + 'null': true + default: + default_function: + collation: + comment: + - &3 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: title + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: varchar + type: :string + limit: + precision: + scale: + 'null': false + default: + default_function: + collation: + comment: + - &4 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: body + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: text + type: :text + limit: + precision: + scale: + 'null': false + default: + default_function: + collation: + comment: + - &5 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: type + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: varchar + type: :string + limit: + precision: + scale: + 'null': true + default: + default_function: + collation: + comment: + - &6 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: comments_count + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: integer + type: :integer + limit: + precision: + scale: + 'null': true + default: '0' + default_function: + collation: + comment: + - &7 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: taggings_with_delete_all_count + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: integer + type: :integer + limit: + precision: + scale: + 'null': true + default: '0' + default_function: + collation: + comment: + - &8 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: taggings_with_destroy_count + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: integer + type: :integer + limit: + precision: + scale: + 'null': true + default: '0' + default_function: + collation: + comment: + - &9 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: tags_count + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: integer + type: :integer + limit: + precision: + scale: + 'null': true + default: '0' + default_function: + collation: + comment: + - &10 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: tags_with_destroy_count + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: integer + type: :integer + limit: + precision: + scale: + 'null': true + default: '0' + default_function: + collation: + comment: + - &11 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: tags_with_nullify_count + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: integer + type: :integer + limit: + precision: + scale: + 'null': true + default: '0' + default_function: + collation: + comment: +columns_hash: + posts: + id: *1 + author_id: *2 + title: *3 + body: *4 + type: *5 + comments_count: *6 + taggings_with_delete_all_count: *7 + taggings_with_destroy_count: *8 + tags_count: *9 + tags_with_destroy_count: *10 + tags_with_nullify_count: *11 +primary_keys: + posts: id +data_sources: + ar_internal_metadata: true + table_with_autoincrement: true + accounts: true + admin_accounts: true + admin_users: true + aircraft: true + articles: true + articles_magazines: true + articles_tags: true + audit_logs: true + authors: true + author_addresses: true + author_favorites: true + auto_id_tests: true + binaries: true + birds: true + books: true + booleans: true + bulbs: true + CamelCase: true + cars: true + carriers: true + categories: true + categories_posts: true + categorizations: true + citations: true + clubs: true + collections: true + colnametests: true + columns: true + comments: true + companies: true + content: true + content_positions: true + vegetables: true + computers: true + computers_developers: true + contracts: true + customers: true + customer_carriers: true + dashboards: true + developers: true + developers_projects: true + dog_lovers: true + dogs: true + doubloons: true + edges: true + engines: true + entrants: true + essays: true + events: true + eyes: true + funny_jokes: true + cold_jokes: true + friendships: true + goofy_string_id: true + having: true + guids: true + guitars: true + inept_wizards: true + integer_limits: true + invoices: true + iris: true + items: true + jobs: true + jobs_pool: true + keyboards: true + legacy_things: true + lessons: true + lessons_students: true + students: true + lint_models: true + line_items: true + lions: true + lock_without_defaults: true + lock_without_defaults_cust: true + magazines: true + mateys: true + members: true + member_details: true + member_friends: true + memberships: true + member_types: true + mentors: true + minivans: true + minimalistics: true + mixed_case_monkeys: true + mixins: true + movies: true + notifications: true + numeric_data: true + orders: true + organizations: true + owners: true + paint_colors: true + paint_textures: true + parrots: true + parrots_pirates: true + parrots_treasures: true + people: true + peoples_treasures: true + personal_legacy_things: true + pets: true + pets_treasures: true + pirates: true + posts: true + serialized_posts: true + images: true + price_estimates: true + products: true + product_types: true + projects: true + randomly_named_table1: true + randomly_named_table2: true + randomly_named_table3: true + ratings: true + readers: true + references: true + shape_expressions: true + ships: true + ship_parts: true + prisoners: true + shop_accounts: true + speedometers: true + sponsors: true + string_key_objects: true + subscribers: true + subscriptions: true + tags: true + taggings: true + tasks: true + topics: true + toys: true + traffic_lights: true + treasures: true + tuning_pegs: true + tyres: true + variants: true + vertices: true + warehouse-things: true + circles: true + squares: true + triangles: true + non_poly_ones: true + non_poly_twos: true + men: true + faces: true + interests: true + zines: true + wheels: true + countries: true + treaties: true + countries_treaties: true + liquid: true + molecules: true + electrons: true + weirds: true + nodes: true + trees: true + hotels: true + departments: true + cake_designers: true + drink_designers: true + chefs: true + recipes: true + records: true + overloaded_types: true + users: true + test_with_keyword_column_name: true + fk_test_has_pk: true + fk_test_has_fk: true +version: 0 diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 8de69869a4..3fce0a1df1 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -285,13 +285,13 @@ module ActiveRecord unless current_adapter?(:PostgreSQLAdapter) def test_log_invalid_encoding - error = assert_raise ActiveRecord::StatementInvalid do + error = assert_raises RuntimeError do @connection.send :log, "SELECT 'ы' FROM DUAL" do raise "ы".force_encoding(Encoding::ASCII_8BIT) end end - assert_not_nil error.cause + assert_not_nil error.message end end diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb index b400f4d2f9..2c778b1150 100644 --- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb @@ -143,7 +143,7 @@ class Mysql2ReservedWordTest < ActiveRecord::Mysql2TestCase end # custom create table, uses execute on connection to create a table, note: escapes table_name, does NOT escape columns - def create_tables_directly (tables, connection = @connection) + def create_tables_directly(tables, connection = @connection) tables.each do |table_name, column_properties| connection.execute("CREATE TABLE `#{table_name}` ( #{column_properties} )") end diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb index d4459603af..1b4f80fc67 100644 --- a/activerecord/test/cases/connection_adapters/schema_cache_test.rb +++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb @@ -12,6 +12,33 @@ module ActiveRecord assert_equal "id", @cache.primary_keys("posts") end + def test_yaml_dump_and_load + @cache.columns("posts") + @cache.columns_hash("posts") + @cache.data_sources("posts") + @cache.primary_keys("posts") + + new_cache = YAML.load(YAML.dump(@cache)) + assert_no_queries do + assert_equal 11, new_cache.columns("posts").size + assert_equal 11, new_cache.columns_hash("posts").size + assert new_cache.data_sources("posts") + assert_equal "id", new_cache.primary_keys("posts") + end + end + + def test_yaml_loads_5_1_dump + body = File.open(schema_dump_path).read + cache = YAML.load(body) + + assert_no_queries do + assert_equal 11, cache.columns("posts").size + assert_equal 11, cache.columns_hash("posts").size + assert cache.data_sources("posts") + assert_equal "id", cache.primary_keys("posts") + end + end + def test_primary_key_for_non_existent_table assert_nil @cache.primary_keys("omgponies") end @@ -45,10 +72,12 @@ module ActiveRecord @cache = Marshal.load(Marshal.dump(@cache)) - assert_equal 11, @cache.columns("posts").size - assert_equal 11, @cache.columns_hash("posts").size - assert @cache.data_sources("posts") - assert_equal "id", @cache.primary_keys("posts") + assert_no_queries do + assert_equal 11, @cache.columns("posts").size + assert_equal 11, @cache.columns_hash("posts").size + assert @cache.data_sources("posts") + assert_equal "id", @cache.primary_keys("posts") + end end def test_table_methods_deprecation @@ -56,6 +85,12 @@ module ActiveRecord assert_deprecated { assert @cache.tables("posts") } assert_deprecated { @cache.clear_table_cache!("posts") } end + + private + + def schema_dump_path + "test/assets/schema_dump_5_1.yml" + end end end end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 7eaf31aa24..f8724b0993 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -983,7 +983,6 @@ class FinderTest < ActiveRecord::TestCase assert_equal devs[2], Developer.offset(2).first assert_equal devs[-3], Developer.offset(2).last - assert_equal devs[-3], Developer.offset(2).last assert_equal devs[-3], Developer.offset(2).order("id DESC").first end diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index f3d0e4a1b1..ea3e8d7727 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -211,9 +211,19 @@ class FixturesTest < ActiveRecord::TestCase end def test_dirty_dirty_yaml_file - assert_raise(ActiveRecord::Fixture::FormatError) do - ActiveRecord::FixtureSet.new(Account.connection, "courses", Course, FIXTURES_ROOT + "/naked/yml/courses") + fixture_path = FIXTURES_ROOT + "/naked/yml/courses" + error = assert_raise(ActiveRecord::Fixture::FormatError) do + ActiveRecord::FixtureSet.new(Account.connection, "courses", Course, fixture_path) end + assert_equal "fixture is not a hash: #{fixture_path}.yml", error.to_s + end + + def test_yaml_file_with_one_invalid_fixture + fixture_path = FIXTURES_ROOT + "/naked/yml/courses_with_invalid_key" + error = assert_raise(ActiveRecord::Fixture::FormatError) do + ActiveRecord::FixtureSet.new(Account.connection, "courses", Course, fixture_path) + end + assert_equal "fixture key is not a hash: #{fixture_path}.yml, keys: [\"two\"]", error.to_s end def test_yaml_file_with_invalid_column diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 90054ce83d..4a49bfe9b1 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -202,6 +202,20 @@ class QueryCacheTest < ActiveRecord::TestCase ActiveSupport::Notifications.unsubscribe subscriber end + def test_query_cache_does_not_allow_sql_key_mutation + subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |_, _, _, _, payload| + payload[:sql].downcase! + end + + assert_raises RuntimeError do + ActiveRecord::Base.cache do + assert_queries(1) { Task.find(1); Task.find(1) } + end + end + ensure + ActiveSupport::Notifications.unsubscribe subscriber + end + def test_cache_is_flat Task.cache do assert_queries(1) { Topic.find(1); Topic.find(1); } diff --git a/activerecord/test/fixtures/naked/yml/courses_with_invalid_key.yml b/activerecord/test/fixtures/naked/yml/courses_with_invalid_key.yml new file mode 100644 index 0000000000..6f9da79b45 --- /dev/null +++ b/activerecord/test/fixtures/naked/yml/courses_with_invalid_key.yml @@ -0,0 +1,3 @@ +one: + id: 1 +two: ['not a hash'] diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 10095ee1bd..2a5c8deb11 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,9 @@ +* Change return value of `NilClass#duplicable?`, `FalseClass#duplicable?`, + `TrueClass#duplicable?`, `Symbol#duplicable?` and `Numeric#duplicable?` + to true with Ruby 2.4+. These classes can dup with Ruby 2.4+. + + *Yuji Yaginuma* + * Remove deprecated class `ActiveSupport::Concurrency::Latch` *Andrew White* diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index af8ddb176f..e6c79f2a38 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -109,16 +109,22 @@ module ActiveSupport invoke_sequence = Proc.new do skipped = nil while true - current, next_sequence = next_sequence, next_sequence.nested + current = next_sequence current.invoke_before(env) if current.final? env.value = !env.halted && (!block_given? || yield) elsif current.skip?(env) (skipped ||= []) << current + next_sequence = next_sequence.nested next else - target, block, method, *arguments = current.expand_call_template(env, invoke_sequence) - target.send(method, *arguments, &block) + next_sequence = next_sequence.nested + begin + target, block, method, *arguments = current.expand_call_template(env, invoke_sequence) + target.send(method, *arguments, &block) + ensure + next_sequence = current + end end current.invoke_after(env) skipped.pop.invoke_after(env) while skipped && skipped.first diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index aa2282cb7e..ebe31b38ca 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -1,7 +1,7 @@ #-- -# Most objects are cloneable, but not all. For example you can't dup +nil+: +# Most objects are cloneable, but not all. For example you can't dup methods: # -# nil.dup # => TypeError: can't dup NilClass +# method(:puts).dup # => TypeError: allocator undefined for Method # # Classes may signal their instances are not duplicable removing +dup+/+clone+ # or raising exceptions from them. So, to dup an arbitrary object you normally @@ -19,7 +19,7 @@ class Object # Can you safely dup this object? # - # False for +nil+, +false+, +true+, symbol, number, method objects; + # False for method objects; # true otherwise. def duplicable? true @@ -27,52 +27,77 @@ class Object end class NilClass - # +nil+ is not duplicable: - # - # nil.duplicable? # => false - # nil.dup # => TypeError: can't dup NilClass - def duplicable? - false + begin + nil.dup + rescue TypeError + + # +nil+ is not duplicable: + # + # nil.duplicable? # => false + # nil.dup # => TypeError: can't dup NilClass + def duplicable? + false + end end end class FalseClass - # +false+ is not duplicable: - # - # false.duplicable? # => false - # false.dup # => TypeError: can't dup FalseClass - def duplicable? - false + begin + false.dup + rescue TypeError + + # +false+ is not duplicable: + # + # false.duplicable? # => false + # false.dup # => TypeError: can't dup FalseClass + def duplicable? + false + end end end class TrueClass - # +true+ is not duplicable: - # - # true.duplicable? # => false - # true.dup # => TypeError: can't dup TrueClass - def duplicable? - false + begin + true.dup + rescue TypeError + + # +true+ is not duplicable: + # + # true.duplicable? # => false + # true.dup # => TypeError: can't dup TrueClass + def duplicable? + false + end end end class Symbol - # Symbols are not duplicable: - # - # :my_symbol.duplicable? # => false - # :my_symbol.dup # => TypeError: can't dup Symbol - def duplicable? - false + begin + :symbol.dup + rescue TypeError + + # Symbols are not duplicable: + # + # :my_symbol.duplicable? # => false + # :my_symbol.dup # => TypeError: can't dup Symbol + def duplicable? + false + end end end class Numeric - # Numbers are not duplicable: - # - # 3.duplicable? # => false - # 3.dup # => TypeError: can't dup Integer - def duplicable? - false + begin + 1.dup + rescue TypeError + + # Numbers are not duplicable: + # + # 3.duplicable? # => false + # 3.dup # => TypeError: can't dup Integer + def duplicable? + false + end end end diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index ef3df1240d..fa063af3f4 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -274,7 +274,7 @@ module ActiveSupport # Go down the ancestors to check if it is owned directly. The check # stops when we reach Object or the end of ancestors tree. - constant = constant.ancestors.inject do |const, ancestor| + constant = constant.ancestors.inject(constant) do |const, ancestor| break const if ancestor == Object break ancestor if ancestor.const_defined?(name, false) const diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index e16581d697..e3203ef076 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -68,7 +68,17 @@ module ActiveSupport "datetime" => Proc.new { |time| Time.xmlschema(time).utc rescue ::DateTime.parse(time).utc }, "integer" => Proc.new { |integer| integer.to_i }, "float" => Proc.new { |float| float.to_f }, - "decimal" => Proc.new { |number| BigDecimal(number) }, + "decimal" => Proc.new do |number| + if String === number + begin + BigDecimal(number) + rescue ArgumentError + BigDecimal('0') + end + else + BigDecimal(number) + end + end, "boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.to_s.strip) }, "string" => Proc.new { |string| string.to_s }, "yaml" => Proc.new { |yaml| YAML::load(yaml) rescue yaml }, diff --git a/activesupport/test/autoloading_fixtures/prepend.rb b/activesupport/test/autoloading_fixtures/prepend.rb new file mode 100644 index 0000000000..3134d1df2b --- /dev/null +++ b/activesupport/test/autoloading_fixtures/prepend.rb @@ -0,0 +1,8 @@ +class SubClassConflict +end + +class Prepend + module PrependedModule + end + prepend PrependedModule +end diff --git a/activesupport/test/autoloading_fixtures/prepend/sub_class_conflict.rb b/activesupport/test/autoloading_fixtures/prepend/sub_class_conflict.rb new file mode 100644 index 0000000000..090dda3043 --- /dev/null +++ b/activesupport/test/autoloading_fixtures/prepend/sub_class_conflict.rb @@ -0,0 +1,2 @@ +class Prepend::SubClassConflict +end diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 551235e9e8..73b5a7d2a8 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -983,7 +983,7 @@ class MemoryStoreTest < ActiveSupport::TestCase end def test_pruning_is_capped_at_a_max_time - def @cache.delete_entry (*args) + def @cache.delete_entry(*args) sleep(0.01) super end diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index aadc40ab84..22f66978f8 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -224,10 +224,51 @@ module CallbacksTest define_callbacks :save end - class AroundPerson < MySuper + class MySlate < MySuper attr_reader :history attr_accessor :save_fails + def initialize + @history = [] + end + + def save + run_callbacks :save do + raise "inside save" if save_fails + @history << "running" + end + end + + def no; false; end + def yes; true; end + + def method_missing(sym, *) + case sym + when /^log_(.*)/ + @history << $1 + nil + when /^wrap_(.*)/ + @history << "wrap_#$1" + yield + @history << "unwrap_#$1" + nil + when /^double_(.*)/ + @history << "first_#$1" + yield + @history << "second_#$1" + yield + @history << "third_#$1" + else + super + end + end + + def respond_to_missing?(sym) + sym =~ /^(log|wrap)_/ || super + end + end + + class AroundPerson < MySlate set_callback :save, :before, :nope, if: :no set_callback :save, :before, :nope, unless: :yes set_callback :save, :after, :tweedle @@ -242,9 +283,6 @@ module CallbacksTest set_callback :save, :around, :w0tno, if: :no set_callback :save, :around, :tweedle_deedle - def no; false; end - def yes; true; end - def nope @history << "boom" end @@ -283,17 +321,6 @@ module CallbacksTest yield @history << "tweedle deedle post" end - - def initialize - @history = [] - end - - def save - run_callbacks :save do - raise "inside save" if save_fails - @history << "running" - end - end end class AroundPersonResult < MySuper @@ -408,6 +435,32 @@ module CallbacksTest end end + class DoubleYieldTest < ActiveSupport::TestCase + class DoubleYieldModel < MySlate + set_callback :save, :around, :wrap_outer + set_callback :save, :around, :double_trouble + set_callback :save, :around, :wrap_inner + end + + def test_double_save + double = DoubleYieldModel.new + double.save + assert_equal [ + "wrap_outer", + "first_trouble", + "wrap_inner", + "running", + "unwrap_inner", + "second_trouble", + "wrap_inner", + "running", + "unwrap_inner", + "third_trouble", + "unwrap_outer", + ], double.history + end + end + class CallStackTest < ActiveSupport::TestCase def test_tidy_call_stack around = AroundPerson.new diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb index af2db8c991..32b720bcbb 100644 --- a/activesupport/test/constantize_test_cases.rb +++ b/activesupport/test/constantize_test_cases.rb @@ -73,6 +73,11 @@ module ConstantizeTestCases yield("RaisesNoMethodError") end end + + with_autoloading_fixtures do + yield("Prepend::SubClassConflict") + assert_equal "constant", defined?(Prepend::SubClassConflict) + end end def run_safe_constantize_tests_on diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb index 677e32db1d..c2a1e68f57 100644 --- a/activesupport/test/core_ext/object/duplicable_test.rb +++ b/activesupport/test/core_ext/object/duplicable_test.rb @@ -4,9 +4,13 @@ require "active_support/core_ext/object/duplicable" require "active_support/core_ext/numeric/time" class DuplicableTest < ActiveSupport::TestCase - RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, method(:puts)] - ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new] - ALLOW_DUP << BigDecimal.new("4.56") + if RUBY_VERSION >= "2.4.0" + RAISE_DUP = [method(:puts)] + ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56"), nil, false, true, :symbol, 1, 2.3] + else + RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, method(:puts)] + ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56")] + end def test_duplicable rubinius_skip "* Method#dup is allowed at the moment on Rubinius\n" \ diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index 20cd34c182..16c7e782bc 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -162,6 +162,10 @@ $ cd actionpack $ bundle exec ruby -Itest path/to/test.rb -n test_name ``` +### Railties Setup + +Some Railties tests depend on a JavaScript runtime environment, such as having [Node.js](https://nodejs.org/) installed. + ### Active Record Setup Active Record's test suite runs three times: once for SQLite3, once for MySQL, and once for PostgreSQL. We are going to see now how to set up the environment for them. diff --git a/guides/source/generators.md b/guides/source/generators.md index 32bbdc554a..d0b6cef3fd 100644 --- a/guides/source/generators.md +++ b/guides/source/generators.md @@ -208,7 +208,15 @@ $ bin/rails generate scaffold User name:string Looking at this output, it's easy to understand how generators work in Rails 3.0 and above. The scaffold generator doesn't actually generate anything, it just invokes others to do the work. This allows us to add/replace/remove any of those invocations. For instance, the scaffold generator invokes the scaffold_controller generator, which invokes erb, test_unit and helper generators. Since each generator has a single responsibility, they are easy to reuse, avoiding code duplication. -Our first customization on the workflow will be to stop generating stylesheet, JavaScript and test fixture files for scaffolds. We can achieve that by changing our configuration to the following: +If we want to avoid generating the default `app/assets/stylesheets/scaffolds.scss` file when scaffolding a new resource we can disable `scaffold_stylesheet`: + +```ruby + config.generators do |g| + g.scaffold_stylesheet false + end +``` + +The next customization on the workflow will be to stop generating stylesheet, JavaScript and test fixture files for scaffolds altogether. We can achieve that by changing our configuration to the following: ```ruby config.generators do |g| @@ -451,6 +459,26 @@ $ rails new thud -m https://gist.github.com/radar/722911/raw/ Whilst the final section of this guide doesn't cover how to generate the most awesome template known to man, it will take you through the methods available at your disposal so that you can develop it yourself. These same methods are also available for generators. +Adding Command Line Arguments +----------------------------- +Rails generators can be easily modified to accept custom command line arguments. This functionality comes from [Thor](http://www.rubydoc.info/github/erikhuda/thor/master/Thor/Base/ClassMethods#class_option-instance_method): + +``` +class_option :scope, type: :string, default: 'read_products' +``` + +Now our generator can be invoked as follows: + +```bash +rails generate initializer --scope write_products +``` + +The command line arguments are accessed through the `options` method inside the generator class. e.g: + +```ruby +@scope = options['scope'] +``` + Generator methods ----------------- diff --git a/guides/source/security.md b/guides/source/security.md index bb67eb75d9..a81a782cf2 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -377,7 +377,7 @@ In 2007 there was the first tailor-made trojan which stole information from an I Having one single place in the admin interface or Intranet, where the input has not been sanitized, makes the entire application vulnerable. Possible exploits include stealing the privileged administrator's cookie, injecting an iframe to steal the administrator's password or installing malicious software through browser security holes to take over the administrator's computer. -Refer to the Injection section for countermeasures against XSS. It is _recommended to use the SafeErb plugin_ also in an Intranet or administration interface. +Refer to the Injection section for countermeasures against XSS. **CSRF** Cross-Site Request Forgery (CSRF), also known as Cross-Site Reference Forgery (XSRF), is a gigantic attack method, it allows the attacker to do everything the administrator or Intranet user may do. As you have already seen above how CSRF works, here are a few examples of what attackers can do in the Intranet or admin interface. diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 0ba220104a..8a1a440fca 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,15 @@ +* Add Webpack support in new apps via the --webpack option, which will delegate to the rails/webpacker gem. + + To generate a new app that has Webpack dependencies configured and binstubs for webpack and webpack-watcher: + + rails new myapp --webpack + + To generate a new app that has Webpack + React configured and an example intalled: + + rails new myapp --webpack=react + + *DHH* + * Add Yarn support in new apps with a yarn binstub and vendor/package.json. Skippable via --skip-yarn option. *Liceth Ovalles*, *Guillermo Iguaran*, *DHH* diff --git a/railties/lib/rails/commands/plugin/plugin_command.rb b/railties/lib/rails/commands/plugin/plugin_command.rb index 16587ce067..b40ab006af 100644 --- a/railties/lib/rails/commands/plugin/plugin_command.rb +++ b/railties/lib/rails/commands/plugin/plugin_command.rb @@ -11,7 +11,7 @@ module Rails "#{executable} new [options]" end - class_option :rc, type: :boolean, default: File.join("~", ".railsrc"), + class_option :rc, type: :string, default: File.join("~", ".railsrc"), desc: "Initialize the plugin command with previous defaults. Uses .railsrc in your home directory by default." class_option :no_rc, desc: "Skip evaluating .railsrc." diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 576c36fc86..15cc070e35 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -33,7 +33,7 @@ module Rails class_option :javascript, type: :string, aliases: "-j", desc: "Preconfigure for selected JavaScript library" - class_option :webpack, type: :boolean, default: false, + class_option :webpack, type: :string, default: nil, desc: "Preconfigure for app-like JavaScript with Webpack" class_option :skip_yarn, type: :boolean, default: false, @@ -426,7 +426,10 @@ module Rails end def run_webpack - rails_command "webpacker:install" if options[:webpack] + if !(webpack = options[:webpack]).nil? + rails_command "webpacker:install" + rails_command "webpacker:install:#{webpack}" unless webpack == "webpack" + end end def generate_spring_binstubs diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index 45f2fba5b9..70f63dc672 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -90,10 +90,6 @@ module Rails @class_path end - def namespaced_file_path - @namespaced_file_path ||= namespaced_class_path.join("/") - end - def namespaced_class_path @namespaced_class_path ||= [namespaced_path] + @class_path end diff --git a/railties/lib/rails/generators/rails/controller/controller_generator.rb b/railties/lib/rails/generators/rails/controller/controller_generator.rb index ced3c85c00..01214dc919 100644 --- a/railties/lib/rails/generators/rails/controller/controller_generator.rb +++ b/railties/lib/rails/generators/rails/controller/controller_generator.rb @@ -16,7 +16,7 @@ module Rails unless options[:skip_routes] actions.reverse_each do |action| # route prepends two spaces onto the front of the string that is passed, this corrects that. - route generate_routing_code(action)[2..-1] + route generate_routing_code(action) end end end @@ -40,7 +40,7 @@ module Rails # namespace :bar do namespace_ladder = regular_class_path.each_with_index.map do |ns, i| indent(" namespace :#{ns} do\n", i * 2) - end.join + end.join[2..-1] # Create route # get 'baz/index' @@ -54,7 +54,7 @@ module Rails end.join # Combine the 3 parts to generate complete route entry - namespace_ladder + route + end_ladder + "#{namespace_ladder}#{route}#{end_ladder}" end end end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index cd09270df1..d80a45a83f 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -387,14 +387,14 @@ module ApplicationTests bin/rails generate model product name:string; bin/rails db:migrate db:schema:cache:dump` end - assert File.exist?(File.join(app_path, "db", "schema_cache.dump")) + assert File.exist?(File.join(app_path, "db", "schema_cache.yml")) end def test_rake_clear_schema_cache Dir.chdir(app_path) do `bin/rails db:schema:cache:dump db:schema:cache:clear` end - assert !File.exist?(File.join(app_path, "db", "schema_cache.dump")) + assert !File.exist?(File.join(app_path, "db", "schema_cache.yml")) end def test_copy_templates |