chewy 0.6.2 → 0.7.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.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.travis.yml +35 -29
  4. data/Appraisals +37 -0
  5. data/CHANGELOG.md +115 -4
  6. data/Gemfile +2 -3
  7. data/README.md +135 -40
  8. data/chewy.gemspec +4 -3
  9. data/gemfiles/rails.3.2.activerecord.gemfile +13 -0
  10. data/gemfiles/rails.3.2.activerecord.kaminari.gemfile +14 -0
  11. data/gemfiles/rails.3.2.activerecord.will_paginate.gemfile +14 -0
  12. data/gemfiles/rails.4.0.activerecord.gemfile +13 -0
  13. data/gemfiles/rails.4.0.activerecord.kaminari.gemfile +14 -0
  14. data/gemfiles/rails.4.0.activerecord.will_paginate.gemfile +14 -0
  15. data/gemfiles/rails.4.0.mongoid.gemfile +13 -0
  16. data/gemfiles/rails.4.0.mongoid.kaminari.gemfile +14 -0
  17. data/gemfiles/rails.4.0.mongoid.will_paginate.gemfile +14 -0
  18. data/gemfiles/rails.4.1.activerecord.gemfile +13 -0
  19. data/gemfiles/rails.4.1.activerecord.kaminari.gemfile +14 -0
  20. data/gemfiles/rails.4.1.activerecord.will_paginate.gemfile +14 -0
  21. data/gemfiles/rails.4.1.mongoid.gemfile +13 -0
  22. data/gemfiles/rails.4.1.mongoid.kaminari.gemfile +14 -0
  23. data/gemfiles/rails.4.1.mongoid.will_paginate.gemfile +14 -0
  24. data/gemfiles/rails.4.2.activerecord.gemfile +13 -0
  25. data/gemfiles/rails.4.2.activerecord.kaminari.gemfile +14 -0
  26. data/gemfiles/rails.4.2.activerecord.will_paginate.gemfile +14 -0
  27. data/gemfiles/rails.4.2.mongoid.gemfile +13 -0
  28. data/gemfiles/rails.4.2.mongoid.kaminari.gemfile +14 -0
  29. data/gemfiles/rails.4.2.mongoid.will_paginate.gemfile +14 -0
  30. data/lib/chewy.rb +65 -0
  31. data/lib/chewy/config.rb +44 -93
  32. data/lib/chewy/errors.rb +14 -5
  33. data/lib/chewy/fields/base.rb +8 -7
  34. data/lib/chewy/fields/root.rb +2 -2
  35. data/lib/chewy/index.rb +7 -9
  36. data/lib/chewy/log_subscriber.rb +34 -0
  37. data/lib/chewy/query.rb +41 -27
  38. data/lib/chewy/query/criteria.rb +28 -23
  39. data/lib/chewy/query/scoping.rb +20 -0
  40. data/lib/chewy/railtie.rb +51 -13
  41. data/lib/chewy/repository.rb +61 -0
  42. data/lib/chewy/rspec/update_index.rb +3 -6
  43. data/lib/chewy/search.rb +28 -7
  44. data/lib/chewy/strategy.rb +60 -0
  45. data/lib/chewy/strategy/atomic.rb +31 -0
  46. data/lib/chewy/strategy/base.rb +27 -0
  47. data/lib/chewy/strategy/bypass.rb +15 -0
  48. data/lib/chewy/strategy/urgent.rb +17 -0
  49. data/lib/chewy/type.rb +19 -5
  50. data/lib/chewy/type/adapter/active_record.rb +28 -117
  51. data/lib/chewy/type/adapter/base.rb +35 -0
  52. data/lib/chewy/type/adapter/mongoid.rb +23 -123
  53. data/lib/chewy/type/adapter/object.rb +41 -19
  54. data/lib/chewy/type/adapter/orm.rb +142 -0
  55. data/lib/chewy/type/import.rb +43 -16
  56. data/lib/chewy/type/observe.rb +8 -21
  57. data/lib/chewy/version.rb +1 -1
  58. data/lib/tasks/chewy.rake +8 -4
  59. data/spec/chewy/config_spec.rb +20 -97
  60. data/spec/chewy/fields/base_spec.rb +24 -11
  61. data/spec/chewy/fields/time_fields_spec.rb +27 -0
  62. data/spec/chewy/index/settings_spec.rb +2 -1
  63. data/spec/chewy/index_spec.rb +98 -79
  64. data/spec/chewy/query/criteria_spec.rb +14 -0
  65. data/spec/chewy/query_spec.rb +1 -1
  66. data/spec/chewy/repository_spec.rb +50 -0
  67. data/spec/chewy/search_spec.rb +100 -0
  68. data/spec/chewy/strategy_spec.rb +109 -0
  69. data/spec/chewy/type/adapter/active_record_spec.rb +110 -46
  70. data/spec/chewy/type/adapter/mongoid_spec.rb +123 -74
  71. data/spec/chewy/type/adapter/object_spec.rb +51 -34
  72. data/spec/chewy/type/import_spec.rb +21 -21
  73. data/spec/chewy/type/observe_spec.rb +26 -29
  74. data/spec/chewy/type_spec.rb +19 -0
  75. data/spec/chewy_spec.rb +19 -3
  76. data/spec/spec_helper.rb +1 -1
  77. data/spec/support/active_record.rb +2 -1
  78. data/spec/support/mongoid.rb +29 -38
  79. metadata +85 -55
  80. data/gemfiles/Gemfile.rails-3.2.active_record +0 -6
  81. data/gemfiles/Gemfile.rails-3.2.active_record.kaminari +0 -7
  82. data/gemfiles/Gemfile.rails-3.2.active_record.will_paginate +0 -7
  83. data/gemfiles/Gemfile.rails-4.0.active_record +0 -6
  84. data/gemfiles/Gemfile.rails-4.0.active_record.kaminari +0 -7
  85. data/gemfiles/Gemfile.rails-4.0.active_record.will_paginate +0 -7
  86. data/gemfiles/Gemfile.rails-4.0.mongoid +0 -6
  87. data/gemfiles/Gemfile.rails-4.0.mongoid.kaminari +0 -7
  88. data/gemfiles/Gemfile.rails-4.0.mongoid.will_paginate +0 -7
  89. data/gemfiles/Gemfile.rails-4.1.active_record +0 -6
  90. data/gemfiles/Gemfile.rails-4.1.active_record.kaminari +0 -7
  91. data/gemfiles/Gemfile.rails-4.1.active_record.will_paginate +0 -7
  92. data/gemfiles/Gemfile.rails-4.1.mongoid +0 -6
  93. data/gemfiles/Gemfile.rails-4.1.mongoid.kaminari +0 -7
  94. data/gemfiles/Gemfile.rails-4.1.mongoid.will_paginate +0 -7
  95. data/gemfiles/Gemfile.rails-4.2.active_record +0 -6
  96. data/gemfiles/Gemfile.rails-4.2.active_record.kaminari +0 -7
  97. data/gemfiles/Gemfile.rails-4.2.active_record.will_paginate +0 -7
  98. data/gemfiles/Gemfile.rails-4.2.mongoid +0 -6
  99. data/gemfiles/Gemfile.rails-4.2.mongoid.kaminari +0 -7
  100. data/gemfiles/Gemfile.rails-4.2.mongoid.will_paginate +0 -7
  101. data/spec/chewy/index/search_spec.rb +0 -46
