hobo 0.6.2 → 0.6.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 (60) hide show
  1. data/bin/hobo +21 -22
  2. data/hobo_files/plugin/CHANGES.txt +429 -4
  3. data/hobo_files/plugin/Rakefile +2 -2
  4. data/hobo_files/plugin/generators/hobo_front_controller/templates/index.dryml +6 -5
  5. data/hobo_files/plugin/generators/hobo_front_controller/templates/search.dryml +2 -2
  6. data/hobo_files/plugin/generators/hobo_migration/hobo_migration_generator.rb +20 -15
  7. data/hobo_files/plugin/generators/hobo_model/templates/model.rb +1 -0
  8. data/hobo_files/plugin/generators/hobo_model_controller/templates/controller.rb +2 -0
  9. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_base.css +1 -2
  10. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_rapid.css +4 -3
  11. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_rapid.js +94 -12
  12. data/hobo_files/plugin/generators/hobo_rapid/templates/lowpro.js +5 -183
  13. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/stylesheets/application.css +1 -1
  14. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/views/application.dryml +23 -1
  15. data/hobo_files/plugin/generators/hobo_user_controller/templates/controller.rb +2 -0
  16. data/hobo_files/plugin/generators/hobo_user_model/templates/model.rb +3 -1
  17. data/hobo_files/plugin/init.rb +18 -7
  18. data/hobo_files/plugin/lib/active_record/has_many_association.rb +2 -2
  19. data/hobo_files/plugin/lib/extensions.rb +56 -12
  20. data/hobo_files/plugin/lib/hobo.rb +25 -88
  21. data/hobo_files/plugin/lib/hobo/composite_model.rb +2 -0
  22. data/hobo_files/plugin/lib/hobo/controller.rb +40 -20
  23. data/hobo_files/plugin/lib/hobo/dryml.rb +122 -106
  24. data/hobo_files/plugin/lib/hobo/dryml/dryml_builder.rb +2 -1
  25. data/hobo_files/plugin/lib/hobo/dryml/part_context.rb +3 -2
  26. data/hobo_files/plugin/lib/hobo/dryml/taglib.rb +19 -3
  27. data/hobo_files/plugin/lib/hobo/dryml/template.rb +40 -25
  28. data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +41 -20
  29. data/hobo_files/plugin/lib/hobo/email_address.rb +4 -1
  30. data/hobo_files/plugin/lib/hobo/enum_string.rb +50 -0
  31. data/hobo_files/plugin/lib/hobo/field_declaration_dsl.rb +36 -0
  32. data/hobo_files/plugin/lib/hobo/field_spec.rb +4 -7
  33. data/hobo_files/plugin/lib/hobo/hobo_helper.rb +47 -44
  34. data/hobo_files/plugin/lib/hobo/html_string.rb +2 -0
  35. data/hobo_files/plugin/lib/hobo/markdown_string.rb +2 -0
  36. data/hobo_files/plugin/lib/hobo/model.rb +158 -89
  37. data/hobo_files/plugin/lib/hobo/model_controller.rb +422 -376
  38. data/hobo_files/plugin/lib/hobo/model_queries.rb +1 -1
  39. data/hobo_files/plugin/lib/hobo/model_router.rb +174 -0
  40. data/hobo_files/plugin/lib/hobo/password_string.rb +2 -0
  41. data/hobo_files/plugin/lib/hobo/percentage.rb +14 -0
  42. data/hobo_files/plugin/lib/hobo/plugins.rb +4 -4
  43. data/hobo_files/plugin/lib/hobo/rapid_helper.rb +10 -2
  44. data/hobo_files/plugin/lib/hobo/text.rb +3 -3
  45. data/hobo_files/plugin/lib/hobo/textile_string.rb +2 -0
  46. data/hobo_files/plugin/lib/hobo/undefined.rb +3 -2
  47. data/hobo_files/plugin/lib/hobo/{authenticated_user.rb → user.rb} +10 -3
  48. data/hobo_files/plugin/lib/hobo/user_controller.rb +27 -23
  49. data/hobo_files/plugin/tags/core.dryml +8 -2
  50. data/hobo_files/plugin/tags/rapid.dryml +52 -40
  51. data/hobo_files/plugin/tags/rapid_document_tags.dryml +15 -11
  52. data/hobo_files/plugin/tags/rapid_editing.dryml +41 -9
  53. data/hobo_files/plugin/tags/rapid_forms.dryml +136 -36
  54. data/hobo_files/plugin/tags/rapid_navigation.dryml +2 -2
  55. data/hobo_files/plugin/tags/rapid_pages.dryml +204 -221
  56. data/hobo_files/plugin/tags/rapid_plus.dryml +8 -6
  57. data/hobo_files/plugin/tags/rapid_support.dryml +2 -3
  58. metadata +44 -42
  59. data/hobo_files/plugin/lib/hobo/define_tags.rb +0 -56
  60. data/hobo_files/plugin/lib/hobo/http_parameters.rb +0 -225
