hobo 0.5.3 → 0.6

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 (80) hide show
  1. data/bin/hobo +18 -4
  2. data/hobo_files/plugin/CHANGES.txt +511 -0
  3. data/hobo_files/plugin/README +8 -3
  4. data/hobo_files/plugin/Rakefile +81 -0
  5. data/hobo_files/plugin/generators/hobo/hobo_generator.rb +4 -4
  6. data/hobo_files/plugin/generators/hobo/templates/guest.rb +1 -1
  7. data/hobo_files/plugin/generators/hobo_front_controller/hobo_front_controller_generator.rb +1 -1
  8. data/hobo_files/plugin/generators/hobo_front_controller/templates/index.dryml +16 -22
  9. data/hobo_files/plugin/generators/hobo_front_controller/templates/login.dryml +4 -6
  10. data/hobo_files/plugin/generators/hobo_front_controller/templates/search.dryml +6 -5
  11. data/hobo_files/plugin/generators/hobo_front_controller/templates/signup.dryml +4 -6
  12. data/hobo_files/plugin/generators/hobo_migration/hobo_migration_generator.rb +237 -0
  13. data/hobo_files/plugin/generators/hobo_migration/templates/migration.rb +9 -0
  14. data/hobo_files/plugin/generators/hobo_model/USAGE +2 -3
  15. data/hobo_files/plugin/generators/hobo_model/hobo_model_generator.rb +1 -14
  16. data/hobo_files/plugin/generators/hobo_model/templates/fixtures.yml +1 -6
  17. data/hobo_files/plugin/generators/hobo_model/templates/model.rb +10 -4
  18. data/hobo_files/plugin/generators/hobo_rapid/hobo_rapid_generator.rb +7 -6
  19. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_base.css +68 -0
  20. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_rapid.css +93 -0
  21. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_rapid.js +11 -6
  22. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/plus.png +0 -0
  23. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/stylesheets/application.css +24 -14
  24. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/views/application.dryml +28 -44
  25. data/hobo_files/plugin/generators/hobo_user_model/USAGE +2 -12
  26. data/hobo_files/plugin/generators/hobo_user_model/hobo_user_model_generator.rb +1 -14
  27. data/hobo_files/plugin/generators/hobo_user_model/templates/fixtures.yml +0 -6
  28. data/hobo_files/plugin/generators/hobo_user_model/templates/model.rb +8 -1
  29. data/hobo_files/plugin/init.rb +6 -2
  30. data/hobo_files/plugin/lib/active_record/has_many_association.rb +23 -12
  31. data/hobo_files/plugin/lib/extensions.rb +134 -40
  32. data/hobo_files/plugin/lib/extensions/test_case.rb +0 -1
  33. data/hobo_files/plugin/lib/hobo.rb +77 -46
  34. data/hobo_files/plugin/lib/hobo/authenticated_user.rb +24 -2
  35. data/hobo_files/plugin/lib/hobo/authentication_support.rb +2 -1
  36. data/hobo_files/plugin/lib/hobo/controller.rb +35 -12
  37. data/hobo_files/plugin/lib/hobo/define_tags.rb +4 -4
  38. data/hobo_files/plugin/lib/hobo/dryml.rb +33 -51
  39. data/hobo_files/plugin/lib/hobo/dryml/dryml_builder.rb +47 -34
  40. data/hobo_files/plugin/lib/hobo/dryml/scoped_variables.rb +37 -0
  41. data/hobo_files/plugin/lib/hobo/dryml/taglib.rb +27 -5
  42. data/hobo_files/plugin/lib/hobo/dryml/template.rb +545 -302
  43. data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +305 -135
  44. data/hobo_files/plugin/lib/hobo/email_address.rb +5 -0
  45. data/hobo_files/plugin/lib/hobo/field_spec.rb +66 -0
  46. data/hobo_files/plugin/lib/hobo/hobo_helper.rb +325 -0
  47. data/hobo_files/plugin/lib/hobo/html_string.rb +2 -0
  48. data/hobo_files/plugin/lib/hobo/lazy_hash.rb +13 -1
  49. data/hobo_files/plugin/lib/hobo/markdown_string.rb +3 -1
  50. data/hobo_files/plugin/lib/hobo/model.rb +185 -66
  51. data/hobo_files/plugin/lib/hobo/model_controller.rb +56 -49
  52. data/hobo_files/plugin/lib/hobo/password_string.rb +2 -0
  53. data/hobo_files/plugin/lib/hobo/plugins.rb +75 -0
  54. data/hobo_files/plugin/lib/hobo/rapid_helper.rb +98 -0
  55. data/hobo_files/plugin/lib/hobo/static_tags +0 -3
  56. data/hobo_files/plugin/lib/hobo/textile_string.rb +11 -1
  57. data/hobo_files/plugin/lib/hobo/undefined.rb +1 -1
  58. data/hobo_files/plugin/lib/rexml.rb +166 -75
  59. data/hobo_files/plugin/spec/fixtures/users.yml +9 -0
  60. data/hobo_files/plugin/spec/spec.opts +6 -0
  61. data/hobo_files/plugin/spec/spec_helper.rb +28 -0
  62. data/hobo_files/plugin/spec/unit/hobo/dryml/template_spec.rb +650 -0
  63. data/hobo_files/plugin/tags/core.dryml +58 -4
  64. data/hobo_files/plugin/tags/rapid.dryml +289 -135
  65. data/hobo_files/plugin/tags/rapid_document_tags.dryml +49 -0
  66. data/hobo_files/plugin/tags/rapid_editing.dryml +92 -69
  67. data/hobo_files/plugin/tags/rapid_forms.dryml +242 -0
  68. data/hobo_files/plugin/tags/rapid_navigation.dryml +65 -65
  69. data/hobo_files/plugin/tags/rapid_pages.dryml +197 -124
  70. data/hobo_files/plugin/tags/rapid_support.dryml +23 -0
  71. metadata +29 -22
  72. data/hobo_files/plugin/generators/hobo_model/templates/migration.rb +0 -13
  73. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/default_mapping.rb +0 -11
  74. data/hobo_files/plugin/generators/hobo_user_model/templates/migration.rb +0 -15
  75. data/hobo_files/plugin/lib/hobo/HtmlString +0 -3
  76. data/hobo_files/plugin/lib/hobo/controller_helpers.rb +0 -135
  77. data/hobo_files/plugin/lib/hobo/core.rb +0 -475
  78. data/hobo_files/plugin/lib/hobo/rapid.rb +0 -447
  79. data/hobo_files/plugin/test/hobo_dryml_template_test.rb +0 -7
  80. data/hobo_files/plugin/test/hobo_test.rb +0 -7
