hobo 0.8.3 → 0.8.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 (80) hide show
  1. data/CHANGES.txt +330 -0
  2. data/Manifest +12 -4
  3. data/Rakefile +4 -6
  4. data/dryml_generators/rapid/cards.dryml.erb +5 -1
  5. data/dryml_generators/rapid/forms.dryml.erb +8 -10
  6. data/dryml_generators/rapid/pages.dryml.erb +65 -36
  7. data/hobo.gemspec +28 -15
  8. data/lib/active_record/association_collection.rb +3 -22
  9. data/lib/hobo.rb +25 -258
  10. data/lib/hobo/accessible_associations.rb +131 -0
  11. data/lib/hobo/authentication_support.rb +15 -9
  12. data/lib/hobo/composite_model.rb +1 -1
  13. data/lib/hobo/controller.rb +7 -8
  14. data/lib/hobo/dryml.rb +9 -10
  15. data/lib/hobo/dryml/dryml_builder.rb +7 -1
  16. data/lib/hobo/dryml/dryml_doc.rb +161 -0
  17. data/lib/hobo/dryml/dryml_generator.rb +18 -9
  18. data/lib/hobo/dryml/part_context.rb +76 -42
  19. data/lib/hobo/dryml/tag_parameters.rb +1 -0
  20. data/lib/hobo/dryml/taglib.rb +2 -1
  21. data/lib/hobo/dryml/template.rb +39 -29
  22. data/lib/hobo/dryml/template_environment.rb +79 -37
  23. data/lib/hobo/dryml/template_handler.rb +66 -21
  24. data/lib/hobo/guest.rb +2 -10
  25. data/lib/hobo/hobo_helper.rb +125 -53
  26. data/lib/hobo/include_in_save.rb +0 -1
  27. data/lib/hobo/lifecycles.rb +54 -24
  28. data/lib/hobo/lifecycles/actions.rb +95 -31
  29. data/lib/hobo/lifecycles/creator.rb +18 -23
  30. data/lib/hobo/lifecycles/lifecycle.rb +86 -62
  31. data/lib/hobo/lifecycles/state.rb +1 -2
  32. data/lib/hobo/lifecycles/transition.rb +22 -28
  33. data/lib/hobo/model.rb +64 -176
  34. data/lib/hobo/model_controller.rb +67 -54
  35. data/lib/hobo/model_router.rb +5 -2
  36. data/lib/hobo/permissions.rb +397 -0
  37. data/lib/hobo/permissions/associations.rb +167 -0
  38. data/lib/hobo/scopes.rb +15 -38
  39. data/lib/hobo/scopes/association_proxy_extensions.rb +15 -5
  40. data/lib/hobo/scopes/automatic_scopes.rb +43 -18
  41. data/lib/hobo/scopes/named_scope_extensions.rb +2 -2
  42. data/lib/hobo/user.rb +10 -4
  43. data/lib/hobo/user_controller.rb +6 -5
  44. data/lib/hobo/view_hints.rb +58 -0
  45. data/rails_generators/hobo/hobo_generator.rb +7 -3
  46. data/rails_generators/hobo/templates/guest.rb +1 -13
  47. data/rails_generators/hobo_front_controller/hobo_front_controller_generator.rb +1 -1
  48. data/rails_generators/hobo_model/hobo_model_generator.rb +4 -2
  49. data/rails_generators/hobo_model/templates/hints.rb +4 -0
  50. data/rails_generators/hobo_model/templates/model.rb +8 -8
  51. data/rails_generators/hobo_model_controller/hobo_model_controller_generator.rb +10 -0
  52. data/rails_generators/hobo_model_controller/templates/controller.rb +1 -1
  53. data/rails_generators/hobo_rapid/templates/hobo-rapid.js +91 -56
  54. data/rails_generators/hobo_rapid/templates/lowpro.js +15 -15
  55. data/rails_generators/hobo_rapid/templates/reset.css +36 -3
  56. data/rails_generators/hobo_rapid/templates/themes/clean/public/stylesheets/clean.css +13 -17
  57. data/rails_generators/hobo_user_controller/templates/controller.rb +1 -1
  58. data/rails_generators/hobo_user_model/templates/model.rb +18 -16
  59. data/taglibs/core.dryml +60 -18
  60. data/taglibs/rapid.dryml +8 -401
  61. data/taglibs/rapid_core.dryml +586 -0
  62. data/taglibs/rapid_document_tags.dryml +28 -10
  63. data/taglibs/rapid_editing.dryml +92 -55
  64. data/taglibs/rapid_forms.dryml +406 -87
  65. data/taglibs/rapid_generics.dryml +1 -1
  66. data/taglibs/rapid_navigation.dryml +2 -1
  67. data/taglibs/rapid_pages.dryml +7 -16
  68. data/taglibs/rapid_plus.dryml +39 -14
  69. data/taglibs/rapid_support.dryml +1 -1
  70. data/taglibs/rapid_user_pages.dryml +14 -4
  71. data/tasks/{generate_tag_reference.rb → generate_tag_reference.rake} +49 -18
  72. data/tasks/hobo_tasks.rake +16 -0
  73. data/test/permissions/models/models.rb +134 -0
  74. data/test/permissions/models/schema.rb +55 -0
  75. data/test/permissions/models/test.sqlite3 +0 -0
  76. data/test/permissions/test_permissions.rb +436 -0
  77. metadata +27 -14
  78. data/lib/hobo/mass_assignment.rb +0 -64
  79. data/rails_generators/hobo/templates/patch_routing.rb +0 -30
  80. data/uninstall.rb +0 -1
