hobo 0.9.0 → 0.9.100

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 (47) hide show
  1. data/CHANGES.txt +132 -0
  2. data/Rakefile +18 -22
  3. data/bin/hobo +14 -13
  4. data/doctest/scopes.rdoctest +1 -0
  5. data/dryml_generators/rapid/forms.dryml.erb +1 -1
  6. data/dryml_generators/rapid/pages.dryml.erb +24 -24
  7. data/lib/hobo.rb +3 -3
  8. data/lib/hobo/accessible_associations.rb +10 -3
  9. data/lib/hobo/controller.rb +2 -1
  10. data/lib/hobo/dryml.rb +7 -2
  11. data/lib/hobo/dryml/dryml_builder.rb +1 -4
  12. data/lib/hobo/dryml/dryml_generator.rb +5 -3
  13. data/lib/hobo/dryml/taglib.rb +3 -8
  14. data/lib/hobo/dryml/template.rb +3 -10
  15. data/lib/hobo/dryml/template_handler.rb +3 -2
  16. data/lib/hobo/hobo_helper.rb +5 -83
  17. data/lib/hobo/lifecycles.rb +9 -5
  18. data/lib/hobo/lifecycles/actions.rb +5 -5
  19. data/lib/hobo/lifecycles/lifecycle.rb +6 -2
  20. data/lib/hobo/model.rb +14 -36
  21. data/lib/hobo/model_controller.rb +28 -15
  22. data/lib/hobo/model_router.rb +1 -1
  23. data/lib/hobo/permissions.rb +55 -5
  24. data/lib/hobo/permissions/associations.rb +13 -5
  25. data/lib/hobo/rapid_helper.rb +4 -10
  26. data/lib/hobo/scopes/automatic_scopes.rb +9 -1
  27. data/lib/hobo/translations.rb +88 -0
  28. data/lib/hobo/user.rb +1 -2
  29. data/lib/hobo/user_controller.rb +31 -9
  30. data/lib/hobo/view_hints.rb +38 -6
  31. data/rails_generators/hobo_model/templates/hints.rb +4 -1
  32. data/rails_generators/hobo_model_controller/hobo_model_controller_generator.rb +1 -1
  33. data/rails_generators/hobo_rapid/hobo_rapid_generator.rb +2 -1
  34. data/rails_generators/hobo_rapid/templates/hobo-rapid.js +9 -0
  35. data/rails_generators/hobo_rapid/templates/ie7-recalc.js +166 -2
  36. data/rails_generators/hobo_user_model/templates/model.rb +1 -3
  37. data/taglibs/rapid.dryml +2 -1
  38. data/taglibs/rapid_core.dryml +7 -5
  39. data/taglibs/rapid_editing.dryml +14 -10
  40. data/taglibs/rapid_forms.dryml +7 -5
  41. data/taglibs/rapid_generics.dryml +1 -1
  42. data/taglibs/rapid_lifecycles.dryml +3 -2
  43. data/taglibs/rapid_pages.dryml +1 -1
  44. data/taglibs/rapid_support.dryml +1 -1
  45. data/tasks/hobo_tasks.rake +10 -0
  46. metadata +7 -7
  47. data/lib/hobo/bundle.rb +0 -330
@@ -24,6 +24,7 @@ module Hobo
24
24
  include Hobo::Lifecycles::ModelExtensions
25
25
  include Hobo::FindFor
26
26
  include Hobo::AccessibleAssociations
27
+ include Hobo::Translations
27
28
  end
28
29
 
29
30
  class << base
@@ -35,14 +36,14 @@ module Hobo
35
36
 
36
37
  def inherited(klass)
37
38
  super
38
- fields do
39
+ fields(false) do
39
40
  Hobo.register_model(klass)
40
41
  field(klass.inheritance_column, :string)
41
42
  end
42
43
  end
43
44
  end
44
45
 
45
- base.fields # force hobofields to load
46
+ base.fields(false) # force hobofields to load
46
47
 
47
48
  included_in_class_callbacks(base)
48
49
  end
@@ -121,7 +122,7 @@ module Hobo
121
122
  ActiveRecord::Base.class_eval do
122
123
  def self.hobo_model
