gum 0.1.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b429a022f13c73d2ce6631b8a2d2a316fb957480
4
- data.tar.gz: e847b27a7c0b6ebe38f39a6c3db87f964ff33c1a
3
+ metadata.gz: e525c89a73abcb7ad8ea62b1526809239ee85262
4
+ data.tar.gz: b8518e7f610131e0de9738cb78dc8761788d7fc6
5
5
  SHA512:
6
- metadata.gz: 8183f45450f9fd2469dc55bae5a35b305e529d667e53775813788dcd48816f3f83d8a70ec00da791ca3833fa285ef5068d83c484069a35a50dc1b69d363da31e
7
- data.tar.gz: a2a03eb5d49e4b85923f1e2a61eda74c224d1c1cfa5ec2ed4550846aedfc636a8a311fe17d347705aa3ae1509cf9e2b129926dac700c166a57312887626ad564
6
+ metadata.gz: 1875400f0fb7eafd0f5aabc1952cc4df34dd26cd7f138b887c6fdc68e3266fd91f392f986d56711f3bf771a3b740d746de575375cc36ff27774ea2272d119ddb
7
+ data.tar.gz: 952bf47456985f1e8845f08b8def61ca72515186590fee02f2e302b011d74a4401287f07519efe839ba889a3392f6a92bc3f613ce10f50c61312b3f8a52bf426
@@ -7,8 +7,8 @@ require 'gum'
7
7
  # with your gem easier. You can also use a different console, if you like.
8
8
 
9
9
  # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
10
+ require 'pry'
11
+ Pry.start
12
12
 
13
- require 'irb'
14
- IRB.start
13
+ # require 'irb'
14
+ # IRB.start
@@ -32,6 +32,8 @@ Gem::Specification.new do |spec|
32
32
  spec.add_development_dependency 'bundler', '~> 1.13'
33
33
  spec.add_development_dependency 'rake', '~> 10.0'
34
34
  spec.add_development_dependency 'rspec', '~> 3.0'
35
- spec.add_dependency 'activesupport', '>= 3.2'
35
+ spec.add_development_dependency 'pry-byebug', '~> 3.4'
36
+ spec.add_development_dependency 'simplecov', '~> 0'
37
+ spec.add_dependency 'activesupport', '>=3.2', '<5.1'
36
38
  spec.add_dependency 'chewy', '~> 0.8.4'
37
39
  end
data/lib/gum.rb CHANGED
@@ -3,8 +3,10 @@ require 'gum/version'
3
3
  require 'active_support/core_ext/class/attribute'
4
4
  require 'active_support/core_ext/object/blank'
5
5
  require 'active_support/core_ext/hash/compact'
6
+ require 'active_support/core_ext/object/try'
7
+ require 'active_support/inflector'
6
8
 
7
- require 'gum/coerce'
9
+ require 'gum/filter'
8
10
  require 'gum/filters'
9
11
  require 'gum/search'
10
12
 
@@ -0,0 +1,33 @@
1
+ module Gum
2
+ class Factory
3
+ def self.build(type, args)
4
+ mapping = args_to_mapping(args)
5
+ options = mapping.delete(:options)
6
+ mapping.each do |param, field|
7
+ yield new(type, param, field, options)
8
+ end
9
+ end
10
+
11
+ def self.args_to_mapping(args)
12
+ args.inject({}) do |mapping, arg|
13
+ mapping.update normalize_mapping(arg)
14
+ end
15
+ end
16
+
17
+ def self.normalize_mapping(mapping)
18
+ case mapping
19
+ when Hash
20
+ mapping
21
+ when Array
22
+ mapping.zip(mapping).to_h
23
+ else
24
+ { mapping => mapping }
25
+ end
26
+ end
27
+
28
+ def self.new(type, param, field, options)
29
+ filter = type.is_a?(Class) ? type : Filters.const_get(type.to_s.camelize, false)
30
+ filter.new param, field, options
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,28 @@
1
+ module Gum
2
+ class Filter
3
+ attr_reader :param, :field, :options
4
+
5
+ def initialize(param, field, options)
6
+ @param = param
7
+ @field = field
8
+ @options = options || {}
9
+ end
10
+
11
+ def render(params)
12
+ return if empty?(params)
13
+ __render__ value_from(params)
14
+ end
15
+
16
+ def value_from(params)
17
+ params[param].presence
18
+ end
19
+
20
+ def empty?(params)
21
+ params[param].blank?
22
+ end
23
+
24
+ def __render__(value)
25
+ raise NoMethodError
26
+ end
27
+ end
28
+ end
@@ -1,91 +1,35 @@
1
+ require 'gum/factory'
2
+ require 'gum/filter'
3
+ require 'gum/filters/term'
4
+ require 'gum/filters/terms'
5
+ require 'gum/filters/exists'
6
+ require 'gum/filters/prefix'
7
+ require 'gum/filters/wildcard'
8
+ require 'gum/filters/regexp'
9
+ require 'gum/filters/fuzzy'
10
+ require 'gum/filters/range'
11
+ require 'gum/filters/geo'
12
+ require 'gum/filters/geo/bbox'
13
+ require 'gum/filters/geo/distance'
14
+ require 'gum/filters/geo/range'
15
+ require 'gum/order'
16
+
1
17
  module Gum
