trailblazer-finder 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +160 -11
  3. data/CHANGES.md +5 -0
  4. data/README.md +93 -22
  5. data/lib/trailblazer/finder.rb +3 -0
  6. data/lib/trailblazer/finder/adapters/active_record.rb +4 -0
  7. data/lib/trailblazer/finder/adapters/active_record/predicates.rb +42 -0
  8. data/lib/trailblazer/finder/adapters/friendly_id.rb +22 -24
  9. data/lib/trailblazer/finder/adapters/sequel.rb +5 -0
  10. data/lib/trailblazer/finder/adapters/sequel/predicates.rb +43 -0
  11. data/lib/trailblazer/finder/base.rb +3 -3
  12. data/lib/trailblazer/finder/features.rb +1 -0
  13. data/lib/trailblazer/finder/features/predicate.rb +37 -0
  14. data/lib/trailblazer/finder/filter.rb +2 -2
  15. data/lib/trailblazer/finder/predicates.rb +39 -0
  16. data/lib/trailblazer/finder/utils/deep_locate.rb +45 -0
  17. data/lib/trailblazer/finder/utils/extra.rb +1 -1
  18. data/lib/trailblazer/finder/utils/splitter.rb +42 -0
  19. data/lib/trailblazer/finder/version.rb +1 -1
  20. data/spec/spec_helper_sequel.rb +1 -0
  21. data/spec/trailblazer/finder/adapters/active_record/base_spec.rb +1 -1
  22. data/spec/trailblazer/finder/adapters/active_record/predicates_spec.rb +98 -0
  23. data/spec/trailblazer/finder/adapters/active_record/sorting_spec.rb +4 -0
  24. data/spec/trailblazer/finder/adapters/data_mapper/base_spec.rb +1 -1
  25. data/spec/trailblazer/finder/adapters/sequel/base_spec.rb +1 -1
  26. data/spec/trailblazer/finder/adapters/sequel/predicates_spec.rb +89 -0
  27. data/spec/trailblazer/finder/adapters/sequel/sorting_spec.rb +4 -0
  28. data/spec/trailblazer/finder/features/paging_spec.rb +1 -1
  29. data/spec/trailblazer/finder/features/predicates_spec.rb +99 -0
  30. data/spec/trailblazer/finder/features/sorting_spec.rb +3 -4
  31. data/spec/trailblazer/operation/predicates_spec.rb +127 -0
  32. data/spec/trailblazer/test_spec.rb +41 -0
  33. data/trailblazer-finder.gemspec +0 -2
  34. metadata +17 -15
@@ -1,31 +1,29 @@
1
- if Gem.loaded_specs.key?('activerecord')
2
- module Trailblazer
3
- class Finder
4
- module Adapters
5
- # FriendlyId Adapter
6
- module FriendlyId
7
- def self.included(base)
8
- base.extend ClassMethods
9
- base.class_eval do
10
- filter_by :id, with: :apply_slug_filter
11
- end
1
+ module Trailblazer
2
+ class Finder
3
+ module Adapters
4
+ # FriendlyId Adapter
5
+ module FriendlyId
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ base.class_eval do
9
+ filter_by :id, with: :apply_slug_filter
12
10
  end
11
+ end
13
12
 
14
- def apply_slug_filter(entity_type, value)
15
- return if value.nil?
16
- return if value == ''
17
- if num?(value)
18
- entity_type.where(id: value)
19
- else
20
- entity_type.where(slug: Utils::String.underscore(value.downcase))
21
- end
13
+ def apply_slug_filter(entity_type, value)
14
+ return if value.nil?
15
+ return if value == ''
16
+ if num?(value)
17
+ entity_type.where(id: value)
18
+ else
19
+ entity_type.where(slug: Utils::String.underscore(value.downcase))
22
20
  end
21
+ end
23
22
 
24
- def num?(str)
25
- Integer(str)
26
- rescue ArgumentError, TypeError
27
- false
28
- end
23
+ def num?(str)
24
+ Integer(str)
25
+ rescue ArgumentError, TypeError
26
+ false
29
27
  end
