hobo 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/bin/hobo +3 -2
  2. data/hobo_files/plugin/CHANGES.txt +299 -2
  3. data/hobo_files/plugin/Rakefile +12 -10
  4. data/hobo_files/plugin/generators/hobo/templates/guest.rb +1 -13
  5. data/hobo_files/plugin/generators/hobo_migration/hobo_migration_generator.rb +11 -7
  6. data/hobo_files/plugin/generators/hobo_rapid/hobo_rapid_generator.rb +1 -0
  7. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_rapid.js +1 -1
  8. data/hobo_files/plugin/generators/hobo_rapid/templates/lowpro.js +405 -0
  9. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/views/application.dryml +1 -1
  10. data/hobo_files/plugin/generators/hobo_user_model/templates/model.rb +1 -9
  11. data/hobo_files/plugin/init.rb +5 -0
  12. data/hobo_files/plugin/lib/active_record/has_many_association.rb +1 -1
  13. data/hobo_files/plugin/lib/extensions.rb +26 -5
  14. data/hobo_files/plugin/lib/extensions/test_case.rb +1 -1
  15. data/hobo_files/plugin/lib/hobo.rb +37 -11
  16. data/hobo_files/plugin/lib/hobo/authenticated_user.rb +7 -2
  17. data/hobo_files/plugin/lib/hobo/authentication_support.rb +7 -6
  18. data/hobo_files/plugin/lib/hobo/composite_model.rb +5 -0
  19. data/hobo_files/plugin/lib/hobo/controller.rb +4 -4
  20. data/hobo_files/plugin/lib/hobo/dryml.rb +5 -5
  21. data/hobo_files/plugin/lib/hobo/dryml/part_context.rb +3 -6
  22. data/hobo_files/plugin/lib/hobo/dryml/template.rb +16 -15
  23. data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +24 -20
  24. data/hobo_files/plugin/lib/hobo/email_address.rb +4 -0
  25. data/hobo_files/plugin/lib/hobo/field_spec.rb +2 -1
  26. data/hobo_files/plugin/lib/hobo/guest.rb +21 -0
  27. data/hobo_files/plugin/lib/hobo/hobo_helper.rb +42 -2
  28. data/hobo_files/plugin/lib/hobo/http_parameters.rb +225 -0
  29. data/hobo_files/plugin/lib/hobo/model.rb +55 -37
  30. data/hobo_files/plugin/lib/hobo/model_controller.rb +151 -151
  31. data/hobo_files/plugin/lib/hobo/model_queries.rb +30 -5
  32. data/hobo_files/plugin/lib/hobo/user_controller.rb +27 -16
  33. data/hobo_files/plugin/lib/hobo/where_fragment.rb +6 -1
  34. data/hobo_files/plugin/tags/rapid.dryml +88 -58
  35. data/hobo_files/plugin/tags/rapid_document_tags.dryml +5 -5
  36. data/hobo_files/plugin/tags/rapid_editing.dryml +3 -3
  37. data/hobo_files/plugin/tags/rapid_forms.dryml +35 -26
  38. data/hobo_files/plugin/tags/rapid_navigation.dryml +13 -12
  39. data/hobo_files/plugin/tags/rapid_pages.dryml +35 -31
  40. data/hobo_files/plugin/tags/rapid_plus.dryml +41 -0
  41. data/hobo_files/plugin/tags/rapid_support.dryml +18 -9
  42. data/hobo_files/plugin/tasks/dump_fixtures.rake +61 -0
  43. metadata +7 -11
  44. data/hobo_files/plugin/spec/fixtures/users.yml +0 -9
  45. data/hobo_files/plugin/spec/spec.opts +0 -6
  46. data/hobo_files/plugin/spec/spec_helper.rb +0 -28
  47. data/hobo_files/plugin/spec/unit/hobo/dryml/template_spec.rb +0 -650