@@ -0,0 +1,5 @@
1
+ class Hobo::EmailAddress < String
2
+
3
+ COLUMN_TYPE = :string
4
+
5
+ end
@@ -0,0 +1,66 @@
1
+ module Hobo
2
+
3
+ class FieldSpec
4
+
5
+ class UnknownSqlTypeError < RuntimeError; end
6
+
7
+ def initialize(model, name, type, options={})
8
+ raise ArgumentError, "you cannot provide a field spec for the primary key" if name == model.primary_key
9
+ self.model = model
10
+ self.name = name.to_sym
11
+ self.type = type.to_sym
12
+ self.options = options
13
+ self.position = model.field_specs.length
14
+ end
15
+
16
+ attr_accessor :model, :name, :type, :position, :options
17
+
18
+ def sql_type
19
+ options[:sql_type] or begin
20
+ sql_types = model.connection.native_database_types.keys - [:primary_key]
21
+ if type.in?(sql_types)
22
+ type
23
+ elsif options[:length]
24
+ :string
25
+ else
26
+ Hobo.field_types[type]::COLUMN_TYPE or raise UnknownSqlTypeError, type
27
+ end
28
+ end
29
+ end
30
+
31
+ def limit
32
+ options[:limit] || types[sql_type][:limit]
33
+ end
34
+
35
+ def precision
36
+ options[:precision]
37
+ end
38
+
39
+ def scale
40
+ options[:scale]
41
+ end
42
+
43
+ def null
44
+ :null.in?(options) ? options[:null] : true
45
+ end
46
+
47
+ def default
48
+ options[:default]
49
+ end
50
+
51
+ def different_to?(col_spec)
52
+ [:limit, :precision, :scale, :null, :default].any? do |k|
53
+ # puts "#{col_spec.send(k).inspect} --- #{self.send(k).inspect} : #{col_spec.send(k) != self.send(k)}"
54
+ col_spec.send(k) != self.send(k)
55
+ end || sql_type != col_spec.type
56
+ end
57
+
58
+ private
59
+
60
+ def types
61
+ @types ||= model.connection.native_database_types
62
+ end
63
+
64
+ end
65
+
66
+ end
@@ -0,0 +1,325 @@
1
+ module Hobo
2
+
3
+ module HoboHelper
4
+
5
+ def self.add_to_controller(controller)
6
+ controller.send(:include, self)
7
+ controller.hide_action(self.instance_methods)
8
+ end
9
+
10
+ protected
11
+
12
+ def current_user
13
+ # simple one-hit-per-request cache
14
+ @current_user or
15
+ @current_user = if Hobo.user_model and session and id = session[:user]
16
+ Hobo.user_model.find(id)
17
+ else
18
+ Guest.new
19
+ end
20
+ end
21
+
22
+
23
+ def logged_in?
24
+ not current_user.guest?
25
+ end
26
+
27
+
28
+ def base_url
29
+ request.relative_url_root
30
+ end
31
+
32
+
33
+ def controller_for(obj)
34
+ if obj.is_a? Class
35
+ obj.name.underscore.pluralize
36
+ else
37
+ obj.class.name.underscore.pluralize
38
+ end
39
+ end
40
+
41
+
42
+ def object_url(obj, action=nil, *param_hashes)
43
+ action &&= action.to_s
44
+
45
+ controller_name = controller_for(obj)
46
+
47
+ parts = if obj.is_a? Class
48
+ [base_url, controller_name]
49
+
50
+ elsif obj.is_a? Hobo::CompositeModel
51
+ [base_url, controller_name, obj.id]
52
+
53
+ elsif obj.is_a? ActiveRecord::Base
54
+ if obj.new_record?
55
+ [base_url, controller_name]
56
+ else
57
+ raise HoboError.new("invalid object url: new for existing object") if action == "new"
58
+
59
+ klass = obj.class
60
+ id = if klass.id_name?
61
+ obj.id_name(true)
62
+ else
63
+ obj.id
64
+ end
65
+
66
+ [base_url, controller_name, id]
67
+ end
68
+
69
+ elsif obj.is_a? Array # warning - this breaks if we use `case/when Array`
70
+ owner = obj.proxy_owner
71
+ new_model = obj.proxy_reflection.klass
72
+ [object_url(owner), obj.proxy_reflection.name]
73
+
74
+ else
75
+ raise HoboError.new("cannot create url for #{obj.inspect} (#{obj.class})")
76
+ end
77
+ basic = parts.join("/")
78
+
79
+ controller = (controller_name.camelize + "Controller").constantize rescue nil
80
+ url = case action
81
+ when "new"
82
+ basic + "/new"
83
+ when "destroy"
84
+ basic + "?_method=DELETE"
85
+ when "update"
86
+ basic + "?_method=PUT"
87
+ when nil
88
+ basic
89
+ else
90
+ basic + "/" + action
91
+ end
92
+ params = make_params(*param_hashes)
93
+ params.blank? ? url : url + "?" + params
94
+ end
95
+
96
+
97
+ def _as_params(name, obj)
98
+ if obj.is_a? Array
99
+ obj.map {|x| _as_params("#{name}[]", x)}.join("&")
100
+ elsif obj.is_a? Hash
101
+ obj.map {|k,v| _as_params("#{name}[#{k}]", v)}.join("&")
102
+ elsif obj.is_a? Hobo::RawJs
103
+ "#{name}=' + #{obj} + '"
104
+ else
105
+ v = if obj.is_a?(ActiveRecord::Base) or obj.is_a?(Array)
106
+ "@" + dom_id(obj)
107
+ else
108
+ obj.to_s.gsub("'"){"\\'"}
109
+ end
110
+ "#{name}=#{v}"
111
+ end
112
+ end
113
+
114
+
115
+ def make_params(*hashes)
116
+ hash = {}
117
+ hashes.each {|h| hash.update(h) if h}
118
+ hash.map {|k,v| _as_params(k, v)}.join("&")
119
+ end
120
+
121
+
122
+ def dom_id(*args)
123
+ if args.length == 0
124
+ Hobo.dom_id(this)
125
+ else
126
+ Hobo.dom_id(*args)
127
+ end
128
+ end
129
+
130
+
131
+ def type_name(type=nil)
132
+ Hobo.type_name(type || this.class)
133
+ end
134
+
135
+
136
+ def type_and_field(*args)
137
+ if args.empty?
138
+ this_parent && this_field && "#{Hobo.type_name(this_parent.class)}_#{this_field}"
139
+ else
140
+ type, field = args
141
+ "#{type_name(type)}_#{field}"
142
+ end
143
+ end
144
+
145
+
146
+ def map_this
147
+ res = []
148
+ this.each_index {|i| new_field_context(i) { res << yield } }
149
+ Dryml.last_if = !this.empty?
150
+ res
151
+ end
152
+ alias_method :collect_this, :map_this
153
+
154
+
155
+ def comma_split(x)
156
+ case x
157
+ when nil
158
+ []
159
+ when Symbol
160
+ x.to_s
161
+ when String
162
+ x.split(/\s*[, ]\s*/)
163
+ else
164
+ x.compact.map{|e| comma_split(e)}.flatten
165
+ end
166
+ end
167
+
168
+
169
+ def can_create?(object=nil)
170
+ Hobo.can_create?(current_user, object || this)
171
+ end
172
+
173
+
174
+ def can_update?(object, new)
175
+ Hobo.can_update?(current_user, object, new)
176
+ end
177
+
178
+
179
+ def can_edit?(*args)
180
+ if args.empty?
181
+ this_parent && this_field && can_edit?(this_parent, this_field)
182
+ else
183
+ object, field = args.length == 2 ? args : [this, args.first]
184
+
185
+ if !field and object.respond_to?(:proxy_reflection)
186
+ Hobo.can_edit?(current_user, object.proxy_owner, object.proxy_reflection.name)
187
+ else
188
+ Hobo.can_edit?(current_user, object, field)
189
+ end
190
+ end
191
+ end
192
+
193
+
194
+ def can_delete?(object=nil)
195
+ Hobo.can_delete?(current_user, object || this)
196
+ end
197
+
198
+
199
+ def can_view?(object=nil, field=nil)
200
+ if object.nil? && field.nil?
201
+ if this_parent && this_field
202
+ object, field = this_parent, this_field
203
+ else
204
+ object = this
205
+ end
206
+ end
207
+
208
+ if !field and object.respond_to?(:proxy_reflection)
209
+ Hobo.can_view?(current_user, object.proxy_owner, object.proxy_reflection.name)
210
+ else
211
+ Hobo.can_view?(current_user, object, field)
212
+ end
213
+ end
214
+
215
+
216
+ def select_viewable(collection)
217
+ collection.select {|x| can_view?(x)}
218
+ end
219
+
220
+
221
+ def theme_asset(path)
222
+ theme_path = Hobo.current_theme ? "hobothemes/#{Hobo.current_theme}/" : ""
223
+ "#{base_url}/#{theme_path}#{path}"
224
+ end
225
+
226
+ def js_str(s)
227
+ if s.is_a? Hobo::RawJs
228
+ s.to_s
229
+ else
230
+ "'" + s.gsub("'"){"\\'"} + "'"
231
+ end
232
+ end
233
+
234
+
235
+ def make_params_js(*args)
236
+ ("'" + make_params(*args) + "'").sub(/ \+ ''$/,'')
237
+ end
238
+
239
+
240
+ def render_params(*args)
241
+ parts = args.map{|x| x.split(/, */) if x}.compact.flatten
242
+ { :part_page => view_name,
243
+ :render => parts.map do |part_id|
244
+ { :object => Hobo::RawJs.new("hoboParts.#{part_id}"),
245
+ :part => part_id }
246
+ end
247
+ }
248
+ end
249
+
250
+
251
+ def nl_to_br(s)
252
+ s.to_s.gsub("\n", "<br/>") if s
253
+ end
254
+
255
+
256
+ def param_name_for(object, field_path)
257
+ field_path = field_path.to_s.split(".") if field_path.is_a?(String, Symbol)
258
+ attrs = field_path.map{|part| "[#{part.to_s.sub /\?$/, ''}]"}.join
259
+ "#{object.class.name.underscore}#{attrs}"
260
+ end
261
+
262
+
263
+ def param_name_for_this(foreign_key=false)
264
+ return "" unless form_this
265
+ name = if foreign_key and this_type.respond_to?(:macro) and this_type.macro == :belongs_to
266
+ param_name_for(form_this, form_field_path[0..-2] + [this_type.primary_key_name])
267
+ else
268
+ param_name_for(form_this, form_field_path)
269
+ end
270
+ register_form_field(name)
271
+ name
272
+ end
273
+
274
+
275
+ def selector_type
276
+ if this.is_a? ActiveRecord::Base
277
+ this.class
278
+ elsif this.respond_to? :member_class
279
+ this.member_class
280
+ elsif this == @this
281
+ @model
282
+ end
283
+ end
284
+
285
+
286
+ def transpose_with_field(field, collection=nil)
287
+ collection ||= this
288
+ matrix = collection.map {|obj| obj.send(field) }
289
+ max_length = matrix.every(:length).max
290
+ matrix = matrix.map do |a|
291
+ a + [nil] * (max_length - a.length)
292
+ end
293
+ matrix.transpose
294
+ end
295
+
296
+
297
+ def create_model(model)
298
+ n = model.new
299
+ n.set_creator(current_user)
300
+ n
301
+ end
302
+
303
+
304
+ def defined_route?(r)
305
+ @view.respond_to?("#{r}_url")
306
+ end
307
+
308
+
309
+ # debugging support
310
+
311
+ def abort_with(*args)
312
+ raise args.map{|arg| PP.pp(arg, "")}.join("-------\n")
313
+ end
314
+
315
+ def log_debug(*args)
316
+ logger.debug("\n### DRYML Debug ###")
317
+ logger.debug(args.map {|a| PP.pp(a, "")}.join("-------\n"))
318
+ logger.debug("DRYML THIS = #{Hobo.dom_id(this) rescue this.inspect}")
319
+ logger.debug("###################\n")
320
+ args.first unless args.empty?
321
+ end
322
+
323
+ end
324
+
325
+ end
@@ -1,3 +1,5 @@
1
1
  class Hobo::HtmlString < String
