hobo 0.5.3 → 0.6

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