2
18
  module Filters
3
- module_function
4
-
5
- def range(attr, flags = {})
6
- lambda do |params|
7
- attr_from = params["#{attr}_from"]
8
- attr_to = params["#{attr}_to"]
9
- return if attr_from.blank? && attr_to.blank?
10
- Coerce.range(attr, params) do |value|
11
- { range: { attr => value.update(flags) } }
12
- end
13
- end
14
- end
15
-
16
- def term(attr, flags = {})
17
- lambda do |params|
18
- Coerce.term(attr, params) do |value|
19
- if flags[:boost]
20
- { term: { attr => { value: value, boost: flags[:boost] } } }
21
- else
22
- { term: { attr => value }.update(flags) }
23
- end
24
- end
25
- end
26
- end
27
-
28
- def terms(attr, _flags = {})
29
- lambda do |params|
30
- Coerce.terms(attr, params) do |value|
31
- { terms: { attr => value } }
32
- end
33
- end
34
- end
35
-
36
- def exists(attr, _flags = {})
37
- lambda do |params|
38
- Coerce.exists(attr, params) do |value|
39
- if value
40
- { exists: { field: attr } }
41
- else
42
- { bool: { must_not: { exists: { field: attr } } } }
43
- end
19
+ def self.register(method, klass = nil)
20
+ define_method method do |*args|
21
+ Factory.build(klass || method, args) do |filter|
22
+ filters.push filter
44
23
  end
45
24
  end
46
25
  end
47
26
 
48
- def prefix(attr, flags = {})
49
- lambda do |params|
50
- Coerce.term(attr, params) do |value|
51
- { prefix: { attr => value }.update(flags) }
52
- end
53
- end
54
- end
55
-
56
- def wildcard(attr, flags = {})
57
- lambda do |params|
58
- Coerce.term(attr, params) do |value|
59
- { wildcard: { attr => value }.update(flags) }
60
- end
61
- end
62
- end
63
-
64
- def regexp(attr, flags = {})
65
- lambda do |params|
66
- Coerce.term(attr, params) do |value|
67
- { regexp: flags.update(attr => value) }
68
- end
69
- end
70
- end
71
-
72
- def fuzzy(attr, separator: '_', **flags)
73
- lambda do |params|
74
- Coerce.split(attr, params, separator: separator, default: flags[:fuzziness]) do |value, fuzziness|
75
- { fuzzy: { attr => { value: value, fuzziness: fuzziness }.update(flags) } }
76
- end
77
- end
27
+ def self.define_filter(method, &block)
28
+ register method, Class.new(Gum::Filter, &block)
78
29
  end
79
30
 
80
- def default_range_result(attr_from, attr_to)
81
- {
82
- range: {
83
- attr => {
84
- gte: attr_from,
85
- lte: attr_to
86
- }
87
- }
88
- }
31
+ %i(term terms prefix wildcard regexp fuzzy exists geo range).each do |method|
32
+ register method
89
33
  end
90
34
  end
91
35
  end
