hobo 0.8.5 → 0.8.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/CHANGES.txt +41 -0
  2. data/Manifest +1 -5
  3. data/Rakefile +10 -3
  4. data/bin/hobo +38 -15
  5. data/dryml_generators/rapid/cards.dryml.erb +7 -7
  6. data/dryml_generators/rapid/pages.dryml.erb +52 -24
  7. data/hobo.gemspec +42 -322
  8. data/init.rb +0 -7
  9. data/lib/active_record/association_collection.rb +9 -0
  10. data/lib/hobo.rb +13 -14
  11. data/lib/hobo/accessible_associations.rb +32 -7
  12. data/lib/hobo/authentication_support.rb +1 -1
  13. data/lib/hobo/controller.rb +5 -7
  14. data/lib/hobo/dryml.rb +9 -2
  15. data/lib/hobo/dryml/dryml_builder.rb +11 -12
  16. data/lib/hobo/dryml/dryml_doc.rb +22 -24
  17. data/lib/hobo/dryml/dryml_generator.rb +41 -4
  18. data/lib/hobo/dryml/part_context.rb +5 -3
  19. data/lib/hobo/dryml/template.rb +7 -7
  20. data/lib/hobo/dryml/template_environment.rb +11 -22
  21. data/lib/hobo/dryml/template_handler.rb +94 -25
  22. data/lib/hobo/find_for.rb +2 -2
  23. data/lib/hobo/hobo_helper.rb +21 -21
  24. data/lib/hobo/include_in_save.rb +9 -5
  25. data/lib/hobo/lifecycles/transition.rb +2 -2
  26. data/lib/hobo/model.rb +11 -61
  27. data/lib/hobo/model_controller.rb +28 -29
  28. data/lib/hobo/model_router.rb +12 -13
  29. data/lib/hobo/permissions.rb +47 -37
  30. data/lib/hobo/permissions/associations.rb +1 -1
  31. data/lib/hobo/scopes/association_proxy_extensions.rb +5 -6
  32. data/lib/hobo/scopes/automatic_scopes.rb +7 -4
  33. data/lib/hobo/tasks/rails.rb +4 -0
  34. data/lib/hobo/user.rb +0 -1
  35. data/lib/hobo/user_controller.rb +3 -1
  36. data/lib/hobo/view_hints.rb +17 -3
  37. data/rails_generators/hobo/hobo_generator.rb +1 -0
  38. data/rails_generators/hobo_front_controller/templates/functional_test.rb +1 -11
  39. data/rails_generators/hobo_front_controller/templates/index.dryml +1 -6
  40. data/rails_generators/hobo_rapid/hobo_rapid_generator.rb +1 -0
  41. data/rails_generators/hobo_rapid/templates/hobo-rapid.css +3 -2
  42. data/rails_generators/hobo_rapid/templates/hobo-rapid.js +24 -15
  43. data/rails_generators/hobo_rapid/templates/themes/clean/public/stylesheets/clean.css +17 -12
  44. data/rails_generators/hobo_rapid/templates/themes/clean/public/stylesheets/rapid-ui.css +6 -2
  45. data/rails_generators/hobo_rapid/templates/themes/clean/views/clean.dryml +2 -2
  46. data/rails_generators/hobo_user_model/templates/forgot_password.erb +2 -2
  47. data/rails_generators/hobo_user_model/templates/model.rb +2 -2
  48. data/taglibs/rapid.dryml +3 -2
  49. data/taglibs/rapid_core.dryml +21 -16
  50. data/taglibs/rapid_document_tags.dryml +1 -1
  51. data/taglibs/rapid_editing.dryml +7 -10
  52. data/taglibs/rapid_forms.dryml +115 -26
  53. data/taglibs/rapid_generics.dryml +13 -3
  54. data/taglibs/rapid_lifecycles.dryml +18 -1
  55. data/taglibs/rapid_navigation.dryml +50 -61
  56. data/taglibs/rapid_pages.dryml +103 -19
  57. data/taglibs/rapid_plus.dryml +54 -6
  58. data/taglibs/rapid_support.dryml +38 -1
  59. data/taglibs/rapid_user_pages.dryml +17 -5
  60. data/test/permissions/models/models.rb +24 -12
  61. data/test/permissions/models/test.sqlite3 +0 -0
  62. metadata +6 -15
  63. data/lib/extensions/test_case.rb +0 -129
  64. data/lib/hobo/composite_model.rb +0 -73
  65. data/lib/hobo/model_support.rb +0 -44
  66. data/tasks/fix_dryml.rake +0 -143
  67. data/tasks/generate_tag_reference.rake +0 -192
  68. data/test/dryml/complilation_test.rb +0 -261
