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.
- data/CHANGES.txt +330 -0
- data/Manifest +12 -4
- data/Rakefile +4 -6
- data/dryml_generators/rapid/cards.dryml.erb +5 -1
- data/dryml_generators/rapid/forms.dryml.erb +8 -10
- data/dryml_generators/rapid/pages.dryml.erb +65 -36
- data/hobo.gemspec +28 -15
- data/lib/active_record/association_collection.rb +3 -22
- data/lib/hobo.rb +25 -258
- data/lib/hobo/accessible_associations.rb +131 -0
- data/lib/hobo/authentication_support.rb +15 -9
- data/lib/hobo/composite_model.rb +1 -1
- data/lib/hobo/controller.rb +7 -8
- data/lib/hobo/dryml.rb +9 -10
- data/lib/hobo/dryml/dryml_builder.rb +7 -1
- data/lib/hobo/dryml/dryml_doc.rb +161 -0
- data/lib/hobo/dryml/dryml_generator.rb +18 -9
- data/lib/hobo/dryml/part_context.rb +76 -42
- data/lib/hobo/dryml/tag_parameters.rb +1 -0
- data/lib/hobo/dryml/taglib.rb +2 -1
- data/lib/hobo/dryml/template.rb +39 -29
- data/lib/hobo/dryml/template_environment.rb +79 -37
- data/lib/hobo/dryml/template_handler.rb +66 -21
- data/lib/hobo/guest.rb +2 -10
- data/lib/hobo/hobo_helper.rb +125 -53
- data/lib/hobo/include_in_save.rb +0 -1
- data/lib/hobo/lifecycles.rb +54 -24
- data/lib/hobo/lifecycles/actions.rb +95 -31
- data/lib/hobo/lifecycles/creator.rb +18 -23
- data/lib/hobo/lifecycles/lifecycle.rb +86 -62
- data/lib/hobo/lifecycles/state.rb +1 -2
- data/lib/hobo/lifecycles/transition.rb +22 -28
- data/lib/hobo/model.rb +64 -176
- data/lib/hobo/model_controller.rb +67 -54
- data/lib/hobo/model_router.rb +5 -2
- data/lib/hobo/permissions.rb +397 -0
- data/lib/hobo/permissions/associations.rb +167 -0
- data/lib/hobo/scopes.rb +15 -38
- data/lib/hobo/scopes/association_proxy_extensions.rb +15 -5
- data/lib/hobo/scopes/automatic_scopes.rb +43 -18
- data/lib/hobo/scopes/named_scope_extensions.rb +2 -2
- data/lib/hobo/user.rb +10 -4
- data/lib/hobo/user_controller.rb +6 -5
- data/lib/hobo/view_hints.rb +58 -0
- data/rails_generators/hobo/hobo_generator.rb +7 -3
- data/rails_generators/hobo/templates/guest.rb +1 -13
- data/rails_generators/hobo_front_controller/hobo_front_controller_generator.rb +1 -1
- data/rails_generators/hobo_model/hobo_model_generator.rb +4 -2
- data/rails_generators/hobo_model/templates/hints.rb +4 -0
- data/rails_generators/hobo_model/templates/model.rb +8 -8
- data/rails_generators/hobo_model_controller/hobo_model_controller_generator.rb +10 -0
- data/rails_generators/hobo_model_controller/templates/controller.rb +1 -1
- data/rails_generators/hobo_rapid/templates/hobo-rapid.js +91 -56
- data/rails_generators/hobo_rapid/templates/lowpro.js +15 -15
- data/rails_generators/hobo_rapid/templates/reset.css +36 -3
- data/rails_generators/hobo_rapid/templates/themes/clean/public/stylesheets/clean.css +13 -17
- data/rails_generators/hobo_user_controller/templates/controller.rb +1 -1
- data/rails_generators/hobo_user_model/templates/model.rb +18 -16
- data/taglibs/core.dryml +60 -18
- data/taglibs/rapid.dryml +8 -401
- data/taglibs/rapid_core.dryml +586 -0
- data/taglibs/rapid_document_tags.dryml +28 -10
- data/taglibs/rapid_editing.dryml +92 -55
- data/taglibs/rapid_forms.dryml +406 -87
- data/taglibs/rapid_generics.dryml +1 -1
- data/taglibs/rapid_navigation.dryml +2 -1
- data/taglibs/rapid_pages.dryml +7 -16
- data/taglibs/rapid_plus.dryml +39 -14
- data/taglibs/rapid_support.dryml +1 -1
- data/taglibs/rapid_user_pages.dryml +14 -4
- data/tasks/{generate_tag_reference.rb → generate_tag_reference.rake} +49 -18
- data/tasks/hobo_tasks.rake +16 -0
- data/test/permissions/models/models.rb +134 -0
- data/test/permissions/models/schema.rb +55 -0
- data/test/permissions/models/test.sqlite3 +0 -0
- data/test/permissions/test_permissions.rb +436 -0
- metadata +27 -14
- data/lib/hobo/mass_assignment.rb +0 -64
- data/rails_generators/hobo/templates/patch_routing.rb +0 -30
- 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
|
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 :
|
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
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
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
|
-
|
58
|
+
case hook
|
59
|
+
when Symbol
|
32
60
|
record.send(hook, *args)
|
33
|
-
|
34
|
-
|
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
|
-
|
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
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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,
|
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
|
-
|
99
|
+
apply_user_becomes!(record)
|
60
100
|
end
|
61
101
|
|
62
102
|
|
63
|
-
def
|
64
|
-
|
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
|
-
|
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, :
|
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
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
60
|
-
|
61
|
-
|
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, :
|
23
|
-
:creators, :transitions, :invariants
|
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.
|
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 ==
|
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,
|
36
|
-
name = name.
|
37
|
-
returning(Creator.new(self, name,
|
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(
|
40
|
+
create(:#{name}, user, attributes)
|
42
41
|
end
|
43
42
|
def self.can_#{name}?(user, attributes=nil)
|
44
|
-
can_create?(
|
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,
|
52
|
-
returning(Transition.new(self, name.to_s,
|
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(
|
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?(
|
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
|
-
|
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
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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.
|
85
|
-
creators.
|
90
|
+
def self.can_create?(name, user)
|
91
|
+
creators[name.to_sym].allowed?(user)
|
86
92
|
end
|
87
93
|
|
88
94
|
|
89
|
-
def self.
|
90
|
-
|
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
|
112
|
-
available_transitions_for(user, name
|
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
|
117
|
-
transition = find_transition(name, user
|
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
|
124
|
-
available_transitions_for(user, name
|
125
|
-
|
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
|
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
|
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
|
147
|
-
|
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.
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
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
|
-
|
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
|
209
|
-
|
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
|