2
+
3
+ COLUMN_TYPE = :text
2
4
 
3
5
  end
@@ -8,6 +8,7 @@ class Hobo::LazyHash < Hash
8
8
  end
9
9
  end
10
10
 
11
+
11
12
  def [](key)
12
13
  val = super
13
14
  if val.is_a?(Proc)
@@ -17,10 +18,21 @@ class Hobo::LazyHash < Hash
17
18
  end
18
19
  end
19
20
 
21
+
22
+ def delete(key)
23
+ val = super
24
+ val.is_a?(Proc) ? val.call : val
25
+ end
26
+
27
+
20
28
  def inspect
21
- "#<LazyHash:#{object_id} #{keys.inspect}>"
29
+ pairs = map do |k, v|
30
+ "#{k.inspect} => #{v.is_a?(Proc) ? '??' : v.inspect}"
31
+ end
32
+ "{#{pairs * ', '}}"
22
33
  end
23
34
 
35
+
24
36
  def to_s
25
37
  inspect
26
38
  end
@@ -1,7 +1,9 @@
1
1
  class Hobo::MarkdownString < String
2
2
 
3
+ COLUMN_TYPE = :text
4
+
3
5
  def to_html
4
- self.blank? ? "" : BlueCloth.new(self).to_html
6
+ blank? ? "" : BlueCloth.new(self).to_html
5
7
  end
