hobo 0.5.3 → 0.6
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 +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
|