hobo 0.6.1 → 0.6.2
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 +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
|
|