@@ -0,0 +1,25 @@
1
+ module Gum
2
+ module Filters
3
+ class Exists < Filter
4
+ private
5
+
6
+ FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].to_set
7
+
8
+ def __render__(value)
9
+ if value
10
+ { exists: { field: field } }
11
+ else
12
+ { bool: { must_not: { exists: { field: field } } } }
13
+ end
14
+ end
15
+
16
+ def value_from(params)
17
+ !FALSE_VALUES.include?(super)
18
+ end
19
+
20
+ def empty?(value)
21
+ value.nil? || value.try(:empty?)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ module Gum
2
+ module Filters
3
+ class Fuzzy < Filter
4
+ private
5
+
6
+ def __render__(value)
7
+ { fuzzy: { field => { value: value }.merge(options) } }
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ module Gum
2
+ module Filters
3
+ class Geo
4
+ def self.new(param, field, options)
5
+ filter = const_get options.delete(:type).to_s.camelize
6
+ filter.new param, field, options
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,70 @@
1
+ module Gum
2
+ module Filters
3
+ class Geo
4
+ class Bbox < Filter
5
+ private
6
+
7
+ def __render__(value)
8
+ {
9
+ geo_bounding_box: {
10
+ field => {
11
+ top: value.top,
12
+ left: value.left,
13
+ bottom: value.bottom,
14
+ right: value.right
15
+ }
16
+ }
17
+ }
18
+ end
19
+
20
+ def parser
21
+ @_parser ||= ParamParser.new(options[:pattern])
22
+ end
23
+
24
+ def value_from(params)
25
+ parser.parse(super)
26
+ end
27
+
28
+ def empty?(params)
29
+ super || parser.empty?(params[param])
30
+ end
31
+
32
+ class ParamParser
33
+ attr_reader :top, :left, :bottom, :right
34
+
35
+ def initialize(order)
36
+ @order = order || 'top,left,bottom,right'
37
+ @regexp = regexp
38
+ end
39
+
40
+ def empty?(value)
41
+ !@regexp.match(value)
42
+ end
43
+
44
+ def parse(value)
45
+ match_data = @regexp.match(value)
46
+ return unless match_data
47
+ @top = match_data[:top].to_f
48
+ @left = match_data[:left].to_f
49
+ @bottom = match_data[:bottom].to_f
50
+ @right = match_data[:right].to_f
51
+ self
52
+ end
53
+
54
+ private
55
+
56
+ def number_pattern
57
+ '-?\\d{,3}\\.?\\d{,12}'
58
+ end
59
+
60
+ def regexp
61
+ order = @order.split(',').map do |param|
62
+ "(?<#{param}>#{number_pattern})"
63
+ end.join(',')
64
+ /^#{order}$/
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,26 @@
1
+ module Gum
2
+ module Filters
3
+ class Geo
4
+ class Distance < Filter
5
+ private
6
+
7
+ def __render__(value)
8
+ {
9
+ geo_distance: {
10
+ distance: value[:distance],
11
+ field => value[:lat_lon]
12
+ }
13
+ }
14
+ end
15
+
16
+ def value_from(params)
17
+ return nil unless params[:"#{param}"].present? && params[:"#{param}_distance"].present?
18
+ {
19
+ distance: params[:"#{param}_distance"],
20
+ lat_lon: params[:"#{param}"]
21
+ }
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,33 @@
1
+ module Gum
2
+ module Filters
3
+ class Geo
4
+ class Range < Filter
5
+ private
6
+
7
+ def __render__(value)
8
+ {
9
+ geo_distance_range: {
10
+ from: value[:from],
11
+ to: value[:to],
12
+ field => value[:lat_lon]
13
+ }
14
+ }
15
+ end
16
+
17
+ def value_from(params)
18
+ {
19
+ from: params[:"#{param}_from"],
20
+ to: params[:"#{param}_to"],
21
+ lat_lon: params[:"#{param}"]
22
+ }
23
+ end
24
+
25
+ def empty?(params)
26
+ params[:"#{param}"].blank? ||
27
+ params[:"#{param}_from"].blank? ||
28
+ params[:"#{param}_to"].blank?
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+ module Gum
2
+ module Filters
3
+ class Prefix < Filter
4
+ private
5
+
6
+ def __render__(value)
7
+ if options[:boost]
8
+ { prefix: { field => { value: value, boost: options[:boost] } } }
9
+ else
10
+ { prefix: { field => value } }
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+ module Gum
2
+ module Filters
3
+ class Range < Filter
4
+ private
5
+
6
+ def __render__(value)
7
+ { range: { field => value.update(options) } }
8
+ end
9
+
10
+ def value_from(params)
11
+ {
12
+ gte: params[:"#{param}_from"],
13
+ lte: params[:"#{param}_to"]
14
+ }
15
+ end
16
+
17
+ def empty?(params)
18
+ params[:"#{param}_from"].blank? || params[:"#{param}_to"].blank?
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ module Gum
2
+ module Filters
3
+ class Regexp < Filter
4
+ private
5
+
6
+ def __render__(value)
7
+ { regexp: { field => { value: value }.merge(options) } }
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module Gum
2
+ module Filters
3
+ class Term < Filter
4
+ private
5
+
6
+ def __render__(value)
7
+ if options[:boost]
8
+ { term: { field => { value: value, boost: options[:boost] } } }
9
+ else
10
+ { term: { field => value } }
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module Gum
2
+ module Filters
3
+ class Terms < Filter
4
+ private
5
+
6
+ def __render__(value)
7
+ { terms: { field => [value].flatten } }
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module Gum
2
+ module Filters
3
+ class Wildcard < Filter
4
+ private
5
+
6
+ def __render__(value)
7
+ if options[:boost]
8
+ { wildcard: { field => { value: value, boost: options[:boost] } } }
9
+ else
10
+ { wildcard: { field => value } }
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ module Gum
2
+ class OrderBy < Filter
3
+ ALLOWED_VALUES = %w(asc desc).freeze
4
+
5
+ def __render__(value)
6
+ { field => value }
7
+ end
8
+
9
+ def value_from(params)
10
+ value = super.to_s
11
+ ALLOWED_VALUES.include?(value) ? value : options[:default]
12
+ end
13
+
14
+ def empty?(params)
15
+ ALLOWED_VALUES.exclude?(params[param].to_s) && options[:default].nil?
16
+ end
17
+ end
18
+ end
@@ -1,5 +1,7 @@
1
1
  module Gum