@@ -6,7 +6,7 @@ module Hobo
6
6
 
7
7
  NAME_FIELD_GUESS = %w(name title)
8
8
  PRIMARY_CONTENT_GUESS = %w(description body content profile)
9
- SEARCH_COLUMNS_GUESS = %w(name title body content profile)
9
+ SEARCH_COLUMNS_GUESS = %w(name title body description content profile)
10
10
 
11
11
 
12
12
  def self.included(base)
@@ -86,7 +86,9 @@ module Hobo
86
86
  end
87
87
 
88
88
  # ...but only return the ones that registered themselves
89
- @model_names.*.constantize
89
+ @model_names.map do |name|
90
+ name.safe_constantize || (@model_names.delete name; nil)
91
+ end.compact
90
92
  end
91
93
 
92
94
 
@@ -133,9 +135,6 @@ module Hobo
133
135
 
134
136
  module ClassMethods
135
137
 
136
- # include methods also shared by CompositeModel
137
- #include ModelSupport::ClassMethods
138
-
139
138
  attr_accessor :creator_attribute
140
139
  attr_writer :name_attribute, :primary_content_attribute
141
140
 
@@ -204,7 +203,7 @@ module Hobo
204
203
  if options[:polymorphic]
205
204
  class_eval %{
206
205
  def #{name}_is?(target)
207
- target.id == self.#{refl.primary_key_name} && target.class.name == self.#{refl.options[:foreign_type]}
206
+ target.class.name == self.#{refl.options[:foreign_type]} && target.id == self.#{refl.primary_key_name}
208
207
  end
209
208
  def #{name}_changed?
210
209
  #{refl.primary_key_name}_changed? || #{refl.options[:foreign_type]}_changed?
@@ -213,7 +212,7 @@ module Hobo
213
212
  else
214
213
  class_eval %{
215
214
  def #{name}_is?(target)
216
- target.id == self.#{refl.primary_key_name}
215
+ target.class == ::#{refl.klass.name} && target.id == self.#{refl.primary_key_name}
217
216
  end
218
217
  def #{name}_changed?
219
218
  #{refl.primary_key_name}_changed?
@@ -359,10 +358,7 @@ module Hobo
359
358
 
360
359
  def method_missing(name, *args, &block)
361
360
  name = name.to_s
362
- if name =~ /\./
363
- # FIXME: Do we need this now?
364
- call_method_chain(name, args, &block)
365
- elsif create_automatic_scope(name)
361
+ if create_automatic_scope(name)
366
362
  send(name.to_sym, *args, &block)
367
363
  else
368
364
  super(name.to_sym, *args, &block)
@@ -375,13 +371,6 @@ module Hobo
375
371
  end
376
372
 
377
373
 
378
- def call_method_chain(chain, args, &block)
379
- parts = chain.split(".")
380
- s = parts[0..-2].inject(self) { |m, scope| m.send(scope) }
381
- s.send(parts.last, *args)
382
- end
383
-
384
-
385
374
  def to_url_path
386
375
  "#{name.underscore.pluralize}"
387
376
  end
@@ -412,7 +401,7 @@ module Hobo
412
401
  def to_param
413
402
  name_attr = self.class.name_attribute and name = send(name_attr)
414
403
  if name_attr && !name.blank? && id.is_a?(Fixnum)
415
- readable = name.to_s.downcase.gsub(/[^a-z0-9]+/, '-').gsub(/-+$/, '').gsub(/^-+/, '').split('-')[0..5].join('-')
404
+ readable = name.to_s.downcase.gsub(/[^a-z0-9]+/, '-').remove(/-+$/).remove(/^-+/).split('-')[0..5].join('-')
416
405
  @to_param ||= "#{id}-#{readable}"
417
406
  else
418
407
  id.to_s
@@ -431,6 +420,9 @@ module Hobo
431
420
  end
432
421
 
433
422
 
423
+ # We deliberately give these three methods unconventional (java-esque) names to avoid
424
+ # polluting the application namespace
425
+
434
426
  def set_creator(user)
435
427
  set_creator!(user) unless get_creator
436
428
  end
@@ -452,53 +444,11 @@ module Hobo
452
444
  end
453
445
 
454
446
 
455
- # We deliberately give this method an unconventional name to avoid
456
- # polluting the application namespace too badly
457
447
  def get_creator
458
448
  self.class.creator_attribute && send(self.class.creator_attribute)
459
449
  end
460
450
 
461
451
 
462
- def duplicate
463
- copy = self.class.new
464
- copy.copy_instance_variables_from(self, ["@attributes_cache"])
465
- copy.instance_variable_set("@attributes", @attributes.dup)
466
- copy.instance_variable_set("@new_record", nil) unless new_record?
467
-
468
- # Shallow copy of belongs_to associations
469
- for refl in self.class.reflections.values
470
- if refl.macro == :belongs_to and (target = self.send(refl.name))
471
- bta = ActiveRecord::Associations::BelongsToAssociation.new(copy, refl)
472
- bta.replace(target)
473
- copy.instance_variable_set("@#{refl.name}", bta)
474
- end
475
- end
476
- copy
477
- end
478
-
479
-
480
- def same_fields?(other, *fields)
481
- return true if other.nil?
482
-
483
- fields = fields.flatten
484
- fields.all?{|f| self.send(f) == other.send(f)}
485
- end
486
-
487
-
488
- def only_changed_fields?(other, *changed_fields)
489
- return true if other.nil?
490
-
491
- changed_fields = changed_fields.flatten.*.to_s
492
- all_cols = self.class.columns.*.name - []
493
- all_cols.all?{|c| c.in?(changed_fields) || self.send(c) == other.send(c) }
494
- end
495
-
496
-
497
- def compose_with(object, use=nil)
498
- CompositeModel.new_for([self, object])
499
- end
500
-
501
-
502
452
  def typed_id
503
453
  "#{self.class.name.underscore}:#{self.id}" if id
504
454
  end
@@ -4,10 +4,9 @@ module Hobo
4
4
 
5
5
  include Hobo::Controller
6
6
 
7
- VIEWLIB_DIR = "taglibs"
8
-
9
7
  DONT_PAGINATE_FORMATS = [ Mime::CSV, Mime::YAML, Mime::JSON, Mime::XML, Mime::ATOM, Mime::RSS ]
10
- WILL_PAGINATE_OPTIONS = [ :page, :per_page, :total_entries, :count, :finder]
8
+
9
+ WILL_PAGINATE_OPTIONS = [ :page, :per_page, :total_entries, :count, :finder ]
11
10
 
12
11
  READ_ONLY_ACTIONS = [:index, :show]
13
12
  WRITE_ONLY_ACTIONS = [:create, :update, :destroy]
@@ -27,7 +26,6 @@ module Hobo
27
26
 
28
27
  helper_method :model, :current_user
29
28
  before_filter :set_no_cache_headers
30
- after_filter :remember_page_path
31
29
 
32
30
  rescue_from ActiveRecord::RecordNotFound, :with => :not_found
33
31
 
@@ -35,7 +33,7 @@ module Hobo
35
33
  rescue_from Hobo::Lifecycles::LifecycleKeyError, :with => :permission_denied
36
34
 
37
35
  alias_method_chain :render, :hobo_model
38
-
36
+
39
37
  end
40
38
  register_controller(base)
41
39
 
@@ -97,7 +95,7 @@ module Hobo
97
95
  index_action "complete_#{name}", &block
98
96
  else
99
97
  index_action "complete_#{name}" do
100
- hobo_completions name, model, options
98
+ hobo_completions field, model, options
101
99
  end
102
100
  end
103
101
  end
@@ -109,8 +107,8 @@ module Hobo
109
107
  got_block = block_given?
110
108
  define_method web_name do
111
109
  # Make sure we have a copy of the options - it is being mutated somewhere
112
- opts = {}.merge(options)
113
- self.this = find_instance(opts) unless opts[:no_find]
110
+ opts = options.dup
111
+ self.this = find_instance(opts)
114
112
  raise Hobo::PermissionDeniedError unless @this.method_callable_by?(current_user, method)
115
113
  if got_block
116
114
  instance_eval(&block)
@@ -118,7 +116,7 @@ module Hobo
118
116
  @this.send(method)
119
117
  end
120
118
 
121
- hobo_ajax_response || render(:nothing => true) unless performed?
119
+ hobo_ajax_response unless performed?
122
120
  end
123
121
  end
124
122
 
@@ -370,7 +368,7 @@ module Hobo
370
368
  after_submit = params[:after_submit]
371
369
 
372
370
  # The after_submit post parameter takes priority
373
- (after_submit == "stay-here" ? previous_page_path : after_submit) ||
371
+ (after_submit == "stay-here" ? url_for_page_path : after_submit) ||
374
372
 
375
373
  # Then try the record's show page
376
374
  (!destroyed && object_url(@this)) ||
@@ -385,6 +383,11 @@ module Hobo
385
383
  home_page
386
384
  end
387
385
 
386
+
387
+ def url_for_page_path
388
+ controller, view = Controller.controller_and_view_for(params[:page_path])
389
+ url_for :controller => controller, :action => view
390
+ end
388
391
 
389
392
  # TODO: Get rid of this joke of an idea that fails miserably if you open another browser window.
390
393
  def previous_page_path
@@ -410,10 +413,12 @@ module Hobo
410
413
 
411
414
  def response_block(&b)
412
415
  if b
413
- if b.arity == 1
414
- respond_to {|wants| yield(wants) }
415
- else
416
- yield
416
+ respond_to do |format|
417
+ if b.arity == 1
418
+ yield format
419
+ else
420
+ format.html { yield }
421
+ end
417
422
  end
418
423
  performed?
419
424
  end
@@ -421,7 +426,7 @@ module Hobo
421
426
 
422
427
 
423
428
  def request_requires_pagination?
424
- request.format.not_in?(DONT_PAGINATE_FORMATS)
429
+ request.format.not_in?(DONT_PAGINATE_FORMATS) && model.view_hints.paginate?
425
430
  end
426
431
 
427
432
 
@@ -442,7 +447,7 @@ module Hobo
442
447
  def find_owner_and_association(owner_association)
443
448
  refl = model.reflections[owner_association]
444
449
  klass = refl.klass
445
- id = params["#{klass.name.underscore}_id"]
450
+ id = params["#{owner_association}_id"]
446
451
  owner = klass.find(id)
447
452
  instance_variable_set("@#{owner_association}", owner)
448
453
  [owner, owner.send(model.reverse_reflection(owner_association).name)]
@@ -470,7 +475,7 @@ module Hobo
470
475
 
471
476
  def hobo_show(*args, &b)
472
477
  options = args.extract_options!
473
- self.this = find_instance(options)
478
+ self.this ||= find_instance(options)
474
479
  response_block(&b)
475
480
  end
476
481
 
@@ -490,7 +495,7 @@ module Hobo
490
495
 
491
496
  def hobo_create(*args, &b)
492
497
  options = args.extract_options!
493
- self.this = args.first || new_for_create
498
+ self.this ||= args.first || new_for_create
494
499
  this.user_update_attributes(current_user, options[:attributes] || attribute_parameters || {})
495
500
  create_response(:new, &b)
496
501
  end
@@ -499,7 +504,7 @@ module Hobo
499
504
  def hobo_create_for(owner, *args, &b)
500
505
  options = args.extract_options!
501
506
  owner, association = find_owner_and_association(owner)
502
- self.this = args.first || association.new
507
+ self.this ||= args.first || association.new
503
508
  this.user_update_attributes(current_user, options[:attributes] || attribute_parameters || {})
504
509
  create_response(:"new_for_#{owner}", &b)
505
510
  end
@@ -547,7 +552,7 @@ module Hobo
547
552
  def hobo_update(*args, &b)
548
553
  options = args.extract_options!
549
554
 
550
- self.this = args.first || find_instance
555
+ self.this ||= args.first || find_instance
551
556
  changes = options[:attributes] || attribute_parameters or raise RuntimeError, "No update specified in params"
552
557
  this.user_update_attributes(current_user, changes)
553
558
 
@@ -594,7 +599,7 @@ module Hobo
594
599
 
595
600
  def hobo_destroy(*args, &b)
596
601
  options = args.extract_options!
597
- self.this = args.first || find_instance
602
+ self.this ||= args.first || find_instance
598
603
  this.user_destroy(current_user)
599
604
  flash[:notice] = "The #{model.name.titleize.downcase} was deleted" unless request.xhr?
600
605
  destroy_response(&b)
@@ -671,7 +676,7 @@ module Hobo
671
676
  options = options.reverse_merge(:limit => 10, :param => :query, :query_scope => "#{attribute}_contains")
672
677
  finder = finder.limit(options[:limit]) unless finder.send(:scope, :find, :limit)
673
678
  finder = finder.send(options[:query_scope], params[options[:param]])
674
- items = finder.find(:all)
679
+ items = finder.find(:all).select { |r| r.viewable_by?(current_user) }
675
680
  render :text => "<ul>\n" + items.map {|i| "<li>#{i.send(attribute)}</li>\n"}.join + "</ul>"
676
681
  end
677
682
 
@@ -694,6 +699,7 @@ module Hobo
694
699
 
695
700
  def permission_denied(error)
696
701
  self.this = true # Otherwise this gets sent user_view
702
+ @permission_error = error
697
703
  if "permission_denied".in?(self.class.superclass.instance_methods)
698
704
  super
699
705
  else
@@ -762,13 +768,6 @@ module Hobo
762
768
  headers["Expires"] ='0'
763
769
  end
764
770
 
765
- def remember_page_path
766
- if request.method == :get
767
- session[:previous_page_path] = request.path
768
- session[:previous_page_path] += "?#{request.query_string}" unless request.query_string.blank?
769
- end
770
- end
771
-
772
771
  # --- end filters --- #
773
772
 
774
773
  public
@@ -73,20 +73,23 @@ module Hobo
73
73
  return
74
74
  end
75
75
 
76
- require "#{RAILS_ROOT}/app/controllers/application" unless Object.const_defined? :ApplicationController
77
-
76
+ begin
77
+ require "#{RAILS_ROOT}/app/controllers/application" unless Object.const_defined? :ApplicationController
78
+ rescue MissingSourceFile => ex
79
+ # must be on Rails 2.3. Yay!
80
+ end
81
+
78
82
  # Add non-subsite, and all subsite routes
79
83
  [nil, *Hobo.subsites].each { |subsite| add_routes_for(map, subsite) }
80
84
 
81
85
  add_developer_routes(map) if Hobo.developer_features?
82
86
 
83
- # Run the DRYML generators
84
- # TODO: This needs a proper home
85
- Hobo::Dryml::DrymlGenerator.run unless caller[-1] =~ /[\/\\]rake:\d+$/
86
87
  rescue ActiveRecord::StatementInvalid => e
87
88
  # Database problem? Just continue without routes
88
- ActiveRecord::Base.logger.warn "!! Database exception during Hobo routing -- continuing without routes"
89
- ActiveRecord::Base.logger.warn "!! #{e.to_s}"
89
+ if ActiveRecord::Base.logger
90
+ ActiveRecord::Base.logger.warn "!! Database exception during Hobo routing -- continuing without routes"
91
+ ActiveRecord::Base.logger.warn "!! #{e.to_s}"
92
+ end
90
93
  end
91
94
 
92
95
 
@@ -132,11 +135,7 @@ module Hobo
132
135
 
133
136
 
134
137
  def add_routes
135
- # Simple support for composite models, we might later need a CompositeModelController
136
- if model < Hobo::CompositeModel
137
- map.connect "#{plural}/:id", :controller => plural, :action => 'show', :requirements => ID_REQUIREMENT
138
-
139
- elsif controller < Hobo::ModelController
138
+ if controller < Hobo::ModelController
140
139
  # index routes need to be first so the index names don't get
141
140
  # taken as IDs
142
141
  index_action_routes
@@ -181,7 +180,7 @@ module Hobo
181
180
 
182
181
  owner = owner.to_s.singularize if model.reflections[owner].macro == :has_many
183
182
 
184
- collection_path = "#{owner_class.pluralize}/:#{owner_class}_id/#{collection}"
183
+ collection_path = "#{owner_class.pluralize}/:#{owner}_id/#{collection}"
185
184
 
186
185
  actions.each do |action|
187
186
  case action
@@ -319,68 +319,78 @@ module Hobo
319
319
 
320
320
  # By default, attempt to derive edit permission from create/update permission
321
321
  def edit_permitted?(attribute)
322
- if attribute
323
- with_attribute_or_belongs_to_keys(attribute) do |attr, ftype|
324
- unknownify_attribute(self, attr)
325
- unknownify_attribute(self, ftype) if ftype
326
- end
327
- end
322
+ unknownify_attribute(attribute) if attribute
328
323
  new_record? ? create_permitted? : update_permitted?
329
324
  rescue Hobo::UndefinedAccessError
330
325
  # The permission is dependent on the unknown value
331
326
  # so this attribute is not editable
332
327
  false
333
328
  ensure
334
- if attribute
335
- with_attribute_or_belongs_to_keys(attribute) do |attr, ftype|
336
- deunknownify_attribute(self, attr)
337
- deunknownify_attribute(self, ftype) if ftype
338
- end
339
- end
329
+ deunknownify_attribute(attribute) if attribute
340
330
  end
341
331
 
342
332
 
343
333
  # Add some singleton methods to +record+ so give the effect that +attribute+ is unknown. That is,
344
334
  # attempts to access the attribute will result in a Hobo::UndefinedAccessError
345
- def unknownify_attribute(record, attr)
346
- record.metaclass.class_eval do
347
-
335
+ def unknownify_attribute(attr)
336
+ metaclass.class_eval do
348
337
  define_method attr do
349
338
  raise Hobo::UndefinedAccessError
350
339
  end
340
+ end
341
+
342
+ if (refl = self.class.reflections[attr.to_sym]) && refl.macro == :belongs_to
343
+ # A belongs_to -- also unknownify the underlying fields
344
+ unknownify_attribute refl.primary_key_name
345
+ unknownify_attribute refl.options[:foreign_type] if refl.options[:polymorphic]
346
+ else
347
+ # A regular field -- hack the dirty tracking methods
351
348
 
352
- define_method "#{attr}_change" do
353
- raise Hobo::UndefinedAccessError
354
- end
349
+ metaclass.class_eval do
355
350
 
356
- define_method "#{attr}_was" do
357
- read_attribute attr
358
- end
351
+ define_method "#{attr}_change" do
352
+ raise Hobo::UndefinedAccessError
353
+ end
359
354
 
360
- define_method "#{attr}_changed?" do
361
- true
362
- end
355
+ define_method "#{attr}_was" do
356
+ read_attribute attr
357
+ end
358
+
359
+ define_method "#{attr}_changed?" do
360
+ true
361
+ end
363
362
 
364
- def changed?
365
- true
366
- end
363
+ def changed?
364
+ true
365
+ end
367
366
 
368
- define_method :changed do
369
- changed_attributes.keys | [attr]
370
- end
367
+ define_method :changed do
368
+ changed_attributes.keys | [attr.to_s]
369
+ end
371
370
 
372
- def changes
373
- raise Hobo::UndefinedAccessError
374
- end
371
+ def changes
372
+ raise Hobo::UndefinedAccessError
373
+ end
375
374
 
375
+ end
376
376
  end
377
-
378
377
  end
379
378
 
380
379
  # Best. Name. Ever
381
- def deunknownify_attribute(record, attr)
382
- [attr, "#{attr}_change", "#{attr}_was", "#{attr}_changed?", :changed?, :changed, :changes].each do |m|
383
- record.metaclass.send :remove_method, m.to_sym
380
+ def deunknownify_attribute(attr)
381
+ attr = attr.to_sym
382
+
383
+ metaclass.send :remove_method, attr
384
+
385
+ if (refl = self.class.reflections[attr]) && refl.macro == :belongs_to
386
+ # A belongs_to -- restore the underlying fields
387
+ deunknownify_attribute refl.primary_key_name
388
+ deunknownify_attribute refl.options[:foreign_type] if refl.options[:polymorphic]
389
+ else
390
+ # A regular field -- restore the dirty tracking methods
391
+ ["#{attr}_change", "#{attr}_was", "#{attr}_changed?", :changed?, :changed, :changes].each do |m|
392
+ metaclass.send :remove_method, m.to_sym
393
+ end
384
394
  end
385
395
  end
386
396
  end