hobo 0.7.2 → 0.7.3

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.
Files changed (77) hide show
  1. data/bin/hobo +24 -7
  2. data/hobo_files/plugin/CHANGES.txt +501 -0
  3. data/hobo_files/plugin/generators/hobo/hobo_generator.rb +8 -6
  4. data/hobo_files/plugin/generators/hobo/templates/application.dryml +3 -0
  5. data/hobo_files/plugin/generators/hobo/templates/dryml-support.js +132 -0
  6. data/hobo_files/plugin/generators/hobo_front_controller/hobo_front_controller_generator.rb +4 -5
  7. data/hobo_files/plugin/generators/hobo_model_resource/hobo_model_resource_generator.rb +75 -0
  8. data/hobo_files/plugin/generators/hobo_model_resource/templates/controller.rb +7 -0
  9. data/hobo_files/plugin/generators/hobo_model_resource/templates/functional_test.rb +8 -0
  10. data/hobo_files/plugin/generators/hobo_model_resource/templates/helper.rb +2 -0
  11. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo-rapid.js +30 -11
  12. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/clean/public/stylesheets/application.css +149 -92
  13. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/clean/public/stylesheets/rapid-ui.css +0 -48
  14. data/hobo_files/plugin/init.rb +45 -13
  15. data/hobo_files/plugin/lib/action_view_extensions/base.rb +4 -3
  16. data/hobo_files/plugin/lib/active_record/association_proxy.rb +18 -0
  17. data/hobo_files/plugin/lib/active_record/association_reflection.rb +5 -0
  18. data/hobo_files/plugin/lib/active_record/has_many_association.rb +7 -11
  19. data/hobo_files/plugin/lib/active_record/has_many_through_association.rb +8 -0
  20. data/hobo_files/plugin/lib/extensions/test_case.rb +1 -1
  21. data/hobo_files/plugin/lib/hobo.rb +38 -60
  22. data/hobo_files/plugin/lib/hobo/authentication_support.rb +1 -1
  23. data/hobo_files/plugin/lib/hobo/bundle.rb +131 -34
  24. data/hobo_files/plugin/lib/hobo/composite_model.rb +1 -1
  25. data/hobo_files/plugin/lib/hobo/controller.rb +7 -8
  26. data/hobo_files/plugin/lib/hobo/dev_controller.rb +21 -0
  27. data/hobo_files/plugin/lib/hobo/dryml/dryml_builder.rb +14 -8
  28. data/hobo_files/plugin/lib/hobo/dryml/dryml_support_controller.rb +13 -0
  29. data/hobo_files/plugin/lib/hobo/dryml/taglib.rb +6 -7
  30. data/hobo_files/plugin/lib/hobo/dryml/template.rb +207 -73
  31. data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +67 -55
  32. data/hobo_files/plugin/lib/hobo/dryml/template_handler.rb +53 -3
  33. data/hobo_files/plugin/lib/hobo/hobo_helper.rb +75 -107
  34. data/hobo_files/plugin/lib/hobo/model.rb +236 -429
  35. data/hobo_files/plugin/lib/hobo/model_controller.rb +277 -437
  36. data/hobo_files/plugin/lib/hobo/model_router.rb +62 -29
  37. data/hobo_files/plugin/lib/hobo/rapid_helper.rb +48 -9
  38. data/hobo_files/plugin/lib/hobo/scopes.rb +98 -0
  39. data/hobo_files/plugin/lib/hobo/scopes/association_proxy_extensions.rb +31 -0
  40. data/hobo_files/plugin/lib/hobo/scopes/automatic_scopes.rb +282 -0
  41. data/hobo_files/plugin/lib/hobo/scopes/defined_scope_proxy_extender.rb +88 -0
  42. data/hobo_files/plugin/lib/hobo/scopes/scope_reflection.rb +18 -0
  43. data/hobo_files/plugin/lib/hobo/scopes/scoped_proxy.rb +59 -0
  44. data/hobo_files/plugin/lib/hobo/undefined.rb +2 -0
  45. data/hobo_files/plugin/lib/hobo/user.rb +31 -14
  46. data/hobo_files/plugin/lib/hobo/user_controller.rb +41 -27
  47. data/hobo_files/plugin/taglibs/core.dryml +9 -11
  48. data/hobo_files/plugin/taglibs/rapid.dryml +51 -108
  49. data/hobo_files/plugin/taglibs/rapid_editing.dryml +25 -25
  50. data/hobo_files/plugin/taglibs/rapid_forms.dryml +111 -79
  51. data/hobo_files/plugin/taglibs/rapid_generics.dryml +74 -0
  52. data/hobo_files/plugin/taglibs/rapid_navigation.dryml +23 -21
  53. data/hobo_files/plugin/taglibs/rapid_pages.dryml +83 -169
  54. data/hobo_files/plugin/taglibs/rapid_plus.dryml +16 -2
  55. data/hobo_files/plugin/taglibs/rapid_support.dryml +3 -3
  56. data/hobo_files/plugin/taglibs/rapid_user_pages.dryml +104 -0
  57. metadata +60 -55
  58. data/hobo_files/plugin/generators/hobo_migration/hobo_migration_generator.rb +0 -276
  59. data/hobo_files/plugin/generators/hobo_migration/templates/migration.rb +0 -9
  60. data/hobo_files/plugin/lib/active_record/table_definition.rb +0 -34
  61. data/hobo_files/plugin/lib/extensions.rb +0 -375
  62. data/hobo_files/plugin/lib/hobo/email_address.rb +0 -12
  63. data/hobo_files/plugin/lib/hobo/enum_string.rb +0 -50
  64. data/hobo_files/plugin/lib/hobo/field_declaration_dsl.rb +0 -43
  65. data/hobo_files/plugin/lib/hobo/field_spec.rb +0 -68
  66. data/hobo_files/plugin/lib/hobo/html_string.rb +0 -7
  67. data/hobo_files/plugin/lib/hobo/lazy_hash.rb +0 -40
  68. data/hobo_files/plugin/lib/hobo/markdown_string.rb +0 -11
  69. data/hobo_files/plugin/lib/hobo/migrations.rb +0 -12
  70. data/hobo_files/plugin/lib/hobo/model_queries.rb +0 -117
  71. data/hobo_files/plugin/lib/hobo/password_string.rb +0 -7
  72. data/hobo_files/plugin/lib/hobo/percentage.rb +0 -14
  73. data/hobo_files/plugin/lib/hobo/predicate_dispatch.rb +0 -78
  74. data/hobo_files/plugin/lib/hobo/proc_binding.rb +0 -32
  75. data/hobo_files/plugin/lib/hobo/text.rb +0 -3
  76. data/hobo_files/plugin/lib/hobo/textile_string.rb +0 -25
  77. data/hobo_files/plugin/lib/hobo/where_fragment.rb +0 -28