@@ -0,0 +1,167 @@
1
+ module Hobo
2
+
3
+ module Permissions
4
+
5
+ module Associations
6
+
7
+ def self.enable
8
+
9
+ # Re-open AR classes...
10
+
11
+ ActiveRecord::Associations::HasManyAssociation.class_eval do
12
+
13
+ # Helper - the user acting on the owner (if there is one)
14
+ def acting_user
15
+ @owner.acting_user if @owner.is_a?(Hobo::Model)
16
+ end
17
+
18
+
19
+ def delete_records(records)
20
+ case @reflection.options[:dependent]
21
+ when :destroy
22
+ records.each { |r| r.is_a?(Hobo::Model) ? r.user_destroy(acting_user) : r.destroy }
23
+ when :delete_all
24
+ # No destroy permission check if the :delete_all option has been chosen
25
+ @reflection.klass.delete(records.map(&:id))
26
+ else
27
+ nullify_keys(records)
28
+ end
29
+ end
30
+
31
+
32
+ # Set the fkey used by this has_many to null on the passed records, checking for permission first if both the owner
33
+ # and record in question are Hobo models
34
+ def nullify_keys(records)
35
+ if (user = acting_user)
36
+ records.each { |r| r.user_changes!(user, @reflection.primary_key_name => nil) if r.is_a?(Hobo::Model) }
37
+ end
38
+
39
+ # Normal ActiveRecord implementatin
40
+ ids = quoted_record_ids(records)
41
+ @reflection.klass.update_all(
42
+ "#{@reflection.primary_key_name} = NULL",
43
+ "#{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
44
+ )
45
+ end
46
+
47
+
48
+ def insert_record(record)
49
+ set_belongs_to_association_for(record)
50
+ if (user = acting_user) && record.is_a?(Hobo::Model)
51
+ record.user_save(user)
52
+ else
53
+ record.save
54
+ end
55
+ end
56
+
57
+ def viewable_by?(user, field=nil)
58
+ # view check on an example member record is not supported on associations with conditions
59
+ return true if @reflection.options[:conditions]
60
+ new_candidate.viewable_by?(user, field)
61
+ end
62
+
63
+ end
64
+
65
+ ActiveRecord::Associations::HasManyThroughAssociation.class_eval do
66
+
67
+ def acting_user
68
+ @owner.acting_user if @owner.is_a?(Hobo::Model)
69
+ end
70
+
71
+
72
+ def create!(attrs = nil)
73
+ klass = @reflection.klass
74
+ user = acting_user if klass < Hobo::Model
75
+ klass.transaction do
76
+ object = if attrs
77
+ klass.send(:with_scope, :create => attrs) { user ? klass.user_create!(user) : klass.create! }
78
+ else
79
+ user ? klass.user_create!(user) : klass.create!
80
+ end
81
+ self << object
82
+ object
83
+ end
84
+ end
85
+
86
+
87
+ def create!(attrs = nil)
88
+ klass = @reflection.klass
89
+ user = acting_user if klass < Hobo::Model
90
+ klass.transaction do
91
+ object = if attrs
92
+ klass.send(:with_scope, :create => attrs) { user ? klass.user_create(user) : klass.create }
93
+ else
94
+ user ? klass.user_create(user) : klass.create
95
+ end
96
+ self << object
97
+ object
98
+ end
99
+ end
100
+
101
+
102
+ def insert_record(record, force=true)
103
+ user = acting_user if record.is_a?(Hobo::Model)
104
+ if record.new_record?
105
+ if force
106
+ user ? record.user_save!(user) : record.save!
107
+ else
108
+ return false unless (user ? record.user_save(user) : record.save)
109
+ end
110
+ end
111
+ klass = @reflection.through_reflection.klass
112
+ @owner.send(@reflection.through_reflection.name).proxy_target <<
113
+ klass.send(:with_scope, :create => construct_join_attributes(record)) { user ? klass.user_create!(user) : klass.create! }
114
+ end
115
+
116
+
117
+ # TODO - add dependent option support
118
+ def delete_records_with_hobo_permission_check(records)
119
+ klass = @reflection.through_reflection.klass
120
+ user = acting_user
121
+ if user && records.any? { |r|
122
+ joiner = klass.find(:first, :conditions => construct_join_attributes(r))
123
+ joiner.is_a?(Hobo::Model) && !joiner.destroyable_by?(user)
124
+ }
125
+ raise Hobo::PermissionDeniedError, "#{@owner.class}##{proxy_reflection.name}.destroy"
126
+ end
127
+ delete_records_without_hobo_permission_check(records)
128
+ end
129
+ alias_method_chain :delete_records, :hobo_permission_check
130
+
131
+ end
132
+
133
+ ActiveRecord::Associations::AssociationCollection.class_eval do
134
+
135
+ # Helper - the user acting on the owner (if there is one)
136
+ def acting_user
137
+ @owner.acting_user if @owner.is_a?(Hobo::Model)
138
+ end
139
+
140
+ def create(attrs = {})
141
+ if attrs.is_a?(Array)
142
+ attrs.collect { |attr| create(attr) }
143
+ else
144
+ create_record(attrs) do |record|
145
+ yield(record) if block_given?
146
+ user = acting_user if record.is_a?(Hobo::Model)
147
+ user ? record.user_save(user) : record.save
148
+ end
149
+ end
150
+ end
151
+
152
+ def create!(attrs = {})
153
+ create_record(attrs) do |record|
154
+ yield(record) if block_given?
155
+ user = acting_user if record.is_a?(Hobo::Model)
156
+ user ? record.user_save!(user) : record.save!
157
+ end
158
+ end
159
+
160
+ end
161
+
162
+ end
163
+
164
+ end
165
+ end
166
+
167
+ end
@@ -2,8 +2,13 @@ module Hobo
2
2
 
