scoped_attr_accessible 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/.bundle/config +2 -0
- data/.gitignore +1 -0
- data/.rspec +3 -0
- data/.rvmrc +1 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +43 -0
- data/LICENSE +20 -0
- data/README.md +150 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/autotest/discover.rb +7 -0
- data/lib/scoped_attr_accessible/active_model_mixin.rb +109 -0
- data/lib/scoped_attr_accessible/sanitizer.rb +80 -0
- data/lib/scoped_attr_accessible.rb +15 -0
- data/spec/scoped_attr_accessible/active_model_mixin_spec.rb +314 -0
- data/spec/scoped_attr_accessible/sanitizer_spec.rb +185 -0
- data/spec/scoped_attr_accessible_spec.rb +15 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/custom_matchers.rb +26 -0
- metadata +122 -0
data/.bundle/config
ADDED
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
coverage
|
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use @scopedattraccessible --create
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
ZenTest (4.4.0)
|
5
|
+
activemodel (3.0.0)
|
6
|
+
activesupport (= 3.0.0)
|
7
|
+
builder (~> 2.1.2)
|
8
|
+
i18n (~> 0.4.1)
|
9
|
+
activesupport (3.0.0)
|
10
|
+
builder (2.1.2)
|
11
|
+
columnize (0.3.1)
|
12
|
+
diff-lcs (1.1.2)
|
13
|
+
i18n (0.4.1)
|
14
|
+
linecache (0.43)
|
15
|
+
rcov (0.9.9)
|
16
|
+
rr (1.0.0)
|
17
|
+
rspec (2.0.0)
|
18
|
+
rspec-core (= 2.0.0)
|
19
|
+
rspec-expectations (= 2.0.0)
|
20
|
+
rspec-mocks (= 2.0.0)
|
21
|
+
rspec-core (2.0.0)
|
22
|
+
rspec-expectations (2.0.0)
|
23
|
+
diff-lcs (>= 1.1.2)
|
24
|
+
rspec-mocks (2.0.0)
|
25
|
+
rspec-core (= 2.0.0)
|
26
|
+
rspec-expectations (= 2.0.0)
|
27
|
+
ruby-debug (0.10.3)
|
28
|
+
columnize (>= 0.1)
|
29
|
+
ruby-debug-base (~> 0.10.3.0)
|
30
|
+
ruby-debug-base (0.10.3)
|
31
|
+
linecache (>= 0.3)
|
32
|
+
|
33
|
+
PLATFORMS
|
34
|
+
ruby
|
35
|
+
|
36
|
+
DEPENDENCIES
|
37
|
+
ZenTest
|
38
|
+
activemodel (~> 3.0.0)
|
39
|
+
activesupport (~> 3.0.0)
|
40
|
+
rcov
|
41
|
+
rr
|
42
|
+
rspec (~> 2.0)
|
43
|
+
ruby-debug
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 The Frontier Group.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
# scoped\_attr\_accessible
|
2
|
+
|
3
|
+
scoped\_attr\_accessible is a plugin that makes it easy to scope the `attr_accessible` and `attr_protected`
|
4
|
+
methods on any library using ActiveModel's MassAssignmentSecurity module. For those unfamiliar with it,
|
5
|
+
[read here](http://api.rubyonrails.org/classes/ActiveModel/MassAssignmentSecurity/ClassMethods.html#method-i-attr_accessible)
|
6
|
+
to get a bit of back story about how it works in ActiveRecord - thanks to ActiveModel, you can
|
7
|
+
now get the joy of scoped access restrictions across any ORM built on ActiveModel, including [mongoid](http://mongoid.org/)!
|
8
|
+
|
9
|
+
## Installation ##
|
10
|
+
|
11
|
+
|
12
|
+
To use, just add to any application using ActiveModel. In Rails 3, this is a simple job of adding:
|
13
|
+
|
14
|
+
gem 'scoped_attr_accessible'
|
15
|
+
|
16
|
+
To our Gemfile and running `bundle install`.
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
With it enabled, your application should continue to work as usual with classic `attr_accessible` and `attr_protected`.
|
21
|
+
When in use, you can simply pass the `:scope` option in your declaration to declare a scope in which it should be accessible.
|
22
|
+
|
23
|
+
For example,
|
24
|
+
|
25
|
+
class User < ActiveRecord::Base
|
26
|
+
|
27
|
+
# All attributes are accessible for the admin scope.
|
28
|
+
attr_accessible :all, :scope => :admin
|
29
|
+
|
30
|
+
# The default scope can only access a and b.
|
31
|
+
attr_accessible :a, :b
|
32
|
+
|
33
|
+
# Make both :c and :d accessible for owners and the default scope
|
34
|
+
attr_accessible :c, :d, :scope => [:owner, :default]
|
35
|
+
|
36
|
+
# Also, it works the same with attr_protected!
|
37
|
+
attr_protected :n, :scope => :default
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
If both `attr_accessible` and `attr_protected` are used on a given scope, attributes
|
42
|
+
declared in `attr_protected` take precedence. Also, If `attr_accessible` isn't called for a scope
|
43
|
+
at all, it will allow all variables except those marked as protected.
|
44
|
+
|
45
|
+
When declaring the scopes in the accessible / protected part, please note that they need to
|
46
|
+
be symbol names for simplicity's sake.
|
47
|
+
|
48
|
+
### Setting the Scope
|
49
|
+
|
50
|
+
Next, when you call methods that use mass assignment (e.g. `ActiveRecord::Base#attributes=`),
|
51
|
+
it will use your current scope to sanitize mass-assigned variables. By default, with no
|
52
|
+
user intervention this scope is simply `:default`.
|
53
|
+
|
54
|
+
To set the scope, you can do so on a class an instance level with instance-level taking precedence.
|
55
|
+
|
56
|
+
To set it on a class level, simply do:
|
57
|
+
|
58
|
+
User.current_sanitizer_scope = :admin
|
59
|
+
# Or, dynamically:
|
60
|
+
User.current_sanitizer_scope = @user.role.name.to_sym
|
61
|
+
|
62
|
+
This will be set Thread local. Also note you can get the current class-level scope:
|
63
|
+
|
64
|
+
p User.current_sanitizer_scope # => nil by default
|
65
|
+
|
66
|
+
Or, temporarily switch it out, resetting it afterwards:
|
67
|
+
|
68
|
+
p User.current_sanitizer_scope
|
69
|
+
User.with_sanitizer_scope :admin do
|
70
|
+
p User.current_sanitizer_scope
|
71
|
+
end
|
72
|
+
p User.current_sanitizer_scope
|
73
|
+
|
74
|
+
You can also declare this on the instance level, e.g:
|
75
|
+
|
76
|
+
user = User.find(params[:id])
|
77
|
+
user.current_sanitizer_scope = :admin
|
78
|
+
# Or, more complex:
|
79
|
+
user.current_sanitizer_scope = "something-else"
|
80
|
+
|
81
|
+
### Complex Scoping
|
82
|
+
|
83
|
+
Although the scope on a given accessible / protected declaration must be a symbol,
|
84
|
+
scoped\_attr\_accessible provides a way to deal with non-symbol scopes when assigning them - Namely,
|
85
|
+
you can set the `current_sanitizer_scope` value on classes or instances to an
|
86
|
+
arbitrary object and let scoped\_attr\_accessible dynamically convert it for you.
|
87
|
+
|
88
|
+
This is done using two seperate processes - Recognizers and Converters, each run when a given
|
89
|
+
scope is not a symbol.
|
90
|
+
|
91
|
+
The first of these (and the highest priority) are recognizers - they are simply blocks you
|
92
|
+
can declare (like below) that have a scope name and return a value denoting whether or not they
|
93
|
+
match. e.g:
|
94
|
+
|
95
|
+
# Reeopen the class
|
96
|
+
class User < ActiveRecord::Base
|
97
|
+
|
98
|
+
sanitizer_scope_recognizer :admin do |record, scope_value|
|
99
|
+
scope_value.is_a?(User) && user.admin?
|
100
|
+
end
|
101
|
+
|
102
|
+
sanitizer_scope_recognizer :owner do |record, scope_value|
|
103
|
+
scope_value.is_a?(User) && scope_value == record
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
In this example, we could simply do:
|
109
|
+
|
110
|
+
user = User.find(params[:id])
|
111
|
+
user.current_sanitizer_scope = current_user
|
112
|
+
user.update_attributes params[:user]
|
113
|
+
|
114
|
+
And it would automatically set the scope to :owner / :admin when sanitizing the attributes.
|
115
|
+
|
116
|
+
The second and more flexible option is scope convertors - they're given the same information (e.g.
|
117
|
+
a record and scope value) and they are responsible for returning nil or a reduced form of the scope.
|
118
|
+
If they return a reduced form (e.g. they may return their creating user, or a plain symbol) it is
|
119
|
+
smart enough to reduce it until it does have a symbol.
|
120
|
+
|
121
|
+
As an example, we could implement the following:
|
122
|
+
|
123
|
+
# Reeopen the class
|
124
|
+
class User < ActiveRecord::Base
|
125
|
+
|
126
|
+
sanitizer_scope_converter do |record, scope_value|
|
127
|
+
return user.role.name.to_sym if scope_value.is_a?(User)
|
128
|
+
return scope_value.user if scope_value.is_a?(UserSession)
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
When combined, these all form a very flexible way to dynamically scope attribute accessible.
|
134
|
+
|
135
|
+
## Note on Patches/Pull Requests
|
136
|
+
|
137
|
+
* Fork the project.
|
138
|
+
* Make your feature addition or bug fix.
|
139
|
+
* Add tests for it. This is important so I don't break it in a future version unintentionally.
|
140
|
+
* Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
141
|
+
* Send me a pull request. Bonus points for topic branches.
|
142
|
+
|
143
|
+
## Contributors
|
144
|
+
|
145
|
+
* Darcy Laycock
|
146
|
+
* Mario Visic
|
147
|
+
|
148
|
+
## Copyright
|
149
|
+
|
150
|
+
Copyright (c) 2010 The Frontier Group. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "scoped_attr_accessible"
|
8
|
+
gem.summary = %Q{Scoping for attr_accessible and attr_protected on ActiveModel objects.}
|
9
|
+
gem.description = %Q{scoped_attr_accessible is a plugin that makes it easy to scope the `attr_accessible` and `attr_protected`
|
10
|
+
methods on any library using ActiveModel's MassAssignmentSecurity module.}
|
11
|
+
gem.email = "team+darcy+mario@thefrontiergroup.com.au"
|
12
|
+
gem.homepage = "http://github.com/thefrontiergroup/scoped_attr_accessible"
|
13
|
+
gem.authors = ["Darcy Laycock", "Mario Visic"]
|
14
|
+
gem.add_dependency "activemodel", "~> 3.0"
|
15
|
+
gem.add_development_dependency "rspec", "~> 2.0"
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'rspec/core'
|
23
|
+
require 'rspec/core/rake_task'
|
24
|
+
task :default => :spec
|
25
|
+
|
26
|
+
desc "Run all specs in spec directory (excluding plugin specs)"
|
27
|
+
RSpec::Core::RakeTask.new(:spec)
|
28
|
+
|
29
|
+
namespace :spec do
|
30
|
+
desc "Run all specs with rcov"
|
31
|
+
RSpec::Core::RakeTask.new(:rcov) do |t|
|
32
|
+
t.rcov = true
|
33
|
+
t.pattern = "./spec/**/*_spec.rb"
|
34
|
+
t.rcov_opts = '--exclude spec/,/gems/,/Library/,/usr/,lib/tasks,.bundle,config,/lib/rspec/,/lib/rspec-'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
require 'rake/rdoctask'
|
41
|
+
Rake::RDocTask.new do |rdoc|
|
42
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
43
|
+
rdoc.rdoc_dir = 'rdoc'
|
44
|
+
rdoc.title = "scoped_attr_accessible #{version}"
|
45
|
+
rdoc.rdoc_files.include('README*')
|
46
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
47
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/core_ext/class/inheritable_attributes'
|
3
|
+
require 'active_support/core_ext/array/extract_options'
|
4
|
+
|
5
|
+
module ScopedAttrAccessible
|
6
|
+
module ActiveModelMixin
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
|
10
|
+
module IncludedHook
|
11
|
+
def included(base)
|
12
|
+
super
|
13
|
+
base.class_eval { include ActiveModelMixin }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
included do
|
18
|
+
extlib_inheritable_accessor :_scoped_attr_sanitizer
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
|
23
|
+
def attr_accessible(*args)
|
24
|
+
scopes = scopes_from_args(args)
|
25
|
+
sanitizer = self.scoped_attr_sanitizer
|
26
|
+
args.each do |attribute|
|
27
|
+
scopes.each { |s| sanitizer.make_accessible attribute, s }
|
28
|
+
end
|
29
|
+
self._active_authorizer = sanitizer
|
30
|
+
end
|
31
|
+
|
32
|
+
def attr_protected(*args)
|
33
|
+
scopes = scopes_from_args(args)
|
34
|
+
sanitizer = self.scoped_attr_sanitizer
|
35
|
+
args.each do |attribute|
|
36
|
+
scopes.each { |s| sanitizer.make_protected attribute, s }
|
37
|
+
end
|
38
|
+
self._active_authorizer = sanitizer
|
39
|
+
end
|
40
|
+
|
41
|
+
def scoped_attr_sanitizer
|
42
|
+
self._scoped_attr_sanitizer ||= ScopedAttrAccessible::Sanitizer.new
|
43
|
+
end
|
44
|
+
|
45
|
+
def current_sanitizer_scope
|
46
|
+
Thread.current[current_sanitizer_scope_key]
|
47
|
+
end
|
48
|
+
|
49
|
+
def current_sanitizer_scope=(value)
|
50
|
+
Thread.current[current_sanitizer_scope_key] = value
|
51
|
+
end
|
52
|
+
|
53
|
+
def with_sanitizer_scope(scope_name)
|
54
|
+
old_scope = current_sanitizer_scope
|
55
|
+
self.current_sanitizer_scope = scope_name
|
56
|
+
yield if block_given?
|
57
|
+
ensure
|
58
|
+
self.current_sanitizer_scope = old_scope
|
59
|
+
end
|
60
|
+
|
61
|
+
def sanitizer_scope_recognizer(name, &recognizer)
|
62
|
+
scoped_attr_sanitizer.define_recognizer(name, &recognizer)
|
63
|
+
end
|
64
|
+
|
65
|
+
def sanitizer_scope_converter(&converter)
|
66
|
+
scoped_attr_sanitizer.define_converter(&converter)
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
def current_sanitizer_scope_key
|
72
|
+
:"#{name}_sanitizer_scope"
|
73
|
+
end
|
74
|
+
|
75
|
+
def scopes_from_args(args)
|
76
|
+
options = args.extract_options!
|
77
|
+
scope = Array(options.delete(:scope)).map(&:to_sym)
|
78
|
+
scope << :default if scope.empty?
|
79
|
+
args << options unless options.empty?
|
80
|
+
scope
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
module InstanceMethods
|
86
|
+
|
87
|
+
def current_sanitizer_scope
|
88
|
+
@current_sanitizer_scope || self.class.current_sanitizer_scope || :default
|
89
|
+
end
|
90
|
+
|
91
|
+
def current_sanitizer_scope=(value)
|
92
|
+
@current_sanitizer_scope = value
|
93
|
+
end
|
94
|
+
|
95
|
+
protected
|
96
|
+
|
97
|
+
def sanitize_for_mass_assignment(attributes)
|
98
|
+
authorizer = self.mass_assignment_authorizer
|
99
|
+
if authorizer.respond_to?(:sanitize_with_scope)
|
100
|
+
authorizer.sanitize_with_scope attributes, current_sanitizer_scope, self
|
101
|
+
else
|
102
|
+
super
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module ScopedAttrAccessible
|
4
|
+
class Sanitizer
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@accessible_attributes = Hash.new { |h,k| h[k] = Set.new }
|
8
|
+
@protected_attributes = Hash.new { |h,k| h[k] = Set.new }
|
9
|
+
# Scope recognizers return a boolean, with a hash key
|
10
|
+
@scope_recognizers = Hash.new { |h,k| h[k] = [] }
|
11
|
+
# Returns a scope symbol.
|
12
|
+
@scope_converters = []
|
13
|
+
end
|
14
|
+
|
15
|
+
# Looks up a scope name from the registered recognizers and then from the converters.
|
16
|
+
def normalize_scope(object, context)
|
17
|
+
return object if object.is_a?(Symbol)
|
18
|
+
# 1. Process recognizers, looking for a match.
|
19
|
+
@scope_recognizers.each_pair do |name, recognizers|
|
20
|
+
return name if recognizers.any? { |r| lambda(&r).call(context, object) }
|
21
|
+
end
|
22
|
+
# 2. Process converters, finding a result.
|
23
|
+
@scope_converters.each do |converter|
|
24
|
+
scope = lambda(&converter).call(context, object)
|
25
|
+
return normalize_scope(scope, converter) unless scope.nil?
|
26
|
+
end
|
27
|
+
# 3. Fall back to default
|
28
|
+
return :default
|
29
|
+
end
|
30
|
+
|
31
|
+
def sanitize(attributes, context = Object.new)
|
32
|
+
sanitize_with_scope attributes, :default, context
|
33
|
+
end
|
34
|
+
|
35
|
+
def sanitize_with_scope(attributes, scope, context)
|
36
|
+
scope = normalize_scope scope, context
|
37
|
+
attributes.reject { |k, v| deny? k, scope }
|
38
|
+
end
|
39
|
+
|
40
|
+
def define_recognizer(scope, &blk)
|
41
|
+
@scope_recognizers[scope.to_sym] << blk
|
42
|
+
end
|
43
|
+
|
44
|
+
def define_converter(&blk)
|
45
|
+
@scope_converters << blk
|
46
|
+
end
|
47
|
+
|
48
|
+
def make_protected(attribute, scope = :default)
|
49
|
+
@protected_attributes[scope.to_sym] << attribute.to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
def make_accessible(attribute, scope = :default)
|
53
|
+
@accessible_attributes[scope.to_sym] << attribute.to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
def deny?(attribute, scope = :default)
|
57
|
+
!attribute_assignable_with_scope?(attribute, scope)
|
58
|
+
end
|
59
|
+
|
60
|
+
def allow?(attribute, scope = :default)
|
61
|
+
attribute_assignable_with_scope?(attribute, scope)
|
62
|
+
end
|
63
|
+
|
64
|
+
def attribute_assignable_with_scope?(attribute, scope)
|
65
|
+
attribute = attribute.to_s.gsub(/\(.+/, '')
|
66
|
+
scope = scope.to_sym
|
67
|
+
scope_protected, scope_accessible = @protected_attributes[scope], @accessible_attributes[scope]
|
68
|
+
if scope_protected.include? attribute
|
69
|
+
return false
|
70
|
+
elsif scope_accessible.include?('all') || scope_accessible.include?(attribute)
|
71
|
+
return true
|
72
|
+
elsif !scope_accessible.empty?
|
73
|
+
return false
|
74
|
+
else
|
75
|
+
return true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module ScopedAttrAccessible
|
4
|
+
autoload :Sanitizer, 'scoped_attr_accessible/sanitizer'
|
5
|
+
autoload :ActiveModelMixin, 'scoped_attr_accessible/active_model_mixin'
|
6
|
+
|
7
|
+
# Mixes the am mixin into ActiveModel's mass assignment helpers.
|
8
|
+
def self.mixin!
|
9
|
+
require 'active_model/mass_assignment_security'
|
10
|
+
ActiveModel::MassAssignmentSecurity.module_eval do
|
11
|
+
extend ScopedAttrAccessible::ActiveModelMixin::IncludedHook
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,314 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ScopedAttrAccessible::ActiveModelMixin do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
ScopedAttrAccessible.mixin!
|
7
|
+
end
|
8
|
+
|
9
|
+
subject { Class.new { include ActiveModel::MassAssignmentSecurity } }
|
10
|
+
|
11
|
+
let(:subject_instance) { subject.new }
|
12
|
+
|
13
|
+
context 'attr_accessible overrides' do
|
14
|
+
|
15
|
+
before :each do
|
16
|
+
@sanitizer = ScopedAttrAccessible::Sanitizer.new
|
17
|
+
stub(subject).scoped_attr_sanitizer { @sanitizer }
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should call make_accessible' do
|
21
|
+
mock(@sanitizer).make_accessible(:a, :x)
|
22
|
+
subject.attr_accessible :a, :scope => :x
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should default the scope' do
|
26
|
+
mock(@sanitizer).make_accessible(:a, :default)
|
27
|
+
subject.attr_accessible :a
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should call it correctly when given multiple scopes' do
|
31
|
+
mock(@sanitizer).make_accessible(:a, :admin)
|
32
|
+
mock(@sanitizer).make_accessible(:a, :normal)
|
33
|
+
subject.attr_accessible :a, :scope => [:admin, :normal]
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should call make_accessible for each attribute' do
|
37
|
+
mock(@sanitizer).make_accessible(:a, :default)
|
38
|
+
mock(@sanitizer).make_accessible(:b, :default)
|
39
|
+
subject.attr_accessible :a, :b
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should set the active authorizer' do
|
43
|
+
subject._active_authorizer = nil
|
44
|
+
subject.attr_accessible :a, :b
|
45
|
+
subject._active_authorizer.should == @sanitizer
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'attr_protected overrides' do
|
51
|
+
|
52
|
+
before :each do
|
53
|
+
@sanitizer = ScopedAttrAccessible::Sanitizer.new
|
54
|
+
stub(subject).scoped_attr_sanitizer { @sanitizer }
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should call make_protected' do
|
58
|
+
mock(@sanitizer).make_protected(:a, :x)
|
59
|
+
subject.attr_protected :a, :scope => :x
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should default the scope' do
|
63
|
+
mock(@sanitizer).make_protected(:a, :default)
|
64
|
+
subject.attr_protected :a
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should call it correctly when given multiple scopes' do
|
68
|
+
mock(@sanitizer).make_protected(:a, :admin)
|
69
|
+
mock(@sanitizer).make_protected(:a, :normal)
|
70
|
+
subject.attr_protected :a, :scope => [:admin, :normal]
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should call make_protected for each attribute' do
|
74
|
+
mock(@sanitizer).make_protected(:a, :default)
|
75
|
+
mock(@sanitizer).make_protected(:b, :default)
|
76
|
+
subject.attr_protected :a, :b
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should set the active authorizer' do
|
80
|
+
subject._active_authorizer = nil
|
81
|
+
subject.attr_protected :a, :b
|
82
|
+
subject._active_authorizer.should == @sanitizer
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'getting the current sanitizer' do
|
88
|
+
|
89
|
+
it 'should create a new sanitizer if not present' do
|
90
|
+
subject._scoped_attr_sanitizer = nil
|
91
|
+
@sanitizer = ScopedAttrAccessible::Sanitizer.new
|
92
|
+
mock(ScopedAttrAccessible::Sanitizer).new { @sanitizer }
|
93
|
+
subject.scoped_attr_sanitizer.should equal(@sanitizer)
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'should memoize the sanitizer' do
|
97
|
+
sanitizer = subject.scoped_attr_sanitizer
|
98
|
+
sanitizer_two = subject.scoped_attr_sanitizer
|
99
|
+
sanitizer.should equal(sanitizer)
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'current sanitizer scope' do
|
105
|
+
|
106
|
+
it 'should let you get the default sanitizer scope' do
|
107
|
+
subject.current_sanitizer_scope.should be_nil
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should let you set the sanitizer scope' do
|
111
|
+
subject.current_sanitizer_scope = :a
|
112
|
+
subject.current_sanitizer_scope.should == :a
|
113
|
+
subject.current_sanitizer_scope = :b
|
114
|
+
subject.current_sanitizer_scope.should == :b
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should let you temporary change the sanitizer scope' do
|
118
|
+
subject.current_sanitizer_scope = :before
|
119
|
+
inner_called = false
|
120
|
+
subject.with_sanitizer_scope :after do
|
121
|
+
inner_called = true
|
122
|
+
subject.current_sanitizer_scope.should == :after
|
123
|
+
end
|
124
|
+
inner_called.should be_true
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'should reset the scope after a temporary change' do
|
128
|
+
subject.current_sanitizer_scope = :before
|
129
|
+
inner_called = false
|
130
|
+
subject.with_sanitizer_scope :after do
|
131
|
+
inner_called = true
|
132
|
+
end
|
133
|
+
inner_called.should be_true
|
134
|
+
subject.current_sanitizer_scope.should == :before
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'instance level scoping' do
|
140
|
+
|
141
|
+
before :each do
|
142
|
+
subject.current_sanitizer_scope = :class_level
|
143
|
+
subject_instance.current_sanitizer_scope = :instance_level
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'should use the instance scope if present' do
|
147
|
+
subject_instance.current_sanitizer_scope.should == :instance_level
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'should fallback to the class scope if the instance scope is not present' do
|
151
|
+
subject_instance.current_sanitizer_scope = nil
|
152
|
+
subject_instance.current_sanitizer_scope.should == :class_level
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'should fallback to default if no scope is set' do
|
156
|
+
subject_instance.current_sanitizer_scope = nil
|
157
|
+
subject.current_sanitizer_scope = nil
|
158
|
+
subject_instance.current_sanitizer_scope.should == :default
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'should let you assign the instance level scope' do
|
162
|
+
subject_instance.current_sanitizer_scope = :after
|
163
|
+
subject_instance.current_sanitizer_scope.should == :after
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
context 'recognizers and converters' do
|
169
|
+
|
170
|
+
before :each do
|
171
|
+
@sanitizer = ScopedAttrAccessible::Sanitizer.new
|
172
|
+
stub(subject).scoped_attr_sanitizer { @sanitizer }
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'should call the sanitizers methods when defining a converter' do
|
176
|
+
mock(@sanitizer).define_converter
|
177
|
+
subject.sanitizer_scope_converter {}
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'should call the sanitizers methods when defining a recognizer' do
|
181
|
+
mock(@sanitizer).define_recognizer(:admin)
|
182
|
+
subject.sanitizer_scope_recognizer(:admin) { }
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
|
187
|
+
context 'hooking into the process' do
|
188
|
+
|
189
|
+
subject do
|
190
|
+
Class.new do
|
191
|
+
include ActiveModel::MassAssignmentSecurity
|
192
|
+
|
193
|
+
attr_accessible :a, :b, :c
|
194
|
+
attr_accessible :a, :b, :c, :d, :scope => :owner
|
195
|
+
attr_accessible :all, :scope => :admin
|
196
|
+
attr_protected :c, :d, :scope => :guy_who_deletes_stuff
|
197
|
+
|
198
|
+
def sanitized_keys_from(hash)
|
199
|
+
sanitize_for_mass_assignment(hash).keys
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
let(:subject_instance) { subject.new }
|
205
|
+
|
206
|
+
let :example_attributes do
|
207
|
+
{'a' => 'a', 'b' => 'c', 'c' => 'c', 'd' => 'd', 'e' => 'e'}
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'should return the correct attributes for the default scope' do
|
211
|
+
subject_instance.current_sanitizer_scope = nil
|
212
|
+
subject.current_sanitizer_scope = nil
|
213
|
+
subject_instance.sanitized_keys_from(example_attributes).should == %w(a b c)
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'should return the correct attributes for the owner scope' do
|
217
|
+
subject_instance.current_sanitizer_scope = :owner
|
218
|
+
subject_instance.sanitized_keys_from(example_attributes).should == %w(a b c d)
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'should return the correct attributes for the admin scope' do
|
222
|
+
subject_instance.current_sanitizer_scope = :admin
|
223
|
+
subject_instance.sanitized_keys_from(example_attributes).should == %w(a b c d e)
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'should return the correct attributes for an unknown scope' do
|
227
|
+
subject_instance.current_sanitizer_scope = :unknown_scope
|
228
|
+
subject_instance.sanitized_keys_from(example_attributes).should == %w(a b c d e)
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'should return the correct attributes for the guy_who_deletes_stuff scope' do
|
232
|
+
subject_instance.current_sanitizer_scope = :guy_who_deletes_stuff
|
233
|
+
subject_instance.sanitized_keys_from(example_attributes).should == %w(a b e)
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
237
|
+
|
238
|
+
context 'tying it all together' do
|
239
|
+
|
240
|
+
subject do
|
241
|
+
Class.new do
|
242
|
+
include ActiveModel::MassAssignmentSecurity
|
243
|
+
|
244
|
+
attr_accessible :a, :b, :c
|
245
|
+
attr_accessible :a, :b, :c, :d, :scope => :owner
|
246
|
+
attr_accessible :all, :scope => :admin
|
247
|
+
attr_protected :c, :d, :scope => :guy_who_deletes_stuff
|
248
|
+
|
249
|
+
sanitizer_scope_recognizer :owner do |record, value|
|
250
|
+
value.is_a?(Numeric)
|
251
|
+
end
|
252
|
+
|
253
|
+
sanitizer_scope_converter do |record, value|
|
254
|
+
return :admin if value == "hola"
|
255
|
+
return :guy_who_deletes_stuff if record.name == "Bob"
|
256
|
+
end
|
257
|
+
|
258
|
+
attr_accessor :name
|
259
|
+
|
260
|
+
def sanitized_keys_from(hash)
|
261
|
+
sanitize_for_mass_assignment(hash).keys
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
let(:subject_instance) { subject.new }
|
267
|
+
|
268
|
+
let :example_attributes do
|
269
|
+
{'a' => 'a', 'b' => 'c', 'c' => 'c', 'd' => 'd', 'e' => 'e'}
|
270
|
+
end
|
271
|
+
|
272
|
+
before :each do
|
273
|
+
subject_instance.current_sanitizer_scope = nil
|
274
|
+
subject.current_sanitizer_scope = nil
|
275
|
+
|
276
|
+
end
|
277
|
+
|
278
|
+
it 'should return the correct attributes for the default scope' do
|
279
|
+
subject_instance.sanitized_keys_from(example_attributes).should == %w(a b c)
|
280
|
+
end
|
281
|
+
|
282
|
+
it 'should return the correct attributes for the owner scope' do
|
283
|
+
subject_instance.current_sanitizer_scope = 12
|
284
|
+
subject_instance.sanitized_keys_from(example_attributes).should == %w(a b c d)
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'should return the correct attributes for the admin scope' do
|
288
|
+
subject_instance.current_sanitizer_scope = "hola"
|
289
|
+
subject_instance.sanitized_keys_from(example_attributes).should == %w(a b c d e)
|
290
|
+
end
|
291
|
+
|
292
|
+
it 'should return the correct attributes for the guy_who_deletes_stuff scope' do
|
293
|
+
subject_instance.name = "Bob"
|
294
|
+
subject_instance.current_sanitizer_scope = "trigger-it"
|
295
|
+
subject_instance.sanitized_keys_from(example_attributes).should == %w(a b e)
|
296
|
+
subject_instance.name = nil
|
297
|
+
end
|
298
|
+
|
299
|
+
it 'should fallback if with a different sanitizer' do
|
300
|
+
klass = Class.new(subject)
|
301
|
+
klass._active_authorizer = nil
|
302
|
+
instance = klass.new
|
303
|
+
instance.sanitized_keys_from(example_attributes).should == %w(a b c d e)
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'should not overwrite children class sanitizers' do
|
307
|
+
klass = Class.new(subject)
|
308
|
+
klass.attr_accessible :e, :scope => :owner
|
309
|
+
subject.scoped_attr_sanitizer.sanitize_with_scope(example_attributes, :owner, nil).should only_have_attributes('a', 'b', 'c', 'd')
|
310
|
+
end
|
311
|
+
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ScopedAttrAccessible::Sanitizer do
|
4
|
+
|
5
|
+
let :complex_sanitizer_one do
|
6
|
+
ScopedAttrAccessible::Sanitizer.new.tap do |s|
|
7
|
+
s.make_protected :a, :default
|
8
|
+
s.make_protected :b, :default
|
9
|
+
s.make_accessible :all, :default
|
10
|
+
s.make_protected :a, :admin
|
11
|
+
s.make_accessible :all, :admin
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
let :complex_sanitizer_two do
|
16
|
+
ScopedAttrAccessible::Sanitizer.new.tap do |s|
|
17
|
+
s.make_protected :d, :admin
|
18
|
+
s.make_accessible :all, :admin
|
19
|
+
s.make_accessible :a, :default
|
20
|
+
s.make_accessible :b, :default
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
let :example_attributes_one do
|
25
|
+
{:a => 'a', :b => 'c', :c => 'c', :d => 'd', :e => 'e'}
|
26
|
+
end
|
27
|
+
|
28
|
+
let :example_attributes_two do
|
29
|
+
{'a' => 'a', 'b' => 'c', 'c' => 'c', 'd' => 'd'}
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'normalizing scopes' do
|
33
|
+
|
34
|
+
subject { ScopedAttrAccessible::Sanitizer.new }
|
35
|
+
|
36
|
+
before :each do
|
37
|
+
subject.define_recognizer :a do |context, object|
|
38
|
+
object.to_s == "a" || context == "test context a"
|
39
|
+
end
|
40
|
+
subject.define_recognizer :b do |context, object|
|
41
|
+
object.to_s == "b" || context == "test context b"
|
42
|
+
end
|
43
|
+
subject.define_converter do |context, object|
|
44
|
+
return :number if object.is_a?(Numeric)
|
45
|
+
return :admin if !object.nil? && %(admin darcy).include?(object)
|
46
|
+
return :awesome if context == "another test context"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should return the argument if passed a symbol' do
|
51
|
+
subject.normalize_scope(:a, nil).should == :a
|
52
|
+
subject.normalize_scope(:another, nil).should == :another
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should let you provide optional context' do
|
56
|
+
subject.normalize_scope(nil, "test context a").should == :a
|
57
|
+
subject.normalize_scope(nil, "test context b").should == :b
|
58
|
+
subject.normalize_scope(nil, "another test context").should == :awesome
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should use recognizers in an attempt to find a scope' do
|
62
|
+
subject.normalize_scope("a", nil).should == :a
|
63
|
+
subject.normalize_scope("b", nil).should == :b
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should use converters in an attempt to find a scope' do
|
67
|
+
subject.normalize_scope(2, nil).should == :number
|
68
|
+
subject.normalize_scope(3.0, nil).should == :number
|
69
|
+
subject.normalize_scope('admin', nil).should == :admin
|
70
|
+
subject.normalize_scope('darcy', nil).should == :admin
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should return default when the item is unknown' do
|
74
|
+
subject.normalize_scope("unknown item here", nil).should == :default
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'marking attributes availability' do
|
80
|
+
|
81
|
+
def allow(*args)
|
82
|
+
be_allow(*args)
|
83
|
+
end
|
84
|
+
|
85
|
+
let :empty_sanitizer do
|
86
|
+
ScopedAttrAccessible::Sanitizer.new
|
87
|
+
end
|
88
|
+
|
89
|
+
let :accessible_sanitizer do
|
90
|
+
ScopedAttrAccessible::Sanitizer.new.tap do |s|
|
91
|
+
s.make_accessible :a, :default
|
92
|
+
s.make_accessible :b, :default
|
93
|
+
s.make_accessible :c, :admin
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
let :protected_sanitizer do
|
98
|
+
ScopedAttrAccessible::Sanitizer.new.tap do |s|
|
99
|
+
s.make_protected :a, :default
|
100
|
+
s.make_protected :b, :default
|
101
|
+
s.make_protected :c, :admin
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should return true by default an empty list' do
|
106
|
+
empty_sanitizer.should allow(:a)
|
107
|
+
empty_sanitizer.should allow(:b)
|
108
|
+
empty_sanitizer.should allow(:c)
|
109
|
+
empty_sanitizer.should allow(:d)
|
110
|
+
empty_sanitizer.should allow(:a, :admin)
|
111
|
+
empty_sanitizer.should allow(:b, :admin)
|
112
|
+
empty_sanitizer.should allow(:c, :admin)
|
113
|
+
empty_sanitizer.should allow(:d, :admin)
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should return false by default with an unknown attribute and an accessible list' do
|
117
|
+
accessible_sanitizer.should_not allow(:d)
|
118
|
+
accessible_sanitizer.should_not allow(:d, :admin)
|
119
|
+
accessible_sanitizer.should_not allow(:c)
|
120
|
+
accessible_sanitizer.should_not allow(:a, :admin)
|
121
|
+
accessible_sanitizer.should_not allow(:b, :admin)
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'should return false when it is protected' do
|
125
|
+
protected_sanitizer.should_not allow(:a, :default)
|
126
|
+
protected_sanitizer.should_not allow(:b, :default)
|
127
|
+
protected_sanitizer.should_not allow(:c, :admin)
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'should work with a string' do
|
131
|
+
complex_sanitizer_two.should allow('a')
|
132
|
+
complex_sanitizer_two.should allow('b')
|
133
|
+
complex_sanitizer_two.should_not allow('d', :admin)
|
134
|
+
complex_sanitizer_two.should allow('a', :admin)
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'should return true if it is in the accessible' do
|
138
|
+
accessible_sanitizer.should allow(:a)
|
139
|
+
accessible_sanitizer.should allow(:b)
|
140
|
+
accessible_sanitizer.should allow(:c, :admin)
|
141
|
+
accessible_sanitizer.should_not allow(:d)
|
142
|
+
accessible_sanitizer.should_not allow(:d, :admin)
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'should return true if all are accessible' do
|
146
|
+
complex_sanitizer_one.should_not allow(:a)
|
147
|
+
complex_sanitizer_one.should_not allow(:b)
|
148
|
+
complex_sanitizer_one.should allow(:c)
|
149
|
+
complex_sanitizer_one.should allow(:d)
|
150
|
+
complex_sanitizer_one.should_not allow(:a, :admin)
|
151
|
+
complex_sanitizer_one.should allow(:b, :admin)
|
152
|
+
complex_sanitizer_one.should allow(:c, :admin)
|
153
|
+
complex_sanitizer_one.should allow(:d, :admin)
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
context 'sanitize_with_scope' do
|
159
|
+
|
160
|
+
it 'should remove protected attributes' do
|
161
|
+
complex_sanitizer_one.sanitize_with_scope(example_attributes_one, :default, nil).should only_have_attributes('c', 'd', 'e')
|
162
|
+
complex_sanitizer_one.sanitize_with_scope(example_attributes_one, :admin, nil).should only_have_attributes('b', 'c', 'd', 'e')
|
163
|
+
complex_sanitizer_two.sanitize_with_scope(example_attributes_one, :default, nil).should only_have_attributes('a', 'b')
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
context 'sanitize' do
|
169
|
+
|
170
|
+
subject { ScopedAttrAccessible::Sanitizer.new }
|
171
|
+
|
172
|
+
it 'should call with the default scope' do
|
173
|
+
mock(subject).sanitize_with_scope example_attributes_one, :default, anything
|
174
|
+
subject.sanitize example_attributes_one
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'should let you provide a context' do
|
178
|
+
my_context = 42
|
179
|
+
mock(subject).sanitize_with_scope example_attributes_one, :default, my_context
|
180
|
+
subject.sanitize example_attributes_one, my_context
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_model'
|
3
|
+
|
4
|
+
describe ScopedAttrAccessible do
|
5
|
+
|
6
|
+
it 'should automatically mix it in when hijacked' do
|
7
|
+
ScopedAttrAccessible.mixin!
|
8
|
+
klass = Class.new { include ActiveModel::MassAssignmentSecurity }
|
9
|
+
klass.ancestors.include?(ActiveModel::MassAssignmentSecurity).should be_true
|
10
|
+
klass.ancestors.include?(ScopedAttrAccessible::ActiveModelMixin).should be_true
|
11
|
+
klass.should respond_to(:with_sanitizer_scope)
|
12
|
+
klass.new.should respond_to(:current_sanitizer_scope)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
ENV["RAILS_ENV"] ||= 'test'
|
2
|
+
$LOAD_PATH.unshift Pathname(__FILE__).dirname.dirname.join("lib").to_s
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
Bundler.setup
|
6
|
+
Bundler.require :default, :development
|
7
|
+
|
8
|
+
require 'scoped_attr_accessible'
|
9
|
+
require 'rspec'
|
10
|
+
|
11
|
+
Dir[Pathname(__FILE__).dirname.join("support/**/*.rb")].each { |f| require f }
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
config.mock_with :rr
|
15
|
+
config.include CustomMatchers
|
16
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module CustomMatchers
|
2
|
+
class OnlyHaveAttributesMatcher
|
3
|
+
|
4
|
+
def initialize(*attrs)
|
5
|
+
@attributes = attrs.map(&:to_s)
|
6
|
+
end
|
7
|
+
|
8
|
+
def matches?(target)
|
9
|
+
@target_attributes = target.keys.map(&:to_s)
|
10
|
+
@target_attributes == @attributes
|
11
|
+
end
|
12
|
+
|
13
|
+
def failure_message_for_should
|
14
|
+
"expected to only allow attributes #{@attributes.join(", ")}, allowed #{@target_attributes.join(", ")}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def failure_message_for_should_not
|
18
|
+
"expected to not only allow attributes #{@attributes.join(", ")}, allowed #{@target_attributes.join(", ")}"
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
def only_have_attributes(*attrs)
|
24
|
+
OnlyHaveAttributesMatcher.new(*attrs)
|
25
|
+
end
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scoped_attr_accessible
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Darcy Laycock
|
14
|
+
- Mario Visic
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2010-10-11 00:00:00 +08:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: activemodel
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 7
|
31
|
+
segments:
|
32
|
+
- 3
|
33
|
+
- 0
|
34
|
+
version: "3.0"
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: rspec
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 2
|
48
|
+
- 0
|
49
|
+
version: "2.0"
|
50
|
+
type: :development
|
51
|
+
version_requirements: *id002
|
52
|
+
description: |-
|
53
|
+
scoped_attr_accessible is a plugin that makes it easy to scope the `attr_accessible` and `attr_protected`
|
54
|
+
methods on any library using ActiveModel's MassAssignmentSecurity module.
|
55
|
+
email: team+darcy+mario@thefrontiergroup.com.au
|
56
|
+
executables: []
|
57
|
+
|
58
|
+
extensions: []
|
59
|
+
|
60
|
+
extra_rdoc_files:
|
61
|
+
- LICENSE
|
62
|
+
- README.md
|
63
|
+
files:
|
64
|
+
- .bundle/config
|
65
|
+
- .gitignore
|
66
|
+
- .rspec
|
67
|
+
- .rvmrc
|
68
|
+
- Gemfile
|
69
|
+
- Gemfile.lock
|
70
|
+
- LICENSE
|
71
|
+
- README.md
|
72
|
+
- Rakefile
|
73
|
+
- VERSION
|
74
|
+
- autotest/discover.rb
|
75
|
+
- lib/scoped_attr_accessible.rb
|
76
|
+
- lib/scoped_attr_accessible/active_model_mixin.rb
|
77
|
+
- lib/scoped_attr_accessible/sanitizer.rb
|
78
|
+
- spec/scoped_attr_accessible/active_model_mixin_spec.rb
|
79
|
+
- spec/scoped_attr_accessible/sanitizer_spec.rb
|
80
|
+
- spec/scoped_attr_accessible_spec.rb
|
81
|
+
- spec/spec_helper.rb
|
82
|
+
- spec/support/custom_matchers.rb
|
83
|
+
has_rdoc: true
|
84
|
+
homepage: http://github.com/thefrontiergroup/scoped_attr_accessible
|
85
|
+
licenses: []
|
86
|
+
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options:
|
89
|
+
- --charset=UTF-8
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
hash: 3
|
98
|
+
segments:
|
99
|
+
- 0
|
100
|
+
version: "0"
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
hash: 3
|
107
|
+
segments:
|
108
|
+
- 0
|
109
|
+
version: "0"
|
110
|
+
requirements: []
|
111
|
+
|
112
|
+
rubyforge_project:
|
113
|
+
rubygems_version: 1.3.7
|
114
|
+
signing_key:
|
115
|
+
specification_version: 3
|
116
|
+
summary: Scoping for attr_accessible and attr_protected on ActiveModel objects.
|
117
|
+
test_files:
|
118
|
+
- spec/scoped_attr_accessible/active_model_mixin_spec.rb
|
119
|
+
- spec/scoped_attr_accessible/sanitizer_spec.rb
|
120
|
+
- spec/scoped_attr_accessible_spec.rb
|
121
|
+
- spec/spec_helper.rb
|
122
|
+
- spec/support/custom_matchers.rb
|