@@ -5,7 +5,13 @@ module Hobo
5
5
  include Hobo::Controller
6
6
 
7
7
  class PermissionDeniedError < RuntimeError; end
8
-
8
+ class UserPermissionError < StandardError
9
+ attr :models
10
+ def initialize(models)
11
+ @models = models || []
12
+ end
13
+ end
14
+
9
15
  VIEWLIB_DIR = "taglibs"
10
16
 
11
17
  GENERIC_PAGE_TAGS = [:index, :show, :new, :edit, :show_collection, :new_in_collection, :login, :signup]
@@ -99,9 +105,10 @@ module Hobo
99
105
  end
100
106
 
101
107
 
102
- def autocomplete_for(attr, options={})
103
- opts = { :limit => 15 }.update(options)
104
- @completers ||= {}
108
+ def autocomplete_for(attr, options={}, &b)
109
+ options = options.reverse_merge(:limit => 15)
110
+ options[:data_filters_block] = b
111
+ @completers ||= HashWithIndifferentAccess.new
105
112
  @completers[attr.to_sym] = opts
106
113
  end
107
114
 
@@ -110,12 +117,6 @@ module Hobo
110
117
  (@completers && @completers[name]) ||
111
118
  (superclass.respond_to?(:autocompleter) && superclass.autocompleter(name))
112
119
  end
113
-
114
-
115
- def def_data_filter(name, &b)
116
- @data_filters ||= {}
117
- @data_filters[name] = b
118
- end
119
120
 
120
121
 
121
122
  def web_method(web_name, method_name=nil)
@@ -139,12 +140,6 @@ module Hobo
139
140
  end
140
141
 
141
142
 
142
- def data_filter(name)
143
- (@data_filters && @data_filters[name]) ||
144
- (superclass.respond_to?(:data_filter) && superclass.data_filter(name))
145
- end
146
-
147
-
148
143
  def find_instance(id)
149
144
  if model.id_name? and id !~ /^\d+$/
150
145
  model.find_by_id_name(id)
@@ -154,6 +149,8 @@ module Hobo
154
149
  end
155
150
 
156
151
  end
152
+
153
+ include HttpParameters
157
154
 
158
155
  # --- ACTIONS --- #
159
156
 
@@ -166,11 +163,13 @@ module Hobo
166
163
  def destroy; hobo_destroy; end
167
164
 
168
165
  def completions
169
- attr = params[:for]
170
- opts = attr && self.class.autocompleter(attr.to_sym)
166
+ opts = self.class.autocompleter(params[:for])
171
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
172
171
  q = params[:query]
173
- items = find_with_data_filter(opts) { send("#{attr}_contains", q) }
172
+ items = model.find(:all) { all?(send("#{attr}_contains", q), conditions && block(conditions)) }
174
173
 
175
174
  render :text => "<ul>\n" + items.map {|i| "<li>#{i.send(attr)}</li>\n"}.join + "</ul>"
176
175
  else
@@ -179,10 +178,34 @@ module Hobo
179
178
  end
180
179
 
181
180
 
182
- ###### END OF ACTIONS ######
181
+ # --- END OF ACTIONS --- #
183
182
 
184
183
  protected
185
184
 
185
+ def data_filter(name, &b)
186
+ @data_filters ||= HashWithIndifferentAccess.new
187
+ @data_filters[name] = b
188
+ end
189
+
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) }
200
+ end
201
+ end
202
+ end.flatten
203
+ any?(*columns_match)
204
+ end
205
+ end
206
+
207
+ attr_accessor :data_filters
208
+
186
209
  def overridable_response(options, key)
187
210
  if options.has_key?(key)
188
211
  options[key]
@@ -210,21 +233,34 @@ module Hobo
210
233
 
211
234
 
212
235
  def paginated_find(*args, &b)
