hobo 0.7.2 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. data/bin/hobo +24 -7
  2. data/hobo_files/plugin/CHANGES.txt +501 -0
  3. data/hobo_files/plugin/generators/hobo/hobo_generator.rb +8 -6
  4. data/hobo_files/plugin/generators/hobo/templates/application.dryml +3 -0
  5. data/hobo_files/plugin/generators/hobo/templates/dryml-support.js +132 -0
  6. data/hobo_files/plugin/generators/hobo_front_controller/hobo_front_controller_generator.rb +4 -5
  7. data/hobo_files/plugin/generators/hobo_model_resource/hobo_model_resource_generator.rb +75 -0
  8. data/hobo_files/plugin/generators/hobo_model_resource/templates/controller.rb +7 -0
  9. data/hobo_files/plugin/generators/hobo_model_resource/templates/functional_test.rb +8 -0
  10. data/hobo_files/plugin/generators/hobo_model_resource/templates/helper.rb +2 -0
  11. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo-rapid.js +30 -11
  12. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/clean/public/stylesheets/application.css +149 -92
  13. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/clean/public/stylesheets/rapid-ui.css +0 -48
  14. data/hobo_files/plugin/init.rb +45 -13
  15. data/hobo_files/plugin/lib/action_view_extensions/base.rb +4 -3
  16. data/hobo_files/plugin/lib/active_record/association_proxy.rb +18 -0
  17. data/hobo_files/plugin/lib/active_record/association_reflection.rb +5 -0
  18. data/hobo_files/plugin/lib/active_record/has_many_association.rb +7 -11
  19. data/hobo_files/plugin/lib/active_record/has_many_through_association.rb +8 -0
  20. data/hobo_files/plugin/lib/extensions/test_case.rb +1 -1
  21. data/hobo_files/plugin/lib/hobo.rb +38 -60
  22. data/hobo_files/plugin/lib/hobo/authentication_support.rb +1 -1
  23. data/hobo_files/plugin/lib/hobo/bundle.rb +131 -34
  24. data/hobo_files/plugin/lib/hobo/composite_model.rb +1 -1
  25. data/hobo_files/plugin/lib/hobo/controller.rb +7 -8
  26. data/hobo_files/plugin/lib/hobo/dev_controller.rb +21 -0
  27. data/hobo_files/plugin/lib/hobo/dryml/dryml_builder.rb +14 -8
  28. data/hobo_files/plugin/lib/hobo/dryml/dryml_support_controller.rb +13 -0
  29. data/hobo_files/plugin/lib/hobo/dryml/taglib.rb +6 -7
  30. data/hobo_files/plugin/lib/hobo/dryml/template.rb +207 -73
  31. data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +67 -55
  32. data/hobo_files/plugin/lib/hobo/dryml/template_handler.rb +53 -3
  33. data/hobo_files/plugin/lib/hobo/hobo_helper.rb +75 -107
  34. data/hobo_files/plugin/lib/hobo/model.rb +236 -429
  35. data/hobo_files/plugin/lib/hobo/model_controller.rb +277 -437
  36. data/hobo_files/plugin/lib/hobo/model_router.rb +62 -29
  37. data/hobo_files/plugin/lib/hobo/rapid_helper.rb +48 -9
  38. data/hobo_files/plugin/lib/hobo/scopes.rb +98 -0
  39. data/hobo_files/plugin/lib/hobo/scopes/association_proxy_extensions.rb +31 -0
  40. data/hobo_files/plugin/lib/hobo/scopes/automatic_scopes.rb +282 -0
  41. data/hobo_files/plugin/lib/hobo/scopes/defined_scope_proxy_extender.rb +88 -0
  42. data/hobo_files/plugin/lib/hobo/scopes/scope_reflection.rb +18 -0
  43. data/hobo_files/plugin/lib/hobo/scopes/scoped_proxy.rb +59 -0
  44. data/hobo_files/plugin/lib/hobo/undefined.rb +2 -0
  45. data/hobo_files/plugin/lib/hobo/user.rb +31 -14
  46. data/hobo_files/plugin/lib/hobo/user_controller.rb +41 -27
  47. data/hobo_files/plugin/taglibs/core.dryml +9 -11
  48. data/hobo_files/plugin/taglibs/rapid.dryml +51 -108
  49. data/hobo_files/plugin/taglibs/rapid_editing.dryml +25 -25
  50. data/hobo_files/plugin/taglibs/rapid_forms.dryml +111 -79
  51. data/hobo_files/plugin/taglibs/rapid_generics.dryml +74 -0
  52. data/hobo_files/plugin/taglibs/rapid_navigation.dryml +23 -21
  53. data/hobo_files/plugin/taglibs/rapid_pages.dryml +83 -169
  54. data/hobo_files/plugin/taglibs/rapid_plus.dryml +16 -2
  55. data/hobo_files/plugin/taglibs/rapid_support.dryml +3 -3
  56. data/hobo_files/plugin/taglibs/rapid_user_pages.dryml +104 -0
  57. metadata +60 -55
  58. data/hobo_files/plugin/generators/hobo_migration/hobo_migration_generator.rb +0 -276
  59. data/hobo_files/plugin/generators/hobo_migration/templates/migration.rb +0 -9
  60. data/hobo_files/plugin/lib/active_record/table_definition.rb +0 -34
  61. data/hobo_files/plugin/lib/extensions.rb +0 -375
  62. data/hobo_files/plugin/lib/hobo/email_address.rb +0 -12
  63. data/hobo_files/plugin/lib/hobo/enum_string.rb +0 -50
  64. data/hobo_files/plugin/lib/hobo/field_declaration_dsl.rb +0 -43
  65. data/hobo_files/plugin/lib/hobo/field_spec.rb +0 -68
  66. data/hobo_files/plugin/lib/hobo/html_string.rb +0 -7
  67. data/hobo_files/plugin/lib/hobo/lazy_hash.rb +0 -40
  68. data/hobo_files/plugin/lib/hobo/markdown_string.rb +0 -11
  69. data/hobo_files/plugin/lib/hobo/migrations.rb +0 -12
  70. data/hobo_files/plugin/lib/hobo/model_queries.rb +0 -117
  71. data/hobo_files/plugin/lib/hobo/password_string.rb +0 -7
  72. data/hobo_files/plugin/lib/hobo/percentage.rb +0 -14
  73. data/hobo_files/plugin/lib/hobo/predicate_dispatch.rb +0 -78
  74. data/hobo_files/plugin/lib/hobo/proc_binding.rb +0 -32
  75. data/hobo_files/plugin/lib/hobo/text.rb +0 -3
  76. data/hobo_files/plugin/lib/hobo/textile_string.rb +0 -25
  77. data/hobo_files/plugin/lib/hobo/where_fragment.rb +0 -28