123
124
  include Hobo::Model
124
- fields # force hobofields to load
125
+ fields(false) # force hobofields to load
125
126
  end
126
127
  def self.hobo_user_model
127
128
  include Hobo::Model
@@ -141,12 +142,12 @@ module Hobo
141
142
  # TODO: should this be an inheriting_cattr_accessor as well? Probably.
142
143
  attr_accessor :creator_attribute
143
144
  inheriting_cattr_accessor :name_attribute => Proc.new { |c|
144
- names = c.columns.*.name + c.public_instance_methods
145
+ names = c.columns.*.name + c.public_instance_methods.*.to_s
145
146
  NAME_FIELD_GUESS.detect {|f| f.in? names }
146
147
  }
147
148
 
148
149
  inheriting_cattr_accessor :primary_content_attribute => Proc.new { |c|
149
- names = c.columns.*.name + c.public_instance_methods
150
+ names = c.columns.*.name + c.public_instance_methods.*.to_s
150
151
  PRIMARY_CONTENT_GUESS.detect {|f| f.in? names }
151
152
  }
152
153
 
@@ -168,25 +169,6 @@ module Hobo
168
169
  end
169
170
 
170
171
 
171
- def dependent_collections
172
- reflections.values.select do |refl|
173
- refl.macro == :has_many && refl.options[:dependent]
174
- end.*.name
175
- end
176
-
177
-
178
- def dependent_on
179
- reflections.values.select do |refl|
180
- refl.macro == :belongs_to && (rev = reverse_reflection(refl.name) and rev.options[:dependent])
181
- end.*.name
182
- end
183
-
184
-
185
- def default_dependent_on
186
- dependent_on.first
187
- end
188
-
189
-
190
172
  private
191
173
 
192
174
 
@@ -273,14 +255,18 @@ module Hobo
273
255
 
274
256
  def find(*args, &b)
275
257
  options = args.extract_options!
276
- if options[:order] == :default
258
+ if options[:order] == :default || (options[:order].blank? && !scoped?(:find, :order))
259
+ # TODO: decide if this is correct. AR is no help, as passing :order to a scoped proxy
260
+ # MERGES the order, but nesting two scopes with :order completely ignores the
261
+ # first scope's order.
262
+ # Are we more like default_scope, or more like passing :order => model.default_order?
277
263
  options = if default_order.blank?
278
264
  options.except :order
279
265
  else
