periscope 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2011 Steve Richert
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,132 @@
1
+ # Periscope [![Build Status](https://secure.travis-ci.org/laserlemon/periscope.png)](http://travis-ci.org/laserlemon/periscope) [![Dependency Status](https://gemnasium.com/laserlemon/periscope.png)](https://gemnasium.com/laserlemon/periscope)
2
+
3
+ Periscope provides a simple way to chain scopes on your models and to open those scopes up to your users.
4
+
5
+ ## The Problem
6
+
7
+ 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_.
8
+
9
+ But it can get ugly building long, complicated chains of scopes in the controller, especially when you try to give your users control over the scoping. Picture this:
10
+
11
+ ```ruby
12
+ def index
13
+ @articles = Article.scoped
14
+ @articles = @articles.published_after(params[:published_after]) if params.key?(:published_after)
15
+ @articles = @articles.published_before(params[:published_before]) if params.key?(:published_before)
16
+ end
17
+ ```
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
+ ```ruby
26
+ def index
27
+ @articles = Article.periscope(request.query_parameters)
28
+ end
29
+ ```
30
+
31
+ The `periscope` method will find keys in your params matching your scope names and chain your scopes for you.
32
+
33
+ **Note:** We're using `request.query_parameters` so that we can exclude our controller and action params. `request.query_parameters` will just return the params that appear in the query string.
34
+
35
+ ## But Wait!
36
+
37
+ "What if I don't want to make all my scopes publicly accessible?"
38
+
39
+ Within your model you can use the `scope_accessible` method to specify which scopes you want Periscope to honor.
40
+
41
+ ```ruby
42
+ class User < ActiveRecord::Base
43
+ scope :gender, lambda{|g| where(gender: g) }
44
+ scope :makes, lambda{|s| where('salary >= ?', s) }
45
+
46
+ scope_accessible :gender
47
+ end
48
+ ```
49
+
50
+ And in your controller:
51
+
52
+ ```ruby
53
+ class UsersController < ApplicationController
54
+ def index
55
+ @users = User.periscope(request.query_parameters)
56
+ end
57
+ end
58
+ ```
59
+
60
+ Requests to `/users?gender=male` will filter results to only male users. But a request to `/users?makes=1000000` will return all users, silently ignoring the protected scope.
61
+
62
+ By default, all scopes are protected.
63
+
64
+ ## There's More!
65
+
66
+ ### Custom Parameter Parsing
67
+
68
+ Sometimes the values you get from the query parameters aren't quite good enough. They may need to be massaged in order to work with your scopes and class methods. In those cases, you can provide a `:parser` option to your `scope_accessible` method.
69
+
70
+ Parsers must respond to the `call` method, receiving the raw query parameter and returning an array of arguments to pass to the scope or class method.
71
+
72
+ ```ruby
73
+ class User < ActiveRecord::Base
74
+ scope :gender, lambda{|g| where(gender: g) }
75
+
76
+ scope_accessible :gender, parser: lambda{|g| [g.downcase] }
77
+ end
78
+ ```
79
+
80
+ ### On/Off Scopes
81
+
82
+ But not all scopes accept arguments. For scopes that you want to toggle on or off, you can set a `:boolean => true` option. Whenever the received parameter is truthy, the scope will be applied. Otherwise, it will be skipped.
83
+
84
+ ```ruby
85
+ class User < ActiveRecord::Base
86
+ scope :male, where(gender: 'male')
87
+ scope :female, where(gender: 'female')
88
+
89
+ scope_accessible :male, :female, boolean: true
90
+ end
91
+ ```
92
+
93
+ ### Custom Method Names
94
+
95
+ Sometimes the query parameters you want to open up to your users may collide with existing method names or reserved Ruby words. In order to avoid collision, you can set a `:method` option to specify what method to use for a query parameter.
96
+
97
+ ```ruby
98
+ class Project < ActiveRecord::Base
99
+ scope_accessible :begin, method: :begins_after
100
+ scope_accessible :end, method: :ends_before
101
+
102
+ def self.begins_after(date)
103
+ where('begins_at >= ?', date)
104
+ end
105
+
106
+ def self.ends_before(date)
107
+ where('ends_at <= ?', date)
108
+ end
109
+ end
110
+ ```
111
+
112
+ Alternatively, you can set `:prefix` and/or `:suffix` options, which will be applied to the query parameter name to determine the corresponding method name.
113
+
114
+ ```ruby
115
+ class Project < ActiveRecord::Base
116
+ scope_accessible :begin, :end, suffix: '_date'
117
+
118
+ def self.begin_date(date)
119
+ where('begins_at >= ?', date)
120
+ end
121
+
122
+ def self.end_date(date)
123
+ where('ends_at <= ?', date)
124
+ end
125
+ end
126
+ ```
127
+
128
+ ## This sucks. How can I make it better?
129
+
130
+ 1. Fork it.
131
+ 2. Make it better.
132
+ 3. Send me a pull request.
@@ -1,6 +1,43 @@
1
- require 'active_support'
2
- require 'periscope/version'
1
+ module Periscope
2
+ def scope_accessible(*scopes)
3
+ options = scopes.last.is_a?(Hash) ? scopes.pop : {}
4
+ scopes.each{|s| periscope_options[s.to_s] = options }
5
+ end
3
6
 
