active_access 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+
2
+ source "http://rubygems.org"
3
+
4
+ gem "activesupport"
5
+ gem "activemodel"
6
+
7
+ group :development do
8
+ gem "rspec"
9
+ gem "rdoc"
10
+ gem "jeweler"
11
+ end
12
+
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Riley Lynch, Teleological Software, LLC
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.
@@ -0,0 +1,67 @@
1
+ = active_access
2
+
3
+ The ActiveAccess mixin makes it easy to limit access to ActiveModel
4
+ and ActiveRecord attributes by declaring generated attribute accessor
5
+ methods private.
6
+
7
+ Mixing the <tt>ActiveAcess::AttributeMethods</tt> into a class that mixes
8
+ in ActiveModel::AttributeMethods or is derived from ActiveRecord::Base
9
+ adds the <tt>attr_private</tt> and <tt>attr_private_writer</tt> macros.
10
+
11
+ These macros change the behavior of +define_attribute_methods+ to declare
12
+ generated attribute accessors private: +attr_private+ declares all
13
+ accessors for an attribute private; +attr_private_writer+ declares only
14
+ writer methods private.
15
+
16
+ ActiveAccess recognizes the following attribute reader method name
17
+ patterns:
18
+
19
+ #name
20
+ #name?
21
+ #name_before_type_cast
22
+ #name_changed?
23
+ #name_change
24
+ #name_was
25
+ #_name
26
+
27
+ ActiveAccess recognizes the following attribute writer method name
28
+ patterns:
29
+
30
+ #name=
31
+ #name_will_change
32
+ #reset_name!
33
+
34
+ == Example
35
+
36
+ class Widget < ActiveRecord::Base
37
+
38
+ include ActiveAccess::AttributeMethods
39
+
40
+ attr_private_writer :read_me
41
+
42
+ attr_private :secret
43
+
44
+ end
45
+
46
+ Widget.new.respond_to? :read_me # => true
47
+ Widget.new.read_me = "new value" # raises NoMethodError
48
+
49
+ Widget.new.respond_to? :secret # => false
50
+ Widget.new.secret = "new value" # raises NoMethodError
51
+
52
+ == Limitations
53
+
54
+ ActiveAccess only restricts access to named attribute accessors. Unnamed
55
+ accessors such as ActiveRecord +read_attribute+ and +write_attribute+ as
56
+ well as hash-style access are not restricted. Attribute aliases are also
57
+ unrestricted.
58
+
59
+ ActiveRecord does not allow attributes with private writers to be
60
+ initialized (i.e. passed as parameters to +new+) or mass-assigned
61
+ (passed as parameters to +assign_attributes+ or +attributes=+).
62
+
63
+ == Copyright
64
+
65
+ Copyright (c) 2012 Riley Lynch, Teleological Software, LLC.
66
+ See LICENSE.txt for further details.
67
+
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ gem.name = "active_access"
17
+ gem.homepage = "http://github.com/teleological/active_access"
18
+ gem.license = "MIT"
19
+ gem.summary =
20
+ %Q{Access control for ActiveModel/ActiveRecord attributes}
21
+ gem.description =
22
+ %Q{Access control for ActiveModel/ActiveRecord attributes}
23
+ gem.authors = ["Riley Lynch"]
24
+ end
25
+ Jeweler::RubygemsDotOrgTasks.new
26
+
27
+ require 'rspec/core'
28
+ require 'rspec/core/rake_task'
29
+ RSpec::Core::RakeTask.new(:spec) do |spec|
30
+ spec.pattern = FileList['spec/**/*_spec.rb']
31
+ end
32
+
33
+ task :default => :spec
34
+
35
+ require 'rdoc/task'
36
+ Rake::RDocTask.new do |rdoc|
37
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
38
+ rdoc.main = "README.rdoc"
39
+ rdoc.rdoc_dir = 'rdoc'
40
+ rdoc.rdoc_files.include('README*')
41
+ rdoc.rdoc_files.include('lib/**/*.rb')
42
+ rdoc.title = "active_access #{version}"
43
+ end
44
+
@@ -0,0 +1,9 @@
1
+
2
+ require 'active_support'
3
+
4
+ module ActiveAccess
5
+ extend ActiveSupport::Autoload
6
+ autoload :AttributeMethods
7
+ autoload :AttributeConventions
8
+ end
9
+
@@ -0,0 +1,40 @@
1
+
2
+ require 'active_model'
3
+
4
+ module ActiveAccess
5
+ class AttributeConventions
6
+
7
+ MethodMatcher = ActiveModel::AttributeMethods::ClassMethods::
8
+ AttributeMethodMatcher
9
+
10
+ READ_MATCHERS = [
11
+ MethodMatcher.new, # "plain" reader
12
+ MethodMatcher.new(suffix: '_changed?'),
13
+ MethodMatcher.new(suffix: '_change'),
14
+ MethodMatcher.new(suffix: '_was'),
15
+ MethodMatcher.new(suffix: '_before_type_cast'),
16
+ MethodMatcher.new(prefix: '_'),
17
+ MethodMatcher.new(prefix: '?')
18
+ ]
19
+
20
+ WRITE_MATCHERS = [
21
+ MethodMatcher.new(prefix: 'reset_', suffix: '!'),
22
+ MethodMatcher.new(suffix: '_will_change'),
23
+ MethodMatcher.new(suffix: '=')
24
+ ]
25
+
26
+ class << self
27
+
28
+ def reader_names(attr)
29
+ READ_MATCHERS.map { |matcher| matcher.method_name(attr) }
30
+ end
31
+
32
+ def writer_names(attr)
33
+ WRITE_MATCHERS.map { |matcher| matcher.method_name(attr) }
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+ end
40
+
@@ -0,0 +1,78 @@
1
+
2
+ require 'active_support/core_ext'
3
+
4
+ module ActiveAccess
5
+ module AttributeMethods
6
+
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class_attribute :_attribute_access, :instance_writer => false
11
+ self._attribute_access = {}
12
+ end
13
+
14
+ module ClassMethods
15
+
16
+ def attr_private(*attrs)
17
+ update_attribute_method_access(:private, attrs)
18
+ end
19
+
20
+ def attr_private_writer(*attrs)
21
+ update_attribute_method_access(:readonly, attrs)
22
+ end
23
+
24
+ def define_attribute_methods(attrs=nil)
25
+ if defined?(ActiveRecord) && (self < ActiveRecord::AttributeMethods)
26
+ super() # takes no arguments
27
+ reset_attribute_method_access
28
+ else
29
+ super # takes a list of attribute names as arguments
30
+ reset_attribute_method_access(attrs)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def update_attribute_method_access(level, attrs)
37
+ self._attribute_access = self._attribute_access.dup
38
+ attrs.each do |attr|
39
+ self._attribute_access[attr.to_sym] = level
40
+ end
41
+ reset_attribute_method_access(attrs)
42
+ end
43
+
44
+ def reset_attribute_method_access(attrs=nil)
45
+
46
+ if attrs
47
+ attr_syms = attrs.map(&:to_sym)
48
+ attr_accesses = _attribute_access.slice(*attr_syms)
49
+ else
50
+ attr_accesses = _attribute_access
51
+ end
52
+
53
+ attr_accesses.each do |attr, access|
54
+ if [:private, :readonly].include?(access)
55
+ privatize_attribute_writers(attr)
56
+ end
57
+ privatize_attribute_readers(attr) if access == :private
58
+ end
59
+
60
+ end
61
+
62
+ def privatize_attribute_writers(attr)
63
+ AttributeConventions.writer_names(attr).each do |name|
64
+ private name if method_defined?(name)
65
+ end
66
+ end
67
+
68
+ def privatize_attribute_readers(attr)
69
+ AttributeConventions.reader_names(attr).each do |name|
70
+ private name if method_defined?(name)
71
+ end
72
+ end
73
+
74
+ end
75
+
76
+ end
77
+ end
78
+
@@ -0,0 +1,145 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ require 'active_model'
4
+
5
+ describe "ActiveAccess::AttributeMethods" do
6
+
7
+ context "given an ActiveModel class" do
8
+
9
+ let(:abstract_base_class) do
10
+ Class.new(Struct.new(:foo, :bar, :baz)).tap do |klass|
11
+ klass.class_eval do
12
+
13
+ include ActiveModel::AttributeMethods
14
+ include ActiveAccess::AttributeMethods
15
+
16
+ def attributes
17
+ members.each_with_object({}) { |m, h| h[m.to_s] = self[m] }
18
+ end
19
+
20
+ alias_method :attribute, :[]
21
+
22
+ end
23
+ end
24
+ end
25
+
26
+ let(:model_instance) { model_class.new("fooh", "bhar", "bazh") }
27
+
28
+ let(:private_attribute) { :bar }
29
+ let(:private_attribute_value) { "bhar" }
30
+
31
+ let(:private_reader) { :"#{private_attribute}" }
32
+ let(:private_writer) { :"#{private_attribute}=" }
33
+
34
+ shared_examples_for "any ActiveModel class with private attributes" do
35
+
36
+ it "prevents public reads with named reader on private attributes" do
37
+ lambda { model_instance.public_send(private_reader) }.
38
+ should raise_error(NoMethodError, /private/)
39
+ end
40
+
41
+ it "prevents public writes with named writer on private attributes" do
42
+ lambda { model_instance.public_send(private_writer,"changed") }.
43
+ should raise_error(NoMethodError, /private/)
44
+ end
45
+
46
+ it "allows private reads with named reader on private attributes" do
47
+ model_instance.send(private_reader).
48
+ should == private_attribute_value
49
+ end
50
+
51
+ it "allows private writes with named writer on private attributes" do
52
+ model_instance.send(private_writer, "changed")
53
+ model_instance.send(private_reader).should == "changed"
54
+ end
55
+
56
+ it "allows read access to private attributes via unnamed reader" do
57
+ model_instance[private_attribute].should == private_attribute_value
58
+ end
59
+
60
+ it "allows read access to private attributes via unnamed reader" do
61
+ model_instance[private_attribute] = "changed"
62
+ model_instance[private_attribute].should == "changed"
63
+ end
64
+
65
+ end
66
+
67
+ shared_examples_for "any ActiveModel class" do
68
+
69
+ it_should_behave_like "any ActiveModel class with private attributes"
70
+
71
+ it "allows public reads on public (default) attributes" do
72
+ model_instance.foo.should == "fooh"
73
+ end
74
+
75
+ it "allows public writes to public (default) attributes" do
76
+ model_instance.foo = "fhoo"
77
+ model_instance.foo.should == "fhoo"
78
+ end
79
+
80
+ end
81
+
82
+ context "when private attributes are declared before methods" do
83
+
84
+ let(:model_class) do
85
+ Class.new(abstract_base_class).tap do |klass|
86
+ klass.class_eval do
87
+ attr_private :bar
88
+ define_attribute_methods([:foo, :bar, :baz])
89
+ end
90
+ end
91
+ end
92
+
93
+ it_should_behave_like "any ActiveModel class"
94
+
95
+ end
96
+
97
+ context "when attribute methods are declared before privacy" do
98
+
99
+ let(:model_class) do
100
+ Class.new(abstract_base_class).tap do |klass|
101
+ klass.class_eval do
102
+ define_attribute_methods([:foo, :bar, :baz])
103
+ attr_private :bar
104
+ end
105
+ end
106
+ end
107
+
108
+ it_should_behave_like "any ActiveModel class"
109
+
110
+ end
111
+
112
+ context "when attributes are inherited " do
113
+
114
+ let(:base_class) do
115
+ Class.new(abstract_base_class).tap do |klass|
116
+ klass.class_eval do
117
+ define_attribute_methods([:foo, :bar])
118
+ attr_private :bar
119
+ end
120
+ end
121
+ end
122
+
123
+ let(:model_class) do
124
+ Class.new(base_class).tap do |klass|
125
+ klass.class_eval do
126
+ define_attribute_methods([:baz])
127
+ attr_private :baz
128
+ end
129
+ end
130
+ end
131
+
132
+ context "with inherited private attributes" do
133
+ it_should_behave_like "any ActiveModel class"
134
+ end
135
+
136
+ context "with derived private attributes" do
137
+ let(:private_attribute) { :baz }
138
+ let(:private_attribute_value) { "bazh" }
139
+ it_should_behave_like "any ActiveModel class"
140
+ end
141
+ end
142
+
143
+ end
144
+ end
145
+
@@ -0,0 +1,14 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+
4
+ require 'rspec'
5
+
6
+ require 'active_access'
7
+
8
+ # Requires supporting files with custom matchers and macros, etc,
9
+ # in ./support/ and its subdirectories.
10
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
11
+
12
+ RSpec.configure do |config|
13
+
14
+ end
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_access
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Riley Lynch
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-06-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: activemodel
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rdoc
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: jeweler
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: Access control for ActiveModel/ActiveRecord attributes
95
+ email:
96
+ executables: []
97
+ extensions: []
98
+ extra_rdoc_files:
99
+ - LICENSE.txt
100
+ - README.rdoc
101
+ files:
102
+ - .rspec
103
+ - Gemfile
104
+ - LICENSE.txt
105
+ - README.rdoc
106
+ - Rakefile
107
+ - lib/active_access.rb
108
+ - lib/active_access/attribute_conventions.rb
109
+ - lib/active_access/attribute_methods.rb
110
+ - spec/active_access_spec.rb
111
+ - spec/spec_helper.rb
112
+ homepage: http://github.com/teleological/active_access
113
+ licenses:
114
+ - MIT
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ none: false
127
+ requirements:
128
+ - - ! '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ requirements: []
132
+ rubyforge_project:
133
+ rubygems_version: 1.8.24
134
+ signing_key:
135
+ specification_version: 3
136
+ summary: Access control for ActiveModel/ActiveRecord attributes
137
+ test_files: []