@@ -17,11 +17,6 @@ table.new-record textarea, table.new-record input {
17
17
  display: inline;
18
18
  }
19
19
 
20
- .edit-page .content-header {overflow: hidden; height: 100%;}
21
- .edit-page .content-header h1 {float: left;}
22
- .edit-page .content-header .delete-button {float: right;}
23
- form .actions {margin: 30px 0;width: 100%; text-align: center;}
24
-
25
20
  /**** Admin ****/
26
21
 
27
22
  .admin-banner {
@@ -39,25 +34,6 @@ form .actions {margin: 30px 0;width: 100%; text-align: center;}
39
34
  float: right;
40
35
  }
41
36
 
42
- /* rails error message */
43
- .error-messages {
44
- font-family: "Lucida Grande", arial, sans-serif;
45
- background: #9d0018;
46
- border: 1px solid #7a0013;
47
- padding: 15px 30px;
48
- color: white;
49
- margin-bottom: 20px;
50
- }
51
- .error-messages h2 {
52
- text-transform: none;
53
- letter-spacing: normal;
54
- color: white;
55
- margin-bottom: 10px;
56
- }
57
- .error-messages li {
58
- margin-left: 20px;
59
- }
60
-
61
37
  /********* everything below here came from hobo_rapid.css, needs looking at ********/
62
38
 
63
39
  /**** Default styling for Rapid ***/
@@ -66,30 +42,6 @@ form .actions {margin: 30px 0;width: 100%; text-align: center;}
66
42
  float: right; margin: 20px;
67
43
  position: fixed; display: none; z-index: 10;
68
44
  }
69
- /*
70
- #ajax-progress {
71
- color: grey;
72
- float: right;
73
- margin: 20px;
74
- position: fixed;
75
- background: white;
76
- font-family: Tahoma "sans serif";
77
- display: none;
78
- z-index: 10;
79
- }
80
-
81
- #ajax-progress div {
82
- border: 1px dashed grey;
83
- margin: 10px;
84
- padding: 3px;
85
- padding-top: -15px;
86
- }
87
-
88
- #ajax-progress img {
89
- padding-left: 6px;
90
- vertical-align: middle;
91
- }
92
- */
93
45
 