@@ -11,20 +11,29 @@ module Chewy
11
11
  class UnderivableType < Error
12
12
  end
13
13
 
14
+ class UndefinedUpdateStrategy < Error
15
+ def initialize type
16
+ super <<-MESSAGE
17
+ Index update strategy is undefined in current context.
18
+ Please wrap your code with `Chewy.strategy(:strategy_name) block.`
19
+ MESSAGE
20
+ end
21
+ end
22
+
14
23
  class DocumentNotFound < Error
15
24
  end
16
25
 
17
26
  class ImportFailed < Error
18
27
  def initialize type, errors
19
- output = "Import failed for `#{type}` with:\n"
28
+ message = "Import failed for `#{type}` with:\n"
20
29
  errors.each do |action, errors|
21
- output << " #{action.to_s.humanize} errors:\n"
30
+ message << " #{action.to_s.humanize} errors:\n"
22
31
  errors.each do |error, documents|
23
- output << " `#{error}`\n"
24
- output << " on #{documents.count} documents: #{documents}\n"
32
+ message << " `#{error}`\n"
33
+ message << " on #{documents.count} documents: #{documents}\n"
25
34
  end
26
35
  end
27
- super output
36
+ super message
28
37
  end
29
38
  end
30
39
  end
@@ -20,9 +20,10 @@ module Chewy
20
20
  false
