hobo 0.7.3 → 0.7.4

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.
Files changed (46) hide show
  1. data/bin/hobo +1 -1
  2. data/hobo_files/plugin/CHANGES.txt +302 -0
  3. data/hobo_files/plugin/generators/hobo_front_controller/templates/index.dryml +2 -9
  4. data/hobo_files/plugin/generators/hobo_model/templates/model.rb +1 -1
  5. data/hobo_files/plugin/generators/hobo_model_resource/hobo_model_resource_generator.rb +0 -2
  6. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo-rapid.js +76 -46
  7. data/hobo_files/plugin/generators/hobo_rapid/templates/lowpro.js +25 -18
  8. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/clean/public/stylesheets/application.css +29 -11
  9. data/hobo_files/plugin/generators/hobo_user_model/templates/model.rb +2 -2
  10. data/hobo_files/plugin/init.rb +0 -1
  11. data/hobo_files/plugin/lib/active_record/has_many_association.rb +3 -0
  12. data/hobo_files/plugin/lib/hobo.rb +12 -8
  13. data/hobo_files/plugin/lib/hobo/bundle.rb +1 -1
  14. data/hobo_files/plugin/lib/hobo/dryml/dryml_builder.rb +1 -1
  15. data/hobo_files/plugin/lib/hobo/dryml/parser/attribute.rb +41 -0
  16. data/hobo_files/plugin/lib/hobo/dryml/parser/base_parser.rb +253 -0
  17. data/hobo_files/plugin/lib/hobo/dryml/parser/document.rb +26 -0
  18. data/hobo_files/plugin/lib/hobo/dryml/parser/element.rb +27 -0
  19. data/hobo_files/plugin/lib/hobo/dryml/parser/elements.rb +45 -0
  20. data/hobo_files/plugin/lib/hobo/dryml/parser/source.rb +58 -0
  21. data/hobo_files/plugin/lib/hobo/dryml/parser/text.rb +13 -0
  22. data/hobo_files/plugin/lib/hobo/dryml/parser/tree_parser.rb +67 -0
  23. data/hobo_files/plugin/lib/hobo/dryml/scoped_variables.rb +10 -5
  24. data/hobo_files/plugin/lib/hobo/dryml/template.rb +48 -27
  25. data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +28 -13
  26. data/hobo_files/plugin/lib/hobo/hobo_helper.rb +3 -1
  27. data/hobo_files/plugin/lib/hobo/model.rb +70 -10
  28. data/hobo_files/plugin/lib/hobo/model_controller.rb +49 -34
  29. data/hobo_files/plugin/lib/hobo/model_router.rb +10 -2
  30. data/hobo_files/plugin/lib/hobo/rapid_helper.rb +1 -0
  31. data/hobo_files/plugin/lib/hobo/scopes.rb +15 -0
  32. data/hobo_files/plugin/lib/hobo/scopes/apply_scopes.rb +23 -0
  33. data/hobo_files/plugin/lib/hobo/scopes/association_proxy_extensions.rb +4 -2
  34. data/hobo_files/plugin/lib/hobo/scopes/automatic_scopes.rb +34 -7
  35. data/hobo_files/plugin/lib/hobo/scopes/defined_scope_proxy_extender.rb +3 -1
  36. data/hobo_files/plugin/lib/hobo/scopes/scoped_proxy.rb +1 -5
  37. data/hobo_files/plugin/taglibs/rapid.dryml +33 -24
  38. data/hobo_files/plugin/taglibs/rapid_editing.dryml +6 -5
  39. data/hobo_files/plugin/taglibs/rapid_forms.dryml +37 -31
  40. data/hobo_files/plugin/taglibs/rapid_generics.dryml +68 -27
  41. data/hobo_files/plugin/taglibs/rapid_navigation.dryml +5 -8
  42. data/hobo_files/plugin/taglibs/rapid_pages.dryml +71 -47
  43. data/hobo_files/plugin/taglibs/rapid_plus.dryml +4 -5
  44. data/hobo_files/plugin/taglibs/rapid_support.dryml +11 -4
  45. metadata +23 -6
  46. data/hobo_files/plugin/lib/rexml.rb +0 -443