94
46
  /* Scriptaculous Autocompleter ---*/
95
47
 
@@ -1,16 +1,21 @@
1
+ # gem dependencies
2
+ require 'hobosupport'
3
+
4
+ # Force load:
5
+ HoboFields
6
+
1
7
  # Monkey patches, ooh ooh
2
- require 'extensions'
3
8
  require 'rexml'
4
9
  require 'active_record/has_many_association'
5
10
  require 'active_record/has_many_through_association'
6
- require 'active_record/table_definition'
11
+ require 'active_record/association_proxy'
12
+ require 'active_record/association_reflection'
7
13
  require 'action_view_extensions/base'
8
14
 
9
15
  require 'hobo'
10
16
  require 'hobo/dryml'
11
17
 
12
18
  require 'hobo/model'
13
- require 'hobo/field_declaration_dsl'
14
19
 
15
20
  require 'hobo/dryml/template'
16
21
  require 'hobo/dryml/taglib'
@@ -19,16 +24,6 @@ require 'hobo/dryml/template_handler'
19
24
 
20
25
  require 'extensions/test_case' if RAILS_ENV == "test"
21
26
 
22
- # Rich data types
23
- require "hobo/html_string"
24
- require "hobo/markdown_string"
25
- require "hobo/textile_string"
26
- require "hobo/password_string"
27
- require "hobo/text"
28
- require "hobo/email_address"
29
- require "hobo/enum_string"
30
- require "hobo/percentage"
31
-
32
27
 
33
28
  ActionView::Base.register_template_handler("dryml", Hobo::Dryml::TemplateHandler)
34
29
 
@@ -54,6 +49,7 @@ end
54
49
  class ActiveRecord::Base
55
50
  def self.hobo_model
56
51
  include Hobo::Model
52
+ fields # force hobofields to load
57
53
  end
58
54
  def self.hobo_user_model
59
55
  include Hobo::Model
@@ -64,3 +60,39 @@ end
64
60
  # Default settings
65
61
 
66
62
  Hobo.developer_features = RAILS_ENV.in?(["development", "test"]) if Hobo.developer_features?.nil?
63
+
64
+
65
+ module ::Hobo
66
+ # Empty class to represent the boolean type.
67
+ class Boolean; end
68
+ end
69
+
70
+
71
+ if defined? HoboFields
72
+ HoboFields.never_wrap(Hobo::Undefined)
73
+ end
74
+
75
+
76
+ # Add support for type metadata to arrays
77
+ class ::Array
78
+
79
+ attr_accessor :member_class, :origin, :origin_attribute
80
+
81
+ def to_url_path
82
+ base_path = origin_object.try.to_url_path
83
+ "#{base_path}/#{origin_attribute}" unless base_path.blank?
84
+ end
85
+
86
+ def typed_id
87
+ origin_id = origin.try.typed_id
88
+ "#{origin_id}_#{origin_attribute}" if origin_id
89
+ end
90
+
91
+ end
92
+
93
+
94
+ class NilClass
95
+ def typed_id
96
+ "nil"
97
+ end
98
+ end
@@ -2,12 +2,13 @@ module ActionView
2
2
 
3
3
  class Base
4
4
 
5
- alias_method :render_file_without_hobo, :render_file
6
- def render_file(template_path, *args)
5
+ def render_file_with_dryml(template_path, *args)
7
6
  @hobo_template_path = template_path
8
- render_file_without_hobo(template_path, *args)
7
+ render_file_without_dryml(template_path, *args)
9
8
  end
10
9
 
10
+ alias_method_chain :render_file, :dryml
11
+
11
12
  end
12
13
 
13
14
  end
@@ -0,0 +1,18 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class AssociationProxy #:nodoc:
4
+
5
+ private
6
+
7
+
8
+ def raise_on_type_mismatch(record)
9
+ # Don't complain if the interface type of a polymorphic association doesn't exist
10
+ klass = @reflection.klass rescue nil
11
+ unless klass.nil? || record.is_a?(klass)
12
+ raise ActiveRecord::AssociationTypeMismatch, "#{@reflection.klass} expected, got #{record.class}"
13
+ end
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ class ActiveRecord::Reflection::AssociationReflection
2
+
3
+ alias_method :association_name, :name
4
+
5
+ end
@@ -18,7 +18,7 @@ module ActiveRecord::Associations
18
18
  record = @reflection.klass.new(attributes)
