trailblazer-finder 0.1.1 → 0.1.2
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 +4 -4
- data/.rubocop_todo.yml +160 -11
- data/CHANGES.md +5 -0
- data/README.md +93 -22
- data/lib/trailblazer/finder.rb +3 -0
- data/lib/trailblazer/finder/adapters/active_record.rb +4 -0
- data/lib/trailblazer/finder/adapters/active_record/predicates.rb +42 -0
- data/lib/trailblazer/finder/adapters/friendly_id.rb +22 -24
- data/lib/trailblazer/finder/adapters/sequel.rb +5 -0
- data/lib/trailblazer/finder/adapters/sequel/predicates.rb +43 -0
- data/lib/trailblazer/finder/base.rb +3 -3
- data/lib/trailblazer/finder/features.rb +1 -0
- data/lib/trailblazer/finder/features/predicate.rb +37 -0
- data/lib/trailblazer/finder/filter.rb +2 -2
- data/lib/trailblazer/finder/predicates.rb +39 -0
- data/lib/trailblazer/finder/utils/deep_locate.rb +45 -0
- data/lib/trailblazer/finder/utils/extra.rb +1 -1
- data/lib/trailblazer/finder/utils/splitter.rb +42 -0
- data/lib/trailblazer/finder/version.rb +1 -1
- data/spec/spec_helper_sequel.rb +1 -0
- data/spec/trailblazer/finder/adapters/active_record/base_spec.rb +1 -1
- data/spec/trailblazer/finder/adapters/active_record/predicates_spec.rb +98 -0
- data/spec/trailblazer/finder/adapters/active_record/sorting_spec.rb +4 -0
- data/spec/trailblazer/finder/adapters/data_mapper/base_spec.rb +1 -1
- data/spec/trailblazer/finder/adapters/sequel/base_spec.rb +1 -1
- data/spec/trailblazer/finder/adapters/sequel/predicates_spec.rb +89 -0
- data/spec/trailblazer/finder/adapters/sequel/sorting_spec.rb +4 -0
- data/spec/trailblazer/finder/features/paging_spec.rb +1 -1
- data/spec/trailblazer/finder/features/predicates_spec.rb +99 -0
- data/spec/trailblazer/finder/features/sorting_spec.rb +3 -4
- data/spec/trailblazer/operation/predicates_spec.rb +127 -0
- data/spec/trailblazer/test_spec.rb +41 -0
- data/trailblazer-finder.gemspec +0 -2
- metadata +17 -15
@@ -1,31 +1,29 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
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
|
@@ -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)
|
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:)
|
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
|
@@ -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
|
data/spec/spec_helper_sequel.rb
CHANGED
@@ -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
|