6
8
 
7
9
  end
@@ -2,12 +2,31 @@ module Hobo
2
2
 
3
3
  module Model
4
4
 
5
+ Hobo.field_types.update({ :html => HtmlString,
6
+ :markdown => MarkdownString,
7
+ :textile => TextileString,
8
+ :password => PasswordString,
9
+ :text => Hobo::Text,
10
+ :boolean => TrueClass,
11
+ :date => Date,
12
+ :datetime => Time,
13
+ :integer => Fixnum,
14
+ :big_integer => BigDecimal,
15
+ :float => Float,
16
+ :string => String,
17
+ :email_address => EmailAddress
18
+ })
19
+
5
20
  def self.included(base)
6
21
  Hobo.register_model(base)
7
22
  base.extend(ClassMethods)
8
- base.set_field_type({})
23
+ base.class_eval do
24
+ @field_specs = HashWithIndifferentAccess.new
25
+ set_field_type({})
26
+ end
9
27
  class << base
10
28
  alias_method_chain :has_many, :defined_scopes
29
+ alias_method_chain :belongs_to, :foreign_key_declaration
11
30
  end
12
31
  end
13
32
 
@@ -16,43 +35,72 @@ module Hobo
16
35
  # include methods also shared by CompositeModel
17
36
  include ModelSupport::ClassMethods