21
21
  end
22
22
 
23
- def compose(object)
23
+ def compose(object, *parent_objects)
24
24
  result = if value && value.is_a?(Proc)
25
- value.arity == 0 ? object.instance_exec(&value) : value.call(object)
25
+ value.arity.zero? ? object.instance_exec(&value) :
26
+ value.call(object, *parent_objects.first(value.arity - 1))
26
27
  elsif object.is_a?(Hash)
27
28
  object[name] || object[name.to_s]
28
29
  else
@@ -30,12 +31,12 @@ module Chewy
30
31
  end
31
32
 
32
33
  result = if result.respond_to?(:to_ary)
33
- result.to_ary.map { |object| nested_compose(object) }
34
+ result.to_ary.map { |result| nested_compose(result, object, *parent_objects) }
34
35
  else
35
- nested_compose(result)
36
+ nested_compose(result, object, *parent_objects)
36
37
  end if nested.any? && !multi_field?
37
38
 
38
- {name => result.as_json}
39
+ {name => result.as_json(root: false)}
39
40
  end
40
41
 
41
42
  def nested(field = nil)
@@ -57,8 +58,8 @@ module Chewy
57
58
 
58
59
  private
59
60
 
60
- def nested_compose(value)
61
- nested.values.map { |field| field.compose(value) if value }.compact.inject(:merge)
61
+ def nested_compose(value, *parent_objects)
62
+ nested.values.map { |field| field.compose(value, *parent_objects) if value }.compact.inject(:merge)
62
63
  end
63
64
  end
64
65
  end
@@ -10,7 +10,7 @@ module Chewy
10
10
  @id = options.delete(:id) || options.delete(:_id)
11
11
  @parent = options.delete(:parent) || options.delete(:_parent)
12
12
  @parent_id = options.delete(:parent_id)
13
- options.reverse_merge!(value: ->(_){_})
13
+ options.reverse_merge!(value: ->(_) { _ })
14
14
  super(name, options)
15
15
  options.delete(:type)
16
16
  @dynamic_templates = []
@@ -66,7 +66,7 @@ module Chewy
66
66
  parent_id.arity == 0 ? object.instance_exec(&parent_id) : parent_id.call(object)
67
67
  end
68
68
  end
69
-
69
+
70
70
  def compose_id(object)
71
71
  if id
72
72
  id.arity == 0 ? object.instance_exec(&id) : id.call(object)
@@ -34,7 +34,7 @@ module Chewy
34
34
  else
35
35
  @index_name ||= begin
36
36
  build_index_name(
37
- name.gsub(/Index\Z/, '').demodulize.underscore,
37
+ name.sub(/Index\Z/, '').demodulize.underscore,
38
38
  prefix: Chewy.configuration[:prefix]
39
39
  ) if name
40
40
  end
@@ -137,6 +137,12 @@ module Chewy
137
137
  self._settings = Chewy::Index::Settings.new params
138
138
  end
139
139
 
140
+ # Returns list of public class methods defined in current index
141
+ #
142
+ def self.scopes
143
+ public_methods - Chewy::Index.public_methods - type_names.map(&:to_sym)
144
+ end
145
+
140
146
  private
141
147
 
142
148
  def self.build_index_name *args
@@ -156,13 +162,5 @@ module Chewy
156
162
  def self.index_params
157
163
  [settings_hash, mappings_hash].inject(:merge)
158
164
  end
159
-
160
- def self.search_index
161
- self
162
- end
163
-
164
- def self.search_type
165
- type_names
166
- end
167
165
  end
168
166
  end