@@ -8,6 +8,10 @@ module Hobo
8
8
 
9
9
  PAGINATE_FORMATS = [ Mime::HTML, Mime::ALL ]
10
10
 
11
+ READ_ONLY_ACTIONS = [:index, :show]
12
+ WRITE_ONLY_ACTIONS = [:create, :update, :destroy]
13
+ FORM_ACTIONS = [:new, :edit]
14
+
11
15
  class << self
12
16
 
13
17
  def included(base)
@@ -18,6 +22,13 @@ module Hobo
18
22
 
19
23
  helper_method :model, :current_user
20
24
  before_filter :set_no_cache_headers
25
+
26
+ rescue_from ActiveRecord::RecordNotFound, :with => :not_found
27
+
28
+ rescue_from Hobo::Model::PermissionDeniedError, :with => :permission_denied
29
+
30
+ alias_method_chain :render, :hobo_model
31
+
21
32
  end
22
33
  base.template_path_cache = {}
23
34
 
@@ -25,45 +36,29 @@ module Hobo
25
36
  end
26
37
 
27
38
  end
39
+
28
40
 
29
41
  module ClassMethods
30
42
 
31
43
  attr_writer :model
32
44
 
33
- attr_accessor :template_path_cache
34
-
35
- def add_collection_actions(name)
36
- defined_methods = instance_methods
37
-
38
- show_collection_method = "show_#{name}".to_sym
39
- if show_collection_method.not_in?(defined_methods) && include_action?(show_collection_method, true)
40
- define_method show_collection_method do
41
- hobo_show_collection(name)
42
- end
43
- end
44
-
45
- if Hobo.simple_has_many_association?(model.reflections[name])
46
- new_method = "new_#{name.to_s.singularize}"
47
- if new_method.not_in?(defined_methods) && include_action?(new_method, true)
48
- define_method new_method do
49
- hobo_new_in_collection(name)
50
- end
51
- end
52
- end
53
- end
54
-
45
+ attr_accessor :template_path_cache
46
+
55
47
  def web_methods
56
48
  @web_methods ||= superclass.respond_to?(:web_methods) ? superclass.web_methods : []
57
49
  end
58
50
 
51
+
59
52
  def show_actions
60
53
  @show_actions ||= superclass.respond_to?(:show_actions) ? superclass.show_actions : []
61
54
  end
62
55
 
56
+
63
57
  def index_actions
64
58
  @index_actions ||= superclass.respond_to?(:index_actions) ? superclass.index_actions : []
65
59
  end
66
60
 
61
+
67
62
  def collections
68
63
  # By default, all has_many associations are published
69
64
  @collections ||= if superclass.respond_to?(:collections)
@@ -73,72 +68,103 @@ module Hobo
73
68
  end
74
69
  end
75
70
 
71
+
76
72
  def model
77
73
  @model ||= name.sub(/Controller$/, "").singularize.constantize
78
74
  end
79
-
80
-
81
- def autocomplete_for(attr, options={}, &b)
75
+
76
+
77
+ def autocomplete_for(field, options={})
82
78
  options = options.reverse_merge(:limit => 15)
83
- options[:data_filters_block] = b
84
- @completers ||= HashWithIndifferentAccess.new
85
- @completers[attr.to_sym] = options
79
+ index_action "complete_#{field}" do
80
+ hobo_completions(model.limit(options[:limit]).send("#{field}_contains", params[:query]))
81
+ end
86
82
  end
87
83
 
88
84
 
