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
@@ -1,73 +1,137 @@
1
+ # We need to be able to eval an expression outside of the Hobo module
2
+ # so that, e.g. "Userd" doesn't eval to "Hobo::User"
3
+ # (Ruby determines this constant lookup scope lexically)
4
+ def __top_level_eval__(obj, expr)
5
+ obj.instance_eval(expr)
6
+ end
7
+
1
8
  module Hobo
2
9
 
3
10
  module Lifecycles
4
11
 
5
12
  module Actions
6
13
 
7
- def set_or_check_who!(record, user)
14
+ def available_to_acting_user?(record)
15
+ return true if available_to.nil? # available_to not specified (these steps are not published)
16
+ acting_user_is?(available_to, record)
17
+ end
18
+
19
+
20
+ def acting_user_is?(who, record)
21
+ user = record.acting_user
22
+
8
23
  case who
9
- when :nobody
10
- user == :nobody
11
- when :anybody
24
+ when :all
12
25
  true
26
+
27
+ when :key_holder
28
+ record.lifecycle.valid_key?
29
+
13
30
  when :self
14
31
  record == user
32
+
15
33
  when Array
16
- who.detect {|attribute| record.send(attribute) == user }
34
+ # recursively apply the same test to every item in the array
35
+ who.detect { |w| acting_user_is?(w, record) }
36
+
17
37
  else
18
- if (current = record.send(who)) # it's already set, check it's the same user
19
- user == current
20
- elsif user.is_a?(record.class.attr_type(who))
21
- record.send("#{who}=", user)
22
- true
38
+ refl = record.class.reflections[who]
39
+ if refl && refl.macro == :has_many
40
+ send(who).include?(user)
41
+ elsif refl && refl.macro == :belongs_to
42
+ send("#{who}_is?", user)
23
43
  else
24
- false
44
+ value = run_hook(record, who)
45
+ if value.is_a?(Class)
46
+ user.is_a?(value)
47
+ elsif value.respond_to?(:include?)
48
+ value.include?(user)
49
+ else
50
+ value == user
51
+ end
25
52
  end
26
53
  end
27
54
  end
28
55
 
29
56
 
30
57
  def run_hook(record, hook, *args)
31
- if hook.is_a?(Symbol)
58
+ case hook
59
+ when Symbol
32
60
  record.send(hook, *args)
33
- elsif hook.is_a?(Proc)
34
- hook.call(record, *args)
61
+ when String
62
+ __top_level_eval__(record, hook)
63
+ when Proc
64
+ if hook.arity == 1
65
+ hook.call(record)
66
+ else
67
+ record.instance_eval(&hook)
68
+ end
35
69
  end
36
70
  end
37
71
 
38
72
 
39
73
  def fire_event(record, event)
40
- record.instance_eval(&event) if event
74
+ if event
75
+ if event.arity == 1
76
+ event.call(record)
77
+ else
78
+ record.instance_eval(&event)
79
+ end
80
+ end
41
81
  end
42
82
 
43
83
 
44
- def check_guard(record, user)
45
- !options[:if] || run_hook(record, options[:if], user)
46
- end
47
-
48
- def check_invariants(record)
49
- record.lifecycle.invariants_satisfied?
84
+ def guard_ok?(record)
85
+ if options[:if]
86
+ raise ArgumentError, "do not provide both :if and :unless to lifecycle steps" if options[:unless]
87
+ run_hook(record, options[:if])
88
+ elsif options[:unless]
89
+ !run_hook(record, options[:unless])
90
+ else
91
+ true
92
+ end
50
93
  end
51
94
 
52
95
 
53
- def prepare(record, user, attributes=nil)
54
- if attributes
55
- attributes = extract_attributes(attributes)
56
- record.attributes = attributes
57
- end
96
+ def prepare!(record, attributes)
97
+ record.attributes = extract_attributes(attributes) if attributes
58
98
  record.lifecycle.generate_key if options[:new_key]
59
- set_or_check_who!(record, user) && record
99
+ apply_user_becomes!(record)
60
100
  end
