expose 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +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: []
|