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.
File without changes
@@ -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.
@@ -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
@@ -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
@@ -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
+
@@ -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