hobo 0.6.2 → 0.6.3

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