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.
- 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
|