hobo_rapid 1.4.0.pre6 → 1.4.0.pre7
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/app/helpers/hobo_cache_helper.rb +37 -0
- data/lib/hobo_rapid/previous_uri_filter.rb +10 -0
- data/lib/hobo_rapid.rb +3 -0
- data/taglibs/buttons/delete-button.dryml +7 -2
- data/taglibs/cache/cache.dryml +41 -0
- data/taglibs/cache/nested_cache.dryml +16 -29
- data/taglibs/cache/swept_cache.dryml +264 -0
- data/taglibs/html/a.dryml +1 -1
- data/taglibs/html/table.dryml +1 -1
- data/taglibs/inputs/after_submit.dryml +10 -4
- data/taglibs/inputs/input.dryml +3 -2
- data/taglibs/plus/filter_menu.dryml +14 -8
- data/taglibs/plus/search_filter.dryml +15 -0
- data/taglibs/plus/table_plus.dryml +1 -6
- metadata +8 -4
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.4.0.
|
1
|
+
1.4.0.pre7
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module HoboCacheHelper
|
2
|
+
def hobo_cache_key(namespace=:views, route_on=nil, query_params=nil, attributes=nil)
|
3
|
+
attributes ||= {}
|
4
|
+
|
5
|
+
if route_on == true
|
6
|
+
route_on = this
|
7
|
+
end
|
8
|
+
|
9
|
+
if route_on.is_a?(ActiveRecord::Base)
|
10
|
+
route_on = url_for(route_on)
|
11
|
+
end
|
12
|
+
|
13
|
+
if route_on
|
14
|
+
attributes.reverse_merge!(Rails.application.routes.recognize_path(route_on))
|
15
|
+
elsif params[:page_path]
|
16
|
+
# it's quite possible that our page was rendered by a different action, so normalize
|
17
|
+
attributes.reverse_merge!(Rails.application.routes.recognize_path(params[:page_path]))
|
18
|
+
end
|
19
|
+
|
20
|
+
key_attrs = attributes
|
21
|
+
key_attrs[:only_path] = false
|
22
|
+
comma_split(query_params || "").each do |qp|
|
23
|
+
key_attrs["#{qp}"] = params[qp] || ""
|
24
|
+
end
|
25
|
+
|
26
|
+
ActiveSupport::Cache.expand_cache_key(url_for(key_attrs).split('://').last, namespace)
|
27
|
+
end
|
28
|
+
|
29
|
+
def item_cache(*args, &block)
|
30
|
+
unless Rails.configuration.action_controller.perform_caching
|
31
|
+
yield if block_given?
|
32
|
+
else
|
33
|
+
key = hobo_cache_key(:item, *args)
|
34
|
+
Rails.cache.fetch(key, &block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/hobo_rapid.rb
CHANGED
@@ -47,7 +47,12 @@ All the standard ajax attributes *except the callbacks* are supported (see the m
|
|
47
47
|
in_place = !(this == @this && request.method.downcase == "get") if in_place.nil?
|
48
48
|
update_attrs, attributes = attributes.partition_hash(HoboRapidHelper::AJAX_UPDATE_ATTRS)
|
49
49
|
ajax_attrs, attributes = attributes.partition_hash(HoboRapidHelper::AJAX_ATTRS)
|
50
|
-
|
50
|
+
if image
|
51
|
+
attributes[:src] ||= image_path(image)
|
52
|
+
attributes[:type] ||= 'image'
|
53
|
+
else
|
54
|
+
attributes[:type] ||= 'submit'
|
55
|
+
end
|
51
56
|
label ||= t("hobo.actions.remove", :default=>"Remove")
|
52
57
|
confirm = t("hobo.messages.confirm", :default=>"Are you sure?") if confirm.nil?
|
53
58
|
ajax_attrs[:confirm] = confirm if confirm
|
@@ -69,7 +74,7 @@ All the standard ajax attributes *except the callbacks* are supported (see the m
|
|
69
74
|
%>
|
70
75
|
<if test="&url && can_delete?">
|
71
76
|
<form method="delete" action="&url" class="button_to" merge-attrs="&ajax_attrs" data-rapid="&data_rapid" data-rapid-context="&typed_id">
|
72
|
-
<input
|
77
|
+
<input value="&label" merge/>
|
73
78
|
</form>
|
74
79
|
</if>
|
75
80
|
</def>
|
data/taglibs/cache/cache.dryml
CHANGED
@@ -1 +1,42 @@
|
|
1
1
|
<!-- Tags for caching -->
|
2
|
+
|
3
|
+
|
4
|
+
<!-- `<cache>` is a simple fragment cache without the hierarchical dependency tracking used in `<swept-cache>` or `<nested-cache>`.
|
5
|
+
|
6
|
+
### Attributes
|
7
|
+
|
8
|
+
All extra attributes are used as cache keys.
|
9
|
+
|
10
|
+
`query-params`: A comma separated list of query (or post) parameters
|
11
|
+
used as non-hierarchical cache keys.
|
12
|
+
|
13
|
+
`route-on`: Rails fragment caching uses the the current route to build
|
14
|
+
its cache key. If you are caching an item that will be used in
|
15
|
+
multiple different actions, you can specify route-on to ensure that
|
16
|
+
the different actions can share the cache. You can pass either an
|
17
|
+
object or a path to route-on. If you pass an object, it is converted
|
18
|
+
to a path with `url_for`. If you specify route-on without a value,
|
19
|
+
`this` is used. An alternative to using `route-on` is to specify
|
20
|
+
`controller`, `action` and any other required path parameters
|
21
|
+
explicitly. For example route-on="&posts_path" is identical to
|
22
|
+
controller="posts" action="index". If you do not specify route-on or
|
23
|
+
controller, action, etc., `params[:page_path]` or the current action
|
24
|
+
is used.
|
25
|
+
|
26
|
+
-->
|
27
|
+
<def tag="cache" attrs="query-params,route-on"><%
|
28
|
+
unless Rails.configuration.action_controller.perform_caching
|
29
|
+
%><%= parameters.default %><%
|
30
|
+
else
|
31
|
+
key_key_s = hobo_cache_key(:views, route_on, query_params, attributes)
|
32
|
+
cache_content = Rails.cache.read key_key_s
|
33
|
+
unless cache_content.nil?
|
34
|
+
Rails.logger.debug "CACHE HIT #{key_key_s}"
|
35
|
+
%><%= raw cache_content %><%
|
36
|
+
else
|
37
|
+
Rails.logger.debug "CACHE MISS #{key_key_s}"
|
38
|
+
%><%= raw cache_content=parameters.default %><%
|
39
|
+
Rails.cache.write(key_key_s, cache_content)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
%></def>
|
@@ -59,7 +59,8 @@ replace with:
|
|
59
59
|
### Attributes
|
60
60
|
|
61
61
|
All extra attributes are used as non-hierarchical cache keys, so for
|
62
|
-
inner caches these should be constants
|
62
|
+
inner caches these should either be constants or be manually
|
63
|
+
propagated to the outer caches
|
63
64
|
|
64
65
|
`methods`: A comma separated list of methods or fields that can be
|
65
66
|
called on the context (aka this) to produce cache keys. Example:
|
@@ -76,7 +77,7 @@ its cache key. If you are caching an item that will be used in
|
|
76
77
|
multiple different actions, you can specify route-on to ensure that
|
77
78
|
the different actions can share the cache. You can pass either an
|
78
79
|
object or a path to route-on. If you pass an object, it is converted
|
79
|
-
to a path with `url_for`. If you specify route-on
|
80
|
+
to a path with `url_for`. If you specify route-on without a value,
|
80
81
|
`this` is used. An alternative to using `route-on` is to specify
|
81
82
|
`controller`, `action` and any other required path parameters
|
82
83
|
explicitly. For example route-on="&posts_path" is identical to
|
@@ -88,25 +89,13 @@ attribute.
|
|
88
89
|
|
89
90
|
### Hints
|
90
91
|
|
91
|
-
If you
|
92
|
-
you typically specify `updated_at` in the methods attribute.
|
92
|
+
If you have sweepers, you probably want `<swept-cache>` instead.
|
93
93
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
Rails.cache.delete_matched(/typed_id\=#{CGI.escape foo.typed_id}(&|$)/)
|
100
|
-
end
|
101
|
-
|
102
|
-
This will delete the fragment, along will all fragments that contain
|
103
|
-
that fragment.
|
104
|
-
|
105
|
-
Another thing to keep in mind is that any Hobo tag that can generate a
|
106
|
-
page refresh, such as filter-menu, table-plus or page-nav stores query
|
107
|
-
parameters such as search, sort & page so that these are not lost when
|
108
|
-
refreshed. So they will need to be added to the query-params for any
|
109
|
-
cache containing such tags.
|
94
|
+
Any Hobo tag that can generate a page refresh, such as filter-menu,
|
95
|
+
table-plus or page-nav stores query parameters such as search, sort &
|
96
|
+
page so that these are not lost when the tag is used to refresh the
|
97
|
+
page. These will need to be added to the query-params for any cache
|
98
|
+
containing such tags.
|
110
99
|
|
111
100
|
-->
|
112
101
|
<def tag="nested-cache" attrs="methods,query-params,route-on">
|
@@ -133,7 +122,7 @@ cache containing such tags.
|
|
133
122
|
end
|
134
123
|
|
135
124
|
comma_split(query_params).each do |qp|
|
136
|
-
key_key["@#{qp}"] = params[qp]
|
125
|
+
key_key["@#{qp}"] = params[qp] || ""
|
137
126
|
end
|
138
127
|
|
139
128
|
if route_on == true
|
@@ -146,10 +135,8 @@ cache containing such tags.
|
|
146
135
|
|
147
136
|
if route_on
|
148
137
|
attributes.reverse_merge!(Rails.application.routes.recognize_path(route_on))
|
149
|
-
|
150
|
-
|
151
|
-
# it's quite possible that our page was rendered by a different action, so normalize
|
152
|
-
if params[:page_path]
|
138
|
+
elsif params[:page_path]
|
139
|
+
# it's quite possible that our page was rendered by a different action, so normalize
|
153
140
|
attributes.reverse_merge!(Rails.application.routes.recognize_path(params[:page_path]))
|
154
141
|
end
|
155
142
|
|
@@ -247,7 +234,7 @@ cache containing such tags.
|
|
247
234
|
fail if scope.cache_paths_stack.length>0
|
248
235
|
end
|
249
236
|
else
|
250
|
-
# we have a parent cache, so it
|
237
|
+
# we have a parent cache, so it has set up the scope.
|
251
238
|
if form_field_path.nil? || form_field_path[0...scope.cache_path_root.length] != scope.cache_path_root
|
252
239
|
fail "nested caching error: form_field_path has been corrupted via with= or form"
|
253
240
|
end
|
@@ -303,9 +290,10 @@ cache containing such tags.
|
|
303
290
|
thunk2.call(k,v,cache_keys[k])
|
304
291
|
end
|
305
292
|
|
293
|
+
# if content_key_s has a value, it means that our original key is still valid, but we were invalidated by our children.
|
306
294
|
if content_key_s.nil?
|
307
295
|
if !have_children
|
308
|
-
# if we don't have any children, then we can store the content against key_key_s. We also store cache_keys in case we ever have a parent who needs to regenerate their keys. We can give
|
296
|
+
# if we don't have any children, then we can store the content against key_key_s. We also store cache_keys in case we ever have a parent who needs to regenerate their keys. We can then give them ours without regenerating.
|
309
297
|
Rails.logger.debug "CACHE: #{key_key_s} -> content + key #{cache_keys}"
|
310
298
|
Rails.cache.write(key_key_s, [cache_content, cache_keys])
|
311
299
|
content_key_s = ""
|
@@ -328,5 +316,4 @@ cache containing such tags.
|
|
328
316
|
|
329
317
|
end
|
330
318
|
end
|
331
|
-
|
332
|
-
</def>
|
319
|
+
%></def>
|
@@ -0,0 +1,264 @@
|
|
1
|
+
<!--
|
2
|
+
|
3
|
+
`<swept-cache>` is a fragment cache that stores context dependency information for itself and all contained inner swept-cache's. Dependencies are not checked if the cache is hit. This means that swept-cache should be considerably faster than `<nested-cache>`, but it does require that you create sweepers for your caches. These sweepers can use the stored dependency information to invalidate the appropriate fragments.
|
4
|
+
|
5
|
+
### Example
|
6
|
+
|
7
|
+
<def tag="card" for="Foo">
|
8
|
+
<swept-cache route-on suffix="card">
|
9
|
+
<card without-header>
|
10
|
+
<body:><view:body/></body>
|
11
|
+
</card>
|
12
|
+
</nested-cache>
|
13
|
+
</def>
|
14
|
+
|
15
|
+
<def tag="view" for="Bar">
|
16
|
+
<swept-cache route-on suffix="view">
|
17
|
+
<view:body/>
|
18
|
+
<swept-cache:foos route-on="&this_parent" suffix="collection">
|
19
|
+
<collection/>
|
20
|
+
<swept-cache>
|
21
|
+
</swept-cache>
|
22
|
+
</def>
|
23
|
+
|
24
|
+
class FooSweeper < ActionController::Caching::Sweeper
|
25
|
+
observe Foo
|
26
|
+
|
27
|
+
def after_create(foo)
|
28
|
+
expire_swept_caches_for(foo.bar, :foos)
|
29
|
+
end
|
30
|
+
|
31
|
+
def after_update(foo)
|
32
|
+
expire_swept_caches_for(foo)
|
33
|
+
expire_swept_caches_for(foo.bar, :foos)
|
34
|
+
end
|
35
|
+
|
36
|
+
def after_destroy(foo)
|
37
|
+
expire_swept_caches_for(foo)
|
38
|
+
expire_swept_caches_for(foo.bar, :foos)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class BarSweeper < ActionController::Caching::Sweeper
|
43
|
+
observe Bar
|
44
|
+
|
45
|
+
def after_update(bar)
|
46
|
+
expire_swept_caches_for(bar)
|
47
|
+
end
|
48
|
+
|
49
|
+
def after_destroy(bar)
|
50
|
+
expire_swept_caches_for(bar)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
In the above example, if a Foo gets updated, the following fragment caches will be invalidated:
|
55
|
+
|
56
|
+
- the card for the foo
|
57
|
+
- the collection of foos inside bar
|
58
|
+
- the bar view
|
59
|
+
- any pages that have a swept-cache that contains a view of bar
|
60
|
+
|
61
|
+
When outer caches are rebuilt, inner caches that are still valid may be used as is.
|
62
|
+
|
63
|
+
### Specifying the Context
|
64
|
+
|
65
|
+
swept-cache assumes that the cache is dependent on the current context
|
66
|
+
(aka this) as well as the context of any contained swept-cache's.
|
67
|
+
|
68
|
+
The context must be either an object that has been saved to the
|
69
|
+
database, or an attribute on an object that has been saved to the
|
70
|
+
database. If it is not one of these two, you must either switch the
|
71
|
+
context to something that is, or specify the dependencies manually.
|
72
|
+
|
73
|
+
When specifying the dependencies manually, you pass a list of database
|
74
|
+
objects, database objects plus an attribute name, and/or strings.
|
75
|
+
|
76
|
+
<swept-cache dependencies="&[this, [this, :comments], foo, :all_foos]"
|
77
|
+
|
78
|
+
Note that when dependencies are specified manually, `this` must be
|
79
|
+
added to the list, if so desired.
|
80
|
+
|
81
|
+
Also note that dependencies are not added to the cache key.
|
82
|
+
|
83
|
+
### Attributes
|
84
|
+
|
85
|
+
All extra attributes are used as non-hierarchical cache keys, so for
|
86
|
+
inner caches these should either be constants or be manually
|
87
|
+
propagated to the outer caches
|
88
|
+
|
89
|
+
`dependencies`: see above. Default is "&[this]"
|
90
|
+
|
91
|
+
`query-params`: A comma separated list of query (or post) parameters
|
92
|
+
used as non-hierarchical cache keys.
|
93
|
+
|
94
|
+
`route-on`: Rails fragment caching uses the the current route to build
|
95
|
+
its cache key. If you are caching an item that will be used in
|
96
|
+
multiple different actions, you can specify route-on to ensure that
|
97
|
+
the different actions can share the cache. You can pass either an
|
98
|
+
object or a path to route-on. If you pass an object, it is converted
|
99
|
+
to a path with `url_for`. If you specify route-on without a value,
|
100
|
+
`this` is used. An alternative to using `route-on` is to specify
|
101
|
+
`controller`, `action` and any other required path parameters
|
102
|
+
explicitly. For example route-on="&posts_path" is identical to
|
103
|
+
controller="posts" action="index". If you do not specify route-on or
|
104
|
+
controller, action, etc., `params[:page_path]` or the current action
|
105
|
+
is used. route-on is a non-hierarchical key.
|
106
|
+
|
107
|
+
### Hints
|
108
|
+
|
109
|
+
Any Hobo tag that can generate a page refresh, such as filter-menu,
|
110
|
+
table-plus or page-nav stores query parameters such as search, sort &
|
111
|
+
page so that these are not lost when the tag is used to refresh the
|
112
|
+
page. These will need to be added to the query-params for any cache
|
113
|
+
containing such tags.
|
114
|
+
|
115
|
+
### Example: Caching @current_user
|
116
|
+
|
117
|
+
This is the pattern we use to cache the page header:
|
118
|
+
|
119
|
+
<swept-cache suffix="header" current-user="&@current_user.id" dependencies="&[@current_user]">
|
120
|
+
|
121
|
+
Notice that @current_user is specified as a dependency in 2 different ways.
|
122
|
+
|
123
|
+
`current-user="&@current_user.id"` ensures that the current user's ID
|
124
|
+
is added to the cache key, ensuring that each user gets their own
|
125
|
+
header rather than having every user share the same header.
|
126
|
+
|
127
|
+
`dependencies="&[@current_user]` does not affect the cache key, only
|
128
|
+
the cache content. It enables the cache to be swept if any of the user
|
129
|
+
columns change, such as the user's name.
|
130
|
+
|
131
|
+
### Cache Store requirements
|
132
|
+
|
133
|
+
#### stability
|
134
|
+
|
135
|
+
For this tag to function correctly, the cache must not evict the
|
136
|
+
dependency information, so purely LRU caches such as MemoryStore may
|
137
|
+
not be used in production. The cache can evict fragments, though. For
|
138
|
+
this reason, you may configure two separate caches:
|
139
|
+
|
140
|
+
config.cache_store = :memory_store, {:size => 512.megabytes}
|
141
|
+
config.hobo.stable_cache_store = :file_store
|
142
|
+
|
143
|
+
Note that the dependency cache store does not have to be persistent,
|
144
|
+
it's OK to clear the dependency cache at the same time as the fragment
|
145
|
+
cache.
|
146
|
+
|
147
|
+
#### atomic updates
|
148
|
+
|
149
|
+
In production, swept-cache needs to be able to update a list
|
150
|
+
atomically. This is not an operation supported by the Rails cache API,
|
151
|
+
but it is supported by most non-trivial caches via one of several
|
152
|
+
mechanisms.
|
153
|
+
|
154
|
+
#### supported caches
|
155
|
+
|
156
|
+
memory_store: not compliant, but can be used for development if the size is set large enough to avoid evictions.
|
157
|
+
|
158
|
+
[file_store](http://api.rubyonrails.org/classes/ActiveSupport/Cache/FileStore.html): A good choice for low traffic sites where reads vastly outnumber writes.
|
159
|
+
|
160
|
+
memcached: not compliant
|
161
|
+
|
162
|
+
redis: a great choice. You can use the same instance for both fragment caching with expiry set the fragments to expire without disturbing the dependency information by setting the options differently:
|
163
|
+
|
164
|
+
config.cache_store = :redis_store, "redis://192.168.1.2:6379/1", :expires_in => 60.minutes
|
165
|
+
config.hobo.stable_cache_store = :redis_store, "redis://192.168.1.2:6379/1"
|
166
|
+
|
167
|
+
[torquebox infinispan](http://torquebox.org/): another great choice
|
168
|
+
|
169
|
+
config.cache_store = :torque_box_store
|
170
|
+
config.hobo.stable_cache_store = :torque_box_store, :name => 'dependencies', :mode => :replicated, :sync => true
|
171
|
+
|
172
|
+
others: ask on the hobo-users list.
|
173
|
+
|
174
|
+
-->
|
175
|
+
<def tag="swept-cache" attrs="query-params,route-on,dependencies"><%
|
176
|
+
unless Rails.configuration.action_controller.perform_caching
|
177
|
+
%><%= parameters.default %><%
|
178
|
+
else
|
179
|
+
key_key_s = hobo_cache_key(:views, route_on, query_params, attributes)
|
180
|
+
cache_content = Rails.cache.read key_key_s
|
181
|
+
unless cache_content.nil?
|
182
|
+
Rails.logger.debug "CACHE HIT #{key_key_s}"
|
183
|
+
|
184
|
+
cache_content, cache_ids = cache_content
|
185
|
+
|
186
|
+
if scope.cache_ids
|
187
|
+
# we have parent caches trying to generate their keys, so oblige them
|
188
|
+
scope.cache_ids += Set.new(cache_ids)
|
189
|
+
end
|
190
|
+
|
191
|
+
%><%= raw cache_content %><%
|
192
|
+
else
|
193
|
+
Rails.logger.debug "CACHE MISS #{key_key_s}"
|
194
|
+
# darn, cache is invalid. Now we have to generate our content and (re)generate our keys.
|
195
|
+
|
196
|
+
unless scope.cache_ids
|
197
|
+
# no parent caches so we need to set up the scope.
|
198
|
+
scope.new_scope(:cache_ids => Set.new, :cache_stack => []) do
|
199
|
+
%><%= cache_content=parameters.default %><%
|
200
|
+
cache_ids = scope.cache_ids
|
201
|
+
end
|
202
|
+
else
|
203
|
+
# we have a parent cache, so it has set up the scope.
|
204
|
+
scope.cache_stack.push scope.cache_ids
|
205
|
+
scope.cache_ids = Set.new
|
206
|
+
%><%= cache_content=parameters.default %><%
|
207
|
+
cache_ids = scope.cache_ids
|
208
|
+
end
|
209
|
+
|
210
|
+
dependencies = comma_split(dependencies) if dependencies.is_a?(String)
|
211
|
+
dependencies ||= [this]
|
212
|
+
dependencies.each do |dep|
|
213
|
+
if dep.respond_to?(:typed_id) && dep.typed_id
|
214
|
+
cache_ids << dep.typed_id
|
215
|
+
elsif dep.respond_to?(:origin) && dep.origin
|
216
|
+
cache_ids << "#{dep.origin.typed_id}:#{dep.origin_attribute}"
|
217
|
+
elsif dep.respond_to?(:to_sym)
|
218
|
+
cache_ids << dep.to_s
|
219
|
+
elsif dep.respond_to?(:first) && dep.first.respond_to?(:typed_id) && dep.first.typed_id && dep.last.respond_to?(:to_sym)
|
220
|
+
cache_ids << "#{dep.first.typed_id}:#{dep.last}"
|
221
|
+
else
|
222
|
+
fail "#{dep} not a Hobo model or not in database"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
if scope.cache_stack
|
227
|
+
scope.cache_ids += scope.cache_stack.pop
|
228
|
+
end
|
229
|
+
|
230
|
+
cache_ids.each do |cache_id|
|
231
|
+
# the database we're using must support atomically adding to a cache key
|
232
|
+
# there are several possible ways of doing so.
|
233
|
+
# transactions: supported by Infinispan and activerecord-cache
|
234
|
+
# sets: Redis has a set datatype (SADD & friends)
|
235
|
+
# regex read: munge the value onto the key and then store that instead. Then do a regex read to get all values
|
236
|
+
|
237
|
+
|
238
|
+
if Hobo.stable_cache.respond_to?(:transaction)
|
239
|
+
key = ActiveSupport::Cache.expand_cache_key(cache_id, :sweep_key)
|
240
|
+
Hobo.stable_cache.transaction do
|
241
|
+
l = Set.new(Hobo.stable_cache.read(key)) << key_key_s
|
242
|
+
Rails.logger.debug "CACHE SWEEP KEY: #{cache_id} #{l.to_a}"
|
243
|
+
Hobo.stable_cache.write(key, l.to_a)
|
244
|
+
end
|
245
|
+
elsif Hobo.stable_cache.respond_to?(:read_matched)
|
246
|
+
key = ActiveSupport::Cache.expand_cache_key([cache_id, key_key_s], :sweep_key)
|
247
|
+
Rails.logger.debug "CACHE SWEEP KEY: #{key}"
|
248
|
+
Hobo.stable_cache.write(key, nil)
|
249
|
+
else
|
250
|
+
# TODO: add support for Redis
|
251
|
+
key = ActiveSupport::Cache.expand_cache_key(cache_id, :sweep_key)
|
252
|
+
Rails.logger.warn "WARNING! cache transactions not supported please fix before going to production"
|
253
|
+
l = Set.new(Hobo.stable_cache.read(key)) << key_key_s
|
254
|
+
Rails.logger.debug "CACHE SWEEP KEY: #{cache_id} #{l.to_a}"
|
255
|
+
Hobo.stable_cache.write(key, l.to_a)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Also store cache_ids in case we ever have a parent who needs to regenerate their keys. We can give then them ours without regenerating.
|
260
|
+
Rails.logger.debug "CACHE: #{key_key_s} -> content + ids #{cache_ids.to_a}"
|
261
|
+
Rails.cache.write(key_key_s, [cache_content, cache_ids.to_a])
|
262
|
+
end
|
263
|
+
end
|
264
|
+
%></def>
|
data/taglibs/html/a.dryml
CHANGED
@@ -114,7 +114,7 @@ The standard AJAX attributes are supported.
|
|
114
114
|
nil_view
|
115
115
|
elsif action == "new"
|
116
116
|
# Link to a new object form
|
117
|
-
new_record = target.new
|
117
|
+
new_record = target.respond_to?(:build) ? target.build : target.new
|
118
118
|
new_record.set_creator(current_user)
|
119
119
|
href = object_url(target, "new", params._?.merge(:subsite => subsite))
|
120
120
|
|
data/taglibs/html/table.dryml
CHANGED
@@ -139,7 +139,7 @@ Produces:
|
|
139
139
|
</if>
|
140
140
|
<else>
|
141
141
|
<% field_tag ||= "view" %>
|
142
|
-
<unless test="&this.empty? && !empty">
|
142
|
+
<unless test="&(this.nil? || this.empty?) && !empty">
|
143
143
|
<%= element "table", attributes - attrs_for(:with_fields) do %>
|
144
144
|
<thead if="&all_parameters[:thead] || fields" param>
|
145
145
|
<tr param="field-heading-row">
|
@@ -10,19 +10,25 @@ Use the `stay-here` attribute to remain on the current page:
|
|
10
10
|
...
|
11
11
|
</form>
|
12
12
|
|
13
|
-
Use the `
|
13
|
+
Use the `uri` option to specify a redirect location:
|
14
14
|
|
15
15
|
<form>
|
16
|
-
<after-submit
|
16
|
+
<after-submit uri="/admin"/>
|
17
17
|
...
|
18
18
|
</form>
|
19
19
|
|
20
|
-
Use the `
|
20
|
+
Use the `go-back` option to return to the page in `session[:previous_uri]`:
|
21
21
|
|
22
22
|
<form>
|
23
|
-
<after-submit
|
23
|
+
<after-submit go-back/>
|
24
24
|
...
|
25
25
|
</form>
|
26
|
+
|
27
|
+
Note that session[:previous_uri] isn't automatically populated. To
|
28
|
+
automatically populate it, add to application_controller.rb:
|
29
|
+
|
30
|
+
after_filter HoboRapid::PreviousUriFilter
|
31
|
+
|
26
32
|
-->
|
27
33
|
<def tag="after-submit" attrs="uri, stay-here, go-back"><%
|
28
34
|
uri = "stay-here" if stay_here
|
data/taglibs/inputs/input.dryml
CHANGED
@@ -48,7 +48,8 @@ If the context is a `has_many :through` association, the polymorphic `<collectio
|
|
48
48
|
elsif no_edit_permission && no_edit == :skip
|
49
49
|
""
|
50
50
|
else
|
51
|
-
attrs =
|
51
|
+
attrs = attributes
|
52
|
+
attrs = add_classes(attrs, type_id.dasherize, type_and_field.dasherize) unless type_id.nil? || type_and_field.nil?
|
52
53
|
attrs[:name] ||= param_name_for_this
|
53
54
|
attrs[:disabled] = true if no_edit_permission && no_edit == :disable
|
54
55
|
the_input = if (refl = this_field_reflection)
|
@@ -66,7 +67,7 @@ If the context is a `has_many :through` association, the polymorphic `<collectio
|
|
66
67
|
(call_polymorphic_tag('input', HoboFields.to_class(this_type::COLUMN_TYPE), attrs) if defined?(this_type::COLUMN_TYPE)) or
|
67
68
|
raise Hobo::Error, ("No input tag for #{this_field}:#{this_type} (this=#{this.inspect})")
|
68
69
|
end
|
69
|
-
unless this_parent.errors[this_field].empty?
|
70
|
+
unless this_parent.nil? || this_parent.errors[this_field].empty?
|
70
71
|
"<span class='field-with-errors'>#{the_input}</span>".html_safe
|
71
72
|
else
|
72
73
|
the_input
|
@@ -22,9 +22,9 @@ See [Filtering stories by status](/tutorials/agility#filtering_stories_by_status
|
|
22
22
|
Standard AJAX attributes such as 'update' are supported. If any are supplied, filter-menu uses Ajax rather than a page refresh.
|
23
23
|
|
24
24
|
- `param-name` - the name of the HTTP parameter to use for the filter
|
25
|
-
- `options` - an array of options or an array of
|
26
|
-
It can be omitted if you provide the options as an array or array of
|
27
|
-
- `no-filter` - The text of the first option which indicates no filter is in effect. Defaults to 'All'
|
25
|
+
- `options` - an array of options or an array of pairs (useful for localized apps) for the menu.
|
26
|
+
It can be omitted if you provide the options as an array or array of pairs in the locale file.
|
27
|
+
- `no-filter` - The text of the first option which indicates no filter is in effect. Defaults to 'All'. Pass "&false" to disable the no-filter option
|
28
28
|
- `first-value` - the value to be used with the first option. Typically not used,
|
29
29
|
meaning the option has a blank value.
|
30
30
|
- model - the model name (optional: needed if you use the "activerecord.attributes" namespace.
|
@@ -85,11 +85,17 @@ or
|
|
85
85
|
</select>
|
86
86
|
|
87
87
|
-->
|
88
|
-
<def tag="filter-menu" attrs="model, param-name, options, no-filter,
|
89
|
-
<%
|
90
|
-
|
91
|
-
|
92
|
-
|
88
|
+
<def tag="filter-menu" attrs="model, param-name, options, no-filter, first-value">
|
89
|
+
<% # this odd construction is required in production mode for some reason
|
90
|
+
translated_options = I18n.translate("activerecord.attributes.#{model}.filter_menu.#{param_name}.options", :default=>[:"tags.filter_menu.#{param_name}.options"])
|
91
|
+
options = translated_options unless translated_options.is_a?(String)
|
92
|
+
raise ArgumentError, %(You must provide an "options" attribute, or set "activerecord.attributes.#{model}.filter_menu.#{param_name}.options" or "tags.filter_menu.#{param_name}.options" to an Array or to an Array of pairs in your locale file(s)) unless options.is_a?(Array)
|
93
|
+
if no_filter==false
|
94
|
+
no_filter = nil
|
95
|
+
else
|
96
|
+
no_filter = t("activerecord.attributes.#{model}.filter_menu.#{param_name}.no_filter", :default=>[:"tags.filter_menu.#{param_name}.no_filter", :"tags.filter_menu.default.no_filter", no_filter, "All"])
|
97
|
+
end
|
98
|
+
attributes['message'] ||= t("FIXME", :default => "Loading...") if HoboRapidHelper::AJAX_ATTRS.any?{|a| attributes.has_key?(a)}
|
93
99
|
%>
|
94
100
|
<form action="&request.path" method="get" class="filter-menu" merge-attrs="&attributes" data-rapid="&data_rapid('filter-menu')">
|
95
101
|
<div>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<def tag="search-filter">
|
2
|
+
<form param="search-form" method="get" action="" with="&nil" merge-attrs >
|
3
|
+
<hidden-fields for-query-string skip="page, search"/>
|
4
|
+
<span param="label"><t key="hobo.table_plus.search">Search</t></span>
|
5
|
+
<input class="search" type="search" name="search" value="¶ms[:search]"/>
|
6
|
+
<submit label="&t('hobo.table_plus.submit_label', :default=>'Go')" class="search-button" param="search-submit"/>
|
7
|
+
</form>
|
8
|
+
<if test="¶ms[:search]">
|
9
|
+
<form param="clear-form" method="get" action="" merge-attrs>
|
10
|
+
<hidden-fields for-query-string skip="page, search"/>
|
11
|
+
<input type="hidden" name="search" value=""/>
|
12
|
+
<submit label="Clear" class="search-button" param="clear-submit"/>
|
13
|
+
</form>
|
14
|
+
</if>
|
15
|
+
</def>
|
@@ -26,12 +26,7 @@ sort-columns: a hash that allow you to map fields to sortable columns. The def
|
|
26
26
|
<div class="table-plus" merge-attrs="&attributes - attrs_for(:with_fields) - attrs_for(:table)">
|
27
27
|
<div class="header" param="header">
|
28
28
|
<div class="search">
|
29
|
-
<
|
30
|
-
<hidden-fields for-query-string skip="page, search"/>
|
31
|
-
<span><t key="hobo.table_plus.search">Search</t></span>
|
32
|
-
<input class="search" type="search" name="search" value="¶ms[:search]"/>
|
33
|
-
<submit label="&t('hobo.table_plus.submit_label', :default=>'Go')" class="search-button" param="search-submit"/>
|
34
|
-
</form>
|
29
|
+
<search-filter merge-attrs="&ajax_attrs" param/>
|
35
30
|
</div>
|
36
31
|
</div>
|
37
32
|
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: hobo_rapid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease: 6
|
5
|
-
version: 1.4.0.
|
5
|
+
version: 1.4.0.pre7
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Tom Locke, Bryan Larsen
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2012-
|
13
|
+
date: 2012-07-05 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: hobo
|
@@ -20,7 +20,7 @@ dependencies:
|
|
20
20
|
requirements:
|
21
21
|
- - "="
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: 1.4.0.
|
23
|
+
version: 1.4.0.pre7
|
24
24
|
type: :runtime
|
25
25
|
version_requirements: *id001
|
26
26
|
description: The RAPID tag library for Hobo
|
@@ -35,9 +35,11 @@ files:
|
|
35
35
|
- README.markdown
|
36
36
|
- VERSION
|
37
37
|
- app/controllers/dev_controller.rb
|
38
|
+
- app/helpers/hobo_cache_helper.rb
|
38
39
|
- app/helpers/hobo_rapid_helper.rb
|
39
40
|
- hobo_rapid.gemspec
|
40
41
|
- lib/hobo_rapid.rb
|
42
|
+
- lib/hobo_rapid/previous_uri_filter.rb
|
41
43
|
- lib/hobo_rapid/railtie.rb
|
42
44
|
- taglibs/buttons/buttons.dryml
|
43
45
|
- taglibs/buttons/create_button.dryml
|
@@ -48,6 +50,7 @@ files:
|
|
48
50
|
- taglibs/buttons/update_button.dryml
|
49
51
|
- taglibs/cache/cache.dryml
|
50
52
|
- taglibs/cache/nested_cache.dryml
|
53
|
+
- taglibs/cache/swept_cache.dryml
|
51
54
|
- taglibs/cards/card.dryml
|
52
55
|
- taglibs/cards/cards.dryml
|
53
56
|
- taglibs/cards/search_card.dryml
|
@@ -122,6 +125,7 @@ files:
|
|
122
125
|
- taglibs/plus/gravatar.dryml
|
123
126
|
- taglibs/plus/live_search.dryml
|
124
127
|
- taglibs/plus/plus.dryml
|
128
|
+
- taglibs/plus/search_filter.dryml
|
125
129
|
- taglibs/plus/sortable_collection.dryml
|
126
130
|
- taglibs/plus/table_plus.dryml
|
127
131
|
- taglibs/views/a_or_an.dryml
|
@@ -165,7 +169,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
165
169
|
requirements: []
|
166
170
|
|
167
171
|
rubyforge_project: hobo
|
168
|
-
rubygems_version: 1.8.
|
172
|
+
rubygems_version: 1.8.21
|
169
173
|
signing_key:
|
170
174
|
specification_version: 3
|
171
175
|
summary: The RAPID tag library for Hobo
|