89
- def autocompleter(name)
90
- (@completers && @completers[name]) ||
91
- (superclass.respond_to?(:autocompleter) && superclass.autocompleter(name))
92
- end
93
-
94
-
95
85
  def web_method(web_name, options={}, &block)
96
86
  web_methods << web_name.to_sym
97
- method = options[:method] || web_name
87
+ method = options.delete(:method) || web_name
98
88
  got_block = block_given?
99
89
  define_method web_name do
100
90
  # Make sure we have a copy of the options - it is being mutated somewhere
101
91
  opts = {}.merge(options)
102
92
  @this = find_instance(opts) unless opts[:no_find]
103
- set_status(Hobo.can_call?(current_user, @this, method) ? :valid : :not_allowed)
104
- # TODO - block should get to handle permission denied?
105
- if not_allowed?
106
- permission_denied
107
- elsif got_block
93
+ raise Hobo::Model::PermissionDeniedError unless Hobo.can_call?(current_user, @this, method)
94
+ if got_block
108
95
  instance_eval(&block)
109
96
  else
110
97
  @this.send(method)
111
98
  end
112
99
 
113
- hobo_ajax_response unless performed?
100
+ hobo_ajax_response || render(:nothing => true) unless performed?
114
101
  end
115
102
  end
116
103
 
117
104
 
118
105
  def auto_actions(*args)
119
- # auto_actions is either an array - the actions to provide, or a hash: { :except => [...] }
106
+ options = args.extract_options!
107
+
120
108
  @auto_actions = case args.first
121
- when :all then args.extract_options!
122
- when :none then []
109
+ when :all then available_auto_actions
110
+ when :write_only then available_auto_write_actions + args.rest
111
+ when :read_only then available_auto_read_actions + args.rest
123
112
  else args
124
113
  end
125
114
 
115
+ except = Array(options[:except])
116
+ except_actions = except.map do |arg|
117
+ if arg == :collections
118
+ available_auto_collection_actions
119
+ else
120
+ arg
121
+ end
122
+ end
123
+
124
+ @auto_actions -= except_actions
125
+
126
+ def_auto_actions
127
+ end
128
+
129
+
130
+ def def_auto_actions
126
131
  self.class_eval do
127
132
  def index; hobo_index end if include_action?(:index)
128
133
  def show; hobo_show end if include_action?(:show)
129
134
  def new; hobo_new end if include_action?(:new)
130
135
  def create; hobo_create end if include_action?(:create)
131
- def edit; hobo_edit end if include_action?(:edit)
136
+ def edit; hobo_show end if include_action?(:edit)
132
137
  def update; hobo_update end if include_action?(:update)
133
138
  def destroy; hobo_destroy end if include_action?(:destroy)
134
139
 
135
140
  def completions; hobo_completions end if include_action?(:completions)
136
-
137
- collections.each { |c| add_collection_actions(c.to_sym) }
138
141
  end
142
+
143
+ collections.each { |c| def_collection_actions(c.to_sym) }
139
144
  end
140
145
 
141
146
 
147
+ def def_collection_actions(name)
148
+ defined_methods = instance_methods
149
+
150
+ show_collection_method = name
151
+ if show_collection_method.not_in?(defined_methods) && include_action?(show_collection_method)
152
+ define_method show_collection_method do
153
+ hobo_show_collection(name)
154
+ end
155
+ end
156
+
157
+ if Hobo.simple_has_many_association?(model.reflections[name])
158
+ new_method = "new_#{name.to_s.singularize}"
159
+ if new_method.not_in?(defined_methods) && include_action?(new_method)
160
+ define_method new_method do
161
+ hobo_new_in_collection(name)
162
+ end
163
+ end
164
+ end
165
+ end
166
+
167
+
142
168
  def show_action(*names, &block)
143
169
  options = names.extract_options!
144
170
  show_actions.concat(names)
@@ -158,35 +184,43 @@ module Hobo
158
184
  if block
159
185
  define_method(name, &block)
160
186
  else
161
- define_method(name) { hobo_index options }
187
+ if scope = options.delete(:scope)
188
+ define_method(name) { hobo_index scope, options }
189
+ else
190
+ define_method(name) { hobo_index options }
191
+ end
162
192
  end
163
193
  end
164
194
  end
165
195
 
166
196
  def publish_collection(*names)
167
197
  collections.concat(names)
168
- names.each {|n| add_collection_actions(n)}
198
+ names.each {|n| def_collection_actions(n)}
169
199
  end
170
200
 
171
201
 
172
- def find_instance(id, options={})
173
- if model.id_name? and id !~ /^\d+$/
174
- model.find_by_id_name(id, options)
175
- else
176
- model.find(id, options)
177
- end
202
+ def include_action?(name)
203
+ name.to_sym.in?(@auto_actions)
178
204
  end