213
- options = extract_options_from_args!(args)
214
-
215
- total_number = options.delete(:total_number)
216
- @association = options.delete(:association) or
236
+ options = args.extract_options!
237
+ filter_conditions = data_filter_conditions
238
+ conditions_proc = if b && filter_conditions
239
+ proc { block(b) & block(filter_conditions) }
240
+ else
241
+ b || filter_conditions
242
+ end
243
+
244
+ @association = options.delete(:association) ||
217
245
  if args.any?
218
246
  owner, collection_name = args
219
247
  @association = collection_name.to_s.split(".").inject(owner) { |m, name| m.send(name) }
220
248
  end
221
-
222
- if @association
223
- total_number ||= @association.count
224
- @reflection = @association.proxy_reflection if @association.respond_to?(:proxy_reflection)
225
- end
249
+ @reflection = @association.proxy_reflection if @association._?.respond_to?(:proxy_reflection)
250
+
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
226
263
 
227
- total_number ||= count_with_data_filter
228
264
  page_size = options.delete(:page_size) || 20
229
265
  page = options.delete(:page) || params[:page]
230
266
  @pages = ::ActionController::Pagination::Paginator.new(self, total_number, page_size, page)
@@ -234,11 +270,28 @@ module Hobo
234
270
  :offset => @pages.current.offset,
235
271
  }.merge(options)
236
272
 
273
+ unless options.has_key?(:order)
274
+ _, desc, field = *params[:sort]._?.match(/^(-)?([a-z_]+(?:\.[a-z_]+)?)$/)
275
+ if field
276
+ @sort_field = field
277
+ @sort_direction = desc ? "desc" : "asc"
278
+
279
+ table, column = if field =~ /^(.*)\.(.*)$/
280
+ [$1.camelize.constantize.table_name, $2]
281
+ else
282
+ sort_model = @association ? @association.member_class : model
283
+ [sort_model.table_name, field]
284
+ end
285
+ options[:order] = "#{table}.#{column} #{@sort_direction}"
286
+ elsif !@association
287
+ options[:order] = :default
288
+ end
289
+ end
290
+
237
291
  if @association
238
- @association.find(:all, options, &b)
292
+ @association.find(:all, options, &conditions_proc)
239
293
  else
240
- options[:order] ||= :default
241
- find_with_data_filter(options, &b)
294
+ model.find(:all, options, &conditions_proc)
242
295
  end
243
296
  end
244
297
 
@@ -291,6 +344,7 @@ module Hobo
291
344
 
292
345
  if (@this = options[:this])
293
346
  permission_denied(options) and return unless Hobo.can_create?(current_user, @this)
347
+ valid = @this.save
294
348
  else
295
349
  attributes = params[model.name.underscore]
296
350
  type_attr = params['type']
@@ -301,16 +355,14 @@ module Hobo
301
355
  model
302
356
  end
303
357
  @this = create_model.new
304
- @check_create_permission = [@this]
305
- initialize_from_params(@this, attributes)
306
- for obj in @check_create_permission
307
- permission_denied(options) and return unless Hobo.can_create?(current_user, obj)
308
- end
309
- @check_create_permission = nil
358
+ status = secure_change_transaction { initialize_record(@this, attributes) }
359
+ permission_denied(options) and return if status == :not_allowed
360
+ valid = status == :valid
310
361
  end
311
362
 
312
363
  set_named_this!
313
- if @this.save
364
+
365
+ if valid
314
366
  if block_given?
315
367
  yield
316
368
  else
@@ -335,6 +387,7 @@ module Hobo
335
387
  end
336
388
  end
337
389
 
390
+
338
391
  def hobo_edit(options={})
339
392
  hobo_show(options)
340
393
  end
@@ -346,22 +399,16 @@ module Hobo
346
399
  @this = find_instance_or_not_found(options, :this)
347
400
  return unless @this
348
401
 
349
- original = @this.duplicate
350
-
351
402
  changes = params[model.name.underscore]