4
- Dir[File.expand_path('../periscope/adapters/**/*.rb', __FILE__)].each{|f| require f }
7
+ def periscope(params = {})
8
+ params.inject(periscope_default_scope) do |chain, (scope, param)|
9
+ periscope_call(chain, scope.to_s, param)
10
+ end
11
+ end
5
12
 
6
- ActiveRecord::Base.send(:include, Periscope::Adapters::ActiveRecord) if defined?(ActiveRecord::Base)
13
+ private
14
+
15
+ def periscope_options
16
+ @periscope_options ||= {}
17
+ end
18
+
19
+ def periscope_default_scope
20
+ raise NotImplementedError
21
+ end
22
+
23
+ def periscope_call(chain, scope, param)
24
+ return chain unless options = periscope_options[scope]
25
+
26
+ method = periscope_method(scope, options)
27
+ values = periscope_values(param, options)
28
+
29
+ if options[:boolean]
30
+ values.first ? chain.send(method) : chain
31
+ else
32
+ chain.send(method, *values)
33
+ end
34
+ end
35
+
36
+ def periscope_method(scope, options)
37
+ options[:method] || [options[:prefix], scope, options[:suffix]].compact.join
38
+ end
39
+
40
+ def periscope_values(param, options)
41
+ options[:parser] ? options[:parser].call(param) : [param]
42
+ end
43
+ end
@@ -1,27 +1,22 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path('../lib', __FILE__)
3
- require 'periscope/version'
1
+ # encoding: utf-8
4
2
 
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.)
3
+ Gem::Specification.new do |gem|
4
+ gem.name = 'periscope'
5
+ gem.version = '1.0.0'
14
6
 
