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 CHANGED
@@ -1 +1 @@
1
- 1.4.0.pre6
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
@@ -0,0 +1,10 @@
1
+ module HoboRapid
2
+ # this after_filter is useful for the after_submit tag
3
+ class PreviousUriFilter
4
+ def self.filter(controller)
5
+ if controller.request.get?
6
+ controller.session[:previous_uri] = controller.request.path
7
+ end
8
+ end
9
+ end
10
+ end
data/lib/hobo_rapid.rb CHANGED
@@ -1,3 +1,6 @@
1
+ ActiveSupport::Dependencies.autoload_paths |= [File.dirname(__FILE__)]
2
+ ActiveSupport::Dependencies.autoload_once_paths |= [File.dirname(__FILE__)]
3
+
1
4
  module HoboRapid
2
5
 
3
6
  VERSION = File.read(File.expand_path('../../VERSION', __FILE__)).strip
@@ -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
- attributes[src] = "#{base_url}/images/#{image}" if image
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 type="submit" value="&label" merge/>
77
+ <input value="&label" merge/>
73
78
  </form>
74
79
  </if>
75
80
  </def>
@@ -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 with a value,
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 are using an LRU cache such as memory-cache, memcached or redis
92
- you typically specify `updated_at` in the methods attribute.
92
+ If you have sweepers, you probably want `<swept-cache>` instead.
93
93
 
94
- If you are using a cache with manual expiration, `typed_id` is a
95
- convenient unique id that is available on all Hobo models that have
96
- been saved in the database. You can then add this to your FooSweeper:
97
-
98
- def after_update(foo)
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
- end
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's set up the scope.
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 then them ours without regenerating.
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
 
@@ -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 `go-back` option to return to the page in `session[:previous_uri]`:
13
+ Use the `uri` option to specify a redirect location:
14
14
 
15
15
  <form>
16
- <after-submit go-back/>
16
+ <after-submit uri="/admin"/>
17
17
  ...
18
18
  </form>
19
19
 
20
- Use the `uri` option to specify a redirect location:
20
+ Use the `go-back` option to return to the page in `session[:previous_uri]`:
21
21
 
22
22
  <form>
23
- <after-submit uri="/admin"/>
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
@@ -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 = add_classes(attributes, type_id.dasherize, type_and_field.dasherize)
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 arrays (useful for localized apps) for the menu.
26
- It can be omitted if you provide the options as an array or array of arrays in the locale file.
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, id, first-value">
89
- <% options = t("activerecord.attributes.#{model}.filter_menu.#{param_name}.options", :default=>[:"tags.filter_menu.#{param_name}.options", options])
90
- 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 Arrays
91
- in your locale file(s)) unless options.is_a?(Array)
92
- 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"])
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="&params[:search]"/>
6
+ <submit label="&t('hobo.table_plus.submit_label', :default=>'Go')" class="search-button" param="search-submit"/>
7
+ </form>
8
+ <if test="&params[: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
- <form param="search-form" method="get" action="" with="&nil" merge-attrs="&ajax_attrs" >
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="&params[: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.pre6
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-04-24 00:00:00 Z
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.pre6
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.17
172
+ rubygems_version: 1.8.21
169
173
  signing_key:
170
174
  specification_version: 3
171
175
  summary: The RAPID tag library for Hobo