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