@@ -130,6 +130,8 @@ module Hobo
130
130
  empty = true
131
131
  if this.respond_to?(:each_index)
132
132
  this.each_index {|i| empty = false; new_field_context(i) { res << yield } }
133
+ elsif this.is_a?(Hash)
134
+ this.map {|key, value| empty = false; self.this_key = key; new_object_context(value) { res << yield } }
133
135
  else
134
136
  this.map {|e| empty = false; new_object_context(e) { res << yield } }
135
137
  end
@@ -346,7 +348,7 @@ module Hobo
346
348
 
347
349
  Hobo::ModelRouter.linkable?(klass, action, options.reverse_merge(:subsite => subsite))
348
350
  end
349
-
351
+
350
352
 
351
353
  # Convenience helper for the default app
352
354
 
@@ -26,11 +26,13 @@ module Hobo
26
26
 
27
27
  class << base
28
28
  alias_method_chain :has_many, :defined_scopes
29
+ alias_method_chain :has_many, :join_record_management
29
30
  alias_method_chain :belongs_to, :creator_metadata
30
31
 
31
32
  alias_method_chain :has_one, :new_method
32
33
 
33
34
  def inherited(klass)
35
+ super
34
36
  fields do
35
37
  Hobo.register_model(klass)
36
38
  field(klass.inheritance_column, :string)
@@ -92,7 +94,7 @@ module Hobo
92
94
 
93
95
  def user_find(user, *args)
94
96
  record = find(*args)
95
- raise PermissionDeniedError unless Hobo.can_view?(user, self)
97
+ raise PermissionDeniedError unless Hobo.can_view?(user, record)
96
98
  record
97
99
  end
98
100
 
@@ -121,6 +123,11 @@ module Hobo
121
123
  end
122
124
 
123
125
 
126
+ def user_update(user, id, attributes={})
127
+ find(id).user_save_changes(user, attributes)
128
+ end
129
+
130
+
124
131
  def name_attribute
125
132
  @name_attribute ||= begin
126
133
  cols = columns.*.name
@@ -232,7 +239,7 @@ module Hobo
232
239
  # FIXME: This should really be a method on AssociationReflection
233
240
  def reverse_reflection(association_name)
234
241
  refl = reflections[association_name]
235
- return nil if refl.options[:conditions]
242
+ return nil if refl.options[:conditions] || refl.options[:polymorphic]
236
243
 
237
244
  reverse_macro = if refl.macro == :has_many
238
245
  :belongs_to
@@ -277,9 +284,36 @@ module Hobo
277
284
  "#{name.underscore.pluralize}"
278
285
  end
279
286
 
287
+
280
288
  def typed_id
281
289
  HoboFields.to_name(self) || name.underscore.gsub("/", "__")
282
290
  end
291
+
292
+
293
+ def manage_join_records(association)
294
+ through = reflections[association].through_reflection
295
+ source = reflections[association].source_reflection
296
+
297
+ method = "manage_join_records_for_#{association}"
298
+ after_save method
299
+ class_eval %{
300
+ def #{method}
301
+ current = #{through.name}.*.#{source.name}
302
+ to_delete = current - #{association}
303
+ to_add = #{association} - current
304
+ #{through.klass.name}.delete_all(["#{through.primary_key_name} = ? and #{source.primary_key_name} in (?)",
305
+ self.id, to_delete.*.id]) if to_delete.any?
306
+ to_add.each { |record| #{association} << record }
307
+ end
308
+ }
309
+ end
310
+
311
+ def has_many_with_join_record_management(name, options={}, &b)
312
+ manage = options.delete(:managed)
313
+ returning (has_many_without_join_record_management(name, options, &b)) do
314
+ manage_join_records(name) if manage
315
+ end
316
+ end
283
317
 
