expose 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/Gemfile +4 -0
- data/README.rdoc +105 -0
- data/Rakefile +11 -0
- data/expose.gemspec +26 -0
- data/lib/expose/version.rb +3 -0
- data/lib/expose.rb +131 -0
- data/spec/db/EMPTY +1 -0
- data/spec/expose_accessible_spec.rb +174 -0
- data/spec/expose_errors_spec.rb +83 -0
- data/spec/expose_protected_spec.rb +171 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/support/matchers.rb +46 -0
- metadata +93 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
== Expose
|
2
|
+
|
3
|
+
Expose allows you to dynamically adjust the 'attr_accessible' or
|
4
|
+
'attr_protected' of a model. This is only for managing mass-assignment
|
5
|
+
security, and not overall security.
|
6
|
+
|
7
|
+
=== Model
|
8
|
+
|
9
|
+
The following would let you mass_assign :sometimes_important when the :state
|
10
|
+
is 'new' or 'pending'.
|
11
|
+
|
12
|
+
class Account < ActiveRecord::Base
|
13
|
+
include Expose::Model
|
14
|
+
|
15
|
+
# name:string
|
16
|
+
# sometimes_important:string
|
17
|
+
# state:string ... example [:new, :pending, :closed]
|
18
|
+
|
19
|
+
expose :sometimes_important,
|
20
|
+
:if => Proc.new { |account| [:new,:pending].include?(account.state) }
|
21
|
+
|
22
|
+
# same result as line above (just using)
|
23
|
+
expose :sometimes_important, :state => [:new, :pending]
|
24
|
+
|
25
|
+
# similar to line above
|
26
|
+
expose :sometimes_important,
|
27
|
+
:unless => Proc.new { |account| [:closed].include?(account.state) }
|
28
|
+
|
29
|
+
# same as line above
|
30
|
+
expose :sometimes_important, :not_state => :closed
|
31
|
+
|
32
|
+
# using whitelist strategy
|
33
|
+
attr_accessible :name
|
34
|
+
|
35
|
+
# OR, using blacklist strategy
|
36
|
+
# attr_protected :sometimes_important
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
== Notes
|
41
|
+
|
42
|
+
This gem has only been tested with Rails 3.1.rc3, but should work with Rails
|
43
|
+
3.X. It only uses the hook :mass_assignment_authorizer.
|
44
|
+
|
45
|
+
== Todo
|
46
|
+
|
47
|
+
This gem is in the early stages of development, so use at your own risk.
|
48
|
+
|
49
|
+
Plans/Ideas:
|
50
|
+
- add 'protect' version, which does the opposite of 'expose'
|
51
|
+
- maybe disable attr_protected. Using this gem shows an interest in
|
52
|
+
mass-assignment security. Why not ensure use of a whitelist only
|
53
|
+
strategy.
|
54
|
+
- add controller version (so that session data can be used, ie: role of
|
55
|
+
logged in user)
|
56
|
+
- add better error handling and option checking, maybe add some logging
|
57
|
+
- do not require ActiveRecord, but rather ActiveModel
|
58
|
+
- not require adding 'include Expose::Model'. When I do, the class variable
|
59
|
+
'_exposures' is shared by all subclasses of ActiveRecord::Base, and each
|
60
|
+
declared model then sees the same '_exposures'.
|
61
|
+
|
62
|
+
== Installation
|
63
|
+
|
64
|
+
Install the gem:
|
65
|
+
|
66
|
+
gem install expose
|
67
|
+
|
68
|
+
Or add Expose to your Gemfile and bundle it up:
|
69
|
+
|
70
|
+
gem 'expose'
|
71
|
+
|
72
|
+
== Options
|
73
|
+
|
74
|
+
'expose' handles a series of options. Those are:
|
75
|
+
|
76
|
+
* :if * - When true, the attribute will be added to whitelist.
|
77
|
+
|
78
|
+
* :unless * - When false, the attribute will be added to whitelist.
|
79
|
+
|
80
|
+
* :state * - When in this state, the attribute will be added to whitelist.
|
81
|
+
|
82
|
+
* :not_state * - When not in this state, the attribute will be added to
|
83
|
+
whitelist.
|
84
|
+
|
85
|
+
== Maintainers
|
86
|
+
|
87
|
+
* Mark G (http://github.com/attack)
|
88
|
+
|
89
|
+
== Contributors
|
90
|
+
|
91
|
+
* you
|
92
|
+
|
93
|
+
== Influence
|
94
|
+
|
95
|
+
* trusted-params (https://github.com/ryanb/trusted-params) -
|
96
|
+
An ActiveController only version, not compatible with Rails 3.X.
|
97
|
+
|
98
|
+
== Bugs and Feedback
|
99
|
+
|
100
|
+
If you discover any bugs or want to drop a line, feel free to create an issue
|
101
|
+
on GitHub.
|
102
|
+
|
103
|
+
http://github.com/attack/expose/issues
|
104
|
+
|
105
|
+
MIT License. Copyright 2011 Mark G. http://github.com/attack
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
desc 'Default: run specs.'
|
5
|
+
task :default => :spec
|
6
|
+
|
7
|
+
desc 'Test the expose gem.'
|
8
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
9
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
10
|
+
spec.rspec_opts = ["-c"]
|
11
|
+
end
|
data/expose.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "expose/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "expose"
|
7
|
+
s.version = Expose::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Mark G"]
|
10
|
+
s.email = ["expose@attackcorp.com"]
|
11
|
+
s.homepage = "https://github.com/attack/expose"
|
12
|
+
s.summary = %q{Simple dynamic configuration of attr_protected}
|
13
|
+
s.description = %q{Simple dynamic configuration of attr_protected}
|
14
|
+
|
15
|
+
s.rubyforge_project = "expose"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {spec}/*`.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("rails", "~> 3.0")
|
23
|
+
|
24
|
+
s.add_development_dependency("rspec", "~> 2.6")
|
25
|
+
s.add_development_dependency('sqlite3-ruby')
|
26
|
+
end
|
data/lib/expose.rb
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
require "expose/version"
|
2
|
+
|
3
|
+
module Expose
|
4
|
+
|
5
|
+
module Model
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.class_eval do
|
9
|
+
extend ClassMethods
|
10
|
+
|
11
|
+
class_attribute :_exposures, :instance_writer => false
|
12
|
+
self._exposures = Hash.new { |h,k| h[k] = [] }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
# USAGE
|
18
|
+
# expose :attribute_name, :if => Proc.new {|model condition}
|
19
|
+
# expose :attribute_name, :state => :new, :state => [:new,:locked]
|
20
|
+
# - this will add :param_name to the 'attr_accessible' params if the condition is true
|
21
|
+
#
|
22
|
+
def expose(*config)
|
23
|
+
options = config.extract_options!
|
24
|
+
options.symbolize_keys!
|
25
|
+
options.assert_valid_keys(:if, :unless, :state, :not_state)
|
26
|
+
|
27
|
+
# TODO - warnings of improper use
|
28
|
+
# :name (attribute)
|
29
|
+
# validate in attr_protected or not in attr_accessible, otherwise no need to expose
|
30
|
+
#
|
31
|
+
# :state + :not_state
|
32
|
+
# include warning if any similarities in :state and :not_state, as they would cancel each other out
|
33
|
+
|
34
|
+
config.each do |attr|
|
35
|
+
if self.attribute_method?(attr.to_sym)
|
36
|
+
# if _exposures.has_key?(name.to_sym)
|
37
|
+
# # log duplication ?
|
38
|
+
# end
|
39
|
+
_exposures[attr.to_sym] = options
|
40
|
+
else
|
41
|
+
raise "Expose: invalid attribute - #{name.to_s}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
|
49
|
+
def mass_assignment_authorizer(attributes)
|
50
|
+
attribute_list = super
|
51
|
+
if attribute_list.is_a?(ActiveModel::MassAssignmentSecurity::WhiteList)
|
52
|
+
attribute_list += attributes_to_expose
|
53
|
+
else
|
54
|
+
attribute_list -= attributes_to_expose
|
55
|
+
end
|
56
|
+
attribute_list
|
57
|
+
end
|
58
|
+
|
59
|
+
def attributes_to_expose
|
60
|
+
attributes_to_expose = []
|
61
|
+
|
62
|
+
# if there are exposures
|
63
|
+
if _exposures && _exposures.respond_to?(:each)
|
64
|
+
# go through each exposure
|
65
|
+
_exposures.each do |k,v|
|
66
|
+
|
67
|
+
# assume exposure of the attribute
|
68
|
+
expose_attr = true
|
69
|
+
|
70
|
+
# RESPOND to configuration
|
71
|
+
#
|
72
|
+
|
73
|
+
# run through both :if and :unless ... then expose attribute if applicable
|
74
|
+
if v.key?(:if) || v.key?(:unless)
|
75
|
+
expose_attr = (applicable?(v[:if], true) && applicable?(v[:unless], false))
|
76
|
+
end
|
77
|
+
|
78
|
+
# try to match state
|
79
|
+
if v.key?(:state)
|
80
|
+
# convert to Array
|
81
|
+
allowed_states = v[:state].is_a?(Array) ? v[:state] : [v[:state].to_s]
|
82
|
+
|
83
|
+
# expose attribute if the current state is in expected states
|
84
|
+
expose_attr = ( self.respond_to?(:state) &&
|
85
|
+
self.state &&
|
86
|
+
allowed_states.include?(self.state.to_s))
|
87
|
+
end
|
88
|
+
|
89
|
+
# try to NOT match state
|
90
|
+
if v.key?(:not_state)
|
91
|
+
# convert to Array
|
92
|
+
disallowed_states = v[:not_state].is_a?(Array) ? v[:not_state] : [v[:not_state].to_s]
|
93
|
+
|
94
|
+
# expose attribute if the current state is NOT in expected states
|
95
|
+
expose_attr = ( self.respond_to?(:state) &&
|
96
|
+
( !self.state ||
|
97
|
+
!disallowed_states.include?(self.state.to_s)))
|
98
|
+
end
|
99
|
+
|
100
|
+
# if nothing is preventing this attribute from being exposed (eg. all pass, or no options)
|
101
|
+
# then add the attribute to attr_accessible
|
102
|
+
attributes_to_expose << k.to_s if expose_attr
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
attributes_to_expose
|
107
|
+
end
|
108
|
+
|
109
|
+
# Evaluates the scope options :if or :unless. Returns true if the proc
|
110
|
+
# method, or string evals to the expected value.
|
111
|
+
def applicable?(string_proc_or_symbol, expected) #:nodoc:
|
112
|
+
case string_proc_or_symbol
|
113
|
+
when String
|
114
|
+
eval(string_proc_or_symbol) == expected
|
115
|
+
when Proc
|
116
|
+
string_proc_or_symbol.call(self) == expected
|
117
|
+
when Symbol
|
118
|
+
send(string_proc_or_symbol) == expected
|
119
|
+
else
|
120
|
+
true
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
# This is currently commented out, and therefore you need to use
|
128
|
+
# 'include Expose::Model' at the top of any subclass of
|
129
|
+
# ActiveRecord::Base where you want to use :expose.
|
130
|
+
#
|
131
|
+
#ActiveRecord::Base.class_eval { include Expose::Model }
|
data/spec/db/EMPTY
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
This directory is left empty, for testing db file to reside in, but not included
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Expose' do
|
4
|
+
|
5
|
+
describe "using attr_accessible strategy" do
|
6
|
+
describe "initial setup" do
|
7
|
+
before(:each) do
|
8
|
+
class User < ActiveRecord::Base
|
9
|
+
include Expose::Model
|
10
|
+
attr_accessible :name
|
11
|
+
attr_accessible :not_important
|
12
|
+
end
|
13
|
+
end
|
14
|
+
subject { User.new }
|
15
|
+
|
16
|
+
it { should expose(:name) }
|
17
|
+
it { should protect(:important) }
|
18
|
+
it { should protect(:sometimes_important) }
|
19
|
+
it { should expose(:not_important) }
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "expose with no options" do
|
23
|
+
before(:each) do
|
24
|
+
class User < ActiveRecord::Base
|
25
|
+
include Expose::Model
|
26
|
+
attr_accessible :name
|
27
|
+
attr_accessible :not_important
|
28
|
+
expose :sometimes_important
|
29
|
+
end
|
30
|
+
end
|
31
|
+
subject { User.new }
|
32
|
+
|
33
|
+
it { should expose(:name) }
|
34
|
+
it { should protect(:important) }
|
35
|
+
it { should expose(:sometimes_important) }
|
36
|
+
it { should expose(:not_important) }
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "expose with :if" do
|
40
|
+
before(:each) do
|
41
|
+
class User < ActiveRecord::Base
|
42
|
+
include Expose::Model
|
43
|
+
attr_accessible :name
|
44
|
+
attr_accessible :not_important
|
45
|
+
expose :sometimes_important, :if => Proc.new { |user| user.name == 'example name' }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
subject { User.new }
|
49
|
+
|
50
|
+
it { should expose(:name) }
|
51
|
+
it { should protect(:important) }
|
52
|
+
it { should expose(:not_important) }
|
53
|
+
|
54
|
+
context "when :if is true" do
|
55
|
+
subject { User.new(:name => 'example name') }
|
56
|
+
it { should expose(:sometimes_important) }
|
57
|
+
end
|
58
|
+
|
59
|
+
context "when :if is false" do
|
60
|
+
subject { User.new(:name => 'name') }
|
61
|
+
it { should protect(:sometimes_important) }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "expose with :unless" do
|
66
|
+
before(:each) do
|
67
|
+
class User < ActiveRecord::Base
|
68
|
+
include Expose::Model
|
69
|
+
attr_accessible :name
|
70
|
+
attr_accessible :not_important
|
71
|
+
expose :sometimes_important, :unless => Proc.new { |user| user.name == 'example name' }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
subject { User.new }
|
75
|
+
|
76
|
+
it { should expose(:name) }
|
77
|
+
it { should protect(:important) }
|
78
|
+
it { should expose(:not_important) }
|
79
|
+
|
80
|
+
context "when :unless is true" do
|
81
|
+
subject { User.new(:name => 'example name') }
|
82
|
+
it { should protect(:sometimes_important) }
|
83
|
+
end
|
84
|
+
|
85
|
+
context "when :unless is false" do
|
86
|
+
subject { User.new(:name => 'name') }
|
87
|
+
it { should expose(:sometimes_important) }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "expose with :state" do
|
92
|
+
before(:each) do
|
93
|
+
class User < ActiveRecord::Base
|
94
|
+
include Expose::Model
|
95
|
+
attr_accessible :state
|
96
|
+
attr_accessible :name
|
97
|
+
attr_accessible :not_important
|
98
|
+
expose :sometimes_important, :state => 'open'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
subject { User.new }
|
102
|
+
|
103
|
+
it { should expose(:name) }
|
104
|
+
it { should protect(:important) }
|
105
|
+
it { should expose(:not_important) }
|
106
|
+
|
107
|
+
context "when :state matches" do
|
108
|
+
subject { User.new(:state => 'open') }
|
109
|
+
it { should expose(:sometimes_important) }
|
110
|
+
end
|
111
|
+
|
112
|
+
context "when :state doesn't match" do
|
113
|
+
subject { User.new(:state => 'closed') }
|
114
|
+
it { should protect(:sometimes_important) }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "expose with :not_state" do
|
119
|
+
before(:each) do
|
120
|
+
class User < ActiveRecord::Base
|
121
|
+
include Expose::Model
|
122
|
+
attr_accessible :state
|
123
|
+
attr_accessible :name
|
124
|
+
attr_accessible :not_important
|
125
|
+
expose :sometimes_important, :not_state => 'closed'
|
126
|
+
end
|
127
|
+
end
|
128
|
+
subject { User.new }
|
129
|
+
|
130
|
+
it { should expose(:name) }
|
131
|
+
it { should protect(:important) }
|
132
|
+
it { should expose(:not_important) }
|
133
|
+
|
134
|
+
context "when :not_state doesn't match" do
|
135
|
+
subject { User.new(:state => 'open') }
|
136
|
+
it { should expose(:sometimes_important) }
|
137
|
+
end
|
138
|
+
|
139
|
+
context "when :state matches" do
|
140
|
+
subject { User.new(:state => 'closed') }
|
141
|
+
it { should protect(:sometimes_important) }
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "allow multiple attributes in a single definition" do
|
146
|
+
before(:each) do
|
147
|
+
class User < ActiveRecord::Base
|
148
|
+
include Expose::Model
|
149
|
+
attr_accessible :state
|
150
|
+
attr_accessible :name
|
151
|
+
attr_accessible :not_important
|
152
|
+
expose :important, :sometimes_important, :if => Proc.new { |user| user.name == 'example name' }
|
153
|
+
end
|
154
|
+
end
|
155
|
+
subject { User.new }
|
156
|
+
|
157
|
+
it { should expose(:name) }
|
158
|
+
it { should expose(:not_important) }
|
159
|
+
|
160
|
+
context "when :if is true" do
|
161
|
+
subject { User.new(:name => 'example name') }
|
162
|
+
it { should expose(:important) }
|
163
|
+
it { should expose(:sometimes_important) }
|
164
|
+
end
|
165
|
+
|
166
|
+
context "when :if is false" do
|
167
|
+
subject { User.new(:name => 'name') }
|
168
|
+
it { should protect(:important) }
|
169
|
+
it { should protect(:sometimes_important) }
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Expose' do
|
4
|
+
describe "handles errors" do
|
5
|
+
|
6
|
+
describe "initial setup (sanity check)" do
|
7
|
+
before(:each) do
|
8
|
+
class User < ActiveRecord::Base
|
9
|
+
include Expose::Model
|
10
|
+
attr_protected :important
|
11
|
+
attr_protected :sometimes_important
|
12
|
+
end
|
13
|
+
end
|
14
|
+
subject { User.new }
|
15
|
+
|
16
|
+
it { should expose(:name) }
|
17
|
+
it { should protect(:important) }
|
18
|
+
it { should protect(:sometimes_important) }
|
19
|
+
it { should expose(:not_important) }
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "expose with invalid name" do
|
23
|
+
it "raises error" do
|
24
|
+
expect {
|
25
|
+
class User < ActiveRecord::Base
|
26
|
+
include Expose::Model
|
27
|
+
attr_protected :important
|
28
|
+
attr_protected :sometimes_important
|
29
|
+
expose :invalid_name
|
30
|
+
end }.to raise_error
|
31
|
+
end
|
32
|
+
# it "should skip over invalid name, issure warning" do
|
33
|
+
# subject.send(:_exposures).should_not include(:invalid_name)
|
34
|
+
# end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "expose with double declaration" do
|
38
|
+
before(:each) do
|
39
|
+
class User < ActiveRecord::Base
|
40
|
+
include Expose::Model
|
41
|
+
attr_protected :important
|
42
|
+
attr_protected :sometimes_important
|
43
|
+
expose :sometimes_important, :state => 'new'
|
44
|
+
expose :sometimes_important, :not_state => 'closed'
|
45
|
+
expose :sometimes_important, :state => 'open'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
subject { User.new }
|
49
|
+
|
50
|
+
it "writes over the previous definitions" do
|
51
|
+
subject.send(:_exposures).should include(:sometimes_important)
|
52
|
+
subject.send(:_exposures)[:sometimes_important].should include(:state)
|
53
|
+
subject.send(:_exposures)[:sometimes_important].should_not include(:not_state)
|
54
|
+
subject.send(:_exposures)[:sometimes_important][:state].should == 'open'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "expose two classes with no interferences" do
|
59
|
+
before(:each) do
|
60
|
+
class User < ActiveRecord::Base
|
61
|
+
include Expose::Model
|
62
|
+
attr_protected :important
|
63
|
+
attr_protected :sometimes_important
|
64
|
+
expose :sometimes_important, :state => 'open'
|
65
|
+
end
|
66
|
+
class Account < ActiveRecord::Base
|
67
|
+
include Expose::Model
|
68
|
+
attr_protected :important
|
69
|
+
attr_protected :sometimes_important
|
70
|
+
expose :important, :state => 'new'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it "keeps separate exposure lists" do
|
75
|
+
User.send(:_exposures).should include(:sometimes_important)
|
76
|
+
User.send(:_exposures).should_not include(:important)
|
77
|
+
Account.send(:_exposures).should include(:important)
|
78
|
+
Account.send(:_exposures).should_not include(:sometimes_important)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Expose' do
|
4
|
+
|
5
|
+
describe "using attr_protected strategy" do
|
6
|
+
describe "initial setup" do
|
7
|
+
before(:each) do
|
8
|
+
class User < ActiveRecord::Base
|
9
|
+
include Expose::Model
|
10
|
+
attr_protected :important
|
11
|
+
attr_protected :sometimes_important
|
12
|
+
end
|
13
|
+
end
|
14
|
+
subject { User.new }
|
15
|
+
|
16
|
+
it { should expose(:name) }
|
17
|
+
it { should protect(:important) }
|
18
|
+
it { should protect(:sometimes_important) }
|
19
|
+
it { should expose(:not_important) }
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "expose with no options" do
|
23
|
+
before(:each) do
|
24
|
+
class User < ActiveRecord::Base
|
25
|
+
include Expose::Model
|
26
|
+
attr_protected :important
|
27
|
+
attr_protected :sometimes_important
|
28
|
+
expose :sometimes_important
|
29
|
+
end
|
30
|
+
end
|
31
|
+
subject { User.new }
|
32
|
+
|
33
|
+
it { should expose(:name) }
|
34
|
+
it { should protect(:important) }
|
35
|
+
it { should expose(:sometimes_important) }
|
36
|
+
it { should expose(:not_important) }
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "expose with :if" do
|
40
|
+
before(:each) do
|
41
|
+
class User < ActiveRecord::Base
|
42
|
+
include Expose::Model
|
43
|
+
attr_protected :important
|
44
|
+
attr_protected :sometimes_important
|
45
|
+
expose :sometimes_important, :if => Proc.new { |user| user.name == 'example name' }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
subject { User.new }
|
49
|
+
|
50
|
+
it { should expose(:name) }
|
51
|
+
it { should protect(:important) }
|
52
|
+
it { should expose(:not_important) }
|
53
|
+
|
54
|
+
context "when :if is true" do
|
55
|
+
subject { User.new(:name => 'example name') }
|
56
|
+
it { should expose(:sometimes_important) }
|
57
|
+
end
|
58
|
+
|
59
|
+
context "when :if is false" do
|
60
|
+
subject { User.new(:name => 'name') }
|
61
|
+
it { should protect(:sometimes_important) }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "expose with :unless" do
|
66
|
+
before(:each) do
|
67
|
+
class User < ActiveRecord::Base
|
68
|
+
include Expose::Model
|
69
|
+
attr_protected :important
|
70
|
+
attr_protected :sometimes_important
|
71
|
+
expose :sometimes_important, :unless => Proc.new { |user| user.name == 'example name' }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
subject { User.new }
|
75
|
+
|
76
|
+
it { should expose(:name) }
|
77
|
+
it { should protect(:important) }
|
78
|
+
it { should expose(:not_important) }
|
79
|
+
|
80
|
+
context "when :unless is true" do
|
81
|
+
subject { User.new(:name => 'example name') }
|
82
|
+
it { should protect(:sometimes_important) }
|
83
|
+
end
|
84
|
+
|
85
|
+
context "when :unless is false" do
|
86
|
+
subject { User.new(:name => 'name') }
|
87
|
+
it { should expose(:sometimes_important) }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "expose with :state" do
|
92
|
+
before(:each) do
|
93
|
+
class User < ActiveRecord::Base
|
94
|
+
include Expose::Model
|
95
|
+
attr_protected :important
|
96
|
+
attr_protected :sometimes_important
|
97
|
+
expose :sometimes_important, :state => 'open'
|
98
|
+
end
|
99
|
+
end
|
100
|
+
subject { User.new }
|
101
|
+
|
102
|
+
it { should expose(:name) }
|
103
|
+
it { should protect(:important) }
|
104
|
+
it { should expose(:not_important) }
|
105
|
+
|
106
|
+
context "when :state matches" do
|
107
|
+
subject { User.new(:state => 'open') }
|
108
|
+
it { should expose(:sometimes_important) }
|
109
|
+
end
|
110
|
+
|
111
|
+
context "when :state doesn't match" do
|
112
|
+
subject { User.new(:state => 'closed') }
|
113
|
+
it { should protect(:sometimes_important) }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "expose with :not_state" do
|
118
|
+
before(:each) do
|
119
|
+
class User < ActiveRecord::Base
|
120
|
+
include Expose::Model
|
121
|
+
attr_protected :important
|
122
|
+
attr_protected :sometimes_important
|
123
|
+
expose :sometimes_important, :not_state => 'closed'
|
124
|
+
end
|
125
|
+
end
|
126
|
+
subject { User.new }
|
127
|
+
|
128
|
+
it { should expose(:name) }
|
129
|
+
it { should protect(:important) }
|
130
|
+
it { should expose(:not_important) }
|
131
|
+
|
132
|
+
context "when :not_state doesn't match" do
|
133
|
+
subject { User.new(:state => 'open') }
|
134
|
+
it { should expose(:sometimes_important) }
|
135
|
+
end
|
136
|
+
|
137
|
+
context "when :state matches" do
|
138
|
+
subject { User.new(:state => 'closed') }
|
139
|
+
it { should protect(:sometimes_important) }
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe "allow multiple attributes in a single definition" do
|
144
|
+
before(:each) do
|
145
|
+
class User < ActiveRecord::Base
|
146
|
+
include Expose::Model
|
147
|
+
attr_protected :important
|
148
|
+
attr_protected :sometimes_important
|
149
|
+
expose :important, :sometimes_important, :if => Proc.new { |user| user.name == 'example name' }
|
150
|
+
end
|
151
|
+
end
|
152
|
+
subject { User.new }
|
153
|
+
|
154
|
+
it { should expose(:name) }
|
155
|
+
it { should expose(:not_important) }
|
156
|
+
|
157
|
+
context "when :if is true" do
|
158
|
+
subject { User.new(:name => 'example name') }
|
159
|
+
it { should expose(:important) }
|
160
|
+
it { should expose(:sometimes_important) }
|
161
|
+
end
|
162
|
+
|
163
|
+
context "when :if is false" do
|
164
|
+
subject { User.new(:name => 'name') }
|
165
|
+
it { should protect(:important) }
|
166
|
+
it { should protect(:sometimes_important) }
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.setup(:default, :development, :test)
|
4
|
+
|
5
|
+
require 'rails'
|
6
|
+
require 'active_support'
|
7
|
+
require 'active_model'
|
8
|
+
require 'active_record'
|
9
|
+
require 'expose'
|
10
|
+
require 'rspec'
|
11
|
+
|
12
|
+
root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
13
|
+
ActiveRecord::Base.establish_connection(
|
14
|
+
:adapter => "sqlite3",
|
15
|
+
:database => "#{root}/spec/db/expose.db"
|
16
|
+
)
|
17
|
+
|
18
|
+
RSpec.configure do |config|
|
19
|
+
end
|
20
|
+
|
21
|
+
# create the tables for the test in the database
|
22
|
+
ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS 'users'")
|
23
|
+
ActiveRecord::Base.connection.create_table(:users) do |t|
|
24
|
+
t.string :name, :default => 'name'
|
25
|
+
t.string :important, :default => 'important'
|
26
|
+
t.string :sometimes_important, :default => 'sometimes_important'
|
27
|
+
t.string :not_important, :default => 'not_important'
|
28
|
+
t.string :state, :default => 'new'
|
29
|
+
end
|
30
|
+
|
31
|
+
ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS 'accounts'")
|
32
|
+
ActiveRecord::Base.connection.create_table(:accounts) do |t|
|
33
|
+
t.string :name, :default => 'name'
|
34
|
+
t.string :important, :default => 'important'
|
35
|
+
t.string :sometimes_important, :default => 'sometimes_important'
|
36
|
+
t.string :not_important, :default => 'not_important'
|
37
|
+
t.string :state, :default => 'new'
|
38
|
+
end
|
39
|
+
|
40
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
41
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
RSpec::Matchers.define :expose do |attr|
|
2
|
+
match do |subject|
|
3
|
+
# expect {
|
4
|
+
# current_value = subject.send(attr.to_sym)
|
5
|
+
# subject.attributes = {attr.to_sym => (current_value && !current_value.blank? ? (current_value + '_new') : 'new')}
|
6
|
+
# }.to change{ subject.send(attr.to_sym) }
|
7
|
+
old_value = subject.send(attr.to_sym).to_s
|
8
|
+
subject.update_attributes({attr.to_sym => (old_value && !old_value.blank? ? (old_value + '_new') : 'new')})
|
9
|
+
subject.reload
|
10
|
+
old_value != subject.send(attr.to_sym)
|
11
|
+
end
|
12
|
+
|
13
|
+
failure_message_for_should do |subject|
|
14
|
+
"expected that #{subject} does not protect #{attr}, but it does"
|
15
|
+
end
|
16
|
+
|
17
|
+
failure_message_for_should_not do |subject|
|
18
|
+
"expected that #{subject} attr_protects #{attr}, but it does not"
|
19
|
+
end
|
20
|
+
|
21
|
+
description do
|
22
|
+
"NOT attr_protected :#{attr}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
RSpec::Matchers.define :protect do |attr|
|
27
|
+
match do |subject|
|
28
|
+
old_value = subject.send(attr.to_sym)
|
29
|
+
subject.attributes = {attr.to_sym => (old_value && !old_value.blank? ? (old_value + '_new') : 'new')}
|
30
|
+
new_value = subject.send(attr.to_sym)
|
31
|
+
|
32
|
+
old_value == new_value
|
33
|
+
end
|
34
|
+
|
35
|
+
failure_message_for_should do |subject|
|
36
|
+
"expected that #{subject} attr_protects #{attr}, but it does not"
|
37
|
+
end
|
38
|
+
|
39
|
+
failure_message_for_should_not do |subject|
|
40
|
+
"expected that #{subject} does not protect #{attr}, but it does"
|
41
|
+
end
|
42
|
+
|
43
|
+
description do
|
44
|
+
"attr_protected :#{attr}"
|
45
|
+
end
|
46
|
+
end
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: expose
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mark G
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-06-20 00:00:00.000000000 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rails
|
17
|
+
requirement: &2153270260 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ~>
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *2153270260
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: rspec
|
28
|
+
requirement: &2153269760 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.6'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *2153269760
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: sqlite3-ruby
|
39
|
+
requirement: &2153269380 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
type: :development
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *2153269380
|
48
|
+
description: Simple dynamic configuration of attr_protected
|
49
|
+
email:
|
50
|
+
- expose@attackcorp.com
|
51
|
+
executables: []
|
52
|
+
extensions: []
|
53
|
+
extra_rdoc_files: []
|
54
|
+
files:
|
55
|
+
- .gitignore
|
56
|
+
- Gemfile
|
57
|
+
- README.rdoc
|
58
|
+
- Rakefile
|
59
|
+
- expose.gemspec
|
60
|
+
- lib/expose.rb
|
61
|
+
- lib/expose/version.rb
|
62
|
+
- spec/db/EMPTY
|
63
|
+
- spec/expose_accessible_spec.rb
|
64
|
+
- spec/expose_errors_spec.rb
|
65
|
+
- spec/expose_protected_spec.rb
|
66
|
+
- spec/spec_helper.rb
|
67
|
+
- spec/support/matchers.rb
|
68
|
+
has_rdoc: true
|
69
|
+
homepage: https://github.com/attack/expose
|
70
|
+
licenses: []
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options: []
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ! '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
requirements: []
|
88
|
+
rubyforge_project: expose
|
89
|
+
rubygems_version: 1.6.2
|
90
|
+
signing_key:
|
91
|
+
specification_version: 3
|
92
|
+
summary: Simple dynamic configuration of attr_protected
|
93
|
+
test_files: []
|