pagelet_rails 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +402 -0
- data/Rakefile +37 -0
- data/app/assets/config/pagelet_rails_manifest.js +0 -0
- data/app/assets/javascripts/pagelet_rails/jquery.ajaxprogress.js +76 -0
- data/app/assets/javascripts/pagelet_rails.js +185 -0
- data/app/assets/javascripts/views/modal_dialog.jst.ejs +13 -0
- data/app/assets/javascripts/views/modal_dialog_iframe.jst.ejs +7 -0
- data/app/assets/javascripts/views/pagelet_load_failed.jst.ejs +7 -0
- data/app/assets/javascripts/views/pagelet_loading_overlay.jst.ejs +15 -0
- data/app/controllers/pagelet_controller.rb +4 -0
- data/app/controllers/pagelet_proxy_controller.rb +20 -0
- data/app/helpers/pagelets_helper.rb +82 -0
- data/app/views/layouts/pagelet_rails/container.html.slim +24 -0
- data/app/views/layouts/pagelet_rails/inner.slim +1 -0
- data/app/views/layouts/pagelet_rails/loading_placeholder.slim +14 -0
- data/config/routes.rb +7 -0
- data/lib/action_controller/action_caching.rb +16 -0
- data/lib/action_controller/caching/actions.rb +210 -0
- data/lib/pagelet_rails/concerns/cache.rb +65 -0
- data/lib/pagelet_rails/concerns/controller.rb +73 -0
- data/lib/pagelet_rails/concerns/options.rb +76 -0
- data/lib/pagelet_rails/concerns/placeholder.rb +35 -0
- data/lib/pagelet_rails/concerns/response_wrapper.rb +33 -0
- data/lib/pagelet_rails/concerns/routes.rb +83 -0
- data/lib/pagelet_rails/encryptor.rb +49 -0
- data/lib/pagelet_rails/engine.rb +4 -0
- data/lib/pagelet_rails/router.rb +23 -0
- data/lib/pagelet_rails/version.rb +3 -0
- data/lib/pagelet_rails.rb +28 -0
- data/lib/tasks/pagelet_rails_tasks.rake +4 -0
- metadata +104 -0
@@ -0,0 +1,185 @@
|
|
1
|
+
//= require_tree ./views
|
2
|
+
//= require pagelet_rails/jquery.ajaxprogress
|
3
|
+
|
4
|
+
(function (w) {
|
5
|
+
|
6
|
+
var root = w['PageletRails'] || {};
|
7
|
+
w['PageletRails'] = root;
|
8
|
+
|
9
|
+
root.addParamsToUrl = function( url, data ) {
|
10
|
+
if ( ! $.isEmptyObject(data) ) {
|
11
|
+
url += ( url.indexOf('?') >= 0 ? '&' : '?' ) + $.param(data);
|
12
|
+
}
|
13
|
+
return url;
|
14
|
+
};
|
15
|
+
|
16
|
+
root.appendScriptTag = function (text) {
|
17
|
+
var script = document.createElement( "script" );
|
18
|
+
script.type = "text/javascript";
|
19
|
+
script.text = text;
|
20
|
+
$("body").append(script);
|
21
|
+
};
|
22
|
+
|
23
|
+
root.loadDirectly = function(data) {
|
24
|
+
$.ajax({
|
25
|
+
url: data.url,
|
26
|
+
dataType: 'script',
|
27
|
+
headers: {
|
28
|
+
'X-Pagelet': 'pagelet'
|
29
|
+
}
|
30
|
+
}).done(function(_) {
|
31
|
+
}).fail(function(){
|
32
|
+
var html = JST['views/pagelet_load_failed']({
|
33
|
+
pagelet_url: data.url,
|
34
|
+
reload_function: "PageletRails.loadPagelets('#" + data.id + "');"
|
35
|
+
});
|
36
|
+
data.elem.html(html)
|
37
|
+
});
|
38
|
+
};
|
39
|
+
|
40
|
+
root.loadThroughBatchProxy = function(urls) {
|
41
|
+
if (urls.length == 0) { return; }
|
42
|
+
|
43
|
+
var prev_index = 0;
|
44
|
+
|
45
|
+
$.ajax({
|
46
|
+
url: '/pagelet_proxy',
|
47
|
+
data: {
|
48
|
+
urls: urls
|
49
|
+
},
|
50
|
+
dataType: 'text',
|
51
|
+
cache: false,
|
52
|
+
headers: {
|
53
|
+
'X-Pagelet': 'pagelet'
|
54
|
+
},
|
55
|
+
progress: function(_, progressEvent) {
|
56
|
+
var text = progressEvent.target.responseText;
|
57
|
+
var end_index = -1;
|
58
|
+
|
59
|
+
do {
|
60
|
+
end_index = text.indexOf("\n\n//\n\n", prev_index);
|
61
|
+
|
62
|
+
if (end_index != -1) {
|
63
|
+
var new_text = text.substring(prev_index, end_index);
|
64
|
+
|
65
|
+
eval(new_text);
|
66
|
+
|
67
|
+
prev_index = end_index + 1;
|
68
|
+
// console.log('found');
|
69
|
+
// console.log(new_text);
|
70
|
+
}
|
71
|
+
|
72
|
+
} while (end_index != -1);
|
73
|
+
}
|
74
|
+
});
|
75
|
+
};
|
76
|
+
|
77
|
+
root.loadPagelets = function(selector) {
|
78
|
+
selector = selector || '[data-widget-url]';
|
79
|
+
var groups = {};
|
80
|
+
|
81
|
+
$(selector).each(function(index, elem) {
|
82
|
+
var $el = $(elem);
|
83
|
+
var path = $el.data('widget-url');
|
84
|
+
var group = $el.data('pagelet-group');
|
85
|
+
var id = $el.attr('id');
|
86
|
+
|
87
|
+
var url = root.addParamsToUrl(path, {
|
88
|
+
target_container: id,
|
89
|
+
original_pagelet_options: $el.data('pagelet-options')
|
90
|
+
});
|
91
|
+
|
92
|
+
groups[group] = groups[group] || [];
|
93
|
+
groups[group].push({
|
94
|
+
id: id,
|
95
|
+
elem: $el,
|
96
|
+
url: url,
|
97
|
+
group: group
|
98
|
+
});
|
99
|
+
});
|
100
|
+
|
101
|
+
for (var group_name in groups) {
|
102
|
+
if (groups.hasOwnProperty(group_name)) {
|
103
|
+
var group = groups[group_name];
|
104
|
+
|
105
|
+
if (group.length == 1) {
|
106
|
+
root.loadDirectly(group[0]);
|
107
|
+
} else {
|
108
|
+
var urls = group.map(function(e) { return e.url; });
|
109
|
+
root.loadThroughBatchProxy(urls);
|
110
|
+
}
|
111
|
+
}
|
112
|
+
}
|
113
|
+
};
|
114
|
+
|
115
|
+
root.pageletArrived = function(id, content) {
|
116
|
+
root.placeToContainer(id, content);
|
117
|
+
root.processDataRemoteTags();
|
118
|
+
$(document).trigger('pagelet-loaded');
|
119
|
+
};
|
120
|
+
|
121
|
+
root.placeToContainer = function(id, content) {
|
122
|
+
$('#' + id).html(content);
|
123
|
+
};
|
124
|
+
|
125
|
+
root.processDataRemoteTags = function() {
|
126
|
+
$('form[data-remote]').each(function(index, elem){
|
127
|
+
var $el = $(elem);
|
128
|
+
var container = $el.closest('[data-pagelet-container]');
|
129
|
+
|
130
|
+
if (!container) {
|
131
|
+
return;
|
132
|
+
}
|
133
|
+
|
134
|
+
var hidden_field = $el.find('input[name=target_container]')[0];
|
135
|
+
if (!hidden_field) {
|
136
|
+
$("<input/>", {
|
137
|
+
name: "target_container",
|
138
|
+
type: "hidden",
|
139
|
+
value: container.attr('id')
|
140
|
+
}).appendTo($el);
|
141
|
+
}
|
142
|
+
|
143
|
+
hidden_field = $el.find('input[name=original_pagelet_options]')[0];
|
144
|
+
if (!hidden_field) {
|
145
|
+
$("<input/>", {
|
146
|
+
name: "original_pagelet_options",
|
147
|
+
type: "hidden",
|
148
|
+
value: container.data('pagelet-options')
|
149
|
+
}).appendTo($el);
|
150
|
+
}
|
151
|
+
});
|
152
|
+
|
153
|
+
var selector = 'a[data-remote]:not([disabled]),button[data-remote]:not([disabled])';
|
154
|
+
$(selector).each(function(index, elem){
|
155
|
+
var $el = $(elem);
|
156
|
+
var container = $el.closest('[data-pagelet-container]');
|
157
|
+
|
158
|
+
if (!container) {
|
159
|
+
return;
|
160
|
+
}
|
161
|
+
|
162
|
+
var params = $el.data('params');
|
163
|
+
if (!params) {
|
164
|
+
var value = $.param({
|
165
|
+
target_container: container.attr('id'),
|
166
|
+
original_pagelet_options: container.data('pagelet-options')
|
167
|
+
});
|
168
|
+
$el.data('params', value);
|
169
|
+
}
|
170
|
+
});
|
171
|
+
};
|
172
|
+
|
173
|
+
function initialise() {
|
174
|
+
document.addEventListener("turbolinks:load", function(){
|
175
|
+
root.loadPagelets();
|
176
|
+
root.processDataRemoteTags();
|
177
|
+
});
|
178
|
+
}
|
179
|
+
|
180
|
+
initialise();
|
181
|
+
})(window);
|
182
|
+
|
183
|
+
|
184
|
+
|
185
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<div class="modal-dialog">
|
2
|
+
<div class="modal-content">
|
3
|
+
<div class="modal-body">
|
4
|
+
<% if (state == 'loading') { %>
|
5
|
+
<p>Loading...</p>
|
6
|
+
<% } %>
|
7
|
+
|
8
|
+
<% if (state == 'error') { %>
|
9
|
+
<p>This page could not be loaded. Please try again later.</p>
|
10
|
+
<% } %>
|
11
|
+
</div>
|
12
|
+
</div>
|
13
|
+
</div>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<div class="pagelet-loading-overlay fade">
|
2
|
+
<div style="display: table; overflow: hidden; width: 100%; height:auto;">
|
3
|
+
<div style="display: table-cell; vertical-align: middle;">
|
4
|
+
<div class="pagelet-loading-overlay__content">
|
5
|
+
<span class="spinner spinner-three-bounce">
|
6
|
+
<div class="bounce1"></div>
|
7
|
+
<div class="bounce2"></div>
|
8
|
+
<div class="bounce3"></div>
|
9
|
+
</span>
|
10
|
+
<br>
|
11
|
+
<span>Loading...</span>
|
12
|
+
</div>
|
13
|
+
</div>
|
14
|
+
</div>
|
15
|
+
</div>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class PageletProxyController < ::ApplicationController
|
2
|
+
include ActionController::Live
|
3
|
+
include PageletsHelper
|
4
|
+
|
5
|
+
def show
|
6
|
+
@urls = params[:urls]
|
7
|
+
|
8
|
+
response.headers['Content-Type'] = 'text/javascript'
|
9
|
+
|
10
|
+
response.stream.write "\n"
|
11
|
+
|
12
|
+
@urls.each do |url|
|
13
|
+
response.stream.write pagelet(url)
|
14
|
+
response.stream.write "\n\n//\n\n"
|
15
|
+
end
|
16
|
+
ensure
|
17
|
+
response.stream.close
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module PageletsHelper
|
2
|
+
|
3
|
+
def pagelet_stream
|
4
|
+
return nil if pagelet_stream_objects.empty?
|
5
|
+
pagelet_stream_objects.each do |key, block|
|
6
|
+
concat content_tag('script', raw("PageletRails.pageletArrived('#{key}', '#{j capture(&block)}');\n"), type: 'text/javascript')
|
7
|
+
end
|
8
|
+
nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def pagelet_default_id
|
12
|
+
"pagelet_#{rand(2**60).to_s(36)}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_pagelet_stream key, &block
|
16
|
+
objects = pagelet_stream_objects
|
17
|
+
raise "duplicate key: #{key}" if objects.has_key?(key)
|
18
|
+
objects[key] = block
|
19
|
+
request.instance_variable_set(:@pagelet_stream_objects, objects)
|
20
|
+
end
|
21
|
+
|
22
|
+
def pagelet_stream_objects
|
23
|
+
request.instance_variable_get(:@pagelet_stream_objects) || {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def pagelet path, p_options = {}
|
27
|
+
puts "Rendering pagelet #{path}".blue
|
28
|
+
|
29
|
+
p_params = p_options.delete(:params) { {} }.with_indifferent_access
|
30
|
+
|
31
|
+
if path.is_a? Symbol
|
32
|
+
path = self.send("#{path}_path", p_params)
|
33
|
+
else
|
34
|
+
uri = URI(path)
|
35
|
+
p_params.merge! Rack::Utils.parse_nested_query(uri.query)
|
36
|
+
p_options.merge! remote: false
|
37
|
+
end
|
38
|
+
|
39
|
+
path_opts = Rails.application.routes.recognize_path(path)
|
40
|
+
p_params.reverse_merge!(path_opts)
|
41
|
+
|
42
|
+
controller_class = path_opts[:controller].camelize.concat('Controller').constantize
|
43
|
+
action = path_opts[:action]
|
44
|
+
|
45
|
+
|
46
|
+
if p_options[:remote] == :stream
|
47
|
+
html_id = p_options.dig(:html, :id) || pagelet_default_id
|
48
|
+
p_options.deep_merge! html: { id: html_id }
|
49
|
+
|
50
|
+
add_pagelet_stream html_id, &Proc.new {
|
51
|
+
pagelet path, p_options.merge(remote: false, skip_container: true)
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
p_options.deep_merge! parent_params: params.to_h
|
56
|
+
|
57
|
+
c = controller_class.new
|
58
|
+
c.pagelet_options p_options
|
59
|
+
c.pagelet_options original_options: p_options
|
60
|
+
|
61
|
+
env = Rack::MockRequest.env_for(path,
|
62
|
+
'REMOTE_ADDR' => request.env['REMOTE_ADDR'],
|
63
|
+
'HTTP_HOST' => request.env['HTTP_HOST'],
|
64
|
+
'HTTP_TURBOLINKS_REFERRER' => request.env['HTTP_TURBOLINKS_REFERRER'],
|
65
|
+
'HTTP_USER_AGENT' => request.env['HTTP_USER_AGENT'],
|
66
|
+
'HTTP_X_CSRF_TOKEN' => request.env['HTTP_X_CSRF_TOKEN'],
|
67
|
+
'HTTP_X_PAGELET' => request.env['HTTP_X_PAGELET'],
|
68
|
+
'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest",
|
69
|
+
)
|
70
|
+
|
71
|
+
p_request = ActionDispatch::Request.new(env)
|
72
|
+
p_request.parameters.clear
|
73
|
+
p_request.parameters.merge! p_params
|
74
|
+
|
75
|
+
p_response = controller_class.make_response! p_request
|
76
|
+
c.dispatch(action, p_request, p_response)
|
77
|
+
|
78
|
+
body = c.response.body
|
79
|
+
body.html_safe
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
- if pagelet_request? || pagelet_options.skip_container
|
2
|
+
= content_for?(:content) ? yield(:content) : yield
|
3
|
+
|
4
|
+
- else
|
5
|
+
ruby:
|
6
|
+
html_opts = pagelet_options.html || {}
|
7
|
+
classes = html_opts.fetch(:class, '').split(' ')
|
8
|
+
classes << "pagelet-#{controller_name}"
|
9
|
+
classes << "pagelet-#{controller_name}-#{action_name}"
|
10
|
+
|
11
|
+
html_opts[:id] ||= pagelet_default_id
|
12
|
+
html_opts[:class] = classes.join(' ')
|
13
|
+
|
14
|
+
html_opts['data-pagelet-container'] = true
|
15
|
+
|
16
|
+
encode_data = pagelet_options.original_options.to_h.except('remote')
|
17
|
+
html_opts['data-pagelet-options'] = PageletRails::Encryptor.encode(encode_data)
|
18
|
+
|
19
|
+
if pagelet_options.ajax_group
|
20
|
+
html_opts['data-pagelet-group'] = pagelet_options.ajax_group
|
21
|
+
end
|
22
|
+
|
23
|
+
div *html_opts
|
24
|
+
= content_for?(:content) ? yield(:content) : yield
|
@@ -0,0 +1 @@
|
|
1
|
+
= content_for?(:content) ? yield(:content) : yield
|
@@ -0,0 +1,14 @@
|
|
1
|
+
ruby:
|
2
|
+
height = pagelet_options.placeholder.try(:[], :height)
|
3
|
+
text = pagelet_options.placeholder.try(:[], :text) || 'Loading ...'
|
4
|
+
height = height.is_a?(Numeric) ? "#{height}px" : 'auto'
|
5
|
+
|
6
|
+
div style="display: table; height: #{height}; overflow: hidden; width: 100%;"
|
7
|
+
div style="display: table-cell; vertical-align: middle;"
|
8
|
+
div.text-center
|
9
|
+
span.spinner.spinner-three-bounce
|
10
|
+
.bounce1
|
11
|
+
.bounce2
|
12
|
+
.bounce3
|
13
|
+
br
|
14
|
+
span = text
|
data/config/routes.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Cloned from https://github.com/rails/actionpack-action_caching
|
2
|
+
# as it does not support rails 5 yet
|
3
|
+
|
4
|
+
require 'action_controller/caching/actions'
|
5
|
+
|
6
|
+
module ActionController
|
7
|
+
module Caching
|
8
|
+
eager_autoload do
|
9
|
+
autoload :Actions
|
10
|
+
end
|
11
|
+
|
12
|
+
include Actions
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
ActionController::Base.send(:include, ActionController::Caching::Actions)
|
@@ -0,0 +1,210 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module ActionController
|
4
|
+
module Caching
|
5
|
+
# Action caching is similar to page caching by the fact that the entire
|
6
|
+
# output of the response is cached, but unlike page caching, every
|
7
|
+
# request still goes through Action Pack. The key benefit of this is
|
8
|
+
# that filters run before the cache is served, which allows for
|
9
|
+
# authentication and other restrictions on whether someone is allowed
|
10
|
+
# to execute such action.
|
11
|
+
#
|
12
|
+
# class ListsController < ApplicationController
|
13
|
+
# before_filter :authenticate, except: :public
|
14
|
+
#
|
15
|
+
# caches_page :public
|
16
|
+
# caches_action :index, :show
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# In this example, the +public+ action doesn't require authentication
|
20
|
+
# so it's possible to use the faster page caching. On the other hand
|
21
|
+
# +index+ and +show+ require authentication. They can still be cached,
|
22
|
+
# but we need action caching for them.
|
23
|
+
#
|
24
|
+
# Action caching uses fragment caching internally and an around
|
25
|
+
# filter to do the job. The fragment cache is named according to
|
26
|
+
# the host and path of the request. A page that is accessed at
|
27
|
+
# <tt>http://david.example.com/lists/show/1</tt> will result in a fragment named
|
28
|
+
# <tt>david.example.com/lists/show/1</tt>. This allows the cacher to
|
29
|
+
# differentiate between <tt>david.example.com/lists/</tt> and
|
30
|
+
# <tt>jamis.example.com/lists/</tt> -- which is a helpful way of assisting
|
31
|
+
# the subdomain-as-account-key pattern.
|
32
|
+
#
|
33
|
+
# Different representations of the same resource, e.g.
|
34
|
+
# <tt>http://david.example.com/lists</tt> and
|
35
|
+
# <tt>http://david.example.com/lists.xml</tt>
|
36
|
+
# are treated like separate requests and so are cached separately.
|
37
|
+
# Keep in mind when expiring an action cache that
|
38
|
+
# <tt>action: 'lists'</tt> is not the same as
|
39
|
+
# <tt>action: 'lists', format: :xml</tt>.
|
40
|
+
#
|
41
|
+
# You can modify the default action cache path by passing a
|
42
|
+
# <tt>:cache_path</tt> option. This will be passed directly to
|
43
|
+
# <tt>ActionCachePath.new</tt>. This is handy for actions with
|
44
|
+
# multiple possible routes that should be cached differently. If a
|
45
|
+
# block is given, it is called with the current controller instance.
|
46
|
+
# If an object that responds to <tt>call</tt> is given, it'll be called
|
47
|
+
# with the current controller instance.
|
48
|
+
#
|
49
|
+
# And you can also use <tt>:if</tt> (or <tt>:unless</tt>) to pass a
|
50
|
+
# proc that specifies when the action should be cached.
|
51
|
+
#
|
52
|
+
# As of Rails 3.0, you can also pass <tt>:expires_in</tt> with a time
|
53
|
+
# interval (in seconds) to schedule expiration of the cached item.
|
54
|
+
#
|
55
|
+
# The following example depicts some of the points made above:
|
56
|
+
#
|
57
|
+
# class CachePathCreator
|
58
|
+
# def initialize(name)
|
59
|
+
# @name = name
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# def call(controller)
|
63
|
+
# "cache-path-#{@name}"
|
64
|
+
# end
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
#
|
68
|
+
# class ListsController < ApplicationController
|
69
|
+
# before_filter :authenticate, except: :public
|
70
|
+
#
|
71
|
+
# caches_page :public
|
72
|
+
#
|
73
|
+
# caches_action :index, if: Proc.new do
|
74
|
+
# !request.format.json? # cache if is not a JSON request
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# caches_action :show, cache_path: { project: 1 },
|
78
|
+
# expires_in: 1.hour
|
79
|
+
#
|
80
|
+
# caches_action :feed, cache_path: Proc.new do
|
81
|
+
# if params[:user_id]
|
82
|
+
# user_list_url(params[:user_id, params[:id])
|
83
|
+
# else
|
84
|
+
# list_url(params[:id])
|
85
|
+
# end
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# caches_action :posts, cache_path: CachePathCreator.new('posts')
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# If you pass <tt>layout: false</tt>, it will only cache your action
|
92
|
+
# content. That's useful when your layout has dynamic information.
|
93
|
+
#
|
94
|
+
# Warning: If the format of the request is determined by the Accept HTTP
|
95
|
+
# header the Content-Type of the cached response could be wrong because
|
96
|
+
# no information about the MIME type is stored in the cache key. So, if
|
97
|
+
# you first ask for MIME type M in the Accept header, a cache entry is
|
98
|
+
# created, and then perform a second request to the same resource asking
|
99
|
+
# for a different MIME type, you'd get the content cached for M.
|
100
|
+
#
|
101
|
+
# The <tt>:format</tt> parameter is taken into account though. The safest
|
102
|
+
# way to cache by MIME type is to pass the format in the route.
|
103
|
+
module Actions
|
104
|
+
extend ActiveSupport::Concern
|
105
|
+
|
106
|
+
module ClassMethods
|
107
|
+
# Declares that +actions+ should be cached.
|
108
|
+
# See ActionController::Caching::Actions for details.
|
109
|
+
def caches_action(*actions)
|
110
|
+
return unless cache_configured?
|
111
|
+
options = actions.extract_options!
|
112
|
+
options[:layout] = true unless options.key?(:layout)
|
113
|
+
filter_options = options.extract!(:if, :unless).merge(only: actions)
|
114
|
+
cache_options = options.extract!(:layout, :cache_path).merge(store_options: options)
|
115
|
+
|
116
|
+
around_filter ActionCacheFilter.new(cache_options), filter_options
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def _save_fragment(name, options)
|
121
|
+
content = ''
|
122
|
+
response_body.each do |parts|
|
123
|
+
content << parts
|
124
|
+
end
|
125
|
+
|
126
|
+
if caching_allowed?
|
127
|
+
write_fragment(name, content, options)
|
128
|
+
else
|
129
|
+
content
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def caching_allowed?
|
134
|
+
(request.get? || request.head?) && response.status == 200
|
135
|
+
end
|
136
|
+
|
137
|
+
protected
|
138
|
+
def expire_action(options = {})
|
139
|
+
return unless cache_configured?
|
140
|
+
|
141
|
+
if options.is_a?(Hash) && options[:action].is_a?(Array)
|
142
|
+
options[:action].each { |action| expire_action(options.merge(action: action)) }
|
143
|
+
else
|
144
|
+
expire_fragment(ActionCachePath.new(self, options, false).path)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class ActionCacheFilter # :nodoc:
|
149
|
+
def initialize(options, &block)
|
150
|
+
@cache_path, @store_options, @cache_layout =
|
151
|
+
options.values_at(:cache_path, :store_options, :layout)
|
152
|
+
end
|
153
|
+
|
154
|
+
def around(controller)
|
155
|
+
cache_layout = @cache_layout.respond_to?(:call) ? @cache_layout.call(controller) : @cache_layout
|
156
|
+
|
157
|
+
path_options = if @cache_path.is_a?(Proc)
|
158
|
+
controller.instance_exec(controller, &@cache_path)
|
159
|
+
elsif @cache_path.respond_to?(:call)
|
160
|
+
@cache_path.call(controller)
|
161
|
+
else
|
162
|
+
@cache_path
|
163
|
+
end
|
164
|
+
|
165
|
+
cache_path = ActionCachePath.new(controller, path_options || {})
|
166
|
+
|
167
|
+
body = controller.read_fragment(cache_path.path, @store_options)
|
168
|
+
|
169
|
+
unless body
|
170
|
+
controller.action_has_layout = false unless cache_layout
|
171
|
+
yield
|
172
|
+
controller.action_has_layout = true
|
173
|
+
body = controller._save_fragment(cache_path.path, @store_options)
|
174
|
+
end
|
175
|
+
|
176
|
+
body = controller.render_to_string(body: body, layout: true) unless cache_layout
|
177
|
+
|
178
|
+
controller.response_body = body
|
179
|
+
controller.content_type = Mime[cache_path.extension || :html]
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
class ActionCachePath
|
184
|
+
attr_reader :path, :extension
|
185
|
+
|
186
|
+
# If +infer_extension+ is +true+, the cache path extension is looked up from the request's
|
187
|
+
# path and format. This is desirable when reading and writing the cache, but not when
|
188
|
+
# expiring the cache - +expire_action+ should expire the same files regardless of the
|
189
|
+
# request format.
|
190
|
+
def initialize(controller, options = {}, infer_extension = true)
|
191
|
+
if infer_extension
|
192
|
+
@extension = controller.params[:format]
|
193
|
+
options.reverse_merge!(format: @extension) if options.is_a?(Hash)
|
194
|
+
end
|
195
|
+
|
196
|
+
path = controller.url_for(options).split('://', 2).last
|
197
|
+
@path = normalize!(path)
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
def normalize!(path)
|
202
|
+
ext = URI.parser.escape(extension) if extension
|
203
|
+
path << 'index' if path[-1] == ?/
|
204
|
+
path << ".#{ext}" if extension and !path.split('?', 2).first.ends_with?(".#{ext}")
|
205
|
+
URI.parser.unescape(path)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module PageletRails::Concerns::Cache
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
include ActionController::Caching::Actions
|
6
|
+
|
7
|
+
pagelet_options cache_defaults: {
|
8
|
+
expires_in: 5.seconds,
|
9
|
+
cache_path: {}
|
10
|
+
}
|
11
|
+
|
12
|
+
around_action :pagelet_cache
|
13
|
+
end
|
14
|
+
|
15
|
+
def pagelet_cache &block
|
16
|
+
# puts 'pagelet_cache'.blue
|
17
|
+
cache_enabled = pagelet_options.cache || pagelet_options.cache_path || pagelet_options.expires_in
|
18
|
+
|
19
|
+
if !cache_enabled
|
20
|
+
return yield
|
21
|
+
end
|
22
|
+
|
23
|
+
cache_defaults = (pagelet_options.cache_defaults || {}).to_h.symbolize_keys
|
24
|
+
store_options = cache_defaults.except(:cache_path)
|
25
|
+
store_options[:expires_in] = pagelet_options.expires_in if pagelet_options.expires_in
|
26
|
+
|
27
|
+
cache_path = pagelet_options.cache_path || cache_defaults[:cache_path]
|
28
|
+
|
29
|
+
cache_path = if cache_path.is_a?(Proc)
|
30
|
+
self.instance_exec(self, &cache_path)
|
31
|
+
elsif cache_path.respond_to?(:call)
|
32
|
+
cache_path.call(self)
|
33
|
+
elsif cache_path.is_a?(String)
|
34
|
+
{
|
35
|
+
custom: cache_path
|
36
|
+
}
|
37
|
+
else
|
38
|
+
cache_path
|
39
|
+
end
|
40
|
+
cache_path ||= {}
|
41
|
+
cache_path[:controller] = params[:controller]
|
42
|
+
cache_path[:action] = params[:action]
|
43
|
+
|
44
|
+
path_object = ActionController::Caching::Actions::ActionCachePath.new(self, cache_path)
|
45
|
+
has_cache = fragment_exist?(path_object.path, store_options)
|
46
|
+
pagelet_options has_cache: has_cache
|
47
|
+
|
48
|
+
|
49
|
+
if (pagelet_render_remotely? && has_cache) || !pagelet_render_remotely?
|
50
|
+
cache_options = {
|
51
|
+
layout: false,
|
52
|
+
store_options: store_options,
|
53
|
+
cache_path: cache_path
|
54
|
+
}
|
55
|
+
|
56
|
+
filter = ActionController::Caching::Actions::ActionCacheFilter.new(cache_options)
|
57
|
+
|
58
|
+
filter.around(self, &block)
|
59
|
+
|
60
|
+
else
|
61
|
+
yield
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|