179
-
180
- def include_action?(name, collection_action=false)
181
- name = name.to_sym
182
- if @auto_actions.is_a?(Array)
183
- # White list
184
- name.in?(@auto_actions) || (collection_action && :collections.in?(@auto_actions))
185
- else
186
- # Black list
187
- except = Array(@auto_actions[:except])
188
- return !(name.in?(except) || (collection_action && :collections.in?(except)))
189
- end
205
+
206
+
207
+ def available_auto_actions
208
+ READ_ONLY_ACTIONS + WRITE_ONLY_ACTIONS + FORM_ACTIONS + available_auto_collection_actions
209
+ end
210
+
211
+
212
+ def available_auto_read_actions
213
+ READ_ONLY_ACTIONS + collections
214
+ end
215
+
216
+
217
+ def available_auto_write_actions
218
+ WRITE_ONLY_ACTIONS
219
+ end
220
+
221
+
222
+ def available_auto_collection_actions
223
+ collections + collections.map {|c| "new_#{c.to_s.singularize}".to_sym}
190
224
  end
191
225
 
192
226
  end
@@ -194,468 +228,295 @@ module Hobo
194
228
 
195
229
  protected
196
230
 
197
- def data_filter(name, &b)
198
- @data_filters ||= HashWithIndifferentAccess.new
199
- @data_filters[name] = b
200
- end
201
231
 
202
- def search(*args)
203
- if args.first.is_a?(Class)
204
- model, search_string, *columns = args
205
- else
206
- model = self.model
207
- search_string, *columns = args
208
- end
209
- return nil if search_string.blank?
210
-
211
- model.conditions do
212
- words = search_string.split
213
- terms = words.map do |word|
214
- cols = columns.map do |c|
215
- if c.is_a?(Symbol)
216
- send("#{c}_contains", word)
217
- elsif c.is_a?(Hash)
218
- c.map do |k, v|
219
- related = send(k)
220
- v = [v] unless v.is_a?(Array)
221
- v.map { |related_col| related.send("#{related_col}_contains", word) }
222
- end
223
- end
224
- end.flatten
225
- any?(*cols)
226
- end
227
- all?(*terms)
232
+ def filter_by(*args)
233
+ filters = args.extract_options!
234
+ finder = args.first || self.model
235
+
236
+ filters.each_pair do |scope, arg|
237
+ dont_filter = arg.is_a?(Array) ? arg.compact.empty? : arg.nil?
238
+ finder = finder.send(scope, arg) unless dont_filter
228
239
  end
240
+ finder
229
241
  end
230
242
 
231
- attr_accessor :data_filters
232
243
 
233
-
234
- # --- Action implementation helpers --- #
235
-
236
- def find_instance(*args)
237
- options = args.extract_options!
238
- res = self.class.find_instance(args.first || params[:id], options)
239
- instance_variable_set("@#{model.name.underscore}", res)
240
- res
241
- end
242
-
243
-
244
- def set_named_this!
245
- instance_variable_set("@#{@this.class.name.underscore}", @this)
246
- end
247
-
248
-
249
- def response_block(&b)
250
- if b
251
- if b.arity == 1
252
- respond_to {|wants| yield(wants) }
253
- else
254
- yield
255
- end
256
- performed?
257
- end
258
- end
259
-
260
-
261
- def paginated_find(*args, &b)
262
- options = args.extract_options!
263
- filter_conditions = data_filter_conditions
264
- conditions_proc = if b && filter_conditions
265
- proc { block(b) & block(filter_conditions) }
266
- else
267
- b || filter_conditions
268
- end
244
+ def sort_fields(*args)
245
+ finder = args.first.is_a?(Class) ? args.shift : model
269
246
 
270
- @association = options.delete(:association) ||
271
- if args.length == 1
272
- scopes = args.first
273
- @association = scopes.to_s.split(".").inject(model) { |m, name| m.send(name) }
274
- elsif args.length == 2
275
- owner, collection_name = args
276
- @association = collection_name.to_s.split(".").inject(owner) { |m, name| m.send(name) }
277
- end
278
- @reflection = @association.proxy_reflection if @association._?.respond_to?(:proxy_reflection)
279
-
280
- model_or_assoc, @member_class = if @association
281
- [@association, @association.member_class]
282
- else
283
- [model, model]
284
- end
285
-
286
- page_size = options.delete(:page_size) || 20
287
- page = options.delete(:page) || params[:page]
288
-
289
- paginate = options.fetch(:paginate, request.format.in?(PAGINATE_FORMATS))
290
- options.delete(:paginate)
291
- if paginate
292
- total_number = options.delete(:total_number) ||
293
- begin
294
- # If there is a conditions block, it may depend on the includes
295
- count_options = conditions_proc ? { :include => options[:include] } : {}
296
- model_or_assoc.count(count_options, &conditions_proc)
297
- end
298
-
299
- @pages = ::ActionController::Pagination::Paginator.new(self, total_number, page_size, page)
247
+ _, desc, field = *params[:sort]._?.match(/^(-)?([a-z_]+(?:\.[a-z_]+)?)$/)
300
248
 
301
- options = options.reverse_merge(:limit => @pages.items_per_page,
302
- :offset => @pages.current.offset)
303
- end
304
-
305
- unless options.has_key?(:order)
306
- _, desc, field = *params[:sort]._?.match(/^(-)?([a-z_]+(?:\.[a-z_]+)?)$/)
307
- if field
249
+ if field
250
+ fields = args.*.to_s
251
+ if field.in?(fields)
308
252
  @sort_field = field
309
253
  @sort_direction = desc ? "desc" : "asc"