3
3
  module Scopes
4
4
 
5
- def self.included_in_class(base)
6
- base.extend(ClassMethods)
5
+ def self.included_in_class(klass)
6
+ klass.class_eval do
7
+ extend ClassMethods
8
+ metaclass.alias_method_chain :valid_keys_for_has_many_association, :scopes
9
+ metaclass.alias_method_chain :valid_keys_for_has_one_association, :scopes
10
+ metaclass.alias_method_chain :valid_keys_for_belongs_to_association, :scopes
11
+ end
7
12
  end
8
13
 
9
14
  module ClassMethods
@@ -13,46 +18,17 @@ module Hobo
13
18
  include ApplyScopes
14
19
 
15
20
  # --- monkey-patches to allow :scope key on has_many, has_one and belongs_to ---
16
-
17
- def create_has_many_reflection(association_id, options, &extension)
18
- options.assert_valid_keys(
19
- :class_name, :table_name, :foreign_key,
20
- :dependent,
21
- :select, :conditions, :include, :order, :group, :limit, :offset,
22
- :as, :through, :source, :source_type,
23
- :uniq,
24
- :finder_sql, :counter_sql,
25
- :before_add, :after_add, :before_remove, :after_remove,
26
- :extend,
27
- :scope
28
- )
29
-
30
- options[:extend] = create_extension_modules(association_id, extension, options[:extend]) if block_given?
31
-
32
- create_reflection(:has_many, association_id, options, self)
21
+
22
+ def valid_keys_for_has_many_association_with_scopes
23
+ valid_keys_for_has_many_association_without_scopes + [:scope]
33
24
  end