@@ -19,6 +19,8 @@ module Hobo::Dryml
19
19
  @tag_attrs ||= {}
20
20
  end
21
21
 
22
+ alias_method :_alias_tag_chain, :alias_tag_chain
23
+
22
24
  end
23
25
 
24
26
  for mod in ActionView::Helpers.constants.grep(/Helper$/).map {|m| ActionView::Helpers.const_get(m)}
@@ -218,8 +220,7 @@ module Hobo::Dryml
218
220
  end
219
221
  parent, field, obj = Hobo.get_field_path(tag_this || this, path)
220
222
 
221
- type = if (obj.nil? or obj.respond_to?(:proxy_reflection)) and
222
- parent.class.respond_to?(:field_type) and field_type = parent.class.field_type(field)
223
+ type = if parent.class.respond_to?(:field_type) && field_type = parent.class.field_type(field)
223
224
  field_type
224
225
  elsif obj == false
225
226
  # In dryml, TrueClass is the 'boolean' class
@@ -305,27 +306,27 @@ module Hobo::Dryml
305
306
  end
306
307
 
307
308
 
308
- def call_block_tag_parameter(the_tag, attributes, overriding_proc, &b)
309
+ def call_block_tag_parameter(the_tag, attributes, overriding_proc, &default_tagbody)
309
310
  if overriding_proc && overriding_proc.arity == 1
310
311
  # This is a 'replace' parameter
311
312
 
312
313
  template_default = proc do |attrs, body_block|
313
- tagbody_proc = body_block && proc {|_| new_context { body_block.call(b) } }
314
- call_block_tag_parameter(the_tag, attributes, proc { attrs.update(:tagbody => tagbody_proc) }, &b)
314
+ tagbody_proc = body_block && proc {|_| new_context { body_block.call(default_tagbody) } }
315
+ call_block_tag_parameter(the_tag, attributes, proc { attrs.update(:tagbody => tagbody_proc) }, &default_tagbody)
315
316
  end
316
317
  overriding_proc.call(template_default)
317
318
  else
318
319
  if overriding_proc
319
320
  overriding_attributes = overriding_proc.call
320
- tagbody = overriding_attributes.delete(:tagbody)
321
+ overriding_tagbody = overriding_attributes.delete(:tagbody)
321
322
  attributes = merge_attrs(attributes, overriding_attributes)
322
323
  end
323
324
 
324
325
  if the_tag.is_a?(String, Symbol) && the_tag.to_s.in?(Hobo.static_tags)
325
- body = if tagbody
326
- new_context { tagbody.call(proc {b.call(nil)}) }
327
- elsif b
328
- new_context { b.call(nil) }
326
+ body = if overriding_tagbody
327
+ new_context { overriding_tagbody.call(proc { default_tagbody.call(nil) }) }
328
+ elsif default_tagbody
329
+ new_context { default_tagbody.call(nil) }
329
330
  else
330
331
  nil
331
332
  end
@@ -337,17 +338,17 @@ module Hobo::Dryml
337
338
  else
338
339
  if the_tag.is_a?(String, Symbol)
339
340
  body = proc do |default|
340
- if tagbody
341
- tagbody.call(proc { b ? b.call(default) : "" })
341
+ if overriding_tagbody
342
+ overriding_tagbody.call(proc { default_tagbody ? default_tagbody.call(default) : "" })
342
343
  else
343
- b ? b.call(default) : ""
344
+ default_tagbody ? default_tagbody.call(default) : ""
344
345
  end
345
346
  end
346
347
 
347
348
  send(the_tag, attributes, &body)
348
349
  else
349
350
  # It's a proc - a template default
350
- the_tag.call(attributes, tagbody || b)
351
+ the_tag.call(attributes, overriding_tagbody || default_tagbody)
351
352
  end
352
353
  end
353
354
  end
@@ -376,11 +377,31 @@ module Hobo::Dryml
376
377
  # Takes two procs that each returh hashes and returns a single
377
378
  # proc that calls these in turn and merges the results into a
378
379
  # single hash
379
- def merge_option_procs(general_proc, overriding_proc)
380
- if overriding_proc
381
- proc { merge_attrs(general_proc.call, overriding_proc.call) }
382
- else
380
+ def merge_block_tag_parameter(general_proc, overriding_proc)
381
+ if overriding_proc.nil?
383
382
  general_proc