@@ -0,0 +1,34 @@
1
+ module Chewy
2
+ class LogSubscriber < ActiveSupport::LogSubscriber
3
+ def logger
4
+ Chewy.logger
5
+ end
6
+
7
+ def import_objects(event)
8
+ render_action('Import', event) { |payload| payload[:import] }
9
+ end
10
+
11
+ def search_query(event)
12
+ render_action('Search', event) { |payload| payload[:request] }
13
+ end
14
+
15
+ def delete_query(event)
16
+ render_action('Delete by Query', event) { |payload| payload[:request] }
17
+ end
18
+
19
+ def render_action action, event, &block
20
+ payload = event.payload
21
+ description = block.call(payload)
22
+
23
+ if description.present?
24
+ subject = payload[:type].presence || payload[:index]
25
+ action = "#{subject} #{action} (#{event.duration.round(1)}ms)"
26
+ action = color(action, GREEN, true)
27
+
28
+ debug(" #{action} #{description}")
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ Chewy::LogSubscriber.attach_to :chewy
@@ -1,5 +1,6 @@
1
1
  require 'chewy/query/criteria'
2
2
  require 'chewy/query/filters'
3
+ require 'chewy/query/scoping'
3
4
  require 'chewy/query/loading'
4
5
  require 'chewy/query/pagination'
5
6
 
@@ -13,6 +14,7 @@ module Chewy
13
14
  #
14
15
  class Query
15
16
  include Enumerable
17
+ include Scoping
16
18
  include Loading
17
19
  include Pagination
18
20
 
@@ -29,13 +31,14 @@ module Chewy
29
31
  delegate :each, :count, :size, to: :_collection
30
32
  alias_method :to_ary, :to_a
31
33
 
32
- attr_reader :index, :options, :criteria
34
+ attr_reader :_indexes, :_types, :options, :criteria
33
35
 
34
- def initialize index, options = {}
35
- @index, @options = index, options
36
- @types = Array.wrap(options.delete(:types))
36
+ def initialize *indexes_or_types_and_options
37
+ @options = indexes_or_types_and_options.extract_options!
38
+ @_types = indexes_or_types_and_options.select { |klass| klass < Chewy::Type }
39
+ @_indexes = indexes_or_types_and_options.select { |klass| klass < Chewy::Index }
40
+ @_indexes |= @_types.map(&:index)
37
41
  @criteria = Criteria.new
38
- reset
39
42
  end
40
43
 
41
44
  # Comparation with other query or collection
@@ -347,21 +350,21 @@ module Chewy
347
350
  # added to the search request and combinded according to
348
351
  # <tt>boost_mode</tt> and <tt>score_mode</tt>
349
352
  #
350
- # UsersIndex.script_score("doc['boost'].value", filter: { term: {foo: :bar} })
353
+ # UsersIndex.script_score("doc['boost'].value", params: { modifier: 2 })
351
354
  # # => {body:
352
355
  # query: {
353
356
  # function_score: {
354
357
  # query: { ...},
355
358
  # functions: [{
356
359
  # script_score: {
357
- # script: "doc['boost'].value"
358
- # },
359
- # filter: { term: { foo: :bar } }
360
+ # script: "doc['boost'].value * modifier",
361
+ # params: { modifier: 2 }
362
+ # }
360
363
  # }
361
364
  # }]
362
365
  # } } }
363
366
  def script_score(script, options = {})
364
- scoring = options.merge(script_score: { script: script })
367
+ scoring = { script_score: { script: script }.merge(options) }
365
368
  chain { criteria.update_scores scoring }
366
369
  end
367
370
 
@@ -800,11 +803,7 @@ module Chewy
800
803
  # }}}}
801
804
  #
802
805
  def types *params
803
- if params.any?
804
- chain { criteria.update_types params }
805
- else
806
- @types
807
- end
806
+ chain { criteria.update_types params }
808
807
  end
809
808
 
810
809
  # Acts the same way as <tt>types</tt>, but cleans up previously set types
@@ -841,8 +840,11 @@ module Chewy
841
840
  #
842
841
  def delete_all
843
842
  request = chain { criteria.update_options simple: true }.send(:_request)
844
- ActiveSupport::Notifications.instrument 'delete_query.chewy', request: request, index: index do
845
- index.client.delete_by_query(request)
843
+ ActiveSupport::Notifications.instrument 'delete_query.chewy',
844
+ request: request, indexes: _indexes, types: _types,
845
+ index: _indexes.one? ? _indexes.first : _indexes,
846
+ type: _types.one? ? _types.first : _types do
847
+ Chewy.client.delete_by_query(request)
846
848
  end
