actionview 4.1.0.beta1
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +274 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +34 -0
- data/lib/action_view.rb +97 -0
- data/lib/action_view/base.rb +205 -0
- data/lib/action_view/buffers.rb +49 -0
- data/lib/action_view/context.rb +36 -0
- data/lib/action_view/dependency_tracker.rb +93 -0
- data/lib/action_view/digestor.rb +116 -0
- data/lib/action_view/flows.rb +76 -0
- data/lib/action_view/helpers.rb +64 -0
- data/lib/action_view/helpers/active_model_helper.rb +49 -0
- data/lib/action_view/helpers/asset_tag_helper.rb +322 -0
- data/lib/action_view/helpers/asset_url_helper.rb +355 -0
- data/lib/action_view/helpers/atom_feed_helper.rb +203 -0
- data/lib/action_view/helpers/cache_helper.rb +200 -0
- data/lib/action_view/helpers/capture_helper.rb +216 -0
- data/lib/action_view/helpers/controller_helper.rb +25 -0
- data/lib/action_view/helpers/csrf_helper.rb +30 -0
- data/lib/action_view/helpers/date_helper.rb +1075 -0
- data/lib/action_view/helpers/debug_helper.rb +39 -0
- data/lib/action_view/helpers/form_helper.rb +1876 -0
- data/lib/action_view/helpers/form_options_helper.rb +843 -0
- data/lib/action_view/helpers/form_tag_helper.rb +746 -0
- data/lib/action_view/helpers/javascript_helper.rb +75 -0
- data/lib/action_view/helpers/number_helper.rb +425 -0
- data/lib/action_view/helpers/output_safety_helper.rb +38 -0
- data/lib/action_view/helpers/record_tag_helper.rb +108 -0
- data/lib/action_view/helpers/rendering_helper.rb +90 -0
- data/lib/action_view/helpers/sanitize_helper.rb +256 -0
- data/lib/action_view/helpers/tag_helper.rb +176 -0
- data/lib/action_view/helpers/tags.rb +41 -0
- data/lib/action_view/helpers/tags/base.rb +148 -0
- data/lib/action_view/helpers/tags/check_box.rb +64 -0
- data/lib/action_view/helpers/tags/checkable.rb +16 -0
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +44 -0
- data/lib/action_view/helpers/tags/collection_helpers.rb +85 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +36 -0
- data/lib/action_view/helpers/tags/collection_select.rb +28 -0
- data/lib/action_view/helpers/tags/color_field.rb +25 -0
- data/lib/action_view/helpers/tags/date_field.rb +13 -0
- data/lib/action_view/helpers/tags/date_select.rb +72 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +22 -0
- data/lib/action_view/helpers/tags/datetime_local_field.rb +19 -0
- data/lib/action_view/helpers/tags/datetime_select.rb +8 -0
- data/lib/action_view/helpers/tags/email_field.rb +8 -0
- data/lib/action_view/helpers/tags/file_field.rb +8 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +29 -0
- data/lib/action_view/helpers/tags/hidden_field.rb +8 -0
- data/lib/action_view/helpers/tags/label.rb +65 -0
- data/lib/action_view/helpers/tags/month_field.rb +13 -0
- data/lib/action_view/helpers/tags/number_field.rb +18 -0
- data/lib/action_view/helpers/tags/password_field.rb +12 -0
- data/lib/action_view/helpers/tags/radio_button.rb +31 -0
- data/lib/action_view/helpers/tags/range_field.rb +8 -0
- data/lib/action_view/helpers/tags/search_field.rb +24 -0
- data/lib/action_view/helpers/tags/select.rb +41 -0
- data/lib/action_view/helpers/tags/tel_field.rb +8 -0
- data/lib/action_view/helpers/tags/text_area.rb +18 -0
- data/lib/action_view/helpers/tags/text_field.rb +29 -0
- data/lib/action_view/helpers/tags/time_field.rb +13 -0
- data/lib/action_view/helpers/tags/time_select.rb +8 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +20 -0
- data/lib/action_view/helpers/tags/url_field.rb +8 -0
- data/lib/action_view/helpers/tags/week_field.rb +13 -0
- data/lib/action_view/helpers/text_helper.rb +447 -0
- data/lib/action_view/helpers/translation_helper.rb +111 -0
- data/lib/action_view/helpers/url_helper.rb +625 -0
- data/lib/action_view/layouts.rb +426 -0
- data/lib/action_view/locale/en.yml +56 -0
- data/lib/action_view/log_subscriber.rb +44 -0
- data/lib/action_view/lookup_context.rb +249 -0
- data/lib/action_view/model_naming.rb +12 -0
- data/lib/action_view/path_set.rb +77 -0
- data/lib/action_view/railtie.rb +49 -0
- data/lib/action_view/record_identifier.rb +84 -0
- data/lib/action_view/renderer/abstract_renderer.rb +47 -0
- data/lib/action_view/renderer/partial_renderer.rb +492 -0
- data/lib/action_view/renderer/renderer.rb +50 -0
- data/lib/action_view/renderer/streaming_template_renderer.rb +103 -0
- data/lib/action_view/renderer/template_renderer.rb +96 -0
- data/lib/action_view/rendering.rb +145 -0
- data/lib/action_view/routing_url_for.rb +109 -0
- data/lib/action_view/tasks/dependencies.rake +17 -0
- data/lib/action_view/template.rb +340 -0
- data/lib/action_view/template/error.rb +141 -0
- data/lib/action_view/template/handlers.rb +53 -0
- data/lib/action_view/template/handlers/builder.rb +26 -0
- data/lib/action_view/template/handlers/erb.rb +145 -0
- data/lib/action_view/template/handlers/raw.rb +11 -0
- data/lib/action_view/template/resolver.rb +329 -0
- data/lib/action_view/template/text.rb +34 -0
- data/lib/action_view/template/types.rb +57 -0
- data/lib/action_view/test_case.rb +272 -0
- data/lib/action_view/testing/resolvers.rb +50 -0
- data/lib/action_view/vendor/html-scanner.rb +20 -0
- data/lib/action_view/vendor/html-scanner/html/document.rb +68 -0
- data/lib/action_view/vendor/html-scanner/html/node.rb +532 -0
- data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +188 -0
- data/lib/action_view/vendor/html-scanner/html/selector.rb +830 -0
- data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +107 -0
- data/lib/action_view/vendor/html-scanner/html/version.rb +11 -0
- data/lib/action_view/version.rb +11 -0
- data/lib/action_view/view_paths.rb +96 -0
- metadata +218 -0
@@ -0,0 +1,203 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module ActionView
|
4
|
+
# = Action View Atom Feed Helpers
|
5
|
+
module Helpers
|
6
|
+
module AtomFeedHelper
|
7
|
+
# Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other
|
8
|
+
# template languages).
|
9
|
+
#
|
10
|
+
# Full usage example:
|
11
|
+
#
|
12
|
+
# config/routes.rb:
|
13
|
+
# Basecamp::Application.routes.draw do
|
14
|
+
# resources :posts
|
15
|
+
# root to: "posts#index"
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# app/controllers/posts_controller.rb:
|
19
|
+
# class PostsController < ApplicationController::Base
|
20
|
+
# # GET /posts.html
|
21
|
+
# # GET /posts.atom
|
22
|
+
# def index
|
23
|
+
# @posts = Post.all
|
24
|
+
#
|
25
|
+
# respond_to do |format|
|
26
|
+
# format.html
|
27
|
+
# format.atom
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# app/views/posts/index.atom.builder:
|
33
|
+
# atom_feed do |feed|
|
34
|
+
# feed.title("My great blog!")
|
35
|
+
# feed.updated(@posts[0].created_at) if @posts.length > 0
|
36
|
+
#
|
37
|
+
# @posts.each do |post|
|
38
|
+
# feed.entry(post) do |entry|
|
39
|
+
# entry.title(post.title)
|
40
|
+
# entry.content(post.body, type: 'html')
|
41
|
+
#
|
42
|
+
# entry.author do |author|
|
43
|
+
# author.name("DHH")
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# The options for atom_feed are:
|
50
|
+
#
|
51
|
+
# * <tt>:language</tt>: Defaults to "en-US".
|
52
|
+
# * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host.
|
53
|
+
# * <tt>:url</tt>: The URL for this feed. Defaults to the current URL.
|
54
|
+
# * <tt>:id</tt>: The id for this feed. Defaults to "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}"
|
55
|
+
# * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you
|
56
|
+
# created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified,
|
57
|
+
# 2005 is used (as an "I don't care" value).
|
58
|
+
# * <tt>:instruct</tt>: Hash of XML processing instructions in the form {target => {attribute => value, }} or {target => [{attribute => value, }, ]}
|
59
|
+
#
|
60
|
+
# Other namespaces can be added to the root element:
|
61
|
+
#
|
62
|
+
# app/views/posts/index.atom.builder:
|
63
|
+
# atom_feed({'xmlns:app' => 'http://www.w3.org/2007/app',
|
64
|
+
# 'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed|
|
65
|
+
# feed.title("My great blog!")
|
66
|
+
# feed.updated((@posts.first.created_at))
|
67
|
+
# feed.tag!('openSearch:totalResults', 10)
|
68
|
+
#
|
69
|
+
# @posts.each do |post|
|
70
|
+
# feed.entry(post) do |entry|
|
71
|
+
# entry.title(post.title)
|
72
|
+
# entry.content(post.body, type: 'html')
|
73
|
+
# entry.tag!('app:edited', Time.now)
|
74
|
+
#
|
75
|
+
# entry.author do |author|
|
76
|
+
# author.name("DHH")
|
77
|
+
# end
|
78
|
+
# end
|
79
|
+
# end
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# The Atom spec defines five elements (content rights title subtitle
|
83
|
+
# summary) which may directly contain xhtml content if type: 'xhtml'
|
84
|
+
# is specified as an attribute. If so, this helper will take care of
|
85
|
+
# the enclosing div and xhtml namespace declaration. Example usage:
|
86
|
+
#
|
87
|
+
# entry.summary type: 'xhtml' do |xhtml|
|
88
|
+
# xhtml.p pluralize(order.line_items.count, "line item")
|
89
|
+
# xhtml.p "Shipped to #{order.address}"
|
90
|
+
# xhtml.p "Paid by #{order.pay_type}"
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
#
|
94
|
+
# <tt>atom_feed</tt> yields an +AtomFeedBuilder+ instance. Nested elements yield
|
95
|
+
# an +AtomBuilder+ instance.
|
96
|
+
def atom_feed(options = {}, &block)
|
97
|
+
if options[:schema_date]
|
98
|
+
options[:schema_date] = options[:schema_date].strftime("%Y-%m-%d") if options[:schema_date].respond_to?(:strftime)
|
99
|
+
else
|
100
|
+
options[:schema_date] = "2005" # The Atom spec copyright date
|
101
|
+
end
|
102
|
+
|
103
|
+
xml = options.delete(:xml) || eval("xml", block.binding)
|
104
|
+
xml.instruct!
|
105
|
+
if options[:instruct]
|
106
|
+
options[:instruct].each do |target,attrs|
|
107
|
+
if attrs.respond_to?(:keys)
|
108
|
+
xml.instruct!(target, attrs)
|
109
|
+
elsif attrs.respond_to?(:each)
|
110
|
+
attrs.each { |attr_group| xml.instruct!(target, attr_group) }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
feed_opts = {"xml:lang" => options[:language] || "en-US", "xmlns" => 'http://www.w3.org/2005/Atom'}
|
116
|
+
feed_opts.merge!(options).reject!{|k,v| !k.to_s.match(/^xml/)}
|
117
|
+
|
118
|
+
xml.feed(feed_opts) do
|
119
|
+
xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}")
|
120
|
+
xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:root_url] || (request.protocol + request.host_with_port))
|
121
|
+
xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:url] || request.url)
|
122
|
+
|
123
|
+
yield AtomFeedBuilder.new(xml, self, options)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class AtomBuilder #:nodoc:
|
128
|
+
XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set
|
129
|
+
|
130
|
+
def initialize(xml)
|
131
|
+
@xml = xml
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
# Delegate to xml builder, first wrapping the element in a xhtml
|
136
|
+
# namespaced div element if the method and arguments indicate
|
137
|
+
# that an xhtml_block? is desired.
|
138
|
+
def method_missing(method, *arguments, &block)
|
139
|
+
if xhtml_block?(method, arguments)
|
140
|
+
@xml.__send__(method, *arguments) do
|
141
|
+
@xml.div(:xmlns => 'http://www.w3.org/1999/xhtml') do |xhtml|
|
142
|
+
block.call(xhtml)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
else
|
146
|
+
@xml.__send__(method, *arguments, &block)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# True if the method name matches one of the five elements defined
|
151
|
+
# in the Atom spec as potentially containing XHTML content and
|
152
|
+
# if type: 'xhtml' is, in fact, specified.
|
153
|
+
def xhtml_block?(method, arguments)
|
154
|
+
if XHTML_TAG_NAMES.include?(method.to_s)
|
155
|
+
last = arguments.last
|
156
|
+
last.is_a?(Hash) && last[:type].to_s == 'xhtml'
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
class AtomFeedBuilder < AtomBuilder #:nodoc:
|
162
|
+
def initialize(xml, view, feed_options = {})
|
163
|
+
@xml, @view, @feed_options = xml, view, feed_options
|
164
|
+
end
|
165
|
+
|
166
|
+
# Accepts a Date or Time object and inserts it in the proper format. If nil is passed, current time in UTC is used.
|
167
|
+
def updated(date_or_time = nil)
|
168
|
+
@xml.updated((date_or_time || Time.now.utc).xmlschema)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Creates an entry tag for a specific record and prefills the id using class and id.
|
172
|
+
#
|
173
|
+
# Options:
|
174
|
+
#
|
175
|
+
# * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists.
|
176
|
+
# * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists.
|
177
|
+
# * <tt>:url</tt>: The URL for this entry. Defaults to the polymorphic_url for the record.
|
178
|
+
# * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}"
|
179
|
+
# * <tt>:type</tt>: The TYPE for this entry. Defaults to "text/html".
|
180
|
+
def entry(record, options = {})
|
181
|
+
@xml.entry do
|
182
|
+
@xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}")
|
183
|
+
|
184
|
+
if options[:published] || (record.respond_to?(:created_at) && record.created_at)
|
185
|
+
@xml.published((options[:published] || record.created_at).xmlschema)
|
186
|
+
end
|
187
|
+
|
188
|
+
if options[:updated] || (record.respond_to?(:updated_at) && record.updated_at)
|
189
|
+
@xml.updated((options[:updated] || record.updated_at).xmlschema)
|
190
|
+
end
|
191
|
+
|
192
|
+
type = options.fetch(:type, 'text/html')
|
193
|
+
|
194
|
+
@xml.link(:rel => 'alternate', :type => type, :href => options[:url] || @view.polymorphic_url(record))
|
195
|
+
|
196
|
+
yield AtomBuilder.new(@xml)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
module ActionView
|
2
|
+
# = Action View Cache Helper
|
3
|
+
module Helpers
|
4
|
+
module CacheHelper
|
5
|
+
# This helper exposes a method for caching fragments of a view
|
6
|
+
# rather than an entire action or page. This technique is useful
|
7
|
+
# caching pieces like menus, lists of new topics, static HTML
|
8
|
+
# fragments, and so on. This method takes a block that contains
|
9
|
+
# the content you wish to cache.
|
10
|
+
#
|
11
|
+
# The best way to use this is by doing key-based cache expiration
|
12
|
+
# on top of a cache store like Memcached that'll automatically
|
13
|
+
# kick out old entries. For more on key-based expiration, see:
|
14
|
+
# http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works
|
15
|
+
#
|
16
|
+
# When using this method, you list the cache dependency as the name of the cache, like so:
|
17
|
+
#
|
18
|
+
# <% cache project do %>
|
19
|
+
# <b>All the topics on this project</b>
|
20
|
+
# <%= render project.topics %>
|
21
|
+
# <% end %>
|
22
|
+
#
|
23
|
+
# This approach will assume that when a new topic is added, you'll touch
|
24
|
+
# the project. The cache key generated from this call will be something like:
|
25
|
+
#
|
26
|
+
# views/projects/123-20120806214154/7a1156131a6928cb0026877f8b749ac9
|
27
|
+
# ^class ^id ^updated_at ^template tree digest
|
28
|
+
#
|
29
|
+
# The cache is thus automatically bumped whenever the project updated_at is touched.
|
30
|
+
#
|
31
|
+
# If your template cache depends on multiple sources (try to avoid this to keep things simple),
|
32
|
+
# you can name all these dependencies as part of an array:
|
33
|
+
#
|
34
|
+
# <% cache [ project, current_user ] do %>
|
35
|
+
# <b>All the topics on this project</b>
|
36
|
+
# <%= render project.topics %>
|
37
|
+
# <% end %>
|
38
|
+
#
|
39
|
+
# This will include both records as part of the cache key and updating either of them will
|
40
|
+
# expire the cache.
|
41
|
+
#
|
42
|
+
# ==== Template digest
|
43
|
+
#
|
44
|
+
# The template digest that's added to the cache key is computed by taking an md5 of the
|
45
|
+
# contents of the entire template file. This ensures that your caches will automatically
|
46
|
+
# expire when you change the template file.
|
47
|
+
#
|
48
|
+
# Note that the md5 is taken of the entire template file, not just what's within the
|
49
|
+
# cache do/end call. So it's possible that changing something outside of that call will
|
50
|
+
# still expire the cache.
|
51
|
+
#
|
52
|
+
# Additionally, the digestor will automatically look through your template file for
|
53
|
+
# explicit and implicit dependencies, and include those as part of the digest.
|
54
|
+
#
|
55
|
+
# The digestor can be bypassed by passing skip_digest: true as an option to the cache call:
|
56
|
+
#
|
57
|
+
# <% cache project, skip_digest: true do %>
|
58
|
+
# <b>All the topics on this project</b>
|
59
|
+
# <%= render project.topics %>
|
60
|
+
# <% end %>
|
61
|
+
#
|
62
|
+
# ==== Implicit dependencies
|
63
|
+
#
|
64
|
+
# Most template dependencies can be derived from calls to render in the template itself.
|
65
|
+
# Here are some examples of render calls that Cache Digests knows how to decode:
|
66
|
+
#
|
67
|
+
# render partial: "comments/comment", collection: commentable.comments
|
68
|
+
# render "comments/comments"
|
69
|
+
# render 'comments/comments'
|
70
|
+
# render('comments/comments')
|
71
|
+
#
|
72
|
+
# render "header" => render("comments/header")
|
73
|
+
#
|
74
|
+
# render(@topic) => render("topics/topic")
|
75
|
+
# render(topics) => render("topics/topic")
|
76
|
+
# render(message.topics) => render("topics/topic")
|
77
|
+
#
|
78
|
+
# It's not possible to derive all render calls like that, though. Here are a few examples of things that can't be derived:
|
79
|
+
#
|
80
|
+
# render group_of_attachments
|
81
|
+
# render @project.documents.where(published: true).order('created_at')
|
82
|
+
#
|
83
|
+
# You will have to rewrite those to the explicit form:
|
84
|
+
#
|
85
|
+
# render partial: 'attachments/attachment', collection: group_of_attachments
|
86
|
+
# render partial: 'documents/document', collection: @project.documents.where(published: true).order('created_at')
|
87
|
+
#
|
88
|
+
# === Explicit dependencies
|
89
|
+
#
|
90
|
+
# Some times you'll have template dependencies that can't be derived at all. This is typically
|
91
|
+
# the case when you have template rendering that happens in helpers. Here's an example:
|
92
|
+
#
|
93
|
+
# <%= render_sortable_todolists @project.todolists %>
|
94
|
+
#
|
95
|
+
# You'll need to use a special comment format to call those out:
|
96
|
+
#
|
97
|
+
# <%# Template Dependency: todolists/todolist %>
|
98
|
+
# <%= render_sortable_todolists @project.todolists %>
|
99
|
+
#
|
100
|
+
# The pattern used to match these is /# Template Dependency: ([^ ]+)/, so it's important that you type it out just so.
|
101
|
+
# You can only declare one template dependency per line.
|
102
|
+
#
|
103
|
+
# === External dependencies
|
104
|
+
#
|
105
|
+
# If you use a helper method, for example, inside of a cached block and you then update that helper,
|
106
|
+
# you'll have to bump the cache as well. It doesn't really matter how you do it, but the md5 of the template file
|
107
|
+
# must change. One recommendation is to simply be explicit in a comment, like:
|
108
|
+
#
|
109
|
+
# <%# Helper Dependency Updated: May 6, 2012 at 6pm %>
|
110
|
+
# <%= some_helper_method(person) %>
|
111
|
+
#
|
112
|
+
# Now all you'll have to do is change that timestamp when the helper method changes.
|
113
|
+
def cache(name = {}, options = nil, &block)
|
114
|
+
if controller.perform_caching
|
115
|
+
safe_concat(fragment_for(cache_fragment_name(name, options), options, &block))
|
116
|
+
else
|
117
|
+
yield
|
118
|
+
end
|
119
|
+
|
120
|
+
nil
|
121
|
+
end
|
122
|
+
|
123
|
+
# Cache fragments of a view if +condition+ is true
|
124
|
+
#
|
125
|
+
# <%= cache_if admin?, project do %>
|
126
|
+
# <b>All the topics on this project</b>
|
127
|
+
# <%= render project.topics %>
|
128
|
+
# <% end %>
|
129
|
+
def cache_if(condition, name = {}, options = nil, &block)
|
130
|
+
if condition
|
131
|
+
cache(name, options, &block)
|
132
|
+
else
|
133
|
+
yield
|
134
|
+
end
|
135
|
+
|
136
|
+
nil
|
137
|
+
end
|
138
|
+
|
139
|
+
# Cache fragments of a view unless +condition+ is true
|
140
|
+
#
|
141
|
+
# <%= cache_unless admin?, project do %>
|
142
|
+
# <b>All the topics on this project</b>
|
143
|
+
# <%= render project.topics %>
|
144
|
+
# <% end %>
|
145
|
+
def cache_unless(condition, name = {}, options = nil, &block)
|
146
|
+
cache_if !condition, name, options, &block
|
147
|
+
end
|
148
|
+
|
149
|
+
# This helper returns the name of a cache key for a given fragment cache
|
150
|
+
# call. By supplying skip_digest: true to cache, the digestion of cache
|
151
|
+
# fragments can be manually bypassed. This is useful when cache fragments
|
152
|
+
# cannot be manually expired unless you know the exact key which is the
|
153
|
+
# case when using memcached.
|
154
|
+
def cache_fragment_name(name = {}, options = nil)
|
155
|
+
skip_digest = options && options[:skip_digest]
|
156
|
+
|
157
|
+
if skip_digest
|
158
|
+
name
|
159
|
+
else
|
160
|
+
fragment_name_with_digest(name)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
def fragment_name_with_digest(name) #:nodoc:
|
167
|
+
if @virtual_path
|
168
|
+
[
|
169
|
+
*Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name),
|
170
|
+
Digestor.digest(@virtual_path, formats.last.to_sym, lookup_context, dependencies: view_cache_dependencies)
|
171
|
+
]
|
172
|
+
else
|
173
|
+
name
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# TODO: Create an object that has caching read/write on it
|
178
|
+
def fragment_for(name = {}, options = nil, &block) #:nodoc:
|
179
|
+
read_fragment_for(name, options) || write_fragment_for(name, options, &block)
|
180
|
+
end
|
181
|
+
|
182
|
+
def read_fragment_for(name, options) #:nodoc:
|
183
|
+
controller.read_fragment(name, options)
|
184
|
+
end
|
185
|
+
|
186
|
+
def write_fragment_for(name, options) #:nodoc:
|
187
|
+
# VIEW TODO: Make #capture usable outside of ERB
|
188
|
+
# This dance is needed because Builder can't use capture
|
189
|
+
pos = output_buffer.length
|
190
|
+
yield
|
191
|
+
output_safe = output_buffer.html_safe?
|
192
|
+
fragment = output_buffer.slice!(pos..-1)
|
193
|
+
if output_safe
|
194
|
+
self.output_buffer = output_buffer.class.new(output_buffer)
|
195
|
+
end
|
196
|
+
controller.write_fragment(name, fragment, options)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
require 'active_support/core_ext/string/output_safety'
|
2
|
+
|
3
|
+
module ActionView
|
4
|
+
# = Action View Capture Helper
|
5
|
+
module Helpers
|
6
|
+
# CaptureHelper exposes methods to let you extract generated markup which
|
7
|
+
# can be used in other parts of a template or layout file.
|
8
|
+
#
|
9
|
+
# It provides a method to capture blocks into variables through capture and
|
10
|
+
# a way to capture a block of markup for use in a layout through content_for.
|
11
|
+
module CaptureHelper
|
12
|
+
# The capture method allows you to extract part of a template into a
|
13
|
+
# variable. You can then use this variable anywhere in your templates or layout.
|
14
|
+
#
|
15
|
+
# The capture method can be used in ERB templates...
|
16
|
+
#
|
17
|
+
# <% @greeting = capture do %>
|
18
|
+
# Welcome to my shiny new web page! The date and time is
|
19
|
+
# <%= Time.now %>
|
20
|
+
# <% end %>
|
21
|
+
#
|
22
|
+
# ...and Builder (RXML) templates.
|
23
|
+
#
|
24
|
+
# @timestamp = capture do
|
25
|
+
# "The current timestamp is #{Time.now}."
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# You can then use that variable anywhere else. For example:
|
29
|
+
#
|
30
|
+
# <html>
|
31
|
+
# <head><title><%= @greeting %></title></head>
|
32
|
+
# <body>
|
33
|
+
# <b><%= @greeting %></b>
|
34
|
+
# </body></html>
|
35
|
+
#
|
36
|
+
def capture(*args)
|
37
|
+
value = nil
|
38
|
+
buffer = with_output_buffer { value = yield(*args) }
|
39
|
+
if string = buffer.presence || value and string.is_a?(String)
|
40
|
+
ERB::Util.html_escape string
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Calling content_for stores a block of markup in an identifier for later use.
|
45
|
+
# In order to access this stored content in other templates, helper modules
|
46
|
+
# or the layout, you would pass the identifier as an argument to <tt>content_for</tt>.
|
47
|
+
#
|
48
|
+
# Note: <tt>yield</tt> can still be used to retrieve the stored content, but calling
|
49
|
+
# <tt>yield</tt> doesn't work in helper modules, while <tt>content_for</tt> does.
|
50
|
+
#
|
51
|
+
# <% content_for :not_authorized do %>
|
52
|
+
# alert('You are not authorized to do that!')
|
53
|
+
# <% end %>
|
54
|
+
#
|
55
|
+
# You can then use <tt>content_for :not_authorized</tt> anywhere in your templates.
|
56
|
+
#
|
57
|
+
# <%= content_for :not_authorized if current_user.nil? %>
|
58
|
+
#
|
59
|
+
# This is equivalent to:
|
60
|
+
#
|
61
|
+
# <%= yield :not_authorized if current_user.nil? %>
|
62
|
+
#
|
63
|
+
# <tt>content_for</tt>, however, can also be used in helper modules.
|
64
|
+
#
|
65
|
+
# module StorageHelper
|
66
|
+
# def stored_content
|
67
|
+
# content_for(:storage) || "Your storage is empty"
|
68
|
+
# end
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# This helper works just like normal helpers.
|
72
|
+
#
|
73
|
+
# <%= stored_content %>
|
74
|
+
#
|
75
|
+
# You can also use the <tt>yield</tt> syntax alongside an existing call to
|
76
|
+
# <tt>yield</tt> in a layout. For example:
|
77
|
+
#
|
78
|
+
# <%# This is the layout %>
|
79
|
+
# <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
80
|
+
# <head>
|
81
|
+
# <title>My Website</title>
|
82
|
+
# <%= yield :script %>
|
83
|
+
# </head>
|
84
|
+
# <body>
|
85
|
+
# <%= yield %>
|
86
|
+
# </body>
|
87
|
+
# </html>
|
88
|
+
#
|
89
|
+
# And now, we'll create a view that has a <tt>content_for</tt> call that
|
90
|
+
# creates the <tt>script</tt> identifier.
|
91
|
+
#
|
92
|
+
# <%# This is our view %>
|
93
|
+
# Please login!
|
94
|
+
#
|
95
|
+
# <% content_for :script do %>
|
96
|
+
# <script>alert('You are not authorized to view this page!')</script>
|
97
|
+
# <% end %>
|
98
|
+
#
|
99
|
+
# Then, in another view, you could to do something like this:
|
100
|
+
#
|
101
|
+
# <%= link_to 'Logout', action: 'logout', remote: true %>
|
102
|
+
#
|
103
|
+
# <% content_for :script do %>
|
104
|
+
# <%= javascript_include_tag :defaults %>
|
105
|
+
# <% end %>
|
106
|
+
#
|
107
|
+
# That will place +script+ tags for your default set of JavaScript files on the page;
|
108
|
+
# this technique is useful if you'll only be using these scripts in a few views.
|
109
|
+
#
|
110
|
+
# Note that content_for concatenates (default) the blocks it is given for a particular
|
111
|
+
# identifier in order. For example:
|
112
|
+
#
|
113
|
+
# <% content_for :navigation do %>
|
114
|
+
# <li><%= link_to 'Home', action: 'index' %></li>
|
115
|
+
# <% end %>
|
116
|
+
#
|
117
|
+
# And in other place:
|
118
|
+
#
|
119
|
+
# <% content_for :navigation do %>
|
120
|
+
# <li><%= link_to 'Login', action: 'login' %></li>
|
121
|
+
# <% end %>
|
122
|
+
#
|
123
|
+
# Then, in another template or layout, this code would render both links in order:
|
124
|
+
#
|
125
|
+
# <ul><%= content_for :navigation %></ul>
|
126
|
+
#
|
127
|
+
# If the flush parameter is true content_for replaces the blocks it is given. For example:
|
128
|
+
#
|
129
|
+
# <% content_for :navigation do %>
|
130
|
+
# <li><%= link_to 'Home', action: 'index' %></li>
|
131
|
+
# <% end %>
|
132
|
+
#
|
133
|
+
# <%# Add some other content, or use a different template: %>
|
134
|
+
#
|
135
|
+
# <% content_for :navigation, flush: true do %>
|
136
|
+
# <li><%= link_to 'Login', action: 'login' %></li>
|
137
|
+
# <% end %>
|
138
|
+
#
|
139
|
+
# Then, in another template or layout, this code would render only the last link:
|
140
|
+
#
|
141
|
+
# <ul><%= content_for :navigation %></ul>
|
142
|
+
#
|
143
|
+
# Lastly, simple content can be passed as a parameter:
|
144
|
+
#
|
145
|
+
# <% content_for :script, javascript_include_tag(:defaults) %>
|
146
|
+
#
|
147
|
+
# WARNING: content_for is ignored in caches. So you shouldn't use it for elements that will be fragment cached.
|
148
|
+
def content_for(name, content = nil, options = {}, &block)
|
149
|
+
if content || block_given?
|
150
|
+
if block_given?
|
151
|
+
options = content if content
|
152
|
+
content = capture(&block)
|
153
|
+
end
|
154
|
+
if content
|
155
|
+
options[:flush] ? @view_flow.set(name, content) : @view_flow.append(name, content)
|
156
|
+
end
|
157
|
+
nil
|
158
|
+
else
|
159
|
+
@view_flow.get(name).presence
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# The same as +content_for+ but when used with streaming flushes
|
164
|
+
# straight back to the layout. In other words, if you want to
|
165
|
+
# concatenate several times to the same buffer when rendering a given
|
166
|
+
# template, you should use +content_for+, if not, use +provide+ to tell
|
167
|
+
# the layout to stop looking for more contents.
|
168
|
+
def provide(name, content = nil, &block)
|
169
|
+
content = capture(&block) if block_given?
|
170
|
+
result = @view_flow.append!(name, content) if content
|
171
|
+
result unless content
|
172
|
+
end
|
173
|
+
|
174
|
+
# content_for? checks whether any content has been captured yet using `content_for`.
|
175
|
+
# Useful to render parts of your layout differently based on what is in your views.
|
176
|
+
#
|
177
|
+
# <%# This is the layout %>
|
178
|
+
# <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
179
|
+
# <head>
|
180
|
+
# <title>My Website</title>
|
181
|
+
# <%= yield :script %>
|
182
|
+
# </head>
|
183
|
+
# <body class="<%= content_for?(:right_col) ? 'two-column' : 'one-column' %>">
|
184
|
+
# <%= yield %>
|
185
|
+
# <%= yield :right_col %>
|
186
|
+
# </body>
|
187
|
+
# </html>
|
188
|
+
def content_for?(name)
|
189
|
+
@view_flow.get(name).present?
|
190
|
+
end
|
191
|
+
|
192
|
+
# Use an alternate output buffer for the duration of the block.
|
193
|
+
# Defaults to a new empty string.
|
194
|
+
def with_output_buffer(buf = nil) #:nodoc:
|
195
|
+
unless buf
|
196
|
+
buf = ActionView::OutputBuffer.new
|
197
|
+
buf.force_encoding(output_buffer.encoding) if output_buffer
|
198
|
+
end
|
199
|
+
self.output_buffer, old_buffer = buf, output_buffer
|
200
|
+
yield
|
201
|
+
output_buffer
|
202
|
+
ensure
|
203
|
+
self.output_buffer = old_buffer
|
204
|
+
end
|
205
|
+
|
206
|
+
# Add the output buffer to the response body and start a new one.
|
207
|
+
def flush_output_buffer #:nodoc:
|
208
|
+
if output_buffer && !output_buffer.empty?
|
209
|
+
response.stream.write output_buffer
|
210
|
+
self.output_buffer = output_buffer.respond_to?(:clone_empty) ? output_buffer.clone_empty : output_buffer[0, 0]
|
211
|
+
nil
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|