284
318
  end # --- of ClassMethods --- #
285
319
 
@@ -441,16 +475,12 @@ module Hobo
441
475
 
442
476
 
443
477
  def convert_type_for_mass_assignment(field_type, value)
444
- if field_type.is_a?(ActiveRecord::Reflection::AssociationReflection) &&
445
- field_type.macro.in?([:belongs_to, :has_one])
446
- if value.is_a?(String) && value.starts_with?('@')
447
- # TODO: This @foo_1 feature is rarely (never?) used - get rid of it
448
- Hobo.object_from_dom_id(value[1..-1])
449
- else
450
- value
451
- end
478
+ if field_type.is_a?(ActiveRecord::Reflection::AssociationReflection)
479
+ convert_associated_records_for_mass_assignment(field_type, value)
480
+
452
481
  elsif !field_type.is_a?(Class)
453
482
  value
483
+
454
484
  elsif field_type <= Date
455
485
  if value.is_a? Hash
456
486
  Date.new(*(%w{year month day}.map{|s| value[s].to_i}))
@@ -460,6 +490,7 @@ module Hobo
460
490
  else
461
491
  value
462
492
  end
493
+
463
494
  elsif field_type <= Time
464
495
  if value.is_a? Hash
465
496
  Time.local(*(%w{year month day hour minute}.map{|s| value[s].to_i}))
@@ -468,13 +499,42 @@ module Hobo
468
499
  else
469
500
  value
470
501
  end
502
+
471
503
  elsif field_type <= Hobo::Boolean
472
504
  (value.is_a?(String) && value.strip.downcase.in?(['0', 'false']) || value.blank?) ? false : true
505
+
473
506
  else
474
507
  # primitive field
475
508
  value
476
509
  end
477
510
  end
511
+
512
+ def convert_associated_records_for_mass_assignment(reflection, value)
513
+ if reflection.macro.in?([:belongs_to, :has_one])
514
+ if value.is_a?(String) && value.starts_with?('@')
515
+ # TODO: This @foo_1 feature is rarely (never?) used - get rid of it
516
+ Hobo.object_from_dom_id(value[1..-1])
517
+ else
518
+ value
519
+ end
520
+ elsif reflection.macro == :has_many
521
+ if reflection.klass.try.name_attribute
522
+ value.map do |x|
523
+ if x.is_a?(String)
524
+ reflection.klass[x] unless x.blank?
525
+ else
526
+ x
527
+ end
528
+ end.compact
529
+ else
530
+ value
531
+ end
532
+
533
+ else
534
+ # unknown kind of accociation - no conversion
535
+ value
536
+ end
537
+ end
478
538
 
479
539
  end
480
540
 
@@ -138,9 +138,11 @@ module Hobo
138
138
  def destroy; hobo_destroy end if include_action?(:destroy)
139
139
 
140
140
  def completions; hobo_completions end if include_action?(:completions)
141
+
142
+ def reorder; hobo_reorder end if include_action?(:reorder)
141
143
  end
142
144
 
143
- collections.each { |c| def_collection_actions(c.to_sym) }
145
+ collections.each { |c| def_collection_actions(c.to_sym) }
144
146
  end
145
147
 
146
148
 
@@ -205,7 +207,10 @@ module Hobo
205
207
 
206
208
 
207
209
  def available_auto_actions
208
- READ_ONLY_ACTIONS + WRITE_ONLY_ACTIONS + FORM_ACTIONS + available_auto_collection_actions
210
+ (available_auto_read_actions +
211
+ available_auto_write_actions +
212
+ FORM_ACTIONS +
213
+ available_auto_collection_actions).uniq
209
214
  end
210
215
 
211
216
 
@@ -215,7 +220,11 @@ module Hobo
215
220
 
216
221
 
217
222
  def available_auto_write_actions
