has_scope 0.7.2 → 0.8.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.
- checksums.yaml +4 -4
- data/README.md +171 -39
- data/lib/has_scope/version.rb +1 -1
- data/lib/has_scope.rb +38 -17
- metadata +17 -18
- data/test/has_scope_test.rb +0 -375
- data/test/test_helper.rb +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: abf0ad461389302c6eef573bc313b2f0753a7dfaa02c23fdf685acce47be111c
|
4
|
+
data.tar.gz: 79209897cf738ba373be55f2b9fe4543d15880ae466c9a577e23cc1abb06ee3d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 04a17d0ea9ee72e18ad98fa79c9d4e3a856219673fe066d1326df8b12af29a22f80282f0ec4cb8de9e25653d5f1179137fa4a8475d5fd8bf247ff12f3ab8c20e
|
7
|
+
data.tar.gz: 87978f08fed8aec6fe006b0d9faee4704b831a11b8b61b1c1211537965d4cd59a85784229d0e3260c49bd7f1313591ba48ac2feb0580417cb69ca1c60d435ad4
|
data/README.md
CHANGED
@@ -1,36 +1,56 @@
|
|
1
1
|
## HasScope
|
2
2
|
|
3
3
|
[](http://badge.fury.io/rb/has_scope)
|
4
|
-
[](http://travis-ci.org/plataformatec/has_scope)
|
5
|
-
[](https://codeclimate.com/github/plataformatec/has_scope)
|
6
4
|
|
7
|
-
|
8
|
-
|
5
|
+
_HasScope_ allows you to dynamically apply named scopes to your resources based on an incoming set of parameters.
|
6
|
+
|
7
|
+
The most common usage is to map incoming controller parameters to named scopes for filtering resources, but it can be used anywhere.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add `has_scope` to your bundle
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
bundle add has_scope
|
15
|
+
```
|
16
|
+
|
17
|
+
or add it manually to your Gemfile if you prefer.
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'has_scope'
|
21
|
+
```
|
22
|
+
|
23
|
+
## Examples
|
24
|
+
|
25
|
+
For the following examples we'll use a model called graduations:
|
9
26
|
|
10
27
|
```ruby
|
11
28
|
class Graduation < ActiveRecord::Base
|
12
|
-
scope :featured, -> { where(:
|
13
|
-
scope :by_degree, -> degree { where(:
|
29
|
+
scope :featured, -> { where(featured: true) }
|
30
|
+
scope :by_degree, -> degree { where(degree: degree) }
|
14
31
|
scope :by_period, -> started_at, ended_at { where("started_at = ? AND ended_at = ?", started_at, ended_at) }
|
15
32
|
end
|
16
33
|
```
|
17
34
|
|
18
|
-
|
35
|
+
### Usage 1: Rails Controllers
|
36
|
+
|
37
|
+
_HasScope_ exposes the `has_scope` method automatically in all your controllers. This is used to declare the scopes a controller action can use to filter a resource:
|
19
38
|
|
20
39
|
```ruby
|
21
40
|
class GraduationsController < ApplicationController
|
22
|
-
has_scope :featured, :
|
41
|
+
has_scope :featured, type: :boolean
|
23
42
|
has_scope :by_degree
|
43
|
+
has_scope :by_period, using: %i[started_at ended_at], type: :hash
|
24
44
|
end
|
25
45
|
```
|
26
46
|
|
27
|
-
|
47
|
+
To apply the scopes to a specific resource, you just need to call `apply_scopes`:
|
28
48
|
|
29
49
|
```ruby
|
30
50
|
class GraduationsController < ApplicationController
|
31
|
-
has_scope :featured, :
|
51
|
+
has_scope :featured, type: :boolean
|
32
52
|
has_scope :by_degree
|
33
|
-
has_scope :by_period, :
|
53
|
+
has_scope :by_period, using: %i[started_at ended_at], type: :hash
|
34
54
|
|
35
55
|
def index
|
36
56
|
@graduations = apply_scopes(Graduation).all
|
@@ -38,36 +58,128 @@ class GraduationsController < ApplicationController
|
|
38
58
|
end
|
39
59
|
```
|
40
60
|
|
41
|
-
Then for each request:
|
61
|
+
Then for each request to the `index` action, _HasScope_ will automatically apply the scopes as follows:
|
42
62
|
|
43
|
-
```
|
44
|
-
/graduations
|
45
|
-
|
63
|
+
``` ruby
|
64
|
+
# GET /graduations
|
65
|
+
# No scopes applied
|
66
|
+
#=> brings all graduations
|
67
|
+
apply_scopes(Graduation).all == Graduation.all
|
46
68
|
|
47
|
-
/graduations?featured=true
|
48
|
-
|
69
|
+
# GET /graduations?featured=true
|
70
|
+
# The "featured' scope is applied
|
71
|
+
#=> brings featured graduations
|
72
|
+
apply_scopes(Graduation).all == Graduation.featured
|
49
73
|
|
50
|
-
/graduations?by_period[started_at]=20100701&by_period[ended_at]=20101013
|
74
|
+
# GET /graduations?by_period[started_at]=20100701&by_period[ended_at]=20101013
|
51
75
|
#=> brings graduations in the given period
|
76
|
+
apply_scopes(Graduation).all == Graduation.by_period('20100701', '20101013')
|
52
77
|
|
53
|
-
/graduations?featured=true&by_degree=phd
|
78
|
+
# GET /graduations?featured=true&by_degree=phd
|
54
79
|
#=> brings featured graduations with phd degree
|
80
|
+
apply_scopes(Graduation).all == Graduation.featured.by_degree('phd')
|
81
|
+
|
82
|
+
# GET /graduations?finished=true&by_degree=phd
|
83
|
+
#=> brings only graduations with phd degree because we didn't declare finished in our controller as a permitted scope
|
84
|
+
apply_scopes(Graduation).all == Graduation.by_degree('phd')
|
55
85
|
```
|
56
86
|
|
57
|
-
|
58
|
-
In the last case, it would return: { :featured => true, :by_degree => "phd" }.
|
87
|
+
#### Check for currently applied scopes
|
59
88
|
|
60
|
-
|
89
|
+
_HasScope_ creates a helper method called `current_scopes` to retrieve all the scopes applied. As it's a helper method, you'll be able to access it in the controller action or the view rendered in that action.
|
61
90
|
|
62
|
-
|
91
|
+
Coming back to one of the examples above:
|
63
92
|
|
64
93
|
```ruby
|
65
|
-
|
94
|
+
# GET /graduations?featured=true&by_degree=phd
|
95
|
+
#=> brings featured graduations with phd degree
|
96
|
+
apply_scopes(Graduation).all == Graduation.featured.by_degree('phd')
|
97
|
+
```
|
98
|
+
|
99
|
+
Calling `current_scopes` after `apply_scopes` in the controller action or view would return the following:
|
100
|
+
|
101
|
+
```
|
102
|
+
current_scopes
|
103
|
+
#=> { featured: true, by_degree: 'phd' }
|
104
|
+
```
|
105
|
+
|
106
|
+
### Usage 2: Standalone Mode
|
107
|
+
|
108
|
+
_HasScope_ can also be used in plain old Ruby objects (PORO). To implement the previous example using this approach, create a bare object and include `HasScope` to get access to its features:
|
109
|
+
|
110
|
+
> Note: We'll create a simple version of a query object for this example as this type of object can have multiple different implementations.
|
111
|
+
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
class GraduationsSearchQuery
|
115
|
+
include HasScope
|
116
|
+
# ...
|
117
|
+
end
|
118
|
+
```
|
119
|
+
|
120
|
+
Next, declare the scopes to be used the same way:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
class GraduationsSearchQuery
|
124
|
+
include HasScope
|
125
|
+
|
126
|
+
has_scope :featured, type: :boolean
|
127
|
+
has_scope :by_degree
|
128
|
+
has_scope :by_period, using: %i[started_at ended_at], type: :hash
|
129
|
+
# ...
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
Now, allow your object to perform the query by exposing a method that will use `apply_scopes`:
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
class GraduationsSearchQuery
|
137
|
+
include HasScope
|
138
|
+
|
139
|
+
has_scope :featured, type: :boolean
|
140
|
+
has_scope :by_degree
|
141
|
+
has_scope :by_period, using: %i[started_at ended_at], type: :hash
|
142
|
+
|
143
|
+
def perform(collection: Graduation, params: {})
|
144
|
+
apply_scopes(collection, params)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
Note that `apply_scopes` receives a `Hash` as a second argument, which represents the incoming params that determine which scopes should be applied to the model/collection. It defaults to `params` for compatibility with controllers, which is why it's not necessary to pass that second argument in the controller context.
|
150
|
+
|
151
|
+
Now in your controller you can call the `GraduationsSearchQuery` with the incoming parameters from the controller:
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
class GraduationsController < ApplicationController
|
155
|
+
def index
|
156
|
+
graduations_query = GraduationsSearchQuery.new
|
157
|
+
@graduations = graduations_query.perform(collection: Graduation, params: params)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
```
|
161
|
+
|
162
|
+
#### Accessing `current_scopes`
|
163
|
+
|
164
|
+
In the controller context, `current_scopes` is made available as a helper method to the controller and view, but it's a `protected` method of _HasScope_'s implementation, to prevent it from becoming publicly accessible outside of _HasScope_ itself. This means that the object implementation showed above has access to `current_scopes` internally, but it's not exposed to other objects that interact with it.
|
165
|
+
|
166
|
+
If you need to access `current_scopes` elsewhere, you can change the method visibility like so:
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
class GraduationsSearchQuery
|
170
|
+
include HasScope
|
171
|
+
|
172
|
+
# ...
|
173
|
+
|
174
|
+
public :current_scopes
|
175
|
+
|
176
|
+
# ...
|
177
|
+
end
|
66
178
|
```
|
67
179
|
|
68
180
|
## Options
|
69
181
|
|
70
|
-
|
182
|
+
`has_scope` supports several options:
|
71
183
|
|
72
184
|
* `:type` - Checks the type of the parameter sent.
|
73
185
|
By default, it does not allow hashes or arrays to be given,
|
@@ -83,23 +195,23 @@ HasScope supports several options:
|
|
83
195
|
|
84
196
|
* `:using` - The subkeys to be used as args when type is a hash.
|
85
197
|
|
86
|
-
* `:
|
198
|
+
* `:in` - A shortcut for combining the `:using` option with nested hashes.
|
87
199
|
|
88
|
-
* `:
|
200
|
+
* `:if` - Specifies a method or proc to call to determine if the scope should apply. Passing a string is deprecated and it will be removed in a future version.
|
201
|
+
|
202
|
+
* `:unless` - Specifies a method or proc to call to determine if the scope should NOT apply. Passing a string is deprecated and it will be removed in a future version.
|
89
203
|
|
90
204
|
* `:default` - Default value for the scope. Whenever supplied the scope is always called.
|
91
205
|
|
92
206
|
* `:allow_blank` - Blank values are not sent to scopes by default. Set to true to overwrite.
|
93
207
|
|
94
|
-
* `:in` - A shortcut for combining the `:using` option with nested hashes.
|
95
|
-
|
96
208
|
## Boolean usage
|
97
209
|
|
98
210
|
If `type: :boolean` is set it just calls the named scope, without any arguments, when parameter
|
99
211
|
is set to a "true" value. `'true'` and `'1'` are parsed as `true`, everything else as `false`.
|
100
212
|
|
101
|
-
When boolean scope is set up with `allow_blank: true`, it will call the scope
|
102
|
-
|
213
|
+
When boolean scope is set up with `allow_blank: true`, it will call the scope with the value as
|
214
|
+
any usual scope.
|
103
215
|
|
104
216
|
```ruby
|
105
217
|
has_scope :visible, type: :boolean
|
@@ -110,15 +222,23 @@ scope :visible, -> { where(visible: true) }
|
|
110
222
|
scope :active, ->(value = true) { where(active: value) }
|
111
223
|
```
|
112
224
|
|
225
|
+
_Note_: it is not possible to apply a boolean scope with just the query param being present, e.g.
|
226
|
+
`?active`, that's not considered a "true" value (the param value will be `nil`), and thus the
|
227
|
+
scope will be called with `false` as argument. In order for the scope to receive a `true` argument
|
228
|
+
the param value must be set to one of the "true" values above, e.g. `?active=true` or `?active=1`.
|
229
|
+
|
113
230
|
## Block usage
|
114
231
|
|
115
|
-
`has_scope` also accepts a block
|
116
|
-
|
117
|
-
|
232
|
+
`has_scope` also accepts a block in case we need to manipulate the given value and/or call the scope in some custom way. Usually three arguments are passed to the block:
|
233
|
+
- The instance of the controller or object where it's included
|
234
|
+
- The current scope chain
|
235
|
+
- The value of the scope to apply
|
236
|
+
|
237
|
+
> 💡 We suggest you name the first argument depending on how you're using _HasScope_. If it's the controller, use the word "controller". If it's a query object for example, use "query", or something meaningful for that context (or simply use "context"). In the following examples, we'll use controller for simplicity.
|
118
238
|
|
119
239
|
```ruby
|
120
240
|
has_scope :category do |controller, scope, value|
|
121
|
-
|
241
|
+
value != 'all' ? scope.by_category(value) : scope
|
122
242
|
end
|
123
243
|
```
|
124
244
|
|
@@ -126,7 +246,7 @@ When used with booleans without `:allow_blank`, it just receives two arguments
|
|
126
246
|
and is just invoked if true is given:
|
127
247
|
|
128
248
|
```ruby
|
129
|
-
has_scope :not_voted_by_me, :
|
249
|
+
has_scope :not_voted_by_me, type: :boolean do |controller, scope|
|
130
250
|
scope.not_voted_by(controller.current_user.id)
|
131
251
|
end
|
132
252
|
```
|
@@ -159,10 +279,22 @@ scope :available, ->(*) { where(blocked: false) }
|
|
159
279
|
This will allow usual users to get only available items, but admins will
|
160
280
|
be able to access blocked items too.
|
161
281
|
|
282
|
+
## Check which scopes have been applied
|
283
|
+
|
284
|
+
To check which scopes have been applied, you can call `current_scopes` from the controller or view.
|
285
|
+
This returns a hash with the scope name as the key and the scope value as the value.
|
286
|
+
|
287
|
+
For example, if a boolean `:active` scope has been applied, `current_scopes` will return `{ active: true }`.
|
288
|
+
|
289
|
+
## Supported Ruby / Rails versions
|
290
|
+
|
291
|
+
We intend to maintain support for all Ruby / Rails versions that haven't reached end-of-life.
|
292
|
+
|
293
|
+
For more information about specific versions please check [Ruby](https://www.ruby-lang.org/en/downloads/branches/)
|
294
|
+
and [Rails](https://guides.rubyonrails.org/maintenance_policy.html) maintenance policies, and our test matrix.
|
295
|
+
|
162
296
|
## Bugs and Feedback
|
163
297
|
|
164
298
|
If you discover any bugs or want to drop a line, feel free to create an issue on GitHub.
|
165
299
|
|
166
|
-
http://
|
167
|
-
|
168
|
-
MIT License. Copyright 2009-2016 Plataformatec. http://blog.plataformatec.com.br
|
300
|
+
MIT License. Copyright 2009-2019 Plataformatec. http://blog.plataformatec.com.br
|
data/lib/has_scope/version.rb
CHANGED
data/lib/has_scope.rb
CHANGED
@@ -1,17 +1,20 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'action_controller'
|
3
|
+
|
1
4
|
module HasScope
|
2
5
|
TRUE_VALUES = ["true", true, "1", 1]
|
3
6
|
|
4
7
|
ALLOWED_TYPES = {
|
5
|
-
:
|
6
|
-
:
|
7
|
-
:
|
8
|
-
:
|
8
|
+
array: [[ Array ]],
|
9
|
+
hash: [[ Hash, ActionController::Parameters ]],
|
10
|
+
boolean: [[ Object ], -> v { TRUE_VALUES.include?(v) }],
|
11
|
+
default: [[ String, Numeric ]],
|
9
12
|
}
|
10
13
|
|
11
14
|
def self.included(base)
|
12
15
|
base.class_eval do
|
13
16
|
extend ClassMethods
|
14
|
-
class_attribute :scopes_configuration, :
|
17
|
+
class_attribute :scopes_configuration, instance_writer: false
|
15
18
|
self.scopes_configuration = {}
|
16
19
|
end
|
17
20
|
end
|
@@ -36,6 +39,8 @@ module HasScope
|
|
36
39
|
# * <tt>:using</tt> - If type is a hash, you can provide :using to convert the hash to
|
37
40
|
# a named scope call with several arguments.
|
38
41
|
#
|
42
|
+
# * <tt>:in</tt> - A shortcut for combining the `:using` option with nested hashes.
|
43
|
+
#
|
39
44
|
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
40
45
|
# if the scope should apply
|
41
46
|
#
|
@@ -57,7 +62,7 @@ module HasScope
|
|
57
62
|
# value != "all" ? scope.by_category(value) : scope
|
58
63
|
# end
|
59
64
|
#
|
60
|
-
# has_scope :not_voted_by_me, :
|
65
|
+
# has_scope :not_voted_by_me, type: :boolean do |controller, scope|
|
61
66
|
# scope.not_voted_by(controller.current_user.id)
|
62
67
|
# end
|
63
68
|
#
|
@@ -69,6 +74,10 @@ module HasScope
|
|
69
74
|
if options.key?(:in)
|
70
75
|
options[:as] = options[:in]
|
71
76
|
options[:using] = scopes
|
77
|
+
|
78
|
+
if options.key?(:default) && !options[:default].is_a?(Hash)
|
79
|
+
options[:default] = scopes.each_with_object({}) { |scope, hash| hash[scope] = options[:default] }
|
80
|
+
end
|
72
81
|
end
|
73
82
|
|
74
83
|
if options.key?(:using)
|
@@ -87,7 +96,7 @@ module HasScope
|
|
87
96
|
self.scopes_configuration = scopes_configuration.dup
|
88
97
|
|
89
98
|
scopes.each do |scope|
|
90
|
-
scopes_configuration[scope] ||= { :
|
99
|
+
scopes_configuration[scope] ||= { as: scope, type: :default, block: block }
|
91
100
|
scopes_configuration[scope] = self.scopes_configuration[scope].merge(options)
|
92
101
|
end
|
93
102
|
end
|
@@ -97,16 +106,16 @@ module HasScope
|
|
97
106
|
|
98
107
|
# Receives an object where scopes will be applied to.
|
99
108
|
#
|
100
|
-
# class GraduationsController <
|
101
|
-
# has_scope :featured, :
|
102
|
-
# has_scope :by_degree, :
|
109
|
+
# class GraduationsController < ApplicationController
|
110
|
+
# has_scope :featured, type: true, only: :index
|
111
|
+
# has_scope :by_degree, only: :index
|
103
112
|
#
|
104
113
|
# def index
|
105
114
|
# @graduations = apply_scopes(Graduation).all
|
106
115
|
# end
|
107
116
|
# end
|
108
117
|
#
|
109
|
-
def apply_scopes(target, hash=params)
|
118
|
+
def apply_scopes(target, hash = params)
|
110
119
|
scopes_configuration.each do |scope, options|
|
111
120
|
next unless apply_scope_to_action?(options)
|
112
121
|
key = options[:as]
|
@@ -120,12 +129,20 @@ module HasScope
|
|
120
129
|
end
|
121
130
|
end
|
122
131
|
|
123
|
-
value = parse_value(options[:type],
|
132
|
+
value = parse_value(options[:type], value)
|
124
133
|
value = normalize_blanks(value)
|
125
134
|
|
126
|
-
if
|
135
|
+
if value && options.key?(:using)
|
136
|
+
scope_value = value.values_at(*options[:using])
|
137
|
+
call_scope &&= scope_value.all?(&:present?) || options[:allow_blank]
|
138
|
+
else
|
139
|
+
scope_value = value
|
140
|
+
call_scope &&= value.present? || options[:allow_blank]
|
141
|
+
end
|
142
|
+
|
143
|
+
if call_scope
|
127
144
|
current_scopes[key] = value
|
128
|
-
target = call_scope_by_type(options[:type], scope, target,
|
145
|
+
target = call_scope_by_type(options[:type], scope, target, scope_value, options)
|
129
146
|
end
|
130
147
|
end
|
131
148
|
|
@@ -133,7 +150,7 @@ module HasScope
|
|
133
150
|
end
|
134
151
|
|
135
152
|
# Set the real value for the current scope if type check.
|
136
|
-
def parse_value(type,
|
153
|
+
def parse_value(type, value) #:nodoc:
|
137
154
|
klasses, parser = ALLOWED_TYPES[type]
|
138
155
|
if klasses.any? { |klass| value.is_a?(klass) }
|
139
156
|
parser ? parser.call(value) : value
|
@@ -160,8 +177,7 @@ module HasScope
|
|
160
177
|
|
161
178
|
if type == :boolean && !options[:allow_blank]
|
162
179
|
block ? block.call(self, target) : target.send(scope)
|
163
|
-
elsif
|
164
|
-
value = value.values_at(*options[:using])
|
180
|
+
elsif options.key?(:using)
|
165
181
|
block ? block.call(self, target, value) : target.send(scope, *value)
|
166
182
|
else
|
167
183
|
block ? block.call(self, target, value) : target.send(scope, value)
|
@@ -185,6 +201,11 @@ module HasScope
|
|
185
201
|
def applicable?(string_proc_or_symbol, expected) #:nodoc:
|
186
202
|
case string_proc_or_symbol
|
187
203
|
when String
|
204
|
+
ActiveSupport::Deprecation.warn <<-DEPRECATION.squish
|
205
|
+
[HasScope] Passing a string to determine if the scope should be applied
|
206
|
+
is deprecated and it will be removed in a future version of HasScope.
|
207
|
+
DEPRECATION
|
208
|
+
|
188
209
|
eval(string_proc_or_symbol) == expected
|
189
210
|
when Proc
|
190
211
|
string_proc_or_symbol.call(self) == expected
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: has_scope
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- José Valim
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-02-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '5.2'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '5.2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activesupport
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '5.2'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '5.2'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -77,13 +77,15 @@ files:
|
|
77
77
|
- README.md
|
78
78
|
- lib/has_scope.rb
|
79
79
|
- lib/has_scope/version.rb
|
80
|
-
- test/has_scope_test.rb
|
81
|
-
- test/test_helper.rb
|
82
80
|
homepage: http://github.com/plataformatec/has_scope
|
83
81
|
licenses:
|
84
82
|
- MIT
|
85
|
-
metadata:
|
86
|
-
|
83
|
+
metadata:
|
84
|
+
homepage_uri: https://github.com/heartcombo/has_scope
|
85
|
+
changelog_uri: https://github.com/heartcombo/has_scope/blob/main/CHANGELOG.md
|
86
|
+
source_code_uri: https://github.com/heartcombo/has_scope
|
87
|
+
bug_tracker_uri: https://github.com/heartcombo/has_scope/issues
|
88
|
+
post_install_message:
|
87
89
|
rdoc_options:
|
88
90
|
- "--charset=UTF-8"
|
89
91
|
require_paths:
|
@@ -92,18 +94,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
92
94
|
requirements:
|
93
95
|
- - ">="
|
94
96
|
- !ruby/object:Gem::Version
|
95
|
-
version: 2.
|
97
|
+
version: 2.5.0
|
96
98
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
99
|
requirements:
|
98
100
|
- - ">="
|
99
101
|
- !ruby/object:Gem::Version
|
100
102
|
version: '0'
|
101
103
|
requirements: []
|
102
|
-
|
103
|
-
|
104
|
-
signing_key:
|
104
|
+
rubygems_version: 3.4.5
|
105
|
+
signing_key:
|
105
106
|
specification_version: 4
|
106
107
|
summary: Maps controller filters to your resource scopes.
|
107
|
-
test_files:
|
108
|
-
- test/test_helper.rb
|
109
|
-
- test/has_scope_test.rb
|
108
|
+
test_files: []
|
data/test/has_scope_test.rb
DELETED
@@ -1,375 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
HasScope::ALLOWED_TYPES[:date] = [[String], -> v { Date.parse(v) rescue nil }]
|
4
|
-
|
5
|
-
class Tree; end
|
6
|
-
|
7
|
-
class TreesController < ApplicationController
|
8
|
-
has_scope :color, :unless => :show_all_colors?
|
9
|
-
has_scope :only_tall, :type => :boolean, :only => :index, :if => :restrict_to_only_tall_trees?
|
10
|
-
has_scope :shadown_range, :default => 10, :except => [ :index, :show, :new ]
|
11
|
-
has_scope :root_type, :as => :root, :allow_blank => true
|
12
|
-
has_scope :planted_before, :default => proc { Date.today }
|
13
|
-
has_scope :planted_after, :type => :date
|
14
|
-
has_scope :calculate_height, :default => proc {|c| c.session[:height] || 20 }, :only => :new
|
15
|
-
has_scope :paginate, :type => :hash
|
16
|
-
has_scope :args_paginate, :type => :hash, :using => [:page, :per_page]
|
17
|
-
has_scope :categories, :type => :array
|
18
|
-
has_scope :title, :in => :q
|
19
|
-
has_scope :content, :in => :q
|
20
|
-
has_scope :conifer, type: :boolean, :allow_blank => true
|
21
|
-
|
22
|
-
has_scope :only_short, :type => :boolean do |controller, scope|
|
23
|
-
scope.only_really_short!(controller.object_id)
|
24
|
-
end
|
25
|
-
|
26
|
-
has_scope :by_category do |controller, scope, value|
|
27
|
-
scope.by_given_category(controller.object_id, value + "_id")
|
28
|
-
end
|
29
|
-
|
30
|
-
def index
|
31
|
-
@trees = apply_scopes(Tree).all
|
32
|
-
end
|
33
|
-
|
34
|
-
def new
|
35
|
-
@tree = apply_scopes(Tree).new
|
36
|
-
end
|
37
|
-
|
38
|
-
def show
|
39
|
-
@tree = apply_scopes(Tree).find(params[:id])
|
40
|
-
end
|
41
|
-
|
42
|
-
alias :edit :show
|
43
|
-
|
44
|
-
protected
|
45
|
-
def restrict_to_only_tall_trees?
|
46
|
-
true
|
47
|
-
end
|
48
|
-
|
49
|
-
def show_all_colors?
|
50
|
-
false
|
51
|
-
end
|
52
|
-
|
53
|
-
if ActionPack::VERSION::MAJOR == 5
|
54
|
-
def default_render
|
55
|
-
render body: action_name
|
56
|
-
end
|
57
|
-
else
|
58
|
-
# TODO: Remove this when we only support Rails 5.
|
59
|
-
def default_render
|
60
|
-
render text: action_name
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
class BonsaisController < TreesController
|
66
|
-
has_scope :categories, :if => :categories?
|
67
|
-
|
68
|
-
protected
|
69
|
-
def categories?
|
70
|
-
false
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
class HasScopeTest < ActionController::TestCase
|
75
|
-
tests TreesController
|
76
|
-
|
77
|
-
def test_boolean_scope_is_called_when_boolean_param_is_true
|
78
|
-
Tree.expects(:only_tall).with().returns(Tree).in_sequence
|
79
|
-
Tree.expects(:all).returns([mock_tree]).in_sequence
|
80
|
-
get :index, :only_tall => 'true'
|
81
|
-
assert_equal([mock_tree], assigns(:@trees))
|
82
|
-
assert_equal({ :only_tall => true }, current_scopes)
|
83
|
-
end
|
84
|
-
|
85
|
-
def test_boolean_scope_is_not_called_when_boolean_param_is_false
|
86
|
-
Tree.expects(:only_tall).never
|
87
|
-
Tree.expects(:all).returns([mock_tree])
|
88
|
-
get :index, :only_tall => 'false'
|
89
|
-
assert_equal([mock_tree], assigns(:@trees))
|
90
|
-
assert_equal({ }, current_scopes)
|
91
|
-
end
|
92
|
-
|
93
|
-
def test_boolean_scope_with_allow_blank_is_called_when_boolean_param_is_true
|
94
|
-
Tree.expects(:conifer).with(true).returns(Tree).in_sequence
|
95
|
-
Tree.expects(:all).returns([mock_tree]).in_sequence
|
96
|
-
get :index, :conifer => 'true'
|
97
|
-
assert_equal([mock_tree], assigns(:@trees))
|
98
|
-
assert_equal({ :conifer => true }, current_scopes)
|
99
|
-
end
|
100
|
-
|
101
|
-
def test_boolean_scope_with_allow_blank_is_called_when_boolean_param_is_false
|
102
|
-
Tree.expects(:conifer).with(false).returns(Tree).in_sequence
|
103
|
-
Tree.expects(:all).returns([mock_tree]).in_sequence
|
104
|
-
get :index, :conifer => 'not_true'
|
105
|
-
assert_equal([mock_tree], assigns(:@trees))
|
106
|
-
assert_equal({ :conifer => false }, current_scopes)
|
107
|
-
end
|
108
|
-
|
109
|
-
def test_boolean_scope_with_allow_blank_is_not_called_when_boolean_param_is_not_present
|
110
|
-
Tree.expects(:conifer).never
|
111
|
-
Tree.expects(:all).returns([mock_tree])
|
112
|
-
get :index
|
113
|
-
assert_equal([mock_tree], assigns(:@trees))
|
114
|
-
assert_equal({ }, current_scopes)
|
115
|
-
end
|
116
|
-
|
117
|
-
def test_scope_is_called_only_on_index
|
118
|
-
Tree.expects(:only_tall).never
|
119
|
-
Tree.expects(:find).with('42').returns(mock_tree)
|
120
|
-
get :show, :only_tall => 'true', :id => '42'
|
121
|
-
assert_equal(mock_tree, assigns(:@tree))
|
122
|
-
assert_equal({ }, current_scopes)
|
123
|
-
end
|
124
|
-
|
125
|
-
def test_scope_is_skipped_when_if_option_is_false
|
126
|
-
@controller.stubs(:restrict_to_only_tall_trees?).returns(false)
|
127
|
-
Tree.expects(:only_tall).never
|
128
|
-
Tree.expects(:all).returns([mock_tree])
|
129
|
-
get :index, :only_tall => 'true'
|
130
|
-
assert_equal([mock_tree], assigns(:@trees))
|
131
|
-
assert_equal({ }, current_scopes)
|
132
|
-
end
|
133
|
-
|
134
|
-
def test_scope_is_skipped_when_unless_option_is_true
|
135
|
-
@controller.stubs(:show_all_colors?).returns(true)
|
136
|
-
Tree.expects(:color).never
|
137
|
-
Tree.expects(:all).returns([mock_tree])
|
138
|
-
get :index, :color => 'blue'
|
139
|
-
assert_equal([mock_tree], assigns(:@trees))
|
140
|
-
assert_equal({ }, current_scopes)
|
141
|
-
end
|
142
|
-
|
143
|
-
def test_scope_is_called_except_on_index
|
144
|
-
Tree.expects(:shadown_range).never
|
145
|
-
Tree.expects(:all).returns([mock_tree])
|
146
|
-
get :index, :shadown_range => 20
|
147
|
-
assert_equal([mock_tree], assigns(:@trees))
|
148
|
-
assert_equal({ }, current_scopes)
|
149
|
-
end
|
150
|
-
|
151
|
-
def test_scope_is_called_with_arguments
|
152
|
-
Tree.expects(:color).with('blue').returns(Tree).in_sequence
|
153
|
-
Tree.expects(:all).returns([mock_tree]).in_sequence
|
154
|
-
get :index, :color => 'blue'
|
155
|
-
assert_equal([mock_tree], assigns(:@trees))
|
156
|
-
assert_equal({ :color => 'blue' }, current_scopes)
|
157
|
-
end
|
158
|
-
|
159
|
-
def test_scope_is_not_called_if_blank
|
160
|
-
Tree.expects(:color).never
|
161
|
-
Tree.expects(:all).returns([mock_tree]).in_sequence
|
162
|
-
get :index, :color => ''
|
163
|
-
assert_equal([mock_tree], assigns(:@trees))
|
164
|
-
assert_equal({ }, current_scopes)
|
165
|
-
end
|
166
|
-
|
167
|
-
def test_scope_is_called_when_blank_if_allow_blank_is_given
|
168
|
-
Tree.expects(:root_type).with('').returns(Tree)
|
169
|
-
Tree.expects(:all).returns([mock_tree]).in_sequence
|
170
|
-
get :index, :root => ''
|
171
|
-
assert_equal([mock_tree], assigns(:@trees))
|
172
|
-
assert_equal({ :root => '' }, current_scopes)
|
173
|
-
end
|
174
|
-
|
175
|
-
def test_multiple_scopes_are_called
|
176
|
-
Tree.expects(:only_tall).with().returns(Tree)
|
177
|
-
Tree.expects(:color).with('blue').returns(Tree)
|
178
|
-
Tree.expects(:all).returns([mock_tree])
|
179
|
-
get :index, :color => 'blue', :only_tall => 'true'
|
180
|
-
assert_equal([mock_tree], assigns(:@trees))
|
181
|
-
assert_equal({ :color => 'blue', :only_tall => true }, current_scopes)
|
182
|
-
end
|
183
|
-
|
184
|
-
def test_scope_of_type_hash
|
185
|
-
hash = { "page" => "1", "per_page" => "10" }
|
186
|
-
Tree.expects(:paginate).with(hash).returns(Tree)
|
187
|
-
Tree.expects(:all).returns([mock_tree])
|
188
|
-
get :index, :paginate => hash
|
189
|
-
assert_equal([mock_tree], assigns(:@trees))
|
190
|
-
assert_equal({ paginate: hash }, current_scopes)
|
191
|
-
end
|
192
|
-
|
193
|
-
def test_scope_of_type_hash_with_using
|
194
|
-
hash = { "page" => "1", "per_page" => "10" }
|
195
|
-
Tree.expects(:args_paginate).with("1", "10").returns(Tree)
|
196
|
-
Tree.expects(:all).returns([mock_tree])
|
197
|
-
get :index, :args_paginate => hash
|
198
|
-
assert_equal([mock_tree], assigns(:@trees))
|
199
|
-
assert_equal({ :args_paginate => hash }, current_scopes)
|
200
|
-
end
|
201
|
-
|
202
|
-
def test_hash_with_blank_values_is_ignored
|
203
|
-
hash = { "page" => "", "per_page" => "" }
|
204
|
-
Tree.expects(:paginate).never
|
205
|
-
Tree.expects(:all).returns([mock_tree])
|
206
|
-
get :index, :paginate => hash
|
207
|
-
assert_equal([mock_tree], assigns(:@trees))
|
208
|
-
assert_equal({ }, current_scopes)
|
209
|
-
end
|
210
|
-
|
211
|
-
def test_nested_hash_with_blank_values_is_ignored
|
212
|
-
hash = { "parent" => {"children" => ""} }
|
213
|
-
Tree.expects(:paginate).never
|
214
|
-
Tree.expects(:all).returns([mock_tree])
|
215
|
-
get :index, :paginate => hash
|
216
|
-
assert_equal([mock_tree], assigns(:@trees))
|
217
|
-
assert_equal({ }, current_scopes)
|
218
|
-
end
|
219
|
-
|
220
|
-
def test_nested_blank_array_param_is_ignored
|
221
|
-
hash = { "parent" => [""] }
|
222
|
-
Tree.expects(:paginate).never
|
223
|
-
Tree.expects(:all).returns([mock_tree])
|
224
|
-
get :index, :paginate => hash
|
225
|
-
assert_equal([mock_tree], assigns(:@trees))
|
226
|
-
assert_equal({ }, current_scopes)
|
227
|
-
end
|
228
|
-
|
229
|
-
def test_scope_of_type_array
|
230
|
-
array = %w(book kitchen sport)
|
231
|
-
Tree.expects(:categories).with(array).returns(Tree)
|
232
|
-
Tree.expects(:all).returns([mock_tree])
|
233
|
-
get :index, :categories => array
|
234
|
-
assert_equal([mock_tree], assigns(:@trees))
|
235
|
-
assert_equal({ :categories => array }, current_scopes)
|
236
|
-
end
|
237
|
-
|
238
|
-
def test_array_of_blank_values_is_ignored
|
239
|
-
Tree.expects(:categories).never
|
240
|
-
Tree.expects(:all).returns([mock_tree])
|
241
|
-
get :index, :categories => [""]
|
242
|
-
assert_equal([mock_tree], assigns(:@trees))
|
243
|
-
assert_equal({ }, current_scopes)
|
244
|
-
end
|
245
|
-
|
246
|
-
def test_scope_of_invalid_type_silently_fails
|
247
|
-
Tree.expects(:all).returns([mock_tree])
|
248
|
-
get :index, :paginate => "1"
|
249
|
-
assert_equal([mock_tree], assigns(:@trees))
|
250
|
-
assert_equal({ }, current_scopes)
|
251
|
-
end
|
252
|
-
|
253
|
-
def test_scope_is_called_with_default_value
|
254
|
-
Tree.expects(:shadown_range).with(10).returns(Tree).in_sequence
|
255
|
-
Tree.expects(:find).with('42').returns(mock_tree).in_sequence
|
256
|
-
get :edit, :id => '42'
|
257
|
-
assert_equal(mock_tree, assigns(:@tree))
|
258
|
-
assert_equal({ :shadown_range => 10 }, current_scopes)
|
259
|
-
end
|
260
|
-
|
261
|
-
def test_default_scope_value_can_be_overwritten
|
262
|
-
Tree.expects(:shadown_range).with('20').returns(Tree).in_sequence
|
263
|
-
Tree.expects(:find).with('42').returns(mock_tree).in_sequence
|
264
|
-
get :edit, :id => '42', :shadown_range => '20'
|
265
|
-
assert_equal(mock_tree, assigns(:@tree))
|
266
|
-
assert_equal({ :shadown_range => '20' }, current_scopes)
|
267
|
-
end
|
268
|
-
|
269
|
-
def test_scope_with_different_key
|
270
|
-
Tree.expects(:root_type).with('outside').returns(Tree).in_sequence
|
271
|
-
Tree.expects(:find).with('42').returns(mock_tree).in_sequence
|
272
|
-
get :show, :id => '42', :root => 'outside'
|
273
|
-
assert_equal(mock_tree, assigns(:@tree))
|
274
|
-
assert_equal({ :root => 'outside' }, current_scopes)
|
275
|
-
end
|
276
|
-
|
277
|
-
def test_scope_with_default_value_as_a_proc_without_argument
|
278
|
-
Date.expects(:today).returns("today")
|
279
|
-
Tree.expects(:planted_before).with("today").returns(Tree)
|
280
|
-
Tree.expects(:all).returns([mock_tree])
|
281
|
-
get :index
|
282
|
-
assert_equal([mock_tree], assigns(:@trees))
|
283
|
-
assert_equal({ :planted_before => "today" }, current_scopes)
|
284
|
-
end
|
285
|
-
|
286
|
-
def test_scope_with_default_value_as_proc_with_argument
|
287
|
-
session[:height] = 100
|
288
|
-
Tree.expects(:calculate_height).with(100).returns(Tree).in_sequence
|
289
|
-
Tree.expects(:new).returns(mock_tree).in_sequence
|
290
|
-
get :new
|
291
|
-
assert_equal(mock_tree, assigns(:@tree))
|
292
|
-
assert_equal({ :calculate_height => 100 }, current_scopes)
|
293
|
-
end
|
294
|
-
|
295
|
-
def test_scope_with_custom_type
|
296
|
-
parsed = Date.civil(2014,11,11)
|
297
|
-
Tree.expects(:planted_after).with(parsed).returns(Tree)
|
298
|
-
Tree.expects(:all).returns([mock_tree])
|
299
|
-
get :index, :planted_after => "2014-11-11"
|
300
|
-
assert_equal([mock_tree], assigns(:@trees))
|
301
|
-
assert_equal({ :planted_after => parsed }, current_scopes)
|
302
|
-
end
|
303
|
-
|
304
|
-
def test_scope_with_boolean_block
|
305
|
-
Tree.expects(:only_really_short!).with(@controller.object_id).returns(Tree)
|
306
|
-
Tree.expects(:all).returns([mock_tree])
|
307
|
-
get :index, :only_short => 'true'
|
308
|
-
assert_equal([mock_tree], assigns(:@trees))
|
309
|
-
assert_equal({ :only_short => true }, current_scopes)
|
310
|
-
end
|
311
|
-
|
312
|
-
def test_scope_with_other_block_types
|
313
|
-
Tree.expects(:by_given_category).with(@controller.object_id, 'for_id').returns(Tree)
|
314
|
-
Tree.expects(:all).returns([mock_tree])
|
315
|
-
get :index, :by_category => 'for'
|
316
|
-
assert_equal([mock_tree], assigns(:@trees))
|
317
|
-
assert_equal({ :by_category => 'for' }, current_scopes)
|
318
|
-
end
|
319
|
-
|
320
|
-
def test_scope_with_nested_hash_and_in_option
|
321
|
-
hash = { 'title' => 'the-title', 'content' => 'the-content' }
|
322
|
-
Tree.expects(:title).with('the-title').returns(Tree)
|
323
|
-
Tree.expects(:content).with('the-content').returns(Tree)
|
324
|
-
Tree.expects(:all).returns([mock_tree])
|
325
|
-
get :index, q: hash
|
326
|
-
assert_equal([mock_tree], assigns(:@trees))
|
327
|
-
assert_equal({ q: hash }, current_scopes)
|
328
|
-
end
|
329
|
-
|
330
|
-
def test_overwritten_scope
|
331
|
-
assert_nil(TreesController.scopes_configuration[:categories][:if])
|
332
|
-
assert_equal(:categories?, BonsaisController.scopes_configuration[:categories][:if])
|
333
|
-
end
|
334
|
-
|
335
|
-
protected
|
336
|
-
|
337
|
-
if ActionPack::VERSION::MAJOR == 5
|
338
|
-
# TODO: Remove this when we only support Rails 5.
|
339
|
-
def get(action, params = {})
|
340
|
-
super action, params: params
|
341
|
-
end
|
342
|
-
end
|
343
|
-
|
344
|
-
def mock_tree(stubs={})
|
345
|
-
@mock_tree ||= mock(stubs)
|
346
|
-
end
|
347
|
-
|
348
|
-
def current_scopes
|
349
|
-
@controller.send :current_scopes
|
350
|
-
end
|
351
|
-
|
352
|
-
def assigns(ivar)
|
353
|
-
@controller.instance_variable_get(ivar)
|
354
|
-
end
|
355
|
-
end
|
356
|
-
|
357
|
-
class TreeHugger
|
358
|
-
include HasScope
|
359
|
-
|
360
|
-
has_scope :color
|
361
|
-
|
362
|
-
def by_color
|
363
|
-
apply_scopes(Tree, :color => 'blue')
|
364
|
-
end
|
365
|
-
|
366
|
-
end
|
367
|
-
|
368
|
-
class HasScopeOutsideControllerTest < ActiveSupport::TestCase
|
369
|
-
|
370
|
-
def test_has_scope_usable_outside_controller
|
371
|
-
Tree.expects(:color).with('blue')
|
372
|
-
TreeHugger.new.by_color
|
373
|
-
end
|
374
|
-
|
375
|
-
end
|
data/test/test_helper.rb
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
require 'bundler/setup'
|
2
|
-
|
3
|
-
require 'minitest/autorun'
|
4
|
-
require 'mocha'
|
5
|
-
require 'mocha/mini_test'
|
6
|
-
|
7
|
-
# Configure Rails
|
8
|
-
ENV['RAILS_ENV'] = 'test'
|
9
|
-
|
10
|
-
require 'active_support'
|
11
|
-
require 'active_support/core_ext/string/strip'
|
12
|
-
require 'action_controller'
|
13
|
-
require 'action_dispatch/middleware/flash'
|
14
|
-
|
15
|
-
$:.unshift File.expand_path('../../lib', __FILE__)
|
16
|
-
require 'has_scope'
|
17
|
-
|
18
|
-
HasScope::Routes = ActionDispatch::Routing::RouteSet.new
|
19
|
-
HasScope::Routes.draw do
|
20
|
-
get '/:controller(/:action(/:id))'
|
21
|
-
end
|
22
|
-
|
23
|
-
class ApplicationController < ActionController::Base
|
24
|
-
include HasScope::Routes.url_helpers
|
25
|
-
end
|
26
|
-
|
27
|
-
class ActiveSupport::TestCase
|
28
|
-
self.test_order = :random if respond_to?(:test_order=)
|
29
|
-
|
30
|
-
setup do
|
31
|
-
@routes = HasScope::Routes
|
32
|
-
end
|
33
|
-
end
|