19
19
  if hobo_has_many?
20
20
  set_belongs_to_association_for(record)
21
- set_reverse_association(record)
21
+ set_reverse_association(record) unless proxy_reflection.options[:as]
22
22
  end
23
23
  record
24
24
  end
@@ -28,17 +28,13 @@ module ActiveRecord::Associations
28
28
  proxy_reflection.klass
29
29
  end
30
30
 
31
-
32
- def find_with_block(*args, &b)
33
- if b
34
- options = args.extract_options!
35
- args << options.merge(:conditions => member_class.conditions(&b))
36
- find_without_block(*args)
37
- else
38
- find_without_block(*args)
39
- end
31
+ def origin
32
+ proxy_owner
33
+ end
34
+
35
+ def origin_attribute
36
+ proxy_reflection.association_name
40
37
  end
41
- alias_method_chain :find, :block
42
38
 
43
39
  private
44
40
 
@@ -5,6 +5,14 @@ module ActiveRecord::Associations
5
5
  def member_class
6
6
  proxy_reflection.klass
7
7
  end
8
+
9
+ def origin
10
+ proxy_owner
11
+ end
12
+
13
+ def origin_attribute
14
+ proxy_reflection.association_name
15
+ end
8
16
 
9
17
  end
10
18
 
@@ -102,7 +102,7 @@ class Test::Unit::TestCase
102
102
  def replace_objects_in_params!(hash)
103
103
  hash.each do |k,v|
104
104
  if v.is_a? ActiveRecord::Base
105
- hash[k] = "@" + Hobo.dom_id(v)
105
+ hash[k] = "@" + v.typed_id
106
106
  elsif v.is_a? Hash
107
107
  replace_objects_in_params!(v)
108
108
  end
@@ -5,20 +5,12 @@ module Hobo
5
5
  class RawJs < String; end
6
6
 
7
7
  @models = []
8
- @field_types = HashWithIndifferentAccess.new
9
8
 
10
9
  class << self
11
10
 
12
- attr_accessor :current_theme, :field_types
11
+ attr_accessor :current_theme
13
12
  attr_writer :developer_features
14
13
 
15
- def symbolic_type_name(type)
16
- field_types.index(type)
17
- end
18
-
19
- def type_id(type)
20
- symbolic_type_name(type) || type.name.underscore.gsub("/", "__")
21
- end
22
14
 
23
15
  def developer_features?
24
16
  @developer_features
@@ -41,7 +33,7 @@ module Hobo
41
33
 
42
34
 
43
35
  def models=(models)
44
- @models = models.every(:name)
36
+ @models = models.*.name
45
37
  end
46
38
 
47
39
 
@@ -52,7 +44,7 @@ module Hobo
52
44
  end
53
45
  @models_loaded = true
54
46
  end
55
- @models.every(:constantize)
47
+ @models.*.constantize
56
48
  end
57
49
 
58
50
 
@@ -87,64 +79,38 @@ module Hobo
87
79
  end
88
80
 
89
81
  def dom_id(obj, attr=nil)
90
- if obj.nil?
91
- raise ArgumentError, "Tried to get dom id of nil.#{attr}" if attr
92
- return 'nil'
93
- end
94
-
95
- if obj.is_a?(Array) and obj.respond_to?(:proxy_owner)
96
- attr = obj.proxy_reflection.name
97
- obj = obj.proxy_owner
98
- elsif obj.is_a?(Class)
99
- return type_id(obj)
100
- elsif !obj.respond_to?(:typed_id)
101
- return (if attr
102
- dom_id(get_field(obj, attr))
103
- elsif obj.respond_to?(:id)
104
- "#{obj.class.name.underscore}_#{obj.id}"
105
- else
106
- raise ArgumentError, "Can't create dom id for #{obj.inspect}"
107
- end)
108
- end
109
82
  attr ? "#{obj.typed_id}_#{attr}" : obj.typed_id
110
83
  end
111
84
 