218
- WRITE_ONLY_ACTIONS
223
+ if "position_column".in?(model.instance_methods)
224
+ WRITE_ONLY_ACTIONS + [:reorder]
225
+ else
226
+ WRITE_ONLY_ACTIONS
227
+ end
219
228
  end
220
229
 
221
230
 
@@ -229,38 +238,17 @@ module Hobo
229
238
  protected
230
239
 
231
240
 
232
- def filter_by(*args)
233
- filters = args.extract_options!
234
- finder = args.first || self.model
235
-
236
- filters.each_pair do |scope, arg|
237
- dont_filter = arg.is_a?(Array) ? arg.compact.empty? : arg.nil?
238
- finder = finder.send(scope, arg) unless dont_filter
239
- end
240
- finder
241
- end
242
-
243
-
244
- def sort_fields(*args)
245
- finder = args.first.is_a?(Class) ? args.shift : model
246
-
241
+ def parse_sort_param(*sort_fields)
247
242
  _, desc, field = *params[:sort]._?.match(/^(-)?([a-z_]+(?:\.[a-z_]+)?)$/)
248
243
 
249
244
  if field
250
- fields = args.*.to_s
251
- if field.in?(fields)
245
+ if field.in?(sort_fields.*.to_s)
252
246
  @sort_field = field
253
247
  @sort_direction = desc ? "desc" : "asc"
254
248
 
255
- table, column = if field =~ /^(.*)\.(.*)$/
256
- [$1.camelize.constantize.table_name, $2]
257
- else
258
- [finder.table_name, field]
259
- end
260
- finder = finder.order("#{table}.#{column}", @sort_direction)
249
+ [@sort_field, @sort_direction]
261
250
  end
262
251
  end
263
- finder
264
252
  end
265
253
 
266
254
 
@@ -301,7 +289,7 @@ module Hobo
301
289
  object_url(@this) ||
302
290
 
303
291
  # Then the show page of the 'owning' object if there is one
304
- (@this.class.default_dependent_on && object_url(@this.class.default_dependent_on)) ||
292
+ (@this.class.default_dependent_on && object_url(@this.send(@this.class.default_dependent_on))) ||
305
293
 
306
294
  # Last try - the index page for this model
307
295
  object_url(@this.class) ||
@@ -322,8 +310,18 @@ module Hobo
322
310
  end
323
311
  end
324
312
 
313
+
314
+ def request_requires_pagination?
315
+ # Internet explorer has a penchant for saying it would mostly
316
+ # like an image, if you clicked on an image link
317
+ request.format.in?(PAGINATE_FORMATS) || request.format.to_s =~ %r(image/)
318
+ end
319
+
320
+
325
321
  def find_or_paginate(finder, options)
326
- do_pagination = options.delete(:paginate) != false && request.format.in?(PAGINATE_FORMATS)
322
+ options = options.reverse_merge(:paginate => request_requires_pagination?)
323
+ do_pagination = options.delete(:paginate)
324
+
327
325
  if do_pagination && !finder.respond_to?(:paginate)
328
326
  do_pagination = false
329
327
  logger.warn "Hobo::ModelController: Pagination is not available. To enable, please install will_paginate or a duck-type compatible paginator"
@@ -419,7 +417,7 @@ module Hobo
419
417
 
420
418
 
421
419
  def update_response(in_place_edit_field=nil, &b)
422
- flash[:notice] = "Changes to the #{@this.class.name.humanize.downcase} were saved" if !request.xhr? && valid?
420
+ flash[:notice] = "Changes to the #{@this.class.name.titleize.downcase} were saved" if !request.xhr? && valid?
423
421
 
424
422
  response_block(&b) or
425
423
  if valid?
@@ -464,7 +462,7 @@ module Hobo
464
462
  response_block(&b) or
465
463
  respond_to do |wants|
466
464
  wants.html { redirect_to(:action => "index") }
