hobo_rapid 1.4.0.pre6 → 1.4.0.pre7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|