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.
- 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
|