112
- def find_by_search(query)
113
- sql = Hobo.models.map do |model|
114
- if model.superclass == ActiveRecord::Base && # filter out STI subclasses
115
- ModelRouter.linkable?(nil, model, :show) # filter out non-linkables
116
- cols = model.search_columns
117
- if cols.blank?
118
- nil
119
- else
120
- where = cols.map {|c| "(#{c} like ?)"}.join(' or ')
121
- type = model.column_names.include?("type") ? "type" : "'#{model.name}'"
122
- ActiveRecord::Base.send(:sanitize_sql,
123
- ["select #{type} as type, id " +
124
- "from #{model.table_name} " +
125
- "where #{where}"] +
126
- ["%#{query}%"] * cols.length)
85
+ def find_by_search(query, search_targets=nil)
86
+ search_targets ||=
87
+ begin
88
+ # FIXME: This should interrogate the model-router directly, there's no need to enumerate models
89
+ # By default, search all models, but filter out...
90
+ Hobo.models.select do |m|
91
+ ModelRouter.linkable?(m, :show) && # ...non-linkables
92
+ m.search_columns.any? # and models with no search-columns
127
93
  end
128
94
  end
129
- end.compact.join(" union ")
130
-
131
- rows = ActiveRecord::Base.connection.select_all(sql)
132
- records = Hash.new {|h,k| h[k] = []}
133
- for row in rows
134
- records[row['type']] << row['id']
135
- end
136
- results = []
137
- for type, ids in records
138
- results.concat(type.constantize.find(:all, :conditions => "id in (#{ids * ','})"))
139
- end
140
95
 