2
2
  class Search
3
+ extend Filters
4
+
3
5
  class_attribute :type
4
6
  class_attribute :scope
5
7
  class_attribute :fields
@@ -7,14 +9,15 @@ module Gum
7
9
  class_attribute :orders
8
10
  class_attribute :query_param
9
11
 
10
- def self.inherited(base)
11
- self.fields = Set.new
12
- self.filters = {}
13
- self.orders = {}
12
+ def self.inherited(klass)
13
+ klass.fields = Set.new
14
+ klass.filters = []
15
+ klass.orders = []
14
16
  end
15
17
 
16
18
  # @param [Chewy::Type] type
17
19
  def self.use(type)
20
+ raise ArgumentError, 'type must be a Chewy::Type' unless type < Chewy::Type
18
21
  self.type = type
19
22
  end
20
23
 
@@ -23,64 +26,49 @@ module Gum
23
26
  end
24
27
 
25
28
  def self.searchable(*attrs)
26
- self.fields += attrs
29
+ self.fields += attrs.flatten
27
30
  end
28
31
 
29
32
  def self.query(attr)
30
33
  self.query_param = attr
31
34
  end
32
35
 
33
- Filters.singleton_methods.each do |method|
34
- define_singleton_method method do |*attrs|
35
- options = attrs.extract_options!
36
- attrs.each do |attr|
37
- filters[attr] = Filters.public_send(method, attr, options)
38
- end
39
- end
40
- end
41
-
42
- def self.order_by(*attrs)
43
- attrs.each do |attr|
44
- orders[attr] =
45
- lambda do |params|
46
- Coerce.split(attr, params) do |field, direction|
47
- { field => direction }
48
- end
49
- end
36
+ def self.order_by(*args)
37
+ Factory.build(Gum::OrderBy, args) do |order|
38
+ orders.push order
50
39
  end
51
40
  end
52
41
 
53
42
  def initialize(params)
54
43
  @params = params
55
- @sort_params = @params[:sort].split(',') if @params[:sort]
56
44
  @q = params[query_param]
57
45
  end
58
46
 
59
- def compile_filters
60
- filters.map do |_, proc|
61
- proc.call(@params)
62
- end.compact
63
- end
64
-
65
- def compile_orders
66
- if @sort_params
67
- self.orders = {}
68
- @sort_params.each do |attr|
69
- self.class.order_by(attr)
70
- end
71
- end
72
- orders.map do |attr, proc|
73
- proc.call(attr)
74
- end.compact
47
+ def to_query
48
+ type.query(query).filter(compile_filters).order(compile_orders)
75
49
  end
76
50
 
77
51
  def search
78
- type.query(query).filter(compile_filters).order(compile_orders).load(scope: scope)
52
+ to_query.load(scope: scope)
79
53
  rescue Elasticsearch::Transport::Transport::Errors::BadRequest => e
