hobo 0.6.1 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/hobo +3 -2
- data/hobo_files/plugin/CHANGES.txt +299 -2
- data/hobo_files/plugin/Rakefile +12 -10
- data/hobo_files/plugin/generators/hobo/templates/guest.rb +1 -13
- data/hobo_files/plugin/generators/hobo_migration/hobo_migration_generator.rb +11 -7
- data/hobo_files/plugin/generators/hobo_rapid/hobo_rapid_generator.rb +1 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_rapid.js +1 -1
- data/hobo_files/plugin/generators/hobo_rapid/templates/lowpro.js +405 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/views/application.dryml +1 -1
- data/hobo_files/plugin/generators/hobo_user_model/templates/model.rb +1 -9
- data/hobo_files/plugin/init.rb +5 -0
- data/hobo_files/plugin/lib/active_record/has_many_association.rb +1 -1
- data/hobo_files/plugin/lib/extensions.rb +26 -5
- data/hobo_files/plugin/lib/extensions/test_case.rb +1 -1
- data/hobo_files/plugin/lib/hobo.rb +37 -11
- data/hobo_files/plugin/lib/hobo/authenticated_user.rb +7 -2
- data/hobo_files/plugin/lib/hobo/authentication_support.rb +7 -6
- data/hobo_files/plugin/lib/hobo/composite_model.rb +5 -0
- data/hobo_files/plugin/lib/hobo/controller.rb +4 -4
- data/hobo_files/plugin/lib/hobo/dryml.rb +5 -5
- data/hobo_files/plugin/lib/hobo/dryml/part_context.rb +3 -6
- data/hobo_files/plugin/lib/hobo/dryml/template.rb +16 -15
- data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +24 -20
- data/hobo_files/plugin/lib/hobo/email_address.rb +4 -0
- data/hobo_files/plugin/lib/hobo/field_spec.rb +2 -1
- data/hobo_files/plugin/lib/hobo/guest.rb +21 -0
- data/hobo_files/plugin/lib/hobo/hobo_helper.rb +42 -2
- data/hobo_files/plugin/lib/hobo/http_parameters.rb +225 -0
- data/hobo_files/plugin/lib/hobo/model.rb +55 -37
- data/hobo_files/plugin/lib/hobo/model_controller.rb +151 -151
- data/hobo_files/plugin/lib/hobo/model_queries.rb +30 -5
- data/hobo_files/plugin/lib/hobo/user_controller.rb +27 -16
- data/hobo_files/plugin/lib/hobo/where_fragment.rb +6 -1
- data/hobo_files/plugin/tags/rapid.dryml +88 -58
- data/hobo_files/plugin/tags/rapid_document_tags.dryml +5 -5
- data/hobo_files/plugin/tags/rapid_editing.dryml +3 -3
- data/hobo_files/plugin/tags/rapid_forms.dryml +35 -26
- data/hobo_files/plugin/tags/rapid_navigation.dryml +13 -12
- data/hobo_files/plugin/tags/rapid_pages.dryml +35 -31
- data/hobo_files/plugin/tags/rapid_plus.dryml +41 -0
- data/hobo_files/plugin/tags/rapid_support.dryml +18 -9
- data/hobo_files/plugin/tasks/dump_fixtures.rake +61 -0
- metadata +7 -11
- data/hobo_files/plugin/spec/fixtures/users.yml +0 -9
- data/hobo_files/plugin/spec/spec.opts +0 -6
- data/hobo_files/plugin/spec/spec_helper.rb +0 -28
- data/hobo_files/plugin/spec/unit/hobo/dryml/template_spec.rb +0 -650
@@ -5,7 +5,13 @@ module Hobo
|
|
5
5
|
include Hobo::Controller
|
6
6
|
|
7
7
|
class PermissionDeniedError < RuntimeError; end
|
8
|
-
|
8
|
+
class UserPermissionError < StandardError
|
9
|
+
attr :models
|
10
|
+
def initialize(models)
|
11
|
+
@models = models || []
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
9
15
|
VIEWLIB_DIR = "taglibs"
|
10
16
|
|
11
17
|
GENERIC_PAGE_TAGS = [:index, :show, :new, :edit, :show_collection, :new_in_collection, :login, :signup]
|
@@ -99,9 +105,10 @@ module Hobo
|
|
99
105
|
end
|
100
106
|
|
101
107
|
|
102
|
-
def autocomplete_for(attr, options={})
|
103
|
-
|
104
|
-
|
108
|
+
def autocomplete_for(attr, options={}, &b)
|
109
|
+
options = options.reverse_merge(:limit => 15)
|
110
|
+
options[:data_filters_block] = b
|
111
|
+
@completers ||= HashWithIndifferentAccess.new
|
105
112
|
@completers[attr.to_sym] = opts
|
106
113
|
end
|
107
114
|
|
@@ -110,12 +117,6 @@ module Hobo
|
|
110
117
|
(@completers && @completers[name]) ||
|
111
118
|
(superclass.respond_to?(:autocompleter) && superclass.autocompleter(name))
|
112
119
|
end
|
113
|
-
|
114
|
-
|
115
|
-
def def_data_filter(name, &b)
|
116
|
-
@data_filters ||= {}
|
117
|
-
@data_filters[name] = b
|
118
|
-
end
|
119
120
|
|
120
121
|
|
121
122
|
def web_method(web_name, method_name=nil)
|
@@ -139,12 +140,6 @@ module Hobo
|
|
139
140
|
end
|
140
141
|
|
141
142
|
|
142
|
-
def data_filter(name)
|
143
|
-
(@data_filters && @data_filters[name]) ||
|
144
|
-
(superclass.respond_to?(:data_filter) && superclass.data_filter(name))
|
145
|
-
end
|
146
|
-
|
147
|
-
|
148
143
|
def find_instance(id)
|
149
144
|
if model.id_name? and id !~ /^\d+$/
|
150
145
|
model.find_by_id_name(id)
|
@@ -154,6 +149,8 @@ module Hobo
|
|
154
149
|
end
|
155
150
|
|
156
151
|
end
|
152
|
+
|
153
|
+
include HttpParameters
|
157
154
|
|
158
155
|
# --- ACTIONS --- #
|
159
156
|
|
@@ -166,11 +163,13 @@ module Hobo
|
|
166
163
|
def destroy; hobo_destroy; end
|
167
164
|
|
168
165
|
def completions
|
169
|
-
|
170
|
-
opts = attr && self.class.autocompleter(attr.to_sym)
|
166
|
+
opts = self.class.autocompleter(params[:for])
|
171
167
|
if opts
|
168
|
+
# Eval any defined filters
|
169
|
+
instance_eval(&opts[:data_filters_block]) if opts[:data_filters_block]
|
170
|
+
conditions = data_filter_conditions
|
172
171
|
q = params[:query]
|
173
|
-
items =
|
172
|
+
items = model.find(:all) { all?(send("#{attr}_contains", q), conditions && block(conditions)) }
|
174
173
|
|
175
174
|
render :text => "<ul>\n" + items.map {|i| "<li>#{i.send(attr)}</li>\n"}.join + "</ul>"
|
176
175
|
else
|
@@ -179,10 +178,34 @@ module Hobo
|
|
179
178
|
end
|
180
179
|
|
181
180
|
|
182
|
-
|
181
|
+
# --- END OF ACTIONS --- #
|
183
182
|
|
184
183
|
protected
|
185
184
|
|
185
|
+
def data_filter(name, &b)
|
186
|
+
@data_filters ||= HashWithIndifferentAccess.new
|
187
|
+
@data_filters[name] = b
|
188
|
+
end
|
189
|
+
|
190
|
+
def search(*columns)
|
191
|
+
data_filter :search do |query|
|
192
|
+
columns_match = columns.map do |c|
|
193
|
+
if c.is_a?(Symbol)
|
194
|
+
send("#{c}_contains", query)
|
195
|
+
elsif c.is_a?(Hash)
|
196
|
+
c.map do |k, v|
|
197
|
+
related = send(k)
|
198
|
+
v = [v] unless v.is_a?(Array)
|
199
|
+
v.map { |related_col| related.send("#{related_col}_contains", query) }
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end.flatten
|
203
|
+
any?(*columns_match)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
attr_accessor :data_filters
|
208
|
+
|
186
209
|
def overridable_response(options, key)
|
187
210
|
if options.has_key?(key)
|
188
211
|
options[key]
|
@@ -210,21 +233,34 @@ module Hobo
|
|
210
233
|
|
211
234
|
|
212
235
|
def paginated_find(*args, &b)
|
213
|
-
options =
|
214
|
-
|
215
|
-
|
216
|
-
|
236
|
+
options = args.extract_options!
|
237
|
+
filter_conditions = data_filter_conditions
|
238
|
+
conditions_proc = if b && filter_conditions
|
239
|
+
proc { block(b) & block(filter_conditions) }
|
240
|
+
else
|
241
|
+
b || filter_conditions
|
242
|
+
end
|
243
|
+
|
244
|
+
@association = options.delete(:association) ||
|
217
245
|
if args.any?
|
218
246
|
owner, collection_name = args
|
219
247
|
@association = collection_name.to_s.split(".").inject(owner) { |m, name| m.send(name) }
|
220
248
|
end
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
249
|
+
@reflection = @association.proxy_reflection if @association._?.respond_to?(:proxy_reflection)
|
250
|
+
|
251
|
+
total_number = options.delete(:total_number) ||
|
252
|
+
begin
|
253
|
+
# If there is a conditions block, it may depend on the includes
|
254
|
+
count_options = conditions_proc ? { :include => options[:include] } : {}
|
255
|
+
if @association
|
256
|
+
@association.count(count_options, &conditions_proc)
|
257
|
+
else
|
258
|
+
model.count(count_options, &conditions_proc)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
puts total_number
|
226
263
|
|
227
|
-
total_number ||= count_with_data_filter
|
228
264
|
page_size = options.delete(:page_size) || 20
|
229
265
|
page = options.delete(:page) || params[:page]
|
230
266
|
@pages = ::ActionController::Pagination::Paginator.new(self, total_number, page_size, page)
|
@@ -234,11 +270,28 @@ module Hobo
|
|
234
270
|
:offset => @pages.current.offset,
|
235
271
|
}.merge(options)
|
236
272
|
|
273
|
+
unless options.has_key?(:order)
|
274
|
+
_, desc, field = *params[:sort]._?.match(/^(-)?([a-z_]+(?:\.[a-z_]+)?)$/)
|
275
|
+
if field
|
276
|
+
@sort_field = field
|
277
|
+
@sort_direction = desc ? "desc" : "asc"
|
278
|
+
|
279
|
+
table, column = if field =~ /^(.*)\.(.*)$/
|
280
|
+
[$1.camelize.constantize.table_name, $2]
|
281
|
+
else
|
282
|
+
sort_model = @association ? @association.member_class : model
|
283
|
+
[sort_model.table_name, field]
|
284
|
+
end
|
285
|
+
options[:order] = "#{table}.#{column} #{@sort_direction}"
|
286
|
+
elsif !@association
|
287
|
+
options[:order] = :default
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
237
291
|
if @association
|
238
|
-
@association.find(:all, options, &
|
292
|
+
@association.find(:all, options, &conditions_proc)
|
239
293
|
else
|
240
|
-
|
241
|
-
find_with_data_filter(options, &b)
|
294
|
+
model.find(:all, options, &conditions_proc)
|
242
295
|
end
|
243
296
|
end
|
244
297
|
|
@@ -291,6 +344,7 @@ module Hobo
|
|
291
344
|
|
292
345
|
if (@this = options[:this])
|
293
346
|
permission_denied(options) and return unless Hobo.can_create?(current_user, @this)
|
347
|
+
valid = @this.save
|
294
348
|
else
|
295
349
|
attributes = params[model.name.underscore]
|
296
350
|
type_attr = params['type']
|
@@ -301,16 +355,14 @@ module Hobo
|
|
301
355
|
model
|
302
356
|
end
|
303
357
|
@this = create_model.new
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
permission_denied(options) and return unless Hobo.can_create?(current_user, obj)
|
308
|
-
end
|
309
|
-
@check_create_permission = nil
|
358
|
+
status = secure_change_transaction { initialize_record(@this, attributes) }
|
359
|
+
permission_denied(options) and return if status == :not_allowed
|
360
|
+
valid = status == :valid
|
310
361
|
end
|
311
362
|
|
312
363
|
set_named_this!
|
313
|
-
|
364
|
+
|
365
|
+
if valid
|
314
366
|
if block_given?
|
315
367
|
yield
|
316
368
|
else
|
@@ -335,6 +387,7 @@ module Hobo
|
|
335
387
|
end
|
336
388
|
end
|
337
389
|
|
390
|
+
|
338
391
|
def hobo_edit(options={})
|
339
392
|
hobo_show(options)
|
340
393
|
end
|
@@ -346,22 +399,16 @@ module Hobo
|
|
346
399
|
@this = find_instance_or_not_found(options, :this)
|
347
400
|
return unless @this
|
348
401
|
|
349
|
-
original = @this.duplicate
|
350
|
-
|
351
402
|
changes = params[model.name.underscore]
|
352
|
-
|
353
|
-
if changes
|
354
|
-
# The 'duplicate' call above can set these, but they can
|
355
|
-
# conflict with the changes so we clear them
|
356
|
-
@this.send(:clear_aggregation_cache)
|
357
|
-
@this.send(:clear_association_cache)
|
358
|
-
|
359
|
-
update_with_params(@this, changes)
|
360
|
-
permission_denied(options) and return unless Hobo.can_update?(current_user, original, @this)
|
361
|
-
end
|
403
|
+
status = secure_change_transaction { update_record(@this, changes) }
|
362
404
|
|
363
405
|
set_named_this!
|
364
|
-
|
406
|
+
case status
|
407
|
+
when :not_allowed
|
408
|
+
permission_denied(options)
|
409
|
+
return
|
410
|
+
|
411
|
+
when :valid
|
365
412
|
# Ensure current_user isn't out of date
|
366
413
|
@current_user = @this if @this == current_user
|
367
414
|
|
@@ -392,7 +439,7 @@ module Hobo
|
|
392
439
|
end
|
393
440
|
end
|
394
441
|
|
395
|
-
|
442
|
+
when :invalid
|
396
443
|
# Validation errors
|
397
444
|
respond_to do |wants|
|
398
445
|
wants.html do
|
@@ -494,18 +541,19 @@ module Hobo
|
|
494
541
|
end
|
495
542
|
|
496
543
|
|
497
|
-
def permission_denied(options=
|
498
|
-
if options
|
544
|
+
def permission_denied(options={})
|
545
|
+
if options[:permission_denied_response]
|
499
546
|
# do nothing (callback handled by LazyHash)
|
500
547
|
elsif respond_to? :permission_denied_response
|
501
548
|
permission_denied_response
|
502
549
|
else
|
503
|
-
|
550
|
+
message = options[:message] || "Permission Denied"
|
551
|
+
render :text => message, :status => 403
|
504
552
|
end
|
505
553
|
end
|
506
554
|
|
507
|
-
def not_found(options=
|
508
|
-
if options
|
555
|
+
def not_found(options={})
|
556
|
+
if options[:not_found_response]
|
509
557
|
# do nothing (callback handled by LazyHash)
|
510
558
|
elsif respond_to? :not_found_response
|
511
559
|
not_found_response
|
@@ -531,15 +579,24 @@ module Hobo
|
|
531
579
|
|
532
580
|
template = Hobo::ModelController.find_model_template(page_model, page_kind)
|
533
581
|
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
else
|
538
|
-
if page_kind.in? GENERIC_PAGE_TAGS
|
539
|
-
render_tag("#{page_kind.to_s.camelize}Page", :with => @this)
|
582
|
+
begin
|
583
|
+
if template
|
584
|
+
render :template => template
|
540
585
|
true
|
541
586
|
else
|
542
|
-
false
|
587
|
+
# This returns false if no such tag exists
|
588
|
+
render_tag("#{page_kind.to_s.camelize}Page", :with => @this)
|
589
|
+
end
|
590
|
+
rescue ActionView::TemplateError => wrapper
|
591
|
+
e = wrapper.original_exception if wrapper.respond_to? :original_exception
|
592
|
+
if e.is_a? Hobo::ModelController::UserPermissionError
|
593
|
+
if current_user.guest?
|
594
|
+
redirect_to login_url(e.models.first || UserController.user_models.first)
|
595
|
+
else
|
596
|
+
permission_denied(:message => e.message)
|
597
|
+
end
|
598
|
+
else
|
599
|
+
raise
|
543
600
|
end
|
544
601
|
end
|
545
602
|
end
|
@@ -548,112 +605,55 @@ module Hobo
|
|
548
605
|
def model
|
549
606
|
self.class.model
|
550
607
|
end
|
551
|
-
|
608
|
+
|
552
609
|
|
553
610
|
def find_template
|
554
611
|
Hobo::ModelController.find_model_template(model, params[:action])
|
555
612
|
end
|
556
613
|
|
557
|
-
|
558
|
-
def with_data_filter(operation, *args, &block)
|
559
|
-
filter_param = params.keys.find &it.starts_with?("where_")
|
560
|
-
proc = filter_param && self.class.data_filter(filter_param[6..-1].to_sym)
|
561
|
-
if proc
|
562
|
-
filter_args = params[filter_param]
|
563
|
-
filter_args = [filter_args] unless filter_args.is_a? Array
|
564
|
-
model.send(operation, *args) do
|
565
|
-
if block
|
566
|
-
instance_eval(&block) & instance_exec(*filter_args, &proc)
|
567
|
-
else
|
568
|
-
instance_exec(*filter_args, &proc)
|
569
|
-
end
|
570
|
-
end
|
571
|
-
else
|
572
|
-
if block
|
573
|
-
model.send(operation, *args) { instance_eval(&block) }
|
574
|
-
else
|
575
|
-
model.send(operation, *args)
|
576
|
-
end
|
577
|
-
end
|
578
|
-
end
|
579
614
|
|
580
|
-
def
|
581
|
-
|
615
|
+
def data_filter_conditions
|
616
|
+
active_filters = data_filters && (params.keys & data_filters.keys)
|
617
|
+
filters = data_filters
|
618
|
+
params = self.params
|
619
|
+
proc do
|
620
|
+
all?(*active_filters.map {|f| instance_exec(params[f], &filters[f])})
|
621
|
+
end unless active_filters.blank?
|
582
622
|
end
|
583
623
|
|
584
|
-
|
585
|
-
def count_with_data_filter(opts={}, &b)
|
586
|
-
with_data_filter(:count, opts, &b)
|
587
|
-
end
|
588
|
-
|
589
|
-
|
590
|
-
def initialize_from_params(obj, params)
|
591
|
-
update_with_params(obj, params)
|
592
|
-
obj.set_creator(current_user)
|
593
|
-
(@check_create_permission ||= []) << obj
|
594
|
-
obj
|
595
|
-
end
|
596
|
-
|
597
624
|
|
598
|
-
def
|
625
|
+
def XXupdate_with_params(object, params)
|
599
626
|
return unless params
|
600
627
|
|
601
628
|
params.each_pair do |field,value|
|
602
629
|
field = field.to_sym
|
603
630
|
refl = object.class.reflections[field]
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
631
|
+
is_has_many = refl && Hobo.simple_has_many_association?(refl)
|
632
|
+
|
633
|
+
if is_has_many
|
634
|
+
items = if value.is_a? Array
|
635
|
+
value.map {|x| associated_record(object, refl, x) }
|
636
|
+
else
|
637
|
+
value.keys.every(:to_i).sort.map{|i| associated_record(object, refl, value[i.to_s]) }
|
638
|
+
end
|
639
|
+
object.send(field).target[0..-1] = items
|
640
|
+
else
|
641
|
+
ar_value = if refl
|
642
|
+
if refl.macro == :belongs_to
|
643
|
+
associated_record(object, refl, value)
|
613
644
|
else
|
614
|
-
|
645
|
+
raise HoboError, "association #{object.class}.#{refl.name} is not settable via parameters"
|
615
646
|
end
|
616
647
|
else
|
617
|
-
|
648
|
+
param_to_value(object.class.field_type(field), value)
|
618
649
|
end
|
619
|
-
|
620
|
-
param_to_value(object.class.field_type(field), value)
|
621
|
-
end
|
622
|
-
object.send("#{field}=".to_sym, ar_value)
|
623
|
-
end
|
624
|
-
end
|
625
|
-
|
626
|
-
|
627
|
-
def parse_datetime(s)
|
628
|
-
defined?(Chronic) ? Chronic.parse(s) : Time.parse(s)
|
629
|
-
end
|
630
|
-
|
631
|
-
|
632
|
-
def param_to_value(field_type, value)
|
633
|
-
if field_type.nil?
|
634
|
-
value
|
635
|
-
elsif field_type <= Date
|
636
|
-
if value.is_a? Hash
|
637
|
-
Date.new(*(%w{year month day}.map{|s| value[s].to_i}))
|
638
|
-
elsif value.is_a? String
|
639
|
-
dt = parse_datetime(value)
|
640
|
-
dt && dt.to_date
|
641
|
-
end
|
642
|
-
elsif field_type <= Time
|
643
|
-
if value.is_a? Hash
|
644
|
-
Time.local(*(%w{year month day hour minute}.map{|s| value[s].to_i}))
|
645
|
-
elsif value.is_a? String
|
646
|
-
parse_datetime(value)
|
650
|
+
object.send("#{field}=".to_sym, ar_value)
|
647
651
|
end
|
648
|
-
elsif field_type <= TrueClass
|
649
|
-
(value.is_a?(String) && value.strip.downcase.in?(['0', 'false']) || value.blank?) ? false : true
|
650
|
-
else
|
651
|
-
# primitive field
|
652
|
-
value
|
653
652
|
end
|
654
653
|
end
|
655
654
|
|
656
|
-
|
655
|
+
|
656
|
+
def XXassociated_record(owner, refl, value)
|
657
657
|
if value.is_a? String
|
658
658
|
if value.starts_with?('@')
|
659
659
|
Hobo.object_from_dom_id(value[1..-1])
|
@@ -674,7 +674,7 @@ module Hobo
|
|
674
674
|
end
|
675
675
|
|
676
676
|
|
677
|
-
def
|
677
|
+
def XXobject_from_param(param)
|
678
678
|
Hobo.object_from_dom_id(param)
|
679
679
|
end
|
680
680
|
|