hobo 0.7.2 → 0.7.3

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