hobo 0.7.2 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
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