bharat_filter_engine 0.1.2 → 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
  SHA256:
3
- metadata.gz: d0c1821308815c4d17986f4cfdf2eea0daeae04849d48fe688aa231891bdfb93
4
- data.tar.gz: bb1a6ad5176d209fa37af14deb1dabdd756884eaab062db7d65ade69b0c4b3bb
3
+ metadata.gz: 45380eb2ba5afe790b2cbc8f0abe65cbab402d2489fb025bd2d2407c5700a57f
4
+ data.tar.gz: 8529dbb3f163c708c819ab166e1d946d23900ef685b9bb44b758f734c44c913b
5
5
  SHA512:
6
- metadata.gz: 989674d80aeeaa3589f6a59d77a426acdd69886f780425100ddfe995aabf5096d03e80ec7958e51f66941b79e3ca14606b1b5e545afac0eddb3e9d9cbb87f048
7
- data.tar.gz: 06d6923226a0b941075d7e7323f4eca428c493f7f22f625ff8d243962dff812eb1992ac0e7eb888e47341f1df1e430523931fac5a55f76b6c6cc360e31aa7c01
6
+ metadata.gz: 4ae9d8027f95b37c0f7f43235a56cc0b234ab444aa0079bfa1425b44ca408677def18f0f11216dced37321b68765a7e64fcd2217add708dc097a8da6e44c536e
7
+ data.tar.gz: a2ea4acb0d03be39e10d59b0c01b7da206489e95fbad43a7d60c1f04e83c9de7031de5d5779599b6c22348e684c17b326b2d9f3fa3b39a022e69b3ab429cd1be
data/Gemfile CHANGED
@@ -1,19 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source "https://rubygems.org"
3
+ source 'https://rubygems.org'
4
4
 
5
5
  gemspec
6
6
 
7
- gem "activerecord", ">= 6.1"
8
- gem "activemodel", ">= 6.1"
9
- gem "activesupport", ">= 6.1"
7
+ gem 'activemodel', '>= 6.1'
8
+ gem 'activerecord', '>= 6.1'
9
+ gem 'activesupport', '>= 6.1'
10
10
 
11
11
  group :development, :test do
12
- gem "sqlite3"
13
- gem "rspec"
14
- gem "rake"
12
+ gem 'rake'
13
+ gem 'rspec'
14
+ gem 'rubocop-rails-omakase', require: false
15
+ gem 'rubocop-rake', require: false
16
+ gem 'rubocop-rspec', require: false
17
+ gem 'sqlite3'
15
18
  end
16
19
 
17
20
  group :test do
18
- gem "database_cleaner-active_record"
19
- end
21
+ gem 'database_cleaner-active_record'
22
+ end
data/README.md CHANGED
@@ -8,7 +8,7 @@ query string) into a filtered `ActiveRecord::Relation` — including
8
8
  filters on associated models, free-text search, and dynamically
9
9
  populated dropdown values.
10
10
 