847
849
  end
848
850
 
@@ -899,7 +901,7 @@ module Chewy
899
901
  private
900
902
 
901
903
  def chain &block
902
- clone.tap { |q| q.instance_eval(&block) }
904
+ clone.tap { |q| q.instance_exec(&block) }
903
905
  end
904
906
 
905
907
  def reset
@@ -907,17 +909,20 @@ module Chewy
907
909
  end
908
910
 
909
911
  def _request
910
- @_request ||= criteria.request_body.merge(index: index.index_name, type: types)
912
+ @_request ||= criteria.request_body.merge(index: _indexes.map(&:index_name), type: _types.map(&:type_name))
911
913
  end
912
914
 
913
915
  def _response
914
- @_response ||= ActiveSupport::Notifications.instrument 'search_query.chewy', request: _request, index: index do
915
- begin
916
- index.client.search(_request)
917
- rescue Elasticsearch::Transport::Transport::Errors::NotFound => e
918
- raise e if e.message !~ /IndexMissingException/
919
- {}
920
- end
916
+ @_response ||= ActiveSupport::Notifications.instrument 'search_query.chewy',
917
+ request: _request, indexes: _indexes, types: _types,
918
+ index: _indexes.one? ? _indexes.first : _indexes,
919
+ type: _types.one? ? _types.first : _types do
920
+ begin
921
+ Chewy.client.search(_request)
922
+ rescue Elasticsearch::Transport::Transport::Errors::NotFound => e
923
+ raise e if e.message !~ /IndexMissingException/
924
+ {}
925
+ end
921
926
  end
922
927
  end
923
928
 
@@ -928,7 +933,7 @@ module Chewy
928
933
  .merge!(_score: hit['_score'])
929
934
  .merge!(_explanation: hit['_explanation'])
930
935
 
931
- wrapper = index.type_hash[hit['_type']].new attributes
936
+ wrapper = _derive_index(hit['_index']).type_hash[hit['_type']].new attributes
932
937
  wrapper._data = hit
933
938
  wrapper
934
939
  end
@@ -941,5 +946,14 @@ module Chewy
941
946
  _results.map(&:_object) : _results
942
947
  end
943
948
  end
949
+
950
+ def _derive_index index_name
951
+ (@derive_index ||= {})[index_name] ||= _indexes_hash[index_name] ||
952
+ _indexes_hash[_indexes_hash.keys.sort_by(&:length).reverse.detect { |name| index_name.starts_with?(name) }]
953
+ end
954
+
955
+ def _indexes_hash
956
+ @_indexes_hash ||= _indexes.index_by(&:index_name)
957
+ end
944
958
  end
945
959
  end
@@ -5,14 +5,14 @@ module Chewy
5
5
  class Criteria
6
6
  include Compose
7
7
  ARRAY_STORAGES = [:queries, :filters, :post_filters, :sort, :fields, :types, :scores]
8
- HASH_STORAGES = [:options, :request_options, :facets, :aggregations, :suggest]
8
+ HASH_STORAGES = [:options, :request_options, :facets, :aggregations, :suggest, :script_fields]
9
9
  STORAGES = ARRAY_STORAGES + HASH_STORAGES
10
10
 
11
11
  def initialize options = {}
12
12
  @options = options.merge(
13
13
  query_mode: Chewy.query_mode,
14
14
  filter_mode: Chewy.filter_mode,
15
- post_filter_mode: Chewy.filter_mode || Chewy.post_filter_mode
15
+ post_filter_mode: Chewy.post_filter_mode || Chewy.filter_mode
16
16
  )
17
17
  end
18
18
 
@@ -40,52 +40,56 @@ module Chewy
40
40
  !!options[:none]
41
41
  end
42
42
 
43
- def update_options(modifer)
44
- options.merge!(modifer)
43
+ def update_options(modifier)
44
+ options.merge!(modifier)
45
45
  end
46
46
 
47
- def update_request_options(modifer)
48
- request_options.merge!(modifer)
47
+ def update_request_options(modifier)
48
+ request_options.merge!(modifier)
49
49
  end
50
50
 
