ScopedProxy 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ == 1.0.0 / 2007-12-16
2
+
3
+ First official release.
4
+
5
+ * Added default_proxy method.
6
+
7
+ * Added delegation/answer to respond_to?
8
+
9
+ == 0.5.2
10
+
11
+ Inofficial release on our blog neotrivium.com. Fixing a few bugs due to user reports.
12
+
13
+ * v0.4 Fixed a bug with scoping (another one) pointed out to me by Severin Schoepke.
14
+ Scoping will now even work in situations where an AssociationProxy sets the context.
15
+
16
+ * v0.3 Fixed a scoping bug pointed out by fractious; now scoping should work as advertised. Thanks go
17
+ to Florian Hanke for teaming up with me on this.
18
+
@@ -0,0 +1,7 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/scoped_proxy.rb
6
+ spec/scoped_proxy_spec.rb
7
+ spec/spec_helper.rb
@@ -0,0 +1,105 @@
1
+ ScopedProxy
2
+
3
+ == SUMMARY
4
+
5
+ Finally, long awaited, scoped proxies as a release. Which you say. Admitted, there are about 15 plugins
6
+ that are called scoped_proxy, but only one comes with a full suite of tests that is full 40 lines longer
7
+ than the actual code. That is the scoped proxy you are looking at. It is y2k compliant and works as a gem.
8
+ Magnificent, isn't it?
9
+
10
+ This gem is all about simplicity; you won't find a whole toolbox here, just another valued screwdriver.
11
+ But we happen to encounter a lot of screws - hopefully you do too - and find this very useful. If you
12
+ want to skip all this summary that is really longer than all the other texts in this release, have a look
13
+ at the synopsis below.
14
+
15
+ == SYNOPSIS:
16
+
17
+ Allows storing scopes as names; that way you can address subsets of your model space by meaningful names.
18
+
19
+ require 'scoped_proxy' # Railsers: You might want to call this in environment.rb
20
+
21
+ class User < ActiveRecord::Base
22
+ scoped_proxy :role do |role|
23
+ {
24
+ :find => { :conditions => ['role = ?', role] }
25
+ }
26
+ end
27
+ scoped_proxy :deleted, :find => {
28
+ :conditions => 'deleted_at is not null'
29
+ }
30
+ end
31
+
32
+ admins = User.role('admin')
33
+ admins.count # => 12
34
+ admins.find(:all) # => [ ... ]
35
+
36
+ User.deleted.count # => a number
37
+
38
+ This implementation also brings (NEW, SHINY) default proxies. That means you can have a proxy in effect
39
+ when no other proxy is in effect.
40
+
41
+ class User < ActiveRecord::Base
42
+ default_proxy :find => { :conditions => 'deleted_at is null' }
43
+ end
44
+
45
+ User.find(:all) # only finds users that aren't deleted
46
+
47
+ == REQUIREMENTS:
48
+
49
+ * gem install metaid
50
+
51
+ == TESTING
52
+
53
+ If you want to run the rspec tests, you must have a database on localhost called 'test' and a user
54
+ called 'developer' that is allowed to (write)access it. Caution: running the tests will delete at least
55
+ your 'users' table in said database. But I hope you wouldn't run this on production, nor call your
56
+ production database 'test'. Warned you.
57
+
58
+ Details can be changed in spec/spec_helper.rb
59
+
60
+ == THANKS
61
+
62
+ Special Thanks to Florian Hanke (florian at restorm dot com) and Severin Schoepke (severin at restorm dot com).
63
+ They have played an enormous role in keeping this mean and lean. Thanks also to the rest of the gang - please
64
+ visit us at labs.restorm.com.
65
+
66
+ == CONTACT
67
+
68
+ Rubyforge Project:
69
+
70
+ http://rubyforge.org/projects/swissrb
71
+
72
+ Documentation:
73
+
74
+ upcoming...
75
+
76
+ Bugs:
77
+
78
+ http://rubyforge.org/tracker/?atid=19774&group_id=5130&func=browse
79
+
80
+
81
+ == LICENSE:
82
+
83
+ (The MIT License or the Ruby License, choose)
84
+
85
+ Copyright (c) 2007 Kaspar Schiess, all rights reserved
86
+
87
+ Permission is hereby granted, free of charge, to any person obtaining
88
+ a copy of this software and associated documentation files (the
89
+ 'Software'), to deal in the Software without restriction, including
90
+ without limitation the rights to use, copy, modify, merge, publish,
91
+ distribute, sublicense, and/or sell copies of the Software, and to
92
+ permit persons to whom the Software is furnished to do so, subject to
93
+ the following conditions:
94
+
95
+ The above copyright notice and this permission notice shall be
96
+ included in all copies or substantial portions of the Software.
97
+
98
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
99
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
100
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
101
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
102
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
103
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
104
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
105
+
@@ -0,0 +1,25 @@
1
+ require 'rubygems'
2
+ require 'hoe'
3
+ require './lib/scoped_proxy'
4
+
5
+ namespace :hoe do
6
+ Hoe.new('ScopedProxy', ScopedProxy::VERSION) do |p|
7
+ p.rubyforge_name = 'swissrb'
8
+ p.author = 'Kaspar Schiess'
9
+ p.email = 'eule@space.ch'
10
+ p.summary = p.paragraphs_of('README.txt', 1..3).join("\n\n")
11
+ p.description = p.paragraphs_of('README.txt', 4..7).join(' ')
12
+ p.url = p.paragraphs_of('README.txt', 17).join(' ')
13
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
14
+ end
15
+ end
16
+
17
+ require 'spec/rake/spectask'
18
+ require 'spec/translator'
19
+
20
+ task :default => :spec
21
+
22
+ desc "Run all specs in spec directory (excluding plugin specs)"
23
+ Spec::Rake::SpecTask.new(:spec) do |t|
24
+ t.spec_files = FileList['spec/**/*_spec.rb']
25
+ end
@@ -0,0 +1,142 @@
1
+ require 'metaid'
2
+ require 'active_record'
3
+
4
+ # Smart scoping for AR models
5
+ # Author: Kaspar Schiess, (c) by Neotrivium AG, 2007
6
+ # Distributed under the terms of the Ruby License
7
+
8
+ module ScopedProxy
9
+ VERSION = '1.0.0'
10
+
11
+ # Allows you to create scoped proxies in models.
12
+ #
13
+ # Example
14
+ #
15
+ # class User < ActiveRecord::Base
16
+ # scoped_proxy :woman, :find => { :conditions => ['sex=?', 'f'] }
17
+ #
18
+ # Allowed options
19
+ # * :exclusive if set to true, this proxy will not apply if there is another proxy active.
20
+ #
21
+ class Proxy
22
+ cattr_accessor :active_proxy
23
+ @@active_proxy = false
24
+
25
+ def initialize(klass, scope={}, old_scope=nil, options={})
26
+ @klass, @scope, @old_scope = klass, scope, old_scope
27
+ @options = options
28
+ end
29
+
30
+ def method_missing(message, *args, &block)
31
+ exclusive_scope = @options[:exclusive] || false
32
+ activate_proxy = (! exclusive_scope) || (exclusive_scope && no_other_proxy_active?)
33
+
34
+ conditional_scope(@old_scope) do # Install previous scope
35
+ conditional_scope(@scope, activate_proxy) do # Install this scope
36
+ with_proxy do # Track active proxies
37
+ @klass.send(message, *args, &block)
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ def respond_to_with_delegation?(symbol)
44
+ self.respond_to_without_delegation?(symbol) || @klass.respond_to?(symbol)
45
+ end
46
+ alias_method_chain :respond_to?, :delegation
47
+
48
+ # Mark the installation of a proxy. This information can be used to make proxies dependent upon context, as in
49
+ # the default proxy.
50
+ #
51
+ def with_proxy
52
+ begin
53
+ old_value = self.class.active_proxy
54
+ self.class.active_proxy = true
55
+ yield
56
+ ensure
57
+ self.class.active_proxy = old_value
58
+ end
59
+ end
60
+
61
+ # Is there no other proxy than this one active?
62
+ #
63
+ def no_other_proxy_active?
64
+ ! self.class.active_proxy
65
+ end
66
+
67
+ # Install given scope if it is not nil and the predicate is true. Then yield.
68
+ #
69
+ def conditional_scope(scope, pred=true)
70
+ if scope && pred
71
+ klass_with_scope(@klass, scope) do
72
+ yield
73
+ end
74
+ else
75
+ yield
76
+ end
77
+ end
78
+
79
+ # Permit access to with_scope from outside the AR::Base class. This is an exception to the rule ;)
80
+ #
81
+ def klass_with_scope(klass, scope, &block)
82
+ klass.send(:with_scope, scope, &block)
83
+ end
84
+ end
85
+
86
+ def self.included(base)
87
+ base.extend(ClassMethods)
88
+ end
89
+
90
+ module ClassMethods
91
+
92
+ private
93
+
94
+ # Add a default proxy to the object. A default proxy is a proxy that is always in effect, unless another proxy is
95
+ # used.
96
+ #
97
+ # Example
98
+ #
99
+ # class User
100
+ # default_proxy :find => { :conditions => { :deleted_at => nil } }
101
+ # end
102
+ #
103
+ # The example only returns users that haven't been marked as deleted - unless you use another proxy on the User
104
+ # object.
105
+ #
106
+ def default_proxy(scope=nil)
107
+ scoped_proxy :default_proxy, scope, :exclusive => true
108
+
109
+ [:find, :count].each do |meth|
110
+ meth_name = "#{meth}_with_default_proxy"
111
+ meth_without = "#{meth}_without_default_proxy"
112
+ metaclass.class_eval %Q{
113
+ def #{meth_name}(*args, &block)
114
+ default_proxy.#{meth_without}(*args, &block)
115
+ end
116
+ alias_method_chain :#{meth}, :default_proxy
117
+ }
118
+ end
119
+ end
120
+
121
+ # Install a scoped proxy with the given name.
122
+ #
123
+ # Example
124
+ #
125
+ # class User < ActiveRecord::Base
126
+ # scoped_proxy :woman, :find => { :conditions => ['sex=?', 'f'] }
127
+ #
128
+ def scoped_proxy(name, scope=nil, opts={}, &block)
129
+ meta_def(name) do |*args|
130
+ resulting_scope = scope
131
+ resulting_scope = block.call(*args) if block
132
+
133
+ target = self
134
+ Proxy.new(target, resulting_scope, self.current_scoped_methods, opts)
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ class ActiveRecord::Base
141
+ include ScopedProxy
142
+ end
@@ -0,0 +1,164 @@
1
+ require 'spec'
2
+
3
+ require File.join(File.dirname(__FILE__), 'spec_helper')
4
+ require 'scoped_proxy'
5
+
6
+ def silence
7
+ old_verbose, $VERBOSE = $VERBOSE, nil
8
+ yield
9
+ ensure
10
+ $VERBOSE = old_verbose
11
+ end
12
+
13
+ # NOTE: Running these tests WILL destroy a database called test (overwriting tables and such). You have been warned.
14
+
15
+ describe ScopedProxy, " availability" do
16
+ it "should be a module" do
17
+ ScopedProxy.should be_kind_of(Module)
18
+ end
19
+ end
20
+
21
+ silence do
22
+ ActiveRecord::Schema.define do
23
+ create_table :users, :force => true do |t|
24
+ t.column :name, :string
25
+ t.column :role, :string
26
+ end
27
+ end
28
+ end
29
+ class User < ActiveRecord::Base
30
+ scoped_proxy :johns, :find => {
31
+ :conditions => ['name like ?', 'john%']
32
+ }
33
+ scoped_proxy :janes, :find => {
34
+ :conditions => ['name like ?', 'jane%']
35
+ }
36
+ end
37
+ john1 = User.create!(:name => 'john1', :role => 'admin')
38
+ john2 = User.create!(:name => 'john2', :role => 'user')
39
+ jane1 = User.create!(:name => 'jane1', :role => 'user')
40
+
41
+ describe User, " and users that are john" do
42
+ it "should have johns" do
43
+ User.should respond_to(:johns)
44
+ end
45
+ it "johns should return a proxy" do
46
+ User.johns.should be_kind_of(ScopedProxy::Proxy)
47
+ end
48
+ it "should allow us to find all johns" do
49
+ johns = User.find(:all, :conditions => 'name like \'john%\'')
50
+
51
+ User.johns.find(:all).should eql(johns)
52
+ end
53
+ it "should have count and other methods" do
54
+ User.johns.find(:all)
55
+ User.johns.count
56
+ end
57
+ it "should respond to count and other methods" do
58
+ User.johns.should respond_to(:count)
59
+ User.johns.should respond_to(:find)
60
+ User.johns.should respond_to(:destroy_all)
61
+ end
62
+ end
63
+
64
+ class User
65
+ scoped_proxy :role do |role|
66
+ {
67
+ :find => { :conditions => ['role = ?', role] }
68
+ }
69
+ end
70
+ end
71
+ describe User, "extended scoping" do
72
+ it "should have role proxy" do
73
+ User.should respond_to(:role)
74
+ end
75
+ it "should select roles as a filter" do
76
+ ['admin', 'user'].each do |role|
77
+ expectation = User.find(:all, :conditions => {:role => role} )
78
+ User.role(role).find(:all).should eql(expectation)
79
+ end
80
+ end
81
+ it "should allow combining roles into a bigger filter" do
82
+ # try both orders
83
+ User.janes.role('user').find(:first).should eql(jane1)
84
+ User.role('user').janes.find(:first).should eql(jane1)
85
+ end
86
+ it "should allow storing proxy and later on using it" do
87
+ janes = User.janes
88
+
89
+ janes.find(:first).should eql(jane1)
90
+ end
91
+ it "should capture its environment" do
92
+ johns = nil
93
+ User.send(:with_scope, :find => {:conditions => { :role => 'user'} } ) do
94
+ johns = User.johns
95
+ end
96
+
97
+ johns.find(:all).should eql([john2])
98
+ end
99
+ it "should capture a complex scoped environment" do
100
+ johns = nil
101
+ User.send(:with_scope, :find => {:conditions => { :role => 'user'} } ) do
102
+ johns = User.johns.role('admin')
103
+ end
104
+
105
+ johns.find(:all).should be_empty
106
+ end
107
+ end
108
+
109
+ silence do
110
+ ActiveRecord::Schema.define do
111
+ create_table :zebras, :force => true do |t|
112
+ t.column :stripes, :boolean
113
+ end
114
+ end
115
+ end
116
+ class Zebra < ActiveRecord::Base
117
+ default_proxy :find => {
118
+ :conditions => ['stripes = ?', true]
119
+ }
120
+
121
+ # Any further proxying will go back to unproxied default
122
+ scoped_proxy :bad
123
+ end
124
+
125
+ # All good zebras have stripes
126
+ 10.times do Zebra.create!(:stripes => true) end
127
+ # Only some don't
128
+ Zebra.create!(:stripes => false)
129
+
130
+ describe Zebra, "default proxies" do
131
+ it "should not have johns or janes - that's User" do
132
+ # NOTE to the confused reader: For johns to be a method on User, it can either be a method of User's class (== Class)
133
+ # or a method of the singleton metaclass of User (== an anonymous class that inherits from Class). Now, we don't want
134
+ # stuff we define for the user (like eat_at_a_table) to be defined for all Zebras too - this is why we need #meta_def and
135
+ # not class_def. The two solutions are otherwise identical - this expectation forces the decision.
136
+ #
137
+ Zebra.should_not respond_to(:johns)
138
+ Zebra.should_not respond_to(:janes)
139
+ end
140
+
141
+ it "should select only striped zebras by default" do
142
+ Zebra.count.should == 10
143
+
144
+ Zebra.find(:all).each do |zebra|
145
+ zebra.stripes.should eql(true)
146
+ end
147
+ end
148
+ it "should select all zebras with any other proxy" do
149
+ Zebra.send(:with_scope, :find => { :conditions => ['id>0'] }) do
150
+ Zebra.bad.count.should == 11
151
+ end
152
+ Zebra.bad.count.should == 11
153
+ Zebra.bad.bad.count.should == 11
154
+
155
+ Zebra.bad.find(:all, :conditions => { :stripes => false }).each do |zebra|
156
+ zebra.stripes.should == false
157
+ end
158
+ end
159
+ it "should allow accessing the default proxy trough #default_proxy" do
160
+ default = Zebra.default_proxy
161
+
162
+ default.count.should == 10
163
+ end
164
+ end
@@ -0,0 +1,12 @@
1
+
2
+ $:.unshift File.join(File.dirname(__FILE__), '../lib')
3
+
4
+ require 'active_record'
5
+
6
+ ActiveRecord::Base.connection = {
7
+ :user => 'developer',
8
+ :adapter => 'mysql',
9
+ :database => 'test',
10
+ :host => '127.0.0.1'
11
+ }
12
+
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ScopedProxy
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ""
6
+ authors:
7
+ - Kaspar Schiess
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2007-12-21 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.3.0
23
+ version:
24
+ description: "== SYNOPSIS: Allows storing scopes as names; that way you can address subsets of your model space by meaningful names. require 'scoped_proxy' # Railsers: You might want to call this in environment.rb class User < ActiveRecord::Base scoped_proxy :role do |role| { :find => { :conditions => ['role = ?', role] } } end scoped_proxy :deleted, :find => { :conditions => 'deleted_at is not null' } end admins = User.role('admin') admins.count # => 12 admins.find(:all) # => [ ... ] User.deleted.count # => a number This implementation also brings (NEW, SHINY) default proxies. That means you can have a proxy in effect when no other proxy is in effect. class User < ActiveRecord::Base default_proxy :find => { :conditions => 'deleted_at is null' } end User.find(:all) # only finds users that aren't deleted"
25
+ email: eule@space.ch
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files:
31
+ - History.txt
32
+ - Manifest.txt
33
+ - README.txt
34
+ files:
35
+ - History.txt
36
+ - Manifest.txt
37
+ - README.txt
38
+ - Rakefile
39
+ - lib/scoped_proxy.rb
40
+ - spec/scoped_proxy_spec.rb
41
+ - spec/spec_helper.rb
42
+ has_rdoc: true
43
+ homepage: http://rubyforge.org/projects/swissrb
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --main
47
+ - README.txt
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ requirements: []
63
+
64
+ rubyforge_project: swissrb
65
+ rubygems_version: 0.9.5
66
+ signing_key:
67
+ specification_version: 2
68
+ summary: == SUMMARY Finally, long awaited, scoped proxies as a release. Which you say. Admitted, there are about 15 plugins that are called scoped_proxy, but only one comes with a full suite of tests that is full 40 lines longer than the actual code. That is the scoped proxy you are looking at. It is y2k compliant and works as a gem. Magnificent, isn't it? This gem is all about simplicity; you won't find a whole toolbox here, just another valued screwdriver. But we happen to encounter a lot of screws - hopefully you do too - and find this very useful. If you want to skip all this summary that is really longer than all the other texts in this release, have a look at the synopsis below.
69
+ test_files: []
70
+