has_scope 0.6.0.rc → 0.8.0
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 +5 -5
- data/MIT-LICENSE +1 -1
- data/README.md +89 -20
- data/lib/has_scope.rb +69 -41
- data/lib/has_scope/version.rb +1 -1
- data/test/has_scope_test.rb +309 -100
- data/test/test_helper.rb +8 -10
- metadata +48 -40
- data/.gitignore +0 -2
- data/.travis.yml +0 -14
- data/Gemfile +0 -8
- data/Gemfile.lock +0 -48
- data/Rakefile +0 -26
- data/gemfiles/Gemfile-rails.3.2.x +0 -8
- data/has_scope.gemspec +0 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c24a5ed875375fb5da93be3f90ac863b0c72d676ecc1fe0959933924452b043d
|
4
|
+
data.tar.gz: 2f8aacdef384ce75c7fc2e4b4b697ba62e68293deeac5754597d10dffc044052
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9541480b36218fb3c1b9b7c48a3297411c8875ec47bb1735b6b394127c02ad90010d79787931c0d0154d662b40f4c236627710ca7b2fa01edb7513f27e043fb9
|
7
|
+
data.tar.gz: 5927fbd54e4a019b2d9f9fd7f7a60cf929fd46eac8f15c4c0e8cdb0cab2cac80a2b432e4398a70013d6bf88a519df60d2b6f571fb4f7f5db2d141292762b9eba
|
data/MIT-LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright 2009-
|
1
|
+
Copyright 2009-2017 Plataforma Tecnologia. http://blog.plataformatec.com.br
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
a copy of this software and associated documentation files (the
|
data/README.md
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
## HasScope
|
2
2
|
|
3
|
-
[](https://codeclimate.com/github/plataformatec/has_scope)
|
3
|
+
[](http://badge.fury.io/rb/has_scope)
|
4
|
+
[](https://codeclimate.com/github/heartcombo/has_scope)
|
6
5
|
|
7
|
-
Has scope allows you to
|
6
|
+
Has scope allows you to map incoming controller parameters to named scopes in your resources.
|
8
7
|
Imagine the following model called graduations:
|
9
8
|
|
10
9
|
```ruby
|
11
10
|
class Graduation < ActiveRecord::Base
|
12
|
-
scope :featured, -> { where(:
|
13
|
-
scope :by_degree, -> degree { where(:
|
11
|
+
scope :featured, -> { where(featured: true) }
|
12
|
+
scope :by_degree, -> degree { where(degree: degree) }
|
13
|
+
scope :by_period, -> started_at, ended_at { where("started_at = ? AND ended_at = ?", started_at, ended_at) }
|
14
14
|
end
|
15
15
|
```
|
16
16
|
|
@@ -18,7 +18,7 @@ You can use those named scopes as filters by declaring them on your controller:
|
|
18
18
|
|
19
19
|
```ruby
|
20
20
|
class GraduationsController < ApplicationController
|
21
|
-
has_scope :featured, :
|
21
|
+
has_scope :featured, type: :boolean
|
22
22
|
has_scope :by_degree
|
23
23
|
end
|
24
24
|
```
|
@@ -27,9 +27,9 @@ Now, if you want to apply them to an specific resource, you just need to call `a
|
|
27
27
|
|
28
28
|
```ruby
|
29
29
|
class GraduationsController < ApplicationController
|
30
|
-
has_scope :featured, :
|
30
|
+
has_scope :featured, type: :boolean
|
31
31
|
has_scope :by_degree
|
32
|
-
has_scope :by_period, :
|
32
|
+
has_scope :by_period, using: %i[started_at ended_at], type: :hash
|
33
33
|
|
34
34
|
def index
|
35
35
|
@graduations = apply_scopes(Graduation).all
|
@@ -46,7 +46,7 @@ Then for each request:
|
|
46
46
|
/graduations?featured=true
|
47
47
|
#=> calls the named scope and bring featured graduations
|
48
48
|
|
49
|
-
/graduations?
|
49
|
+
/graduations?by_period[started_at]=20100701&by_period[ended_at]=20101013
|
50
50
|
#=> brings graduations in the given period
|
51
51
|
|
52
52
|
/graduations?featured=true&by_degree=phd
|
@@ -54,7 +54,7 @@ Then for each request:
|
|
54
54
|
```
|
55
55
|
|
56
56
|
You can retrieve all the scopes applied in one action with `current_scopes` method.
|
57
|
-
In the last case, it would return: { :
|
57
|
+
In the last case, it would return: `{ featured: true, by_degree: 'phd' }`.
|
58
58
|
|
59
59
|
## Installation
|
60
60
|
|
@@ -68,7 +68,11 @@ gem 'has_scope'
|
|
68
68
|
|
69
69
|
HasScope supports several options:
|
70
70
|
|
71
|
-
* `:type` - Checks the type of the parameter sent.
|
71
|
+
* `:type` - Checks the type of the parameter sent.
|
72
|
+
By default, it does not allow hashes or arrays to be given,
|
73
|
+
except if type `:hash` or `:array` are set.
|
74
|
+
Symbols are never permitted to prevent memory leaks, so ensure any routing
|
75
|
+
constraints you have that add parameters use string values.
|
72
76
|
|
73
77
|
* `:only` - In which actions the scope is applied.
|
74
78
|
|
@@ -78,14 +82,38 @@ HasScope supports several options:
|
|
78
82
|
|
79
83
|
* `:using` - The subkeys to be used as args when type is a hash.
|
80
84
|
|
81
|
-
* `:
|
85
|
+
* `:in` - A shortcut for combining the `:using` option with nested hashes.
|
82
86
|
|
83
|
-
* `:
|
87
|
+
* `: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.
|
88
|
+
|
89
|
+
* `: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.
|
84
90
|
|
85
91
|
* `:default` - Default value for the scope. Whenever supplied the scope is always called.
|
86
92
|
|
87
93
|
* `:allow_blank` - Blank values are not sent to scopes by default. Set to true to overwrite.
|
88
94
|
|
95
|
+
## Boolean usage
|
96
|
+
|
97
|
+
If `type: :boolean` is set it just calls the named scope, without any arguments, when parameter
|
98
|
+
is set to a "true" value. `'true'` and `'1'` are parsed as `true`, everything else as `false`.
|
99
|
+
|
100
|
+
When boolean scope is set up with `allow_blank: true`, it will call the scope with the value as
|
101
|
+
any usual scope.
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
has_scope :visible, type: :boolean
|
105
|
+
has_scope :active, type: :boolean, allow_blank: true
|
106
|
+
|
107
|
+
# and models with
|
108
|
+
scope :visible, -> { where(visible: true) }
|
109
|
+
scope :active, ->(value = true) { where(active: value) }
|
110
|
+
```
|
111
|
+
|
112
|
+
_Note_: it is not possible to apply a boolean scope with just the query param being present, e.g.
|
113
|
+
`?active`, that's not considered a "true" value (the param value will be `nil`), and thus the
|
114
|
+
scope will be called with `false` as argument. In order for the scope to receive a `true` argument
|
115
|
+
the param value must be set to one of the "true" values above, e.g. `?active=true` or `?active=1`.
|
116
|
+
|
89
117
|
## Block usage
|
90
118
|
|
91
119
|
`has_scope` also accepts a block. The controller, current scope and value are yielded
|
@@ -94,22 +122,63 @@ need to manipulate the given value:
|
|
94
122
|
|
95
123
|
```ruby
|
96
124
|
has_scope :category do |controller, scope, value|
|
97
|
-
|
125
|
+
value != 'all' ? scope.by_category(value) : scope
|
98
126
|
end
|
99
127
|
```
|
100
128
|
|
101
|
-
When used with booleans
|
129
|
+
When used with booleans without `:allow_blank`, it just receives two arguments
|
130
|
+
and is just invoked if true is given:
|
102
131
|
|
103
132
|
```ruby
|
104
|
-
has_scope :not_voted_by_me, :
|
133
|
+
has_scope :not_voted_by_me, type: :boolean do |controller, scope|
|
105
134
|
scope.not_voted_by(controller.current_user.id)
|
106
135
|
end
|
107
136
|
```
|
108
137
|
|
138
|
+
## Keyword arguments
|
139
|
+
|
140
|
+
Scopes with keyword arguments need to be called in a block:
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
# in the model
|
144
|
+
scope :for_course, lambda { |course_id:| where(course_id: course_id) }
|
145
|
+
|
146
|
+
# in the controller
|
147
|
+
has_scope :for_course do |controller, scope, value|
|
148
|
+
scope.for_course(course_id: value)
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
## Apply scope on every request
|
153
|
+
|
154
|
+
To apply scope on every request set default value and `allow_blank: true`:
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
has_scope :available, default: nil, allow_blank: true, only: :show, unless: :admin?
|
158
|
+
|
159
|
+
# model:
|
160
|
+
scope :available, ->(*) { where(blocked: false) }
|
161
|
+
```
|
162
|
+
|
163
|
+
This will allow usual users to get only available items, but admins will
|
164
|
+
be able to access blocked items too.
|
165
|
+
|
166
|
+
## Check which scopes have been applied
|
167
|
+
|
168
|
+
To check which scopes have been applied, you can call `current_scopes` from the controller or view.
|
169
|
+
This returns a hash with the scope name as the key and the scope value as the value.
|
170
|
+
|
171
|
+
For example, if a boolean `:active` scope has been applied, `current_scopes` will return `{ active: true }`.
|
172
|
+
|
173
|
+
## Supported Ruby / Rails versions
|
174
|
+
|
175
|
+
We intend to maintain support for all Ruby / Rails versions that haven't reached end-of-life.
|
176
|
+
|
177
|
+
For more information about specific versions please check [Ruby](https://www.ruby-lang.org/en/downloads/branches/)
|
178
|
+
and [Rails](https://guides.rubyonrails.org/maintenance_policy.html) maintenance policies, and our test matrix.
|
179
|
+
|
109
180
|
## Bugs and Feedback
|
110
181
|
|
111
182
|
If you discover any bugs or want to drop a line, feel free to create an issue on GitHub.
|
112
183
|
|
113
|
-
http://
|
114
|
-
|
115
|
-
MIT License. Copyright 2009-2013 Plataformatec. http://blog.plataformatec.com.br
|
184
|
+
MIT License. Copyright 2009-2019 Plataformatec. http://blog.plataformatec.com.br
|
data/lib/has_scope.rb
CHANGED
@@ -1,17 +1,21 @@
|
|
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
|
18
|
+
self.scopes_configuration = {}
|
15
19
|
end
|
16
20
|
end
|
17
21
|
|
@@ -35,6 +39,8 @@ module HasScope
|
|
35
39
|
# * <tt>:using</tt> - If type is a hash, you can provide :using to convert the hash to
|
36
40
|
# a named scope call with several arguments.
|
37
41
|
#
|
42
|
+
# * <tt>:in</tt> - A shortcut for combining the `:using` option with nested hashes.
|
43
|
+
#
|
38
44
|
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
39
45
|
# if the scope should apply
|
40
46
|
#
|
@@ -56,14 +62,23 @@ module HasScope
|
|
56
62
|
# value != "all" ? scope.by_category(value) : scope
|
57
63
|
# end
|
58
64
|
#
|
59
|
-
# has_scope :not_voted_by_me, :
|
65
|
+
# has_scope :not_voted_by_me, type: :boolean do |controller, scope|
|
60
66
|
# scope.not_voted_by(controller.current_user.id)
|
61
67
|
# end
|
62
68
|
#
|
63
69
|
def has_scope(*scopes, &block)
|
64
70
|
options = scopes.extract_options!
|
65
71
|
options.symbolize_keys!
|
66
|
-
options.assert_valid_keys(:type, :only, :except, :if, :unless, :default, :as, :using, :allow_blank)
|
72
|
+
options.assert_valid_keys(:type, :only, :except, :if, :unless, :default, :as, :using, :allow_blank, :in)
|
73
|
+
|
74
|
+
if options.key?(:in)
|
75
|
+
options[:as] = options[:in]
|
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
|
81
|
+
end
|
67
82
|
|
68
83
|
if options.key?(:using)
|
69
84
|
if options.key?(:type) && options[:type] != :hash
|
@@ -78,11 +93,11 @@ module HasScope
|
|
78
93
|
options[:only] = Array(options[:only])
|
79
94
|
options[:except] = Array(options[:except])
|
80
95
|
|
81
|
-
self.scopes_configuration =
|
96
|
+
self.scopes_configuration = scopes_configuration.dup
|
82
97
|
|
83
98
|
scopes.each do |scope|
|
84
|
-
|
85
|
-
|
99
|
+
scopes_configuration[scope] ||= { as: scope, type: :default, block: block }
|
100
|
+
scopes_configuration[scope] = self.scopes_configuration[scope].merge(options)
|
86
101
|
end
|
87
102
|
end
|
88
103
|
end
|
@@ -91,19 +106,17 @@ module HasScope
|
|
91
106
|
|
92
107
|
# Receives an object where scopes will be applied to.
|
93
108
|
#
|
94
|
-
# class GraduationsController <
|
95
|
-
# has_scope :featured, :
|
96
|
-
# has_scope :by_degree, :
|
109
|
+
# class GraduationsController < ApplicationController
|
110
|
+
# has_scope :featured, type: true, only: :index
|
111
|
+
# has_scope :by_degree, only: :index
|
97
112
|
#
|
98
113
|
# def index
|
99
114
|
# @graduations = apply_scopes(Graduation).all
|
100
115
|
# end
|
101
116
|
# end
|
102
117
|
#
|
103
|
-
def apply_scopes(target, hash=params)
|
104
|
-
|
105
|
-
|
106
|
-
self.scopes_configuration.each do |scope, options|
|
118
|
+
def apply_scopes(target, hash = params)
|
119
|
+
scopes_configuration.each do |scope, options|
|
107
120
|
next unless apply_scope_to_action?(options)
|
108
121
|
key = options[:as]
|
109
122
|
|
@@ -111,15 +124,25 @@ module HasScope
|
|
111
124
|
value, call_scope = hash[key], true
|
112
125
|
elsif options.key?(:default)
|
113
126
|
value, call_scope = options[:default], true
|
114
|
-
|
127
|
+
if value.is_a?(Proc)
|
128
|
+
value = value.arity == 0 ? value.call : value.call(self)
|
129
|
+
end
|
115
130
|
end
|
116
131
|
|
117
|
-
value = parse_value(options[:type],
|
132
|
+
value = parse_value(options[:type], value)
|
118
133
|
value = normalize_blanks(value)
|
119
134
|
|
120
|
-
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
|
121
144
|
current_scopes[key] = value
|
122
|
-
target = call_scope_by_type(options[:type], scope, target,
|
145
|
+
target = call_scope_by_type(options[:type], scope, target, scope_value, options)
|
123
146
|
end
|
124
147
|
end
|
125
148
|
|
@@ -127,21 +150,22 @@ module HasScope
|
|
127
150
|
end
|
128
151
|
|
129
152
|
# Set the real value for the current scope if type check.
|
130
|
-
def parse_value(type,
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
value
|
153
|
+
def parse_value(type, value) #:nodoc:
|
154
|
+
klasses, parser = ALLOWED_TYPES[type]
|
155
|
+
if klasses.any? { |klass| value.is_a?(klass) }
|
156
|
+
parser ? parser.call(value) : value
|
135
157
|
end
|
136
158
|
end
|
137
159
|
|
138
160
|
# Screens pseudo-blank params.
|
139
161
|
def normalize_blanks(value) #:nodoc:
|
140
|
-
|
141
|
-
|
162
|
+
case value
|
163
|
+
when Array
|
142
164
|
value.select { |v| v.present? }
|
143
|
-
|
165
|
+
when Hash
|
144
166
|
value.select { |k, v| normalize_blanks(v).present? }.with_indifferent_access
|
167
|
+
when ActionController::Parameters
|
168
|
+
normalize_blanks(value.to_unsafe_h)
|
145
169
|
else
|
146
170
|
value
|
147
171
|
end
|
@@ -151,10 +175,9 @@ module HasScope
|
|
151
175
|
def call_scope_by_type(type, scope, target, value, options) #:nodoc:
|
152
176
|
block = options[:block]
|
153
177
|
|
154
|
-
if type == :boolean
|
178
|
+
if type == :boolean && !options[:allow_blank]
|
155
179
|
block ? block.call(self, target) : target.send(scope)
|
156
|
-
elsif
|
157
|
-
value = value.values_at(*options[:using])
|
180
|
+
elsif options.key?(:using)
|
158
181
|
block ? block.call(self, target, value) : target.send(scope, *value)
|
159
182
|
else
|
160
183
|
block ? block.call(self, target, value) : target.send(scope, value)
|
@@ -177,14 +200,19 @@ module HasScope
|
|
177
200
|
# method, or string evals to the expected value.
|
178
201
|
def applicable?(string_proc_or_symbol, expected) #:nodoc:
|
179
202
|
case string_proc_or_symbol
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
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
|
+
|
209
|
+
eval(string_proc_or_symbol) == expected
|
210
|
+
when Proc
|
211
|
+
string_proc_or_symbol.call(self) == expected
|
212
|
+
when Symbol
|
213
|
+
send(string_proc_or_symbol) == expected
|
214
|
+
else
|
215
|
+
true
|
188
216
|
end
|
189
217
|
end
|
190
218
|
|
@@ -196,5 +224,5 @@ end
|
|
196
224
|
|
197
225
|
ActiveSupport.on_load :action_controller do
|
198
226
|
include HasScope
|
199
|
-
helper_method :current_scopes
|
227
|
+
helper_method :current_scopes if respond_to?(:helper_method)
|
200
228
|
end
|
data/lib/has_scope/version.rb
CHANGED
data/test/has_scope_test.rb
CHANGED
@@ -1,19 +1,34 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
HasScope::ALLOWED_TYPES[:date] = [[String], -> v { Date.parse(v) rescue nil }]
|
4
|
+
|
5
|
+
class Tree; end
|
5
6
|
|
6
7
|
class TreesController < ApplicationController
|
7
|
-
has_scope :color, :
|
8
|
-
has_scope :only_tall, :
|
9
|
-
has_scope :shadown_range, :
|
10
|
-
has_scope :root_type, :
|
11
|
-
has_scope :
|
12
|
-
has_scope :
|
13
|
-
has_scope :
|
14
|
-
has_scope :
|
15
|
-
|
16
|
-
has_scope :
|
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 :paginate_blank, type: :hash, allow_blank: true
|
17
|
+
has_scope :paginate_default, type: :hash, default: { page: 1, per_page: 10 }, only: :edit
|
18
|
+
has_scope :args_paginate, type: :hash, using: [:page, :per_page]
|
19
|
+
has_scope :args_paginate_blank, using: [:page, :per_page], allow_blank: true
|
20
|
+
has_scope :args_paginate_default, using: [:page, :per_page], default: { page: 1, per_page: 10 }, only: :edit
|
21
|
+
has_scope :categories, type: :array
|
22
|
+
has_scope :title, in: :q
|
23
|
+
has_scope :content, in: :q
|
24
|
+
has_scope :metadata, in: :q
|
25
|
+
has_scope :metadata_blank, in: :q, allow_blank: true
|
26
|
+
has_scope :metadata_default, in: :q, default: "default", only: :edit
|
27
|
+
has_scope :conifer, type: :boolean, allow_blank: true
|
28
|
+
has_scope :eval_plant, if: "params[:eval_plant].present?", unless: "params[:skip_eval_plant].present?"
|
29
|
+
has_scope :proc_plant, if: -> c { c.params[:proc_plant].present? }, unless: -> c { c.params[:skip_proc_plant].present? }
|
30
|
+
|
31
|
+
has_scope :only_short, type: :boolean do |controller, scope|
|
17
32
|
scope.only_really_short!(controller.object_id)
|
18
33
|
end
|
19
34
|
|
@@ -32,9 +47,20 @@ class TreesController < ApplicationController
|
|
32
47
|
def show
|
33
48
|
@tree = apply_scopes(Tree).find(params[:id])
|
34
49
|
end
|
50
|
+
|
35
51
|
alias :edit :show
|
36
52
|
|
37
53
|
protected
|
54
|
+
# Silence deprecations in the test suite, except for the actual deprecated String if/unless options.
|
55
|
+
# TODO: remove with the deprecation.
|
56
|
+
def apply_scopes(*)
|
57
|
+
if params[:eval_plant]
|
58
|
+
super
|
59
|
+
else
|
60
|
+
ActiveSupport::Deprecation.silence { super }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
38
64
|
def restrict_to_only_tall_trees?
|
39
65
|
true
|
40
66
|
end
|
@@ -44,12 +70,12 @@ class TreesController < ApplicationController
|
|
44
70
|
end
|
45
71
|
|
46
72
|
def default_render
|
47
|
-
render :
|
73
|
+
render body: action_name
|
48
74
|
end
|
49
75
|
end
|
50
76
|
|
51
77
|
class BonsaisController < TreesController
|
52
|
-
has_scope :categories, :
|
78
|
+
has_scope :categories, if: :categories?
|
53
79
|
|
54
80
|
protected
|
55
81
|
def categories?
|
@@ -63,24 +89,60 @@ class HasScopeTest < ActionController::TestCase
|
|
63
89
|
def test_boolean_scope_is_called_when_boolean_param_is_true
|
64
90
|
Tree.expects(:only_tall).with().returns(Tree).in_sequence
|
65
91
|
Tree.expects(:all).returns([mock_tree]).in_sequence
|
66
|
-
|
67
|
-
|
68
|
-
|
92
|
+
|
93
|
+
get :index, params: { only_tall: 'true' }
|
94
|
+
|
95
|
+
assert_equal([mock_tree], assigns(:@trees))
|
96
|
+
assert_equal({ only_tall: true }, current_scopes)
|
69
97
|
end
|
70
98
|
|
71
99
|
def test_boolean_scope_is_not_called_when_boolean_param_is_false
|
72
100
|
Tree.expects(:only_tall).never
|
73
101
|
Tree.expects(:all).returns([mock_tree])
|
74
|
-
|
75
|
-
|
76
|
-
|
102
|
+
|
103
|
+
get :index, params: { only_tall: 'false' }
|
104
|
+
|
105
|
+
assert_equal([mock_tree], assigns(:@trees))
|
106
|
+
assert_equal({ }, current_scopes)
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_boolean_scope_with_allow_blank_is_called_when_boolean_param_is_true
|
110
|
+
Tree.expects(:conifer).with(true).returns(Tree).in_sequence
|
111
|
+
Tree.expects(:all).returns([mock_tree]).in_sequence
|
112
|
+
|
113
|
+
get :index, params: { conifer: 'true' }
|
114
|
+
|
115
|
+
assert_equal([mock_tree], assigns(:@trees))
|
116
|
+
assert_equal({ conifer: true }, current_scopes)
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_boolean_scope_with_allow_blank_is_called_when_boolean_param_is_false
|
120
|
+
Tree.expects(:conifer).with(false).returns(Tree).in_sequence
|
121
|
+
Tree.expects(:all).returns([mock_tree]).in_sequence
|
122
|
+
|
123
|
+
get :index, params: { conifer: 'not_true' }
|
124
|
+
|
125
|
+
assert_equal([mock_tree], assigns(:@trees))
|
126
|
+
assert_equal({ conifer: false }, current_scopes)
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_boolean_scope_with_allow_blank_is_not_called_when_boolean_param_is_not_present
|
130
|
+
Tree.expects(:conifer).never
|
131
|
+
Tree.expects(:all).returns([mock_tree])
|
132
|
+
|
133
|
+
get :index
|
134
|
+
|
135
|
+
assert_equal([mock_tree], assigns(:@trees))
|
136
|
+
assert_equal({ }, current_scopes)
|
77
137
|
end
|
78
138
|
|
79
139
|
def test_scope_is_called_only_on_index
|
80
140
|
Tree.expects(:only_tall).never
|
81
141
|
Tree.expects(:find).with('42').returns(mock_tree)
|
82
|
-
|
83
|
-
|
142
|
+
|
143
|
+
get :show, params: { only_tall: 'true', id: '42' }
|
144
|
+
|
145
|
+
assert_equal(mock_tree, assigns(:@tree))
|
84
146
|
assert_equal({ }, current_scopes)
|
85
147
|
end
|
86
148
|
|
@@ -88,8 +150,10 @@ class HasScopeTest < ActionController::TestCase
|
|
88
150
|
@controller.stubs(:restrict_to_only_tall_trees?).returns(false)
|
89
151
|
Tree.expects(:only_tall).never
|
90
152
|
Tree.expects(:all).returns([mock_tree])
|
91
|
-
|
92
|
-
|
153
|
+
|
154
|
+
get :index, params: { only_tall: 'true' }
|
155
|
+
|
156
|
+
assert_equal([mock_tree], assigns(:@trees))
|
93
157
|
assert_equal({ }, current_scopes)
|
94
158
|
end
|
95
159
|
|
@@ -97,85 +161,149 @@ class HasScopeTest < ActionController::TestCase
|
|
97
161
|
@controller.stubs(:show_all_colors?).returns(true)
|
98
162
|
Tree.expects(:color).never
|
99
163
|
Tree.expects(:all).returns([mock_tree])
|
100
|
-
|
101
|
-
|
164
|
+
|
165
|
+
get :index, params: { color: 'blue' }
|
166
|
+
|
167
|
+
assert_equal([mock_tree], assigns(:@trees))
|
102
168
|
assert_equal({ }, current_scopes)
|
103
169
|
end
|
104
170
|
|
171
|
+
def test_scope_with_eval_string_if_and_unless_options_is_deprecated
|
172
|
+
Tree.expects(:eval_plant).with('value').returns(Tree)
|
173
|
+
Tree.expects(:all).returns([mock_tree])
|
174
|
+
|
175
|
+
assert_deprecated(/Passing a string to determine if the scope should be applied is deprecated/) do
|
176
|
+
get :index, params: { eval_plant: 'value', skip_eval_plant: nil }
|
177
|
+
end
|
178
|
+
|
179
|
+
assert_equal([mock_tree], assigns(:@trees))
|
180
|
+
assert_equal({ eval_plant: 'value' }, current_scopes)
|
181
|
+
end
|
182
|
+
|
183
|
+
def test_scope_with_proc_if_and_unless_options
|
184
|
+
Tree.expects(:proc_plant).with('value').returns(Tree)
|
185
|
+
Tree.expects(:all).returns([mock_tree])
|
186
|
+
|
187
|
+
get :index, params: { proc_plant: 'value', skip_proc_plant: nil }
|
188
|
+
|
189
|
+
assert_equal([mock_tree], assigns(:@trees))
|
190
|
+
assert_equal({ proc_plant: 'value' }, current_scopes)
|
191
|
+
end
|
192
|
+
|
105
193
|
def test_scope_is_called_except_on_index
|
106
|
-
Tree.expects(:shadown_range).
|
194
|
+
Tree.expects(:shadown_range).never
|
107
195
|
Tree.expects(:all).returns([mock_tree])
|
108
|
-
|
109
|
-
|
196
|
+
|
197
|
+
get :index, params: { shadown_range: 20 }
|
198
|
+
|
199
|
+
assert_equal([mock_tree], assigns(:@trees))
|
110
200
|
assert_equal({ }, current_scopes)
|
111
201
|
end
|
112
202
|
|
113
203
|
def test_scope_is_called_with_arguments
|
114
204
|
Tree.expects(:color).with('blue').returns(Tree).in_sequence
|
115
205
|
Tree.expects(:all).returns([mock_tree]).in_sequence
|
116
|
-
|
117
|
-
|
118
|
-
|
206
|
+
|
207
|
+
get :index, params: { color: 'blue' }
|
208
|
+
|
209
|
+
assert_equal([mock_tree], assigns(:@trees))
|
210
|
+
assert_equal({ color: 'blue' }, current_scopes)
|
119
211
|
end
|
120
212
|
|
121
213
|
def test_scope_is_not_called_if_blank
|
122
214
|
Tree.expects(:color).never
|
123
215
|
Tree.expects(:all).returns([mock_tree]).in_sequence
|
124
|
-
|
125
|
-
|
216
|
+
|
217
|
+
get :index, params: { color: '' }
|
218
|
+
|
219
|
+
assert_equal([mock_tree], assigns(:@trees))
|
126
220
|
assert_equal({ }, current_scopes)
|
127
221
|
end
|
128
222
|
|
129
223
|
def test_scope_is_called_when_blank_if_allow_blank_is_given
|
130
224
|
Tree.expects(:root_type).with('').returns(Tree)
|
131
225
|
Tree.expects(:all).returns([mock_tree]).in_sequence
|
132
|
-
|
133
|
-
|
134
|
-
|
226
|
+
|
227
|
+
get :index, params: { root: '' }
|
228
|
+
|
229
|
+
assert_equal([mock_tree], assigns(:@trees))
|
230
|
+
assert_equal({ root: '' }, current_scopes)
|
135
231
|
end
|
136
232
|
|
137
233
|
def test_multiple_scopes_are_called
|
138
234
|
Tree.expects(:only_tall).with().returns(Tree)
|
139
235
|
Tree.expects(:color).with('blue').returns(Tree)
|
140
236
|
Tree.expects(:all).returns([mock_tree])
|
141
|
-
|
142
|
-
|
143
|
-
|
237
|
+
|
238
|
+
get :index, params: { color: 'blue', only_tall: 'true' }
|
239
|
+
|
240
|
+
assert_equal([mock_tree], assigns(:@trees))
|
241
|
+
assert_equal({ color: 'blue', only_tall: true }, current_scopes)
|
144
242
|
end
|
145
243
|
|
146
244
|
def test_scope_of_type_hash
|
147
245
|
hash = { "page" => "1", "per_page" => "10" }
|
148
246
|
Tree.expects(:paginate).with(hash).returns(Tree)
|
149
247
|
Tree.expects(:all).returns([mock_tree])
|
150
|
-
|
151
|
-
|
152
|
-
|
248
|
+
|
249
|
+
get :index, params: { paginate: hash }
|
250
|
+
|
251
|
+
assert_equal([mock_tree], assigns(:@trees))
|
252
|
+
assert_equal({ paginate: hash }, current_scopes)
|
153
253
|
end
|
154
254
|
|
155
255
|
def test_scope_of_type_hash_with_using
|
156
256
|
hash = { "page" => "1", "per_page" => "10" }
|
157
257
|
Tree.expects(:args_paginate).with("1", "10").returns(Tree)
|
158
258
|
Tree.expects(:all).returns([mock_tree])
|
159
|
-
|
160
|
-
|
161
|
-
|
259
|
+
|
260
|
+
get :index, params: { args_paginate: hash }
|
261
|
+
|
262
|
+
assert_equal([mock_tree], assigns(:@trees))
|
263
|
+
assert_equal({ args_paginate: hash }, current_scopes)
|
162
264
|
end
|
163
265
|
|
164
266
|
def test_hash_with_blank_values_is_ignored
|
165
267
|
hash = { "page" => "", "per_page" => "" }
|
166
268
|
Tree.expects(:paginate).never
|
167
269
|
Tree.expects(:all).returns([mock_tree])
|
168
|
-
|
169
|
-
|
270
|
+
|
271
|
+
get :index, params: { paginate: hash }
|
272
|
+
|
273
|
+
assert_equal([mock_tree], assigns(:@trees))
|
170
274
|
assert_equal({ }, current_scopes)
|
171
275
|
end
|
172
276
|
|
277
|
+
def test_hash_with_blank_values_and_allow_blank_is_called
|
278
|
+
hash = { "page" => "", "per_page" => "" }
|
279
|
+
Tree.expects(:paginate_blank).with({}).returns(Tree)
|
280
|
+
Tree.expects(:all).returns([mock_tree])
|
281
|
+
|
282
|
+
get :index, params: { paginate_blank: hash }
|
283
|
+
|
284
|
+
assert_equal([mock_tree], assigns(:@trees))
|
285
|
+
assert_equal({ paginate_blank: {} }, current_scopes)
|
286
|
+
end
|
287
|
+
|
288
|
+
def test_hash_with_using_and_blank_values_and_allow_blank_is_called
|
289
|
+
hash = { "page" => "", "per_page" => "" }
|
290
|
+
Tree.expects(:args_paginate_blank).with(nil, nil).returns(Tree)
|
291
|
+
Tree.expects(:all).returns([mock_tree])
|
292
|
+
|
293
|
+
get :index, params: { args_paginate_blank: hash }
|
294
|
+
|
295
|
+
assert_equal([mock_tree], assigns(:@trees))
|
296
|
+
assert_equal({ args_paginate_blank: {} }, current_scopes)
|
297
|
+
end
|
298
|
+
|
173
299
|
def test_nested_hash_with_blank_values_is_ignored
|
174
|
-
hash = { "parent" => {"children" => ""} }
|
300
|
+
hash = { "parent" => { "children" => "" } }
|
175
301
|
Tree.expects(:paginate).never
|
176
302
|
Tree.expects(:all).returns([mock_tree])
|
177
|
-
|
178
|
-
|
303
|
+
|
304
|
+
get :index, params: { paginate: hash }
|
305
|
+
|
306
|
+
assert_equal([mock_tree], assigns(:@trees))
|
179
307
|
assert_equal({ }, current_scopes)
|
180
308
|
end
|
181
309
|
|
@@ -183,8 +311,10 @@ class HasScopeTest < ActionController::TestCase
|
|
183
311
|
hash = { "parent" => [""] }
|
184
312
|
Tree.expects(:paginate).never
|
185
313
|
Tree.expects(:all).returns([mock_tree])
|
186
|
-
|
187
|
-
|
314
|
+
|
315
|
+
get :index, params: { paginate: hash }
|
316
|
+
|
317
|
+
assert_equal([mock_tree], assigns(:@trees))
|
188
318
|
assert_equal({ }, current_scopes)
|
189
319
|
end
|
190
320
|
|
@@ -192,89 +322,170 @@ class HasScopeTest < ActionController::TestCase
|
|
192
322
|
array = %w(book kitchen sport)
|
193
323
|
Tree.expects(:categories).with(array).returns(Tree)
|
194
324
|
Tree.expects(:all).returns([mock_tree])
|
195
|
-
|
196
|
-
|
197
|
-
|
325
|
+
|
326
|
+
get :index, params: { categories: array }
|
327
|
+
|
328
|
+
assert_equal([mock_tree], assigns(:@trees))
|
329
|
+
assert_equal({ categories: array }, current_scopes)
|
198
330
|
end
|
199
331
|
|
200
332
|
def test_array_of_blank_values_is_ignored
|
201
333
|
Tree.expects(:categories).never
|
202
334
|
Tree.expects(:all).returns([mock_tree])
|
203
|
-
|
204
|
-
|
335
|
+
|
336
|
+
get :index, params: { categories: [""] }
|
337
|
+
|
338
|
+
assert_equal([mock_tree], assigns(:@trees))
|
205
339
|
assert_equal({ }, current_scopes)
|
206
340
|
end
|
207
341
|
|
208
342
|
def test_scope_of_invalid_type_silently_fails
|
209
343
|
Tree.expects(:all).returns([mock_tree])
|
210
|
-
|
211
|
-
|
212
|
-
|
344
|
+
|
345
|
+
get :index, params: { paginate: "1" }
|
346
|
+
|
347
|
+
assert_equal([mock_tree], assigns(:@trees))
|
348
|
+
assert_equal({ }, current_scopes)
|
213
349
|
end
|
214
350
|
|
215
351
|
def test_scope_is_called_with_default_value
|
216
352
|
Tree.expects(:shadown_range).with(10).returns(Tree).in_sequence
|
353
|
+
Tree.expects(:paginate_default).with('page' => 1, 'per_page' => 10).returns(Tree).in_sequence
|
354
|
+
Tree.expects(:args_paginate_default).with(1, 10).returns(Tree).in_sequence
|
355
|
+
Tree.expects(:metadata_default).with('default').returns(Tree).in_sequence
|
217
356
|
Tree.expects(:find).with('42').returns(mock_tree).in_sequence
|
218
|
-
|
219
|
-
|
220
|
-
|
357
|
+
|
358
|
+
get :edit, params: { id: '42' }
|
359
|
+
|
360
|
+
assert_equal(mock_tree, assigns(:@tree))
|
361
|
+
assert_equal({
|
362
|
+
shadown_range: 10,
|
363
|
+
paginate_default: { 'page' => 1, 'per_page' => 10 },
|
364
|
+
args_paginate_default: { 'page' => 1, 'per_page' => 10 },
|
365
|
+
q: { 'metadata_default' => 'default' }
|
366
|
+
}, current_scopes)
|
221
367
|
end
|
222
368
|
|
223
369
|
def test_default_scope_value_can_be_overwritten
|
224
370
|
Tree.expects(:shadown_range).with('20').returns(Tree).in_sequence
|
371
|
+
Tree.expects(:paginate_default).with('page' => '2', 'per_page' => '20').returns(Tree).in_sequence
|
372
|
+
Tree.expects(:args_paginate_default).with('3', '15').returns(Tree).in_sequence
|
373
|
+
Tree.expects(:metadata_blank).with(nil).returns(Tree).in_sequence
|
374
|
+
Tree.expects(:metadata_default).with('other').returns(Tree).in_sequence
|
225
375
|
Tree.expects(:find).with('42').returns(mock_tree).in_sequence
|
226
|
-
|
227
|
-
|
228
|
-
|
376
|
+
|
377
|
+
get :edit, params: {
|
378
|
+
id: '42',
|
379
|
+
shadown_range: '20',
|
380
|
+
paginate_default: { page: 2, per_page: 20 },
|
381
|
+
args_paginate_default: { page: 3, per_page: 15},
|
382
|
+
q: { metadata_default: 'other' }
|
383
|
+
}
|
384
|
+
|
385
|
+
assert_equal(mock_tree, assigns(:@tree))
|
386
|
+
assert_equal({
|
387
|
+
shadown_range: '20',
|
388
|
+
paginate_default: { 'page' => '2', 'per_page' => '20' },
|
389
|
+
args_paginate_default: { 'page' => '3', 'per_page' => '15' },
|
390
|
+
q: { 'metadata_default' => 'other' }
|
391
|
+
}, current_scopes)
|
229
392
|
end
|
230
393
|
|
231
394
|
def test_scope_with_different_key
|
232
395
|
Tree.expects(:root_type).with('outside').returns(Tree).in_sequence
|
233
396
|
Tree.expects(:find).with('42').returns(mock_tree).in_sequence
|
234
|
-
|
235
|
-
|
236
|
-
|
397
|
+
|
398
|
+
get :show, params: { id: '42', root: 'outside' }
|
399
|
+
|
400
|
+
assert_equal(mock_tree, assigns(:@tree))
|
401
|
+
assert_equal({ root: 'outside' }, current_scopes)
|
237
402
|
end
|
238
403
|
|
239
|
-
def
|
404
|
+
def test_scope_with_default_value_as_a_proc_without_argument
|
405
|
+
Date.expects(:today).returns("today")
|
406
|
+
Tree.expects(:planted_before).with("today").returns(Tree)
|
407
|
+
Tree.expects(:all).returns([mock_tree])
|
408
|
+
|
409
|
+
get :index
|
410
|
+
|
411
|
+
assert_equal([mock_tree], assigns(:@trees))
|
412
|
+
assert_equal({ planted_before: "today" }, current_scopes)
|
413
|
+
end
|
414
|
+
|
415
|
+
def test_scope_with_default_value_as_proc_with_argument
|
240
416
|
session[:height] = 100
|
241
417
|
Tree.expects(:calculate_height).with(100).returns(Tree).in_sequence
|
242
418
|
Tree.expects(:new).returns(mock_tree).in_sequence
|
419
|
+
|
243
420
|
get :new
|
244
|
-
|
245
|
-
assert_equal(
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
421
|
+
|
422
|
+
assert_equal(mock_tree, assigns(:@tree))
|
423
|
+
assert_equal({ calculate_height: 100 }, current_scopes)
|
424
|
+
end
|
425
|
+
|
426
|
+
def test_scope_with_custom_type
|
427
|
+
parsed = Date.civil(2014,11,11)
|
428
|
+
Tree.expects(:planted_after).with(parsed).returns(Tree)
|
429
|
+
Tree.expects(:all).returns([mock_tree])
|
430
|
+
|
431
|
+
get :index, params: { planted_after: "2014-11-11" }
|
432
|
+
|
433
|
+
assert_equal([mock_tree], assigns(:@trees))
|
434
|
+
assert_equal({ planted_after: parsed }, current_scopes)
|
435
|
+
end
|
436
|
+
|
437
|
+
def test_scope_with_boolean_block
|
438
|
+
Tree.expects(:only_really_short!).with(@controller.object_id).returns(Tree)
|
439
|
+
Tree.expects(:all).returns([mock_tree])
|
440
|
+
|
441
|
+
get :index, params: { only_short: 'true' }
|
442
|
+
|
443
|
+
assert_equal([mock_tree], assigns(:@trees))
|
444
|
+
assert_equal({ only_short: true }, current_scopes)
|
445
|
+
end
|
446
|
+
|
447
|
+
def test_scope_with_other_block_types
|
448
|
+
Tree.expects(:by_given_category).with(@controller.object_id, 'for_id').returns(Tree)
|
449
|
+
Tree.expects(:all).returns([mock_tree])
|
450
|
+
|
451
|
+
get :index, params: { by_category: 'for' }
|
452
|
+
|
453
|
+
assert_equal([mock_tree], assigns(:@trees))
|
454
|
+
assert_equal({ by_category: 'for' }, current_scopes)
|
455
|
+
end
|
456
|
+
|
457
|
+
def test_scope_with_nested_hash_and_in_option
|
458
|
+
hash = { 'title' => 'the-title', 'content' => 'the-content' }
|
459
|
+
Tree.expects(:title).with('the-title').returns(Tree)
|
460
|
+
Tree.expects(:content).with('the-content').returns(Tree)
|
461
|
+
Tree.expects(:metadata).never
|
462
|
+
Tree.expects(:metadata_blank).with(nil).returns(Tree)
|
463
|
+
Tree.expects(:all).returns([mock_tree])
|
464
|
+
|
465
|
+
get :index, params: { q: hash }
|
466
|
+
|
467
|
+
assert_equal([mock_tree], assigns(:@trees))
|
468
|
+
assert_equal({ q: hash }, current_scopes)
|
469
|
+
end
|
470
|
+
|
471
|
+
def test_overwritten_scope
|
472
|
+
assert_nil(TreesController.scopes_configuration[:categories][:if])
|
473
|
+
assert_equal(:categories?, BonsaisController.scopes_configuration[:categories][:if])
|
474
|
+
end
|
268
475
|
|
269
476
|
protected
|
270
477
|
|
271
|
-
def mock_tree(stubs={})
|
478
|
+
def mock_tree(stubs = {})
|
272
479
|
@mock_tree ||= mock(stubs)
|
273
480
|
end
|
274
481
|
|
275
482
|
def current_scopes
|
276
483
|
@controller.send :current_scopes
|
277
484
|
end
|
485
|
+
|
486
|
+
def assigns(ivar)
|
487
|
+
@controller.instance_variable_get(ivar)
|
488
|
+
end
|
278
489
|
end
|
279
490
|
|
280
491
|
class TreeHugger
|
@@ -283,16 +494,14 @@ class TreeHugger
|
|
283
494
|
has_scope :color
|
284
495
|
|
285
496
|
def by_color
|
286
|
-
apply_scopes(Tree, :
|
497
|
+
apply_scopes(Tree, color: 'blue')
|
287
498
|
end
|
288
|
-
|
289
499
|
end
|
290
500
|
|
291
501
|
class HasScopeOutsideControllerTest < ActiveSupport::TestCase
|
292
|
-
|
293
502
|
def test_has_scope_usable_outside_controller
|
294
503
|
Tree.expects(:color).with('blue')
|
504
|
+
|
295
505
|
TreeHugger.new.by_color
|
296
506
|
end
|
297
|
-
|
298
507
|
end
|