actionview 6.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

Files changed (113) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +271 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +40 -0
  5. data/lib/action_view.rb +98 -0
  6. data/lib/action_view/base.rb +312 -0
  7. data/lib/action_view/buffers.rb +67 -0
  8. data/lib/action_view/cache_expiry.rb +54 -0
  9. data/lib/action_view/context.rb +32 -0
  10. data/lib/action_view/dependency_tracker.rb +175 -0
  11. data/lib/action_view/digestor.rb +126 -0
  12. data/lib/action_view/flows.rb +76 -0
  13. data/lib/action_view/gem_version.rb +17 -0
  14. data/lib/action_view/helpers.rb +66 -0
  15. data/lib/action_view/helpers/active_model_helper.rb +55 -0
  16. data/lib/action_view/helpers/asset_tag_helper.rb +488 -0
  17. data/lib/action_view/helpers/asset_url_helper.rb +470 -0
  18. data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
  19. data/lib/action_view/helpers/cache_helper.rb +271 -0
  20. data/lib/action_view/helpers/capture_helper.rb +216 -0
  21. data/lib/action_view/helpers/controller_helper.rb +36 -0
  22. data/lib/action_view/helpers/csp_helper.rb +26 -0
  23. data/lib/action_view/helpers/csrf_helper.rb +35 -0
  24. data/lib/action_view/helpers/date_helper.rb +1200 -0
  25. data/lib/action_view/helpers/debug_helper.rb +36 -0
  26. data/lib/action_view/helpers/form_helper.rb +2569 -0
  27. data/lib/action_view/helpers/form_options_helper.rb +896 -0
  28. data/lib/action_view/helpers/form_tag_helper.rb +920 -0
  29. data/lib/action_view/helpers/javascript_helper.rb +95 -0
  30. data/lib/action_view/helpers/number_helper.rb +456 -0
  31. data/lib/action_view/helpers/output_safety_helper.rb +70 -0
  32. data/lib/action_view/helpers/rendering_helper.rb +101 -0
  33. data/lib/action_view/helpers/sanitize_helper.rb +171 -0
  34. data/lib/action_view/helpers/tag_helper.rb +314 -0
  35. data/lib/action_view/helpers/tags.rb +44 -0
  36. data/lib/action_view/helpers/tags/base.rb +196 -0
  37. data/lib/action_view/helpers/tags/check_box.rb +66 -0
  38. data/lib/action_view/helpers/tags/checkable.rb +18 -0
  39. data/lib/action_view/helpers/tags/collection_check_boxes.rb +36 -0
  40. data/lib/action_view/helpers/tags/collection_helpers.rb +119 -0
  41. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
  42. data/lib/action_view/helpers/tags/collection_select.rb +30 -0
  43. data/lib/action_view/helpers/tags/color_field.rb +27 -0
  44. data/lib/action_view/helpers/tags/date_field.rb +15 -0
  45. data/lib/action_view/helpers/tags/date_select.rb +74 -0
  46. data/lib/action_view/helpers/tags/datetime_field.rb +32 -0
  47. data/lib/action_view/helpers/tags/datetime_local_field.rb +21 -0
  48. data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
  49. data/lib/action_view/helpers/tags/email_field.rb +10 -0
  50. data/lib/action_view/helpers/tags/file_field.rb +10 -0
  51. data/lib/action_view/helpers/tags/grouped_collection_select.rb +31 -0
  52. data/lib/action_view/helpers/tags/hidden_field.rb +10 -0
  53. data/lib/action_view/helpers/tags/label.rb +81 -0
  54. data/lib/action_view/helpers/tags/month_field.rb +15 -0
  55. data/lib/action_view/helpers/tags/number_field.rb +20 -0
  56. data/lib/action_view/helpers/tags/password_field.rb +14 -0
  57. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  58. data/lib/action_view/helpers/tags/radio_button.rb +33 -0
  59. data/lib/action_view/helpers/tags/range_field.rb +10 -0
  60. data/lib/action_view/helpers/tags/search_field.rb +27 -0
  61. data/lib/action_view/helpers/tags/select.rb +43 -0
  62. data/lib/action_view/helpers/tags/tel_field.rb +10 -0
  63. data/lib/action_view/helpers/tags/text_area.rb +24 -0
  64. data/lib/action_view/helpers/tags/text_field.rb +34 -0
  65. data/lib/action_view/helpers/tags/time_field.rb +15 -0
  66. data/lib/action_view/helpers/tags/time_select.rb +10 -0
  67. data/lib/action_view/helpers/tags/time_zone_select.rb +22 -0
  68. data/lib/action_view/helpers/tags/translator.rb +39 -0
  69. data/lib/action_view/helpers/tags/url_field.rb +10 -0
  70. data/lib/action_view/helpers/tags/week_field.rb +15 -0
  71. data/lib/action_view/helpers/text_helper.rb +486 -0
  72. data/lib/action_view/helpers/translation_helper.rb +145 -0
  73. data/lib/action_view/helpers/url_helper.rb +676 -0
  74. data/lib/action_view/layouts.rb +433 -0
  75. data/lib/action_view/locale/en.yml +56 -0
  76. data/lib/action_view/log_subscriber.rb +96 -0
  77. data/lib/action_view/lookup_context.rb +316 -0
  78. data/lib/action_view/model_naming.rb +14 -0
  79. data/lib/action_view/path_set.rb +95 -0
  80. data/lib/action_view/railtie.rb +105 -0
  81. data/lib/action_view/record_identifier.rb +112 -0
  82. data/lib/action_view/renderer/abstract_renderer.rb +108 -0
  83. data/lib/action_view/renderer/partial_renderer.rb +563 -0
  84. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +103 -0
  85. data/lib/action_view/renderer/renderer.rb +68 -0
  86. data/lib/action_view/renderer/streaming_template_renderer.rb +105 -0
  87. data/lib/action_view/renderer/template_renderer.rb +108 -0
  88. data/lib/action_view/rendering.rb +171 -0
  89. data/lib/action_view/routing_url_for.rb +146 -0
  90. data/lib/action_view/tasks/cache_digests.rake +25 -0
  91. data/lib/action_view/template.rb +393 -0
  92. data/lib/action_view/template/error.rb +161 -0
  93. data/lib/action_view/template/handlers.rb +92 -0
  94. data/lib/action_view/template/handlers/builder.rb +25 -0
  95. data/lib/action_view/template/handlers/erb.rb +84 -0
  96. data/lib/action_view/template/handlers/erb/erubi.rb +87 -0
  97. data/lib/action_view/template/handlers/html.rb +11 -0
  98. data/lib/action_view/template/handlers/raw.rb +11 -0
  99. data/lib/action_view/template/html.rb +43 -0
  100. data/lib/action_view/template/inline.rb +22 -0
  101. data/lib/action_view/template/raw_file.rb +28 -0
  102. data/lib/action_view/template/resolver.rb +394 -0
  103. data/lib/action_view/template/sources.rb +13 -0
  104. data/lib/action_view/template/sources/file.rb +17 -0
  105. data/lib/action_view/template/text.rb +35 -0
  106. data/lib/action_view/template/types.rb +57 -0
  107. data/lib/action_view/test_case.rb +300 -0
  108. data/lib/action_view/testing/resolvers.rb +67 -0
  109. data/lib/action_view/unbound_template.rb +32 -0
  110. data/lib/action_view/version.rb +10 -0
  111. data/lib/action_view/view_paths.rb +129 -0
  112. data/lib/assets/compiled/rails-ujs.js +746 -0
  113. metadata +260 -0
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module ActionView
6
+ # = Action View Atom Feed Helpers
7
+ module Helpers #:nodoc:
8
+ module AtomFeedHelper
9
+ # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other
10
+ # template languages).
11
+ #
12
+ # Full usage example:
13
+ #
14
+ # config/routes.rb:
15
+ # Rails.application.routes.draw do
16
+ # resources :posts
17
+ # root to: "posts#index"
18
+ # end
19
+ #
20
+ # app/controllers/posts_controller.rb:
21
+ # class PostsController < ApplicationController
22
+ # # GET /posts.html
23
+ # # GET /posts.atom
24
+ # def index
25
+ # @posts = Post.all
26
+ #
27
+ # respond_to do |format|
28
+ # format.html
29
+ # format.atom
30
+ # end
31
+ # end
32
+ # end
33
+ #
34
+ # app/views/posts/index.atom.builder:
35
+ # atom_feed do |feed|
36
+ # feed.title("My great blog!")
37
+ # feed.updated(@posts[0].created_at) if @posts.length > 0
38
+ #
39
+ # @posts.each do |post|
40
+ # feed.entry(post) do |entry|
41
+ # entry.title(post.title)
42
+ # entry.content(post.body, type: 'html')
43
+ #
44
+ # entry.author do |author|
45
+ # author.name("DHH")
46
+ # end
47
+ # end
48
+ # end
49
+ # end
50
+ #
51
+ # The options for atom_feed are:
52
+ #
53
+ # * <tt>:language</tt>: Defaults to "en-US".
54
+ # * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host.
55
+ # * <tt>:url</tt>: The URL for this feed. Defaults to the current URL.
56
+ # * <tt>:id</tt>: The id for this feed. Defaults to "tag:localhost,2005:/posts", in this case.
57
+ # * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you
58
+ # created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified,
59
+ # 2005 is used (as an "I don't care" value).
60
+ # * <tt>:instruct</tt>: Hash of XML processing instructions in the form {target => {attribute => value, }} or {target => [{attribute => value, }, ]}
61
+ #
62
+ # Other namespaces can be added to the root element:
63
+ #
64
+ # app/views/posts/index.atom.builder:
65
+ # atom_feed({'xmlns:app' => 'http://www.w3.org/2007/app',
66
+ # 'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed|
67
+ # feed.title("My great blog!")
68
+ # feed.updated((@posts.first.created_at))
69
+ # feed.tag!('openSearch:totalResults', 10)
70
+ #
71
+ # @posts.each do |post|
72
+ # feed.entry(post) do |entry|
73
+ # entry.title(post.title)
74
+ # entry.content(post.body, type: 'html')
75
+ # entry.tag!('app:edited', Time.now)
76
+ #
77
+ # entry.author do |author|
78
+ # author.name("DHH")
79
+ # end
80
+ # end
81
+ # end
82
+ # end
83
+ #
84
+ # The Atom spec defines five elements (content rights title subtitle
85
+ # summary) which may directly contain xhtml content if type: 'xhtml'
86
+ # is specified as an attribute. If so, this helper will take care of
87
+ # the enclosing div and xhtml namespace declaration. Example usage:
88
+ #
89
+ # entry.summary type: 'xhtml' do |xhtml|
90
+ # xhtml.p pluralize(order.line_items.count, "line item")
91
+ # xhtml.p "Shipped to #{order.address}"
92
+ # xhtml.p "Paid by #{order.pay_type}"
93
+ # end
94
+ #
95
+ #
96
+ # <tt>atom_feed</tt> yields an +AtomFeedBuilder+ instance. Nested elements yield
97
+ # an +AtomBuilder+ instance.
98
+ def atom_feed(options = {}, &block)
99
+ if options[:schema_date]
100
+ options[:schema_date] = options[:schema_date].strftime("%Y-%m-%d") if options[:schema_date].respond_to?(:strftime)
101
+ else
102
+ options[:schema_date] = "2005" # The Atom spec copyright date
103
+ end
104
+
105
+ xml = options.delete(:xml) || eval("xml", block.binding)
106
+ xml.instruct!
107
+ if options[:instruct]
108
+ options[:instruct].each do |target, attrs|
109
+ if attrs.respond_to?(:keys)
110
+ xml.instruct!(target, attrs)
111
+ elsif attrs.respond_to?(:each)
112
+ attrs.each { |attr_group| xml.instruct!(target, attr_group) }
113
+ end
114
+ end
115
+ end
116
+
117
+ feed_opts = { "xml:lang" => options[:language] || "en-US", "xmlns" => "http://www.w3.org/2005/Atom" }
118
+ feed_opts.merge!(options).reject! { |k, v| !k.to_s.match(/^xml/) }
119
+
120
+ xml.feed(feed_opts) do
121
+ xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}")
122
+ xml.link(rel: "alternate", type: "text/html", href: options[:root_url] || (request.protocol + request.host_with_port))
123
+ xml.link(rel: "self", type: "application/atom+xml", href: options[:url] || request.url)
124
+
125
+ yield AtomFeedBuilder.new(xml, self, options)
126
+ end
127
+ end
128
+
129
+ class AtomBuilder #:nodoc:
130
+ XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set
131
+
132
+ def initialize(xml)
133
+ @xml = xml
134
+ end
135
+
136
+ private
137
+ # Delegate to xml builder, first wrapping the element in an xhtml
138
+ # namespaced div element if the method and arguments indicate
139
+ # that an xhtml_block? is desired.
140
+ def method_missing(method, *arguments, &block)
141
+ if xhtml_block?(method, arguments)
142
+ @xml.__send__(method, *arguments) do
143
+ @xml.div(xmlns: "http://www.w3.org/1999/xhtml") do |xhtml|
144
+ block.call(xhtml)
145
+ end
146
+ end
147
+ else
148
+ @xml.__send__(method, *arguments, &block)
149
+ end
150
+ end
151
+
152
+ # True if the method name matches one of the five elements defined
153
+ # in the Atom spec as potentially containing XHTML content and
154
+ # if type: 'xhtml' is, in fact, specified.
155
+ def xhtml_block?(method, arguments)
156
+ if XHTML_TAG_NAMES.include?(method.to_s)
157
+ last = arguments.last
158
+ last.is_a?(Hash) && last[:type].to_s == "xhtml"
159
+ end
160
+ end
161
+ end
162
+
163
+ class AtomFeedBuilder < AtomBuilder #:nodoc:
164
+ def initialize(xml, view, feed_options = {})
165
+ @xml, @view, @feed_options = xml, view, feed_options
166
+ end
167
+
168
+ # Accepts a Date or Time object and inserts it in the proper format. If +nil+ is passed, current time in UTC is used.
169
+ def updated(date_or_time = nil)
170
+ @xml.updated((date_or_time || Time.now.utc).xmlschema)
171
+ end
172
+
173
+ # Creates an entry tag for a specific record and prefills the id using class and id.
174
+ #
175
+ # Options:
176
+ #
177
+ # * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists.
178
+ # * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists.
179
+ # * <tt>:url</tt>: The URL for this entry or +false+ or +nil+ for not having a link tag. Defaults to the +polymorphic_url+ for the record.
180
+ # * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}"
181
+ # * <tt>:type</tt>: The TYPE for this entry. Defaults to "text/html".
182
+ def entry(record, options = {})
183
+ @xml.entry do
184
+ @xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}")
185
+
186
+ if options[:published] || (record.respond_to?(:created_at) && record.created_at)
187
+ @xml.published((options[:published] || record.created_at).xmlschema)
188
+ end
189
+
190
+ if options[:updated] || (record.respond_to?(:updated_at) && record.updated_at)
191
+ @xml.updated((options[:updated] || record.updated_at).xmlschema)
192
+ end
193
+
194
+ type = options.fetch(:type, "text/html")
195
+
196
+ url = options.fetch(:url) { @view.polymorphic_url(record) }
197
+ @xml.link(rel: "alternate", type: type, href: url) if url
198
+
199
+ yield AtomBuilder.new(@xml)
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,271 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ # = Action View Cache Helper
5
+ module Helpers #:nodoc:
6
+ module CacheHelper
7
+ # This helper exposes a method for caching fragments of a view
8
+ # rather than an entire action or page. This technique is useful
9
+ # caching pieces like menus, lists of new topics, static HTML
10
+ # fragments, and so on. This method takes a block that contains
11
+ # the content you wish to cache.
12
+ #
13
+ # The best way to use this is by doing recyclable key-based cache expiration
14
+ # on top of a cache store like Memcached or Redis that'll automatically
15
+ # kick out old entries.
16
+ #
17
+ # When using this method, you list the cache dependency as the name of the cache, like so:
18
+ #
19
+ # <% cache project do %>
20
+ # <b>All the topics on this project</b>
21
+ # <%= render project.topics %>
22
+ # <% end %>
23
+ #
24
+ # This approach will assume that when a new topic is added, you'll touch
25
+ # the project. The cache key generated from this call will be something like:
26
+ #
27
+ # views/template/action.html.erb:7a1156131a6928cb0026877f8b749ac9/projects/123
28
+ # ^template path ^template tree digest ^class ^id
29
+ #
30
+ # This cache key is stable, but it's combined with a cache version derived from the project
31
+ # record. When the project updated_at is touched, the #cache_version changes, even
32
+ # if the key stays stable. This means that unlike a traditional key-based cache expiration
33
+ # approach, you won't be generating cache trash, unused keys, simply because the dependent
34
+ # record is updated.
35
+ #
36
+ # If your template cache depends on multiple sources (try to avoid this to keep things simple),
37
+ # you can name all these dependencies as part of an array:
38
+ #
39
+ # <% cache [ project, current_user ] do %>
40
+ # <b>All the topics on this project</b>
41
+ # <%= render project.topics %>
42
+ # <% end %>
43
+ #
44
+ # This will include both records as part of the cache key and updating either of them will
45
+ # expire the cache.
46
+ #
47
+ # ==== \Template digest
48
+ #
49
+ # The template digest that's added to the cache key is computed by taking an MD5 of the
50
+ # contents of the entire template file. This ensures that your caches will automatically
51
+ # expire when you change the template file.
52
+ #
53
+ # Note that the MD5 is taken of the entire template file, not just what's within the
54
+ # cache do/end call. So it's possible that changing something outside of that call will
55
+ # still expire the cache.
56
+ #
57
+ # Additionally, the digestor will automatically look through your template file for
58
+ # explicit and implicit dependencies, and include those as part of the digest.
59
+ #
60
+ # The digestor can be bypassed by passing skip_digest: true as an option to the cache call:
61
+ #
62
+ # <% cache project, skip_digest: true do %>
63
+ # <b>All the topics on this project</b>
64
+ # <%= render project.topics %>
65
+ # <% end %>
66
+ #
67
+ # ==== Implicit dependencies
68
+ #
69
+ # Most template dependencies can be derived from calls to render in the template itself.
70
+ # Here are some examples of render calls that Cache Digests knows how to decode:
71
+ #
72
+ # render partial: "comments/comment", collection: commentable.comments
73
+ # render "comments/comments"
74
+ # render 'comments/comments'
75
+ # render('comments/comments')
76
+ #
77
+ # render "header" translates to render("comments/header")
78
+ #
79
+ # render(@topic) translates to render("topics/topic")
80
+ # render(topics) translates to render("topics/topic")
81
+ # render(message.topics) translates to render("topics/topic")
82
+ #
83
+ # It's not possible to derive all render calls like that, though.
84
+ # Here are a few examples of things that can't be derived:
85
+ #
86
+ # render group_of_attachments
87
+ # render @project.documents.where(published: true).order('created_at')
88
+ #
89
+ # You will have to rewrite those to the explicit form:
90
+ #
91
+ # render partial: 'attachments/attachment', collection: group_of_attachments
92
+ # render partial: 'documents/document', collection: @project.documents.where(published: true).order('created_at')
93
+ #
94
+ # === Explicit dependencies
95
+ #
96
+ # Sometimes you'll have template dependencies that can't be derived at all. This is typically
97
+ # the case when you have template rendering that happens in helpers. Here's an example:
98
+ #
99
+ # <%= render_sortable_todolists @project.todolists %>
100
+ #
101
+ # You'll need to use a special comment format to call those out:
102
+ #
103
+ # <%# Template Dependency: todolists/todolist %>
104
+ # <%= render_sortable_todolists @project.todolists %>
105
+ #
106
+ # In some cases, like a single table inheritance setup, you might have
107
+ # a bunch of explicit dependencies. Instead of writing every template out,
108
+ # you can use a wildcard to match any template in a directory:
109
+ #
110
+ # <%# Template Dependency: events/* %>
111
+ # <%= render_categorizable_events @person.events %>
112
+ #
113
+ # This marks every template in the directory as a dependency. To find those
114
+ # templates, the wildcard path must be absolutely defined from <tt>app/views</tt> or paths
115
+ # otherwise added with +prepend_view_path+ or +append_view_path+.
116
+ # This way the wildcard for <tt>app/views/recordings/events</tt> would be <tt>recordings/events/*</tt> etc.
117
+ #
118
+ # The pattern used to match explicit dependencies is <tt>/# Template Dependency: (\S+)/</tt>,
119
+ # so it's important that you type it out just so.
120
+ # You can only declare one template dependency per line.
121
+ #
122
+ # === External dependencies
123
+ #
124
+ # If you use a helper method, for example, inside a cached block and
125
+ # you then update that helper, you'll have to bump the cache as well.
126
+ # It doesn't really matter how you do it, but the MD5 of the template file
127
+ # must change. One recommendation is to simply be explicit in a comment, like:
128
+ #
129
+ # <%# Helper Dependency Updated: May 6, 2012 at 6pm %>
130
+ # <%= some_helper_method(person) %>
131
+ #
132
+ # Now all you have to do is change that timestamp when the helper method changes.
133
+ #
134
+ # === Collection Caching
135
+ #
136
+ # When rendering a collection of objects that each use the same partial, a <tt>:cached</tt>
137
+ # option can be passed.
138
+ #
139
+ # For collections rendered such:
140
+ #
141
+ # <%= render partial: 'projects/project', collection: @projects, cached: true %>
142
+ #
143
+ # The <tt>cached: true</tt> will make Action View's rendering read several templates
144
+ # from cache at once instead of one call per template.
145
+ #
146
+ # Templates in the collection not already cached are written to cache.
147
+ #
148
+ # Works great alongside individual template fragment caching.
149
+ # For instance if the template the collection renders is cached like:
150
+ #
151
+ # # projects/_project.html.erb
152
+ # <% cache project do %>
153
+ # <%# ... %>
154
+ # <% end %>
155
+ #
156
+ # Any collection renders will find those cached templates when attempting
157
+ # to read multiple templates at once.
158
+ #
159
+ # If your collection cache depends on multiple sources (try to avoid this to keep things simple),
160
+ # you can name all these dependencies as part of a block that returns an array:
161
+ #
162
+ # <%= render partial: 'projects/project', collection: @projects, cached: -> project { [ project, current_user ] } %>
163
+ #
164
+ # This will include both records as part of the cache key and updating either of them will
165
+ # expire the cache.
166
+ def cache(name = {}, options = {}, &block)
167
+ if controller.respond_to?(:perform_caching) && controller.perform_caching
168
+ name_options = options.slice(:skip_digest, :virtual_path)
169
+ safe_concat(fragment_for(cache_fragment_name(name, name_options), options, &block))
170
+ else
171
+ yield
172
+ end
173
+
174
+ nil
175
+ end
176
+
177
+ # Cache fragments of a view if +condition+ is true
178
+ #
179
+ # <% cache_if admin?, project do %>
180
+ # <b>All the topics on this project</b>
181
+ # <%= render project.topics %>
182
+ # <% end %>
183
+ def cache_if(condition, name = {}, options = {}, &block)
184
+ if condition
185
+ cache(name, options, &block)
186
+ else
187
+ yield
188
+ end
189
+
190
+ nil
191
+ end
192
+
193
+ # Cache fragments of a view unless +condition+ is true
194
+ #
195
+ # <% cache_unless admin?, project do %>
196
+ # <b>All the topics on this project</b>
197
+ # <%= render project.topics %>
198
+ # <% end %>
199
+ def cache_unless(condition, name = {}, options = {}, &block)
200
+ cache_if !condition, name, options, &block
201
+ end
202
+
203
+ # This helper returns the name of a cache key for a given fragment cache
204
+ # call. By supplying <tt>skip_digest: true</tt> to cache, the digestion of cache
205
+ # fragments can be manually bypassed. This is useful when cache fragments
206
+ # cannot be manually expired unless you know the exact key which is the
207
+ # case when using memcached.
208
+ #
209
+ # The digest will be generated using +virtual_path:+ if it is provided.
210
+ #
211
+ def cache_fragment_name(name = {}, skip_digest: nil, virtual_path: nil, digest_path: nil)
212
+ if skip_digest
213
+ name
214
+ else
215
+ fragment_name_with_digest(name, virtual_path, digest_path)
216
+ end
217
+ end
218
+
219
+ def digest_path_from_template(template) # :nodoc:
220
+ digest = Digestor.digest(name: template.virtual_path, format: template.format, finder: lookup_context, dependencies: view_cache_dependencies)
221
+
222
+ if digest.present?
223
+ "#{template.virtual_path}:#{digest}"
224
+ else
225
+ template.virtual_path
226
+ end
227
+ end
228
+
229
+ private
230
+
231
+ def fragment_name_with_digest(name, virtual_path, digest_path)
232
+ virtual_path ||= @virtual_path
233
+
234
+ if virtual_path || digest_path
235
+ name = controller.url_for(name).split("://").last if name.is_a?(Hash)
236
+
237
+ digest_path ||= digest_path_from_template(@current_template)
238
+
239
+ [ digest_path, name ]
240
+ else
241
+ name
242
+ end
243
+ end
244
+
245
+ def fragment_for(name = {}, options = nil, &block)
246
+ if content = read_fragment_for(name, options)
247
+ @view_renderer.cache_hits[@virtual_path] = :hit if defined?(@view_renderer)
248
+ content
249
+ else
250
+ @view_renderer.cache_hits[@virtual_path] = :miss if defined?(@view_renderer)
251
+ write_fragment_for(name, options, &block)
252
+ end
253
+ end
254
+
255
+ def read_fragment_for(name, options)
256
+ controller.read_fragment(name, options)
257
+ end
258
+
259
+ def write_fragment_for(name, options)
260
+ pos = output_buffer.length
261
+ yield
262
+ output_safe = output_buffer.html_safe?
263
+ fragment = output_buffer.slice!(pos..-1)
264
+ if output_safe
265
+ self.output_buffer = output_buffer.class.new(output_buffer)
266
+ end
267
+ controller.write_fragment(name, fragment, options)
268
+ end
269
+ end
270
+ end
271
+ end