Skip to content

Commit 845280a

Browse files
authored
Merge branch 'main' into structurally-compatible
2 parents d31071a + cd469fd commit 845280a

File tree

20 files changed

+243
-13
lines changed

20 files changed

+243
-13
lines changed

Gemfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ PATH
8181
i18n (>= 1.6, < 2)
8282
minitest (>= 5.1)
8383
tzinfo (~> 2.0)
84-
zeitwerk (~> 2.3)
84+
zeitwerk (~> 2.5.0.beta)
8585
rails (7.0.0.alpha)
8686
actioncable (= 7.0.0.alpha)
8787
actionmailbox (= 7.0.0.alpha)
@@ -522,7 +522,7 @@ GEM
522522
websocket-extensions (0.1.5)
523523
xpath (3.2.0)
524524
nokogiri (~> 1.8)
525-
zeitwerk (2.4.2)
525+
zeitwerk (2.5.0.beta)
526526

527527
PLATFORMS
528528
ruby

actionpack/lib/action_dispatch/journey/gtg/transition_table.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class TransitionTable # :nodoc:
1111
attr_reader :memos
1212

1313
DEFAULT_EXP = /[^.\/?]+/
14-
DEFAULT_EXP_ANCHORED = Regexp.new(/\A#{DEFAULT_EXP}\Z/)
14+
DEFAULT_EXP_ANCHORED = /\A#{DEFAULT_EXP}\Z/
1515

1616
def initialize
1717
@stdparam_states = {}
@@ -165,7 +165,11 @@ def []=(from, to, sym)
165165
case sym
166166
when Regexp
167167
# we must match the whole string to a token boundary
168-
sym = Regexp.new(/\A#{sym}\Z/)
168+
if sym == DEFAULT_EXP
169+
sym = DEFAULT_EXP_ANCHORED
170+
else
171+
sym = /\A#{sym}\Z/
172+
end
169173
when Symbol
170174
# account for symbols in the constraints the same as strings
171175
sym = sym.to_s

actionview/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
* Add `caching?` helper that returns whether the current code path is being cached and `unacheable!` to denote helper methods that can't participate in fragment caching.
2+
3+
*Ben Toews*, *John Hawthorn*, *Kasper Timm Hansen*, *Joel Hawksley*
4+
15
* Add `include_seconds` option for `time_field`
26

37
<%= form.time_field :foo, include_seconds: false %>

actionview/lib/action_view/helpers/cache_helper.rb

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ module ActionView
44
# = Action View Cache Helper
55
module Helpers # :nodoc:
66
module CacheHelper
7+
class UncacheableFragmentError < StandardError; end
8+
79
# This helper exposes a method for caching fragments of a view
810
# rather than an entire action or page. This technique is useful
911
# caching pieces like menus, lists of new topics, static HTML
@@ -165,15 +167,45 @@ module CacheHelper
165167
# expire the cache.
166168
def cache(name = {}, options = {}, &block)
167169
if controller.respond_to?(:perform_caching) && controller.perform_caching
168-
name_options = options.slice(:skip_digest)
169-
safe_concat(fragment_for(cache_fragment_name(name, **name_options), options, &block))
170+
CachingRegistry.track_caching do
171+
name_options = options.slice(:skip_digest)
172+
safe_concat(fragment_for(cache_fragment_name(name, **name_options), options, &block))
173+
end
170174
else
171175
yield
172176
end
173177

174178
nil
175179
end
176180

181+
# Returns whether the current view fragment is within a +cache+ block.
182+
#
183+
# Useful when certain fragments aren't cacheable:
184+
#
185+
# <% cache project do %>
186+
# <% raise StandardError, "Caching private data!" if caching? %>
187+
# <% end %>
188+
def caching?
189+
CachingRegistry.caching?
190+
end
191+
192+
# Raises +UncacheableFragmentError+ when called from within a +cache+ block.
193+
#
194+
# Useful to denote helper methods that can't participate in fragment caching:
195+
#
196+
# def project_name_with_time(project)
197+
# uncacheable!
198+
# "#{project.name} - #{Time.now}"
199+
# end
200+
#
201+
# # Which will then raise if used within a +cache+ block:
202+
# <% cache project do %>
203+
# <%= project_name_with_time(project) %>
204+
# <% end %>
205+
def uncacheable!
206+
raise UncacheableFragmentError, "can't be fragment cached" if caching?
207+
end
208+
177209
# Cache fragments of a view if +condition+ is true
178210
#
179211
# <% cache_if admin?, project do %>
@@ -259,6 +291,22 @@ def write_fragment_for(name, options)
259291
end
260292
controller.write_fragment(name, fragment, options)
261293
end
294+
295+
class CachingRegistry
296+
extend ActiveSupport::PerThreadRegistry
297+
298+
attr_accessor :caching
299+
alias caching? caching
300+
301+
def self.track_caching
302+
caching_was = self.caching
303+
self.caching = true
304+
305+
yield
306+
ensure
307+
self.caching = caching_was
308+
end
309+
end
262310
end
263311
end
264312
end
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<%= cache "foo" do %>
2+
<%= "Cached!" if caching? %>
3+
<% end %>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<%= "Not cached!" unless caching? %>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<%= cache "uhoh" do %>
2+
<%= uncacheable! %>
3+
<% end %>

actionview/test/template/render_test.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,24 @@ def test_cache_fragments_inside_render_layout_call_with_block
721721

722722
assert_not_equal cat, dog
723723
end
724+
725+
def test_caching_predicate_method
726+
result = @view.render(template: "test/caching_predicate")
727+
728+
assert_match "Cached!", result
729+
end
730+
731+
def test_caching_predicate_method_outside_of_cache
732+
result = @view.render(template: "test/caching_predicate_outside_cache")
733+
734+
assert_match "Not cached!", result
735+
end
736+
737+
def test_uncacheable
738+
e = assert_raises(ActionView::Template::Error) { @view.render(template: "test/uncacheable") }
739+
740+
assert_match "can't be fragment cached", e.cause.message
741+
end
724742
end
725743

726744
class LazyViewRenderTest < ActiveSupport::TestCase

activerecord/CHANGELOG.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,32 @@
66

77
*Kevin Newton*
88

9+
* Add `ActiveRecord::QueryMethods#in_order_of`.
10+
11+
This allows you to specify an explicit order that you'd like records
12+
returned in based on a SQL expression. By default, this will be accomplished
13+
using a case statement, as in:
14+
15+
```ruby
16+
Post.in_order_of(:id, [3, 5, 1])
17+
```
18+
19+
will generate the SQL:
20+
21+
```sql
22+
SELECT "posts".* FROM "posts" ORDER BY CASE "posts"."id" WHEN 3 THEN 1 WHEN 5 THEN 2 WHEN 1 THEN 3 ELSE 4 END ASC
23+
```
24+
25+
However, because this functionality is built into MySQL in the form of the
26+
`FIELD` function, that connection adapter will generate the following SQL
27+
instead:
28+
29+
```sql
30+
SELECT "posts".* FROM "posts" ORDER BY FIELD("posts"."id", 1, 5, 3) DESC
31+
```
32+
33+
*Kevin Newton*
34+
935
* Fix `eager_loading?` when ordering with `Symbol`
1036

1137
`eager_loading?` is triggered correctly when using `order` with symbols.
@@ -42,7 +68,7 @@
4268

4369
*Luis Vasconcellos*, *Eileen M. Uchitelle*
4470

45-
* Fix `eager_loading?` when ordering with `Hash` syntax.
71+
* Fix `eager_loading?` when ordering with `Hash` syntax
4672

4773
`eager_loading?` is triggered correctly when using `order` with hash syntax
4874
on an outer table.

activerecord/lib/active_record/associations/builder/has_many.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ def self.macro
77
end
88

99
def self.valid_options(options)
10-
valid = super + [:counter_cache, :join_table, :index_errors, :ensuring_owner_was]
10+
valid = super + [:counter_cache, :join_table, :index_errors]
1111
valid += [:as, :foreign_type] if options[:as]
1212
valid += [:through, :source, :source_type] if options[:through]
1313
valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async

0 commit comments

Comments
 (0)