80
54
  @error = e.message.match(/QueryParsingException\[([^;]+)\]/).try(:[], 1)
81
55
  type.none
82
56
  end
83
57
 
58
+ private
59
+
60
+ def compile_filters
61
+ filters.map do |filter|
62
+ filter.render(@params)
63
+ end.compact
64
+ end
65
+
66
+ def compile_orders
67
+ orders.map do |order|
68
+ order.render(@params)
69
+ end.compact
70
+ end
71
+
84
72
  def query
85
73
  return {} unless @q.present?
86
74
  {
@@ -1,3 +1,3 @@
1
1
  module Gum
2
- VERSION = '0.1.1'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gum
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anton Sozontov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-11-14 00:00:00.000000000 Z
11
+ date: 2016-11-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,6 +52,34 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry-byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.4'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
55
83
  - !ruby/object:Gem::Dependency
56
84
  name: activesupport
57
85
  requirement: !ruby/object:Gem::Requirement
@@ -59,6 +87,9 @@ dependencies:
59
87
  - - ">="
60
88
  - !ruby/object:Gem::Version
61
89
  version: '3.2'
90
+ - - "<"
91
+ - !ruby/object:Gem::Version
92
+ version: '5.1'
62
93
  type: :runtime
63
94
  prerelease: false
64
95
  version_requirements: !ruby/object:Gem::Requirement
@@ -66,6 +97,9 @@ dependencies:
66
97
  - - ">="
67
98
  - !ruby/object:Gem::Version
68
99
  version: '3.2'
100
+ - - "<"
101
+ - !ruby/object:Gem::Version
102
+ version: '5.1'
69
103
  - !ruby/object:Gem::Dependency
70
104
  name: chewy
71
105
  requirement: !ruby/object:Gem::Requirement
@@ -98,8 +132,22 @@ files:
98
132
  - bin/setup
99
133
  - gum.gemspec
100
134
  - lib/gum.rb
101
- - lib/gum/coerce.rb
135
+ - lib/gum/factory.rb
136
+ - lib/gum/filter.rb
102
137
  - lib/gum/filters.rb
138
+ - lib/gum/filters/exists.rb
139
+ - lib/gum/filters/fuzzy.rb
140
+ - lib/gum/filters/geo.rb
141
+ - lib/gum/filters/geo/bbox.rb
142
+ - lib/gum/filters/geo/distance.rb
143
+ - lib/gum/filters/geo/range.rb
144
+ - lib/gum/filters/prefix.rb
145
+ - lib/gum/filters/range.rb
146
+ - lib/gum/filters/regexp.rb
147
+ - lib/gum/filters/term.rb
148
+ - lib/gum/filters/terms.rb
149
+ - lib/gum/filters/wildcard.rb
150
+ - lib/gum/order.rb
103
151
  - lib/gum/search.rb
104
152
  - lib/gum/version.rb
105
153
  homepage: https://github.com/qwiqer/gum
@@ -1,51 +0,0 @@
1
- module Gum
2
- module Coerce
3
- FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].to_set
4
-
5
- module_function
6
-
7
- def range(attr, params)
8
- key_from = :"#{attr}_from"
9
- key_to = :"#{attr}_to"
10
-
11
- return unless params.values_at(key_from, key_to).all?
12
-
13
- yield(gte: params[key_from], lte: params[key_to])
14
- end
15
-
16
- def term(attr, params)
17
- return if params[attr].blank?
18
-
19
- yield params[attr]
20
- end
21
-
22
- def terms(attr, params)
23
- return if params[attr].blank?
24
-
25
- if params[attr].is_a?(Array)
26
- yield params[attr]
27
- elsif params[attr].is_a?(String)
28
- yield params[attr].split(',')
29
- else
30
- raise ArgumentError, 'Terms should be either Array or String'
31
- end
32
- end
33
-
34
- def exists(attr, params)
35
- return if params[attr].eql?('')
36
-
37
- yield !FALSE_VALUES.include?(params[attr])
38
- end
39
-
40
- def split(attr, params, separator: '-', default: nil)
41
- return if params[attr].blank?
42
-
43
- value, option = params[attr].split(separator, 2)
44
- option ||= default.presence
45
-
46
- return if value.blank? || option.blank?
47
-
48
- yield value, option
49
- end
50
- end
51
- end