has_scope 0.1
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/CHANGELOG.rdoc +0 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +51 -0
- data/Rakefile +42 -0
- data/lib/has_scope.rb +127 -0
- data/test/has_scope_test.rb +153 -0
- data/test/test_helper.rb +32 -0
- metadata +60 -0
data/CHANGELOG.rdoc
ADDED
File without changes
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2009 Plataforma Tecnologia. http://blog.plataformatec.com.br
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
== HasScope
|
2
|
+
|
3
|
+
Has scope allows you to easily create controller filters based on your resources named scopes.
|
4
|
+
Imagine the following model called graduations:
|
5
|
+
|
6
|
+
class Graduation < ActiveRecord::Base
|
7
|
+
named_scope :featured, :conditions => { :featured => true }
|
8
|
+
named_scope :by_degree, proc {|degree| { :conditions => { :degree => degree } } }
|
9
|
+
end
|
10
|
+
|
11
|
+
You can use those named scopes as filters by declaring them on your controller:
|
12
|
+
|
13
|
+
class GraduationsController < ApplicationController
|
14
|
+
has_scope :featured, :boolean => true
|
15
|
+
has_scope :by_degree
|
16
|
+
end
|
17
|
+
|
18
|
+
Now, if you want to apply them to an specific resource, you just need to call <tt>apply_scopes</tt>:
|
19
|
+
|
20
|
+
class GraduationsController < ApplicationController
|
21
|
+
has_scope :featured, :boolean => true
|
22
|
+
has_scope :by_degree
|
23
|
+
|
24
|
+
def index
|
25
|
+
@graduations = apply_scopes(Graduations).all
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
Then for each request:
|
30
|
+
|
31
|
+
/graduations
|
32
|
+
#=> acts like a normal request
|
33
|
+
|
34
|
+
/graduations?featured=true
|
35
|
+
#=> calls the named scope and bring featured graduations
|
36
|
+
|
37
|
+
/graduations?featured=true&by_degree=phd
|
38
|
+
#=> brings featured graduations with phd degree
|
39
|
+
|
40
|
+
You can retrieve all the scopes applied in one action with <tt>current_scopes</tt> method.
|
41
|
+
In the last case, it would return: { :featured => true, :by_degree => "phd" }.
|
42
|
+
|
43
|
+
Please check <tt>has_scope</tt> method for all the supported options.
|
44
|
+
|
45
|
+
== Bugs and Feedback
|
46
|
+
|
47
|
+
If you discover any bugs or want to drop a line, feel free to create an issue on GitHub.
|
48
|
+
|
49
|
+
http://github.com/plataformatec/has_scope/issues
|
50
|
+
|
51
|
+
MIT License. Copyright 2009 Plataforma Tecnologia. http://blog.plataformatec.com.br
|
data/Rakefile
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
|
6
|
+
desc 'Default: run unit tests.'
|
7
|
+
task :default => :test
|
8
|
+
|
9
|
+
desc 'Test HasScope'
|
10
|
+
Rake::TestTask.new(:test) do |t|
|
11
|
+
t.libs << 'lib'
|
12
|
+
t.libs << 'test'
|
13
|
+
t.pattern = 'test/**/*_test.rb'
|
14
|
+
t.verbose = true
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'Generate documentation for HasScope'
|
18
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
19
|
+
rdoc.rdoc_dir = 'rdoc'
|
20
|
+
rdoc.title = 'HasScope'
|
21
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
22
|
+
rdoc.rdoc_files.include('README.rdoc')
|
23
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
24
|
+
end
|
25
|
+
|
26
|
+
begin
|
27
|
+
require 'jeweler'
|
28
|
+
Jeweler::Tasks.new do |s|
|
29
|
+
s.name = "has_scope"
|
30
|
+
s.version = "0.1"
|
31
|
+
s.summary = "Maps controller filters to your resource scopes"
|
32
|
+
s.email = "contact@plataformatec.com.br"
|
33
|
+
s.homepage = "http://github.com/plataformatec/has_scope"
|
34
|
+
s.description = "Maps controller filters to your resource scopes"
|
35
|
+
s.authors = ['José Valim']
|
36
|
+
s.files = FileList["[A-Z]*", "lib/**/*", "init.rb"]
|
37
|
+
end
|
38
|
+
|
39
|
+
Jeweler::GemcutterTasks.new
|
40
|
+
rescue LoadError
|
41
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler"
|
42
|
+
end
|
data/lib/has_scope.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
module HasScope
|
2
|
+
TRUE_VALUES = ["true", true, "1", 1]
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
extend ClassMethods
|
7
|
+
helper_method :current_scopes
|
8
|
+
|
9
|
+
class_inheritable_accessor :scopes_configuration, :instance_writer => false
|
10
|
+
self.scopes_configuration ||= {}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
# Detects params from url and apply as scopes to your classes.
|
16
|
+
#
|
17
|
+
# == Options
|
18
|
+
#
|
19
|
+
# * <tt>:boolean</tt> - When set to true, call the scope only when the param is true or 1,
|
20
|
+
# and does not send the value as argument.
|
21
|
+
#
|
22
|
+
# * <tt>:only</tt> - In which actions the scope is applied. By default is :all.
|
23
|
+
#
|
24
|
+
# * <tt>:except</tt> - In which actions the scope is not applied. By default is :none.
|
25
|
+
#
|
26
|
+
# * <tt>:as</tt> - The key in the params hash expected to find the scope.
|
27
|
+
# Defaults to the scope name.
|
28
|
+
#
|
29
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
30
|
+
# if the scope should apply
|
31
|
+
#
|
32
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
|
33
|
+
# if the scope should NOT apply.
|
34
|
+
#
|
35
|
+
# * <tt>:default</tt> - Default value for the scope. Whenever supplied the scope
|
36
|
+
# is always called. This is useful to add easy pagination.
|
37
|
+
#
|
38
|
+
def has_scope(*scopes)
|
39
|
+
options = scopes.extract_options!
|
40
|
+
options.symbolize_keys!
|
41
|
+
options.assert_valid_keys(:boolean, :only, :except, :if, :unless, :default, :as)
|
42
|
+
|
43
|
+
scopes.each do |scope|
|
44
|
+
self.scopes_configuration[scope] ||= {}
|
45
|
+
self.scopes_configuration[scope][:as] = options[:as] || scope
|
46
|
+
self.scopes_configuration[scope][:only] = Array(options[:only])
|
47
|
+
self.scopes_configuration[scope][:except] = Array(options[:except])
|
48
|
+
|
49
|
+
[:if, :unless, :boolean, :default].each do |opt|
|
50
|
+
self.scopes_configuration[scope][opt] = options[opt] if options.key?(opt)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
# Receives an object where scopes will be applied to.
|
59
|
+
#
|
60
|
+
# class GraduationsController < InheritedResources::Base
|
61
|
+
# has_scope :featured, :boolean => true, :only => :index
|
62
|
+
# has_scope :by_degree, :only => :index
|
63
|
+
#
|
64
|
+
# def index
|
65
|
+
# @graduations = apply_scopes(Graduation).all
|
66
|
+
# end
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
def apply_scopes(target_object)
|
70
|
+
self.scopes_configuration.each do |scope, options|
|
71
|
+
next unless apply_scope_to_action?(options)
|
72
|
+
key = options[:as]
|
73
|
+
|
74
|
+
if params.key?(key)
|
75
|
+
value, call_scope = params[key], true
|
76
|
+
elsif options.key?(:default)
|
77
|
+
value, call_scope = options[:default], true
|
78
|
+
value = value.call(self) if value.is_a?(Proc)
|
79
|
+
end
|
80
|
+
|
81
|
+
if call_scope
|
82
|
+
if options[:boolean]
|
83
|
+
target_object = target_object.send(scope) if current_scopes[key] = TRUE_VALUES.include?(value)
|
84
|
+
else
|
85
|
+
current_scopes[key] = value
|
86
|
+
target_object = target_object.send(scope, value)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
target_object
|
92
|
+
end
|
93
|
+
|
94
|
+
# Given an options with :only and :except arrays, check if the scope
|
95
|
+
# can be performed in the current action.
|
96
|
+
def apply_scope_to_action?(options) #:nodoc:
|
97
|
+
return false unless applicable?(options[:if], true) && applicable?(options[:unless], false)
|
98
|
+
|
99
|
+
if options[:only].empty?
|
100
|
+
options[:except].empty? || !options[:except].include?(action_name.to_sym)
|
101
|
+
else
|
102
|
+
options[:only].include?(action_name.to_sym)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Evaluates the scope options :if or :unless. Returns true if the proc
|
107
|
+
# method, or string evals to the expected value.
|
108
|
+
def applicable?(string_proc_or_symbol, expected) #:nodoc:
|
109
|
+
case string_proc_or_symbol
|
110
|
+
when String
|
111
|
+
eval(string_proc_or_symbol) == expected
|
112
|
+
when Proc
|
113
|
+
string_proc_or_symbol.call(self) == expected
|
114
|
+
when Symbol
|
115
|
+
send(string_proc_or_symbol) == expected
|
116
|
+
else
|
117
|
+
true
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Returns the scopes used in this action.
|
122
|
+
def current_scopes
|
123
|
+
@current_scopes ||= {}
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
ApplicationController.send :include, HasScope
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class Tree
|
4
|
+
end
|
5
|
+
|
6
|
+
class TreesController < ApplicationController
|
7
|
+
has_scope :color, :unless => :show_all_colors?
|
8
|
+
has_scope :only_tall, :boolean => true, :only => :index, :if => :restrict_to_only_tall_trees?
|
9
|
+
has_scope :shadown_range, :default => 10, :except => [ :index, :show, :destroy, :new ]
|
10
|
+
has_scope :root_type, :as => :root
|
11
|
+
has_scope :calculate_height, :default => proc {|c| c.session[:height] || 20 }, :only => :new
|
12
|
+
|
13
|
+
def index
|
14
|
+
@trees = apply_scopes(Tree).all
|
15
|
+
end
|
16
|
+
|
17
|
+
def new
|
18
|
+
@tree = apply_scopes(Tree).new
|
19
|
+
end
|
20
|
+
|
21
|
+
def show
|
22
|
+
@tree = apply_scopes(Tree).find(params[:id])
|
23
|
+
end
|
24
|
+
alias :edit :show
|
25
|
+
|
26
|
+
protected
|
27
|
+
def restrict_to_only_tall_trees?
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def show_all_colors?
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
def default_render
|
36
|
+
render :text => action_name
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class HasScopeTest < ActionController::TestCase
|
41
|
+
tests TreesController
|
42
|
+
|
43
|
+
def test_boolean_scope_is_called_when_boolean_param_is_true
|
44
|
+
Tree.expects(:only_tall).with().returns(Tree).in_sequence
|
45
|
+
Tree.expects(:all).returns([mock_tree]).in_sequence
|
46
|
+
get :index, :only_tall => 'true'
|
47
|
+
assert_equal([mock_tree], assigns(:trees))
|
48
|
+
assert_equal({ :only_tall => true }, current_scopes)
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_boolean_scope_is_called_when_boolean_param_is_false
|
52
|
+
Tree.expects(:only_tall).never
|
53
|
+
Tree.expects(:all).returns([mock_tree])
|
54
|
+
get :index, :only_tall => 'false'
|
55
|
+
assert_equal([mock_tree], assigns(:trees))
|
56
|
+
assert_equal({ :only_tall => false }, current_scopes)
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_scope_is_called_only_on_index
|
60
|
+
Tree.expects(:only_tall).never
|
61
|
+
Tree.expects(:find).with('42').returns(mock_tree)
|
62
|
+
get :show, :only_tall => 'true', :id => '42'
|
63
|
+
assert_equal(mock_tree, assigns(:tree))
|
64
|
+
assert_equal({ }, current_scopes)
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_scope_is_skipped_when_if_option_is_false
|
68
|
+
@controller.stubs(:restrict_to_only_tall_trees?).returns(false)
|
69
|
+
Tree.expects(:only_tall).never
|
70
|
+
Tree.expects(:all).returns([mock_tree])
|
71
|
+
get :index, :only_tall => 'true'
|
72
|
+
assert_equal([mock_tree], assigns(:trees))
|
73
|
+
assert_equal({ }, current_scopes)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_scope_is_skipped_when_unless_option_is_true
|
77
|
+
@controller.stubs(:show_all_colors?).returns(true)
|
78
|
+
Tree.expects(:color).never
|
79
|
+
Tree.expects(:all).returns([mock_tree])
|
80
|
+
get :index, :color => 'blue'
|
81
|
+
assert_equal([mock_tree], assigns(:trees))
|
82
|
+
assert_equal({ }, current_scopes)
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_scope_is_called_except_on_index
|
86
|
+
Tree.expects(:shadown_range).with().never
|
87
|
+
Tree.expects(:all).returns([mock_tree])
|
88
|
+
get :index, :shadown_range => 20
|
89
|
+
assert_equal([mock_tree], assigns(:trees))
|
90
|
+
assert_equal({ }, current_scopes)
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_scope_is_called_with_arguments
|
94
|
+
Tree.expects(:color).with('blue').returns(Tree).in_sequence
|
95
|
+
Tree.expects(:all).returns([mock_tree]).in_sequence
|
96
|
+
get :index, :color => 'blue'
|
97
|
+
assert_equal([mock_tree], assigns(:trees))
|
98
|
+
assert_equal({ :color => 'blue' }, current_scopes)
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_multiple_scopes_are_called
|
102
|
+
Tree.expects(:only_tall).with().returns(Tree)
|
103
|
+
Tree.expects(:color).with('blue').returns(Tree)
|
104
|
+
Tree.expects(:all).returns([mock_tree])
|
105
|
+
get :index, :color => 'blue', :only_tall => 'true'
|
106
|
+
assert_equal([mock_tree], assigns(:trees))
|
107
|
+
assert_equal({ :color => 'blue', :only_tall => true }, current_scopes)
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_scope_is_called_with_default_value
|
111
|
+
Tree.expects(:shadown_range).with(10).returns(Tree).in_sequence
|
112
|
+
Tree.expects(:find).with('42').returns(mock_tree).in_sequence
|
113
|
+
get :edit, :id => '42'
|
114
|
+
assert_equal(mock_tree, assigns(:tree))
|
115
|
+
assert_equal({ :shadown_range => 10 }, current_scopes)
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_default_scope_value_can_be_overwritten
|
119
|
+
Tree.expects(:shadown_range).with('20').returns(Tree).in_sequence
|
120
|
+
Tree.expects(:find).with('42').returns(mock_tree).in_sequence
|
121
|
+
get :edit, :id => '42', :shadown_range => '20'
|
122
|
+
assert_equal(mock_tree, assigns(:tree))
|
123
|
+
assert_equal({ :shadown_range => '20' }, current_scopes)
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_scope_with_different_key
|
127
|
+
Tree.expects(:root_type).with('outside').returns(Tree).in_sequence
|
128
|
+
Tree.expects(:find).with('42').returns(mock_tree).in_sequence
|
129
|
+
get :show, :id => '42', :root => 'outside'
|
130
|
+
assert_equal(mock_tree, assigns(:tree))
|
131
|
+
assert_equal({ :root => 'outside' }, current_scopes)
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_scope_with_default_value_as_proc
|
135
|
+
session[:height] = 100
|
136
|
+
Tree.expects(:calculate_height).with(100).returns(Tree).in_sequence
|
137
|
+
Tree.expects(:new).returns(mock_tree).in_sequence
|
138
|
+
get :new
|
139
|
+
assert_equal(mock_tree, assigns(:tree))
|
140
|
+
assert_equal({ :calculate_height => 100 }, current_scopes)
|
141
|
+
end
|
142
|
+
|
143
|
+
protected
|
144
|
+
|
145
|
+
def mock_tree(stubs={})
|
146
|
+
@mock_tree ||= mock(stubs)
|
147
|
+
end
|
148
|
+
|
149
|
+
def current_scopes
|
150
|
+
@controller.send :current_scopes
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
begin
|
4
|
+
gem "test-unit"
|
5
|
+
rescue LoadError
|
6
|
+
end
|
7
|
+
|
8
|
+
begin
|
9
|
+
gem "ruby-debug"
|
10
|
+
require 'ruby-debug'
|
11
|
+
rescue LoadError
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'test/unit'
|
15
|
+
require 'mocha'
|
16
|
+
|
17
|
+
ENV["RAILS_ENV"] = "test"
|
18
|
+
RAILS_ROOT = "anywhere"
|
19
|
+
|
20
|
+
require 'active_support'
|
21
|
+
require 'action_controller'
|
22
|
+
require 'action_controller/test_case'
|
23
|
+
require 'action_controller/test_process'
|
24
|
+
|
25
|
+
class ApplicationController < ActionController::Base; end
|
26
|
+
|
27
|
+
$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
28
|
+
require 'has_scope'
|
29
|
+
|
30
|
+
ActionController::Routing::Routes.draw do |map|
|
31
|
+
map.connect ':controller/:action/:id'
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: has_scope
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "Jos\xC3\xA9 Valim"
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-12-04 00:00:00 -02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Maps controller filters to your resource scopes
|
17
|
+
email: contact@plataformatec.com.br
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.rdoc
|
24
|
+
files:
|
25
|
+
- CHANGELOG.rdoc
|
26
|
+
- MIT-LICENSE
|
27
|
+
- README.rdoc
|
28
|
+
- Rakefile
|
29
|
+
- lib/has_scope.rb
|
30
|
+
has_rdoc: true
|
31
|
+
homepage: http://github.com/plataformatec/has_scope
|
32
|
+
licenses: []
|
33
|
+
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options:
|
36
|
+
- --charset=UTF-8
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
requirements: []
|
52
|
+
|
53
|
+
rubyforge_project:
|
54
|
+
rubygems_version: 1.3.5
|
55
|
+
signing_key:
|
56
|
+
specification_version: 3
|
57
|
+
summary: Maps controller filters to your resource scopes
|
58
|
+
test_files:
|
59
|
+
- test/has_scope_test.rb
|
60
|
+
- test/test_helper.rb
|