hobo 0.6.2 → 0.6.3

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