141
- results
96
+ query_words = ActiveRecord::Base.connection.quote_string(query).split
97
+
98
+ search_targets.build_hash do |search_target|
99
+ conditions = query_words.map do |word|
100
+ "(" + search_target.search_columns.map { |column| %(#{column} like "%#{word}%") }.join(" or ") + ")"
101
+ end.join(" and ")
102
+
103
+ results = search_target.find(:all, :conditions => conditions)
104
+ [search_target.name, results] unless results.empty?
105
+ end
142
106
  end
143
107
 
144
108
  def add_routes(m)
145
109
  Hobo::ModelRouter.add_routes(m)
146
110
  end
147
111
 
112
+
113
+ # FIXME: This method won't be needed
148
114
  def all_models
149
115
  Hobo.models.map { |m| m.name.underscore }
150
116
  end
@@ -159,6 +125,16 @@ module Hobo
159
125
  end
160
126
 
161
127
 
128
+ def can_create_in_association?(array_or_reflection)
129
+ refl =
130
+ (array_or_reflection.is_a?(ActiveRecord::Reflection::AssociationReflection) and array_or_reflection) or
131
+ array_or_reflection.try.proxy_reflection or
132
+ (origin = array_or_reflection.try.origin and origin.send(array_or_reflection.origin_attribute).try.proxy_reflection)
133
+
134
+ refl && refl.macro == :has_many && (!refl.through_reflection) && (!refl.options[:conditions])
135
+ end
136
+
137
+
162
138
  def get_field(object, field)
163
139
  return nil if object.nil?
164
140
  if field.to_s =~ /^\d+$/
@@ -213,10 +189,12 @@ module Hobo
213
189
  return false if !can_view?(person, object, field)
214
190
 
215
191
  if field.nil?
216
- if respond_to?(:editable_by?)
192
+ if object.has_hobo_method?(:editable_by?)
217
193
  object.editable_by?(person)
218
- else
194
+ elsif object.has_hobo_method?(:updatable_by?)
219
195
  object.updatable_by?(person, nil)
196
+ else
197
+ false
220
198
  end
221
199
 
222
200
  else
@@ -333,7 +311,7 @@ module Hobo
333
311
  else
334
312
  File.join(File.dirname(__FILE__), "hobo/static_tags")
335
313
  end
336
- File.readlines(path).every(:chop)
314
+ File.readlines(path).*.chop
337
315
  end
338
316
  end
339
317
 
@@ -38,7 +38,7 @@ module Hobo
38
38
  # skip_before_filter :login_required
39
39
  #
40
40
  def login_required(user_model=nil)
41
- auth_model = user_model || UserController.user_models.first
41
+ auth_model = user_model || User.default_user_model
42
42
  if current_user.guest?
43
43
  username, passwd = get_auth_data
44
44
  self.current_user = auth_model.authenticate(username, passwd) || nil if username && passwd && auth_model
@@ -1,5 +1,3 @@
1
- require 'extensions'
2
-
3
1
  module ::Hobo
4
2
 
5
3
  class Bundle
@@ -8,48 +6,93 @@ module ::Hobo
8
6
 
9
7
  class << self
10
8
 
11
- attr_accessor :bundles, :plugin
9
+ # Hobo::Bundle.bundles is a hash of all instantiated bundles by name
10
+ attr_accessor :bundles
11
+
12
+ # Used by subclasses, e.g MyBundle.plugin is the name of the
13
+ # plugin the bundle came from
14
+ attr_reader :plugin
15
+
16
+ attr_reader :model_declarations, :controller_declarations
17
+
18
+ attr_accessor :dirname
12
19
 
13
20
  def inherited(base)
14
21
  filename = caller[0].match(/^(.*):\d+/)[1]
15
- dirname = filename.match(%r(^.*/plugins/[^/]+))[0]
16
- base.plugin = File.basename(dirname)
22
+ base.dirname = filename.match(%r(^.*/plugins/[^/]+))[0]
23
+ end
24
+
25
+
26
+ def load_models_and_controllers
27
+ return if models_and_controllers_loaded?
17
28
 
18
- base.meta_eval do
19
- attr_accessor :models, :controllers
20
- end
29
+ @plugin = File.basename(dirname)
21
30
 
22
- base.models = []
23
- base.controllers = []
31
+ @model_declarations = []
32
+ @controller_declarations = []
24
33
 
25
- eval_ruby_files(base, "#{dirname}/models")
26
- eval_ruby_files(base, "#{dirname}/controllers")
34
+ class_eval do
35
+ eval_ruby_files("#{dirname}/models", @models)
36
+ eval_ruby_files("#{dirname}/controllers", @controllers)
37
+ end
38
+ end
39
+
40
+ def [](bundle_name)
41
+ bundles[bundle_name]
27
42
  end
28
43
 
44
+
45
+ private
29
46
 
30
47
  def bundle_model(name, &block)
31
- models << [name, block]
48
+ @model_declarations << [name, block]
32
49
  end
33
50
 
34
51
 
35
52
  def bundle_model_controller(model_name, &block)
36
- controllers << [model_name, block]
53
+ @controller_declarations << [model_name, block]
37
54
  end
55
+
38
56
 
57
+ def models_and_controllers_loaded?
58
+ @model_declarations
59
+ end
39
60
 
40
- private
41
61
 
42
- def eval_ruby_files(base, dir)
43
- Dir["#{dir}/*.rb"].each do |f|
44
- base.instance_eval(File.read(f), f, 1)
45
- end
62
+
63
+ def eval_ruby_files(dir, filenames)
64
+ files = if filenames == [:none]
65
+ []
66
+ elsif filenames.blank? || filenames == [:all]
67
+ Dir["#{dir}/*.rb"]
68
+ else
69
+ filenames.map { |f| "#{dir}/#{f}.rb" }
70
+ end
71
+
72
+ files.each { |f| instance_eval(File.read(f), f, 1) }
73
+ end
74
+
75
+
76
+ # Declatations
77
+
78
+ def models(*models)
79
+ @models = models
80
+ end
81
+
82
+ def controllers(*controllers)
83
+ @controllers = controllers.map {|c| case c.to_s
84
+ when /controller$/, "all", "none" then c
85
+ else "#{c.to_s.pluralize}_controller"
86
+ end }
46
87
  end
47
88
 
48
89
  end
49
90
 
50
91
  def initialize(*args)
92
+ self.class.load_models_and_controllers
93
+
51
94
  options = defaults.with_indifferent_access
52
- options.update(args.extract_options!)
95
+ options.recursive_update(args.extract_options!)
53
96
 
54
97
  self.name = args.first || self.class.name.match(/[^:]+$/)[0].underscore
55
98
  Bundle.bundles[name] = self
@@ -78,18 +121,52 @@ module ::Hobo
78
121
 
79
122
 
80
123
  def create_models
81
- self.class.models.each do |name, block|
82
- klass = make_class(new_name_for(name), ActiveRecord::Base) do
83
- hobo_model
124
+ self.class.model_declarations.each do |name, block|
125
+ klass = make_class(new_name_for(name), ActiveRecord::Base)
126
+
127
+ klass.meta_def :belongs_to_with_optional_polymorphism do |*args|
128
+ opts = args.extract_options!
129
+
130
+ if opts[:polymorphic] == :optional
131
+ if bundle.options["polymorphic_#{name}"]
132
+ opts[:polymorphic] = true
133
+ opts.delete(:class_name)
134
+ else
135
+ opts.delete(:polymorphic)
136
+ end
137
+ end
138
+ belongs_to_without_optional_polymorphism(name, opts)
84
139
  end
85
- klass.class_eval(&block)
140
+ klass.meta_eval { alias_method_chain :belongs_to, :optional_polymorphism }
141
+
142
+ klass.class_eval { hobo_model }
143
+
144
+ # FIXME this extension breaks passing a block to belongs_to
145
+ klass.meta_def :belongs_to_with_alias do |*args|
146
+ opts = args.extract_options!
147
+ name = args.first.to_sym
148
+
149
+ alias_name = opts.delete(:alias)
150
+
151
+ belongs_to_without_alias(name, opts)
152
+
153
+ if alias_name && name != alias_name
154
+ klass.send(:alias_method, alias_name, name)
155
+ # make the aliased name available in the classes metadata
156
+ klass.reflections[alias_name] = klass.reflections[name]
157
+ end
158
+
159
+ end
160
+ klass.meta_eval { alias_method_chain :belongs_to, :alias }
161
+
162
+ klass.class_eval(&block)
86
163
  end
87
164
  end
88
165
 
89
166
 
90
167
  def create_controllers
91
168
  bundle = self
92
- self.class.controllers.each do |model_name, block|
169
+ self.class.controller_declarations.each do |model_name, block|
93
170
  klass = make_class("#{new_name_for(model_name).to_s.pluralize}Controller", ApplicationController) do
94
171
  hobo_model_controller
95
172
  end
@@ -106,6 +183,14 @@ module ::Hobo
106
183
  def self.feature(name, &block)
107
184
  _feature(name, block)
108
185
  end
186
+
187
+ def method_missing(name, *args)
188
+ if name.to_s =~ /^_.*_$/
189
+ self.class.bundle.magic_option(name)
190
+ else
191
+ super
192
+ end
193
+ end
109
194
  end
110
195
 
111
196
  klass.meta_def(:bundle) do
@@ -134,19 +219,23 @@ module ::Hobo
134
219
  end
135
220
 
136
221
  klass.class_eval(&b) if b
222
+
137
223
  klass
138
224
  end
139
225
 
140
226
 
141
227
  def new_name_for(name)
142
- while renames.has_key?(name)
143
- name = renames[name]
144
- name2 = name.to_s.gsub(/_.*?_/) { |s| new_name_for(s[1..-2]) }
145
-
146
- # Make sure symbols stay symbols
147
- name = name.is_a?(Symbol) ? name2.to_sym : name2
228
+ while true
229
+ if renames.has_key?(name)
230
+ name = renames[name]
231
+ elsif name.to_s =~ /_.*?_/
232
+ name2 = name.to_s.gsub(/_.*?_/) { |s| new_name_for(s[1..-2]) }
233
+ # Make sure symbols stay symbols
234
+ name = name.is_a?(Symbol) ? name2.to_sym : name2
235
+ else
236
+ return name
237
+ end
148
238
  end
149
- name
150
239
  end
151
240
 
152
241
 
@@ -169,7 +258,7 @@ module ::Hobo
169
258
  new_name_for(name).to_s.constantize.class_eval(&block)
170
259
  end
171
260
 
172
-
261
+
173
262
  def method_missing(name, *args)
174
263
  if name.to_s =~ /^_.*_$/
175
264
  magic_option(name)
@@ -213,7 +302,15 @@ module ::Hobo
213
302
  external_options = self.options[option_name]
214
303
  external_options = {} if external_options.nil? || external_options == true
215
304
  name = "#{self.name}_#{option_name}"
216
- class_name.to_s.constantize.new(name, external_options.merge(local_options).merge(renames))
305
+
306
+ sub_bundle_options = external_options.merge(local_options).merge(renames)
307
+ sub_bundle = class_name.to_s.constantize.new(name, sub_bundle_options)
308
+
309
+ conflicting_renames = (renames.keys & sub_bundle.renames.keys).select { |k| renames[k] != sub_bundle.renames[k] }
310
+ unless conflicting_renames.empty?
311
+ raise ArgumentError, "Conflicting renames in included bundle '#{name}' of '#{self.name}': #{conflicting_renames * ', '}"
312
+ end
313
+ renames.update(sub_bundle.renames)
217
314
  self.options["#{option_name}_bundle"] = name
218
315
  end
219
316