310
-
254
+
311
255
  table, column = if field =~ /^(.*)\.(.*)$/
312
256
  [$1.camelize.constantize.table_name, $2]
313
257
  else
314
- [@member_class.table_name, field]
315
- end
316
- options[:order] = "#{table}.#{column} #{@sort_direction}"
317
- elsif !@association
318
- options[:order] = :default
258
+ [finder.table_name, field]
259
+ end
260
+ finder = finder.order("#{table}.#{column}", @sort_direction)
319
261
  end
320
262
  end
321
-
322
- model_or_assoc.find(:all, options, &conditions_proc)
263
+ finder
323
264
  end
324
-
325
265
 
326
- def find_instance_or_not_found(this=nil)
327
- begin
328
- this || find_instance
329
- rescue ActiveRecord::RecordNotFound
330
- not_found
331
- false
332
- end
333
- end
334
266
 
335
- def save_and_set_status!(record, original=nil)
336
- can = if record.new_record?
337
- Hobo.can_create?(current_user, record)
338
- else
339
- Hobo.can_update?(current_user, original, record)
340
- end
341
- status = if can
342
- record.save ? :valid : :invalid
343
- else
344
- :not_allowed
345
- end
346
- set_status(status)
347
- end
267
+ # --- Action implementation helpers --- #
268
+
348
269
 
349
- def set_status(status)
350
- @status = status
270
+ def find_instance(*args)
271
+ options = args.extract_options!
272
+ id = args.first || params[:id]
273
+ model.user_find(current_user, id, options)
351
274
  end
352
-
353
- def invalid?; @status == :invalid; end
354
275
 
355
- def valid?; @status == :valid; end
356
-
357
- def not_allowed?; @status == :not_allowed; end
358
276
 
277
+ def invalid?; !valid?; end
278
+
279
+
280
+ def valid?; this.errors.empty?; end
281
+
359
282
 
360
283
  def re_render_form(default_action)
361
284
  if params[:page_path]
362
285
  controller, view = Controller.controller_and_view_for(params[:page_path])
363
286
  view = default_action if view == Dryml::EMPTY_PAGE
364
- hobo_render(view, model_for(controller))
287
+ render :action => view, :controller => controller
365
288
  else
366
- hobo_render(default_action)
289
+ render :action => default_action
367
290
  end
368
291
  end
369
292
 
370
293
 
371
- def model_for(controller_name)
372
- "#{controller_name.camelize}Controller".constantize.model
373
- end
374
-
375
-
376
- def destination_after_create(record)
294
+ def destination_after_submit(record=nil)
295
+ record ||= this
296
+
377
297
  # The after_submit post parameter takes priority
378
298
  params[:after_submit] ||
379
299
 
380
- # Then try the records show page
381
- object_url(@this, :if_available => true) ||
300
+ # Then try the record's show page
301
+ object_url(@this) ||
382
302
 
383
303
  # Then the show page of the 'owning' object if there is one
384
- (@this.dependent_on.first && object_url(@this.dependent_on.first, :if_available => true)) ||
304
+ (@this.class.default_dependent_on && object_url(@this.class.default_dependent_on)) ||
385
305
 
386
306
  # Last try - the index page for this model
387
- object_url(@this.class, :if_available => true) ||
307
+ object_url(@this.class) ||
388
308
 
389
309
  # Give up
390
310
  home_page
391
311
  end
392
312
 
393
313
 
314
+ def response_block(&b)
315
+ if b
316
+ if b.arity == 1
317
+ respond_to {|wants| yield(wants) }
318
+ else
319
+ yield
320
+ end
321
+ performed?
322
+ end
323
+ end
324
+
325
+ def find_or_paginate(finder, options)
326
+ do_pagination = options.delete(:paginate) != false && request.format.in?(PAGINATE_FORMATS)
327
+ if do_pagination && !finder.respond_to?(:paginate)
328
+ do_pagination = false
329
+ logger.warn "Hobo::ModelController: Pagination is not available. To enable, please install will_paginate or a duck-type compatible paginator"
330
+ end
331
+
332
+ if do_pagination
333
+ finder.paginate(options.reverse_merge(:page => params[:page] || 1))
334
+ else
335
+ finder.all(options)
336
+ end
337
+ end
338
+
339
+
394
340
  # --- Action implementations --- #
395
341
 
396
342
  def hobo_index(*args, &b)
397
343
  options = args.extract_options!
398
- options = LazyHash.new(options)
399
- @model = model
400
- collection = args.first
401
- @this = if collection.blank?
402
- paginated_find(options)
403
- elsif collection.is_a?(String, Symbol)
404
- paginated_find(collection, options) # a scope name
405
- else
406
- collection
407
- end
408
- instance_variable_set("@#{@model.name.pluralize.underscore}", @this)
409
- response_block(&b) or hobo_render
344
+ finder = args.first || model
345
+ self.this = find_or_paginate(finder, options)
346
+ response_block(&b)
410
347
  end
411
348
 
412
349
 
413
350
  def hobo_show(*args, &b)
414
351
  options = args.extract_options!