383
+ else
384
+ if overriding_proc.arity == 1
385
+ # The override is a replace parameter - just pass it on
386
+ overriding_proc
387
+ else
388
+ proc do
389
+ overriding_attrs = overriding_proc.call
390
+ defined_attrs = general_proc.call
391
+
392
+ attrs = merge_attrs(defined_attrs, overriding_attrs)
393
+
394
+ # The overrider should provide its tagbody as the new
395
+ # <default_tagbody/>
396
+ if overriding_attrs[:tagbody] && defined_attrs[:tagbody]
397
+ attrs[:tagbody] = proc do |default|
398
+ overriding_attrs[:tagbody].call(proc { _output(defined_attrs[:tagbody].call(default)) })
399
+ end
400
+ end
401
+
402
+ attrs
403
+ end
404
+ end
384
405
  end
385
406
  end
386
407
 
@@ -388,7 +409,7 @@ module Hobo::Dryml
388
409
  # hashes rather than a single hash. The first hash is the tag
389
410
  # attributes, the second is a hash of procs -- the template
390
411
  # parameters.
391
- def merge_template_parameter_procs(general_proc, overriding_proc)
412
+ def merge_template_parameter(general_proc, overriding_proc)
392
413
  proc do
393
414
  general_attributes, general_template_procs = general_proc.call
394
415
  overriding_attributes, overriding_template_procs = overriding_proc.call
@@ -399,7 +420,7 @@ module Hobo::Dryml
399
420
 
400
421
  def part_contexts_storage_tag
401
422
  storage = part_contexts_storage
402
- storage.blank? ? "" : "<script>\nvar hoboParts = {}\n#{storage}</script>\n"
423
+ storage.blank? ? "" : "<script>\n#{storage}</script>\n"
403
424
  end
404
425
 
405
426
 
@@ -3,7 +3,10 @@ class Hobo::EmailAddress < String
3
3
  COLUMN_TYPE = :string
4
4
 
5
5
  def validate