34
25
 
35
- def create_has_one_reflection(association_id, options)
36
- options.assert_valid_keys(
37
- :class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :scope
38
- )
39
-
40
- create_reflection(:has_one, association_id, options, self)
26
+ def valid_keys_for_has_one_association_with_scopes
27
+ valid_keys_for_has_one_association_without_scopes + [:scope]
41
28
  end
42
29
 
43
- def create_belongs_to_reflection(association_id, options)
44
- options.assert_valid_keys(
45
- :class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent,
46
- :counter_cache, :extend, :polymorphic, :scope
47
- )
48
-
49
- reflection = create_reflection(:belongs_to, association_id, options, self)
50
-
51
- if options[:polymorphic]
52
- reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type"
53
- end
54
-
55
- reflection
30
+ def valid_keys_for_belongs_to_association_with_scopes
31
+ valid_keys_for_belongs_to_association_without_scopes + [:scope]
56
32
  end
57
33
 
58
34
  end
@@ -62,5 +38,6 @@ module Hobo
62
38
  end
63
39
 
64
40
  ActiveRecord::Associations::AssociationProxy.send(:include, Hobo::Scopes::AssociationProxyExtensions)
41
+ ActiveRecord::Associations::AssociationCollection.send(:include, Hobo::Scopes::AssociationCollectionExtensions)
65
42
  ActiveRecord::Associations::HasManyThroughAssociation.send(:include, Hobo::Scopes::HasManyThroughAssociationExtensions)
66
43
  require "hobo/scopes/named_scope_extensions"
@@ -24,22 +24,32 @@ module Hobo
24
24
  unscoped_conditions = conditions_without_hobo_scopes
25
25
  combine_conditions(scope_conditions, unscoped_conditions)
26
26
  end
27
-
28
27
  alias_method_chain :conditions, :hobo_scopes
29
28
 
30
29
  end
31
30
 
32
31
  HasManyThroughAssociationExtensions = classy_module do
33
32
 
34
- def sql_conditions_with_hobo_scopes
33
+ def conditions_with_hobo_scopes
35
34
  scope_conditions = self.scope_conditions(@reflection)
36
35
  through_scope_conditions = self.scope_conditions(@reflection.through_reflection)
37
- unscoped_conditions = sql_conditions_without_hobo_scopes
36
+ unscoped_conditions = conditions_without_hobo_scopes
38
37
  combine_conditions(scope_conditions, through_scope_conditions, unscoped_conditions)
39
38
  end
39
+ alias_method_chain :conditions, :hobo_scopes
40
+ alias_method :sql_conditions, :conditions
41
+ public :conditions, :sql_conditions
40
42
 
41
- alias_method_chain :sql_conditions, :hobo_scopes
42
-
43
+ end
44
+
45
+ AssociationCollectionExtensions = classy_module do
46
+
47
+ def proxy_respond_to_with_automatic_scopes?(method, include_priv = false)
48
+ proxy_respond_to_without_automatic_scopes?(method, include_priv) ||
49
+ (@reflection.klass.create_automatic_scope(method) if @reflection.klass.respond_to?(:create_automatic_scope))
50
+ end
51
+ alias_method_chain :proxy_respond_to?, :automatic_scopes
52
+
43
53
  end
44
54
 
45
55
  end
@@ -35,9 +35,13 @@ module Hobo
35
35
  if name =~ /^with_(.*)/ && (refl = reflection($1))
36
36
 
37
37
  def_scope do |*records|