415
- options = LazyHash.new(options)
416
-
417
- @this = find_instance_or_not_found(args.first) and
418
- begin
419
- set_status(:not_allowed) unless Hobo.can_view?(current_user, @this)
420
- set_named_this!
421
- response_block(&b) or
422
- if not_allowed?
423
- permission_denied
424
- else
425
- hobo_render
426
- end
427
- end
352
+ self.this = find_instance(options)
353
+ response_block(&b)
428
354
  end
429
-
430
-
431
- def hobo_new(*args, &b)
432
- options = args.extract_options!
433
- options = LazyHash.new(options)
434
- @this = args.first || model.new
435
- @this.set_creator(current_user) if options.fetch(:set_creator, true)
436
-
437
- set_status(:not_allowed) unless Hobo.can_create?(current_user, @this)
438
- set_named_this!
439
- response_block(&b) or
440
- if not_allowed?
441
- permission_denied
442
- else
443
- hobo_render
444
- end
355
+
356
+
357
+ def hobo_new(new_record=nil, &b)
358
+ self.this = new_record || model.new
359
+ this.user_changes!(current_user) # set_creator and permission check
360
+ response_block(&b)
445
361
  end
446
362
 
447
-
363
+
448
364
  def hobo_create(*args, &b)
449
365
  options = args.extract_options!
450
- options = LazyHash.new(options)
451
-
452
- @this = args.first ||
453
- begin
454
- create_model = if 'type'.in?(model.column_names) &&
455
- (type_attr = params['type']) &&
456
- type_attr.in?(model.send(:subclasses).every(:name))
457
- type_attr.constantize
458
- else
459
- model
460
- end
461
- create_model.new(params[model.name.underscore])
462
- end
463
- @this.set_creator(current_user) if options.fetch(:set_creator, true)
464
- save_and_set_status!(@this)
465
- set_named_this!
466
-
467
- flash[:notice] = "The #{model.name.titleize.downcase} was created successfully" if !request.xhr? && valid?
366
+ self.this = args.first || new_for_create
367
+ this.user_save_changes(current_user, options[:attributes] || attribute_parameters)
368
+ create_response(&b)
369
+ end
370
+
371
+
372
+ def attribute_parameters
373
+ params[this.class.name.underscore]
374
+ end
375
+
376
+
377
+ def new_for_create
378
+ if model.has_inheritance_column? && (type_attr = params['type']) && type_attr.in?(model.send(:subclasses).*.name)
379
+ type_attr.constantize
380
+ else
381
+ model
382
+ end.new
383
+ end
384
+
385
+
386
+ def create_response(&b)
387
+ flash[:notice] = "The #{@this.class.name.titleize.downcase} was created successfully" if !request.xhr? && valid?
468
388
 
469
389
  response_block(&b) or
470
390
  if valid?
471
391
  respond_to do |wants|
472
- wants.html { redirect_to(destination_after_create(@this)) }
392
+ wants.html { redirect_to destination_after_submit }
473
393
  wants.js { hobo_ajax_response || render(:text => "") }
474
394
  end
475
- elsif invalid?
395
+ else
476
396
  respond_to do |wants|
477
397
  wants.html { re_render_form(:new) }
478
398
  wants.js { render(:status => 500,
479
- :text => ("There was a problem creating that #{create_model.name}.\n" +
480
- @this.errors.full_messages.join("\n"))) }
399
+ :text => ("Couldn't create the #{this.class.name.titleize.downcase}.\n" +
400
+ this.errors.full_messages.join("\n"))) }
481
401
  end
482
- elsif not_allowed?
483
- permission_denied
484
402
  end
485
403
  end
486
404
 
487
405
 
488
- def hobo_edit(*args, &b)
489
- hobo_show(*args, &b)
490
- end
491
-
492
-
493
406
  def hobo_update(*args, &b)
494
407
  options = args.extract_options!
495
- options = LazyHash.new(options)
496
408
 
497
- @this = find_instance_or_not_found(args.first) or return
498
- original = @this.duplicate
499
- # 'duplicate' can cause these to be set, but they can conflict
500
- # with the changes so we clear them
501
- @this.send(:clear_aggregation_cache)
502
- @this.send(:clear_association_cache)
503
-
504
- changes = params[@this.class.name.underscore]
505
- @this.attributes = changes
506
- save_and_set_status!(@this, original)
409
+ self.this ||= args.first || find_instance
410
+ changes = options[:attributes] || attribute_parameters
411
+ this.user_save_changes(current_user, changes)
507
412
 
508
413
  # Ensure current_user isn't out of date
509
414
  @current_user = @this if @this == current_user
510
415
 
511
- flash[:notice] = "Changes to the #{@this.class.name.titleize.downcase} were saved" if !request.xhr? && valid?
416
+ in_place_edit_field = changes.keys.first if changes.size == 1 && params[:render]
417
+ update_response(in_place_edit_field, &b)
418
+ end
419
+
420
+
421
+ def update_response(in_place_edit_field=nil, &b)
422
+ flash[:notice] = "Changes to the #{@this.class.name.humanize.downcase} were saved" if !request.xhr? && valid?
512
423
 
513
- set_named_this!
514
424
  response_block(&b) or
515
425
  if valid?
516
426
  respond_to do |wants|
517
427
  wants.html do
518
- redirect_to(params[:after_submit] || object_url(@this))
428
+ redirect_to destination_after_submit
519
429
  end