61
101
 
62
102
 
63
- def prepare_and_check!(record, user, attributes=nil)
64
- prepare(record, user, attributes) && check_guard(record, user) && check_invariants(record)
103
+ def can_run?(record)
104
+ available_to_acting_user?(record) && guard_ok?(record) && record.lifecycle.invariants_satisfied?
105
+ end
106
+
107
+
108
+ def available_to
109
+ options[:available_to]
65
110
  end
66
111
 
112
+
67
113
  def publishable?
68
- who != :nobody
114
+ available_to
69
115
  end
70
-
116
+
117
+
118
+ def apply_user_becomes!(record)
119
+ if (assoc = options[:user_becomes])
120
+ record.send("#{assoc}=", record.acting_user)
121
+ end
122
+ end
123
+
124
+ def get_state(record, state)
125
+ case state
126
+ when Proc
127
+ state.call(record)
128
+ when String
129
+ eval(state, record.instance_eval { binding })
130
+ else
131
+ state
132
+ end
133
+ end
134
+
71
135
  end
72
136
 
73
137
  end
@@ -1,35 +1,25 @@
1
1
  module Hobo
2
2
  module Lifecycles
3
3
 
4
- class Creator < Struct.new(:lifecycle, :name, :who, :on_create, :options)
4
+ class Creator < Struct.new(:lifecycle, :name, :on_create, :options)
5
5
 
6
6
  def initialize(*args)
7
7
  super
8
+ self.name = name.to_sym
8
9
  lifecycle.creators[name] = self
9
10
  end
10
11
 
11
12
  include Actions
12
13
 
13
- def check_preconditions(record)
14
- record.lifecycle.preconditions_satisfied?
15
- end
16
-
17
-
18
- def prepare_and_check_with_preconditions!(record, user, attributes=nil)
19
- prepare_and_check_without_preconditions!(record, user, attributes) && check_preconditions(record)
20
- end
21
- alias_method_chain :prepare_and_check!, :preconditions
22
-
23
-
24
- def allowed?(user, attributes=nil)
14
+ def allowed?(user)
25
15
  record = lifecycle.model.new
26
- prepare_and_check!(record, user, attributes)
16
+ record.with_acting_user(user) { can_run?(record) }
27
17
  end
28
18
 
29
19
 
30
20
  def candidate(user, attributes=nil)
31
21
  record = lifecycle.model.new
32
- prepare_and_check!(record, user, attributes)
22
+ record.with_acting_user(user) { prepare!(record, attributes) }
33
23
  record.exempt_from_edit_checks = true
34
24
  record
35
25
  end
@@ -42,6 +32,7 @@ module Hobo
42
32
  params.each do |p|
43
33
  if (refl = model.reflections[p]) && refl.macro == :belongs_to
44
34
  allowed << refl.primary_key_name.to_s
35
+ allowed << refl.options[:foreign_type] if refl.options[:polymorphic]
45
36
  end
46
37
  end
47
38
  attributes & allowed
@@ -49,20 +40,24 @@ module Hobo
49
40
 
50
41
 
51
42
  def change_state(record)
52
- state = options[:become]
53
- record.lifecycle.become(state) if state
43
+ state = get_state(record, options[:become])
44
+ record.lifecycle.become state if state
54
45
  end
55
46
 
56
47
 
57
48
  def run!(user, attributes)
58
49
  record = lifecycle.model.new
59
- if prepare_and_check!(record, user, attributes)
60
- if change_state(record)
61
- fire_event(record, on_create)
50
+ record.lifecycle.active_step = self
51
+ record.with_acting_user(user) do
52
+ prepare!(record, attributes)
53
+ if can_run?(record)
54
+ if change_state(record)
55
+ fire_event(record, on_create)
56
+ end
57
+ record
58
+ else
59
+ raise Hobo::PermissionDeniedError
62
60
  end
63
- record
64
- else
65
- raise Hobo::Model::PermissionDeniedError
66
61
  end
67
62
  end
68
63
 
@@ -14,49 +14,51 @@ module Hobo
14
14
  @states = {}