38
- records = records.flatten.compact.map {|r| find_if_named(refl, r) }
39
- exists_sql = ([exists_sql_condition(refl)] * records.length).join(" AND ")
40
- { :conditions => [exists_sql] + records }
38
+ if records.empty?
39
+ { :conditions => exists_sql_condition(refl, true) }
40
+ else
41
+ records = records.flatten.compact.map {|r| find_if_named(refl, r) }
42
+ exists_sql = ([exists_sql_condition(refl)] * records.length).join(" AND ")
43
+ { :conditions => [exists_sql] + records }
44
+ end
41
45
  end
42
46
 
43
47
  # with_player(a_player)
@@ -53,9 +57,13 @@ module Hobo
53
57
  elsif name =~ /^without_(.*)/ && (refl = reflection($1))
54
58
 
55
59
  def_scope do |*records|
56
- records = records.flatten.compact.map {|r| find_if_named(refl, r) }
57
- exists_sql = ([exists_sql_condition(refl)] * records.length).join(" AND ")
58
- { :conditions => ["NOT (#{exists_sql})"] + records }
60
+ if records.empty?
61
+ { :conditions => "NOT (#{exists_sql_condition(refl, true)})" }
62
+ else
63
+ records = records.flatten.compact.map {|r| find_if_named(refl, r) }
64
+ exists_sql = ([exists_sql_condition(refl)] * records.length).join(" AND ")
65
+ { :conditions => ["NOT (#{exists_sql})"] + records }
66
+ end
59
67
  end
60
68
 
61
69
  # without_player(a_player)
@@ -219,11 +227,19 @@ module Hobo
219
227
  def_scope :order => "#{@klass.table_name}.created_at DESC"
220
228
 
221
229
  when "recent"
222
- def_scope do |*args|
223
- count = args.first || 3
224
- { :limit => count, :order => "#{@klass.table_name}.created_at DESC" }
230
+
231
+ if "created_at".in?(@klass.columns.*.name)
232
+ def_scope do |*args|
233
+ count = args.first || 6
234
+ { :limit => count, :order => "#{@klass.table_name}.created_at DESC" }
235
+ end
236
+ else
237
+ def_scope do |*args|
238
+ count = args.first || 6
239
+ { :limit => count }
240
+ end
225
241
  end
226
-
242
+
227
243
  when "limit"
228
244
  def_scope do |count|
229
245
  { :limit => count }
@@ -276,22 +292,31 @@ module Hobo
276
292
  end
277
293
 
278
294
 
279
- def exists_sql_condition(reflection)
295
+ def exists_sql_condition(reflection, any=false)
280
296
  owner = @klass
281
297
  owner_primary_key = "#{owner.table_name}.#{owner.primary_key}"
282
298
  if reflection.options[:through]
283
299
  join_table = reflection.through_reflection.klass.table_name
284
- source_fkey = reflection.source_reflection.primary_key_name
285
300
  owner_fkey = reflection.through_reflection.primary_key_name
286
- "EXISTS (SELECT * FROM #{join_table} " +
287
- "WHERE #{join_table}.#{source_fkey} = ? AND #{join_table}.#{owner_fkey} = #{owner_primary_key})"
301
+ if any
302
+ "EXISTS (SELECT * FROM #{join_table} WHERE #{join_table}.#{owner_fkey} = #{owner_primary_key})"
303
+ else
304
+ source_fkey = reflection.source_reflection.primary_key_name
305
+ "EXISTS (SELECT * FROM #{join_table} " +
306
+ "WHERE #{join_table}.#{source_fkey} = ? AND #{join_table}.#{owner_fkey} = #{owner_primary_key})"
307
+ end
288
308
  else
289
- related = reflection.klass
290
309
  foreign_key = reflection.primary_key_name
310
+ related = reflection.klass
291
311
 
292
- "EXISTS (SELECT * FROM #{related.table_name} " +
293
- "WHERE #{related.table_name}.#{foreign_key} = #{owner_primary_key} AND " +
294
- "#{related.table_name}.#{related.primary_key} = ?)"
312
+ if any
313
+ "EXISTS (SELECT * FROM #{related.table_name} " +
314
+ "WHERE #{related.table_name}.#{foreign_key} = #{owner_primary_key})"
315
+ else
316
+ "EXISTS (SELECT * FROM #{related.table_name} " +
317
+ "WHERE #{related.table_name}.#{foreign_key} = #{owner_primary_key} AND " +
318
+ "#{related.table_name}.#{related.primary_key} = ?)"
319
+ end
295
320
  end