30
28
  end
31
29
  end
@@ -1,5 +1,6 @@
1
1
  require 'trailblazer/finder/adapters/sequel/paging'
2
2
  require 'trailblazer/finder/adapters/sequel/sorting'
3
+ require 'trailblazer/finder/adapters/sequel/predicates'
3
4
 
4
5
  module Trailblazer
5
6
  class Finder
@@ -8,6 +9,10 @@ module Trailblazer
8
9
  module Sequel
9
10
  def self.included(base)
10
11
  base.extend ClassMethods
12
+ base.instance_eval do
13
+ include Sequel::Predicates if defined?(Features::Predicate::ClassMethods)
14
+ end
15
+ # require 'trailblazer/finder/adapters/sequel/predicates' if defined?(Features::Predicates::ClassMethods)
11
16
  end
12
17
 
13
18
  module ClassMethods
@@ -0,0 +1,43 @@
1
+ module Trailblazer
2
+ class Finder
3
+ module Adapters
4
+ module Sequel
5
+ # Sequel - Predicates Adapter
6
+ module Predicates
7
+
8
+ def not_eq(attribute, value, entity_type)
9
+ entity_type.exclude(attribute.to_sym => value.to_s)
10
+ end
11
+
12
+ def eq(attribute, value, entity_type)
13
+ entity_type.where(attribute.to_sym => value.to_s)
14
+ end
15
+
16
+ def blank(attribute, value, entity_type)
17
+ entity_type.where(attribute.to_sym => nil).or(attribute.to_sym => "")
18
+ end
19
+
20
+ def not_blank(attribute, value, entity_type)
21
+ entity_type.exclude(attribute.to_sym => nil).exclude(attribute.to_sym => "")
22
+ end
23
+
24
+ def gt(attribute, value, entity_type)
25
+ entity_type.where{::Sequel[attribute.to_sym] > value.to_f}
26
+ end
27
+
28
+ def gte(attribute, value, entity_type)
29
+ entity_type.where{::Sequel[attribute.to_sym] >= value.to_f}
30
+ end
31
+
32
+ def lt(attribute, value, entity_type)
33
+ entity_type.where{::Sequel[attribute.to_sym] < value.to_f}
34
+ end
35
+
36
+ def lte(attribute, value, entity_type)
37
+ entity_type.where{::Sequel[attribute.to_sym] <= value.to_f}
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,9 +1,9 @@
1
- require 'hashie/extensions/deep_locate'
2
-
3
1
  module Trailblazer
4
2
  class Finder
5
3
  # Base module
6
4
  module Base
5
+ PREDICATES = %w[eq not_eq].freeze
6
+
7
7
  def self.included(base)
8
8
  base.extend ClassMethods
9
9
  base.instance_eval do
@@ -88,7 +88,7 @@ module Trailblazer
88
88
  else
89
89
  lambda do |entity_type, value|
90
90
  return if Utils::String.blank?(value)
91
- Hashie::Extensions::DeepLocate.deep_locate ->(k, v, _object) { k == key.to_sym && v == value }, entity_type
91
+ Utils::DeepLocate.deep_locate ->(k, v, _object) { k == key.to_sym && v == value }, entity_type
92
92
  end
93
93
  end
94
94
  end
@@ -1,5 +1,6 @@
1
1
  require 'trailblazer/finder/features/paging'
2
2
  require 'trailblazer/finder/features/sorting'
3
+ require 'trailblazer/finder/features/predicate'
3
4
 
4
5
  module Trailblazer
5
6
  class Finder
