hobo 0.7.0 → 0.7.1

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 (48) hide show
  1. data/hobo_files/plugin/CHANGES.txt +220 -23
  2. data/hobo_files/plugin/generators/hobo_front_controller/templates/index.dryml +18 -25
  3. data/hobo_files/plugin/generators/hobo_migration/hobo_migration_generator.rb +20 -15
  4. data/hobo_files/plugin/generators/hobo_model/templates/model.rb +3 -3
  5. data/hobo_files/plugin/generators/hobo_rapid/hobo_rapid_generator.rb +3 -3
  6. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo-rapid.css +1 -2
  7. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo-rapid.js +21 -4
  8. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/clean/public/images/fieldbg.gif +0 -0
  9. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/clean/public/images/spinner.gif +0 -0
  10. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/clean/public/stylesheets/application.css +154 -26
  11. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/clean/public/stylesheets/rapid-ui.css +144 -0
  12. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/views/application.dryml +1 -1
  13. data/hobo_files/plugin/generators/hobo_user_controller/templates/controller.rb +1 -1
  14. data/hobo_files/plugin/generators/hobo_user_model/templates/model.rb +8 -11
  15. data/hobo_files/plugin/init.rb +0 -2
  16. data/hobo_files/plugin/lib/active_record/has_many_association.rb +0 -9
  17. data/hobo_files/plugin/lib/active_record/has_many_through_association.rb +0 -10
  18. data/hobo_files/plugin/lib/hobo.rb +57 -44
  19. data/hobo_files/plugin/lib/hobo/bundle.rb +222 -0
  20. data/hobo_files/plugin/lib/hobo/controller.rb +2 -5
  21. data/hobo_files/plugin/lib/hobo/dryml.rb +8 -7
  22. data/hobo_files/plugin/lib/hobo/dryml/dryml_builder.rb +10 -21
  23. data/hobo_files/plugin/lib/hobo/dryml/taglib.rb +107 -80
  24. data/hobo_files/plugin/lib/hobo/dryml/template.rb +27 -20
  25. data/hobo_files/plugin/lib/hobo/enum_string.rb +1 -1
  26. data/hobo_files/plugin/lib/hobo/field_declaration_dsl.rb +7 -0
  27. data/hobo_files/plugin/lib/hobo/guest.rb +4 -0
  28. data/hobo_files/plugin/lib/hobo/hobo_helper.rb +37 -9
  29. data/hobo_files/plugin/lib/hobo/model.rb +79 -17
  30. data/hobo_files/plugin/lib/hobo/model_controller.rb +59 -60
  31. data/hobo_files/plugin/lib/hobo/model_router.rb +16 -4
  32. data/hobo_files/plugin/lib/hobo/rapid_helper.rb +2 -1
  33. data/hobo_files/plugin/lib/hobo/user.rb +10 -7
  34. data/hobo_files/plugin/lib/hobo/user_controller.rb +6 -6
  35. data/hobo_files/plugin/{tags → taglibs}/core.dryml +5 -4
  36. data/hobo_files/plugin/{tags → taglibs}/rapid.dryml +54 -7
  37. data/hobo_files/plugin/{tags → taglibs}/rapid_document_tags.dryml +0 -0
  38. data/hobo_files/plugin/{tags → taglibs}/rapid_editing.dryml +4 -2
  39. data/hobo_files/plugin/{tags → taglibs}/rapid_forms.dryml +1 -4
  40. data/hobo_files/plugin/{tags → taglibs}/rapid_navigation.dryml +1 -2
  41. data/hobo_files/plugin/taglibs/rapid_pages.dryml +411 -0
  42. data/hobo_files/plugin/{tags → taglibs}/rapid_plus.dryml +0 -0
  43. data/hobo_files/plugin/{tags → taglibs}/rapid_support.dryml +9 -6
  44. data/hobo_files/plugin/tasks/fix_dryml.rake +0 -1
  45. metadata +16 -14
  46. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/clean/public/stylesheets/rapid_ui.css +0 -167
  47. data/hobo_files/plugin/lib/hobo/plugins.rb +0 -75
  48. data/hobo_files/plugin/tags/rapid_pages.dryml +0 -341
@@ -2,6 +2,10 @@ module Hobo
2
2
 
3
3
  module Model
4
4
 