51
- def update_facets(modifer)
52
- facets.merge!(modifer)
51
+ def update_facets(modifier)
52
+ facets.merge!(modifier)
53
53
  end
54
54
 
55
- def update_scores(modifer)
56
- @scores = scores + Array.wrap(modifer).reject(&:blank?)
55
+ def update_scores(modifier)
56
+ @scores = scores + Array.wrap(modifier).reject(&:blank?)
57
57
  end
58
58
 
59
- def update_aggregations(modifer)
60
- aggregations.merge!(modifer)
59
+ def update_aggregations(modifier)
60
+ aggregations.merge!(modifier)
61
61
  end
62
62
 
63
63
  def update_suggest(modifier)
64
64
  suggest.merge!(modifier)
65
65
  end
66
66
 
67
+ def update_script_fields(modifier)
68
+ script_fields.merge!(modifier)
69
+ end
70
+
67
71
  [:filters, :queries, :post_filters].each do |storage|
68
72
  class_eval <<-RUBY
69
- def update_#{storage}(modifer)
70
- @#{storage} = #{storage} + Array.wrap(modifer).reject(&:blank?)
73
+ def update_#{storage}(modifier)
74
+ @#{storage} = #{storage} + Array.wrap(modifier).reject(&:blank?)
71
75
  end
72
76
  RUBY
73
77
  end
74
78
 
75
- def update_sort(modifer, options = {})
79
+ def update_sort(modifier, options = {})
76
80
  @sort = nil if options[:purge]
77
- modifer = Array.wrap(modifer).flatten.map do |element|
81
+ modifier = Array.wrap(modifier).flatten.map do |element|
78
82
  element.is_a?(Hash) ? element.map { |k, v| {k => v} } : element
79
83
  end.flatten
80
- @sort = sort + modifer
84
+ @sort = sort + modifier
81
85
  end
82
86
 
83
87
  %w(fields types).each do |storage|
84
- define_method "update_#{storage}" do |modifer, options = {}|
88
+ define_method "update_#{storage}" do |modifier, options = {}|
85
89
  variable = "@#{storage}"
86
90
  instance_variable_set(variable, nil) if options[:purge]
87
- modifer = send(storage) | Array.wrap(modifer).flatten.map(&:to_s).reject(&:blank?)
88
- instance_variable_set(variable, modifer)
91
+ modifier = send(storage) | Array.wrap(modifier).flatten.map(&:to_s).reject(&:blank?)
92
+ instance_variable_set(variable, modifier)
89
93
  end
90
94
  end
91
95
 
@@ -112,6 +116,7 @@ module Chewy
112
116
  body.merge!(suggest: suggest) if suggest?
113
117
  body.merge!(sort: sort) if sort?
114
118
  body.merge!(_source: fields) if fields?
119
+ body.merge!(script_fields: script_fields) if script_fields?
115
120
 
116
121
  body = _boost_query(body)
117
122
 
@@ -133,14 +138,14 @@ module Chewy
133
138
  end
134
139
 
135
140
  def _boost_query(body)
136
- scores? or return body
141
+ return body unless scores?
137
142
  query = body.delete :query
138
143
  filter = body.delete :filter
139
144
  if query && filter
140
145
  query = { filtered: { query: query, filter: filter } }
141
146
  filter = nil
142
147
  end
143
- score = { }
148
+ score = {}
144
149
  score[:functions] = scores
145
150
  score[:boost_mode] = options[:boost_mode] if options[:boost_mode]
146
151
  score[:score_mode] = options[:score_mode] if options[:score_mode]
@@ -165,7 +170,7 @@ module Chewy
165
170
  end
166
171
 
167
172
  def _request_types
168
- _filters_join(types.map { |type| {type: {value: type}} }, :or)
173
+ _filters_join(types.map { |type| { type: { value: type } } }, :or)
169
174
  end
170
175
 
171
176
  def _request_post_filter
@@ -0,0 +1,20 @@
1
+ module Chewy
2
+ class Query
3
+ module Scoping
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def scopes
8
+ Thread.current[:chewy_scopes] ||= []
9
+ end
10
+ end
11
+
12
+ def scoping
13
+ self.class.scopes.push(self)
14
+ yield
15
+ ensure
16
+ self.class.scopes.pop
17
+ end
18
+ end
19
+ end
20
+ end