@@ -0,0 +1,37 @@
1
+ module Trailblazer
2
+ class Finder
3
+ module Features
4
+ module Predicate
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ base.instance_eval do
8
+ include Predicates
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+ def do_filters(attribute, predicate)
14
+ filter_by "#{attribute}_#{predicate}" do |entity_type, value|
15
+ splitter = Utils::Splitter.new "#{attribute}_#{predicate}", value
16
+ splitter.split_key predicate.to_sym
17
+ send splitter.op, splitter.field, splitter.value, entity_type
18
+ end
19
+ end
20
+
21
+ def predicates_for(*attributes)
22
+ attributes.each do |attribute|
23
+ do_filters(attribute, "eq")
24
+ do_filters(attribute, "not_eq")
25
+ do_filters(attribute, "blank")
26
+ do_filters(attribute, "not_blank")
27
+ do_filters(attribute, "gt")
28
+ do_filters(attribute, "gte")
29
+ do_filters(attribute, "lt")
30
+ do_filters(attribute, "lte")
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -21,7 +21,7 @@ module Trailblazer
21
21
  module Handler
22
22
  module_function
23
23
 
24
- def build(name, defined_bys) # rubocop:disable Metrics/MethodLength
24
+ def build(name, defined_bys)
25
25
  defined_bys = defined_bys.map(&:to_s)
26
26
  handler = self
27
27
  lambda do |entity_type, value|
@@ -35,7 +35,7 @@ module Trailblazer
35
35
  end
36
36
  end
37
37
 
38
- def apply_filter(object:, filter_by:, defined_bys:, entity_type:, value:) # rubocop:disable Metrics/MethodLength
38
+ def apply_filter(object:, filter_by:, defined_bys:, entity_type:, value:)
39
39
  return if value.nil? || value == ''
40
40
 
41
41
  unless defined_bys.include? value
@@ -0,0 +1,39 @@
1
+ module Trailblazer
2
+ class Finder
3
+ module Predicates
4
+ def not_eq(attribute, value, entity_type)
5
+ return if Utils::String.blank?(value.to_s)
6
+ Utils::DeepLocate.deep_locate ->(k, v, _object) { k == attribute.to_sym && v.to_s != value.to_s && v != nil }, entity_type
7
+ end
8
+
9
+ def eq(attribute, value, entity_type)
10
+ return if Utils::String.blank?(value.to_s)
11
+ Utils::DeepLocate.deep_locate ->(k, v, _object) { k == attribute.to_sym && v.to_s == value.to_s && v != nil }, entity_type
12
+ end
13
+
14
+ def blank(attribute, _value, entity_type)
15
+ Utils::DeepLocate.deep_locate ->(k, v, _object) { k == attribute.to_sym && Utils::String.blank?(v.to_s) }, entity_type
16
+ end
17
+
18
+ def not_blank(attribute, _value, entity_type)
19
+ Utils::DeepLocate.deep_locate ->(k, v, _object) { k == attribute.to_sym && !Utils::String.blank?(v.to_s) }, entity_type
20
+ end
21
+
22
+ def gt(attribute, value, entity_type)
23
+ Utils::DeepLocate.deep_locate ->(k, v, _object) { k == attribute.to_sym && v.to_i > value.to_f }, entity_type
24
+ end
25
+
26
+ def gte(attribute, value, entity_type)
27
+ Utils::DeepLocate.deep_locate ->(k, v, _object) { k == attribute.to_sym && v.to_i >= value.to_f }, entity_type
28
+ end
29
+
30
+ def lt(attribute, value, entity_type)
31
+ Utils::DeepLocate.deep_locate ->(k, v, _object) { k == attribute.to_sym && v.to_i < value.to_f }, entity_type
32
+ end
33
+
34
+ def lte(attribute, value, entity_type)
35
+ Utils::DeepLocate.deep_locate ->(k, v, _object) { k == attribute.to_sym && v.to_i <= value.to_f }, entity_type
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,45 @@
1
+ module Trailblazer
2
+ class Finder
3
+ module Utils
4
+ module DeepLocate
5
+ def self.deep_locate(comparator, object)
6
+ comparator = _construct_key_comparator(comparator, object) unless comparator.respond_to?(:call)
7
+
8
+ _deep_locate(comparator, object)
9
+ end
10
+
11
+ def self._construct_key_comparator(search_key, object)
12
+ search_key = search_key.to_s if defined?(::ActiveSupport::HashWithIndifferentAccess) && object.is_a?(::ActiveSupport::HashWithIndifferentAccess)
13
+ search_key = search_key.to_s if object.respond_to?(:indifferent_access?) && object.indifferent_access?
14
+
15
+ lambda do |non_callable_object|
16
+ ->(key, _, _) { key == non_callable_object }
17
+ end.call(search_key)
18
+ end
19
+
20
+ def self._deep_locate(comparator, object, result = [])
21
+ if object.is_a?(::Enumerable)
22
+ if object.any? { |value| _match_comparator?(value, comparator, object) }
23
+ result.push object
24
+ end
25
+ (object.respond_to?(:values) ? object.values : object.entries).each do |value|
26
+ _deep_locate(comparator, value, result)
27
+ end
28
+ end
29
+
30
+ result
31
+ end
32
+
33
+ def self._match_comparator?(value, comparator, object)
34
+ if object.is_a?(::Hash)
35
+ key, value = value
36
+ else
37
+ key = nil
38
+ end
39
+
40
+ comparator.call(key, value, object)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -11,7 +11,7 @@ module Trailblazer
11
11
  end