6
- "is not a valid email address" unless self.blank? || self =~ /^\s*([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\s*$/i
6
+ "is not a valid email address" unless
7
+ self.blank? || self =~ /^\s*([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\s*$/i
7
8
  end
8
9
 
9
10
  end
11
+
12
+ Hobo.field_types[:email_address] = Hobo::EmailAddress
@@ -0,0 +1,50 @@
1
+ module Hobo
2
+
3
+ class EnumString < String
4
+
5
+ module Helper
6
+
7
+ def enum_string(*values)
8
+ EnumString.for(*values)
9
+ end
10
+
11
+ end
12
+
13
+ class << self
14
+
15
+ def with_values(*values)
16
+ @values = values.every(:to_s)
17
+ end
18
+
19
+ attr_accessor :values
20
+
21
+ def for(*values)
22
+ values = values.every(:to_s)
23
+ c = Class.new(EnumString) do
24
+ values.each do |v|
25
+ define_method("#{v.underscore}?") { self == v }
26
+ meta_def("#{v.underscore}") { v }
27
+ end
28
+ end
29
+ c.with_values(*values)
30
+ c
31
+ end
32
+
33
+ def inspect
34
+ "#<EnumString #{(values || []) * ' '}>"
35
+ end
36
+ alias_method :to_s, :inspect
37
+
38
+ end
39
+
40
+ COLUMN_TYPE = :string
41
+
42
+ def validate
43
+ "must be one of #{self.class.values * ', '}" unless self.in?(self.class.values)
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+
50
+ Hobo::FieldDeclarationsDsl.send(:include, Hobo::EnumString::Helper)
@@ -0,0 +1,36 @@
1
+ module Hobo
2
+
3
+ class FieldDeclarationsDsl
4
+
5
+ def initialize(model)
6
+ @model = model
7
+ end
8
+
9
+ attr_reader :model
10
+
11
+ def timestamps
12
+ field(:created_at, :datetime)
13
+ field(:updated_at, :datetime)
14
+ end
15
+
16
+ def field(name, *args)
17
+ type = args.shift
18
+ options = args.extract_options!
19
+ @model.send(:set_field_type, name => type) unless
20
+ type.in?(@model.connection.native_database_types.keys - [:text])
21
+ @model.field_specs[name] = FieldSpec.new(@model, name, type, options)
22
+
23
+ @model.send(:validates_presence_of, name) if :required.in?(args)
24
+ @model.send(:validates_uniqueness_of, name) if :unique.in?(args)
25
+ end
26
+
27
+ def method_missing(name, *args)
28
+ field(name, *args)
29
+ end
30
+
31
+ undef_method :type
32
+
33
+
34
+ end
35
+
36
+ end
@@ -8,7 +8,7 @@ module Hobo
8
8
  raise ArgumentError, "you cannot provide a field spec for the primary key" if name == model.primary_key
9
9
  self.model = model
10
10
  self.name = name.to_sym
11
- self.type = type.to_sym
11
+ self.type = type.is_a?(String) ? type.to_sym : type
12
12
  self.options = options
13
13
  self.position = model.field_specs.length
14
14
  end
@@ -17,13 +17,11 @@ module Hobo
17
17
 
18
18
  def sql_type
19
19
  options[:sql_type] or begin
20
- sql_types = model.connection.native_database_types.keys - [:primary_key]
21
- if type.in?(sql_types)
20
+ native_types = model.connection.native_database_types.keys - [:primary_key]
21
+ if type.in?(native_types)
22
22
  type
23
- elsif options[:length]
24
- :string
25
23
  else
26
- field_type = Hobo.field_types[type]
24
+ field_type = type.is_a?(Class) ? type : Hobo.field_types[type]
27
25
  field_type && field_type::COLUMN_TYPE or raise UnknownSqlTypeError, [model, name, type]
28
26
  end
29
27
  end
@@ -51,7 +49,6 @@ module Hobo
51
49
 
52
50
  def different_to?(col_spec)
53
51
  [:limit, :precision, :scale, :null, :default].any? do |k|
54
- # puts "#{col_spec.send(k).inspect} --- #{self.send(k).inspect} : #{col_spec.send(k) != self.send(k)}"
55
52
  col_spec.send(k) != self.send(k)
56
53
  end || sql_type != col_spec.type
57
54
  end
@@ -11,12 +11,10 @@ module Hobo
11
11
 
12
12
  def current_user
13
13
  # simple one-hit-per-request cache
14
- @current_user or
15
- @current_user = if session and id = session[:user]
16
- Hobo.object_from_dom_id(id)
17
- else
18
- ::Guest.new
19
- end
14
+ @current_user ||= begin
15
+ id = session._?[:user]
16
+ (id && Hobo.object_from_dom_id(id) rescue nil) || ::Guest.new
17
+ end
20
18
  end
21
19
 
22
20
 
@@ -37,22 +35,33 @@ module Hobo
37
35
  obj.class.name.underscore.pluralize
38
36
  end
39
37
  end
38
+
39
+
40
+ def subsite
41
+ params[:controller].match(/([^\/]+)\//)._?[1]
42
+ end
40
43
 
41
44
 
42
- def object_url(obj, action=nil, *param_hashes)
45
+ def object_url(*args)
46
+ params = args.extract_options!
47
+ obj, action = args
48
+
43
49
  action &&= action.to_s
44
50
 
45
51
  controller_name = controller_for(obj)
46
52
 
53
+ subsite = params[:subsite] || self.subsite
54
+ base = subsite ? "/#{subsite}#{base_url}" : base_url
55
+
47
56
  parts = if obj.is_a? Class
48
- [base_url, controller_name]
57
+ [base, controller_name]
49
58
 
50
59
  elsif obj.is_a? Hobo::CompositeModel
51
- [base_url, controller_name, obj.id]
60
+ [base, controller_name, obj.to_param]
52
61
 
53
62
  elsif obj.is_a? ActiveRecord::Base
54
63
  if obj.new_record?
55
- [base_url, controller_name]
64
+ [base, controller_name]
56
65
  else
57
66
  raise HoboError.new("invalid object url: new for existing object") if action == "new"
58
67
 
@@ -60,10 +69,10 @@ module Hobo
60
69
  id = if klass.id_name?
61
70
  obj.id_name(true)
62
71
  else
63
- obj.id
72
+ obj.to_param
64
73
  end
65
74
 
66
- [base_url, controller_name, id]
75
+ [base, controller_name, id]
67
76
  end
68
77
 
69
78
  elsif obj.is_a? Array # warning - this breaks if we use `case/when Array`
@@ -74,23 +83,17 @@ module Hobo
74
83
  else
75
84
  raise HoboError.new("cannot create url for #{obj.inspect} (#{obj.class})")
76
85
  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
86
+ url = parts.join("/")
87
+
88
+ case action
89
+ when nil # do nothing
90
+ when "destroy" then params["_method"] = "DELETE"
91
+ when "update" then params["_method"] = "PUT"
92
+ else url += "/#{action}"
93
+ end
94
+
95
+ params = make_params(params - [:subsite])
96
+ params.blank? ? url : "#{url}?#{params}"
94
97
  end
95
98
 
96
99
 
@@ -125,6 +128,8 @@ module Hobo
125
128
  else
126
129
  Hobo.dom_id(*args)
127
130
  end
131
+ rescue ArgumentError
132
+ ""
128
133
  end
129
134
 
130
135
 
@@ -146,8 +151,13 @@ module Hobo
146
151
 
147
152
  def map_this
148
153
  res = []
149
- this.each_index {|i| new_field_context(i) { res << yield } }
150
- Dryml.last_if = !this.empty?
154
+ empty = true
155
+ if this.respond_to?(:each_index)
156
+ this.each_index {|i| empty = false; new_field_context(i) { res << yield } }
157
+ else
158
+ this.map {|e| empty = false; new_object_context(e) { res << yield } }
159
+ end
160
+ Dryml.last_if = !empty
151
161
  res
152
162
  end
153
163
  alias_method :collect_this, :map_this
@@ -238,17 +248,6 @@ module Hobo
238
248
  end
239
249
 
240
250
 
241
- def render_params(*args)
242
- parts = args.map{|x| x.split(/, */) if x}.compact.flatten
243
- { :part_page => view_name,
244
- :render => parts.map do |part_id|
245
- { :object => Hobo::RawJs.new("hoboParts.#{part_id}"),
246
- :part => part_id }
247
- end
248
- }
249
- end
250
-
251
-
252
251
  def nl_to_br(s)
253
252
  s.to_s.gsub("\n", "<br/>") if s
254
253
  end
@@ -332,6 +331,10 @@ module Hobo
332
331
  c = user_or_class.is_a?(Class) ? user_or_class : user_or_class.class
333
332
  send("#{c.name.underscore}_signup_url") rescue nil
334
333
  end
334
+
335
+ def current_page_url
336
+ request.request_uri.match(/^([^?]*)/)._?[1]
337
+ end
335
338
 
336
339
  def query_params
337
340
  query = request.request_uri.match(/(?:\?(.+))/)._?[1]
@@ -341,9 +344,9 @@ module Hobo
341
344
  pair = param.split('=')
342
345
  pair.length == 1 ? pair + [''] : pair
343
346
  end
344
- Hash[*pairs.flatten]
347
+ HashWithIndifferentAccess[*pairs.flatten]
345
348
  else
346
- {}
349
+ HashWithIndifferentAccess.new
347
350
  end
348
351
  end
349
352
 
@@ -3,3 +3,5 @@ class Hobo::HtmlString < String
3
3
  COLUMN_TYPE = :text
4
4
 
5
5
  end
6
+
7
+ Hobo.field_types[:html] = Hobo::HtmlString
@@ -7,3 +7,5 @@ class Hobo::MarkdownString < String
7
7
  end
8
8
 
9
9
  end
10
+
11
+ Hobo.field_types[:markdown] = Hobo::MarkdownString
@@ -2,20 +2,16 @@ 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
- })
5
+ PLAIN_TYPES = { :boolean => TrueClass,
6
+ :date => Date,
7
+ :datetime => Time,
8
+ :integer => Fixnum,
9
+ :big_integer => BigDecimal,
10
+ :float => Float,
11
+ :string => String
12
+ }
13
+
14
+ Hobo.field_types.update(PLAIN_TYPES)
19
15
 
20
16
  def self.included(base)
21
17
  Hobo.register_model(base)
@@ -23,13 +19,12 @@ module Hobo
23
19
  base.class_eval do
24
20
  @field_specs = HashWithIndifferentAccess.new
25
21
  set_field_type({})
22
+ alias_method_chain :attributes=, :hobo_type_conversion
26
23
  end
27
24
  class << base
28
25
  alias_method_chain :has_many, :defined_scopes
29
26
  alias_method_chain :belongs_to, :foreign_key_declaration
30
27
  end
31
- # respond_to? is slow on AR objects, use this instead where possible
32
- base.send(:alias_method, :has_hobo_method?, :respond_to_without_attributes?)
33
28
  end
34
29
 
35
30
  module ClassMethods
@@ -51,50 +46,27 @@ module Hobo
51
46
  end
52
47
 
53
48
 
54
- class FieldDeclarationsDsl
55
-
56
- def initialize(model)
57
- @model = model
58
- end
59
-
60
- attr_reader :model
61
-
62
- def timestamps
63
- field(:created_at, :datetime)
64
- field(:updated_at, :datetime)
65
- end
66
-
67
- def field(name, *args)
68
- type = args.shift
69
- options = args.extract_options!
70
- @model.send(:set_field_type, name => type) unless
71
- type.in?(@model.connection.native_database_types.keys - [:text])
72
- @model.field_specs[name] = FieldSpec.new(@model, name, type, options)
73
-
74
- @model.send(:validates_presence_of, name) if :required.in?(args)
75
- @model.send(:validates_uniqueness_of, name) if :unique.in?(args)
76
- end
77
-
78
- def method_missing(name, *args)
79
- field(name, *args)
80
- end
81
-
82
- end
83
-
84
-
85
49
  def fields(&b)
86
- FieldDeclarationsDsl.new(self).instance_eval(&b)
50
+ dsl = FieldDeclarationsDsl.new(self)
51
+ if b.arity == 1
52
+ yield dsl
53
+ else
54
+ dsl.instance_eval(&b)
55
+ end
87
56
  end
88
57
 
89
58
 
90
59
  def belongs_to_with_foreign_key_declaration(name, *args, &block)
91
- res = belongs_to_without_foreign_key_declaration(name, *args, &block)
60
+ options = args.extract_options!
61
+ res = belongs_to_without_foreign_key_declaration(name, *args + [options], &block)
92
62
  refl = reflections[name]
93
63
  fkey = refl.primary_key_name
94
- field_specs[fkey] ||= FieldSpec.new(self, fkey, :integer)
64
+ column_options = {}
65
+ column_options[:null] = options[:null] if options.has_key?(:null)
66
+ field_specs[fkey] ||= FieldSpec.new(self, fkey, :integer, column_options)
95
67
  if refl.options[:polymorphic]
96
68
  type_col = "#{name}_type"
97
- field_specs[type_col] ||= FieldSpec.new(self, type_col, :string)
69
+ field_specs[type_col] ||= FieldSpec.new(self, type_col, :string, column_options)
98
70
  end
99
71
  res
100
72
  end
@@ -108,9 +80,9 @@ module Hobo
108
80
  type_class = Hobo.field_types[type] || type
109
81
  field_types[field] = type_class
110
82
 
111
- if "validate".in?(type_class.instance_methods)
83
+ if type_class && "validate".in?(type_class.instance_methods)
112
84
  self.validate do |record|
113
- v = record.send(field).validate
85
+ v = record.send(field)._?.validate
114
86
  record.errors.add(field, v) if v.is_a?(String)
115
87
  end
116
88
  end
@@ -136,7 +108,7 @@ module Hobo
136
108
  end
137
109
 
138
110
  def never_show?(field)
139
- @hobo_never_show and field.to_sym.in?(@hobo_never_show)
111
+ @hobo_never_show && field.to_sym.in?(@hobo_never_show)
140
112
  end
141
113
  public :never_show?
142
114
 
@@ -176,20 +148,20 @@ module Hobo
176
148
  end
177
149
  }
178
150
  end
179
-
151
+
180
152
  key = "id_name#{if underscore; ".gsub('_', ' ')"; end}"
181
153
  finder = if insenstive
182
- "find(:first, :conditions => ['lower(#{id_name_field}) = ?', #{key}.downcase])"
154
+ "find(:first, options.merge(:conditions => ['lower(#{id_name_field}) = ?', #{key}.downcase]))"
183
155
  else
184
- "find_by_#{id_name_field}(#{key})"
156
+ "find_by_#{id_name_field}(#{key}, options)"
185
157
  end
186
158
 
187
159
  class_eval %{
188
- def self.find_by_id_name(id_name)
160
+ def self.find_by_id_name(id_name, options)
189
161
  #{finder}
190
162
  end
191
163
  }
192
-
164
+
193
165
  model = self
194
166
  validate do
195
167
  erros.add id_name_field, "is taken" if model.find_by_id_name(name)
@@ -207,12 +179,11 @@ module Hobo
207
179
  attr_reader :id_name_column
208
180
 
209
181
 
210
-
211
182
  def field_type(name)
212
183
  name = name.to_sym
213
184
  field_types[name] or
214
185
  reflections[name] or begin
215
- col = columns.find {|c| c.name == name.to_s} rescue nil
186
+ col = column(name)
216
187
  return nil if col.nil?
217
188
  case col.type
218
189
  when :boolean
@@ -224,19 +195,18 @@ module Hobo
224
195
  end
225
196
  end
226
197
  end
227
-
228
198
 
229
- def nilable_field?(name)
230
- col = columns.find {|c| c.name == name.to_s} rescue nil
231
- col.nil? || col.null
199
+
200
+ def column(name)
201
+ columns.find {|c| c.name == name.to_s} rescue nil
232
202
  end
233
203
 
234
204
 
235
205
  def conditions(*args, &b)
236
206
  if args.empty?
237
- ModelQueries.new(self).instance_eval(&b).to_sql
207
+ ModelQueries.new(self).instance_eval(&b)._?.to_sql
238
208
  else
239
- ModelQueries.new(self).instance_exec(*args, &b).to_sql
209
+ ModelQueries.new(self).instance_exec(*args, &b)._?.to_sql
240
210
  end
241
211
  end
242
212
 
@@ -251,16 +221,28 @@ module Hobo
251
221
  end
252
222
  end
253
223
 
254
- if b && !(block_conditions = conditions(&b)).blank?
255
- c = if !options[:conditions].blank?
256
- "(#{options[:conditons]}) and (#{block_conditions})"
224
+ res = if b && !(block_conditions = conditions(&b)).blank?
225
+ c = if !options[:conditions].blank?
226
+ "(#{sanitize_sql options[:conditions]}) AND (#{sanitize_sql block_conditions})"
227
+ else
228
+ block_conditions
229
+ end
230
+ super(args.first, options.merge(:conditions => c))
257
231
  else
258
- block_conditions
232
+ super(*args + [options])
259
233
  end
260
- super(args.first, options.merge(:conditions => c))
261
- else
262
- super(*args + [options])
234
+ if args.first == :all
235
+ def res.member_class
236
+ @member_class
237
+ end
238
+ res.instance_variable_set("@member_class", self)
263
239
  end
240
+ res
241
+ end
242
+
243
+
244
+ def all(options={})
245
+ find(:all, options.reverse_merge(:order => :default))
264
246
  end
265
247
 
266
248
 
@@ -314,7 +296,7 @@ module Hobo
314
296
 
315
297
 
316
298
  class ScopedProxy
317
- def initialize(klass, scope={})
299
+ def initialize(klass, scope)
318
300
  @klass = klass
319
301
 
320
302
  # If there's no :find, or :create specified, assume it's a find scope
@@ -325,9 +307,14 @@ module Hobo
325
307
  end
326
308
  end
327
309
 
310
+
328
311
  def method_missing(name, *args, &block)
329
- @klass.send(:with_scope, @scope) do
330
- @klass.send(name, *args, &block)
312
+ if name.to_sym.in?(@klass.defined_scopes.keys)
313
+ proxy = @klass.send(name, *args)
314
+ proxy.instance_variable_set("@parent_scope", self)
315
+ proxy
316
+ else
317
+ _apply_scope { @klass.send(name, *args, &block) }
331
318
  end
332
319
  end
333
320
 
@@ -338,12 +325,28 @@ module Hobo
338
325
  def first
339
326
  self.find(:first)
340
327
  end
328
+
329
+ def member_class
330
+ @klass
331
+ end
332
+
333
+ private
334
+ def _apply_scope
335
+ if @parent_scope
336
+ @parent_scope.send(:_apply_scope) do
337
+ @scope ? @klass.send(:with_scope, @scope) { yield } : yield
338
+ end
339
+ else
340
+ @scope ? @klass.send(:with_scope, @scope) { yield } : yield
341
+ end
342
+ end
343
+
341
344
  end
342
345
  (Object.instance_methods +
343
346
  Object.private_instance_methods +
344
347
  Object.protected_instance_methods).each do |m|
345
348
  ScopedProxy.send(:undef_method, m) unless
346
- m.in?(%w{initialize method_missing send}) || m.starts_with?('_')
349
+ m.in?(%w{initialize method_missing send instance_variable_set instance_variable_get puts}) || m.starts_with?('_')
347
350
  end
348
351
 
349
352
  attr_accessor :defined_scopes
@@ -384,18 +387,20 @@ module Hobo
384
387
 
385
388
  unless assoc
386
389
  options = proxy_reflection.options
387
- has_many_conditions = options.has_key?(:conditions)
390
+ has_many_conditions = options[:conditions]
391
+ has_many_conditions = nil if has_many_conditions.blank?
388
392
  source = proxy_reflection.source_reflection
389
393
  scope_conditions = find_scope[:conditions]
394
+ scope_conditions = nil if scope_conditions.blank?
390
395
  conditions = if has_many_conditions && scope_conditions
391
- "(#{scope_conditions}) AND (#{has_many_conditions})"
396
+ "(#{sanitize_sql scope_conditions}) AND (#{sanitize_sql has_many_conditions})"
392
397
  else
393
398
  scope_conditions || has_many_conditions
394
399
  end
395
400
 
396
- options = options.merge(find_scope).update(:conditions => conditions,
397
- :class_name => proxy_reflection.klass.name,
401
+ options = options.merge(find_scope).update(:class_name => proxy_reflection.klass.name,
398
402
  :foreign_key => proxy_reflection.primary_key_name)
403
+ options[:conditions] = conditions unless conditions.blank?
399
404
  options[:source] = source.name if source
400
405
 
401
406
  r = ActiveRecord::Reflection::AssociationReflection.new(:has_many,
@@ -437,6 +442,13 @@ module Hobo
437
442
  end
438
443
 
439
444
 
445
+ def attributes_with_hobo_type_conversion=(attributes)
446
+ converted = attributes.map_hash { |k, v| convert_type_for_mass_assignment(self.class.field_type(k), v) }
447
+ self.attributes_without_hobo_type_conversion = converted
448
+ end
449
+
450
+
451
+
440
452
  def set_creator(user)
441
453
  self.send("#{self.class.creator_attr}=", user) if (t = self.class.creator_type) && user.is_a?(t)
442
454
  end
@@ -460,11 +472,14 @@ module Hobo
460
472
 
461
473
 
462
474
  def same_fields?(other, *fields)
475
+ fields = fields.flatten
463
476
  fields.all?{|f| self.send(f) == other.send(f)}
464
477
  end
465
478
 
466
- def changed_fields?(other, *fields)
467
- fields.all?{|f| self.send(f) != other.send(f)}
479
+ def only_changed_fields?(other, *changed_fields)
480
+ changed_fields = changed_fields.flatten.every(:to_s)
481
+ all_cols = self.class.columns.every(:name) - []
482
+ all_cols.all?{|c| c.in?(changed_fields) || self.send(c) == other.send(c) }
468
483
  end
469
484
 
470
485
  def compose_with(object, use=nil)
@@ -493,6 +508,42 @@ module Hobo
493
508
  end
494
509
  end
495
510
 
511
+ private
512
+
513
+ def parse_datetime(s)
514
+ defined?(Chronic) ? Chronic.parse(s) : Time.parse(s)
515
+ end
516
+
517
+ def convert_type_for_mass_assignment(field_type, value)
518
+ if field_type.is_a?(ActiveRecord::Reflection::AssociationReflection) and field_type.macro.in?([:belongs_to, :has_one])
519
+ if value.is_a?(String) && value.starts_with?('@')
520
+ Hobo.object_from_dom_id(value[1..-1])
521
+ else
522
+ value
523
+ end
524
+ elsif !field_type.is_a?(Class)
525
+ value
526
+ elsif field_type <= Date
527
+ if value.is_a? Hash
528
+ Date.new(*(%w{year month day}.map{|s| value[s].to_i}))
529
+ elsif value.is_a? String
530
+ dt = parse_datetime(value)
531
+ dt && dt.to_date
532
+ end
533
+ elsif field_type <= Time
534
+ if value.is_a? Hash
535
+ Time.local(*(%w{year month day hour minute}.map{|s| value[s].to_i}))
536
+ elsif value.is_a? String
537
+ parse_datetime(value)
538
+ end
539
+ elsif field_type <= TrueClass
540
+ (value.is_a?(String) && value.strip.downcase.in?(['0', 'false']) || value.blank?) ? false : true
541
+ else
542
+ # primitive field
543
+ value
544
+ end
545
+ end
546
+
496
547
  end
497
548
  end
498
549
 
@@ -507,17 +558,17 @@ module ActiveRecord::AttributeMethods::ClassMethods
507
558
  access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
508
559
 
509
560
  unless attr_name.to_s == self.primary_key.to_s
510
- access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
561
+ access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) " +
562
+ "unless @attributes.has_key?('#{attr_name}'); ")
511
563
  end
512
564
 
513
565
  # This is the Hobo hook - add a type wrapper around the field
514
566
  # value if we have a special type defined
515
567
  src = if connected? && respond_to?(:field_type) && (type_wrapper = field_type(symbol)) &&
516
- type_wrapper.is_a?(Class) && type_wrapper < String
568
+ type_wrapper.is_a?(Class) && type_wrapper.not_in?(Hobo::Model::PLAIN_TYPES.values)
517
569
  "val = begin; #{access_code}; end; " +
518
- "if val.nil?; nil; " +
519
- "elsif val.respond_to?(:hobo_undefined?) && val.hobo_undefined?; val; " +
520
- "else; #{type_wrapper}.new(val); end"
570
+ "if val.nil? || (val.respond_to?(:hobo_undefined?) && val.hobo_undefined?); val; " +
571
+ "else; self.class.field_type(:#{attr_name}).new(val); end"
521
572
  else
522
573
  access_code
523
574
  end
@@ -525,4 +576,22 @@ module ActiveRecord::AttributeMethods::ClassMethods
525
576
  evaluate_attribute_method(attr_name,
526
577
  "def #{symbol}; @attributes_cache['#{attr_name}'] ||= begin; #{src}; end; end")
527
578
  end
579
+
580
+ def define_write_method(attr_name)
581
+ src = if connected? && respond_to?(:field_type) && (type_wrapper = field_type(attr_name)) &&
582
+ type_wrapper.is_a?(Class) && type_wrapper.not_in?(Hobo::Model::PLAIN_TYPES.values)
583
+ "if val.nil? || (val.respond_to?(:hobo_undefined?) && val.hobo_undefined?); val; " +
584
+ "else; self.class.field_type(:#{attr_name}).new(val); end"
585
+ else
586
+ "val"
587
+ end
588
+ evaluate_attribute_method(attr_name, "def #{attr_name}=(val); " +
589
+ "write_attribute('#{attr_name}', #{src});end", "#{attr_name}=")
590
+
591
+ end
592
+
593
+ end
594
+
595
+ class ActiveRecord::Base
596
+ alias_method :has_hobo_method?, :respond_to_without_attributes?
528
597
  end