5
+ NAME_FIELD_GUESS = %w(name title)
6
+ PRIMARY_CONTENT_GUESS = %w(description body content profile)
7
+ SEARCH_COLUMNS_GUESS = %w(name title body content profile)
8
+
5
9
  PLAIN_TYPES = { :boolean => TrueClass,
6
10
  :date => Date,
7
11
  :datetime => Time,
@@ -18,10 +22,12 @@ module Hobo
18
22
  base.extend(ClassMethods)
19
23
  base.class_eval do
20
24
  alias_method_chain :attributes=, :hobo_type_conversion
25
+ default_scopes
21
26
  end
22
27
  class << base
23
28
  alias_method_chain :has_many, :defined_scopes
24
29
  alias_method_chain :belongs_to, :foreign_key_declaration
30
+ alias_method_chain :belongs_to, :hobo_metadata
25
31
  alias_method_chain :acts_as_list, :fields if defined?(ActiveRecord::Acts::List)
26
32
  def inherited(klass)
27
33
  fields do
@@ -37,6 +43,44 @@ module Hobo
37
43
  # include methods also shared by CompositeModel
38
44
  include ModelSupport::ClassMethods
39
45
 
46
+ attr_accessor :creator_attribute
47
+ attr_writer :name_attribute, :primary_content_attribute
48
+
49
+ def default_scopes
50
+ def_scope :recent do |*args|
51
+ count = args.first || 3
52
+ { :limit => count, :order => "#{table_name}.created_at DESC" }
53
+ end
54
+ end
55
+
56
+ def name_attribute
57
+ @name_attribute ||= begin
58
+ cols = columns.every :name
59
+ NAME_FIELD_GUESS.detect {|f| f.in? columns.every(:name) }
60
+ end
61
+ end
62
+
63
+
64
+ def primary_content_attribute
65
+ @description_attribute ||= begin
66
+ cols = columns.every :name
67
+ PRIMARY_CONTENT_GUESS.detect {|f| f.in? columns.every(:name) }
68
+ end
69
+ end
70
+
71
+ def dependent_collections
72
+ reflections.values.select do |refl|
73
+ refl.macro == :has_many && refl.options[:dependent]
74
+ end.every(:name)
75
+ end
76
+
77
+
78
+ def dependent_on
79
+ reflections.values.select do |refl|
80
+ refl.macro == :belongs_to && (rev = reverse_reflection(refl.name) and rev.options[:dependent])
81
+ end.every(:name)
82
+ end
83
+
40
84
  private
41
85
 
42
86
  def return_type(type)
@@ -75,6 +119,13 @@ module Hobo
75
119
  end
76
120
  res
77
121
  end
122
+
123
+
124
+ def belongs_to_with_hobo_metadata(name, *args, &block)
125
+ options = args.extract_options!
126
+ self.creator_attribute = name.to_sym if options.delete(:creator)
127
+ belongs_to_without_hobo_metadata(name, *args + [options], &block)
128
+ end
78
129
 
79
130
 
80
131
  def acts_as_list_with_fields(options = {})
@@ -125,12 +176,6 @@ module Hobo
125
176
  end
126
177
  public :never_show?
127
178
 
128
- def set_creator_attr(attr)
129
- @creator_attr = attr.to_sym
130
- end
131
- attr_reader :creator_attr
132
- public :creator_attr
133
-
134
179
  def set_search_columns(*columns)
135
180
  class_eval %{
136
181
  def self.search_columns
@@ -281,12 +326,12 @@ module Hobo
281
326
  end
282
327
 
283
328
  def creator_type
284
- reflections[@creator_attr]._?.klass
329
+ reflections[creator_attribute]._?.klass
285
330
  end
286
331
 
287
332
  def search_columns
288
333
  cols = columns.every(:name)
289
- %w{name title body content}.select{|c| c.in?(cols) }
334
+ SEARCH_COLUMNS_GUESS.select{|c| c.in?(cols) }
290
335
  end
291
336
 
292
337
  # This should really be a method on AssociationReflection
@@ -300,9 +345,9 @@ module Hobo
300
345
  :has_many
301
346
  end
302
347
  refl.klass.reflections.values.find do |r|
303
- r.macro == reverse_macro and
304
- r.klass == self and
305
- !r.options[:conditions] and
348
+ r.macro == reverse_macro &&
349
+ r.klass == self &&
350
+ !r.options[:conditions] &&
306
351
  r.primary_key_name == refl.primary_key_name
307
352
  end
308
353
  end
@@ -457,6 +502,11 @@ module Hobo
457
502
  end
458
503
 
459
504
 
505
+ def dependent_on
506
+ self.class.dependent_on.map { |assoc| send(assoc) }
507
+ end
508
+
509
+
460
510
  def attributes_with_hobo_type_conversion=(attributes, guard_protected_attributes=true)
461
511
  converted = attributes.map_hash { |k, v| convert_type_for_mass_assignment(self.class.field_type(k), v) }
462
512
  send(:attributes_without_hobo_type_conversion=, converted, guard_protected_attributes)
@@ -465,7 +515,17 @@ module Hobo
465
515
 
466
516
 
467
517
  def set_creator(user)
468
- self.send("#{self.class.creator_attr}=", user) if (t = self.class.creator_type) && user.is_a?(t)
518
+ attr = self.class.creator_attribute
519
+ return unless attr
520
+
521
+ # Is creator a string field or an association?
522
+ if self.class.reflections[attr]
523
+ # It's an association
524
+ self.send("#{attr}=", user) if (t = self.class.creator_type) && user.is_a?(t)
525
+ else
526
+ # Assume it's a string field -- set it to the name of the current user
527
+ self.send("#{attr}=", user.to_s) unless user.guest?
528
+ end
469
529
  end
470
530
 
471
531
 
@@ -487,11 +547,15 @@ module Hobo
487
547
 
488
548
 
489
549
  def same_fields?(other, *fields)
550
+ return true if other.nil?
551
+
490
552
  fields = fields.flatten
491
553
  fields.all?{|f| self.send(f) == other.send(f)}
492
554
  end
493
555
 
494
556
  def only_changed_fields?(other, *changed_fields)
557
+ return true if other.nil?
558
+
495
559
  changed_fields = changed_fields.flatten.every(:to_s)
496
560
  all_cols = self.class.columns.every(:name) - []
497
561
  all_cols.all?{|c| c.in?(changed_fields) || self.send(c) == other.send(c) }
@@ -514,12 +578,10 @@ module Hobo
514
578
  end
515
579
 
516
580
  def to_s
517
- if respond_to? :title
518
- title
519
- elsif respond_to? :name
520
- name
581
+ if self.class.name_attribute
582
+ send self.class.name_attribute
521
583
  else
522
- "#{self.class.name.humanize} #{id}"
584
+ "#{self.class.name.titleize} #{id}"
523
585
  end
524
586
  end
525
587
 
@@ -4,18 +4,10 @@ module Hobo
4
4
 
5
5
  include Hobo::Controller
6
6
 
7
- class PermissionDeniedError < RuntimeError; end
8
- class UserPermissionError < StandardError
9
- attr :models
10
- def initialize(models)
11
- @models = models || []
12
- end
13
- end
14
-
15
7
  VIEWLIB_DIR = "taglibs"
16
8
 
17
9
  PAGINATE_FORMATS = [ Mime::HTML, Mime::ALL ]
18
-
10
+
19
11
  class << self
20
12
 
21
13
  def included(base)
@@ -23,38 +15,14 @@ module Hobo
23
15
  @auto_actions ||= {}
24
16
 
25
17
  extend ClassMethods
26
- helper_method :find_partial, :model, :current_user
18
+
19
+ helper_method :model, :current_user
27
20
  before_filter :set_no_cache_headers
28
21
  end
22
+ base.template_path_cache = {}
29
23
 
30
24
  Hobo::Controller.included_in_class(base)
31
25
  end
32
-
33
- def find_partial(klass, as)
34
- find_model_template(klass, as, :is_parital => true)
35
- end
36
-
37
-
38
- def template_path(dir, name, is_partial)
39
- fileRx = is_partial ? /^_#{name}\.[^.]+/ : /^#{name}\.[^.]+/
40
- full_dir = "#{RAILS_ROOT}/app/views/#{dir}"
41
- if File.exists?(full_dir) && Dir.entries(full_dir).grep(fileRx).any?
42
- return "#{dir}/#{name}"
43
- end
44
- end
45
-
46
-
47
- def find_model_template(klass, name, options={})
48
- while klass and klass != ActiveRecord::Base
49
- dir = klass.name.underscore.pluralize
50
- dir = File.join(options[:subsite], dir) if options[:subsite]
51
- path = template_path(dir, name, options[:is_partial])
52
- return path if path
53
-
54
- klass = klass.superclass
55
- end
56
- nil
57
- end
58
26
 
59
27
  end
60
28
 
@@ -62,6 +30,8 @@ module Hobo
62
30
 
63
31
  attr_writer :model
64
32
 
33
+ attr_accessor :template_path_cache
34
+
65
35
  def add_collection_actions(name)
66
36
  defined_methods = instance_methods
67
37
 
@@ -400,6 +370,24 @@ module Hobo
400
370
  end
401
371
 
402
372
 
373
+ def destination_after_create(record)
374
+ # The after_submit post parameter takes priority
375
+ params[:after_submit] ||
376
+
377
+ # Then try the records show page
378
+ object_url(@this, :if_available => true) ||
379
+
380
+ # Then the show page of the 'owning' object if there is one
381
+ (@this.dependent_on.first && object_url(@this.dependent_on.first, :if_available => true)) ||
382
+
383
+ # Last try - the index page for this model
384
+ object_url(@this.class, :if_available => true) ||
385
+
386
+ # Give up
387
+ home_page
388
+ end
389
+
390
+
403
391
  # --- Action implementations --- #
404
392
 
405
393
  def hobo_index(*args, &b)
@@ -478,7 +466,7 @@ module Hobo
478
466
  response_block(&b) or
479
467
  if valid?
480
468
  respond_to do |wants|
481
- wants.html { redirect_to(params[:after_submit] || object_url(@this)) }
469
+ wants.html { redirect_to(destination_after_create(@this)) }
482
470
  wants.js { hobo_ajax_response || render(:text => "") }
483
471
  end
484
472
  elsif invalid?
@@ -510,14 +498,14 @@ module Hobo
510
498
  @this.send(:clear_aggregation_cache)
511
499
  @this.send(:clear_association_cache)
512
500
 
513
- changes = params[model.name.underscore]
501
+ changes = params[@this.class.name.underscore]
514
502
  @this.attributes = changes
515
503
  save_and_set_status!(@this, original)
516
504
 
517
505
  # Ensure current_user isn't out of date
518
506
  @current_user = @this if @this == current_user
519
507
 
520
- flash[:notice] = "Changes to the #{model.name.titleize.downcase} were saved" if !request.xhr? && valid?
508
+ flash[:notice] = "Changes to the #{@this.class.name.titleize.downcase} were saved" if !request.xhr? && valid?
521
509
 
522
510
  set_named_this!
523
511
  response_block(&b) or
@@ -671,32 +659,43 @@ module Hobo
671
659
  end
672
660
  end
673
661
 
662
+
663
+ def hobo_template_exists?(dir, name)
664
+ self.class.template_path_cache.clear if RAILS_ENV == "development"
665
+ self.class.template_path_cache.fetch([dir, name],
666
+ begin
667
+ full_dir = "#{RAILS_ROOT}/app/views/#{dir}"
668
+ !Dir["#{full_dir}/#{name}.*"].empty?
669
+ end)
670
+ end
671
+
672
+
673
+ def find_model_template(klass, name)
674
+ while klass and klass != ActiveRecord::Base
675
+ dir = klass.name.underscore.pluralize
676
+ dir = File.join(subsite, dir) if subsite
677
+ if hobo_template_exists?(dir, name)
678
+ return "#{dir}/#{name}"
679
+ end
680
+ klass = klass.superclass
681
+ end
682
+ nil
683
+ end
674
684
 
685
+
675
686
  def hobo_render(page_kind = nil, page_model=nil)
676
687
  page_kind ||= params[:action].to_sym
677
688
  page_model ||= model
678
-
679
- template = ModelController.find_model_template(page_model, page_kind, :subsite => subsite)
680
689
 
681
- begin
682
- if template
683
- render :template => template
684
- true
685
- else
686
- # This returns false if no such tag exists
687
- render_tag("#{page_kind.to_s.dasherize}-page", :with => @this)
688
- end
689
- rescue ActionView::TemplateError => wrapper
690
- e = wrapper.original_exception if wrapper.respond_to? :original_exception
691
- if e.is_a? Hobo::ModelController::UserPermissionError
692
- if current_user.guest?
693
- redirect_to login_url(e.models.first || UserController.user_models.first)
694
- else
695
- permission_denied(:message => e.message)
696
- end
697
- else
698
- raise
699
- end
690
+ if hobo_template_exists?(controller_path, page_kind)
691
+ render :action => page_kind
692
+ true
693
+ elsif (template = find_model_template(page_model, page_kind))
694
+ render :template => template
695
+ true
696
+ else
697
+ # This returns false if no such tag exists
698
+ render_tag("#{page_kind.to_s.dasherize}-page", :with => @this)
700
699
  end
701
700
  end
702
701
 
@@ -1,13 +1,24 @@
1
+ class ActionController::Routing::RouteSet
2
+ # Monkey patch this method so routes are reloaded on *every*
3
+ # request. Without this Rails checks the mtime of config/routes.rb
4
+ # which doesn't take into account Hobo's auto routing
5
+ def reload
6
+ load!
7
+ end
8
+ end
9
+
1
10
  module Hobo
2
11
 
3
12
  class ModelRouter
4
13
 
5
- @linkable = Hash.new {|h, k| h[k] = Hash.new {|h, k| h[k] = {} } }
6
-
7
14
  APP_ROOT = "#{RAILS_ROOT}/app"
8
15
 
9
16
  class << self
10
17
 
18
+ def reset_linkables
19
+ @linkable = Hash.new {|h, k| h[k] = Hash.new {|h, k| h[k] = {} } }
20
+ end
21
+
11
22
  def linkable(subsite, klass, action)
12
23
  @linkable[subsite][klass.name][action] = true
13
24
  end
@@ -17,6 +28,7 @@ module Hobo
17
28
  end
18
29
 
19
30
  def add_routes(map)
31
+ reset_linkables
20
32
  begin
21
33
  ActiveRecord::Base.connection.reconnect! unless ActiveRecord::Base.connection.active?
22
34
  rescue
@@ -25,8 +37,8 @@ module Hobo
25
37
  end
26
38
 
27
39
  require "#{APP_ROOT}/controllers/application" unless Object.const_defined? :ApplicationController
28
- require "#{APP_ROOT}/assemble.rb" if File.exists? "#{APP_ROOT}/assemble.rb"
29
-
40
+ load "#{APP_ROOT}/assemble.rb" if File.exists? "#{APP_ROOT}/assemble.rb"
41
+
30
42
  # Add non-subsite routes
31
43
  add_routes_for(map, nil)
32
44
 
@@ -11,6 +11,7 @@ module Hobo::RapidHelper
11
11
  js_options['resultUpdate'] = js_result_updates(options[:result_update]) if options[:result_update]
12
12
  js_options['resetForm'] = options[:reset_form] if options.has_key?(:reset_form)
13
13
  js_options['refocusForm'] = options[:refocus_form] if options.has_key?(:refocus_form)
14
+ js_options['spinnerNextTo'] = options[:spinner_next_to] if options.has_key?(:spinner_next_to)
14
15
 
15
16
  js_options.empty? ? nil : options_for_javascript(js_options)
16
17
  end
@@ -97,7 +98,7 @@ module Hobo::RapidHelper
97
98
 
98
99
  AJAX_ATTRS = [:before, :success, :failure, :complete, :type, :method,
99
100
  :script, :form, :params, :confirm,
100
- :reset_form, :refocus_form, :result_update]
101
+ :reset_form, :refocus_form, :result_update, :spinner_next_to]
101
102
 
102
103
 
103
104
  def editor_class
@@ -41,7 +41,7 @@ module Hobo
41
41
  end
42
42
  end
43
43
 
44
- # Additional classmethods for AuthenticatedUser
44
+ # Additional classmethods for authentication
45
45
  module ClassMethods
46
46
 
47
47
  # Validation of the plaintext password
@@ -49,22 +49,20 @@ module Hobo
49
49
  validates_length_of :password, :within => 4..40, :if => :password_required?
50
50
  end
51
51
 
52
- def set_login_attr(attr)
52
+ def login_attribute=(attr, validate=true)
53
53
  @login_attr = attr = attr.to_sym
54
54
  unless attr == :login
55
55
  alias_attribute(:login, attr)
56
56
  set_field_type :login => field_type(attr)
57
57
  end
58
58
 
59
- if block_given?
60
- yield
61
- else
59
+ if validate
62
60
  validates_presence_of attr
63
61
  validates_length_of attr, :within => 3..100
64
62
  validates_uniqueness_of attr, :case_sensitive => false
65
63
  end
66
64
  end
67
- def login_attr; @login_attr; end
65
+ attr_reader :login_attr
68
66
 
69
67
  # Authenticates a user by their login name and unencrypted password. Returns the user or nil.
70
68
  def authenticate(login, password)
@@ -86,6 +84,11 @@ module Hobo
86
84
  def encrypt(password, salt)
87
85
  Digest::SHA1.hexdigest("--#{salt}--#{password}--")
88
86
  end
87
+
88
+
89
+ def set_admin_on_first_user
90
+ before_create { |user| user.administrator = true if count == 0 }
91
+ end
89
92
 
90
93
  end
91
94
 
@@ -121,7 +124,7 @@ module Hobo
121
124
  def guest?
122
125
  false
123
126
  end
124
-
127
+
125
128
  protected
126
129
  # Before filter that encrypts the password before having it stored in the database.
127
130
  def encrypt_password