hobo 0.8.3 → 0.8.4

Sign up to get free protection for your applications and to get access to all the features.
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