acl9 0.11.0
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/CHANGELOG.textile +32 -0
- data/MIT-LICENSE +20 -0
- data/README.textile +888 -0
- data/Rakefile +40 -0
- data/TODO +42 -0
- data/VERSION.yml +4 -0
- data/lib/acl9.rb +16 -0
- data/lib/acl9/config.rb +10 -0
- data/lib/acl9/controller_extensions.rb +85 -0
- data/lib/acl9/controller_extensions/dsl_base.rb +229 -0
- data/lib/acl9/controller_extensions/generators.rb +197 -0
- data/lib/acl9/helpers.rb +19 -0
- data/lib/acl9/model_extensions.rb +133 -0
- data/lib/acl9/model_extensions/object.rb +59 -0
- data/lib/acl9/model_extensions/subject.rb +175 -0
- data/test/access_control_test.rb +338 -0
- data/test/dsl_base_test.rb +758 -0
- data/test/helpers_test.rb +93 -0
- data/test/roles_test.rb +310 -0
- data/test/support/controllers.rb +207 -0
- data/test/support/models.rb +47 -0
- data/test/support/schema.rb +69 -0
- data/test/test_helper.rb +31 -0
- metadata +103 -0
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
desc 'Default: run tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
begin
|
9
|
+
require 'jeweler'
|
10
|
+
Jeweler::Tasks.new do |s|
|
11
|
+
s.name = "acl9"
|
12
|
+
s.summary = "Yet another role-based authorization system for Rails"
|
13
|
+
s.email = "olegdashevskii@gmail.com"
|
14
|
+
s.homepage = "http://github.com/be9/acl9"
|
15
|
+
s.description = "Role-based authorization system for Rails with a nice DSL for access control lists"
|
16
|
+
s.authors = ["oleg dashevskii"]
|
17
|
+
s.files = FileList["[A-Z]*", "{lib,test}/**/*.rb"]
|
18
|
+
s.add_development_dependency "jeremymcanally-context", ">= 0.5.5"
|
19
|
+
s.add_development_dependency "jnunemaker-matchy", ">= 0.4.0"
|
20
|
+
end
|
21
|
+
Jeweler::GemcutterTasks.new
|
22
|
+
rescue LoadError
|
23
|
+
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
24
|
+
end
|
25
|
+
|
26
|
+
Rake::TestTask.new(:test) do |test|
|
27
|
+
test.libs << 'lib' << 'test'
|
28
|
+
test.pattern = 'test/**/*_test.rb'
|
29
|
+
test.verbose = false
|
30
|
+
end
|
31
|
+
|
32
|
+
begin
|
33
|
+
require 'yard'
|
34
|
+
|
35
|
+
YARD::Rake::YardocTask.new do |t|
|
36
|
+
t.files = ['lib/**/*.rb']
|
37
|
+
#t.options = ['--any', '--extra', '--opts'] # optional
|
38
|
+
end
|
39
|
+
rescue LoadError
|
40
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
* Complex roles with ANDing.
|
2
|
+
|
3
|
+
Something like:
|
4
|
+
|
5
|
+
access_control do
|
6
|
+
allow all, :except => :destroy
|
7
|
+
allow complex_role, :to => :destroy do
|
8
|
+
is :strong
|
9
|
+
is :decisive
|
10
|
+
is :owner, :of => :object
|
11
|
+
is_not :banned
|
12
|
+
is_not :fake
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
* Acl9-based menu generator.
|
17
|
+
|
18
|
+
If you get Access Denied on /secrets/index, probably you shouldn't see "Secrets" item
|
19
|
+
in the menu at all.
|
20
|
+
|
21
|
+
It can be very DRY. Say, we introduce :menu => true option to access_control method which
|
22
|
+
will make it register a lambda (can see/cannot see) in some global hash (indexed by controller name).
|
23
|
+
|
24
|
+
Then, given an URL, you'll be able to check it against this hash. /secrets/index is mapped to
|
25
|
+
SecretsController#index, so you run access_control_hash['SecretsController'].call('index') and
|
26
|
+
show the link only if true is returned.
|
27
|
+
|
28
|
+
The problem here is with objects. SecretsController's access_control block can reference instance
|
29
|
+
variables during the permission check, but we have only current instantiated controller which can be any.
|
30
|
+
|
31
|
+
Another option is to distinguish visible part from access control part.
|
32
|
+
|
33
|
+
menu do
|
34
|
+
item 'Home', home_path
|
35
|
+
item 'Secrets', secrets_path do
|
36
|
+
allow :trusted
|
37
|
+
end
|
38
|
+
|
39
|
+
# ...
|
40
|
+
end
|
41
|
+
|
42
|
+
Here only "trusted" users will see "Secrets" item.
|
data/VERSION.yml
ADDED
data/lib/acl9.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'acl9', 'config')
|
2
|
+
|
3
|
+
if defined? ActiveRecord::Base
|
4
|
+
require File.join(File.dirname(__FILE__), 'acl9', 'model_extensions')
|
5
|
+
|
6
|
+
ActiveRecord::Base.send(:include, Acl9::ModelExtensions)
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
if defined? ActionController::Base
|
11
|
+
require File.join(File.dirname(__FILE__), 'acl9', 'controller_extensions')
|
12
|
+
require File.join(File.dirname(__FILE__), 'acl9', 'helpers')
|
13
|
+
|
14
|
+
ActionController::Base.send(:include, Acl9::ControllerExtensions)
|
15
|
+
Acl9Helpers = Acl9::Helpers unless defined?(Acl9Helpers)
|
16
|
+
end
|
data/lib/acl9/config.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'controller_extensions', 'generators')
|
2
|
+
|
3
|
+
module Acl9
|
4
|
+
module ControllerExtensions
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def access_control(*args, &block)
|
11
|
+
opts = args.extract_options!
|
12
|
+
|
13
|
+
case args.size
|
14
|
+
when 0 then true
|
15
|
+
when 1
|
16
|
+
meth = args.first
|
17
|
+
|
18
|
+
if meth.is_a? Symbol
|
19
|
+
opts[:as_method] = meth
|
20
|
+
else
|
21
|
+
raise ArgumentError, "access_control argument must be a :symbol!"
|
22
|
+
end
|
23
|
+
else
|
24
|
+
raise ArgumentError, "Invalid arguments for access_control"
|
25
|
+
end
|
26
|
+
|
27
|
+
subject_method = opts[:subject_method] || Acl9::config[:default_subject_method]
|
28
|
+
|
29
|
+
raise ArgumentError, "Block must be supplied to access_control" unless block
|
30
|
+
|
31
|
+
filter = opts[:filter]
|
32
|
+
filter = true if filter.nil?
|
33
|
+
|
34
|
+
case helper = opts[:helper]
|
35
|
+
when true
|
36
|
+
raise ArgumentError, "you should specify :helper => :method_name" if !opts[:as_method]
|
37
|
+
when nil then nil
|
38
|
+
else
|
39
|
+
if opts[:as_method]
|
40
|
+
raise ArgumentError, "you can't specify both method name and helper name"
|
41
|
+
else
|
42
|
+
opts[:as_method] = helper
|
43
|
+
filter = false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
method = opts[:as_method]
|
48
|
+
|
49
|
+
query_method_available = true
|
50
|
+
generator = case
|
51
|
+
when method && filter
|
52
|
+
Acl9::Dsl::Generators::FilterMethod.new(subject_method, method)
|
53
|
+
when method && !filter
|
54
|
+
query_method_available = false
|
55
|
+
Acl9::Dsl::Generators::BooleanMethod.new(subject_method, method)
|
56
|
+
else
|
57
|
+
Acl9::Dsl::Generators::FilterLambda.new(subject_method)
|
58
|
+
end
|
59
|
+
|
60
|
+
generator.acl_block!(&block)
|
61
|
+
|
62
|
+
generator.install_on(self, opts)
|
63
|
+
|
64
|
+
if query_method_available && (query_method = opts.delete(:query_method))
|
65
|
+
case query_method
|
66
|
+
when true
|
67
|
+
if method
|
68
|
+
query_method = "#{method}?"
|
69
|
+
else
|
70
|
+
raise ArgumentError, "You must specify :query_method as Symbol"
|
71
|
+
end
|
72
|
+
when Symbol, String
|
73
|
+
# okay here
|
74
|
+
else
|
75
|
+
raise ArgumentError, "Invalid value for :query_method"
|
76
|
+
end
|
77
|
+
|
78
|
+
second_generator = Acl9::Dsl::Generators::BooleanMethod.new(subject_method, query_method)
|
79
|
+
second_generator.acl_block!(&block)
|
80
|
+
second_generator.install_on(self, opts)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
module Acl9
|
2
|
+
module Dsl
|
3
|
+
class Base
|
4
|
+
attr_reader :allows, :denys
|
5
|
+
|
6
|
+
def initialize(*args)
|
7
|
+
@default_action = nil
|
8
|
+
|
9
|
+
@allows = []
|
10
|
+
@denys = []
|
11
|
+
|
12
|
+
@original_args = args
|
13
|
+
end
|
14
|
+
|
15
|
+
def acl_block!(&acl_block)
|
16
|
+
instance_eval(&acl_block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def default_action
|
20
|
+
if @default_action.nil? then :deny else @default_action end
|
21
|
+
end
|
22
|
+
|
23
|
+
def allowance_expression
|
24
|
+
allowed_expr = if @allows.size > 0
|
25
|
+
@allows.map { |clause| "(#{clause})" }.join(' || ')
|
26
|
+
else
|
27
|
+
"false"
|
28
|
+
end
|
29
|
+
|
30
|
+
not_denied_expr = if @denys.size > 0
|
31
|
+
@denys.map { |clause| "!(#{clause})" }.join(' && ')
|
32
|
+
else
|
33
|
+
"true"
|
34
|
+
end
|
35
|
+
|
36
|
+
[allowed_expr, not_denied_expr].
|
37
|
+
map { |expr| "(#{expr})" }.
|
38
|
+
join(default_action == :deny ? ' && ' : ' || ')
|
39
|
+
end
|
40
|
+
|
41
|
+
alias to_s allowance_expression
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
def default(default_action)
|
46
|
+
raise ArgumentError, "default can only be called once in access_control block" if @default_action
|
47
|
+
|
48
|
+
unless [:allow, :deny].include? default_action
|
49
|
+
raise ArgumentError, "invalid value for default (can be :allow or :deny)"
|
50
|
+
end
|
51
|
+
|
52
|
+
@default_action = default_action
|
53
|
+
end
|
54
|
+
|
55
|
+
def allow(*args)
|
56
|
+
@current_rule = :allow
|
57
|
+
_parse_and_add_rule(*args)
|
58
|
+
end
|
59
|
+
|
60
|
+
def deny(*args)
|
61
|
+
@current_rule = :deny
|
62
|
+
_parse_and_add_rule(*args)
|
63
|
+
end
|
64
|
+
|
65
|
+
def actions(*args, &block)
|
66
|
+
raise ArgumentError, "actions should receive at least 1 action as argument" if args.size < 1
|
67
|
+
|
68
|
+
subsidiary = self.class.new(*@original_args)
|
69
|
+
|
70
|
+
class <<subsidiary
|
71
|
+
def actions(*args)
|
72
|
+
raise ArgumentError, "You cannot use actions inside another actions block"
|
73
|
+
end
|
74
|
+
|
75
|
+
def default(*args)
|
76
|
+
raise ArgumentError, "You cannot use default inside an actions block"
|
77
|
+
end
|
78
|
+
|
79
|
+
def _set_action_clause(to, except)
|
80
|
+
raise ArgumentError, "You cannot use :to/:except inside actions block" if to || except
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
subsidiary.acl_block!(&block)
|
85
|
+
|
86
|
+
action_check = _action_check_expression(args)
|
87
|
+
|
88
|
+
squash = lambda do |rules|
|
89
|
+
_either_of(rules) + ' && ' + action_check
|
90
|
+
end
|
91
|
+
|
92
|
+
@allows << squash.call(subsidiary.allows) if subsidiary.allows.size > 0
|
93
|
+
@denys << squash.call(subsidiary.denys) if subsidiary.denys.size > 0
|
94
|
+
end
|
95
|
+
|
96
|
+
alias action actions
|
97
|
+
|
98
|
+
def logged_in; false end
|
99
|
+
def anonymous; nil end
|
100
|
+
def all; true end
|
101
|
+
|
102
|
+
alias everyone all
|
103
|
+
alias everybody all
|
104
|
+
alias anyone all
|
105
|
+
|
106
|
+
def _parse_and_add_rule(*args)
|
107
|
+
options = args.extract_options!
|
108
|
+
|
109
|
+
_set_action_clause(options.delete(:to), options.delete(:except))
|
110
|
+
|
111
|
+
object = _role_object(options)
|
112
|
+
|
113
|
+
role_checks = args.map do |who|
|
114
|
+
case who
|
115
|
+
when anonymous() then "#{_subject_ref}.nil?"
|
116
|
+
when logged_in() then "!#{_subject_ref}.nil?"
|
117
|
+
when all() then "true"
|
118
|
+
else
|
119
|
+
"!#{_subject_ref}.nil? && #{_subject_ref}.has_role?('#{who.to_s.singularize}', #{object})"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
[:if, :unless].each do |cond|
|
124
|
+
val = options[cond]
|
125
|
+
raise ArgumentError, "#{cond} option must be a Symbol" if val && !val.is_a?(Symbol)
|
126
|
+
end
|
127
|
+
|
128
|
+
condition = [
|
129
|
+
(_method_ref(options[:if]) if options[:if]),
|
130
|
+
("!#{_method_ref(options[:unless])}" if options[:unless])
|
131
|
+
].compact.join(' && ')
|
132
|
+
|
133
|
+
condition = nil if condition.blank?
|
134
|
+
|
135
|
+
_add_rule(case role_checks.size
|
136
|
+
when 0
|
137
|
+
raise ArgumentError, "allow/deny should have at least 1 argument"
|
138
|
+
when 1 then role_checks.first
|
139
|
+
else
|
140
|
+
_either_of(role_checks)
|
141
|
+
end, condition)
|
142
|
+
end
|
143
|
+
|
144
|
+
def _either_of(exprs)
|
145
|
+
exprs.map { |expr| "(#{expr})" }.join(' || ')
|
146
|
+
end
|
147
|
+
|
148
|
+
def _add_rule(what, condition)
|
149
|
+
anded = [what] + [@action_clause, condition].compact
|
150
|
+
anded[0] = "(#{anded[0]})" if anded.size > 1
|
151
|
+
|
152
|
+
(@current_rule == :allow ? @allows : @denys) << anded.join(' && ')
|
153
|
+
end
|
154
|
+
|
155
|
+
def _set_action_clause(to, except)
|
156
|
+
raise ArgumentError, "both :to and :except cannot be specified in the rule" if to && except
|
157
|
+
|
158
|
+
@action_clause = nil
|
159
|
+
|
160
|
+
action_list = to || except
|
161
|
+
return unless action_list
|
162
|
+
|
163
|
+
expr = _action_check_expression(action_list)
|
164
|
+
|
165
|
+
@action_clause = if to
|
166
|
+
"#{expr}"
|
167
|
+
else
|
168
|
+
"!#{expr}"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def _action_check_expression(action_list)
|
173
|
+
unless action_list.is_a?(Array)
|
174
|
+
action_list = [ action_list.to_s ]
|
175
|
+
end
|
176
|
+
|
177
|
+
case action_list.size
|
178
|
+
when 0 then "true"
|
179
|
+
when 1 then "(#{_action_ref} == '#{action_list.first}')"
|
180
|
+
else
|
181
|
+
set_of_actions = "Set.new([" + action_list.map { |act| "'#{act}'"}.join(',') + "])"
|
182
|
+
|
183
|
+
"#{set_of_actions}.include?(#{_action_ref})"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
VALID_PREPOSITIONS = %w(of for in on at by).freeze unless defined? VALID_PREPOSITIONS
|
188
|
+
|
189
|
+
def _role_object(options)
|
190
|
+
object = nil
|
191
|
+
|
192
|
+
VALID_PREPOSITIONS.each do |prep|
|
193
|
+
if options[prep.to_sym]
|
194
|
+
raise ArgumentError, "You may only use one preposition to specify object" if object
|
195
|
+
|
196
|
+
object = options[prep.to_sym]
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
case object
|
201
|
+
when Class
|
202
|
+
object.to_s
|
203
|
+
when Symbol
|
204
|
+
_object_ref object
|
205
|
+
when nil
|
206
|
+
"nil"
|
207
|
+
else
|
208
|
+
raise ArgumentError, "object specified by preposition can only be a Class or a Symbol"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def _subject_ref
|
213
|
+
raise
|
214
|
+
end
|
215
|
+
|
216
|
+
def _object_ref(object)
|
217
|
+
raise
|
218
|
+
end
|
219
|
+
|
220
|
+
def _action_ref
|
221
|
+
raise
|
222
|
+
end
|
223
|
+
|
224
|
+
def _method_ref(method)
|
225
|
+
raise
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'dsl_base')
|
2
|
+
|
3
|
+
module Acl9
|
4
|
+
##
|
5
|
+
# This exception is raised whenever ACL block finds that the current user
|
6
|
+
# is not authorized for the controller action he wants to execute.
|
7
|
+
# @example How to catch this exception in ApplicationController
|
8
|
+
# class ApplicationController < ActionController::Base
|
9
|
+
# rescue_from 'Acl9::AccessDenied', :with => :access_denied
|
10
|
+
#
|
11
|
+
# # ...other stuff...
|
12
|
+
# private
|
13
|
+
#
|
14
|
+
# def access_denied
|
15
|
+
# if current_user
|
16
|
+
# # It's presumed you have a template with words of pity and regret
|
17
|
+
# # for unhappy user who is not authorized to do what he wanted
|
18
|
+
# render :template => 'home/access_denied'
|
19
|
+
# else
|
20
|
+
# # In this case user has not even logged in. Might be OK after login.
|
21
|
+
# flash[:notice] = 'Access denied. Try to log in first.'
|
22
|
+
# redirect_to login_path
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
class AccessDenied < StandardError; end
|
28
|
+
|
29
|
+
##
|
30
|
+
# This exception is raised when acl9 has generated invalid code for the
|
31
|
+
# filtering method or block. Should never happen, and it's a bug when it
|
32
|
+
# happens.
|
33
|
+
class FilterSyntaxError < StandardError; end
|
34
|
+
|
35
|
+
module Dsl
|
36
|
+
module Generators
|
37
|
+
class BaseGenerator < Acl9::Dsl::Base
|
38
|
+
def initialize(*args)
|
39
|
+
@subject_method = args[0]
|
40
|
+
|
41
|
+
super
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def _access_denied
|
47
|
+
"raise Acl9::AccessDenied"
|
48
|
+
end
|
49
|
+
|
50
|
+
def _subject_ref
|
51
|
+
"#{_controller_ref}send(:#{@subject_method})"
|
52
|
+
end
|
53
|
+
|
54
|
+
def _object_ref(object)
|
55
|
+
"#{_controller_ref}instance_variable_get('@#{object}')"
|
56
|
+
end
|
57
|
+
|
58
|
+
def _action_ref
|
59
|
+
"#{_controller_ref}action_name"
|
60
|
+
end
|
61
|
+
|
62
|
+
def _method_ref(method)
|
63
|
+
"#{_controller_ref}send(:#{method})"
|
64
|
+
end
|
65
|
+
|
66
|
+
def _controller_ref
|
67
|
+
@controller ? "#{@controller}." : ''
|
68
|
+
end
|
69
|
+
|
70
|
+
def install_on(controller_class, options)
|
71
|
+
debug_dump(controller_class) if options[:debug]
|
72
|
+
end
|
73
|
+
|
74
|
+
def debug_dump(klass)
|
75
|
+
return unless logger
|
76
|
+
logger.debug "=== Acl9 access_control expression dump (#{klass.to_s})"
|
77
|
+
logger.debug self.to_s
|
78
|
+
logger.debug "======"
|
79
|
+
end
|
80
|
+
|
81
|
+
def logger
|
82
|
+
ActionController::Base.logger
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class FilterLambda < BaseGenerator
|
87
|
+
def initialize(subject_method)
|
88
|
+
super
|
89
|
+
|
90
|
+
@controller = 'controller'
|
91
|
+
end
|
92
|
+
|
93
|
+
def install_on(controller_class, options)
|
94
|
+
super
|
95
|
+
|
96
|
+
controller_class.send(:before_filter, options, &self.to_proc)
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_proc
|
100
|
+
code = <<-RUBY
|
101
|
+
lambda do |controller|
|
102
|
+
unless #{allowance_expression}
|
103
|
+
#{_access_denied}
|
104
|
+
end
|
105
|
+
end
|
106
|
+
RUBY
|
107
|
+
|
108
|
+
self.instance_eval(code, __FILE__, __LINE__)
|
109
|
+
rescue SyntaxError
|
110
|
+
raise FilterSyntaxError, code
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
################################################################
|
115
|
+
|
116
|
+
class FilterMethod < BaseGenerator
|
117
|
+
def initialize(subject_method, method_name)
|
118
|
+
super
|
119
|
+
|
120
|
+
@method_name = method_name
|
121
|
+
@controller = nil
|
122
|
+
end
|
123
|
+
|
124
|
+
def install_on(controller_class, options)
|
125
|
+
super
|
126
|
+
_add_method(controller_class)
|
127
|
+
controller_class.send(:before_filter, @method_name, options)
|
128
|
+
end
|
129
|
+
|
130
|
+
protected
|
131
|
+
|
132
|
+
def _add_method(controller_class)
|
133
|
+
code = self.to_method_code
|
134
|
+
controller_class.send(:class_eval, code, __FILE__, __LINE__)
|
135
|
+
rescue SyntaxError
|
136
|
+
raise FilterSyntaxError, code
|
137
|
+
end
|
138
|
+
|
139
|
+
def to_method_code
|
140
|
+
<<-RUBY
|
141
|
+
def #{@method_name}
|
142
|
+
unless #{allowance_expression}
|
143
|
+
#{_access_denied}
|
144
|
+
end
|
145
|
+
end
|
146
|
+
RUBY
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
################################################################
|
151
|
+
|
152
|
+
class BooleanMethod < FilterMethod
|
153
|
+
def install_on(controller_class, opts)
|
154
|
+
debug_dump(controller_class) if opts[:debug]
|
155
|
+
|
156
|
+
_add_method(controller_class)
|
157
|
+
|
158
|
+
if opts[:helper]
|
159
|
+
controller_class.send(:helper_method, @method_name)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
protected
|
164
|
+
|
165
|
+
def to_method_code
|
166
|
+
<<-RUBY
|
167
|
+
def #{@method_name}(*args)
|
168
|
+
options = args.extract_options!
|
169
|
+
|
170
|
+
unless args.size <= 1
|
171
|
+
raise ArgumentError, "call #{@method_name} with 0, 1 or 2 arguments"
|
172
|
+
end
|
173
|
+
|
174
|
+
action_name = args.empty? ? self.action_name : args.first.to_s
|
175
|
+
|
176
|
+
return #{allowance_expression}
|
177
|
+
end
|
178
|
+
RUBY
|
179
|
+
end
|
180
|
+
|
181
|
+
def _object_ref(object)
|
182
|
+
"(options[:#{object}] || #{super})"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
################################################################
|
187
|
+
|
188
|
+
class HelperMethod < BooleanMethod
|
189
|
+
def initialize(subject_method, method)
|
190
|
+
super
|
191
|
+
|
192
|
+
@controller = 'controller'
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|