296
321
  end
297
322
 
@@ -6,8 +6,8 @@ module ActiveRecord
6
6
 
7
7
  include Hobo::Scopes::ApplyScopes
8
8
 
9
- def respond_to?(method)
10
- super || scopes.include?(method) || proxy_scope.respond_to?(method)
9
+ def respond_to?(method, include_private=false)
10
+ super || scopes.include?(method) || proxy_scope.respond_to?(method, include_private)
11
11
  end
12
12
 
13
13
  private
@@ -31,7 +31,8 @@ module Hobo
31
31
  remember_token_expires_at :datetime
32
32
  end
33
33
 
34
- validates_confirmation_of :password, :if => :new_password_required?
34
+ validates_presence_of :password_confirmation, :if => :new_password_required?
35
+ validates_confirmation_of :password, :if => :new_password_required?
35
36
  password_validations
36
37
  validate :validate_current_password_when_changing_password
37
38
 
@@ -147,13 +148,18 @@ module Hobo
147
148
 
148
149
 
149
150
  def changing_password?
150
- crypted_password? && (password || password_confirmation) && !lifecycle.valid_key?
151
+ !new_record? && !lifecycle_changing_password? &&
152
+ (current_password.present? || password.present? || password_confirmation.present?)
153
+ end
154
+
155
+
156
+ def lifecycle_changing_password?
157
+ lifecycle.active_step && :password.in?(lifecycle.active_step.parameters)
151
158
  end
152
-
153
159
 
154
160
  # Is a new password (and confirmation) required? (i.e. signing up or changing password)
155
161
  def new_password_required?
156
- (account_active? && crypted_password.blank?) || password || password_confirmation
162
+ lifecycle_changing_password? || changing_password?
157
163
  end
158
164
 
159
165
 
@@ -17,8 +17,6 @@ module Hobo
17
17
 
18
18
  include_taglib "rapid_user_pages", :plugin => "hobo"
19
19
 
20
- show_action :account
21
-
22
20
  alias_method_chain :hobo_update, :account_flash
23
21
  end
24
22
 
@@ -31,7 +29,7 @@ module Hobo
31
29
 
32
30
  def available_auto_actions_with_user_actions
33
31
  available_auto_actions_without_user_actions +
34
- [:login, :signup, :logout, :forgot_password, :reset_password]
32
+ [:login, :signup, :logout, :forgot_password, :reset_password, :account]
35
33
  end
36
34
 
37
35
 
@@ -44,6 +42,7 @@ module Hobo
44
42
  def do_signup; hobo_do_signup end if include_action?(:do_signup)
45
43
  def forgot_password; hobo_forgot_password; end if include_action?(:forgot_password)
46
44
  def do_reset_password; hobo_do_reset_password; end if include_action?(:do_reset_password)
45
+ show_action :account if include_action?(:account)
47
46
  end
48
47
  end
49
48
 
@@ -53,6 +52,8 @@ module Hobo
53
52
  private
54
53
 
55
54
  def hobo_login(options={})
55
+ (redirect_to home_page; return) if logged_in?
56
+
56
57
  login_attr = model.login_attribute.to_s.titleize.downcase
57
58
  options.reverse_merge!(:success_notice => "You have logged in.",
58
59
  :failure_notice => "You did not provide a valid #{login_attr} and password.")
@@ -72,7 +73,7 @@ module Hobo
72
73
  self.current_user = old_user
73
74
  render :action => :account_disabled unless performed?
74
75
  else
75
- if params[:remember_me] == "1"
76
+ if params[:remember_me].present?
76
77
  current_user.remember_me
77
78
  create_auth_cookie
78
79
  end
@@ -113,7 +114,7 @@ module Hobo
113
114
  if request.post?
114
115
  user = model.find_by_email_address(params[:email_address])
115
116
  if user && (!block_given? || yield(user))
116
- user.lifecycle.request_password_reset(:nobody)
117
+ user.lifecycle.request_password_reset!(:nobody)
117
118
  end
118
119
  render_tag :forgot_password_email_sent_page
119
120
  end