15
- s.rubyforge_project = 'periscope'
7
+ gem.authors = ['Steve Richert']
8
+ gem.email = ['steve.richert@gmail.com']
9
+ gem.description = %(Push your models' scopes up to the surface)
10
+ gem.summary = gem.description
11
+ gem.homepage = 'https://github.com/laserlemon/periscope'
16
12
 
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']
13
+ gem.add_development_dependency 'rake', '~> 0.9'
14
+ gem.add_development_dependency 'rspec', '~> 2.0'
21
15
 
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'
16
+ gem.files = %w(
17
+ LICENSE
18
+ lib/periscope.rb
19
+ periscope.gemspec
20
+ README.md
21
+ )
27
22
  end
metadata CHANGED
@@ -1,148 +1,87 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
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
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
10
6
  platform: ruby
11
- authors:
7
+ authors:
12
8
  - Steve Richert
13
9
  autorequire:
14
10
  bindir: bin
15
11
  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
12
+ date: 2012-06-29 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
39
17
  none: false
40
- requirements:
41
- - - ">="
42
- - !ruby/object:Gem::Version
43
- segments:
44
- - 0
45
- version: "0"
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '0.9'
46
22
  type: :development
47
- version_requirements: *id002
48
- - !ruby/object:Gem::Dependency
49
- name: sqlite3
50
23
  prerelease: false
51
- requirement: &id003 !ruby/object:Gem::Requirement
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '0.9'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
52
33
  none: false
53
- requirements:
54
- - - ">="
55
- - !ruby/object:Gem::Version
56
- segments:
57
- - 0
58
- version: "0"
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '2.0'
59
38
  type: :development
60
- version_requirements: *id003
61
- - !ruby/object:Gem::Dependency
62
- name: activerecord
63
39
  prerelease: false
64
- requirement: &id004 !ruby/object:Gem::Requirement
40
+ version_requirements: !ruby/object:Gem::Requirement
65
41
  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:
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '2.0'
46
+ description: Push your models' scopes up to the surface
47
+ email:
78
48
  - steve.richert@gmail.com
79
49
  executables: []
80
-
81
50
  extensions: []
82
-
83
51
  extra_rdoc_files: []
84
-
85
- files:
86
- - .gitignore
87
- - Gemfile
88
- - README.rdoc
89
- - Rakefile
90
- - init.rb
52
+ files:
53
+ - LICENSE
91
54
  - 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
55
  - 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
56
+ - README.md
108
57
  homepage: https://github.com/laserlemon/periscope
109
58
  licenses: []
110
-
111
59
  post_install_message:
112
60
  rdoc_options: []
113
-
114
- require_paths:
61
+ require_paths:
115
62
  - lib
116
- required_ruby_version: !ruby/object:Gem::Requirement
63
+ required_ruby_version: !ruby/object:Gem::Requirement
117
64
  none: false
118
- requirements:
119
- - - ">="
120
- - !ruby/object:Gem::Version
121
- segments:
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ segments:
122
70
  - 0
123
- version: "0"
124
- required_rubygems_version: !ruby/object:Gem::Requirement
71
+ hash: -4385253506114328427
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
73
  none: false
126
- requirements:
127
- - - ">="
128
- - !ruby/object:Gem::Version
129
- segments:
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ segments:
130
79
  - 0
131
- version: "0"
80
+ hash: -4385253506114328427
132
81
  requirements: []
133
-
134
- rubyforge_project: periscope
135
- rubygems_version: 1.3.7
82
+ rubyforge_project:
83
+ rubygems_version: 1.8.24
136
84
  signing_key:
137
85
  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
86
+ summary: Push your models' scopes up to the surface
87
+ test_files: []
data/.gitignore DELETED
@@ -1,4 +0,0 @@
1
- *.gem
2
- .bundle
3
- Gemfile.lock
4
- pkg/*
data/Gemfile DELETED
@@ -1,3 +0,0 @@
1
- source :rubygems
2
-
3
- gemspec
@@ -1,58 +0,0 @@
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 DELETED
@@ -1,2 +0,0 @@
1
- require 'bundler'
2
- Bundler::GemHelper.install_tasks
data/init.rb DELETED
@@ -1 +0,0 @@
1
- require 'periscope'
@@ -1,67 +0,0 @@
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
@@ -1,18 +0,0 @@
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
@@ -1,34 +0,0 @@
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
@@ -1,27 +0,0 @@
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
@@ -1,3 +0,0 @@
1
- module Periscope
2
- VERSION = '0.1.0'
3
- end
@@ -1,129 +0,0 @@
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
@@ -1,19 +0,0 @@
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
@@ -1,10 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Periscope
4
- describe VERSION do
5
- it 'is a major/minor/patch version string' do
6
- should be_a String
7
- should match /^\d+\.\d+\.\d+$/
8
- end
9
- end
10
- end
@@ -1,19 +0,0 @@
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
@@ -1,14 +0,0 @@
1
- Dir[File.expand_path('../support/**/*.rb', __FILE__)].each{|f| require f }
2
-
3
- require 'periscope'
4
-
5
- class FakeLogger
6
- def debug(message)
7
- output << message
8
- end
9
-
10
- def output
11
- @output ||= []
12
- end
13
- end
14
-
@@ -1,89 +0,0 @@
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
@@ -1,17 +0,0 @@
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
@@ -1,35 +0,0 @@
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
@@ -1,66 +0,0 @@
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