280
266
  options.merge(:order => if default_order[/(\.|\(|,| )/]
281
267
  default_order
282
268
  else
283
- "#{table_name}.#{default_order}"
269
+ "#{quoted_table_name}.#{default_order}"
284
270
  end)
285
271
  end
286
272
  end
@@ -297,11 +283,6 @@ module Hobo
297
283
  end
298
284
 
299
285
 
300
- def all(options={})
301
- find(:all, options.reverse_merge(:order => :default))
302
- end
303
-
304
-
305
286
  def creator_type
306
287
  attr_type(creator_attribute)
307
288
  end
@@ -329,6 +310,7 @@ module Hobo
329
310
  refl.klass.reflections.values.find do |r|
330
311
  r.macro == :has_many &&
331
312
  !r.options[:conditions] &&
313
+ !r.options[:scope] &&
332
314
  r.through_reflection == other_to_join &&
333
315
  r.source_reflection == join_to_self
334
316
  end
@@ -346,6 +328,7 @@ module Hobo
346
328
  r.macro.in?(reverse_macros) &&
347
329
  r.klass >= self &&
348
330
  !r.options[:conditions] &&
331
+ !r.options[:scope] &&
349
332
  r.primary_key_name == refl.primary_key_name
350
333
  end
351
334
  end
@@ -410,11 +393,6 @@ module Hobo
410
393
  end
411
394
 
412
395
 
413
- def dependent_on
414
- self.class.dependent_on.map { |assoc| send(assoc) }
415
- end
416
-
417
-
418
396
  def attributes_with_hobo_type_conversion=(attributes, guard_protected_attributes=true)
419
397
  converted = attributes.map_hash { |k, v| convert_type_for_mass_assignment(self.class.attr_type(k), v) }
420
398
  send(:attributes_without_hobo_type_conversion=, converted, guard_protected_attributes)
@@ -166,7 +166,7 @@ module Hobo
166
166
 
167
167
 
168
168
  def def_auto_action(name, &block)
169
- define_method name, &block if name.not_in?(instance_methods) && include_action?(name)
169
+ define_method name, &block if !method_defined?(name) && include_action?(name)
170
170
  end
171
171
 
172
172
 
@@ -259,7 +259,7 @@ module Hobo
259
259
 
260
260
  def auto_actions_for(owner, actions)
261
261
  name = model.reflections[owner].macro == :has_many ? owner.to_s.singularize : owner
262
-
262
+
263
263
  owner_actions[owner] ||= []
264
264
  Array(actions).each do |action|
265
265
  case action
@@ -296,7 +296,7 @@ module Hobo
296
296
 
297
297
 
298
298
  def available_auto_write_actions
299
- if "position_column".in?(model.instance_methods)
299
+ if model.method_defined?("position_column")
300
300
  WRITE_ONLY_ACTIONS + [:reorder]
301
301
  else
302
302
  WRITE_ONLY_ACTIONS
@@ -393,7 +393,7 @@ module Hobo
393
393
  end
394
394
 
395
395
  def owning_object
396
- method = @this.class.dependent_on.detect {|m| !@this.send(m).nil?}
396
+ method = @this.class.view_hints.parent
397
397
  method ? @this.send(method) : nil
398
398
  end
399
399
 
@@ -461,14 +461,17 @@ module Hobo
461
461
 
462
462
 
463
463
  def find_owner_and_association(owner_association)
464
+ owner_name = name_of_auto_action_for(owner_association)
464
465
  refl = model.reflections[owner_association]
465
- owner_name = refl.macro == :has_many ? owner_association.to_s.singularize : owner_association
466
466
  id = params["#{owner_name}_id"]
467
467
  owner = refl.klass.find(id)
468
468
  instance_variable_set("@#{owner_association}", owner)
469
469
  [owner, owner.send(model.reverse_reflection(owner_association).name)]
470
470
  end
471
471
 
472
+ def name_of_auto_action_for(owner_association)
473
+ model.reflections[owner_association].macro == :has_many ? owner_association.to_s.singularize : owner_association
474
+ end
472
475
 
473
476
  # --- Action implementations --- #
474
477
 
@@ -511,18 +514,28 @@ module Hobo
511
514
 
512
515
  def hobo_create(*args, &b)
513
516
  options = args.extract_options!
514
- self.this ||= args.first || new_for_create
515
- this.user_update_attributes(current_user, options[:attributes] || attribute_parameters || {})
517
+ attributes = options[:attributes] || attribute_parameters || {}
518
+ if self.this ||= args.first
519
+ this.user_update_attributes(current_user, attributes)
520
+ else
521
+ self.this = new_for_create(attributes)
522
+ this.save
523
+ end
516
524
  create_response(:new, options, &b)
517
525
  end
518
526
 
519
527
 
520
- def hobo_create_for(owner, *args, &b)
528
+ def hobo_create_for(owner_association, *args, &b)
521
529
  options = args.extract_options!
522
- owner, association = find_owner_and_association(owner)
523
- self.this ||= args.first || association.new
524
- this.user_update_attributes(current_user, options[:attributes] || attribute_parameters || {})
525
- create_response(:"new_for_#{owner}", options, &b)
530
+ owner, association = find_owner_and_association(owner_association)
531
+ attributes = options[:attributes] || attribute_parameters || {}
532
+ if self.this ||= args.first
533
+ this.user_update_attributes(current_user, attributes)
534
+ else
535
+ self.this = association.new(attributes)
536
+ this.save
537
+ end
538
+ create_response(:"new_for_#{name_of_auto_action_for(owner_association)}", options, &b)
526
539
  end
527
540
 
528
541
 
@@ -531,10 +544,10 @@ module Hobo
531
544
  end
532
545
 
533
546
 
534
- def new_for_create
547
+ def new_for_create(attributes = {})
535
548
  type_param = subtype_for_create
536
549
  create_model = type_param ? type_param.constantize : model
537
- create_model.user_new(current_user)
550
+ create_model.user_new(current_user, attributes)
538
551
  end
539
552
 
540
553
 
@@ -755,7 +768,7 @@ module Hobo
755
768
  self.this = true # Otherwise this gets sent user_view
756
769
  logger.info "Hobo: Permission Denied!"
757
770
  @permission_error = error
758
- if "permission_denied".in?(self.class.superclass.instance_methods)
771
+ if self.class.superclass.method_defined?("permission_denied")
759
772
  super
760
773
  else
761
774
  respond_to do |wants|
@@ -248,7 +248,7 @@ module Hobo
248
248
 
249
249
 
250
250
  def named_route(name, route, options={})
251
- if controller.public_instance_methods.include?(options[:action].to_s)
251
+ if controller.public_method_defined?(options[:action])
252
252
  options.reverse_merge!(:controller => route_with_subsite(plural))
253
253
  name = name_with_subsite(name)
254
254
  route = route_with_subsite(route)
@@ -13,7 +13,12 @@ module Hobo
13
13
  alias_method_chain :create, :hobo_permission_check
14
14
  alias_method_chain :update, :hobo_permission_check
15
15
  alias_method_chain :destroy, :hobo_permission_check
16
-
16
+ class << self
17
+ alias_method_chain :has_many, :hobo_permission_check
18
+ alias_method_chain :has_one, :hobo_permission_check
19
+ alias_method_chain :belongs_to, :hobo_permission_check
20
+ end
21
+
17
22
  attr_accessor :acting_user, :origin, :origin_attribute
18
23
 
19
24
  bool_attr_accessor :exempt_from_edit_checks
@@ -25,9 +30,9 @@ module Hobo
25
30
  def self.find_aliased_name(klass, method_name)
26
31
  # The method +method_name+ will have been aliased. We jump through some hoops to figure out
27
32
  # what it's new name is
28
- method_name = method_name.to_s
33
+ method_name = method_name.to_sym
29
34
  method = klass.instance_method method_name
30
- methods = klass.private_instance_methods + klass.instance_methods
35
+ methods = (klass.private_instance_methods + klass.instance_methods).*.to_sym
31
36
  new_name = methods.select {|m| klass.instance_method(m) == method }.find { |m| m != method_name }
32
37
  end
33
38
 
@@ -75,7 +80,52 @@ module Hobo
75
80
  def viewable_by?(user, attribute=nil)
76
81
  new.viewable_by?(user, attribute)
77
82
  end
83
+
84
+ # ensure active_user gets passed down to :dependent => destroy
85
+ # associations (Ticket #528)
78
86
 
87
+ def has_many_with_hobo_permission_check(association_id, options = {}, &extension)
88
+ has_many_without_hobo_permission_check(association_id, options, &extension)
89
+ reflection = reflections[association_id]
90
+ if reflection.options[:dependent]==:destroy
91
+ #overriding dynamic method created in ActiveRecord::Associations#configure_dependency_for_has_many
92
+ method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym
93
+ define_method(method_name) do
94
+ send(reflection.name).each { |r| r.is_a?(Hobo::Model) ? r.user_destroy(acting_user) : r.destroy }
95
+ end
96
+ end
97
+ end
98
+
99
+ def has_one_with_hobo_permission_check(association_id, options = {}, &extension)
100
+ has_one_without_hobo_permission_check(association_id, options, &extension)
101
+ reflection = reflections[association_id]
102
+ if reflection.options[:dependent]==:destroy
103
+ #overriding dynamic method created in ActiveRecord::Associations#configure_dependency_for_has_many
104
+ method_name = "has_one_dependent_destroy_for_#{reflection.name}".to_sym
105
+ define_method(method_name) do
106
+ association = send(reflection.name)
107
+ unless association.nil?
108
+ association.is_a?(Hobo::Model) ? association.user_destroy(active_user) : association.destroy
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ def belongs_to_with_hobo_permission_check(association_id, options = {}, &extension)
115
+ belongs_to_without_hobo_permission_check(association_id, options, &extension)
116
+ reflection = reflections[association_id]
117
+ if reflection.options[:dependent]==:destroy
118
+ #overriding dynamic method created in ActiveRecord::Associations#configure_dependency_for_has_many
119
+ method_name = "belongs_to_dependent_destroy_for_#{reflection.name}".to_sym
120
+ define_method(method_name) do
121
+ association = send(reflection.name)
122
+ unless association.nil?
123
+ association.is_a?(Hobo::Model) ? association.user_destroy(active_user) : association.destroy
124
+ end
125
+ end
126
+ end
127
+ end
128
+
79
129
  end
80
130
 
81
131
 
@@ -123,8 +173,8 @@ module Hobo
123
173
  result
124
174
  end
125
175
 
126
- def user_save(user)
127
- with_acting_user(user) { save }
176
+ def user_save(user, validate = true)
177
+ with_acting_user(user) { save(validate) }
128
178
  end
129
179
 
130
180
  def user_save!(user)
@@ -45,12 +45,20 @@ module Hobo
45
45
  end
46
46
 
47
47
 
48
- def insert_record(record)
48
+ def insert_record(record, force = false, validate = true)
49
49
  set_belongs_to_association_for(record)
50
50
  if (user = acting_user) && record.is_a?(Hobo::Model)
51
- record.user_save(user)
51
+ if force
52
+ record.user_save!(user)
53
+ else
54
+ record.user_save(user, validate)
55
+ end
52
56
  else
53
- record.save
57
+ if force
58
+ record.save!
59
+ else
60
+ record.save(validate)
61
+ end
54
62
  end
55
63
  end
56
64
 
@@ -99,13 +107,13 @@ module Hobo
99
107
  end
100
108
 
101
109
 
102
- def insert_record(record, force=true)
110
+ def insert_record(record, force=true, validate=true)
103
111
  user = acting_user if record.is_a?(Hobo::Model)
104
112
  if record.new_record?
105
113
  if force
106
114
  user ? record.user_save!(user) : record.save!
107
115
  else
108
- return false unless (user ? record.user_save(user) : record.save)
116
+ return false unless (user ? record.user_save(user, validate) : record.save(validate))
109
117
  end
110
118
  end
111
119
  klass = @reflection.through_reflection.klass
@@ -92,13 +92,16 @@ module Hobo::RapidHelper
92
92
 
93
93
 
94
94
 
95
- def in_place_editor(attributes)
95
+ def in_place_editor(attributes, this=nil)
96
96
  blank_message = attributes.delete(:blank_message) || "(click to edit)"
97
97
 
98
98
  attributes = add_classes(attributes, "in-place-edit", model_id_class(this_parent, this_field))
99
99
  attributes.update(:hobo_blank_message => blank_message,
100
100
  :if_blank => blank_message,
101
101
  :no_wrapper => false)
102
+
103
+ edit_text = this._?.to_s
104
+ attributes.update(:hobo_edit_text => edit_text) unless edit_text.nil?
102
105
 
103
106
  update = attributes.delete(:update)
104
107
  attributes[:hobo_update] = update if update
@@ -122,15 +125,6 @@ module Hobo::RapidHelper
122
125
  end
123
126
 
124
127
 
125
- def primary_collection_name(object=this)
126
- dependent_collection_names = object.class.reflections.values.select do |refl|
127
- refl.macro == :has_many && refl.options[:dependent]
128
- end.*.name
129
-
130
- (dependent_collection_names - through_collection_names(object)).first
131
- end
132
-
133
-
134
128
  def non_through_collections(object=this)
135
129
  names = object.class.reflections.values.select do |refl|
136
130
  refl.macro == :has_many
@@ -347,7 +347,15 @@ module Hobo
347
347
 
348
348
 
349
349
  def def_scope(options={}, &block)
350
- @klass.named_scope(name.to_sym, block || options)
350
+ _name = name.to_sym
351
+ @klass.named_scope(_name, block || options)
352
+ # this is tricky; ordinarily, we'd worry about subclasses that haven't yet been loaded.
353
+ # HOWEVER, they will pick up the scope setting via read_inheritable_attribute when they do
354
+ # load, so only the currently existing subclasses need to be fixed up.
355
+ _scope = @klass.scopes[_name]
356
+ @klass.send(:subclasses).each do |k|
357
+ k.scopes[_name] = _scope
358
+ end
351
359
  end
352
360
 
353
361
 
@@ -0,0 +1,88 @@
1
+ module Hobo
2
+
3
+ module Translations
4
+
5
+ # --- Translation Helper --- #
6
+ #
7
+ # Uses RoR native I18n.translate.
8
+ #
9
+ # Adds some conventions for easier hobo translation.
10
+ # 1. Assumes the first part of the key to be a model name (e.g.: users.index.title -> user)
11
+ # 2. Tries to translate the model by lookup for: (e.g.: user-> activerecord.models.user)
12
+ # 3. Adds a default fallback to the beginning of the fallback chain
13
+ # by replacing the first part of the key with "hobo" and using the translated model name
14
+ # as additional attribute. This allows us to have default translations
15
+ # (e.g.: hobo.index.title: "{{model}} Index")
16
+ #
17
+ # Is also used as a tag in the dryml-view files. The syntax is:
18
+ # <ht key="my.app">My Application</ht>
19
+ # --> Will lookup the "my.app"-key for your locale and replaces the "My Application" content
20
+ # if found.
21
+ #
22
+ # <ht key="my" app="Program">My Application</ht>
23
+ # --> Will look up both the "my"- and "app"-key for your locale, and replaces the
24
+ # "My Application" with the "my"-key contents (interpolated using the "app"-key.
25
+ # sample.en.yml-file:
26
+ # "no":
27
+ # my: "Mitt {{app}}"
28
+ # The output should be: Mitt Program
29
+ #
30
+ # Otherwise with features as the ht method, step 1, 2 and 3 above.
31
+ def self.ht(key, options={})
32
+
33
+ # Check if called as a tag, i.e. like this <ht></ht>
34
+ if (key.class == Hash)
35
+ if key.has_key?(:default) && !key[:default].blank?
36
+ Rails.logger.warn "hobo-i18n: 'default' should not be used as an attribute on the ht-tag. If used, then you need to make sure that the tags inner-contents are not used. These are normally treated as defaults automatically, but if there is a default attribute then that inner-content will be hidden from this method - and will not be replaced with the translation found."
37
+ end
38
+ defaults = options[:default];
39
+ # Swap key and options, remove options[:key]
40
+ options = key
41
+ key = options.delete(:key) # returns value for options[:key] as well as deleting it
42
+ # Set options[:default] to complete the tag-argument-conversion process.
43
+ options[:default] = (defaults.class == Proc) ? [defaults.call(options)] : (options[:default].blank? ? [] : [options[:default]])
44
+ else
45
+ # Not called as a tag. Prepare options[:default].
46
+ if options[:default].nil?
47
+ options[:default]=[]
48
+ elsif options[:default].class != Array
49
+ options[:default] = [options[:default]]
50
+ end
51
+ end
52
+
53
+ # assume the first part of the key to be the model
54
+ keys = key.to_s.split(".")
55
+ if keys.length > 1
56
+ model = keys.shift()
57
+ subkey = keys.join(".")
58
+ else
59
+ subkey = key
60
+ end
61
+
62
+ # add :"hobo.#{key}" as the first fallback
63
+ options[:default].unshift("hobo.#{subkey}".to_sym)
64
+
65
+ # translate the model
66
+ unless model.blank?
67
+ translated_model = I18n.translate( "activerecord.models.#{model.singularize.underscore}", :default=>model).titleize
68
+ options[:model] = translated_model
69
+ end
70
+
71
+ key_prefix = "<span class='translation-key'>#{key}</span>" if defined?(HOBO_SHOW_LOCALE_KEYS) && HOBO_SHOW_LOCALE_KEYS
72
+
73
+ Rails.logger.info "..translate(#{key}, #{options.inspect}) to #{I18n.locale}" if defined?(HOBO_VERBOSE_TRANSLATIONS)
74
+
75
+ I18n.translate(key.to_sym, options)+(key_prefix ? key_prefix:"")
76
+ end
77
+
78
+ # if somebody includes us, give them ht as an instance method
79
+ def self.included(base)
80
+ translation_class = self
81
+ base.class_eval do
82
+ define_method :ht do |*args|
83
+ translation_class.ht(*args)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end