Skip to content

Commit

Permalink
Merge branch 'main' into action-view-nested-field-name-calls
Browse files Browse the repository at this point in the history
  • Loading branch information
amatsuda committed Jul 2, 2023
2 parents 5cb8678 + f46d345 commit bceac79
Show file tree
Hide file tree
Showing 16 changed files with 140 additions and 46 deletions.
12 changes: 12 additions & 0 deletions actionview/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@

*Sean Doyle*

* Allow opting in/out of `Link preload` headers when calling `stylesheet_link_tag` or `javascript_include_tag`

```ruby
# will exclude header, even if setting is enabled:
javascript_include_tag("http://example.com/all.js", preload_links_header: false)

# will include header, even if setting is disabled:
stylesheet_link_tag("http://example.com/all.js", preload_links_header: true)
```

*Alex Ghiculescu*

* Stop generating `Link preload` headers once it has reached 1KB.

Some proxies have trouble handling large headers, but more importantly preload links
Expand Down
10 changes: 6 additions & 4 deletions actionview/lib/action_view/helpers/asset_tag_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ def javascript_include_tag(*sources)
options = sources.extract_options!.stringify_keys
path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
preload_links = []
use_preload_links_header = options["preload_links_header"].nil? ? preload_links_header : options.delete("preload_links_header")
nopush = options["nopush"].nil? ? true : options.delete("nopush")
crossorigin = options.delete("crossorigin")
crossorigin = "anonymous" if crossorigin == true
Expand All @@ -120,7 +121,7 @@ def javascript_include_tag(*sources)

sources_tags = sources.uniq.map { |source|
href = path_to_javascript(source, path_options)
if preload_links_header && !options["defer"] && href.present? && !href.start_with?("data:")
if use_preload_links_header && !options["defer"] && href.present? && !href.start_with?("data:")
preload_link = "<#{href}>; rel=#{rel}; as=script"
preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
preload_link += "; integrity=#{integrity}" unless integrity.nil?
Expand All @@ -137,7 +138,7 @@ def javascript_include_tag(*sources)
content_tag("script", "", tag_options)
}.join("\n").html_safe

if preload_links_header
if use_preload_links_header
send_preload_links_header(preload_links)
end

Expand Down Expand Up @@ -192,6 +193,7 @@ def javascript_include_tag(*sources)
def stylesheet_link_tag(*sources)
options = sources.extract_options!.stringify_keys
path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
use_preload_links_header = options["preload_links_header"].nil? ? preload_links_header : options.delete("preload_links_header")
preload_links = []
crossorigin = options.delete("crossorigin")
crossorigin = "anonymous" if crossorigin == true
Expand All @@ -200,7 +202,7 @@ def stylesheet_link_tag(*sources)

sources_tags = sources.uniq.map { |source|
href = path_to_stylesheet(source, path_options)
if preload_links_header && href.present? && !href.start_with?("data:")
if use_preload_links_header && href.present? && !href.start_with?("data:")
preload_link = "<#{href}>; rel=preload; as=style"
preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
preload_link += "; integrity=#{integrity}" unless integrity.nil?
Expand All @@ -220,7 +222,7 @@ def stylesheet_link_tag(*sources)
tag(:link, tag_options)
}.join("\n").html_safe

if preload_links_header
if use_preload_links_header
send_preload_links_header(preload_links)
end

Expand Down
17 changes: 17 additions & 0 deletions actionview/test/template/asset_tag_helper_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,23 @@ def test_should_not_set_preload_links_for_data_url
end
end

def test_should_not_set_preload_links_if_opted_out_at_invokation
with_preload_links_header do
stylesheet_link_tag("http://example.com/style.css", preload_links_header: false)
javascript_include_tag("http://example.com/all.js", preload_links_header: false)
assert_nil @response.headers["Link"]
end
end

def test_should_set_preload_links_if_opted_in_at_invokation
with_preload_links_header(false) do
stylesheet_link_tag("http://example.com/style.css", preload_links_header: true)
javascript_include_tag("http://example.com/all.js", preload_links_header: true)
expected = "<http://example.com/style.css>; rel=preload; as=style; nopush,<http://example.com/all.js>; rel=preload; as=script; nopush"
assert_equal expected, @response.headers["Link"]
end
end

def test_should_generate_links_under_the_max_size
with_preload_links_header do
100.times do |i|
Expand Down
17 changes: 10 additions & 7 deletions activemodel/lib/active_model/dirty.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ module ActiveModel
# person.name_change # => ["Bill", "Bill"]
# person.name << 'y'
# person.name_change # => ["Bill", "Billy"]
#
# Methods can be invoked as +name_changed?+ or by passing an argument to the
# generic method <tt>attribute_changed?("name")</tt>.
module Dirty
extend ActiveSupport::Concern
include ActiveModel::AttributeMethods
Expand All @@ -134,8 +137,8 @@ module Dirty
# person = Person.new
# person.name = 'Britanny'
# person.save
# person.name_previously_changed? # => true
# person.name_previously_changed?(from: nil, to: 'Britanny') # => true
# person.name_previously_changed? # => true
# person.name_previously_changed?(from: nil, to: 'Britanny') # => true