11
- [![Gem Version](https://img.shields.io/badge/version-0.1.1-blue)](CHANGELOG.md)
11
+ [![Gem Version](https://img.shields.io/badge/version-0.1.2-blue)](CHANGELOG.md)
12
12
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
13
13
 
14
14
  ---
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task default: :spec
8
+ task default: :spec
@@ -7,19 +7,14 @@ module BharatFilterEngine
7
7
  end
8
8
 
9
9
  def resolve(path)
10
- parts = path.to_s.split("__").map(&:to_sym)
11
-
10
+ parts = path.to_s.split('__').map(&:to_sym)
12
11
  return nil if parts.empty?
13
12
 
14
13
  current_klass = @scope.klass
15
14
  reflections = []
16
15
 
17
16
  parts.each do |association_name|
18
- reflection =
19
- current_klass.reflect_on_association(
20
- association_name
21
- )
22
-
17
+ reflection = current_klass.reflect_on_association(association_name)
23
18
  return nil unless reflection
24
19
 
25
20
  reflections << reflection
@@ -36,11 +31,6 @@ module BharatFilterEngine
36
31
 
37
32
  def resolve_column(field)
38
33
  field = field.to_s
39
-
40
- # ----------------------------------------
41
- # Direct column
42
- # ----------------------------------------
43
-
44
34
  if @scope.klass.column_names.include?(field)
45
35
  return {
46
36
  column: field,
@@ -50,41 +40,32 @@ module BharatFilterEngine
50
40
  }
51
41
  end
52
42
 
53
- # ----------------------------------------
54
43
  # Nested column
55
- #
56
44
  # lead__client__name
57
- #
58
45
  # lead -> client -> name
59
- # ----------------------------------------
60
-
61
- parts = field.split("__")
62
-
63
- column =
64
- parts.pop
65
-
66
- association_path =
67
- parts.join("__")
68
-
69
- result =
70
- resolve(association_path)
46
+ parts = field.split('__')
47
+ column = parts.pop
48
+ association_path = parts.join('__')
49
+ result = resolve(association_path)
71
50
 
72
51
  return nil unless result
52
+ return nil unless result[:klass].column_names.include?(column)
73
53
 
74
- return nil unless
75
- result[:klass].column_names.include?(column)
76
-
77
- result.merge(
78
- column: column
79
- )
54
+ result.merge(column: column)
80
55
  end
81
56
 
82
57
  def join(scope, associations)
83
58
  return scope if associations.blank?
84
59
 
85
- scope.joins(
86
- build_nested_join(associations)
87
- )
60
+ scope.joins(build_nested_join(associations))
61
+ end
62
+
63
+ def subquery(associations)
64
+ klass = @scope.klass
65
+ relation = klass.unscoped.joins(build_nested_join(associations))
66
+ relation = yield(relation) if block_given?
67
+
68
+ relation.select(klass.arel_table[klass.primary_key])
88
69
  end
89
70
 
90
71
  private
@@ -99,4 +80,4 @@ module BharatFilterEngine
99
80
  end
100
81
  end
101
82
  end
102
- end
83
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BharatFilterEngine
4
+ module ColumnHelper
5
+ STRING_COLUMN_TYPES = %i[string text].freeze
6
+
7
+ module_function
8
+
9
+ def string_like?(klass, column)
10
+ db_column = klass&.columns_hash&.[](column.to_s)
11
+ db_column.nil? || STRING_COLUMN_TYPES.include?(db_column.type)
12
+ end
13
+
14
+ def present_predicate(arel_column, klass, column)
15
+ predicate = arel_column.not_eq(nil)
16
+ return predicate unless string_like?(klass, column)
17
+
18
+ predicate.and(arel_column.not_eq(''))
19
+ end
20
+
21
+ def blank_predicate(arel_column, klass, column)
22
+ predicate = arel_column.eq(nil)
23
+ return predicate unless string_like?(klass, column)
24
+
25
+ predicate.or(arel_column.eq(''))
26
+ end
27
+ end
28
+ end
@@ -2,10 +2,14 @@
2
2
 
3
3
  module BharatFilterEngine
4
4
  class DynamicFilterValues
5
- def initialize(scope:, model: nil, field:)
5
+ LIKE_ESCAPE_CHAR = '\\'
6
+
7
+ def initialize(scope:, field:, model: nil, term: nil, limit: nil)
6
8
  @scope = scope
7
9
  @model = model || scope.klass
8
10
  @field = field.to_s
11
+ @term = term.presence
12
+ @limit = limit.presence&.to_i
9
13
  end
10
14
 
11
15
  def call
@@ -14,26 +18,21 @@ module BharatFilterEngine
14
18
  result = resolver.resolve_column(@field)
15
19
  return nil unless result
16
20
 
17
- scoped =
18
- resolver.join(
19
- @scope,
20
- result[:associations]
21
- )
22
-
23
- column = result[:column]
24
- nested = result[:associations].present?
21
+ scoped = resolver.join(@scope, result[:associations])
22
+ arel_column = result[:klass].arel_table[result[:column]]
23
+ scoped = scoped.where(ColumnHelper.present_predicate(arel_column, result[:klass], result[:column]))
24
+ scoped = apply_term(scoped, arel_column) if @term
25
+ scoped = scoped.distinct.order(arel_column)
26
+ scoped = scoped.limit(@limit) if @limit&.positive?
25
27
 
26
- not_condition =
27
- nested ? { result[:table_name] => { column => [nil, ""] } } : { column => [nil, ""] }
28
+ scoped.pluck(arel_column)
29
+ end
28
30
 
29
- qualified_column =
30
- nested ? Arel.sql("#{result[:table_name]}.#{column}") : column
31
+ private
31
32
 
32
- scoped
33
- .where.not(not_condition)
34
- .distinct
35
- .order(qualified_column)
36
- .pluck(qualified_column)
33
+ def apply_term(scoped, arel_column)
34
+ escaped = @term.to_s.gsub(/[\\%_]/) { |char| "#{LIKE_ESCAPE_CHAR}#{char}" }
35
+ scoped.where(arel_column.matches("%#{escaped}%", LIKE_ESCAPE_CHAR))
37
36
  end
38
37
  end
39
38
  end