467
- wants.js { hobo_ajax_response || render(:text => "") }
465
+ wants.js { hobo_ajax_response || render(:nothing => true) }
468
466
  end
469
467
  end
470
468
 
@@ -498,6 +496,14 @@ module Hobo
498
496
  end
499
497
 
500
498
 
499
+ def hobo_reorder
500
+ params["#{model.name.underscore}_ordering"].each_with_index do |id, position|
501
+ model.user_update(current_user, id, :position => position+1)
502
+ end
503
+ hobo_ajax_response || render(:nothing => true)
504
+ end
505
+
506
+
501
507
 
502
508
  # --- Response helpers --- #
503
509
 
@@ -505,10 +511,19 @@ module Hobo
505
511
  def permission_denied(error)
506
512
  if respond_to? :permission_denied_response
507
513
  permission_denied_response
508
- elsif render_tag("permission-denied-page", { }, :status => 403)
509
- # job done
510
514
  else
511
- render :text => "Permission Denied", :status => 403
515
+ respond_to do |wants|
516
+ wants.html do
517
+ if render_tag("permission-denied-page", { }, :status => 403)
518
+ # job done
519
+ else
520
+ render :text => "Permission Denied", :status => 403
521
+ end
522
+ end
523
+ wants.js do
524
+ render :text => "Permission Denied", :status => 403
525
+ end
526
+ end
512
527
  end
513
528
  end
514
529
 
@@ -10,8 +10,10 @@ class ActionController::Routing::RouteSet
10
10
 
11
11
  # temporay hack -- reload assemble.rb whenever routes need reloading
12
12
  def reload_with_hobo_assemble
13
- load "#{RAILS_ROOT}/app/assemble.rb" if File.exists? "#{RAILS_ROOT}/app/assemble.rb"
14
- reload_without_hobo_assemble
13
+ if defined? ::ApplicationController
14
+ load "#{RAILS_ROOT}/app/assemble.rb" if File.exists? "#{RAILS_ROOT}/app/assemble.rb"
15
+ reload_without_hobo_assemble
16
+ end
15
17
  end
16
18
  alias_method_chain :reload, :hobo_assemble
17
19
 
@@ -131,6 +133,7 @@ module Hobo
131
133
  collection_routes
132
134
  web_method_routes
133
135
  show_action_routes
136
+ reorder_route
134
137
  user_routes if controller < Hobo::UserController
135
138
  end
136
139
  end
@@ -192,6 +195,11 @@ module Hobo
192
195
  end
193
196
  end
194
197
 
198
+
199
+ def reorder_route
200
+ linkable_route("reorder_#{plural}", "#{plural}/reorder", 'reorder', :conditions => { :method => :post })
201
+ end
202
+
195
203
 
196
204
  def user_routes
197
205
  prefix = plural == "users" ? "" : "#{singular}_"
@@ -143,4 +143,5 @@ module Hobo::RapidHelper
143
143
 
144
144
  names - through_collection_names
145
145
  end
146
+
146
147
  end
@@ -13,6 +13,8 @@ module Hobo
13
13
  module ClassMethods
14
14
 
15
15
  include AutomaticScopes
16
+
17
+ include ApplyScopes
16
18
 
17
19
  def defined_scopes
18
20
  @defined_scopes
@@ -29,6 +31,19 @@ module Hobo
29
31
  end
30
32
 
31
33
 
34
+ def apply_scopes(scopes)
35
+ result = self
36
+ scopes.each_pair do |scope, arg|
37
+ if arg.is_a?(Array)
38
+ result = result.send(scope, *arg) unless arg.first.blank?
39
+ else
40
+ result = result.send(scope, arg) unless arg.blank?
41
+ end
42
+ end
43
+ result
44
+ end
45
+
46
+
32
47
  def alias_scope(new_name, old_name)
33
48
  metaclass.send(:alias_method, new_name, old_name)
34
49
  defined_scopes[new_name] = defined_scopes[old_name]