352
-
353
- if changes
354
- # The 'duplicate' call above can set these, but they can
355
- # conflict with the changes so we clear them
356
- @this.send(:clear_aggregation_cache)
357
- @this.send(:clear_association_cache)
358
-
359
- update_with_params(@this, changes)
360
- permission_denied(options) and return unless Hobo.can_update?(current_user, original, @this)
361
- end
403
+ status = secure_change_transaction { update_record(@this, changes) }
362
404
 
363
405
  set_named_this!
364
- if changes.nil? || @this.save
406
+ case status
407
+ when :not_allowed
408
+ permission_denied(options)
409
+ return
410
+
411
+ when :valid
365
412
  # Ensure current_user isn't out of date
366
413
  @current_user = @this if @this == current_user
367
414
 
@@ -392,7 +439,7 @@ module Hobo
392
439
  end
393
440
  end
394
441
 
395
- else
442
+ when :invalid
396
443
  # Validation errors
397
444
  respond_to do |wants|
398
445
  wants.html do
@@ -494,18 +541,19 @@ module Hobo
494
541
  end
495
542
 
496
543
 
497
- def permission_denied(options=nil)
498
- if options and options[:permission_denied_response]
544
+ def permission_denied(options={})
545
+ if options[:permission_denied_response]
499
546
  # do nothing (callback handled by LazyHash)
500
547
  elsif respond_to? :permission_denied_response
501
548
  permission_denied_response
502
549
  else
503
- render :text => "Permission Denied", :status => 403
550
+ message = options[:message] || "Permission Denied"
551
+ render :text => message, :status => 403
504
552
  end
505
553
  end
506
554
 
507
- def not_found(options=nil)
508
- if options && options[:not_found_response]
555
+ def not_found(options={})
556
+ if options[:not_found_response]
509
557
  # do nothing (callback handled by LazyHash)
510
558
  elsif respond_to? :not_found_response
511
559
  not_found_response
@@ -531,15 +579,24 @@ module Hobo
531
579
 
532
580
  template = Hobo::ModelController.find_model_template(page_model, page_kind)
533
581
 
534
- if template
535
- render :template => template
536
- true
537
- else
538
- if page_kind.in? GENERIC_PAGE_TAGS
539
- render_tag("#{page_kind.to_s.camelize}Page", :with => @this)
582
+ begin
583
+ if template
584
+ render :template => template
540
585
  true
541
586
  else
542
- false
587
+ # This returns false if no such tag exists
588
+ render_tag("#{page_kind.to_s.camelize}Page", :with => @this)
589
+ end
590
+ rescue ActionView::TemplateError => wrapper
591
+ e = wrapper.original_exception if wrapper.respond_to? :original_exception
592
+ if e.is_a? Hobo::ModelController::UserPermissionError
593
+ if current_user.guest?
594
+ redirect_to login_url(e.models.first || UserController.user_models.first)
595
+ else
596
+ permission_denied(:message => e.message)
597
+ end
598
+ else
599
+ raise
543
600
  end
544
601
  end
545
602
  end
@@ -548,112 +605,55 @@ module Hobo
548
605
  def model
549
606
  self.class.model
550
607
  end
551
-
608
+
552
609
 
553
610
  def find_template
554
611
  Hobo::ModelController.find_model_template(model, params[:action])
555
612
  end
556
613
 
557
-
558
- def with_data_filter(operation, *args, &block)
559
- filter_param = params.keys.find &it.starts_with?("where_")
560
- proc = filter_param && self.class.data_filter(filter_param[6..-1].to_sym)
561
- if proc
562
- filter_args = params[filter_param]
563
- filter_args = [filter_args] unless filter_args.is_a? Array
564
- model.send(operation, *args) do
565
- if block
566
- instance_eval(&block) & instance_exec(*filter_args, &proc)
567
- else
568
- instance_exec(*filter_args, &proc)
569
- end
570
- end
571
- else
572
- if block
573
- model.send(operation, *args) { instance_eval(&block) }
574
- else
575
- model.send(operation, *args)
576
- end
577
- end
578
- end
579
614
 