18
37
 
38
+ private
39
+
40
+ def return_type(type)
41
+ @next_method_type = type
42
+ end
43
+
19
44
  def method_added(name)
20
- # avoid error when running model generators before
21
- # db exists
22
- return unless connected?
45
+ if @next_method_type
46
+ set_field_type(name => @next_method_type)
47
+ @next_method_type = nil
48
+ end
49
+ end
50
+
51
+
52
+ class FieldDeclarationsDsl
23
53
 
24
- aliased_name = "#{name}_without_hobo_type"
25
- return if name.to_s.ends_with?('without_hobo_type') or aliased_name.in?(instance_methods)
54
+ def initialize(model)
55
+ @model = model
56
+ end
26
57
 
27
- type_wrapper = self.field_type(name)
28
- if type_wrapper && type_wrapper.is_a?(Class) && type_wrapper < String
29
- aliased_name = "#{name}_without_hobo_type"
30
- alias_method aliased_name, name
31
- define_method name do
32
- res = send(aliased_name)
33
- if res.nil?
34
- nil
35
- elsif res.respond_to?(:hobo_undefined?) && res.hobo_undefined?
36
- res
37
- else
38
- type_wrapper.new(res)
39
- end
40
- end
58
+ attr_reader :model
59
+
60
+ def timestamps
61
+ field(:created_at, :datetime)
62
+ field(:updated_at, :datetime)
63
+ end
64
+
65
+ def field(name, *args)
66
+ type = args.shift
67
+ options = extract_options_from_args!(args)
68
+ @model.send(:set_field_type, name => type) unless
69
+ type.in?(@model.connection.native_database_types.keys - [:text])
70
+ @model.field_specs[name] = FieldSpec.new(@model, name, type, options)
71
+ end
72
+
73
+ def method_missing(name, *args)
74
+ field(name, *args)
41
75
  end