@@ -0,0 +1,23 @@
1
+ module Hobo
2
+
3
+ module Scopes
4
+
5
+ module ApplyScopes
6
+
7
+ def apply_scopes(scopes)
8
+ result = self
9
+ scopes.each_pair do |scope, arg|
10
+ if arg.is_a?(Array)
11
+ result = result.send(scope, *arg) unless arg.first.blank?
12
+ else
13
+ result = result.send(scope, arg) unless arg.blank?
14
+ end
15
+ end
16
+ result
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -18,9 +18,11 @@ module Hobo
18
18
  target_class.send(scope_name).scope(:find)[:conditions]
19
19
  end
20
20
  if scope_conditions && conditions_without_hobo_scopes
21
- "#{conditions_without_hobo_scopes} AND #{scope_conditions}"
21
+ "(#{sanitize_sql conditions_without_hobo_scopes}) AND (#{sanitize_sql scope_conditions})"
22
+ elsif scope_conditions
23
+ sanitize_sql scope_conditions
22
24
  else
23
- scope_conditions || conditions_without_hobo_scopes
25
+ conditions_without_hobo_scopes
24
26
  end
25
27
  end
26
28
 
@@ -152,17 +152,17 @@ module Hobo
152
152
  end
153
153
 
154
154
  # published
155
- elsif (col = column($1)) && (col.type == :boolean)
155
+ elsif (col = column(name)) && (col.type == :boolean)
156
156
 
157
157
  def_scope do
158
- { :conditions => "#{column_sql(col)} = 1" }
158
+ { :conditions => "#{column_sql(col)}" }
159
159
  end
160
160
 
161
161
  # not_published
162
- elsif (col = column($1)) && (col.type == :boolean)
162
+ elsif name =~ /^not_(.*)$/ && (col = column($1)) && (col.type == :boolean)
163
163
 
164
164
  def_scope do
165
- { :conditions => "#{column_sql(col)} <> 1" }
165
+ { :conditions => "NOT #{column_sql(col)}" }
166
166
  end
167
167
 
168
168
  # published_before(time)
@@ -200,11 +200,38 @@ module Hobo
200
200
  def_scope do |count|
201
201
  { :limit => count }
202
202
  end
203
-
203
+
204
204
  when "order_by"
205
+ klass = @klass
205
206
  def_scope do |*args|
206
207
  field, asc = args
207
- { :order => "#{field} #{asc._?.upcase}" }
208
+ type = klass.attr_type(field)
209
+ if type.respond_to?(:table_name) && (name = type.name_attribute)
210
+ include = field
211
+ colspec = "#{type.table_name}.#{name}"
212
+ else
213
+ colspec = "#{klass.table_name}.#{field}"
214
+ end
215
+ { :order => "#{colspec} #{asc._?.upcase}", :include => include }
216
+ end
217
+
218
+
219
+ when "include"
220
+ def_scope do |inclusions|
221
+ { :include => inclusions }
222
+ end
223
+
224
+ when "search"
225
+ def_scope do |query, *fields|
226
+ words = query.split
227
+ args = []
228
+ word_queries = words.map do |word|
229
+ field_query = '(' + fields.map { |field| "(#{@klass.table_name}.#{field} like ?)" }.join(" OR ") + ')'
230
+ args += ["%#{word}%"] * fields.length
231
+ field_query
232
+ end
233
+
234
+ { :conditions => [word_queries.join(" OR ")] + args }
208
235
  end
209
236
 
210
237
  else
@@ -236,7 +263,7 @@ module Hobo
236
263
 
237
264
  "EXISTS (SELECT * FROM #{related.table_name} " +
238
265
  "WHERE #{related.table_name}.#{foreign_key} = #{owner_primary_key} AND " +
239
- "#{related.table_name}.#{related.primary_key} = ?"
266
+ "#{related.table_name}.#{related.primary_key} = ?)"
240
267
  end
241
268
  end
242
269