##
# :method: *_changed?
Expand All @@ -146,7 +149,7 @@ module Dirty
#
# person = Person.new
# person.name = 'Andrew'
# person.name_changed? # => true
# person.name_changed? # => true

##
# :method: *_change
Expand All @@ -157,7 +160,7 @@ module Dirty
#
# person = Person.new
# person.name = 'Nick'
# person.name_change # => [nil, 'Nick']
# person.name_change # => [nil, 'Nick']

##
# :method: *_will_change!
Expand All @@ -173,7 +176,7 @@ module Dirty
#
# person = Person.new('Sandy')
# person.name_will_change!
# person.name_change ['Sandy', 'Sandy']
# person.name_change # => ['Sandy', 'Sandy']

##
# :method: *_was
Expand All @@ -184,7 +187,7 @@ module Dirty
#
# person = Person.new(name: 'Steph')
# person.name = 'Stephanie'
# person.name_change # => ['Steph', 'Stephanie']
# person.name_change # => ['Steph', 'Stephanie']

##
# :method: *_previous_change
Expand All @@ -196,7 +199,7 @@ module Dirty
# person = Person.new
# person.name = 'Emmanuel'
# person.save
# person.name_previous_change # => [nil, 'Emmanuel']
# person.name_previous_change # => [nil, 'Emmanuel']

##
# :method: *_previously_was
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def @contact.favorite_quote; "Constraints are liberating"; end
end

test "custom as_json should be honored when generating json" do
def @contact.as_json(options); { name: name, created_at: created_at }; end
def @contact.as_json(options = nil); { name: name, created_at: created_at }; end
json = @contact.to_json

assert_match %r{"name":"Konata Izumi"}, json
Expand Down
1 change: 1 addition & 0 deletions activerecord/lib/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ module ActiveRecord
autoload :Encryption
autoload :Enum
autoload :Explain
autoload :FixtureSet, "active_record/fixtures"
autoload :Inheritance
autoload :Integration
autoload :InternalMetadata
Expand Down
31 changes: 31 additions & 0 deletions activerecord/lib/active_record/attribute_methods/dirty.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,37 @@
module ActiveRecord
module AttributeMethods
# = Active Record Attribute Methods \Dirty
#
# Provides a way to track changes in your Active Record models. It adds all
# methods from ActiveModel::Dirty and adds database specific methods.
#
# A newly created +Person+ object is unchanged:
#
# class Person < ActiveRecord::Base
# end
#
# person = Person.create(name: "Alisson")
# person.changed? # => false
#
# Change the name:
#
# person.name = 'Alice'
# person.name_in_database # => "Allison"
# person.will_save_change_to_name? # => true
# person.name_change_to_be_saved # => ["Allison", "Alice"]
# person.changes_to_save # => {"name"=>["Allison", "Alice"]}
#
# Save the changes:
#
# person.save
# person.name_in_database # => "Alice"
# person.saved_change_to_name? # => true
# person.saved_change_to_name # => ["Allison", "Alice"]
# person.name_before_last_change # => "Allison"
#
# Similar to ActiveModel::Dirty, methods can be invoked as
# +saved_change_to_name?+ or by passing an argument to the generic method
# <tt>saved_change_to_attribute?("name")</tt>.
module Dirty
extend ActiveSupport::Concern

Expand Down
7 changes: 4 additions & 3 deletions activerecord/lib/active_record/fixtures.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
require "set"
require "active_support/dependencies"
require "active_support/core_ext/digest/uuid"
require "active_record/fixture_set/file"
require "active_record/fixture_set/render_context"
require "active_record/fixture_set/table_rows"
require "active_record/test_fixtures"

module ActiveRecord
Expand Down Expand Up @@ -473,6 +470,10 @@ class FixtureClassNotFound < ActiveRecord::ActiveRecordError # :nodoc:
#
# Any fixtures labeled "_fixture" are safely ignored.
class FixtureSet
require "active_record/fixture_set/file"
require "active_record/fixture_set/render_context"
require "active_record/fixture_set/table_rows"

#--
# An instance of FixtureSet is normally stored in a single YAML file and
# possibly in a folder with the same name.
Expand Down
4 changes: 2 additions & 2 deletions activerecord/test/cases/json_serialization_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ def test_serializable_hash_with_default_except_option_and_excluding_inheritance_
@contact = ContactSti.new(@contact.attributes)
assert_equal "ContactSti", @contact.type

def @contact.serializable_hash(options = {})
super({ except: %w(age) }.merge!(options))
def @contact.serializable_hash(options = nil)
super({ except: %w(age) }.merge!(options || {}))
end