580
- def find_with_data_filter(opts={}, &b)
581
- with_data_filter(:find, :all, opts, &b)
615
+ def data_filter_conditions
616
+ active_filters = data_filters && (params.keys & data_filters.keys)
617
+ filters = data_filters
618
+ params = self.params
619
+ proc do
620
+ all?(*active_filters.map {|f| instance_exec(params[f], &filters[f])})
621
+ end unless active_filters.blank?
582
622
  end
583
623
 
584
-
585
- def count_with_data_filter(opts={}, &b)
586
- with_data_filter(:count, opts, &b)
587
- end
588
-
589
-
590
- def initialize_from_params(obj, params)
591
- update_with_params(obj, params)
592
- obj.set_creator(current_user)
593
- (@check_create_permission ||= []) << obj
594
- obj
595
- end
596
-
597
624
 
598
- def update_with_params(object, params)
625
+ def XXupdate_with_params(object, params)
599
626
  return unless params
600
627
 
601
628
  params.each_pair do |field,value|
602
629
  field = field.to_sym
603
630
  refl = object.class.reflections[field]
604
- ar_value = if refl
605
- if refl.macro == :belongs_to
606
- associated_record(object, refl, value)
607
-
608
- elsif Hobo.simple_has_many_association?(refl) and object.new_record?
609
- # only populate has_many relationships for new records. For existing
610
- # records, AR updates the DB immediately, bypassing Hobo's permission check
611
- if value.is_a? Array
612
- value.map {|x| associated_record(object, refl, x) }
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)
613
644
  else
614
- value.keys.every(:to_i).sort.map{|i| associated_record(object, refl, value[i.to_s]) }
645
+ raise HoboError, "association #{object.class}.#{refl.name} is not settable via parameters"
615
646
  end
616
647
  else
617
- raise HoboError.new("association #{refl.name} is not settable via parameters")
648
+ param_to_value(object.class.field_type(field), value)
618
649
  end
619
- else
620
- param_to_value(object.class.field_type(field), value)
621
- end
622
- object.send("#{field}=".to_sym, ar_value)
623
- end
624
- end
625
-
626
-
627
- def parse_datetime(s)
628
- defined?(Chronic) ? Chronic.parse(s) : Time.parse(s)
629
- end
630
-
631
-
632
- def param_to_value(field_type, value)
633
- if field_type.nil?
634
- value
635
- elsif field_type <= Date
636
- if value.is_a? Hash
637
- Date.new(*(%w{year month day}.map{|s| value[s].to_i}))
638
- elsif value.is_a? String
639
- dt = parse_datetime(value)
640
- dt && dt.to_date
641
- end
642
- elsif field_type <= Time
643
- if value.is_a? Hash
644
- Time.local(*(%w{year month day hour minute}.map{|s| value[s].to_i}))
645
- elsif value.is_a? String
646
- parse_datetime(value)
650
+ object.send("#{field}=".to_sym, ar_value)
647
651
  end
648
- elsif field_type <= TrueClass
649
- (value.is_a?(String) && value.strip.downcase.in?(['0', 'false']) || value.blank?) ? false : true
650
- else
651
- # primitive field
652
- value
653
652
  end
654
653
  end
655
654
 
656
- def associated_record(owner, refl, value)
655
+
656
+ def XXassociated_record(owner, refl, value)
657
657
  if value.is_a? String
658
658
  if value.starts_with?('@')
659
659
  Hobo.object_from_dom_id(value[1..-1])
@@ -674,7 +674,7 @@ module Hobo
674
674
  end
675
675
 
676
676
 
677
- def object_from_param(param)
677
+ def XXobject_from_param(param)
678
678
  Hobo.object_from_dom_id(param)
679
679
  end
680
680