maiha-scoped_access 0.1
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/README +4 -0
- data/Rakefile +22 -0
- data/init.rb +42 -0
- data/install.rb +1 -0
- data/lib/scoped_access.rb +197 -0
- data/tasks/scoped_access_tasks.rake +4 -0
- data/test/database.yml +22 -0
- data/test/filter_test.rb +143 -0
- data/test/fixtures/favorite.rb +4 -0
- data/test/fixtures/favorites.yml +17 -0
- data/test/fixtures/group.rb +4 -0
- data/test/fixtures/groups.yml +13 -0
- data/test/fixtures/member.rb +5 -0
- data/test/fixtures/members.yml +32 -0
- data/test/fixtures/song.rb +4 -0
- data/test/method_scoping_test.rb +87 -0
- data/test/schema.rb +20 -0
- data/test/scoped_access_test.rb +247 -0
- data/test/sql_condition_test.rb +78 -0
- data/test/test_helper.rb +39 -0
- metadata +72 -0
data/README
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the scoped_access plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Generate documentation for the scoped_access plugin.'
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'ScopedAccess'
|
19
|
+
rdoc.options << '--line-numbers --inline-source'
|
20
|
+
rdoc.rdoc_files.include('README')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
ActionController::Base.instance_eval do
|
2
|
+
def scoped_access (*args, &block)
|
3
|
+
options = (Hash === args.last && !(args.last.keys & [:only, :except]).empty?) ? args.pop : {}
|
4
|
+
send(:around_filter, ScopedAccess::Filter.new(*args, &block), options)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'dispatcher'
|
9
|
+
if ActionController.const_defined?(:Dispatcher)
|
10
|
+
# Rails2.1
|
11
|
+
ActionController::Dispatcher.class_eval do
|
12
|
+
class << self
|
13
|
+
def define_dispatcher_callbacks_with_reset(cache_classes)
|
14
|
+
define_dispatcher_callbacks_without_reset(cache_classes)
|
15
|
+
ScopedAccess.reset
|
16
|
+
end
|
17
|
+
alias_method_chain :define_dispatcher_callbacks, :reset
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
else
|
22
|
+
# Rails1.2 or Rails2.0
|
23
|
+
class ::Dispatcher
|
24
|
+
app = respond_to?(:prepare_application, true) ? (class << self; self end) : self
|
25
|
+
app.class_eval do
|
26
|
+
private
|
27
|
+
def prepare_application_with_reset
|
28
|
+
ScopedAccess.reset
|
29
|
+
prepare_application_without_reset
|
30
|
+
end
|
31
|
+
|
32
|
+
alias_method :prepare_application_without_reset, :prepare_application
|
33
|
+
alias_method :prepare_application, :prepare_application_with_reset
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
ActiveRecord::Base.instance_eval do
|
39
|
+
def reset_scope
|
40
|
+
scoped_methods.clear
|
41
|
+
end
|
42
|
+
end
|
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
@@ -0,0 +1,197 @@
|
|
1
|
+
module ScopedAccess
|
2
|
+
class SqlCondition
|
3
|
+
attr_reader :attributes, :constrains
|
4
|
+
|
5
|
+
Constrain = Struct.new(:statement, :parameters)
|
6
|
+
|
7
|
+
def initialize (attributes = nil)
|
8
|
+
if attributes.is_a?(SqlCondition)
|
9
|
+
constrains = attributes.constrains.dup
|
10
|
+
attributes = attributes.attributes
|
11
|
+
end
|
12
|
+
|
13
|
+
@attributes = HashWithIndifferentAccess.new(attributes ? attributes.dup : {})
|
14
|
+
@constrains = constrains || []
|
15
|
+
@cached = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def add (statement, *parameters)
|
19
|
+
@constrains << Constrain.new(normalize_statement(statement), parameters)
|
20
|
+
updated
|
21
|
+
end
|
22
|
+
|
23
|
+
def []= (key, val)
|
24
|
+
@attributes[key] = val
|
25
|
+
updated
|
26
|
+
end
|
27
|
+
|
28
|
+
def generate
|
29
|
+
@cached ||= parse
|
30
|
+
end
|
31
|
+
|
32
|
+
def + (other)
|
33
|
+
other.attributes.to_a.inject(self.class.new(@attributes)){|obj, (key, val)| obj[key] = val; obj}
|
34
|
+
end
|
35
|
+
|
36
|
+
def empty?
|
37
|
+
@attributes.empty? && @constrains.empty?
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
def normalize_column_name (column_name)
|
42
|
+
column_name
|
43
|
+
end
|
44
|
+
|
45
|
+
def normalize_statement (statement)
|
46
|
+
# statement.sub(/\A\s*\(\s*(.*?)\s*\)\s*\Z/) {$2} # strip parentheses
|
47
|
+
# this is buggy for the case: "(date1 <= ?) AND (date2 BETWEEN ? AND ?)"
|
48
|
+
statement
|
49
|
+
end
|
50
|
+
|
51
|
+
def updated
|
52
|
+
@cached = nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def parse
|
56
|
+
constrains = @attributes.keys.sort.inject(@constrains.dup) {|array, key| array << parse_attribute(key, @attributes[key])}
|
57
|
+
statements = constrains.collect{|constrain| "( %s )" % constrain.statement}
|
58
|
+
parameters = constrains.inject([]){|array, constrain| array += constrain.parameters unless constrain.parameters.empty?; array}
|
59
|
+
[statements.join(" AND ")] + parameters
|
60
|
+
end
|
61
|
+
|
62
|
+
def parse_attribute (column_name, value)
|
63
|
+
column_name = normalize_column_name(column_name)
|
64
|
+
if value.nil?
|
65
|
+
Constrain.new("#{column_name} is NULL", [])
|
66
|
+
elsif value.is_a? Array
|
67
|
+
Constrain.new("#{column_name} in (?)", value)
|
68
|
+
else
|
69
|
+
Constrain.new("#{column_name} = ?", [value])
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class MethodScoping < SqlCondition
|
75
|
+
attr_accessor :find_options, :options
|
76
|
+
|
77
|
+
def initialize (attributes = nil, options = {}, finder_options = {})
|
78
|
+
super(attributes)
|
79
|
+
@options = HashWithIndifferentAccess.new(options || {})
|
80
|
+
@find_options = finder_options || {}
|
81
|
+
end
|
82
|
+
|
83
|
+
def method_scoping
|
84
|
+
constrains = {
|
85
|
+
:find => construct_finder,
|
86
|
+
:create => @attributes,
|
87
|
+
}
|
88
|
+
constrains.delete(:find) if constrains[:find] == {:conditions => [""]}
|
89
|
+
return constrains
|
90
|
+
end
|
91
|
+
|
92
|
+
protected
|
93
|
+
def construct_finder
|
94
|
+
(generate == [""]) ? @find_options : @find_options.merge(:conditions => generate)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class ClassScoping < MethodScoping
|
99
|
+
def initialize (klass, *args)
|
100
|
+
(@klass = klass).is_a?(Class) or
|
101
|
+
raise ArgumentError, "A subclass of ActiveRecord::Base is required for '#{klass.class}'"
|
102
|
+
super(*args)
|
103
|
+
end
|
104
|
+
|
105
|
+
protected
|
106
|
+
def normalize_column_name (column_name)
|
107
|
+
"#{ @klass.table_name }.#{ column_name }"
|
108
|
+
end
|
109
|
+
|
110
|
+
def column_name_regexp_string
|
111
|
+
@column_name_regexp_string ||= "(%s)" % @klass.column_names.join('|')
|
112
|
+
end
|
113
|
+
|
114
|
+
def normalize_statement (statement)
|
115
|
+
super.sub(/\A#{column_name_regexp_string}(\s*=|\s+(is|in)\s+)/mi) {
|
116
|
+
"%s%s" % [normalize_column_name($1), $2]
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
class Filter
|
123
|
+
@applied_classes = Set.new
|
124
|
+
class << self
|
125
|
+
def reset
|
126
|
+
ActiveRecord::Base.logger.debug("ScopedAccess: reset %s" % @applied_classes.to_a.inspect)
|
127
|
+
@applied_classes.each(&:reset_scope)
|
128
|
+
@applied_classes.clear
|
129
|
+
end
|
130
|
+
|
131
|
+
def mark(klass)
|
132
|
+
@applied_classes << klass
|
133
|
+
ActiveRecord::Base.logger.debug("ScopedAccess: mark %s" % klass)
|
134
|
+
end
|
135
|
+
|
136
|
+
def unmark(klass)
|
137
|
+
@applied_classes.delete(klass)
|
138
|
+
ActiveRecord::Base.logger.debug("ScopedAccess: unmark %s" % klass)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def initialize (klass, scoping = :method_scoping, &block)
|
143
|
+
@klass = klass
|
144
|
+
@scoping = block || scoping
|
145
|
+
end
|
146
|
+
|
147
|
+
def before (controller)
|
148
|
+
constrain = self.class.generate_constrain(@klass, @scoping, :table_name=>@klass.table_name, :controller=>controller)
|
149
|
+
@klass.logger.debug("ScopedAccessFilter#before (called from %s):\n\t[%s] scope becomes %s" %
|
150
|
+
[controller.class, @klass, constrain.inspect])
|
151
|
+
@klass.instance_eval do
|
152
|
+
Filter.mark(self)
|
153
|
+
scoped_methods << with_scope(constrain){current_scoped_methods}
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def after (controller)
|
158
|
+
@klass.instance_eval do
|
159
|
+
scoped_methods.pop
|
160
|
+
end
|
161
|
+
@klass.logger.debug("ScopedAccess::Filter#after (called from %s):\n\t[%s] scope is restored to %s" %
|
162
|
+
[controller.class, @klass, @klass.send(:scoped_methods).inspect])
|
163
|
+
end
|
164
|
+
|
165
|
+
class << self
|
166
|
+
def generate_constrain (klass, scoping, scoping_options = {})
|
167
|
+
case scoping
|
168
|
+
when Symbol
|
169
|
+
controller = scoping_options[:controller] or raise RuntimeError, "[plugin bug???] missing controller"
|
170
|
+
method_scoping = controller.__send__(scoping)
|
171
|
+
raise RuntimeError, "generate_constrain got infinite loop!" if method_scoping.is_a?(Symbol)
|
172
|
+
return generate_constrain(klass, method_scoping, scoping_options)
|
173
|
+
when Proc
|
174
|
+
method_scoping = scoping.call(scoping_options[:controller])
|
175
|
+
raise RuntimeError, "generate_constrain got infinite loop!" if method_scoping.is_a?(Proc)
|
176
|
+
return generate_constrain(klass, method_scoping, scoping_options)
|
177
|
+
when Hash, ClassScoping
|
178
|
+
return scoping
|
179
|
+
when SqlCondition
|
180
|
+
method_scoping = ClassScoping.new(klass, scoping, scoping_options.reject{|k,| k==:controller})
|
181
|
+
return generate_constrain(klass, method_scoping)
|
182
|
+
when Array, String
|
183
|
+
method_scoping = ClassScoping.new(klass, {}, scoping_options.reject{|k,| k==:controller})
|
184
|
+
method_scoping.add(*scoping)
|
185
|
+
return generate_constrain(klass, method_scoping)
|
186
|
+
else
|
187
|
+
raise TypeError, "cannot generate constrain from this type: (%s)" % scoping.class
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
module_function
|
194
|
+
def reset
|
195
|
+
Filter.reset
|
196
|
+
end
|
197
|
+
end
|
data/test/database.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
sqlite:
|
2
|
+
:adapter: sqlite
|
3
|
+
:dbfile: scoped_access_plugin_test.sqlite.db
|
4
|
+
|
5
|
+
sqlite3:
|
6
|
+
:adapter: sqlite3
|
7
|
+
:dbfile: scoped_access_plugin_test.sqlite3.db
|
8
|
+
|
9
|
+
postgresql:
|
10
|
+
:adapter: postgresql
|
11
|
+
:username: postgres
|
12
|
+
:password: postgres
|
13
|
+
:database: scoped_access_plugin_test
|
14
|
+
:min_messages: ERROR
|
15
|
+
|
16
|
+
mysql:
|
17
|
+
:adapter: mysql
|
18
|
+
:host: localhost
|
19
|
+
:username: rails
|
20
|
+
:password:
|
21
|
+
:database: scoped_access_plugin_test
|
22
|
+
|
data/test/filter_test.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
require __DIR__ + '/fixtures/member'
|
3
|
+
|
4
|
+
class FilterGenerateConstrainTest < Test::Unit::TestCase
|
5
|
+
def test_generate_constrain_from_hash
|
6
|
+
scoping = Filter.generate_constrain(Member, {:find=>{:limit=>10}})
|
7
|
+
expected = {
|
8
|
+
:find => { :limit => 10 }
|
9
|
+
}
|
10
|
+
assert_equal expected, scoping
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_generate_constrain_from_string
|
14
|
+
scoping = Filter.generate_constrain(Member, 'name = "Maiha"').method_scoping
|
15
|
+
expected = {
|
16
|
+
:find => { :conditions => ['( members.name = "Maiha" )'] },
|
17
|
+
:create => { },
|
18
|
+
}
|
19
|
+
assert_equal expected, scoping
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_generate_constrain_from_array
|
23
|
+
scoping = Filter.generate_constrain(Member, ['name = ?', "Maiha"]).method_scoping
|
24
|
+
expected = {
|
25
|
+
:find => { :conditions => ['( members.name = ? )', "Maiha"] },
|
26
|
+
:create => { },
|
27
|
+
}
|
28
|
+
assert_equal expected, scoping
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_generate_constrain_from_symbol
|
32
|
+
assert_raise(NoMethodError) {
|
33
|
+
Filter.generate_constrain(Member, :method_scoping, :controller=>:dummy)
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_generate_constrain_from_proc
|
38
|
+
expected = {:find=>{}, :create=>{}}
|
39
|
+
proc = Proc.new{expected}
|
40
|
+
assert_equal expected, Filter.generate_constrain(Member, proc)
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_generate_constrain_from_sql_condition
|
44
|
+
condition = SqlCondition.new(:name => "Maiha")
|
45
|
+
|
46
|
+
scoping = Filter.generate_constrain(Member, condition).method_scoping
|
47
|
+
expected = {
|
48
|
+
:find => { :conditions => ['( members.name = ? )', "Maiha"] },
|
49
|
+
:create => { 'name' => "Maiha" },
|
50
|
+
}
|
51
|
+
assert_equal expected, scoping
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_generate_constrain_from_method_scoping
|
55
|
+
method_scoping = MethodScoping.new(:name => "Maiha")
|
56
|
+
|
57
|
+
scoping = Filter.generate_constrain(Member, method_scoping).method_scoping
|
58
|
+
expected = {
|
59
|
+
:find => { :conditions => ['( members.name = ? )', "Maiha"] },
|
60
|
+
:create => { 'name' => "Maiha" },
|
61
|
+
}
|
62
|
+
assert_equal expected, scoping
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
|
68
|
+
class FilterWithMethodScopingTest < Test::Unit::TestCase
|
69
|
+
def setup
|
70
|
+
@controller = Struct.new(:method_scoping).new # faked
|
71
|
+
@class = Member
|
72
|
+
@filter = Filter.new(@class)
|
73
|
+
@nested_depth = @class.instance_eval{scoped_methods}.size
|
74
|
+
end
|
75
|
+
|
76
|
+
def teardown
|
77
|
+
current_scopings = @class.instance_eval{scoped_methods}
|
78
|
+
nested_depth = current_scopings.size - @nested_depth
|
79
|
+
@class.instance_eval{nested_depth.times{scoped_methods.pop}}
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_generate_constrain_from_hash
|
83
|
+
@controller.method_scoping = {:find=>{:limit=>10}}
|
84
|
+
scoping = @filter.before(@controller).last
|
85
|
+
expected = {
|
86
|
+
:find => { :limit => 10 }
|
87
|
+
}
|
88
|
+
assert_equal expected, scoping
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_generate_constrain_from_string
|
92
|
+
@controller.method_scoping = 'name = "Maiha"'
|
93
|
+
scoping = @filter.before(@controller).last
|
94
|
+
expected = {
|
95
|
+
:find => { :conditions => ['( members.name = "Maiha" )'] },
|
96
|
+
:create => { },
|
97
|
+
}
|
98
|
+
assert_equal expected, scoping
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_generate_constrain_from_array
|
102
|
+
@controller.method_scoping = ['name = ?', "Maiha"]
|
103
|
+
scoping = @filter.before(@controller).last
|
104
|
+
expected = {
|
105
|
+
:find => { :conditions => ['( members.name = ? )', "Maiha"] },
|
106
|
+
:create => { },
|
107
|
+
}
|
108
|
+
assert_equal expected, scoping
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_generate_constrain_from_symbol
|
112
|
+
expected = {:find=>{}, :create=>{}}
|
113
|
+
@controller.method_scoping = expected
|
114
|
+
assert_equal expected, scoping = @filter.before(@controller).last
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_generate_constrain_from_proc
|
118
|
+
expected = {:find=>{}, :create=>{}}
|
119
|
+
@controller.method_scoping = Proc.new{expected}
|
120
|
+
assert_equal expected, scoping = @filter.before(@controller).last
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_generate_constrain_from_sql_condition
|
124
|
+
@controller.method_scoping = SqlCondition.new(:name => "Maiha")
|
125
|
+
scoping = @filter.before(@controller).last
|
126
|
+
expected = {
|
127
|
+
:find => { :conditions => ['( members.name = ? )', "Maiha"] },
|
128
|
+
:create => { 'name' => "Maiha" },
|
129
|
+
}
|
130
|
+
assert_equal expected, scoping
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_generate_constrain_from_method_scoping
|
134
|
+
@controller.method_scoping = MethodScoping.new(:name => "Maiha")
|
135
|
+
scoping = @filter.before(@controller).last
|
136
|
+
expected = {
|
137
|
+
:find => { :conditions => ['( members.name = ? )', "Maiha"] },
|
138
|
+
:create => { 'name' => "Maiha" },
|
139
|
+
}
|
140
|
+
assert_equal expected, scoping
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
|
2
|
+
saki:
|
3
|
+
id: 1
|
4
|
+
name: Saki
|
5
|
+
group_id: 1
|
6
|
+
grade: 2
|
7
|
+
deleted: false
|
8
|
+
maiha:
|
9
|
+
id: 2
|
10
|
+
name: Maiha
|
11
|
+
group_id: 1
|
12
|
+
grade: 2
|
13
|
+
deleted: true
|
14
|
+
yurina:
|
15
|
+
id: 3
|
16
|
+
name: Yurina
|
17
|
+
group_id: 1
|
18
|
+
grade: 1
|
19
|
+
deleted: false
|
20
|
+
risako:
|
21
|
+
id: 4
|
22
|
+
name: Risako
|
23
|
+
group_id: 1
|
24
|
+
grade: 1
|
25
|
+
deleted: false
|
26
|
+
airi:
|
27
|
+
id: 10
|
28
|
+
name: Airi
|
29
|
+
group_id: 2
|
30
|
+
grade: 2
|
31
|
+
deleted: false
|
32
|
+
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class MethodScopingTest < Test::Unit::TestCase
|
4
|
+
def test_no_conditions
|
5
|
+
assert_equal({ :find=>{}, :create=>{} }, MethodScoping.new(nil).method_scoping)
|
6
|
+
assert_equal({ :find=>{}, :create=>{} }, MethodScoping.new({}).method_scoping)
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_create_method_scoping
|
10
|
+
method_scoping = MethodScoping.new(:name => "Maiha")
|
11
|
+
expected = {
|
12
|
+
:find => { :conditions => ['( name = ? )', "Maiha"] },
|
13
|
+
:create => { 'name' => "Maiha" },
|
14
|
+
}
|
15
|
+
assert_equal expected, method_scoping.method_scoping
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_create_method_scoping_with_attributed_and_conditioned_values
|
19
|
+
method_scoping = MethodScoping.new(:name => "Maiha")
|
20
|
+
method_scoping.add("grade IN ?", [1,2])
|
21
|
+
method_scoping[:group] = 'Berryz'
|
22
|
+
|
23
|
+
expected = {
|
24
|
+
:find => { :conditions => ["( grade IN ? ) AND ( group = ? ) AND ( name = ? )", [1,2], "Berryz", "Maiha"] },
|
25
|
+
:create => { 'name' => "Maiha", 'group' => "Berryz" },
|
26
|
+
}
|
27
|
+
assert_equal expected, method_scoping.method_scoping
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_plus_method_with_merge
|
31
|
+
scoping1 = MethodScoping.new(:name => "Maiha")
|
32
|
+
scoping2 = MethodScoping.new(:group => "Berryz")
|
33
|
+
|
34
|
+
expected = {
|
35
|
+
"name" => "Maiha",
|
36
|
+
"group" => "Berryz",
|
37
|
+
}
|
38
|
+
assert_equal expected, (scoping1 + scoping2).attributes
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_plus_method_with_overwrite
|
42
|
+
scoping1 = MethodScoping.new(:name => "Maiha", :group => "Berryz")
|
43
|
+
scoping2 = MethodScoping.new(:name => "Saki")
|
44
|
+
|
45
|
+
expected = {
|
46
|
+
"name" => "Saki",
|
47
|
+
"group" => "Berryz",
|
48
|
+
}
|
49
|
+
assert_equal expected, (scoping1 + scoping2).attributes
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
class ClassScopingTest < Test::Unit::TestCase
|
55
|
+
def test_create_class_scoping
|
56
|
+
class_scoping = ClassScoping.new(Member, :name => "Maiha")
|
57
|
+
expected = {
|
58
|
+
:find => { :conditions => ['( members.name = ? )', "Maiha"] },
|
59
|
+
:create => { 'name' => "Maiha" },
|
60
|
+
}
|
61
|
+
assert_equal expected, class_scoping.method_scoping
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_create_class_scoping_from_method_scoping
|
65
|
+
method_scoping = MethodScoping.new(:name => "Maiha")
|
66
|
+
class_scoping = ClassScoping.new(Member, method_scoping.attributes)
|
67
|
+
|
68
|
+
expected = {
|
69
|
+
:find => { :conditions => ['( members.name = ? )', "Maiha"] },
|
70
|
+
:create => { 'name' => "Maiha" },
|
71
|
+
}
|
72
|
+
assert_equal expected, class_scoping.method_scoping
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_create_class_scoping_with_attributed_and_conditioned_values
|
76
|
+
class_scoping = ClassScoping.new(Member, :name => "Maiha")
|
77
|
+
class_scoping.add("grade IN ?", [1,2])
|
78
|
+
class_scoping[:group] = 'Berryz'
|
79
|
+
|
80
|
+
expected = {
|
81
|
+
:find => { :conditions => ["( members.grade IN ? ) AND ( members.group = ? ) AND ( members.name = ? )",
|
82
|
+
[1,2], "Berryz", "Maiha"] },
|
83
|
+
:create => { 'name' => "Maiha", 'group' => "Berryz" },
|
84
|
+
}
|
85
|
+
assert_equal expected, class_scoping.method_scoping
|
86
|
+
end
|
87
|
+
end
|
data/test/schema.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
ActiveRecord::Schema.define(:version => 2) do
|
2
|
+
|
3
|
+
create_table :groups, :force => true do |t|
|
4
|
+
t.column :name, :string
|
5
|
+
t.column :deleted, :boolean, :default=>0
|
6
|
+
end
|
7
|
+
|
8
|
+
create_table :members, :force => true do |t|
|
9
|
+
t.column :name, :string
|
10
|
+
t.column :grade, :integer
|
11
|
+
t.column :deleted, :boolean, :default=>0
|
12
|
+
t.column :group_id,:integer
|
13
|
+
end
|
14
|
+
|
15
|
+
create_table :favorites, :force => true do |t|
|
16
|
+
t.column :name, :string
|
17
|
+
t.column :member_id,:integer
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'init')
|
3
|
+
|
4
|
+
module Scopings
|
5
|
+
ActiveMember = MethodScoping.new(:deleted => false)
|
6
|
+
ElementarySchool = MethodScoping.new(:grade => 1)
|
7
|
+
JuniorHighSchool = MethodScoping.new(:grade => 2)
|
8
|
+
end
|
9
|
+
|
10
|
+
class TestController < ActionController::Base
|
11
|
+
attr_reader :members, :member
|
12
|
+
|
13
|
+
def list
|
14
|
+
@members = Member.find(:all, :order=>:id)
|
15
|
+
render :text=>''
|
16
|
+
end
|
17
|
+
|
18
|
+
def show
|
19
|
+
@member = Member.find(params[:id])
|
20
|
+
render :text=>''
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class ScopedAccessTestCase < Test::Unit::TestCase
|
25
|
+
fixtures :members
|
26
|
+
|
27
|
+
def setup
|
28
|
+
Member.instance_eval{scoped_methods.clear}
|
29
|
+
@request = ActionController::TestRequest.new
|
30
|
+
@response = ActionController::TestResponse.new
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_dummy
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def test_process(controller, action = "show")
|
38
|
+
request = ActionController::TestRequest.new
|
39
|
+
request.action = action
|
40
|
+
controller.process(request, ActionController::TestResponse.new)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
class WithoutFilterTest < ScopedAccessTestCase
|
47
|
+
class WithoutFilterController < TestController
|
48
|
+
end
|
49
|
+
|
50
|
+
def setup
|
51
|
+
super
|
52
|
+
@controller = WithoutFilterController.new
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_filter_list
|
56
|
+
get :list
|
57
|
+
assert_response :success
|
58
|
+
assert_equal %w( Saki Maiha Yurina Risako Airi ), @controller.members.map{|o| o.name}
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_filter_show
|
62
|
+
get :show, {'id' => "2"}
|
63
|
+
assert_response :success
|
64
|
+
assert_equal members(:maiha), @controller.member
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class ActiveMemberTest < ScopedAccessTestCase
|
69
|
+
class ActiveMemberController < TestController
|
70
|
+
around_filter ScopedAccess::Filter.new(Member, Scopings::ActiveMember)
|
71
|
+
|
72
|
+
def nested_scoping_with_elementary_school
|
73
|
+
Member.with_scope(Scopings::ElementarySchool) do
|
74
|
+
@members = Member.find(:all)
|
75
|
+
end
|
76
|
+
render :text=>''
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def setup
|
81
|
+
super
|
82
|
+
@controller = ActiveMemberController.new
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_filter_list
|
86
|
+
get :list
|
87
|
+
assert_response :success
|
88
|
+
expected = %w( saki yurina risako airi ).collect{|name| members(name)}
|
89
|
+
assert_equal expected, @controller.members
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_filter_show
|
93
|
+
assert_raises(ActiveRecord::RecordNotFound) {
|
94
|
+
get :show, {'id' => "2"}
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_filter_nested_scoping
|
99
|
+
get :nested_scoping_with_elementary_school
|
100
|
+
assert_response :success
|
101
|
+
assert_equal [members(:yurina), members(:risako)], @controller.members
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
class DoubleAroundWithMergedScopedFilterTest < ScopedAccessTestCase
|
107
|
+
class DoubleAroundWithMergedScopedFilterController < TestController
|
108
|
+
scoped_access Member, Scopings::ActiveMember
|
109
|
+
scoped_access Member, Scopings::JuniorHighSchool
|
110
|
+
|
111
|
+
def list_with_exclusive_scope
|
112
|
+
Member.with_exclusive_scope(Scopings::ElementarySchool) do
|
113
|
+
@members = Member.find(:all)
|
114
|
+
end
|
115
|
+
render :text=>''
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def setup
|
120
|
+
super
|
121
|
+
@controller = DoubleAroundWithMergedScopedFilterController.new
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_filter_list
|
125
|
+
get :list
|
126
|
+
assert_response :success
|
127
|
+
assert_equal [members(:saki), members(:airi)], @controller.members
|
128
|
+
end
|
129
|
+
|
130
|
+
def test_filter_show_active_member
|
131
|
+
get :show, {'id' => "1"}
|
132
|
+
assert_response :success
|
133
|
+
assert_equal members(:saki), @controller.member
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_filter_show_deleted_member
|
137
|
+
assert_raises(ActiveRecord::RecordNotFound) {
|
138
|
+
get :show, {'id' => "2"}
|
139
|
+
}
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_list_with_exclusive_scope
|
143
|
+
get :list_with_exclusive_scope
|
144
|
+
assert_response :success
|
145
|
+
assert_equal [members(:yurina), members(:risako)], @controller.members
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
class InheritedDoubleAroundFilterTest < ScopedAccessTestCase
|
151
|
+
class ParentAroundFilterController < TestController
|
152
|
+
scoped_access Member, Scopings::ActiveMember
|
153
|
+
end
|
154
|
+
|
155
|
+
class InheritedDoubleAroundFilterController < ParentAroundFilterController
|
156
|
+
scoped_access Member, Scopings::JuniorHighSchool
|
157
|
+
|
158
|
+
def list_with_conflicted_condition
|
159
|
+
Member.with_scope(Scopings::ElementarySchool) do
|
160
|
+
@members = Member.find(:all)
|
161
|
+
end
|
162
|
+
render :text=>''
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def setup
|
167
|
+
super
|
168
|
+
@controller = InheritedDoubleAroundFilterController.new
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_filter_list
|
172
|
+
get :list
|
173
|
+
assert_response :success
|
174
|
+
assert_equal [members(:saki), members(:airi)], @controller.members
|
175
|
+
end
|
176
|
+
|
177
|
+
def test_filter_show
|
178
|
+
assert_raises(ActiveRecord::RecordNotFound) {
|
179
|
+
get :show, {'id' => "2"}
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
183
|
+
def test_filter_list_with_conflicted_condition
|
184
|
+
get :list_with_conflicted_condition
|
185
|
+
assert_response :success
|
186
|
+
assert_equal [], @controller.members
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
class ConditionalAroundFilterTest < ScopedAccessTestCase
|
192
|
+
class ConditionalParentAroundFilterController < TestController
|
193
|
+
scoped_access Member, Scopings::ActiveMember, :except=>:all
|
194
|
+
scoped_access Member, Scopings::JuniorHighSchool
|
195
|
+
end
|
196
|
+
|
197
|
+
class InheritedDoubleConditionalAroundFilterController < ConditionalParentAroundFilterController
|
198
|
+
scoped_access Member, Scopings::ElementarySchool, :only=>:list_with_conflicted_condition
|
199
|
+
|
200
|
+
def list_with_conflicted_condition
|
201
|
+
@members = Member.find(:all)
|
202
|
+
render :text=>''
|
203
|
+
end
|
204
|
+
|
205
|
+
def list
|
206
|
+
@members = Member.find(:all)
|
207
|
+
render :text=>''
|
208
|
+
end
|
209
|
+
|
210
|
+
def all
|
211
|
+
@members = Member.find(:all)
|
212
|
+
render :text=>''
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def setup
|
217
|
+
super
|
218
|
+
@controller = InheritedDoubleConditionalAroundFilterController.new
|
219
|
+
end
|
220
|
+
|
221
|
+
def test_filter_list_out_of_only_condition
|
222
|
+
expected = Member.with_exclusive_scope(Scopings::ActiveMember+Scopings::JuniorHighSchool){Member.find(:all)}
|
223
|
+
get :list
|
224
|
+
assert_response :success
|
225
|
+
assert_equal expected, @controller.members
|
226
|
+
end
|
227
|
+
|
228
|
+
def test_filter_show
|
229
|
+
assert_raises(ActiveRecord::RecordNotFound) {
|
230
|
+
get :show, {'id' => "2"}
|
231
|
+
}
|
232
|
+
end
|
233
|
+
|
234
|
+
def test_filter_list_with_conflicted_condition
|
235
|
+
get :list_with_conflicted_condition
|
236
|
+
assert_response :success
|
237
|
+
assert_equal [], @controller.members
|
238
|
+
end
|
239
|
+
|
240
|
+
def test_filter_except_condition
|
241
|
+
expected = Member.with_exclusive_scope(Scopings::JuniorHighSchool){Member.find(:all)}
|
242
|
+
get :all
|
243
|
+
assert_response :success
|
244
|
+
assert_equal expected, @controller.members
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class SqlConditionTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
attributes = { :name => 'Maiha' }
|
6
|
+
@condition = SqlCondition.new(attributes)
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_immutable_attributes
|
10
|
+
assert_equal "Maiha", @condition.attributes[:name]
|
11
|
+
attributes = { :name => 'Saki' }
|
12
|
+
assert_equal "Maiha", @condition.attributes[:name]
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_add_attribute
|
16
|
+
@condition[:group] = 'Berryz'
|
17
|
+
assert_equal({'name'=>"Maiha", 'group'=>'Berryz'}, @condition.attributes)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_overwrite_attribute
|
21
|
+
@condition[:name] = 'Saki'
|
22
|
+
assert_equal({'name'=>"Saki"}, @condition.attributes)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_generate_with_one_attribute
|
26
|
+
assert_equal(["( name = ? )", "Maiha"], @condition.generate)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_generate_with_one_condition
|
30
|
+
@condition = SqlCondition.new
|
31
|
+
@condition.add("name = 'Maiha'")
|
32
|
+
assert_equal ["( name = 'Maiha' )"], @condition.generate
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_generate_with_one_attribute_and_one_condition
|
36
|
+
@condition.add("group = 'Berryz'")
|
37
|
+
assert_equal "( group = 'Berryz' ) AND ( name = 'Maiha' )", sanitize_sql(@condition.generate)
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_generate_same_sqls_in_attributed_and_conditioned_ways
|
41
|
+
attributed = @condition
|
42
|
+
conditioned = SqlCondition.new
|
43
|
+
|
44
|
+
conditioned.add("name = 'Maiha'")
|
45
|
+
assert_equal sanitize_sql(attributed.generate), sanitize_sql(conditioned.generate)
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_generate_with_one_and_two_place_folders
|
49
|
+
@condition.add("group = ?", 'Berryz')
|
50
|
+
@condition.add("age BETWEEN ? AND ?", 12, 14)
|
51
|
+
expected_sql = "( group = 'Berryz' ) AND ( age BETWEEN 12 AND 14 ) AND ( name = 'Maiha' )"
|
52
|
+
assert_equal expected_sql, sanitize_sql(@condition.generate)
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_generate_with_complex_constrains
|
56
|
+
@condition.add("group = ?", 'Berryz')
|
57
|
+
@condition.add("state IN (?)", [1,2])
|
58
|
+
@condition[:graduate] = 1
|
59
|
+
expected_sql = "( group = 'Berryz' ) AND ( state IN (1,2) ) AND ( graduate = 1 ) AND ( name = 'Maiha' )"
|
60
|
+
assert_equal expected_sql, sanitize_sql(@condition.generate)
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_generate_from_sql_condition
|
64
|
+
@condition.add("group = ?", 'Berryz')
|
65
|
+
cond = SqlCondition.new(@condition)
|
66
|
+
assert_equal({"name"=>'Maiha'}, cond.attributes)
|
67
|
+
assert_equal([SqlCondition::Constrain.new("group = ?", ['Berryz'])], cond.constrains)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
def sanitize_sql (sql)
|
72
|
+
ActiveRecord::Base.instance_eval do
|
73
|
+
sanitize_sql(sql)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
def __DIR__; File.dirname(__FILE__); end
|
2
|
+
|
3
|
+
$:.unshift(__DIR__ + '/../lib')
|
4
|
+
# begin
|
5
|
+
# require 'rubygems'
|
6
|
+
# rescue LoadError
|
7
|
+
$:.unshift(__DIR__ + '/../../../rails/activerecord/lib')
|
8
|
+
$:.unshift(__DIR__ + '/../../../rails/activesupport/lib')
|
9
|
+
$:.unshift(__DIR__ + '/../../../rails/actionpack/lib')
|
10
|
+
#end
|
11
|
+
require 'test/unit'
|
12
|
+
require 'active_support'
|
13
|
+
require 'active_record'
|
14
|
+
require 'active_record/fixtures'
|
15
|
+
|
16
|
+
config = YAML::load_file(__DIR__ + '/database.yml')
|
17
|
+
ActiveRecord::Base.logger = Logger.new(__DIR__ + "/debug.log")
|
18
|
+
ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite3'])
|
19
|
+
|
20
|
+
|
21
|
+
# create tables
|
22
|
+
load(__DIR__ + "/schema.rb")
|
23
|
+
|
24
|
+
# insert sample data to the tables from 'fixtures/*.yml'
|
25
|
+
Test::Unit::TestCase.fixture_path = __DIR__ + "/fixtures/"
|
26
|
+
$LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path)
|
27
|
+
Test::Unit::TestCase.use_instantiated_fixtures = false
|
28
|
+
|
29
|
+
# for controller test
|
30
|
+
require 'action_pack'
|
31
|
+
require 'action_controller'
|
32
|
+
require 'action_controller/test_process'
|
33
|
+
|
34
|
+
ActionController::Base.ignore_missing_templates = true
|
35
|
+
ActionController::Routing::Routes.reload rescue nil
|
36
|
+
class ActionController::Base; def rescue_action(e) raise e end; end
|
37
|
+
|
38
|
+
include ScopedAccess
|
39
|
+
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: maiha-scoped_access
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- maiha
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-10-03 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: ""
|
17
|
+
email: maiha@wota.jp
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- README
|
26
|
+
- Rakefile
|
27
|
+
- init.rb
|
28
|
+
- install.rb
|
29
|
+
- lib/scoped_access.rb
|
30
|
+
- tasks/scoped_access_tasks.rake
|
31
|
+
- test/database.yml
|
32
|
+
- test/filter_test.rb
|
33
|
+
- test/fixtures/favorite.rb
|
34
|
+
- test/fixtures/favorites.yml
|
35
|
+
- test/fixtures/group.rb
|
36
|
+
- test/fixtures/groups.yml
|
37
|
+
- test/fixtures/member.rb
|
38
|
+
- test/fixtures/members.yml
|
39
|
+
- test/fixtures/song.rb
|
40
|
+
- test/method_scoping_test.rb
|
41
|
+
- test/schema.rb
|
42
|
+
- test/scoped_access_test.rb
|
43
|
+
- test/sql_condition_test.rb
|
44
|
+
- test/test_helper.rb
|
45
|
+
has_rdoc: true
|
46
|
+
homepage: http://github.com/maiha/scoped_access
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
version:
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0"
|
63
|
+
version:
|
64
|
+
requirements: []
|
65
|
+
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 1.2.0
|
68
|
+
signing_key:
|
69
|
+
specification_version: 2
|
70
|
+
summary: ""
|
71
|
+
test_files: []
|
72
|
+
|