76
+
42
77
  end
43
78
 
79
+
80
+ def fields(&b)
81
+ FieldDeclarationsDsl.new(self).instance_eval(&b)
82
+ end
83
+
84
+
85
+ def belongs_to_with_foreign_key_declaration(name, *args, &block)
86
+ res = belongs_to_without_foreign_key_declaration(name, *args, &block)
87
+ refl = reflections[name]
88
+ fkey = refl.primary_key_name
89
+ field_specs[fkey] ||= FieldSpec.new(self, fkey, :integer)
90
+ if refl.options[:polymorphic]
91
+ type_col = "#{name}_type"
92
+ field_specs[type_col] ||= FieldSpec.new(self, type_col, :string)
93
+ end
94
+ res
95
+ end
96
+
97
+
98
+ attr_reader :field_specs
99
+ public :field_specs
100
+
44
101
  def set_field_type(types)
45
102
  types.each_pair do |field, type|
46
-
47
- # TODO: Make this extensible
48
- type_class = case type
49
- when :html; HtmlString
50
- when :markdown; MarkdownString
51
- when :textile; TextileString
52
- when :password; PasswordString
53
- else type
54
- end
55
-
103
+ type_class = Hobo.field_types[type] || type
56
104
  field_types[field] = type_class
57
105
  end
