hobo 0.6.2 → 0.6.3
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/hobo +21 -22
- data/hobo_files/plugin/CHANGES.txt +429 -4
- data/hobo_files/plugin/Rakefile +2 -2
- data/hobo_files/plugin/generators/hobo_front_controller/templates/index.dryml +6 -5
- data/hobo_files/plugin/generators/hobo_front_controller/templates/search.dryml +2 -2
- data/hobo_files/plugin/generators/hobo_migration/hobo_migration_generator.rb +20 -15
- data/hobo_files/plugin/generators/hobo_model/templates/model.rb +1 -0
- data/hobo_files/plugin/generators/hobo_model_controller/templates/controller.rb +2 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_base.css +1 -2
- data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_rapid.css +4 -3
- data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_rapid.js +94 -12
- data/hobo_files/plugin/generators/hobo_rapid/templates/lowpro.js +5 -183
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/stylesheets/application.css +1 -1
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/views/application.dryml +23 -1
- data/hobo_files/plugin/generators/hobo_user_controller/templates/controller.rb +2 -0
- data/hobo_files/plugin/generators/hobo_user_model/templates/model.rb +3 -1
- data/hobo_files/plugin/init.rb +18 -7
- data/hobo_files/plugin/lib/active_record/has_many_association.rb +2 -2
- data/hobo_files/plugin/lib/extensions.rb +56 -12
- data/hobo_files/plugin/lib/hobo.rb +25 -88
- data/hobo_files/plugin/lib/hobo/composite_model.rb +2 -0
- data/hobo_files/plugin/lib/hobo/controller.rb +40 -20
- data/hobo_files/plugin/lib/hobo/dryml.rb +122 -106
- data/hobo_files/plugin/lib/hobo/dryml/dryml_builder.rb +2 -1
- data/hobo_files/plugin/lib/hobo/dryml/part_context.rb +3 -2
- data/hobo_files/plugin/lib/hobo/dryml/taglib.rb +19 -3
- data/hobo_files/plugin/lib/hobo/dryml/template.rb +40 -25
- data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +41 -20
- data/hobo_files/plugin/lib/hobo/email_address.rb +4 -1
- data/hobo_files/plugin/lib/hobo/enum_string.rb +50 -0
- data/hobo_files/plugin/lib/hobo/field_declaration_dsl.rb +36 -0
- data/hobo_files/plugin/lib/hobo/field_spec.rb +4 -7
- data/hobo_files/plugin/lib/hobo/hobo_helper.rb +47 -44
- data/hobo_files/plugin/lib/hobo/html_string.rb +2 -0
- data/hobo_files/plugin/lib/hobo/markdown_string.rb +2 -0
- data/hobo_files/plugin/lib/hobo/model.rb +158 -89
- data/hobo_files/plugin/lib/hobo/model_controller.rb +422 -376
- data/hobo_files/plugin/lib/hobo/model_queries.rb +1 -1
- data/hobo_files/plugin/lib/hobo/model_router.rb +174 -0
- data/hobo_files/plugin/lib/hobo/password_string.rb +2 -0
- data/hobo_files/plugin/lib/hobo/percentage.rb +14 -0
- data/hobo_files/plugin/lib/hobo/plugins.rb +4 -4
- data/hobo_files/plugin/lib/hobo/rapid_helper.rb +10 -2
- data/hobo_files/plugin/lib/hobo/text.rb +3 -3
- data/hobo_files/plugin/lib/hobo/textile_string.rb +2 -0
- data/hobo_files/plugin/lib/hobo/undefined.rb +3 -2
- data/hobo_files/plugin/lib/hobo/{authenticated_user.rb → user.rb} +10 -3
- data/hobo_files/plugin/lib/hobo/user_controller.rb +27 -23
- data/hobo_files/plugin/tags/core.dryml +8 -2
- data/hobo_files/plugin/tags/rapid.dryml +52 -40
- data/hobo_files/plugin/tags/rapid_document_tags.dryml +15 -11
- data/hobo_files/plugin/tags/rapid_editing.dryml +41 -9
- data/hobo_files/plugin/tags/rapid_forms.dryml +136 -36
- data/hobo_files/plugin/tags/rapid_navigation.dryml +2 -2
- data/hobo_files/plugin/tags/rapid_pages.dryml +204 -221
- data/hobo_files/plugin/tags/rapid_plus.dryml +8 -6
- data/hobo_files/plugin/tags/rapid_support.dryml +2 -3
- metadata +44 -42
- data/hobo_files/plugin/lib/hobo/define_tags.rb +0 -56
- 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
|
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, &
|
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(
|
314
|
-
call_block_tag_parameter(the_tag, attributes, proc { attrs.update(:tagbody => tagbody_proc) }, &
|
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
|
-
|
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
|
326
|
-
new_context {
|
327
|
-
elsif
|
328
|
-
new_context {
|
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
|
341
|
-
|
341
|
+
if overriding_tagbody
|
342
|
+
overriding_tagbody.call(proc { default_tagbody ? default_tagbody.call(default) : "" })
|
342
343
|
else
|
343
|
-
|
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,
|
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
|
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
|
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>\
|
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
|
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
|
-
|
21
|
-
if type.in?(
|
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
|
15
|
-
|
16
|
-
Hobo.object_from_dom_id(id)
|
17
|
-
|
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(
|
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
|
-
[
|
57
|
+
[base, controller_name]
|
49
58
|
|
50
59
|
elsif obj.is_a? Hobo::CompositeModel
|
51
|
-
[
|
60
|
+
[base, controller_name, obj.to_param]
|
52
61
|
|
53
62
|
elsif obj.is_a? ActiveRecord::Base
|
54
63
|
if obj.new_record?
|
55
|
-
[
|
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.
|
72
|
+
obj.to_param
|
64
73
|
end
|
65
74
|
|
66
|
-
[
|
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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
150
|
-
|
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
|
-
|
347
|
+
HashWithIndifferentAccess[*pairs.flatten]
|
345
348
|
else
|
346
|
-
|
349
|
+
HashWithIndifferentAccess.new
|
347
350
|
end
|
348
351
|
end
|
349
352
|
|
@@ -2,20 +2,16 @@ module Hobo
|
|
2
2
|
|
3
3
|
module Model
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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)
|
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
|
-
|
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
|
-
|
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
|
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 =
|
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
|
-
|
230
|
-
|
231
|
-
|
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
|
-
|
256
|
-
|
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
|
-
|
232
|
+
super(*args + [options])
|
259
233
|
end
|
260
|
-
|
261
|
-
|
262
|
-
|
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.
|
330
|
-
@klass.send(name, *args
|
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
|
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(:
|
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
|
467
|
-
|
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)
|
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
|
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
|
519
|
-
"
|
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
|