520
430
  wants.js do
521
- if changes.size == 1 && params[:render]
431
+ if in_place_edit_field
522
432
  # Decreasingly hacky support for the scriptaculous in-place-editor
523
- new_val = Hobo::Dryml.render_tag(@template, "view",
524
- :with => @this, :field => changes.keys.first,
525
- :no_wrapper => true)
526
- hobo_ajax_response(@this, :new_field_value => new_val)
433
+ new_val = Hobo::Dryml.render_tag(@template, "view", :field => in_place_edit_field, :no_wrapper => true)
434
+ hobo_ajax_response(this, :new_field_value => new_val)
527
435
  else
528
- hobo_ajax_response(@this)
436
+ hobo_ajax_response(this)
529
437
  end
530
438
 
531
439
  # Maybe no ajax requests were made
532
440
  render :nothing => true unless performed?
533
441
  end
534
442
  end
535
- elsif invalid?
443
+ else
536
444
  respond_to do |wants|
537
445
  wants.html { re_render_form(:edit) }
538
446
  wants.js { render(:status => 500,
539
447
  :text => ("There was a problem with that change.\n" +
540
448
  @this.errors.full_messages.join("\n"))) }
541
449
  end
542
- elsif not_allowed?
543
- permission_denied
544
450
  end
545
451
  end
546
452
 
547
453
 
548
454
  def hobo_destroy(*args, &b)
549
455
  options = args.extract_options!
550
- options = LazyHash.new(options)
551
- @this = find_instance_or_not_found(args.first) or return
552
-
553
- set_named_this!
554
-
555
- set_status(Hobo.can_delete?(current_user, @this) ? :valid : :not_allowed)
556
- unless not_allowed?
557
- @this.destroy
558
- flash[:notice] = "The #{model.name.titleize.downcase} was deleted" unless request.xhr?
559
- end
560
-
456
+ self.this ||= args.first || find_instance
457
+ this.user_destroy(current_user)
458
+ flash[:notice] = "The #{model.name.titleize.downcase} was deleted" unless request.xhr?
459
+ destroy_response(&b)
460
+ end
461
+
462
+
463
+ def destroy_response(&b)
561
464
  response_block(&b) or
562
- if not_allowed?
563
- permission_denied
564
- else
565
- respond_to do |wants|
566
- wants.html { redirect_to(:action => "index") }
567
- wants.js { hobo_ajax_response || render(:text => "") }
568
- end
465
+ respond_to do |wants|
466
+ wants.html { redirect_to(:action => "index") }
467
+ wants.js { hobo_ajax_response || render(:text => "") }
569
468
  end
570
469
  end
571
-
470
+
572
471
 
573
- def hobo_show_collection(collection, options={}, &b)
574
- options = LazyHash.new(options)
575
-
576
- @owner = find_instance_or_not_found(options[:owner]) or return
577
-
578
- if collection.is_a?(Array)
579
- @this = collection
580
- @reflection = collection.proxy_reflection if collection.respond_to?(:proxy_reflection)
581
- else
582
- toplevel_collection = collection.to_s.split(".").first
583
- set_status(:not_allowed) unless Hobo.can_view?(current_user, @owner, toplevel_collection)
584
- @this = paginated_find(@owner, collection, options) unless not_allowed?
472
+ def hobo_show_collection(association, *args, &b)
473
+ options = args.extract_options!
474
+ association = find_instance.send(association) if association.is_a?(String, Symbol)
475
+ if association.respond_to?(:origin)
476
+ association.origin.user_view(current_user, association.origin_attribute) # permission check
585
477
  end
586
-
587
- response_block(&b) or
588
- if not_allowed?
589
- permission_denied
590
- else
591
- hobo_render(params[:action]) or (@reflection and hobo_render(:show_collection, @reflection.klass))
592
- end
478
+ self.this = find_or_paginate(association, options)
479
+ dryml_fallback_tag("show_collection_page")
480
+ response_block(&b)
593
481
  end
594
482
 
595
483
 
596
- def hobo_new_in_collection(collection, *args, &b)
484
+ # TODO: This action needs some more tidying up
485
+ def hobo_new_in_collection(association, *args, &b)
597
486
  options = args.extract_options!
598
- this = args.first
599
- options = LazyHash.new(options)
600
-
601
- @owner = find_instance_or_not_found(options[:owner]) or return
602
- @association = collection.is_a?(Array) ? collection : @owner.send(collection)
603
- @this = this || @association.new
604
- set_named_this!
605
- @this.set_creator(current_user) if options.fetch(:set_creator, true)
606
-
607
- set_status(:not_allowed) unless Hobo.can_create?(current_user, @this)
608
-
609
- response_block(&b) or
610
- if not_allowed?
611
- permission_denied
612
- else
613
- hobo_render("new_#{collection.to_s.singularize}") or hobo_render("new_in_collection", @this.class)
614
- end
487
+ @association = association.is_a?(String, Symbol) ? find_instance.send(association) : association
488
+ self.this = args.first || @association.new
489
+ this.user_changes(current_user) # set_creator and permission check
490
+ dryml_fallback_tag("new_in_collection_page")
491
+ response_block(&b)
615
492
  end