12
12
  end
13
13
 
14
- def self.deep_copy(object) # rubocop:disable Metrics/MethodLength
14
+ def self.deep_copy(object)
15
15
  case object
16
16
  when Array
17
17
  object.map { |element| deep_copy(element) }
@@ -0,0 +1,42 @@
1
+ module Trailblazer
2
+ class Finder
3
+ # Helper module
4
+ module Utils
5
+ class Splitter
6
+ def initialize( key, value )
7
+ @key, @value = key, value
8
+ end
9
+
10
+ attr_reader :key, :value
11
+
12
+ # split suffix from the key and store the two values as name and op
13
+ # return truthy if successful
14
+ def split_key( suffix )
15
+ rv = @key =~ /\A(?:(.*?)_)?(#{suffix})\z/
16
+ @field, @op = $1, $2
17
+ rv
18
+ end
19
+
20
+ alias_method :===, :split_key
21
+ alias_method :=~, :split_key
22
+
23
+ # return name if the split was successful, or fall back to key
24
+ # which is handy when none of the predicates match and so key
25
+ # is probably just a field name.
26
+ def field
27
+ (@field || @key)
28
+ end
29
+
30
+ # the operator, or predicate
31
+ def op
32
+ @op.to_sym
33
+ end
34
+
35
+ # the value
36
+ def value
37
+ @value.to_s
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,5 +1,5 @@
1
1
  module Trailblazer
2
2
  class Finder
3
- VERSION = '0.1.1'.freeze
3
+ VERSION = '0.1.2'.freeze
4
4
  end
5
5
  end
@@ -3,6 +3,7 @@ require_relative 'support/paging_shared_example'
3
3
  require_relative 'support/sorting_shared_example'
4
4
  require 'sequel'
5
5
  Sequel::Model.plugin :timestamps
6
+
6
7
  DB = Sequel.sqlite
7
8
 
8
9
  DB.create_table :s_products do
@@ -16,7 +16,7 @@ module Trailblazer
16
16
  end
17
17
  end
18
18
 
19
- def finder_class(&block) # rubocop:disable Metrics/MethodLength
19
+ def finder_class(&block)
20
20
  define_finder_class do
21
21
  adapters ActiveRecord
22
22
 
@@ -0,0 +1,98 @@
1
+ require 'spec_helper_active_record'
2
+ # require 'spec_helper'
3
+
4
+ module Trailblazer
5
+ class Finder
6
+ module Adapters
7
+ module ActiveRecord
8
+ describe Predicates do
9
+ class TestFinder < Trailblazer::Finder
10
+ features Predicate
11
+ adapters ActiveRecord
12
+
13
+ entity_type { Product }
14
+
15
+ predicates_for :name, :price, :created_at
16
+
17
+ filter_by(:category) { |entity_type, _| entity_type.joins(:category) }
18
+ end
19
+
20
+ def finder_with_predicate(filter = nil, value = nil, filters = {})
21
+ TestFinder.new filter: { filter => value }.merge(filters)
22
+ end
23
+
24
+ describe 'equals' do
25
+ before do
26
+ Product.delete_all
27
+ Product.reset_pk_sequence
28
+ 10.times do |i|
29
+ next Product.create name: "", price: "1#{i}" if i == 7
30
+ next Product.create name: nil, price: "1#{i}" if i == 8
31
+ next Product.create name: "product_4", price: "1#{i}" if i == 9
32
+ Product.create name: "product_#{i}", price: "1#{i}"
33
+ end
34
+ end
35
+
36
+ it 'it finds single row with name equal to product_5' do
37
+ finder = finder_with_predicate 'name_eq', 'product_5'
38
+ expect(finder.results.map(&:price)).to eq [15]
39
+ end
40
+
41
+ it 'it finds multiple rows with name equal to product_4' do
42
+ finder = finder_with_predicate 'name_eq', 'product_4'
43
+ expect(finder.results.map(&:price)).to eq [14, 19]
44
+ end
45
+
46
+ it 'it finds rows with name not equal to product_4' do
47
+ finder = finder_with_predicate 'name_not_eq', 'product_4'
48
+ expect(finder.results.map(&:name)).to eq ["product_0", "product_1", "product_2", "product_3", "product_5", "product_6", ""]
49
+ expect(finder.results.map(&:price)).to eq [10, 11, 12, 13, 15, 16, 17]
50
+ end
51
+
52
+ it 'it finds multiple rows with name blank (empty/nil)' do
53
+ finder = finder_with_predicate 'name_blank', ''
54
+ expect(finder.results.map(&:price)).to eq [17, 18]
55
+ end
56
+
57
+ it 'it finds multiple rows with name not blank (empty/nil)' do
58
+ finder = finder_with_predicate 'name_not_blank', ''
59
+ expect(finder.results.map(&:name)).to eq ["product_0", "product_1", "product_2", "product_3", "product_4", "product_5", "product_6", "product_4"]
60
+ end
61
+
62
+ it 'it finds multiple rows with price less than 15' do
63
+ finder = finder_with_predicate 'price_lt', 15
64
+ expect(finder.results.map(&:name)).to eq ["product_0", "product_1", "product_2", "product_3", "product_4"]
65
+ expect(finder.results.map(&:price)).to eq [10, 11, 12, 13, 14]
66
+ end
67
+
68
+ it 'it finds multiple rows with price equal to and less than 15' do
69
+ finder = finder_with_predicate 'price_lte', 15
70
+ expect(finder.results.map(&:name)).to eq ["product_0", "product_1", "product_2", "product_3", "product_4", "product_5"]
71
+ expect(finder.results.map(&:price)).to eq [10, 11, 12, 13, 14, 15]
72
+ end
73
+
74
+ it 'it finds multiple rows with price greater than 15' do
75
+ finder = finder_with_predicate 'price_gt', 15
76
+ expect(finder.results.map(&:name)).to eq ["product_6", "", nil, "product_4"]
77
+ expect(finder.results.map(&:price)).to eq [16, 17, 18, 19]
78
+ end
79
+
80
+ it 'it finds multiple rows with price equal to and greater than 15' do
81
+ finder = finder_with_predicate 'price_gte', 15
82
+ expect(finder.results.map(&:name)).to eq ["product_5", "product_6", "", nil, "product_4"]
83
+ expect(finder.results.map(&:price)).to eq [15, 16, 17, 18, 19]
84
+ end
85
+
86
+ it 'it finds rows with name not equal to product_4 and name not blank and price greater than 14' do
87
+ params = { name_not_blank: true, price_gt: 14 }
88
+ finder = finder_with_predicate 'name_not_eq', 'product_4', params
89
+
90
+ expect(finder.results.map(&:name)).to eq ["product_5", "product_6"]
91
+ expect(finder.results.map(&:price)).to eq [15, 16]
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end