15
15
  @creators = {}
16
16
  @transitions = []
17
- @preconditions = []
18
17
  @invariants = []
19
18
  end
20
19
 
21
20
  class << self
22
- attr_accessor :model, :options, :states, :initial_state,
23
- :creators, :transitions, :invariants, :preconditions
21
+ attr_accessor :model, :options, :states, :default_state,
22
+ :creators, :transitions, :invariants
24
23
  end
25
24
 
26
25
  def self.def_state(name, on_enter)
27
- name = name.to_s
28
- returning(State.new(name, on_enter)) do |s|
26
+ name = name.to_sym
27
+ returning(Lifecycles::State.new(name, on_enter)) do |s|
29
28
  states[name] = s
30
- class_eval "def #{name}_state?; state_name == '#{name}' end"
29
+ class_eval "def #{name}_state?; state_name == :#{name} end"
31
30
  end
32
31
  end
33
32
 
34
33
 
35
- def self.def_creator(name, who, on_create, options)
36
- name = name.to_s
37
- returning(Creator.new(self, name, who, on_create, options)) do |creator|
34
+ def self.def_creator(name, on_create, options)
35
+ name = name.to_sym
36
+ returning(Creator.new(self, name, on_create, options)) do |creator|
38
37
 
39
38
  class_eval %{
40
39
  def self.#{name}(user, attributes=nil)
41
- create('#{name}', user, attributes)
40
+ create(:#{name}, user, attributes)
42
41
  end
43
42
  def self.can_#{name}?(user, attributes=nil)
44
- can_create?('#{name}', user, attributes)
43
+ can_create?(:#{name}, user)
45
44
  end
46
45
  }
47
46
 
48
47
  end
49
48
  end
50
49
 
51
- def self.def_transition(name, who, start_state, end_states, on_transition, options)
52
- returning(Transition.new(self, name.to_s, who, start_state, end_states, on_transition, options)) do |t|
50
+ def self.def_transition(name, start_state, end_states, on_transition, options)
51
+ returning(Transition.new(self, name.to_s, start_state, end_states, on_transition, options)) do |t|
53
52
 
54
53
  class_eval %{
55
- def #{name}(user, attributes=nil)
56
- transition('#{name}', user, attributes)
54
+ def #{name}!(user, attributes=nil)
55
+ transition(:#{name}, user, attributes)
57
56
  end
58
57
  def can_#{name}?(user, attributes=nil)
59
- can_transition?('#{name}', user, attributes)
58
+ can_transition?(:#{name}, user)
59
+ end
60
+ def valid_for_#{name}?
61
+ valid_for_transition?(:#{name})
60
62
  end
61
63
  }
62
64
 
@@ -66,28 +68,33 @@ module Hobo
66
68
  def self.state_names
67
69
  states.keys
68
70
  end
69
-
70
-
71
- def self.can_create?(name, user, attributes=nil)
72
- creators[name.to_s].allowed?(user, attributes)
71
+
72
+ def self.publishable_creators
73
+ creators.values.where.publishable?
73
74
  end
74
-
75
-
76
- def self.create(name, user, attributes=nil)
77
- creator = creators[name.to_s]
78
- record = creator.run!(user, attributes)
79
- record.lifecycle.active_step = creator
80
- record
75
+
76
+ def self.publishable_transitions
77
+ transitions.where.publishable?
78
+ end
79
+
80
+ def self.step_names
81
+ (creators.keys | transitions.*.name).uniq
82
+ end
83
+
84
+
85
+ def self.creator(name)
86
+ creators[name.to_sym] or raise ArgumentError, "No such creator in lifecycle: #{name}"
81
87
  end
82
88
 
83
89
 
84
- def self.creator_names
85
- creators.keys
90
+ def self.can_create?(name, user)
91
+ creators[name.to_sym].allowed?(user)
86
92
  end
87
93
 
88
94
 
89
- def self.transition_names
90
- transitions.*.name.uniq
95
+ def self.create(name, user, attributes=nil)
96
+ creator = creators[name.to_sym]
97
+ creator.run!(user, attributes)
91
98
  end
92
99
 
93
100
 
@@ -108,26 +115,33 @@ module Hobo
108
115
  end
109
116
 
110
117
 
111
- def can_transition?(name, user, attributes=nil)
112
- available_transitions_for(user, name, attributes).any?
118
+ def can_transition?(name, user)
119
+ available_transitions_for(user, name).any?
113
120
  end
114
121
 
115
122
 
116
- def transition(name, user, attributes=nil)
117
- transition = find_transition(name, user, attributes)
118
- self.active_step = transition
123
+ def transition(name, user, attributes)
124
+ transition = find_transition(name, user)
119
125
  transition.run!(record, user, attributes)
120
126
  end
121
127
 
122
128
 
123
- def find_transition(name, user, attributes=nil)
124
- available_transitions_for(user, name, attributes).first or
125
- raise LifecycleError, "No #{name} transition available to #{user} on this #{record.class.name}"
129
+ def find_transition(name, user)
130
+ available_transitions_for(user, name).first
131
+ end
132
+
133
+
134
+ def valid_for_transition?(name)
135
+ record.valid?
136
+ callback = :"validate_on_#{name}"
137
+ record.run_callbacks callback
138
+ record.send callback
139
+ record.errors.empty?
126
140
  end
127
141
 
128
142
 
129
143
  def state_name
130
- record.read_attribute self.class.state_field
144
+ record.read_attribute(self.class.state_field).to_sym
131
145
  end
132
146
 
133
147
 
@@ -141,36 +155,38 @@ module Hobo
141
155
  end
142
156
 
143
157
 
144
- def available_transitions_for(user, name=nil, attributes=nil)
158
+ def available_transitions_for(user, name=nil)
159
+ name = name.to_sym if name
145
160
  matches = available_transitions
146
- matches = matches.select { |t| t.name == name.to_s } if name
147
- matches.select { |t| t.allowed?(record, user, attributes) }
161
+ matches = matches.select { |t| t.name == name } if name
162
+ record.with_acting_user(user) do
163
+ matches.select { |t| t.can_run?(record) }
164
+ end
148
165
  end
149
166
 
150
167
 
151
168
  def become(state_name, validate=true)
152
- state_name = state_name.to_s
153
- if self.state != state_name
154
- record.write_attribute self.class.state_field, state_name
155
-
156
- if state_name == "destroy"
157
- record.destroy
169
+ state_name = state_name.to_sym
170
+ record.write_attribute self.class.state_field, state_name.to_s
171
+
172
+ if state_name == :destroy
173
+ record.destroy
174
+ true
175
+ else
176
+ s = self.class.states[state_name]
177
+ raise ArgumentError, "No such state '#{state_name}' for #{record.class.name}" unless s
178
+
179
+ if record.save(validate)
180
+ s.activate! record
181
+ self.active_step = nil # That's the end of this step
158
182
  true
159
183
  else
160
- s = self.class.states[state_name]
161
- raise ArgumentError, "No such state '#{state_name}' for #{record.class.name}" unless s
162
- if record.save(validate)
163
- s.activate! record
164
- self.active_step = nil # That's the end of this step
165
- true
166
- else
167
- false
168
- end
184
+ false
169
185
  end
170
186
  end
171
187
  end
172
-
173
-
188
+
189
+
174
190
  def key_timestamp_field
175
191
  record.class::Lifecycle.options[:key_timestamp_field]
176
192
  end
@@ -205,8 +221,16 @@ module Hobo
205
221
  end
206
222
 
207
223
 
208
- def preconditions_satisfied?
209
- self.class.preconditions.all? { |i| record.instance_eval(&i) }
224
+ def active_step_is?(name)
225
+ active_step && active_step.name == name.to_sym
226
+ end
227
+
228
+ def method_missing(name, *args)
229
+ if name.to_s =~ /^(.*)_in_progress\?$/
230
+ active_step_is?($1)
231
+ else
232
+ super
233
+ end
210
234
  end
211
235
 
212
236
  end