616
493
 
617
494
 
618
- def hobo_completions
619
- opts = self.class.autocompleter(params[:for])
620
- if opts
621
- # Eval any defined filters
622
- instance_eval(&opts[:data_filters_block]) if opts[:data_filters_block]
623
- conditions = data_filter_conditions
624
- q = params[:query]
625
- items = model.find(:all) { all?(send("#{attr}_contains", q), conditions && block(conditions)) }
626
-
627
- render :text => "<ul>\n" + items.map {|i| "<li>#{i.send(attr)}</li>\n"}.join + "</ul>"
628
- else
629
- render :text => "No completer for #{attr}", :status => 404
630
- end
495
+ def hobo_completions(finder)
496
+ items = finder.find(:all)
497
+ render :text => "<ul>\n" + items.map {|i| "<li>#{i.send(attr)}</li>\n"}.join + "</ul>"
631
498
  end
632
499
 
633
- #def hobo_create_in_collection(collection, options={}, &b)
634
- # hobo_create do
635
- # hobo_new_in_collection(collection, :this => @this, &b)
636
- # end
637
- #end
638
500
 
639
501
 
640
502
  # --- Response helpers --- #
641
503
 
642
504
 
643
- def permission_denied(options={})
505
+ def permission_denied(error)
644
506
  if respond_to? :permission_denied_response
645
507
  permission_denied_response
646
- elsif render_tag("permission-denied-page", { :with => @this }, :status => 403)
647
- # cool
508
+ elsif render_tag("permission-denied-page", { }, :status => 403)
509
+ # job done
648
510
  else
649
- message = options[:message] || "Permission Denied"
650
- render :text => message, :status => 403
511
+ render :text => "Permission Denied", :status => 403
651
512
  end
652
513
  end
653
514
 
654
515
 
655
- def not_found
516
+ def not_found(error)
656
517
  if respond_to? :not_found_response
657
518
  not_found_response
658
- elsif render_tag("not-found-page", { :with => @this }, :status => 404)
519
+ elsif render_tag("not-found-page", {}, :status => 404)
659
520
  # cool
660
521
  else
661
522
  render(:text => "The page you requested cannot be found.", :status => 404)
@@ -663,46 +524,34 @@ module Hobo
663
524
  end
664
525
 
665
526
 
666
- def hobo_template_exists?(dir, name)
667
- self.class.template_path_cache.clear if RAILS_ENV == "development"
668
- self.class.template_path_cache.fetch([dir, name],
669
- begin
670
- full_dir = "#{RAILS_ROOT}/app/views/#{dir}"
671
- !Dir["#{full_dir}/#{name}.*"].empty?
672
- end)
527
+ def this
528
+ @this ||= (instance_variable_get("@#{model.name.underscore}") ||
529
+ instance_variable_get("@#{model.name.underscore.pluralize}"))
673
530
  end
674
-
675
531
 
676
- def find_model_template(klass, name)
677
- while klass and klass != ActiveRecord::Base
678
- dir = klass.name.underscore.pluralize
679
- dir = File.join(subsite, dir) if subsite
680
- if hobo_template_exists?(dir, name)
681
- return "#{dir}/#{name}"
682
- end
683
- klass = klass.superclass
684
- end
685
- nil
532
+
533
+ def this=(object)
534
+ ivar = if object.is_a?(Array)
535
+ (object.try.member_class || model).name.underscore.pluralize
536
+ else
537
+ model.name.underscore
538
+ end
539
+ @this = instance_variable_set("@#{ivar}", object)
686
540
  end
687
-
688
541
 
689
- def hobo_render(page_kind = nil, page_model=nil)
690
- page_kind ||= params[:action].to_sym
691
- page_model ||= model
692
-
693
- if hobo_template_exists?(controller_path, page_kind)
694
- render :action => page_kind
695
- true
696
- elsif (template = find_model_template(page_model, page_kind))
697
- render :template => template
698
- true
699
- else
700
- # This returns false if no such tag exists
701
- render_tag("#{page_kind.to_s.dasherize}-page", :with => @this)
702
- end
542
+
543
+ def dryml_context
544
+ this
703
545
  end
704
546
 
705
547
 
548
+ def render_with_hobo_model(*args, &block)
549
+ options = args.extract_options!
550
+ self.this = options[:object] if options[:object]
551
+ this.user_view(current_user) if this && this.respond_to?(:user_view)
552
+ render_without_hobo_model(*args + [options], &block)
553
+ end
554
+
706
555
  # --- filters --- #
707
556
 
708
557
  def set_no_cache_headers
@@ -715,21 +564,12 @@ module Hobo
715
564
 
716
565
  # --- end filters --- #
717
566
 
567
+ public
718
568
 
719
569
  def model
720
570
  self.class.model
721
571
  end
722
572
 
723
-
724
- def data_filter_conditions
725
- active_filters = data_filters && (params.keys & data_filters.keys)
726
- filters = data_filters
727
- params = self.params
728
- proc do
729
- all?(*active_filters.map {|f| instance_exec(params[f], &filters[f])})
730
- end unless active_filters.blank?
731
- end
732
-
733
573
  end
734
574
 
735
575
  end