hobo 0.5.3 → 0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/hobo +18 -4
- data/hobo_files/plugin/CHANGES.txt +511 -0
- data/hobo_files/plugin/README +8 -3
- data/hobo_files/plugin/Rakefile +81 -0
- data/hobo_files/plugin/generators/hobo/hobo_generator.rb +4 -4
- data/hobo_files/plugin/generators/hobo/templates/guest.rb +1 -1
- data/hobo_files/plugin/generators/hobo_front_controller/hobo_front_controller_generator.rb +1 -1
- data/hobo_files/plugin/generators/hobo_front_controller/templates/index.dryml +16 -22
- data/hobo_files/plugin/generators/hobo_front_controller/templates/login.dryml +4 -6
- data/hobo_files/plugin/generators/hobo_front_controller/templates/search.dryml +6 -5
- data/hobo_files/plugin/generators/hobo_front_controller/templates/signup.dryml +4 -6
- data/hobo_files/plugin/generators/hobo_migration/hobo_migration_generator.rb +237 -0
- data/hobo_files/plugin/generators/hobo_migration/templates/migration.rb +9 -0
- data/hobo_files/plugin/generators/hobo_model/USAGE +2 -3
- data/hobo_files/plugin/generators/hobo_model/hobo_model_generator.rb +1 -14
- data/hobo_files/plugin/generators/hobo_model/templates/fixtures.yml +1 -6
- data/hobo_files/plugin/generators/hobo_model/templates/model.rb +10 -4
- data/hobo_files/plugin/generators/hobo_rapid/hobo_rapid_generator.rb +7 -6
- data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_base.css +68 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_rapid.css +93 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_rapid.js +11 -6
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/plus.png +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/stylesheets/application.css +24 -14
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/views/application.dryml +28 -44
- data/hobo_files/plugin/generators/hobo_user_model/USAGE +2 -12
- data/hobo_files/plugin/generators/hobo_user_model/hobo_user_model_generator.rb +1 -14
- data/hobo_files/plugin/generators/hobo_user_model/templates/fixtures.yml +0 -6
- data/hobo_files/plugin/generators/hobo_user_model/templates/model.rb +8 -1
- data/hobo_files/plugin/init.rb +6 -2
- data/hobo_files/plugin/lib/active_record/has_many_association.rb +23 -12
- data/hobo_files/plugin/lib/extensions.rb +134 -40
- data/hobo_files/plugin/lib/extensions/test_case.rb +0 -1
- data/hobo_files/plugin/lib/hobo.rb +77 -46
- data/hobo_files/plugin/lib/hobo/authenticated_user.rb +24 -2
- data/hobo_files/plugin/lib/hobo/authentication_support.rb +2 -1
- data/hobo_files/plugin/lib/hobo/controller.rb +35 -12
- data/hobo_files/plugin/lib/hobo/define_tags.rb +4 -4
- data/hobo_files/plugin/lib/hobo/dryml.rb +33 -51
- data/hobo_files/plugin/lib/hobo/dryml/dryml_builder.rb +47 -34
- data/hobo_files/plugin/lib/hobo/dryml/scoped_variables.rb +37 -0
- data/hobo_files/plugin/lib/hobo/dryml/taglib.rb +27 -5
- data/hobo_files/plugin/lib/hobo/dryml/template.rb +545 -302
- data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +305 -135
- data/hobo_files/plugin/lib/hobo/email_address.rb +5 -0
- data/hobo_files/plugin/lib/hobo/field_spec.rb +66 -0
- data/hobo_files/plugin/lib/hobo/hobo_helper.rb +325 -0
- data/hobo_files/plugin/lib/hobo/html_string.rb +2 -0
- data/hobo_files/plugin/lib/hobo/lazy_hash.rb +13 -1
- data/hobo_files/plugin/lib/hobo/markdown_string.rb +3 -1
- data/hobo_files/plugin/lib/hobo/model.rb +185 -66
- data/hobo_files/plugin/lib/hobo/model_controller.rb +56 -49
- data/hobo_files/plugin/lib/hobo/password_string.rb +2 -0
- data/hobo_files/plugin/lib/hobo/plugins.rb +75 -0
- data/hobo_files/plugin/lib/hobo/rapid_helper.rb +98 -0
- data/hobo_files/plugin/lib/hobo/static_tags +0 -3
- data/hobo_files/plugin/lib/hobo/textile_string.rb +11 -1
- data/hobo_files/plugin/lib/hobo/undefined.rb +1 -1
- data/hobo_files/plugin/lib/rexml.rb +166 -75
- data/hobo_files/plugin/spec/fixtures/users.yml +9 -0
- data/hobo_files/plugin/spec/spec.opts +6 -0
- data/hobo_files/plugin/spec/spec_helper.rb +28 -0
- data/hobo_files/plugin/spec/unit/hobo/dryml/template_spec.rb +650 -0
- data/hobo_files/plugin/tags/core.dryml +58 -4
- data/hobo_files/plugin/tags/rapid.dryml +289 -135
- data/hobo_files/plugin/tags/rapid_document_tags.dryml +49 -0
- data/hobo_files/plugin/tags/rapid_editing.dryml +92 -69
- data/hobo_files/plugin/tags/rapid_forms.dryml +242 -0
- data/hobo_files/plugin/tags/rapid_navigation.dryml +65 -65
- data/hobo_files/plugin/tags/rapid_pages.dryml +197 -124
- data/hobo_files/plugin/tags/rapid_support.dryml +23 -0
- metadata +29 -22
- data/hobo_files/plugin/generators/hobo_model/templates/migration.rb +0 -13
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/default_mapping.rb +0 -11
- data/hobo_files/plugin/generators/hobo_user_model/templates/migration.rb +0 -15
- data/hobo_files/plugin/lib/hobo/HtmlString +0 -3
- data/hobo_files/plugin/lib/hobo/controller_helpers.rb +0 -135
- data/hobo_files/plugin/lib/hobo/core.rb +0 -475
- data/hobo_files/plugin/lib/hobo/rapid.rb +0 -447
- data/hobo_files/plugin/test/hobo_dryml_template_test.rb +0 -7
- data/hobo_files/plugin/test/hobo_test.rb +0 -7
@@ -0,0 +1,66 @@
|
|
1
|
+
module Hobo
|
2
|
+
|
3
|
+
class FieldSpec
|
4
|
+
|
5
|
+
class UnknownSqlTypeError < RuntimeError; end
|
6
|
+
|
7
|
+
def initialize(model, name, type, options={})
|
8
|
+
raise ArgumentError, "you cannot provide a field spec for the primary key" if name == model.primary_key
|
9
|
+
self.model = model
|
10
|
+
self.name = name.to_sym
|
11
|
+
self.type = type.to_sym
|
12
|
+
self.options = options
|
13
|
+
self.position = model.field_specs.length
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_accessor :model, :name, :type, :position, :options
|
17
|
+
|
18
|
+
def sql_type
|
19
|
+
options[:sql_type] or begin
|
20
|
+
sql_types = model.connection.native_database_types.keys - [:primary_key]
|
21
|
+
if type.in?(sql_types)
|
22
|
+
type
|
23
|
+
elsif options[:length]
|
24
|
+
:string
|
25
|
+
else
|
26
|
+
Hobo.field_types[type]::COLUMN_TYPE or raise UnknownSqlTypeError, type
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def limit
|
32
|
+
options[:limit] || types[sql_type][:limit]
|
33
|
+
end
|
34
|
+
|
35
|
+
def precision
|
36
|
+
options[:precision]
|
37
|
+
end
|
38
|
+
|
39
|
+
def scale
|
40
|
+
options[:scale]
|
41
|
+
end
|
42
|
+
|
43
|
+
def null
|
44
|
+
:null.in?(options) ? options[:null] : true
|
45
|
+
end
|
46
|
+
|
47
|
+
def default
|
48
|
+
options[:default]
|
49
|
+
end
|
50
|
+
|
51
|
+
def different_to?(col_spec)
|
52
|
+
[:limit, :precision, :scale, :null, :default].any? do |k|
|
53
|
+
# puts "#{col_spec.send(k).inspect} --- #{self.send(k).inspect} : #{col_spec.send(k) != self.send(k)}"
|
54
|
+
col_spec.send(k) != self.send(k)
|
55
|
+
end || sql_type != col_spec.type
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def types
|
61
|
+
@types ||= model.connection.native_database_types
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,325 @@
|
|
1
|
+
module Hobo
|
2
|
+
|
3
|
+
module HoboHelper
|
4
|
+
|
5
|
+
def self.add_to_controller(controller)
|
6
|
+
controller.send(:include, self)
|
7
|
+
controller.hide_action(self.instance_methods)
|
8
|
+
end
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
def current_user
|
13
|
+
# simple one-hit-per-request cache
|
14
|
+
@current_user or
|
15
|
+
@current_user = if Hobo.user_model and session and id = session[:user]
|
16
|
+
Hobo.user_model.find(id)
|
17
|
+
else
|
18
|
+
Guest.new
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def logged_in?
|
24
|
+
not current_user.guest?
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def base_url
|
29
|
+
request.relative_url_root
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def controller_for(obj)
|
34
|
+
if obj.is_a? Class
|
35
|
+
obj.name.underscore.pluralize
|
36
|
+
else
|
37
|
+
obj.class.name.underscore.pluralize
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def object_url(obj, action=nil, *param_hashes)
|
43
|
+
action &&= action.to_s
|
44
|
+
|
45
|
+
controller_name = controller_for(obj)
|
46
|
+
|
47
|
+
parts = if obj.is_a? Class
|
48
|
+
[base_url, controller_name]
|
49
|
+
|
50
|
+
elsif obj.is_a? Hobo::CompositeModel
|
51
|
+
[base_url, controller_name, obj.id]
|
52
|
+
|
53
|
+
elsif obj.is_a? ActiveRecord::Base
|
54
|
+
if obj.new_record?
|
55
|
+
[base_url, controller_name]
|
56
|
+
else
|
57
|
+
raise HoboError.new("invalid object url: new for existing object") if action == "new"
|
58
|
+
|
59
|
+
klass = obj.class
|
60
|
+
id = if klass.id_name?
|
61
|
+
obj.id_name(true)
|
62
|
+
else
|
63
|
+
obj.id
|
64
|
+
end
|
65
|
+
|
66
|
+
[base_url, controller_name, id]
|
67
|
+
end
|
68
|
+
|
69
|
+
elsif obj.is_a? Array # warning - this breaks if we use `case/when Array`
|
70
|
+
owner = obj.proxy_owner
|
71
|
+
new_model = obj.proxy_reflection.klass
|
72
|
+
[object_url(owner), obj.proxy_reflection.name]
|
73
|
+
|
74
|
+
else
|
75
|
+
raise HoboError.new("cannot create url for #{obj.inspect} (#{obj.class})")
|
76
|
+
end
|
77
|
+
basic = parts.join("/")
|
78
|
+
|
79
|
+
controller = (controller_name.camelize + "Controller").constantize rescue nil
|
80
|
+
url = case action
|
81
|
+
when "new"
|
82
|
+
basic + "/new"
|
83
|
+
when "destroy"
|
84
|
+
basic + "?_method=DELETE"
|
85
|
+
when "update"
|
86
|
+
basic + "?_method=PUT"
|
87
|
+
when nil
|
88
|
+
basic
|
89
|
+
else
|
90
|
+
basic + "/" + action
|
91
|
+
end
|
92
|
+
params = make_params(*param_hashes)
|
93
|
+
params.blank? ? url : url + "?" + params
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
def _as_params(name, obj)
|
98
|
+
if obj.is_a? Array
|
99
|
+
obj.map {|x| _as_params("#{name}[]", x)}.join("&")
|
100
|
+
elsif obj.is_a? Hash
|
101
|
+
obj.map {|k,v| _as_params("#{name}[#{k}]", v)}.join("&")
|
102
|
+
elsif obj.is_a? Hobo::RawJs
|
103
|
+
"#{name}=' + #{obj} + '"
|
104
|
+
else
|
105
|
+
v = if obj.is_a?(ActiveRecord::Base) or obj.is_a?(Array)
|
106
|
+
"@" + dom_id(obj)
|
107
|
+
else
|
108
|
+
obj.to_s.gsub("'"){"\\'"}
|
109
|
+
end
|
110
|
+
"#{name}=#{v}"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
def make_params(*hashes)
|
116
|
+
hash = {}
|
117
|
+
hashes.each {|h| hash.update(h) if h}
|
118
|
+
hash.map {|k,v| _as_params(k, v)}.join("&")
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
def dom_id(*args)
|
123
|
+
if args.length == 0
|
124
|
+
Hobo.dom_id(this)
|
125
|
+
else
|
126
|
+
Hobo.dom_id(*args)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
def type_name(type=nil)
|
132
|
+
Hobo.type_name(type || this.class)
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
def type_and_field(*args)
|
137
|
+
if args.empty?
|
138
|
+
this_parent && this_field && "#{Hobo.type_name(this_parent.class)}_#{this_field}"
|
139
|
+
else
|
140
|
+
type, field = args
|
141
|
+
"#{type_name(type)}_#{field}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
def map_this
|
147
|
+
res = []
|
148
|
+
this.each_index {|i| new_field_context(i) { res << yield } }
|
149
|
+
Dryml.last_if = !this.empty?
|
150
|
+
res
|
151
|
+
end
|
152
|
+
alias_method :collect_this, :map_this
|
153
|
+
|
154
|
+
|
155
|
+
def comma_split(x)
|
156
|
+
case x
|
157
|
+
when nil
|
158
|
+
[]
|
159
|
+
when Symbol
|
160
|
+
x.to_s
|
161
|
+
when String
|
162
|
+
x.split(/\s*[, ]\s*/)
|
163
|
+
else
|
164
|
+
x.compact.map{|e| comma_split(e)}.flatten
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
def can_create?(object=nil)
|
170
|
+
Hobo.can_create?(current_user, object || this)
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
def can_update?(object, new)
|
175
|
+
Hobo.can_update?(current_user, object, new)
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
def can_edit?(*args)
|
180
|
+
if args.empty?
|
181
|
+
this_parent && this_field && can_edit?(this_parent, this_field)
|
182
|
+
else
|
183
|
+
object, field = args.length == 2 ? args : [this, args.first]
|
184
|
+
|
185
|
+
if !field and object.respond_to?(:proxy_reflection)
|
186
|
+
Hobo.can_edit?(current_user, object.proxy_owner, object.proxy_reflection.name)
|
187
|
+
else
|
188
|
+
Hobo.can_edit?(current_user, object, field)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
def can_delete?(object=nil)
|
195
|
+
Hobo.can_delete?(current_user, object || this)
|
196
|
+
end
|
197
|
+
|
198
|
+
|
199
|
+
def can_view?(object=nil, field=nil)
|
200
|
+
if object.nil? && field.nil?
|
201
|
+
if this_parent && this_field
|
202
|
+
object, field = this_parent, this_field
|
203
|
+
else
|
204
|
+
object = this
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
if !field and object.respond_to?(:proxy_reflection)
|
209
|
+
Hobo.can_view?(current_user, object.proxy_owner, object.proxy_reflection.name)
|
210
|
+
else
|
211
|
+
Hobo.can_view?(current_user, object, field)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
|
216
|
+
def select_viewable(collection)
|
217
|
+
collection.select {|x| can_view?(x)}
|
218
|
+
end
|
219
|
+
|
220
|
+
|
221
|
+
def theme_asset(path)
|
222
|
+
theme_path = Hobo.current_theme ? "hobothemes/#{Hobo.current_theme}/" : ""
|
223
|
+
"#{base_url}/#{theme_path}#{path}"
|
224
|
+
end
|
225
|
+
|
226
|
+
def js_str(s)
|
227
|
+
if s.is_a? Hobo::RawJs
|
228
|
+
s.to_s
|
229
|
+
else
|
230
|
+
"'" + s.gsub("'"){"\\'"} + "'"
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
|
235
|
+
def make_params_js(*args)
|
236
|
+
("'" + make_params(*args) + "'").sub(/ \+ ''$/,'')
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
def render_params(*args)
|
241
|
+
parts = args.map{|x| x.split(/, */) if x}.compact.flatten
|
242
|
+
{ :part_page => view_name,
|
243
|
+
:render => parts.map do |part_id|
|
244
|
+
{ :object => Hobo::RawJs.new("hoboParts.#{part_id}"),
|
245
|
+
:part => part_id }
|
246
|
+
end
|
247
|
+
}
|
248
|
+
end
|
249
|
+
|
250
|
+
|
251
|
+
def nl_to_br(s)
|
252
|
+
s.to_s.gsub("\n", "<br/>") if s
|
253
|
+
end
|
254
|
+
|
255
|
+
|
256
|
+
def param_name_for(object, field_path)
|
257
|
+
field_path = field_path.to_s.split(".") if field_path.is_a?(String, Symbol)
|
258
|
+
attrs = field_path.map{|part| "[#{part.to_s.sub /\?$/, ''}]"}.join
|
259
|
+
"#{object.class.name.underscore}#{attrs}"
|
260
|
+
end
|
261
|
+
|
262
|
+
|
263
|
+
def param_name_for_this(foreign_key=false)
|
264
|
+
return "" unless form_this
|
265
|
+
name = if foreign_key and this_type.respond_to?(:macro) and this_type.macro == :belongs_to
|
266
|
+
param_name_for(form_this, form_field_path[0..-2] + [this_type.primary_key_name])
|
267
|
+
else
|
268
|
+
param_name_for(form_this, form_field_path)
|
269
|
+
end
|
270
|
+
register_form_field(name)
|
271
|
+
name
|
272
|
+
end
|
273
|
+
|
274
|
+
|
275
|
+
def selector_type
|
276
|
+
if this.is_a? ActiveRecord::Base
|
277
|
+
this.class
|
278
|
+
elsif this.respond_to? :member_class
|
279
|
+
this.member_class
|
280
|
+
elsif this == @this
|
281
|
+
@model
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
|
286
|
+
def transpose_with_field(field, collection=nil)
|
287
|
+
collection ||= this
|
288
|
+
matrix = collection.map {|obj| obj.send(field) }
|
289
|
+
max_length = matrix.every(:length).max
|
290
|
+
matrix = matrix.map do |a|
|
291
|
+
a + [nil] * (max_length - a.length)
|
292
|
+
end
|
293
|
+
matrix.transpose
|
294
|
+
end
|
295
|
+
|
296
|
+
|
297
|
+
def create_model(model)
|
298
|
+
n = model.new
|
299
|
+
n.set_creator(current_user)
|
300
|
+
n
|
301
|
+
end
|
302
|
+
|
303
|
+
|
304
|
+
def defined_route?(r)
|
305
|
+
@view.respond_to?("#{r}_url")
|
306
|
+
end
|
307
|
+
|
308
|
+
|
309
|
+
# debugging support
|
310
|
+
|
311
|
+
def abort_with(*args)
|
312
|
+
raise args.map{|arg| PP.pp(arg, "")}.join("-------\n")
|
313
|
+
end
|
314
|
+
|
315
|
+
def log_debug(*args)
|
316
|
+
logger.debug("\n### DRYML Debug ###")
|
317
|
+
logger.debug(args.map {|a| PP.pp(a, "")}.join("-------\n"))
|
318
|
+
logger.debug("DRYML THIS = #{Hobo.dom_id(this) rescue this.inspect}")
|
319
|
+
logger.debug("###################\n")
|
320
|
+
args.first unless args.empty?
|
321
|
+
end
|
322
|
+
|
323
|
+
end
|
324
|
+
|
325
|
+
end
|
@@ -8,6 +8,7 @@ class Hobo::LazyHash < Hash
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
+
|
11
12
|
def [](key)
|
12
13
|
val = super
|
13
14
|
if val.is_a?(Proc)
|
@@ -17,10 +18,21 @@ class Hobo::LazyHash < Hash
|
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
21
|
+
|
22
|
+
def delete(key)
|
23
|
+
val = super
|
24
|
+
val.is_a?(Proc) ? val.call : val
|
25
|
+
end
|
26
|
+
|
27
|
+
|
20
28
|
def inspect
|
21
|
-
|
29
|
+
pairs = map do |k, v|
|
30
|
+
"#{k.inspect} => #{v.is_a?(Proc) ? '??' : v.inspect}"
|
31
|
+
end
|
32
|
+
"{#{pairs * ', '}}"
|
22
33
|
end
|
23
34
|
|
35
|
+
|
24
36
|
def to_s
|
25
37
|
inspect
|
26
38
|
end
|
@@ -2,12 +2,31 @@ module Hobo
|
|
2
2
|
|
3
3
|
module Model
|
4
4
|
|
5
|
+
Hobo.field_types.update({ :html => HtmlString,
|
6
|
+
:markdown => MarkdownString,
|
7
|
+
:textile => TextileString,
|
8
|
+
:password => PasswordString,
|
9
|
+
:text => Hobo::Text,
|
10
|
+
:boolean => TrueClass,
|
11
|
+
:date => Date,
|
12
|
+
:datetime => Time,
|
13
|
+
:integer => Fixnum,
|
14
|
+
:big_integer => BigDecimal,
|
15
|
+
:float => Float,
|
16
|
+
:string => String,
|
17
|
+
:email_address => EmailAddress
|
18
|
+
})
|
19
|
+
|
5
20
|
def self.included(base)
|
6
21
|
Hobo.register_model(base)
|
7
22
|
base.extend(ClassMethods)
|
8
|
-
base.
|
23
|
+
base.class_eval do
|
24
|
+
@field_specs = HashWithIndifferentAccess.new
|
25
|
+
set_field_type({})
|
26
|
+
end
|
9
27
|
class << base
|
10
28
|
alias_method_chain :has_many, :defined_scopes
|
29
|
+
alias_method_chain :belongs_to, :foreign_key_declaration
|
11
30
|
end
|
12
31
|
end
|
13
32
|
|
@@ -16,43 +35,72 @@ module Hobo
|
|
16
35
|
# include methods also shared by CompositeModel
|
17
36
|
include ModelSupport::ClassMethods
|
18
37
|
|
38
|
+
private
|
39
|
+
|
40
|
+
def return_type(type)
|
41
|
+
@next_method_type = type
|
42
|
+
end
|
43
|
+
|
19
44
|
def method_added(name)
|
20
|
-
|
21
|
-
|
22
|
-
|
45
|
+
if @next_method_type
|
46
|
+
set_field_type(name => @next_method_type)
|
47
|
+
@next_method_type = nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
class FieldDeclarationsDsl
|
23
53
|
|
24
|
-
|
25
|
-
|
54
|
+
def initialize(model)
|
55
|
+
@model = model
|
56
|
+
end
|
26
57
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
58
|
+
attr_reader :model
|
59
|
+
|
60
|
+
def timestamps
|
61
|
+
field(:created_at, :datetime)
|
62
|
+
field(:updated_at, :datetime)
|
63
|
+
end
|
64
|
+
|
65
|
+
def field(name, *args)
|
66
|
+
type = args.shift
|
67
|
+
options = extract_options_from_args!(args)
|
68
|
+
@model.send(:set_field_type, name => type) unless
|
69
|
+
type.in?(@model.connection.native_database_types.keys - [:text])
|
70
|
+
@model.field_specs[name] = FieldSpec.new(@model, name, type, options)
|
71
|
+
end
|
72
|
+
|
73
|
+
def method_missing(name, *args)
|
74
|
+
field(name, *args)
|
41
75
|
end
|
76
|
+
|
42
77
|
end
|
43
78
|
|
79
|
+
|
80
|
+
def fields(&b)
|
81
|
+
FieldDeclarationsDsl.new(self).instance_eval(&b)
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
def belongs_to_with_foreign_key_declaration(name, *args, &block)
|
86
|
+
res = belongs_to_without_foreign_key_declaration(name, *args, &block)
|
87
|
+
refl = reflections[name]
|
88
|
+
fkey = refl.primary_key_name
|
89
|
+
field_specs[fkey] ||= FieldSpec.new(self, fkey, :integer)
|
90
|
+
if refl.options[:polymorphic]
|
91
|
+
type_col = "#{name}_type"
|
92
|
+
field_specs[type_col] ||= FieldSpec.new(self, type_col, :string)
|
93
|
+
end
|
94
|
+
res
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
attr_reader :field_specs
|
99
|
+
public :field_specs
|
100
|
+
|
44
101
|
def set_field_type(types)
|
45
102
|
types.each_pair do |field, type|
|
46
|
-
|
47
|
-
# TODO: Make this extensible
|
48
|
-
type_class = case type
|
49
|
-
when :html; HtmlString
|
50
|
-
when :markdown; MarkdownString
|
51
|
-
when :textile; TextileString
|
52
|
-
when :password; PasswordString
|
53
|
-
else type
|
54
|
-
end
|
55
|
-
|
103
|
+
type_class = Hobo.field_types[type] || type
|
56
104
|
field_types[field] = type_class
|
57
105
|
end
|
58
106
|
end
|
@@ -75,10 +123,10 @@ module Hobo
|
|
75
123
|
@hobo_never_show.concat(fields.omap{to_sym})
|
76
124
|
end
|
77
125
|
|
78
|
-
|
79
126
|
def never_show?(field)
|
80
127
|
@hobo_never_show and field.to_sym.in?(@hobo_never_show)
|
81
128
|
end
|
129
|
+
public :never_show?
|
82
130
|
|
83
131
|
def set_creator_attr(attr)
|
84
132
|
class_eval %{
|
@@ -143,12 +191,12 @@ module Hobo
|
|
143
191
|
underscore
|
144
192
|
end
|
145
193
|
|
146
|
-
|
194
|
+
public
|
195
|
+
|
147
196
|
def id_name?
|
148
197
|
respond_to?(:find_by_id_name)
|
149
198
|
end
|
150
199
|
|
151
|
-
|
152
200
|
attr_reader :id_name_column
|
153
201
|
|
154
202
|
|
@@ -156,15 +204,24 @@ module Hobo
|
|
156
204
|
def field_type(name)
|
157
205
|
name = name.to_sym
|
158
206
|
field_types[name] or
|
159
|
-
reflections[name] or
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
207
|
+
reflections[name] or begin
|
208
|
+
col = columns.find {|c| c.name == name.to_s} rescue nil
|
209
|
+
return nil if col.nil?
|
210
|
+
case col.type
|
211
|
+
when :boolean
|
212
|
+
TrueClass
|
213
|
+
when :text
|
214
|
+
Hobo::Text
|
215
|
+
else
|
216
|
+
col.klass
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
def nilable_field?(name)
|
223
|
+
col = columns.find {|c| c.name == name.to_s} rescue nil
|
224
|
+
col.nil? || col.null
|
168
225
|
end
|
169
226
|
|
170
227
|
|
@@ -244,20 +301,35 @@ module Hobo
|
|
244
301
|
|
245
302
|
class ScopedProxy
|
246
303
|
def initialize(klass, scope={})
|
247
|
-
@klass
|
304
|
+
@klass = klass
|
305
|
+
|
306
|
+
# If there's no :find, or :create specified, assume it's a find scope
|
307
|
+
@scope = if scope.has_key?(:find) || scope.has_key?(:create)
|
308
|
+
scope
|
309
|
+
else
|
310
|
+
{ :find => scope }
|
311
|
+
end
|
248
312
|
end
|
249
313
|
|
250
314
|
def method_missing(name, *args, &block)
|
251
|
-
klass.with_scope
|
315
|
+
@klass.send(:with_scope, @scope) do
|
252
316
|
@klass.send(name, *args, &block)
|
253
317
|
end
|
254
318
|
end
|
319
|
+
|
320
|
+
def all
|
321
|
+
self.find(:all)
|
322
|
+
end
|
323
|
+
|
324
|
+
def first
|
325
|
+
self.find(:first)
|
326
|
+
end
|
255
327
|
end
|
256
328
|
(Object.instance_methods +
|
257
329
|
Object.private_instance_methods +
|
258
330
|
Object.protected_instance_methods).each do |m|
|
259
331
|
ScopedProxy.send(:undef_method, m) unless
|
260
|
-
m.in?(%w{initialize method_missing}) || m.starts_with?('_')
|
332
|
+
m.in?(%w{initialize method_missing send}) || m.starts_with?('_')
|
261
333
|
end
|
262
334
|
|
263
335
|
attr_accessor :defined_scopes
|
@@ -278,24 +350,35 @@ module Hobo
|
|
278
350
|
attr_accessor :reflections
|
279
351
|
|
280
352
|
def method_missing(name, *args, &block)
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
353
|
+
scope = (proxy_reflection.klass.respond_to?(:defined_scopes) and
|
354
|
+
scopes = proxy_reflection.klass.defined_scopes and
|
355
|
+
scopes[name.to_sym])
|
356
|
+
|
357
|
+
scope = scope.call(*args) if scope.is_a?(Proc)
|
358
|
+
|
359
|
+
# If there's no :find, or :create specified, assume it's a find scope
|
360
|
+
find_scope = if scope && (scope.has_key?(:find) || scope.has_key?(:create))
|
361
|
+
scope[:find]
|
362
|
+
else
|
363
|
+
scope
|
364
|
+
end
|
365
|
+
|
366
|
+
if find_scope
|
367
|
+
# Calling instance_variable_get directly causes self to
|
368
|
+
# get loaded, hence this trick
|
287
369
|
assoc = Kernel.instance_method(:instance_variable_get).bind(self).call("@#{name}")
|
370
|
+
|
288
371
|
unless assoc
|
289
372
|
options = proxy_reflection.options
|
290
|
-
has_many_conditions = options.has_key?(:
|
291
|
-
scope_conditions =
|
373
|
+
has_many_conditions = options.has_key?(:conditions)
|
374
|
+
scope_conditions = find_scope.delete(:conditions)
|
292
375
|
conditions = if has_many_conditions && scope_conditions
|
293
376
|
"(#{scope_conditions}) AND (#{has_many_conditions})"
|
294
377
|
else
|
295
378
|
scope_conditions || has_many_conditions
|
296
379
|
end
|
297
380
|
|
298
|
-
options = options.merge(
|
381
|
+
options = options.merge(find_scope).update(:conditions => conditions,
|
299
382
|
:class_name => proxy_reflection.klass.name,
|
300
383
|
:foreign_key => proxy_reflection.primary_key_name)
|
301
384
|
r = ActiveRecord::Reflection::AssociationReflection.new(:has_many,
|
@@ -336,18 +419,7 @@ module Hobo
|
|
336
419
|
end
|
337
420
|
|
338
421
|
|
339
|
-
def
|
340
|
-
val = super
|
341
|
-
if val.nil?
|
342
|
-
nil
|
343
|
-
else
|
344
|
-
type_wrapper = self.class.field_type(name)
|
345
|
-
(type_wrapper && type_wrapper.is_a?(Class) && type_wrapper < String) ? type_wrapper.new(val) : val
|
346
|
-
end
|
347
|
-
end
|
348
|
-
|
349
|
-
|
350
|
-
def created_by(user)
|
422
|
+
def set_creator(user)
|
351
423
|
self.creator ||= user if self.class.has_creator? and not user.guest?
|
352
424
|
end
|
353
425
|
|
@@ -381,11 +453,58 @@ module Hobo
|
|
381
453
|
CompositeModel.new_for([self, object])
|
382
454
|
end
|
383
455
|
|
456
|
+
def created_date
|
457
|
+
created_at.to_date
|
458
|
+
end
|
384
459
|
|
460
|
+
def modified_date
|
461
|
+
modified_at.to_date
|
462
|
+
end
|
463
|
+
|
385
464
|
def typed_id
|
386
465
|
id ? "#{self.class.name.underscore}_#{self.id}" : nil
|
387
466
|
end
|
388
467
|
|
468
|
+
def to_s
|
469
|
+
if respond_to? :title
|
470
|
+
title
|
471
|
+
elsif respond_to? :name
|
472
|
+
name
|
473
|
+
else
|
474
|
+
"#{self.class.name.humanize} #{id}"
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
389
478
|
end
|
390
479
|
end
|
391
480
|
|
481
|
+
|
482
|
+
# Hack AR to get Hobo type wrappers in
|
483
|
+
|
484
|
+
module ActiveRecord::AttributeMethods::ClassMethods
|
485
|
+
|
486
|
+
# Define an attribute reader method. Cope with nil column.
|
487
|
+
def define_read_method(symbol, attr_name, column)
|
488
|
+
cast_code = column.type_cast_code('v') if column
|
489
|
+
access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
|
490
|
+
|
491
|
+
unless attr_name.to_s == self.primary_key.to_s
|
492
|
+
access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
|
493
|
+
end
|
494
|
+
|
495
|
+
# This is the Hobo hook - add a type wrapper around the field
|
496
|
+
# value if we have a special type defined
|
497
|
+
src = if connected? && respond_to?(:field_type) && (type_wrapper = field_type(symbol)) &&
|
498
|
+
type_wrapper.is_a?(Class) && type_wrapper < String
|
499
|
+
"val = begin; #{access_code}; end; " +
|
500
|
+
"if val.nil?; nil; " +
|
501
|
+
"elsif val.respond_to?(:hobo_undefined?) && val.hobo_undefined?; val; " +
|
502
|
+
"else; #{type_wrapper}.new(val); end"
|
503
|
+
else
|
504
|
+
access_code
|
505
|
+
end
|
506
|
+
|
507
|
+
evaluate_attribute_method(attr_name,
|
508
|
+
"def #{symbol}; @attributes_cache['#{attr_name}'] ||= begin; #{src}; end; end")
|
509
|
+
end
|
510
|
+
end
|