58
106
  end
@@ -75,10 +123,10 @@ module Hobo
75
123
  @hobo_never_show.concat(fields.omap{to_sym})
76
124
  end
77
125
 
78
-
79
126
  def never_show?(field)
80
127
  @hobo_never_show and field.to_sym.in?(@hobo_never_show)
81
128
  end
129
+ public :never_show?
82
130
 
83
131
  def set_creator_attr(attr)
84
132
  class_eval %{
@@ -143,12 +191,12 @@ module Hobo
143
191
  underscore
144
192
  end
145
193
 
146
-
194
+ public
195
+
147
196
  def id_name?
148
197
  respond_to?(:find_by_id_name)
149
198
  end
150
199
 
151
-
152
200
  attr_reader :id_name_column
153
201
 
154
202
 
@@ -156,15 +204,24 @@ module Hobo
156
204
  def field_type(name)
157
205
  name = name.to_sym
158
206
  field_types[name] or
159
- reflections[name] or
160
- ((col = columns.find {|c| c.name == name.to_s}) and case col.type
161
- when :boolean
162
- TrueClass
163
- when :text
164
- Hobo::Text
165
- else
166
- col.klass
167
- end)
207
+ reflections[name] or begin
208
+ col = columns.find {|c| c.name == name.to_s} rescue nil
209
+ return nil if col.nil?
210
+ case col.type
211
+ when :boolean
212
+ TrueClass
213
+ when :text
214
+ Hobo::Text
215
+ else
216
+ col.klass
217
+ end
218
+ end
219
+ end
220
+
221
+
222
+ def nilable_field?(name)
223
+ col = columns.find {|c| c.name == name.to_s} rescue nil
224
+ col.nil? || col.null
168
225
  end
169
226
 
170
227
 
@@ -244,20 +301,35 @@ module Hobo
244
301
 
245
302
  class ScopedProxy
246
303
  def initialize(klass, scope={})
247
- @klass, @scope = klass, scope
304
+ @klass = klass
305
+
306
+ # If there's no :find, or :create specified, assume it's a find scope
307
+ @scope = if scope.has_key?(:find) || scope.has_key?(:create)
308
+ scope
309
+ else
310
+ { :find => scope }
311
+ end
248
312
  end
249
313
 
250
314
  def method_missing(name, *args, &block)
251
- klass.with_scope(@scope) do
315
+ @klass.send(:with_scope, @scope) do
252
316
  @klass.send(name, *args, &block)
253
317
  end
254
318
  end
319
+
320
+ def all
321
+ self.find(:all)
322
+ end
323
+
324
+ def first
325
+ self.find(:first)
326
+ end
255
327
  end
256
328
  (Object.instance_methods +
257
329
  Object.private_instance_methods +
258
330
  Object.protected_instance_methods).each do |m|
259
331
  ScopedProxy.send(:undef_method, m) unless
260
- m.in?(%w{initialize method_missing}) || m.starts_with?('_')
332
+ m.in?(%w{initialize method_missing send}) || m.starts_with?('_')
261
333
  end
262
334
 
263
335
  attr_accessor :defined_scopes
@@ -278,24 +350,35 @@ module Hobo
278
350
  attr_accessor :reflections
279
351
 
280
352
  def method_missing(name, *args, &block)
281
- scopes = proxy_reflection.klass.defined_scopes
282
- scope = scopes && scopes[name.to_sym]
283
- if scope
284
- scope = scope.call(*args) if scope.is_a?(Proc)
285
-
286
- # Calling directly causes self to get loaded
353
+ scope = (proxy_reflection.klass.respond_to?(:defined_scopes) and
354
+ scopes = proxy_reflection.klass.defined_scopes and
355
+ scopes[name.to_sym])
356
+
357
+ scope = scope.call(*args) if scope.is_a?(Proc)
358
+
359
+ # If there's no :find, or :create specified, assume it's a find scope
360
+ find_scope = if scope && (scope.has_key?(:find) || scope.has_key?(:create))
361
+ scope[:find]
362
+ else
363
+ scope
364
+ end
365
+
366
+ if find_scope
367
+ # Calling instance_variable_get directly causes self to
368
+ # get loaded, hence this trick
287
369
  assoc = Kernel.instance_method(:instance_variable_get).bind(self).call("@#{name}")
370
+
288
371
  unless assoc
289
372
  options = proxy_reflection.options
290
- has_many_conditions = options.has_key?(:condition)
291
- scope_conditions = scope.delete(:conditions)
373
+ has_many_conditions = options.has_key?(:conditions)
374
+ scope_conditions = find_scope.delete(:conditions)
292
375
  conditions = if has_many_conditions && scope_conditions
293
376
  "(#{scope_conditions}) AND (#{has_many_conditions})"
294
377
  else
295
378
  scope_conditions || has_many_conditions
296
379
  end
297
380
 
298
- options = options.merge(scope).update(:conditions => conditions,
381
+ options = options.merge(find_scope).update(:conditions => conditions,
299
382
  :class_name => proxy_reflection.klass.name,
300
383
  :foreign_key => proxy_reflection.primary_key_name)
301
384
  r = ActiveRecord::Reflection::AssociationReflection.new(:has_many,
@@ -336,18 +419,7 @@ module Hobo
336
419
  end
337
420
 
338
421
 
339
- def method_missing(name, *args, &b)
340
- val = super
341
- if val.nil?
342
- nil
343
- else
344
- type_wrapper = self.class.field_type(name)
345
- (type_wrapper && type_wrapper.is_a?(Class) && type_wrapper < String) ? type_wrapper.new(val) : val
346
- end
347
- end
348
-
349
-
350
- def created_by(user)
422
+ def set_creator(user)
351
423
  self.creator ||= user if self.class.has_creator? and not user.guest?
352
424
  end
353
425
 
@@ -381,11 +453,58 @@ module Hobo
381
453
  CompositeModel.new_for([self, object])
382
454
  end
383
455
 
456
+ def created_date
457
+ created_at.to_date
458
+ end
384
459
 
460
+ def modified_date
461
+ modified_at.to_date
462
+ end
463
+
385
464
  def typed_id
386
465
  id ? "#{self.class.name.underscore}_#{self.id}" : nil
387
466
  end
388
467
 
468
+ def to_s
469
+ if respond_to? :title
470
+ title
471
+ elsif respond_to? :name
472
+ name
473
+ else
474
+ "#{self.class.name.humanize} #{id}"
475
+ end
476
+ end
477
+
389
478
  end
390
479
  end
391
480
 
481
+
482
+ # Hack AR to get Hobo type wrappers in
483
+
484
+ module ActiveRecord::AttributeMethods::ClassMethods
485
+
486
+ # Define an attribute reader method. Cope with nil column.
487
+ def define_read_method(symbol, attr_name, column)
488
+ cast_code = column.type_cast_code('v') if column
489
+ access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
490
+
491
+ unless attr_name.to_s == self.primary_key.to_s
492
+ access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
493
+ end
494
+
495
+ # This is the Hobo hook - add a type wrapper around the field
496
+ # value if we have a special type defined
497
+ src = if connected? && respond_to?(:field_type) && (type_wrapper = field_type(symbol)) &&
498
+ type_wrapper.is_a?(Class) && type_wrapper < String
499
+ "val = begin; #{access_code}; end; " +
500
+ "if val.nil?; nil; " +
501
+ "elsif val.respond_to?(:hobo_undefined?) && val.hobo_undefined?; val; " +
502
+ "else; #{type_wrapper}.new(val); end"
503
+ else
504
+ access_code
505
+ end
506
+
507
+ evaluate_attribute_method(attr_name,
508
+ "def #{symbol}; @attributes_cache['#{attr_name}'] ||= begin; #{src}; end; end")
509
+ end
510
+ end