periscope 0.1.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/.gitignore +4 -0
- data/Gemfile +3 -0
- data/README.rdoc +58 -0
- data/Rakefile +2 -0
- data/init.rb +1 -0
- data/lib/periscope.rb +6 -0
- data/lib/periscope/adapters/abstract.rb +67 -0
- data/lib/periscope/adapters/active_record.rb +18 -0
- data/lib/periscope/permission_set.rb +34 -0
- data/lib/periscope/sanitizer.rb +27 -0
- data/lib/periscope/version.rb +3 -0
- data/periscope.gemspec +27 -0
- data/spec/periscope/adapters/active_record_spec.rb +129 -0
- data/spec/periscope/black_list_spec.rb +19 -0
- data/spec/periscope/version_spec.rb +10 -0
- data/spec/periscope/white_list_spec.rb +19 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/support/abstract_examples.rb +89 -0
- data/spec/support/connections/active_record.rb +17 -0
- data/spec/support/permission_set_examples.rb +35 -0
- data/spec/support/sanitizer_examples.rb +66 -0
- metadata +148 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
= Periscope
|
2
|
+
|
3
|
+
Bring your models' scopes up above the surface.
|
4
|
+
|
5
|
+
Periscope acts like +attr_accessible+ or +attr_protected+, but for your models' scopes.
|
6
|
+
|
7
|
+
== The Problem
|
8
|
+
|
9
|
+
More often than not, the index action in a RESTful Rails controller is expected to do a lot more than simply return all the records for a given model. We ask it to do all sorts of stuff like filtering, sorting and paginating results. Of course, this is typically done using _scopes_.
|
10
|
+
|
11
|
+
But sometimes it can get ugly building long, complicated chains of scope in the controller, especially when you try to give your users control over the scoping. Picture this:
|
12
|
+
|
13
|
+
def index
|
14
|
+
@articles = Article.scoped
|
15
|
+
@articles = @articles.published_after(params[:published_after]) if params.key?(:published_after)
|
16
|
+
@articles = @articles.published_before(params[:published_before]) if params.key?(:published_before)
|
17
|
+
end
|
18
|
+
|
19
|
+
You can imagine how bad this would get if more than two scopes were involved.
|
20
|
+
|
21
|
+
== The Solution
|
22
|
+
|
23
|
+
With Periscope, you can have this instead:
|
24
|
+
|
25
|
+
def index
|
26
|
+
@articles = Article.periscope(request.query_parameters)
|
27
|
+
end
|
28
|
+
|
29
|
+
The +periscope+ method will find keys in your params matching your scope names and chain your scopes for you.
|
30
|
+
|
31
|
+
<b>Note:</b> We're using <code>request.query_parameters</code> so we can exclude your controller and action params. <code>request.query_parameters</code> will just return the params that show up after the "?" in the URL.
|
32
|
+
|
33
|
+
== But Wait!
|
34
|
+
|
35
|
+
"What if I don't want to make all my scopes publicly accessible?"
|
36
|
+
|
37
|
+
In your model you can use either the +scope_accessible+ or +scope_protected+ method to specify which scopes you want Periscope to pay attention to.
|
38
|
+
|
39
|
+
class User < ActiveRecord::Base
|
40
|
+
attr_accessible :name, :gender, :salary
|
41
|
+
|
42
|
+
scope :gender, lambda{|g| where(:gender => g) }
|
43
|
+
scope :makes_more_than, lambda{|s| where('users.salary >= ?', s) }
|
44
|
+
|
45
|
+
scope_accessible :gender
|
46
|
+
end
|
47
|
+
|
48
|
+
And in your controller:
|
49
|
+
|
50
|
+
class UsersController < ApplicationController
|
51
|
+
def index
|
52
|
+
@users = User.periscope(request.query_parameters)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
Now, requests to <code>/users?gender=male</code> will filter results to only male users. But a request to <code>/users?makes_more_than=1000000</code> will return all users, silently ignoring the protected scope.
|
57
|
+
|
58
|
+
By default, all scopes are protected.
|
data/Rakefile
ADDED
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'periscope'
|
data/lib/periscope.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute.rb'
|
2
|
+
require 'periscope/permission_set'
|
3
|
+
|
4
|
+
module Periscope
|
5
|
+
module Adapters
|
6
|
+
module Abstract
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
class_attribute :_accessible_scopes
|
11
|
+
class_attribute :_protected_scopes
|
12
|
+
class_attribute :_periscope_authorizer
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def periscope(*)
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
|
20
|
+
def scope_protected(*scopes)
|
21
|
+
self._protected_scopes = protected_scopes + scopes
|
22
|
+
self._periscope_authorizer = _protected_scopes
|
23
|
+
end
|
24
|
+
|
25
|
+
alias_method :down_periscope, :scope_protected
|
26
|
+
|
27
|
+
def scope_accessible(*scopes)
|
28
|
+
self._accessible_scopes = accessible_scopes + scopes
|
29
|
+
self._periscope_authorizer = _accessible_scopes
|
30
|
+
end
|
31
|
+
|
32
|
+
alias_method :up_periscope, :scope_accessible
|
33
|
+
|
34
|
+
def protected_scopes
|
35
|
+
self._protected_scopes ||= BlackList.new.tap do |list|
|
36
|
+
list.logger = logger if respond_to?(:logger)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def accessible_scopes
|
41
|
+
self._accessible_scopes ||= WhiteList.new(scopes_accessible_by_default).tap do |list|
|
42
|
+
list.logger = logger if respond_to?(:logger)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def periscope_authorizer
|
47
|
+
self._periscope_authorizer ||= accessible_scopes
|
48
|
+
end
|
49
|
+
|
50
|
+
def scopes_accessible_by_default
|
51
|
+
[]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
module InstanceMethods
|
56
|
+
protected
|
57
|
+
def sanitize_for_search(params)
|
58
|
+
search_authorizer.sanitize(params)
|
59
|
+
end
|
60
|
+
|
61
|
+
def search_authorizer
|
62
|
+
self.class.periscope_authorizer
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'periscope/adapters/abstract'
|
2
|
+
|
3
|
+
module Periscope
|
4
|
+
module Adapters
|
5
|
+
module ActiveRecord
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
include Abstract
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def periscope(params = {})
|
11
|
+
periscope_authorizer.sanitize(params).inject(scoped) do |chain, (key, value)|
|
12
|
+
chain.send(key, value)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'periscope/sanitizer'
|
3
|
+
|
4
|
+
module Periscope
|
5
|
+
class PermissionSet < Set
|
6
|
+
def initialize(values = nil)
|
7
|
+
super(values, &:to_s)
|
8
|
+
end
|
9
|
+
|
10
|
+
def +(values)
|
11
|
+
super(values.map(&:to_s))
|
12
|
+
end
|
13
|
+
|
14
|
+
def include?(value)
|
15
|
+
super(value.to_s)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class WhiteList < PermissionSet
|
20
|
+
include Sanitizer
|
21
|
+
|
22
|
+
def deny?(value)
|
23
|
+
!include?(value)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class BlackList < PermissionSet
|
28
|
+
include Sanitizer
|
29
|
+
|
30
|
+
def deny?(value)
|
31
|
+
include?(value)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Periscope
|
2
|
+
module Sanitizer
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
attr_accessor :logger
|
7
|
+
end
|
8
|
+
|
9
|
+
module InstanceMethods
|
10
|
+
def sanitize(params)
|
11
|
+
params.reject{|k,v| deny?(k) }.tap do |sanitized|
|
12
|
+
debug_protected_scope_removal(params, sanitized)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
def debug_protected_scope_removal(params, sanitized)
|
18
|
+
removed = params.keys - sanitized.keys
|
19
|
+
warn!(removed) if removed.any?
|
20
|
+
end
|
21
|
+
|
22
|
+
def warn!(scopes)
|
23
|
+
logger.debug("WARNING: Can't search protected scopes: #{scopes.join(', ')}") if logger
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/periscope.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'periscope/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'periscope'
|
7
|
+
s.version = Periscope::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ['Steve Richert']
|
10
|
+
s.email = ['steve.richert@gmail.com']
|
11
|
+
s.homepage = 'https://github.com/laserlemon/periscope'
|
12
|
+
s.summary = %(Bring your models' scopes up above the surface.)
|
13
|
+
s.description = %(Periscope acts like attr_accessible or attr_protected, but for your models' scopes.)
|
14
|
+
|
15
|
+
s.rubyforge_project = 'periscope'
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f) }
|
20
|
+
s.require_paths = ['lib']
|
21
|
+
|
22
|
+
s.add_dependency 'activesupport', '>= 3.0.0'
|
23
|
+
|
24
|
+
s.add_development_dependency 'rspec'
|
25
|
+
s.add_development_dependency 'sqlite3'
|
26
|
+
s.add_development_dependency 'activerecord', '>= 3.0.0'
|
27
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Periscope
|
4
|
+
module Adapters
|
5
|
+
describe ActiveRecord do
|
6
|
+
it_should_behave_like 'an adapter'
|
7
|
+
|
8
|
+
describe :periscope do
|
9
|
+
subject do
|
10
|
+
Class.new(User).tap do |klass|
|
11
|
+
klass.class_eval do
|
12
|
+
scope :gender, lambda{|g| where(:gender => g) }
|
13
|
+
scope :makes, lambda{|s| where('users.salary >= ?', s) }
|
14
|
+
scope :rich, where('users.salary >= 1000000')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'ignores' do
|
20
|
+
specify 'all scopes by default' do
|
21
|
+
subject.should_receive(:gender).never
|
22
|
+
subject.should_receive(:makes).never
|
23
|
+
|
24
|
+
subject.periscope(:gender => 'male', :makes => 100000)
|
25
|
+
end
|
26
|
+
|
27
|
+
specify 'all scopes when none are accessible' do
|
28
|
+
subject.scope_accessible
|
29
|
+
|
30
|
+
subject.should_receive(:gender).never
|
31
|
+
subject.should_receive(:makes).never
|
32
|
+
|
33
|
+
subject.periscope(:gender => 'male', :makes => 100000)
|
34
|
+
end
|
35
|
+
|
36
|
+
specify 'protected scopes' do
|
37
|
+
subject.scope_protected :makes
|
38
|
+
|
39
|
+
subject.should_receive(:gender).with('male').once
|
40
|
+
subject.should_receive(:makes).never
|
41
|
+
|
42
|
+
subject.periscope(:gender => 'male', :makes => 100000)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'uses' do
|
47
|
+
specify 'all scopes when none are protected' do
|
48
|
+
subject.scope_protected
|
49
|
+
|
50
|
+
subject.should_receive(:gender).with('male').once
|
51
|
+
subject.should_receive(:makes).with(100000).once
|
52
|
+
|
53
|
+
subject.periscope(:gender => 'male', :makes => 100000)
|
54
|
+
end
|
55
|
+
|
56
|
+
specify 'accessible scopes' do
|
57
|
+
subject.scope_accessible :gender
|
58
|
+
|
59
|
+
subject.should_receive(:gender).with('male').once
|
60
|
+
subject.should_receive(:makes).never
|
61
|
+
|
62
|
+
subject.periscope(:gender => 'male', :makes => 100000)
|
63
|
+
end
|
64
|
+
|
65
|
+
specify 'accessible, zero-arity scopes' do
|
66
|
+
subject.scope_accessible :rich
|
67
|
+
|
68
|
+
subject.should_receive(:rich).with(true).once
|
69
|
+
|
70
|
+
lambda{ subject.periscope(:rich => true) }.should_not raise_error
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'returns' do
|
75
|
+
before do
|
76
|
+
subject.delete_all
|
77
|
+
subject.create(:name => 'Henry', :gender => 'male', :salary => 50_000)
|
78
|
+
subject.create(:name => 'Penny', :gender => 'female', :salary => 1_000_000)
|
79
|
+
subject.create(:name => 'Sammy', :gender => 'male', :salary => 100_000)
|
80
|
+
end
|
81
|
+
|
82
|
+
let(:params){ {:gender => 'male', :rich => true} }
|
83
|
+
|
84
|
+
context 'all records' do
|
85
|
+
specify 'for no params' do
|
86
|
+
subject.periscope.map(&:name).should == %w(Henry Penny Sammy)
|
87
|
+
end
|
88
|
+
|
89
|
+
specify 'for empty params' do
|
90
|
+
subject.periscope({}).map(&:name).should == %w(Henry Penny Sammy)
|
91
|
+
end
|
92
|
+
|
93
|
+
specify 'for no accessible scopes' do
|
94
|
+
subject.scope_accessible
|
95
|
+
|
96
|
+
subject.periscope(params).map(&:name).should == %w(Henry Penny Sammy)
|
97
|
+
end
|
98
|
+
|
99
|
+
specify 'for all protected scopes' do
|
100
|
+
subject.scope_protected :gender, :rich
|
101
|
+
|
102
|
+
subject.periscope(params).map(&:name).should == %w(Henry Penny Sammy)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'scoped results' do
|
107
|
+
specify 'for an accessible scope' do
|
108
|
+
subject.scope_accessible :rich
|
109
|
+
|
110
|
+
subject.periscope(params).map(&:name).should == %w(Penny)
|
111
|
+
end
|
112
|
+
|
113
|
+
specify 'for an unprotected scope' do
|
114
|
+
subject.scope_protected :gender
|
115
|
+
|
116
|
+
subject.periscope(params).map(&:name).should == %w(Penny)
|
117
|
+
end
|
118
|
+
|
119
|
+
specify 'for multiple accessible scopes' do
|
120
|
+
subject.scope_accessible :gender, :rich
|
121
|
+
|
122
|
+
subject.periscope(params).should be_empty
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Periscope
|
4
|
+
describe BlackList do
|
5
|
+
it_should_behave_like 'a permission set'
|
6
|
+
it_should_behave_like 'a sanitizer'
|
7
|
+
|
8
|
+
let(:values){ %w(one two three) }
|
9
|
+
subject{ described_class.new(values) }
|
10
|
+
|
11
|
+
it 'denies an included value' do
|
12
|
+
subject.deny?(values.first).should == true
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'accepts an excluded value' do
|
16
|
+
subject.deny?('four').should == false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Periscope
|
4
|
+
describe WhiteList do
|
5
|
+
it_should_behave_like 'a permission set'
|
6
|
+
it_should_behave_like 'a sanitizer'
|
7
|
+
|
8
|
+
let(:values){ %w(one two three) }
|
9
|
+
subject{ described_class.new(values) }
|
10
|
+
|
11
|
+
it 'accepts an included value' do
|
12
|
+
subject.deny?(values.first).should == false
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'denies an excluded value' do
|
16
|
+
subject.deny?('four').should == true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
shared_examples_for 'an adapter' do
|
2
|
+
subject do
|
3
|
+
Class.new.tap do |klass|
|
4
|
+
klass.send(:include, described_class)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'adds accessible scopes' do
|
9
|
+
subject.scope_accessible :current, :expired
|
10
|
+
|
11
|
+
subject.accessible_scopes.should include(:current)
|
12
|
+
subject.accessible_scopes.should include(:expired)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'adds protected scopes' do
|
16
|
+
subject.scope_protected :current, :expired
|
17
|
+
|
18
|
+
subject.protected_scopes.should include(:current)
|
19
|
+
subject.protected_scopes.should include(:expired)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'stacks accessible scopes' do
|
23
|
+
subject.scope_accessible :current
|
24
|
+
subject.scope_accessible :expired
|
25
|
+
|
26
|
+
subject.accessible_scopes.should include(:current)
|
27
|
+
subject.accessible_scopes.should include(:expired)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'stacks protected scopes' do
|
31
|
+
subject.scope_protected :current
|
32
|
+
subject.scope_protected :expired
|
33
|
+
|
34
|
+
subject.protected_scopes.should include(:current)
|
35
|
+
subject.protected_scopes.should include(:expired)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'uses accessible scopes if defined last' do
|
39
|
+
subject.scope_protected :current
|
40
|
+
subject.scope_accessible :expired
|
41
|
+
|
42
|
+
subject.periscope_authorizer.should be_a(Periscope::WhiteList)
|
43
|
+
subject.periscope_authorizer.should == subject.accessible_scopes
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'uses protected scopes if defined last' do
|
47
|
+
subject.scope_accessible :current
|
48
|
+
subject.scope_protected :expired
|
49
|
+
|
50
|
+
subject.periscope_authorizer.should be_a(Periscope::BlackList)
|
51
|
+
subject.periscope_authorizer.should == subject.protected_scopes
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'defaults to using accessible scopes' do
|
55
|
+
subject.periscope_authorizer.should be_a(Periscope::WhiteList)
|
56
|
+
subject.periscope_authorizer.should == subject.accessible_scopes
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'has no accesible scopes by default' do
|
60
|
+
subject.accessible_scopes.should be_empty
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'has no protected scopes by default' do
|
64
|
+
subject.protected_scopes.should be_empty
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'can override the default accessible scopes' do
|
68
|
+
subject.class_eval do
|
69
|
+
def self.scopes_accessible_by_default
|
70
|
+
[:current]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
subject.accessible_scopes.should include(:current)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'makes its authorizer available to its instances' do
|
78
|
+
subject.scope_accessible :current
|
79
|
+
|
80
|
+
subject.new.send(:search_authorizer).should == subject.accessible_scopes
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'sanitizes params from its instances' do
|
84
|
+
params = {:current => 'yes', :expired => 'no'}
|
85
|
+
subject.scope_accessible :current
|
86
|
+
|
87
|
+
subject.new.send(:sanitize_for_search, params).should == params.slice(:current)
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
ActiveRecord::Base.establish_connection(
|
4
|
+
:adapter => 'sqlite3',
|
5
|
+
:database => ':memory:'
|
6
|
+
)
|
7
|
+
|
8
|
+
ActiveRecord::Schema.define do
|
9
|
+
create_table :users do |t|
|
10
|
+
t.string :name
|
11
|
+
t.string :gender
|
12
|
+
t.integer :salary
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class User < ActiveRecord::Base
|
17
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
shared_examples_for 'a permission set' do
|
2
|
+
it 'has unique values' do
|
3
|
+
values = %w(one two two three)
|
4
|
+
|
5
|
+
permission_set = described_class.new(values)
|
6
|
+
permission_set.to_a.should == values.uniq
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'initializes with no values' do
|
10
|
+
lambda{ described_class.new }.should_not raise_error
|
11
|
+
described_class.new.should be_empty
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'stringifies values when initializing' do
|
15
|
+
values = [:one, :two, :three]
|
16
|
+
|
17
|
+
permission_set = described_class.new(values)
|
18
|
+
permission_set.to_a.should == values.map(&:to_s)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'stringifies values when adding' do
|
22
|
+
values = [:one, :two, :three]
|
23
|
+
|
24
|
+
permission_set = described_class.new + values
|
25
|
+
permission_set.to_a.should == values.map(&:to_s)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'stringifies a value when checking for inclusion' do
|
29
|
+
values = %w(one two three)
|
30
|
+
value = values.first.to_sym
|
31
|
+
|
32
|
+
permission_set = described_class.new(values)
|
33
|
+
permission_set.should include(value)
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'active_support/core_ext/hash/slice'
|
2
|
+
|
3
|
+
shared_examples_for 'a sanitizer' do
|
4
|
+
subject{ described_class.new }
|
5
|
+
|
6
|
+
let(:logger){ FakeLogger.new }
|
7
|
+
let(:params){ {:one => 1, :two => 2} }
|
8
|
+
|
9
|
+
it 'has its own logger' do
|
10
|
+
logger.should_receive(:debug).once
|
11
|
+
|
12
|
+
subject.logger = logger
|
13
|
+
subject.logger.should == logger
|
14
|
+
|
15
|
+
subject.logger.debug
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'removes denied keys from a hash' do
|
19
|
+
subject.stub(:deny?).with(:one).and_return(false)
|
20
|
+
subject.stub(:deny?).with(:two).and_return(true)
|
21
|
+
subject.sanitize(params).should == params.slice(:one)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "doesn't remove keys if none are denied" do
|
25
|
+
subject.stub(:deny? => false)
|
26
|
+
subject.sanitize(params).should == params
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'debugs removed keys' do
|
30
|
+
subject.logger = logger
|
31
|
+
subject.logger.should_receive(:debug).once
|
32
|
+
|
33
|
+
subject.stub(:deny? => true)
|
34
|
+
subject.sanitize(params)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "doesn't debug if there's no logger" do
|
38
|
+
subject.logger.should be_nil
|
39
|
+
|
40
|
+
allow_message_expectations_on_nil
|
41
|
+
subject.logger.should_receive(:debug).never
|
42
|
+
|
43
|
+
subject.stub(:deny? => true)
|
44
|
+
subject.sanitize(params)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "doesn't debug if no keys are removed" do
|
48
|
+
subject.logger = logger
|
49
|
+
subject.logger.should_receive(:debug).never
|
50
|
+
|
51
|
+
subject.stub(:deny? => false)
|
52
|
+
subject.sanitize(params)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'only debugs the removed keys' do
|
56
|
+
subject.logger = logger
|
57
|
+
|
58
|
+
subject.stub(:deny?).with(:one).and_return(false)
|
59
|
+
subject.stub(:deny?).with(:two).and_return(true)
|
60
|
+
subject.sanitize(params)
|
61
|
+
|
62
|
+
message = subject.logger.output.pop
|
63
|
+
message.should_not match(/\bone\b/)
|
64
|
+
message.should match(/\btwo\b/)
|
65
|
+
end
|
66
|
+
end
|
metadata
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: periscope
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Steve Richert
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-02-03 00:00:00 -05:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: activesupport
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 3
|
30
|
+
- 0
|
31
|
+
- 0
|
32
|
+
version: 3.0.0
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rspec
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: sqlite3
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
type: :development
|
60
|
+
version_requirements: *id003
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: activerecord
|
63
|
+
prerelease: false
|
64
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
segments:
|
70
|
+
- 3
|
71
|
+
- 0
|
72
|
+
- 0
|
73
|
+
version: 3.0.0
|
74
|
+
type: :development
|
75
|
+
version_requirements: *id004
|
76
|
+
description: Periscope acts like attr_accessible or attr_protected, but for your models' scopes.
|
77
|
+
email:
|
78
|
+
- steve.richert@gmail.com
|
79
|
+
executables: []
|
80
|
+
|
81
|
+
extensions: []
|
82
|
+
|
83
|
+
extra_rdoc_files: []
|
84
|
+
|
85
|
+
files:
|
86
|
+
- .gitignore
|
87
|
+
- Gemfile
|
88
|
+
- README.rdoc
|
89
|
+
- Rakefile
|
90
|
+
- init.rb
|
91
|
+
- lib/periscope.rb
|
92
|
+
- lib/periscope/adapters/abstract.rb
|
93
|
+
- lib/periscope/adapters/active_record.rb
|
94
|
+
- lib/periscope/permission_set.rb
|
95
|
+
- lib/periscope/sanitizer.rb
|
96
|
+
- lib/periscope/version.rb
|
97
|
+
- periscope.gemspec
|
98
|
+
- spec/periscope/adapters/active_record_spec.rb
|
99
|
+
- spec/periscope/black_list_spec.rb
|
100
|
+
- spec/periscope/version_spec.rb
|
101
|
+
- spec/periscope/white_list_spec.rb
|
102
|
+
- spec/spec_helper.rb
|
103
|
+
- spec/support/abstract_examples.rb
|
104
|
+
- spec/support/connections/active_record.rb
|
105
|
+
- spec/support/permission_set_examples.rb
|
106
|
+
- spec/support/sanitizer_examples.rb
|
107
|
+
has_rdoc: true
|
108
|
+
homepage: https://github.com/laserlemon/periscope
|
109
|
+
licenses: []
|
110
|
+
|
111
|
+
post_install_message:
|
112
|
+
rdoc_options: []
|
113
|
+
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
segments:
|
122
|
+
- 0
|
123
|
+
version: "0"
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
segments:
|
130
|
+
- 0
|
131
|
+
version: "0"
|
132
|
+
requirements: []
|
133
|
+
|
134
|
+
rubyforge_project: periscope
|
135
|
+
rubygems_version: 1.3.7
|
136
|
+
signing_key:
|
137
|
+
specification_version: 3
|
138
|
+
summary: Bring your models' scopes up above the surface.
|
139
|
+
test_files:
|
140
|
+
- spec/periscope/adapters/active_record_spec.rb
|
141
|
+
- spec/periscope/black_list_spec.rb
|
142
|
+
- spec/periscope/version_spec.rb
|
143
|
+
- spec/periscope/white_list_spec.rb
|
144
|
+
- spec/spec_helper.rb
|
145
|
+
- spec/support/abstract_examples.rb
|
146
|
+
- spec/support/connections/active_record.rb
|
147
|
+
- spec/support/permission_set_examples.rb
|
148
|
+
- spec/support/sanitizer_examples.rb
|