active_record_seek 0.0.1
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 +7 -0
- data/.gitignore +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +45 -0
- data/LICENSE.txt +21 -0
- data/README.md +47 -0
- data/Rakefile +21 -0
- data/active_record_seek.gemspec +31 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/gemfiles/mysql.gemfile +5 -0
- data/gemfiles/mysql.gemfile.lock +47 -0
- data/gemfiles/postgresql.gemfile +5 -0
- data/gemfiles/postgresql.gemfile.lock +47 -0
- data/gemfiles/sqlite.gemfile +7 -0
- data/gemfiles/sqlite.gemfile.lock +47 -0
- data/lib/active_record_seek.rb +29 -0
- data/lib/active_record_seek/component.rb +57 -0
- data/lib/active_record_seek/concerns/active_record_concern.rb +20 -0
- data/lib/active_record_seek/concerns/instance_variable_concern.rb +40 -0
- data/lib/active_record_seek/middleware.rb +24 -0
- data/lib/active_record_seek/operators/base_operator.rb +65 -0
- data/lib/active_record_seek/operators/ci_base_operator.rb +66 -0
- data/lib/active_record_seek/operators/ci_matches_base_operator.rb +38 -0
- data/lib/active_record_seek/operators/ci_regexp_base_operator.rb +46 -0
- data/lib/active_record_seek/operators/matches_base_operator.rb +32 -0
- data/lib/active_record_seek/operators/regexp_base_operator.rb +46 -0
- data/lib/active_record_seek/query.rb +68 -0
- data/lib/active_record_seek/scopes/association_scope.rb +98 -0
- data/lib/active_record_seek/scopes/base_scope.rb +10 -0
- data/lib/active_record_seek/scopes/seek_or_scope.rb +53 -0
- data/lib/active_record_seek/scopes/seek_scope.rb +54 -0
- data/lib/active_record_seek/version.rb +3 -0
- metadata +149 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
require "byebug"
|
2
|
+
require "active_support"
|
3
|
+
require "active_support/core_ext"
|
4
|
+
require "active_record_seek/version"
|
5
|
+
# concerns
|
6
|
+
require "active_record_seek/concerns/instance_variable_concern"
|
7
|
+
# core
|
8
|
+
require "active_record_seek/query"
|
9
|
+
require "active_record_seek/component"
|
10
|
+
require "active_record_seek/middleware"
|
11
|
+
# operators
|
12
|
+
require "active_record_seek/operators/base_operator"
|
13
|
+
require "active_record_seek/operators/matches_base_operator"
|
14
|
+
require "active_record_seek/operators/regexp_base_operator"
|
15
|
+
require "active_record_seek/operators/ci_base_operator"
|
16
|
+
require "active_record_seek/operators/ci_matches_base_operator"
|
17
|
+
require "active_record_seek/operators/ci_regexp_base_operator"
|
18
|
+
# scopes
|
19
|
+
require "active_record_seek/scopes/base_scope"
|
20
|
+
require "active_record_seek/scopes/association_scope"
|
21
|
+
require "active_record_seek/scopes/seek_or_scope"
|
22
|
+
require "active_record_seek/scopes/seek_scope"
|
23
|
+
# callbacks
|
24
|
+
require "active_record_seek/concerns/active_record_concern"
|
25
|
+
|
26
|
+
ActiveSupport.on_load(:active_record) do
|
27
|
+
# adds builder methods used to initialize seek scopes on columns of a model
|
28
|
+
include ActiveRecordSeek::Concerns::ActiveRecordConcern
|
29
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module ActiveRecordSeek
|
2
|
+
class Component
|
3
|
+
|
4
|
+
include Concerns::InstanceVariableConcern
|
5
|
+
|
6
|
+
attr_accessor(*%w[
|
7
|
+
base_query
|
8
|
+
value
|
9
|
+
operator
|
10
|
+
column
|
11
|
+
namespace
|
12
|
+
original_key
|
13
|
+
])
|
14
|
+
attr_reader(*%w[ association ])
|
15
|
+
delegate(*%w[ active_record_query seek_query ], to: :base_query)
|
16
|
+
delegate(*%w[ table_name adapter_name ], to: :seek_query)
|
17
|
+
|
18
|
+
|
19
|
+
def key=(new_key)
|
20
|
+
self.original_key = new_key.to_s
|
21
|
+
parts = original_key.split(".").select(&:present?)
|
22
|
+
self.operator = parts.pop
|
23
|
+
self.column = parts.pop
|
24
|
+
self.association = parts.pop || "self"
|
25
|
+
self.namespace = parts.pop || "default"
|
26
|
+
key
|
27
|
+
end
|
28
|
+
|
29
|
+
def key
|
30
|
+
"#{namespace}.#{association}.#{column}.#{operator}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def association=(new_association)
|
34
|
+
@association = new_association == "self" ? table_name : new_association
|
35
|
+
end
|
36
|
+
|
37
|
+
def is_base_query_component?
|
38
|
+
association == table_name
|
39
|
+
end
|
40
|
+
|
41
|
+
def operator_class
|
42
|
+
"::ActiveRecordSeek::Operators::#{operator.camelcase}Operator".constantize
|
43
|
+
end
|
44
|
+
|
45
|
+
def apply(query)
|
46
|
+
Middleware.middleware.each do |middleware|
|
47
|
+
action = middleware.call(self)
|
48
|
+
case action
|
49
|
+
when :skip then return query
|
50
|
+
else # no action
|
51
|
+
end
|
52
|
+
end
|
53
|
+
operator_class.new(component: self).apply(query)
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ActiveRecordSeek
|
2
|
+
module Concerns
|
3
|
+
module ActiveRecordConcern
|
4
|
+
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
include ActiveRecordSeek::Scopes::SeekScope::ActiveRecordScopeConcern
|
9
|
+
include ActiveRecordSeek::Scopes::SeekOrScope::ActiveRecordScopeConcern
|
10
|
+
end
|
11
|
+
|
12
|
+
class_methods do
|
13
|
+
def to_seek_query
|
14
|
+
::ActiveRecordSeek::Query.new(active_record_query: all)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module ActiveRecordSeek
|
2
|
+
module Concerns
|
3
|
+
module InstanceVariableConcern
|
4
|
+
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
def initialize(variables = {})
|
8
|
+
set(variables)
|
9
|
+
after_initialize
|
10
|
+
end
|
11
|
+
|
12
|
+
def after_initialize
|
13
|
+
# noop
|
14
|
+
end
|
15
|
+
|
16
|
+
def set(params = {})
|
17
|
+
params.each { |key, value| send("#{key}=", value) }
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def instance_variable_reset(variable, &block)
|
22
|
+
if instance_variable_defined?(variable)
|
23
|
+
remove_instance_variable(variable)
|
24
|
+
true
|
25
|
+
else
|
26
|
+
false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def instance_variable_yield(variable)
|
31
|
+
if instance_variable_defined?(variable)
|
32
|
+
value = instance_variable_get(variable)
|
33
|
+
yield(value)
|
34
|
+
value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ActiveRecordSeek
|
2
|
+
class Middleware
|
3
|
+
|
4
|
+
attr_accessor(*%w[ name middleware_block ])
|
5
|
+
|
6
|
+
def initialize(name:, &middleware_block)
|
7
|
+
raise(ArgumentError, "#{self.class} expects a block") if !middleware_block
|
8
|
+
self.name = name.to_s
|
9
|
+
self.middleware_block = middleware_block
|
10
|
+
self.class.middleware.push(self)
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(*params, &block)
|
14
|
+
middleware_block.call(*params, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def middleware
|
19
|
+
@middleware ||= []
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module ActiveRecordSeek
|
2
|
+
module Operators
|
3
|
+
class BaseOperator
|
4
|
+
|
5
|
+
include Concerns::InstanceVariableConcern
|
6
|
+
|
7
|
+
attr_accessor(*%w[ component query ])
|
8
|
+
attr_writer(*%w[ arel_table arel_column arel_value ])
|
9
|
+
|
10
|
+
def arel_table
|
11
|
+
query.arel_table
|
12
|
+
end
|
13
|
+
|
14
|
+
def arel_column
|
15
|
+
arel_table[component.column]
|
16
|
+
end
|
17
|
+
|
18
|
+
def arel_value
|
19
|
+
component.value
|
20
|
+
end
|
21
|
+
|
22
|
+
def arel_operation
|
23
|
+
arel_column.send(component.operator, arel_value)
|
24
|
+
end
|
25
|
+
|
26
|
+
def apply(query)
|
27
|
+
set(query: query)
|
28
|
+
query.where(arel_operation)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
%w[
|
34
|
+
between
|
35
|
+
eq
|
36
|
+
eq_all
|
37
|
+
eq_any
|
38
|
+
gt
|
39
|
+
gt_all
|
40
|
+
gt_any
|
41
|
+
gteq
|
42
|
+
gteq_all
|
43
|
+
gteq_any
|
44
|
+
in
|
45
|
+
in_all
|
46
|
+
in_any
|
47
|
+
lt
|
48
|
+
lt_all
|
49
|
+
lt_any
|
50
|
+
lteq
|
51
|
+
lteq_all
|
52
|
+
lteq_any
|
53
|
+
not_between
|
54
|
+
not_eq
|
55
|
+
not_eq_all
|
56
|
+
not_eq_any
|
57
|
+
not_in
|
58
|
+
not_in_all
|
59
|
+
not_in_any
|
60
|
+
].each do |operator|
|
61
|
+
const_set("#{operator.camelcase}Operator", Class.new(BaseOperator))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module ActiveRecordSeek
|
2
|
+
module Operators
|
3
|
+
class CiBaseOperator < BaseOperator
|
4
|
+
|
5
|
+
def ci_arel_column
|
6
|
+
arel_column.lower
|
7
|
+
end
|
8
|
+
|
9
|
+
def ci_arel_value(value = arel_value)
|
10
|
+
if value.is_a?(Array)
|
11
|
+
value.map { |v| ci_arel_value(v) }
|
12
|
+
elsif value.is_a?(String)
|
13
|
+
arel_table.lower(value)
|
14
|
+
else
|
15
|
+
value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def arel_operation
|
20
|
+
ci_arel_column.send(component.operator, ci_arel_value)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
%w[
|
26
|
+
eq
|
27
|
+
eq_all
|
28
|
+
eq_any
|
29
|
+
gt
|
30
|
+
gt_all
|
31
|
+
gt_any
|
32
|
+
gteq
|
33
|
+
gteq_all
|
34
|
+
gteq_any
|
35
|
+
in
|
36
|
+
in_all
|
37
|
+
in_any
|
38
|
+
lt
|
39
|
+
lt_all
|
40
|
+
lt_any
|
41
|
+
lteq
|
42
|
+
lteq_all
|
43
|
+
lteq_any
|
44
|
+
not_eq
|
45
|
+
not_eq_all
|
46
|
+
not_eq_any
|
47
|
+
not_in
|
48
|
+
not_in_all
|
49
|
+
not_in_any
|
50
|
+
].to_h do |operator|
|
51
|
+
[
|
52
|
+
operator.gsub(/\A(not_)?/, '\1ci_'),
|
53
|
+
operator,
|
54
|
+
]
|
55
|
+
end.each do |seek_operator, arel_operator|
|
56
|
+
operator_class = Class.new(CiBaseOperator) do
|
57
|
+
define_method(:arel_operation) do
|
58
|
+
ci_arel_column.send(arel_operator, ci_arel_value)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
const_set("#{seek_operator.to_s.camelcase}Operator", operator_class)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module ActiveRecordSeek
|
2
|
+
module Operators
|
3
|
+
class CiMatchesBaseOperator < CiBaseOperator
|
4
|
+
|
5
|
+
end
|
6
|
+
|
7
|
+
{
|
8
|
+
ci_like: :matches,
|
9
|
+
ci_like_all: :matches_all,
|
10
|
+
ci_like_any: :matches_any,
|
11
|
+
not_ci_like: :does_not_match,
|
12
|
+
not_ci_like_all: :does_not_match_all,
|
13
|
+
not_ci_like_any: :does_not_match_any,
|
14
|
+
}.each do |seek_operator, arel_operator|
|
15
|
+
operator_class = Class.new(CiMatchesBaseOperator) do
|
16
|
+
define_method(:arel_operation) do
|
17
|
+
operation =
|
18
|
+
# manually LOWER for adapters without ILIKE support
|
19
|
+
if component.adapter_name.in?(%w[ SQLite Mysql2 ])
|
20
|
+
ci_arel_column.send(arel_operator, ci_arel_value)
|
21
|
+
else
|
22
|
+
arel_column.send(arel_operator, arel_value)
|
23
|
+
end
|
24
|
+
# switch to ILIKE if supported
|
25
|
+
operation.each do |operation_part|
|
26
|
+
if operation_part.class.in?([Arel::Nodes::Matches, Arel::Nodes::DoesNotMatch])
|
27
|
+
operation_part.case_sensitive = false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
operation
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
const_set("#{seek_operator.to_s.camelcase}Operator", operator_class)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ActiveRecordSeek
|
2
|
+
module Operators
|
3
|
+
class CiRegexpBaseOperator < BaseOperator
|
4
|
+
|
5
|
+
end
|
6
|
+
|
7
|
+
{
|
8
|
+
ci_regexp: :matches_regexp,
|
9
|
+
ci_regexp_all: :matches_regexp,
|
10
|
+
ci_regexp_any: :matches_regexp,
|
11
|
+
not_ci_regexp: :does_not_match_regexp,
|
12
|
+
not_ci_regexp_all: :does_not_match_regexp,
|
13
|
+
not_ci_regexp_any: :does_not_match_regexp,
|
14
|
+
}.each do |seek_operator, arel_operator|
|
15
|
+
operator_class = Class.new(RegexpBaseOperator) do
|
16
|
+
define_method(:arel_operation) do
|
17
|
+
operation = nil
|
18
|
+
# must manually define _all and _any operations because arel only has matches_regexp
|
19
|
+
if seek_operator =~ /_all\z/
|
20
|
+
arel_value.each do |and_arel_value|
|
21
|
+
and_operation = arel_column.send(arel_operator, and_arel_value)
|
22
|
+
operation = operation ? operation.and(and_operation) : and_operation
|
23
|
+
end
|
24
|
+
elsif seek_operator =~ /_any\z/
|
25
|
+
arel_value.each do |or_arel_value|
|
26
|
+
or_operation = arel_column.send(arel_operator, or_arel_value)
|
27
|
+
operation = operation ? operation.or(or_operation) : or_operation
|
28
|
+
end
|
29
|
+
else
|
30
|
+
operation = arel_column.send(arel_operator, arel_value)
|
31
|
+
end
|
32
|
+
# switch to ~ operator
|
33
|
+
operation.each do |operation_part|
|
34
|
+
if operation_part.class.in?([Arel::Nodes::Regexp, Arel::Nodes::NotRegexp])
|
35
|
+
operation_part.case_sensitive = false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
operation
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
const_set("#{seek_operator.to_s.camelcase}Operator", operator_class)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ActiveRecordSeek
|
2
|
+
module Operators
|
3
|
+
class MatchesBaseOperator < BaseOperator
|
4
|
+
|
5
|
+
end
|
6
|
+
|
7
|
+
{
|
8
|
+
like: :matches,
|
9
|
+
like_all: :matches_all,
|
10
|
+
like_any: :matches_any,
|
11
|
+
not_like: :does_not_match,
|
12
|
+
not_like_all: :does_not_match_all,
|
13
|
+
not_like_any: :does_not_match_any,
|
14
|
+
}.each do |seek_operator, arel_operator|
|
15
|
+
operator_class = Class.new(MatchesBaseOperator) do
|
16
|
+
define_method(:arel_operation) do
|
17
|
+
operation = arel_column.send(arel_operator, arel_value)
|
18
|
+
# switch to LIKE operator
|
19
|
+
operation.each do |operation_part|
|
20
|
+
if operation_part.class.in?([Arel::Nodes::Matches, Arel::Nodes::DoesNotMatch])
|
21
|
+
operation_part.case_sensitive = true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
operation
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
const_set("#{seek_operator.to_s.camelcase}Operator", operator_class)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ActiveRecordSeek
|
2
|
+
module Operators
|
3
|
+
class RegexpBaseOperator < BaseOperator
|
4
|
+
|
5
|
+
end
|
6
|
+
|
7
|
+
{
|
8
|
+
regexp: :matches_regexp,
|
9
|
+
regexp_all: :matches_regexp,
|
10
|
+
regexp_any: :matches_regexp,
|
11
|
+
not_regexp: :does_not_match_regexp,
|
12
|
+
not_regexp_all: :does_not_match_regexp,
|
13
|
+
not_regexp_any: :does_not_match_regexp,
|
14
|
+
}.each do |seek_operator, arel_operator|
|
15
|
+
operator_class = Class.new(RegexpBaseOperator) do
|
16
|
+
define_method(:arel_operation) do
|
17
|
+
operation = nil
|
18
|
+
# must manually define _all and _any operations because arel only has matches_regexp
|
19
|
+
if seek_operator =~ /_all\z/
|
20
|
+
arel_value.each do |and_arel_value|
|
21
|
+
and_operation = arel_column.send(arel_operator, and_arel_value)
|
22
|
+
operation = operation ? operation.and(and_operation) : and_operation
|
23
|
+
end
|
24
|
+
elsif seek_operator =~ /_any\z/
|
25
|
+
arel_value.each do |or_arel_value|
|
26
|
+
or_operation = arel_column.send(arel_operator, or_arel_value)
|
27
|
+
operation = operation ? operation.or(or_operation) : or_operation
|
28
|
+
end
|
29
|
+
else
|
30
|
+
operation = arel_column.send(arel_operator, arel_value)
|
31
|
+
end
|
32
|
+
# switch to ~ operator
|
33
|
+
operation.each do |operation_part|
|
34
|
+
if operation_part.class.in?([Arel::Nodes::Regexp, Arel::Nodes::NotRegexp])
|
35
|
+
operation_part.case_sensitive = true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
operation
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
const_set("#{seek_operator.to_s.camelcase}Operator", operator_class)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|