json = @contact.to_json
Expand Down
5 changes: 5 additions & 0 deletions activesupport/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
* Improve error message when EventedFileUpdateChecker is used without a
compatible version of the Listen gem

*Hartley McGuire*

* Add `:report` behavior for Deprecation

Setting `config.active_support.deprecation = :report` uses the error
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

gem "listen"
gem "listen", "~> 3.5"
require "listen"

require "set"
Expand Down
43 changes: 17 additions & 26 deletions activesupport/lib/active_support/json/encoding.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,16 @@ def initialize(options = nil)

# Encode the given object into a JSON string
def encode(value)
stringify jsonify value.as_json(options.dup)
unless options.empty?
value = value.as_json(options.dup)
end
json = stringify(jsonify(value))
if Encoding.escape_html_entities_in_json
json.gsub! ESCAPE_REGEX_WITH_HTML_ENTITIES, ESCAPED_CHARS
else
json.gsub! ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS
end
json
end

private
Expand All @@ -53,31 +62,12 @@ def encode(value)
ESCAPE_REGEX_WITH_HTML_ENTITIES = /[\u2028\u2029><&]/u
ESCAPE_REGEX_WITHOUT_HTML_ENTITIES = /[\u2028\u2029]/u

# This class wraps all the strings we see and does the extra escaping
class EscapedString < String # :nodoc:
def to_json(*)
if Encoding.escape_html_entities_in_json
s = super
s.gsub! ESCAPE_REGEX_WITH_HTML_ENTITIES, ESCAPED_CHARS
s
else
s = super
s.gsub! ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS
s
end
end

def to_s
self
end
end

# Mark these as private so we don't leak encoding-specific constructs
private_constant :ESCAPED_CHARS, :ESCAPE_REGEX_WITH_HTML_ENTITIES,
:ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, :EscapedString
:ESCAPE_REGEX_WITHOUT_HTML_ENTITIES

# Convert an object into a "JSON-ready" representation composed of
# primitives like Hash, Array, String, Numeric,
# primitives like Hash, Array, String, Symbol, Numeric,
# and +true+/+false+/+nil+.
# Recursively calls #as_json to the object to recursively build a
# fully JSON-ready object.
Expand All @@ -91,14 +81,15 @@ def to_s
# calls.
def jsonify(value)
case value
when String
EscapedString.new(value)
when Numeric, NilClass, TrueClass, FalseClass
when String, Integer, Symbol, nil, true, false
value
when Numeric
value.as_json
when Hash
result = {}
value.each do |k, v|
result[jsonify(k)] = jsonify(v)
k = k.to_s unless Symbol === k || String === k
result[k] = jsonify(v)
end
result
when Array
Expand Down
25 changes: 24 additions & 1 deletion activesupport/test/json/encoding_test_cases.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,26 @@ def initialize(*)
end
end

class RomanNumeral < Numeric
def initialize(str)
@str = str
end

def as_json(options = nil)
@str
end
end

class CustomNumeric < Numeric
def initialize(str)
@str = str
end

def to_json(options = nil)
@str
end
end

module EncodingTestCases
TrueTests = [[ true, %(true) ]]
FalseTests = [[ false, %(false) ]]
Expand All @@ -46,7 +66,10 @@ module EncodingTestCases
[ 1.0 / 0.0, %(null) ],
[ -1.0 / 0.0, %(null) ],
[ BigDecimal("0.0") / BigDecimal("0.0"), %(null) ],
[ BigDecimal("2.5"), %("#{BigDecimal('2.5')}") ]]
[ BigDecimal("2.5"), %("#{BigDecimal('2.5')}") ],
[ RomanNumeral.new("MCCCXXXVII"), %("MCCCXXXVII") ],
[ [CustomNumeric.new("123")], %([123]) ]
]

StringTests = [[ "this is the <string>", %("this is the \\u003cstring\\u003e")],
[ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
Expand Down
7 changes: 7 additions & 0 deletions railties/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
* Update default scaffold templates to set 303 (See Other) as status code
on redirect for the update action for XHR requests other than GET or POST
to avoid issues (e.g browsers trying to follow the redirect using the
original request method resulting in double PATCH/PUT)

*Guillermo Iguaran*

* The new `config.autoload_lib_once` is similar to `config.autoload_lib`,
except that it adds `lib` to `config.autoload_once_paths` instead.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class <%= controller_class_name %>Controller < ApplicationController
# PATCH/PUT <%= route_url %>/1
def update
if @<%= orm_instance.update("#{singular_table_name}_params") %>
redirect_to <%= redirect_resource_name %>, notice: <%= %("#{human_name} was successfully updated.") %>
redirect_to <%= redirect_resource_name %>, notice: <%= %("#{human_name} was successfully updated.") %>, status: :see_other
else
render :edit, status: :unprocessable_entity
end
Expand Down

0 comments on commit bceac79

Please sign in to comment.