has_scope 0.6.0.rc → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Gem Version](https://fury-badge.herokuapp.com/rb/has_scope.
|
4
|
-
[![
|
5
|
-
[![Code Climate](https://codeclimate.com/github/plataformatec/has_scope.png)](https://codeclimate.com/github/plataformatec/has_scope)
|
3
|
+
[![Gem Version](https://fury-badge.herokuapp.com/rb/has_scope.svg)](http://badge.fury.io/rb/has_scope)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/heartcombo/has_scope.svg)](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
|