gum 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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