diff options
25 files changed, 175 insertions, 325 deletions
diff --git a/.travis.yml b/.travis.yml index 9d63fd4b13..3a774e1564 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,7 @@ addons: bundler_args: --without test --jobs 3 --retry 3 before_install: - "rm ${BUNDLE_GEMFILE}.lock" - - "gem update --system 2.6.11" + - "gem update --system" - "gem update bundler" - "[ -f /tmp/beanstalkd-1.10/Makefile ] || (curl -L https://github.com/kr/beanstalkd/archive/v1.10.tar.gz | tar xz -C /tmp)" - "pushd /tmp/beanstalkd-1.10 && make && (./beanstalkd &); popd" diff --git a/Gemfile.lock b/Gemfile.lock index e63fad4fed..2b8cc2862c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -253,7 +253,7 @@ GEM rack (2.0.3) rack-cache (1.6.1) rack (>= 0.4) - rack-protection (1.5.3) + rack-protection (2.0.0) rack rack-test (0.6.3) rack (>= 1.0) @@ -308,11 +308,11 @@ GEM sequel (4.42.1) serverengine (1.5.11) sigdump (~> 0.2.2) - sidekiq (4.2.9) + sidekiq (5.0.0) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) rack-protection (>= 1.5.0) - redis (~> 3.2, >= 3.2.1) + redis (~> 3.3, >= 3.3.3) sigdump (0.2.4) simple_uuid (0.4.0) sinatra (1.0) diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index ce4ecf17cc..ba7dec6083 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -1,4 +1,24 @@ module AbstractController + # = Abstract Controller Callbacks + # + # Abstract Controller provides hooks during the life cycle of a controller action. + # Callbacks allow you to trigger logic during this cycle. Available callbacks are: + # + # * <tt>after_action</tt> + # * <tt>append_after_action</tt> + # * <tt>append_around_action</tt> + # * <tt>append_before_action</tt> + # * <tt>around_action</tt> + # * <tt>before_action</tt> + # * <tt>prepend_after_action</tt> + # * <tt>prepend_around_action</tt> + # * <tt>prepend_before_action</tt> + # * <tt>skip_after_action</tt> + # * <tt>skip_around_action</tt> + # * <tt>skip_before_action</tt> + # + # NOTE: Calling the same callback multiple times will overwrite previous callback definitions. + # module Callbacks extend ActiveSupport::Concern diff --git a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb index d692f6415c..62f052ced6 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb @@ -18,14 +18,6 @@ module ActionDispatch @tt = transition_table end - def simulate(string) - ms = memos(string) { return } - MatchData.new(ms) - end - - alias :=~ :simulate - alias :match :simulate - def memos(string) input = StringScanner.new(string) state = [0] diff --git a/actionpack/test/journey/gtg/transition_table_test.rb b/actionpack/test/journey/gtg/transition_table_test.rb index c7315c0338..889640fdd7 100644 --- a/actionpack/test/journey/gtg/transition_table_test.rb +++ b/actionpack/test/journey/gtg/transition_table_test.rb @@ -35,25 +35,25 @@ module ActionDispatch def test_simulate_gt sim = simulator_for ["/foo", "/bar"] - assert_match sim, "/foo" + assert_match_route sim, "/foo" end def test_simulate_gt_regexp sim = simulator_for [":foo"] - assert_match sim, "foo" + assert_match_route sim, "foo" end def test_simulate_gt_regexp_mix sim = simulator_for ["/get", "/:method/foo"] - assert_match sim, "/get" - assert_match sim, "/get/foo" + assert_match_route sim, "/get" + assert_match_route sim, "/get/foo" end def test_simulate_optional sim = simulator_for ["/foo(/bar)"] - assert_match sim, "/foo" - assert_match sim, "/foo/bar" - assert_no_match sim, "/foo/" + assert_match_route sim, "/foo" + assert_match_route sim, "/foo/bar" + assert_no_match_route sim, "/foo/" end def test_match_data @@ -65,11 +65,11 @@ module ActionDispatch sim = GTG::Simulator.new tt - match = sim.match "/get" - assert_equal [paths.first], match.memos + memos = sim.memos "/get" + assert_equal [paths.first], memos - match = sim.match "/get/foo" - assert_equal [paths.last], match.memos + memos = sim.memos "/get/foo" + assert_equal [paths.last], memos end def test_match_data_ambiguous @@ -86,8 +86,8 @@ module ActionDispatch builder = GTG::Builder.new ast sim = GTG::Simulator.new builder.transition_table - match = sim.match "/articles/new" - assert_equal [paths[1], paths[3]], match.memos + memos = sim.memos "/articles/new" + assert_equal [paths[1], paths[3]], memos end private @@ -109,6 +109,14 @@ module ActionDispatch def simulator_for(paths) GTG::Simulator.new tt(paths) end + + def assert_match_route(simulator, path) + assert simulator.memos(path), "Simulator should match #{path}." + end + + def assert_no_match_route(simulator, path) + assert_not simulator.memos(path) { nil }, "Simulator should not match #{path}." + end end end end diff --git a/actionview/app/assets/javascripts/README.md b/actionview/app/assets/javascripts/README.md index 92f3e8a3b3..399ebc7324 100644 --- a/actionview/app/assets/javascripts/README.md +++ b/actionview/app/assets/javascripts/README.md @@ -39,7 +39,7 @@ Require `rails-ujs` into your application.js manifest. How to run tests ------------ -Run `bundle exec rake ujs:server` first, and then run the web tests by visiting [[http://localhost:4567]] in your browser. +Run `bundle exec rake ujs:server` first, and then run the web tests by visiting http://localhost:4567 in your browser. ## License rails-ujs is released under the [MIT License](MIT-LICENSE). diff --git a/activejob/lib/active_job/callbacks.rb b/activejob/lib/active_job/callbacks.rb index d5b17de8b5..9aebc880a5 100644 --- a/activejob/lib/active_job/callbacks.rb +++ b/activejob/lib/active_job/callbacks.rb @@ -4,7 +4,7 @@ module ActiveJob # = Active Job Callbacks # # Active Job provides hooks during the life cycle of a job. Callbacks allow you - # to trigger logic during the life cycle of a job. Available callbacks are: + # to trigger logic during this cycle. Available callbacks are: # # * <tt>before_enqueue</tt> # * <tt>around_enqueue</tt> @@ -13,6 +13,8 @@ module ActiveJob # * <tt>around_perform</tt> # * <tt>after_perform</tt> # + # NOTE: Calling the same callback multiple times will overwrite previous callback definitions. + # module Callbacks extend ActiveSupport::Concern include ActiveSupport::Callbacks diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index eac2761433..835e6f7716 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -56,6 +56,9 @@ module ActiveModel # # Would only create the +after_create+ and +before_create+ callback methods in # your class. + # + # NOTE: Calling the same callback multiple times will overwrite previous callback definitions. + # module Callbacks def self.extended(base) #:nodoc: base.class_eval do diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index d460068830..9fcde45167 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -147,6 +147,9 @@ module ActiveModel # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The # method, proc or string should return or evaluate to a +true+ or +false+ # value. + # + # NOTE: Calling +validate+ multiple times on the same method will overwrite previous definitions. + # def validate(*args, &block) options = args.extract_options! diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index 8995b1e352..643226267c 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -106,12 +106,7 @@ module ActiveRecord def join_constraints(outer_joins, join_type) joins = join_root.children.flat_map { |child| - - if join_type == Arel::Nodes::OuterJoin - make_left_outer_joins join_root, child - else - make_inner_joins join_root, child - end + make_join_constraints(join_root, child, join_type) } joins.concat outer_joins.flat_map { |oj| @@ -175,27 +170,15 @@ module ActiveRecord end def make_outer_joins(parent, child) - tables = table_aliases_for(parent, child) - join_type = Arel::Nodes::OuterJoin - info = make_constraints parent, child, tables, join_type - - [info] + child.children.flat_map { |c| make_outer_joins(child, c) } - end - - def make_left_outer_joins(parent, child) - tables = child.tables join_type = Arel::Nodes::OuterJoin - info = make_constraints parent, child, tables, join_type - - [info] + child.children.flat_map { |c| make_left_outer_joins(child, c) } + make_join_constraints(parent, child, join_type, true) end - def make_inner_joins(parent, child) - tables = child.tables - join_type = Arel::Nodes::InnerJoin - info = make_constraints parent, child, tables, join_type + def make_join_constraints(parent, child, join_type, aliasing = false) + tables = aliasing ? table_aliases_for(parent, child) : child.tables + info = make_constraints(parent, child, tables, join_type) - [info] + child.children.flat_map { |c| make_inner_joins(child, c) } + [info] + child.children.flat_map { |c| make_join_constraints(child, c, join_type, aliasing) } end def table_aliases_for(parent, node) diff --git a/activerecord/lib/active_record/associations/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/join_dependency/join_part.rb index 61cec5403a..80c9fde5d1 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb @@ -22,10 +22,6 @@ module ActiveRecord @children = children end - def name - reflection.name - end - def match?(other) self.class == other.class end diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 01ecd79b8f..3f39fb84e8 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -458,7 +458,7 @@ module ActiveRecord end unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array) - raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})" + raise ArgumentError, "Hash or Array expected for attribute `#{association_name}`, got #{attributes_collection.class.name} (#{attributes_collection.inspect})" end check_record_limit!(options[:limit], attributes_collection) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 333ad16e11..2d5be32266 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -2,7 +2,7 @@ module ActiveRecord # = Active Record \Relation class Relation MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group, - :order, :joins, :left_joins, :left_outer_joins, :references, + :order, :joins, :left_outer_joins, :references, :extending, :unscope] SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering, diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index b87419d203..5a62cbd3a6 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -752,7 +752,7 @@ module NestedAttributesOnACollectionAssociationTests exception = assert_raise ArgumentError do @pirate.send(association_setter, "foo") end - assert_equal 'Hash or Array expected, got String ("foo")', exception.message + assert_equal %{Hash or Array expected for attribute `#{@association_name}`, got String ("foo")}, exception.message end def test_should_work_with_update_as_well diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb index 11ef0d8743..dea787c07f 100644 --- a/activerecord/test/cases/relation/mutation_test.rb +++ b/activerecord/test/cases/relation/mutation_test.rb @@ -36,7 +36,7 @@ module ActiveRecord @relation ||= Relation.new FakeKlass.new("posts"), Post.arel_table, Post.predicate_builder end - (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select, :left_joins]).each do |method| + (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select]).each do |method| test "##{method}!" do assert relation.public_send("#{method}!", :foo).equal?(relation) assert_equal [:foo], relation.public_send("#{method}_values") diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index c50c1902fe..bae573cf37 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,25 @@ +* Fix implicit coercion calculations with scalars and durations + + Previously calculations where the scalar is first would be converted to a duration + of seconds but this causes issues with dates being converted to times, e.g: + + Time.zone = "Beijing" # => Asia/Shanghai + date = Date.civil(2017, 5, 20) # => Mon, 20 May 2017 + 2 * 1.day # => 172800 seconds + date + 2 * 1.day # => Mon, 22 May 2017 00:00:00 CST +08:00 + + Now the `ActiveSupport::Duration::Scalar` calculation methods will try to maintain + the part structure of the duration where possible, e.g: + + Time.zone = "Beijing" # => Asia/Shanghai + date = Date.civil(2017, 5, 20) # => Mon, 20 May 2017 + 2 * 1.day # => 2 days + date + 2 * 1.day # => Mon, 22 May 2017 + + Fixes #29160, #28970. + + *Andrew White* + * Add support for versioned cache entries. This enables the cache stores to recycle cache keys, greatly saving on storage in cases with frequent churn. Works together with the separation of `#cache_key` and `#cache_version` in Active Record and its use in Action Pack's fragment caching. diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index d4424ed792..39deb2313f 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -37,27 +37,56 @@ module ActiveSupport end def +(other) - calculate(:+, other) + if Duration === other + seconds = value + other.parts[:seconds] + new_parts = other.parts.merge(seconds: seconds) + new_value = value + other.value + + Duration.new(new_value, new_parts) + else + calculate(:+, other) + end end def -(other) - calculate(:-, other) + if Duration === other + seconds = value - other.parts[:seconds] + new_parts = other.parts.map { |part, other_value| [part, -other_value] }.to_h + new_parts = new_parts.merge(seconds: seconds) + new_value = value - other.value + + Duration.new(new_value, new_parts) + else + calculate(:-, other) + end end def *(other) - calculate(:*, other) + if Duration === other + new_parts = other.parts.map { |part, other_value| [part, value * other_value] }.to_h + new_value = value * other.value + + Duration.new(new_value, new_parts) + else + calculate(:*, other) + end end def /(other) - calculate(:/, other) + if Duration === other + new_parts = other.parts.map { |part, other_value| [part, value / other_value] }.to_h + new_value = new_parts.inject(0) { |total, (part, value)| total + value * Duration::PARTS_IN_SECONDS[part] } + + Duration.new(new_value, new_parts) + else + calculate(:/, other) + end end private def calculate(op, other) if Scalar === other Scalar.new(value.public_send(op, other.value)) - elsif Duration === other - Duration.seconds(value).public_send(op, other) elsif Numeric === other Scalar.new(value.public_send(op, other)) else diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index 1648a9b270..3108f24f21 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -337,6 +337,13 @@ class DurationTest < ActiveSupport::TestCase assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message end + def test_scalar_plus_parts + scalar = ActiveSupport::Duration::Scalar.new(10) + + assert_equal({ days: 1, seconds: 10 }, (scalar + 1.day).parts) + assert_equal({ days: -1, seconds: 10 }, (scalar + -1.day).parts) + end + def test_scalar_minus scalar = ActiveSupport::Duration::Scalar.new(10) @@ -349,6 +356,9 @@ class DurationTest < ActiveSupport::TestCase assert_equal 5, scalar - 5.seconds assert_instance_of ActiveSupport::Duration, scalar - 5.seconds + assert_equal({ days: -1, seconds: 10 }, (scalar - 1.day).parts) + assert_equal({ days: 1, seconds: 10 }, (scalar - -1.day).parts) + exception = assert_raises(TypeError) do scalar - "foo" end @@ -356,6 +366,13 @@ class DurationTest < ActiveSupport::TestCase assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message end + def test_scalar_minus_parts + scalar = ActiveSupport::Duration::Scalar.new(10) + + assert_equal({ days: -1, seconds: 10 }, (scalar - 1.day).parts) + assert_equal({ days: 1, seconds: 10 }, (scalar - -1.day).parts) + end + def test_scalar_multiply scalar = ActiveSupport::Duration::Scalar.new(5) @@ -375,6 +392,14 @@ class DurationTest < ActiveSupport::TestCase assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message end + def test_scalar_multiply_parts + scalar = ActiveSupport::Duration::Scalar.new(1) + assert_equal({ days: 2 }, (scalar * 2.days).parts) + assert_equal(172800, (scalar * 2.days).value) + assert_equal({ days: -2 }, (scalar * -2.days).parts) + assert_equal(-172800, (scalar * -2.days).value) + end + def test_scalar_divide scalar = ActiveSupport::Duration::Scalar.new(10) @@ -394,6 +419,15 @@ class DurationTest < ActiveSupport::TestCase assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message end + def test_scalar_divide_parts + scalar = ActiveSupport::Duration::Scalar.new(10) + + assert_equal({ days: 2 }, (scalar / 5.days).parts) + assert_equal(172800, (scalar / 5.days).value) + assert_equal({ days: -2 }, (scalar / -5.days).parts) + assert_equal(-172800, (scalar / -5.days).value) + end + def test_twelve_months_equals_one_year assert_equal 12.months, 1.year end diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 5d987264f5..22537f960c 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -715,6 +715,9 @@ end Now, the `LoginsController`'s `new` and `create` actions will work as before without requiring the user to be logged in. The `:only` option is used to skip this filter only for these actions, and there is also an `:except` option which works the other way. These options can be used when adding filters too, so you can add a filter which only runs for selected actions in the first place. +NOTE: Calling the same filter multiple times with different options will not work, +since the last filter definition will overwrite the previous ones. + ### After Filters and Around Filters In addition to "before" filters, you can also run filters after an action has been executed, or both before and after. diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md index 3c61754982..c3c7367304 100644 --- a/guides/source/api_documentation_guidelines.md +++ b/guides/source/api_documentation_guidelines.md @@ -281,7 +281,7 @@ Methods created with `(module|class)_eval(STRING)` have a comment by their side ```ruby for severity in Severity.constants - class_eval <<-EOT, __FILE__, __LINE__ + class_eval <<-EOT, __FILE__, __LINE__ + 1 def #{severity.downcase}(message = nil, progname = nil, &block) # def debug(message = nil, progname = nil, &block) add(#{severity}, message, progname, &block) # add(DEBUG, message, progname, &block) end # end diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index f3ae5a5b28..5553f08456 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -20,16 +20,7 @@ Guide Assumptions This guide is designed for beginners who want to get started with a Rails application from scratch. It does not assume that you have any prior experience -with Rails. However, to get the most out of it, you need to have some -prerequisites installed: - -* The [Ruby](https://www.ruby-lang.org/en/downloads) language version 2.2.2 or newer. -* Right version of [Development Kit](http://rubyinstaller.org/downloads/), if you - are using Windows. -* The [RubyGems](https://rubygems.org) packaging system, which is installed with - Ruby by default. To learn more about RubyGems, please read the - [RubyGems Guides](http://guides.rubygems.org). -* A working installation of the [SQLite3 Database](https://www.sqlite.org). +with Rails. Rails is a web application framework running on the Ruby programming language. If you have no prior experience with Ruby, you will find a very steep learning @@ -86,6 +77,9 @@ your prompt will look something like `c:\source_code>` ### Installing Rails +Before you install Rails, you should check to make sure that your system has the +proper prerequisites installed. These include Ruby and SQLite3. + Open up a command line prompt. On macOS open Terminal.app, on Windows choose "Run" from your Start menu and type 'cmd.exe'. Any commands prefaced with a dollar sign `$` should be run in the command line. Verify that you have a @@ -96,12 +90,19 @@ $ ruby -v ruby 2.3.1p112 ``` +Rails requires Ruby version 2.2.2 or later. If the version number returned is +less than that number, you'll need to install a fresh copy of Ruby. + TIP: A number of tools exist to help you quickly install Ruby and Ruby on Rails on your system. Windows users can use [Rails Installer](http://railsinstaller.org), while macOS users can use [Tokaido](https://github.com/tokaido/tokaidoapp). For more installation methods for most Operating Systems take a look at [ruby-lang.org](https://www.ruby-lang.org/en/documentation/installation/). +If you are working on Windows, you should also install the +[Ruby Installer Development Kit](http://rubyinstaller.org/downloads/). + +You will also need an installation of the SQLite3 database. Many popular UNIX-like OSes ship with an acceptable version of SQLite3. On Windows, if you installed Rails through Rails Installer, you already have SQLite installed. Others can find installation instructions @@ -127,7 +128,7 @@ run the following: $ rails --version ``` -If it says something like "Rails 5.1.0", you are ready to continue. +If it says something like "Rails 5.1.1", you are ready to continue. ### Creating the Blog Application @@ -1195,7 +1196,7 @@ it look as follows: This time we point the form to the `update` action, which is not defined yet but will be very soon. -Passing the article object to the method, will automagically create url for submitting the edited article form. +Passing the article object to the method, will automagically create url for submitting the edited article form. This option tells Rails that we want this form to be submitted via the `PATCH` HTTP method which is the HTTP method you're expected to use to **update** resources according to the REST protocol. diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md deleted file mode 100644 index 71efa4b0d0..0000000000 --- a/guides/source/nested_model_forms.md +++ /dev/null @@ -1,230 +0,0 @@ -**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** - -Rails Nested Model Forms -======================== - -Creating a form for a model _and_ its associations can become quite tedious. Therefore Rails provides helpers to assist in dealing with the complexities of generating these forms _and_ the required CRUD operations to create, update, and destroy associations. - -After reading this guide, you will know: - -* do stuff. - --------------------------------------------------------------------------------- - -NOTE: This guide assumes the user knows how to use the [Rails form helpers](form_helpers.html) in general. Also, it's **not** an API reference. For a complete reference please visit [the Rails API documentation](http://api.rubyonrails.org/). - - -Model setup ------------ - -To be able to use the nested model functionality in your forms, the model will need to support some basic operations. - -First of all, it needs to define a writer method for the attribute that corresponds to the association you are building a nested model form for. The `fields_for` form helper will look for this method to decide whether or not a nested model form should be built. - -If the associated object is an array, a form builder will be yielded for each object, else only a single form builder will be yielded. - -Consider a Person model with an associated Address. When asked to yield a nested FormBuilder for the `:address` attribute, the `fields_for` form helper will look for a method on the Person instance named `address_attributes=`. - -### ActiveRecord::Base model - -For an ActiveRecord::Base model and association this writer method is commonly defined with the `accepts_nested_attributes_for` class method: - -#### has_one - -```ruby -class Person < ApplicationRecord - has_one :address - accepts_nested_attributes_for :address -end -``` - -#### belongs_to - -```ruby -class Person < ApplicationRecord - belongs_to :firm - accepts_nested_attributes_for :firm -end -``` - -#### has_many / has_and_belongs_to_many - -```ruby -class Person < ApplicationRecord - has_many :projects - accepts_nested_attributes_for :projects -end -``` - -NOTE: For greater detail on associations see [Active Record Associations](association_basics.html). -For a complete reference on associations please visit the API documentation for [ActiveRecord::Associations::ClassMethods](http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html). - -### Custom model - -As you might have inflected from this explanation, you _don't_ necessarily need an ActiveRecord::Base model to use this functionality. The following examples are sufficient to enable the nested model form behavior: - -#### Single associated object - -```ruby -class Person - def address - Address.new - end - - def address_attributes=(attributes) - # ... - end -end -``` - -#### Association collection - -```ruby -class Person - def projects - [Project.new, Project.new] - end - - def projects_attributes=(attributes) - # ... - end -end -``` - -NOTE: See (TODO) in the advanced section for more information on how to deal with the CRUD operations in your custom model. - -Views ------ - -### Controller code - -A nested model form will _only_ be built if the associated object(s) exist. This means that for a new model instance you would probably want to build the associated object(s) first. - -Consider the following typical RESTful controller which will prepare a new Person instance and its `address` and `projects` associations before rendering the `new` template: - -```ruby -class PeopleController < ApplicationController - def new - @person = Person.new - @person.build_address - 2.times { @person.projects.build } - end - - def create - @person = Person.new(params[:person]) - if @person.save - # ... - end - end -end -``` - -NOTE: Obviously the instantiation of the associated object(s) can become tedious and not DRY, so you might want to move that into the model itself. ActiveRecord::Base provides an `after_initialize` callback which is a good way to refactor this. - -### Form code - -Now that you have a model instance, with the appropriate methods and associated object(s), you can start building the nested model form. - -#### Standard form - -Start out with a regular RESTful form: - -```erb -<%= form_for @person do |f| %> - <%= f.text_field :name %> -<% end %> -``` - -This will generate the following html: - -```html -<form action="/people" class="new_person" id="new_person" method="post"> - <input id="person_name" name="person[name]" type="text" /> -</form> -``` - -#### Nested form for a single associated object - -Now add a nested form for the `address` association: - -```erb -<%= form_for @person do |f| %> - <%= f.text_field :name %> - - <%= f.fields_for :address do |af| %> - <%= af.text_field :street %> - <% end %> -<% end %> -``` - -This generates: - -```html -<form action="/people" class="new_person" id="new_person" method="post"> - <input id="person_name" name="person[name]" type="text" /> - - <input id="person_address_attributes_street" name="person[address_attributes][street]" type="text" /> -</form> -``` - -Notice that `fields_for` recognized the `address` as an association for which a nested model form should be built by the way it has namespaced the `name` attribute. - -When this form is posted the Rails parameter parser will construct a hash like the following: - -```ruby -{ - "person" => { - "name" => "Eloy Duran", - "address_attributes" => { - "street" => "Nieuwe Prinsengracht" - } - } -} -``` - -That's it. The controller will simply pass this hash on to the model from the `create` action. The model will then handle building the `address` association for you and automatically save it when the parent (`person`) is saved. - -#### Nested form for a collection of associated objects - -The form code for an association collection is pretty similar to that of a single associated object: - -```erb -<%= form_for @person do |f| %> - <%= f.text_field :name %> - - <%= f.fields_for :projects do |pf| %> - <%= pf.text_field :name %> - <% end %> -<% end %> -``` - -Which generates: - -```html -<form action="/people" class="new_person" id="new_person" method="post"> - <input id="person_name" name="person[name]" type="text" /> - - <input id="person_projects_attributes_0_name" name="person[projects_attributes][0][name]" type="text" /> - <input id="person_projects_attributes_1_name" name="person[projects_attributes][1][name]" type="text" /> -</form> -``` - -As you can see it has generated 2 `project name` inputs, one for each new `project` that was built in the controller's `new` action. Only this time the `name` attribute of the input contains a digit as an extra namespace. This will be parsed by the Rails parameter parser as: - -```ruby -{ - "person" => { - "name" => "Eloy Duran", - "projects_attributes" => { - "0" => { "name" => "Project 1" }, - "1" => { "name" => "Project 2" } - } - } -} -``` - -You can basically see the `projects_attributes` hash as an array of attribute hashes, one for each model instance. - -NOTE: The reason that `fields_for` constructed a hash instead of an array is that it won't work for any form nested deeper than one level deep. - -TIP: You _can_ however pass an array to the writer method generated by `accepts_nested_attributes_for` if you're using plain Ruby or some other API access. See (TODO) for more info and example. diff --git a/guides/source/profiling.md b/guides/source/profiling.md deleted file mode 100644 index ce093f78ba..0000000000 --- a/guides/source/profiling.md +++ /dev/null @@ -1,16 +0,0 @@ -*DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** - -A Guide to Profiling Rails Applications -======================================= - -This guide covers built-in mechanisms in Rails for profiling your application. - -After reading this guide, you will know: - -* Rails profiling terminology. -* How to write benchmark tests for your application. -* Other benchmarking approaches and plugins. - --------------------------------------------------------------------------------- - - diff --git a/guides/source/security.md b/guides/source/security.md index c305350243..1fcb2fc91f 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -796,7 +796,7 @@ In December 2006, 34,000 actual user names and passwords were stolen in a [MySpa INFO: _CSS Injection is actually JavaScript injection, because some browsers (IE, some versions of Safari and others) allow JavaScript in CSS. Think twice about allowing custom CSS in your web application._ -CSS Injection is explained best by the well-known [MySpace Samy worm](http://namb.la/popular/tech.html). This worm automatically sent a friend request to Samy (the attacker) simply by visiting his profile. Within several hours he had over 1 million friend requests, which created so much traffic that MySpace went offline. The following is a technical explanation of that worm. +CSS Injection is explained best by the well-known [MySpace Samy worm](https://samy.pl/popular/tech.html). This worm automatically sent a friend request to Samy (the attacker) simply by visiting his profile. Within several hours he had over 1 million friend requests, which created so much traffic that MySpace went offline. The following is a technical explanation of that worm. MySpace blocked many tags, but allowed CSS. So the worm's author put JavaScript into CSS like this: diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index cf08c5dd1d..290f2a509b 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -250,7 +250,7 @@ Since it's just a `<form>`, all of the information on `form_with` also applies. ### Customize remote elements It is possible to customize the behavior of elements with a `data-remote` -attribute without writing a line of JavaScript. Your can specify extra `data-` +attribute without writing a line of JavaScript. You can specify extra `data-` attributes to accomplish this. #### `data-method` |