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,68 @@
|
|
1
|
+
module ActiveRecordSeek
|
2
|
+
class Query
|
3
|
+
|
4
|
+
include Concerns::InstanceVariableConcern
|
5
|
+
|
6
|
+
delegate(*%w[ table_name arel_table reflect_on_association ], to: :model)
|
7
|
+
attr_reader(*%w[ active_record_query ])
|
8
|
+
|
9
|
+
def active_record_query=(new_active_record_query)
|
10
|
+
instance_variable_reset(:@where_sql)
|
11
|
+
@active_record_query = new_active_record_query
|
12
|
+
end
|
13
|
+
alias_method(:to_active_record_query, :active_record_query)
|
14
|
+
|
15
|
+
def to_seek_query
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def model
|
20
|
+
to_active_record_query.klass
|
21
|
+
end
|
22
|
+
|
23
|
+
def adapter_name
|
24
|
+
model.connection.adapter_name
|
25
|
+
end
|
26
|
+
|
27
|
+
def arel_column(column_name)
|
28
|
+
arel_table[column_name]
|
29
|
+
end
|
30
|
+
|
31
|
+
def merge_where_sql(other_query)
|
32
|
+
to_active_record_query.where(other_query.to_seek_query.to_where_sql(enclose_with_parentheses: false))
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_where_sql(enclose_with_parentheses: true)
|
36
|
+
if !instance_variable_defined?(:@where_sql)
|
37
|
+
@where_sql = active_record_query.reorder(nil).to_sql.split(" WHERE ", 2)[1].to_s.strip
|
38
|
+
end
|
39
|
+
return "" if @where_sql.blank?
|
40
|
+
enclose_with_parentheses ? "(#{@where_sql})" : @where_sql
|
41
|
+
end
|
42
|
+
|
43
|
+
def has_where_sql?
|
44
|
+
to_where_sql.present?
|
45
|
+
end
|
46
|
+
|
47
|
+
def apply(components)
|
48
|
+
to_active_record_query.seek_or(self) do |this|
|
49
|
+
components.group_by(&:namespace).each do |namespace, namespace_components|
|
50
|
+
case namespace
|
51
|
+
when "unscoped"
|
52
|
+
namespace_components.each do |component|
|
53
|
+
add_query { component.apply(self) }
|
54
|
+
end
|
55
|
+
else
|
56
|
+
add_query do |namespace_query|
|
57
|
+
namespace_components.each do |component|
|
58
|
+
namespace_query = component.apply(namespace_query)
|
59
|
+
end
|
60
|
+
namespace_query
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module ActiveRecordSeek
|
2
|
+
module Scopes
|
3
|
+
class AssociationScope < BaseScope
|
4
|
+
|
5
|
+
attr_accessor(*%w[ components base_query association ])
|
6
|
+
|
7
|
+
class JumpPlan
|
8
|
+
include Concerns::InstanceVariableConcern
|
9
|
+
|
10
|
+
attr_accessor(*%w[ reflection ])
|
11
|
+
delegate(:each, to: :jumps)
|
12
|
+
|
13
|
+
def after_initialize
|
14
|
+
add_reflection_jump(reflection)
|
15
|
+
end
|
16
|
+
|
17
|
+
def jumps
|
18
|
+
@jumps ||= []
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_jump(model, association)
|
22
|
+
jumps.push(Jump.new(model: model, association: association))
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_reflection_jump(jump_reflection)
|
26
|
+
# used to protect against infinite loop
|
27
|
+
association = jump_reflection.source_reflection.name
|
28
|
+
through_ref = jump_reflection.through_reflection
|
29
|
+
if through_ref
|
30
|
+
throughception = through_ref.klass.reflect_on_association(association)
|
31
|
+
# check for throughception (jump through another association)
|
32
|
+
# normal example:
|
33
|
+
# GroupProperty.has_many(:member_groups, through: :group)
|
34
|
+
# GroupProperty.has_many(:members, through: :member_groups)
|
35
|
+
# throughception example:
|
36
|
+
# GroupProperty.has_many(:members, through: :group)
|
37
|
+
if throughception && throughception.through_reflection
|
38
|
+
add_reflection_jump(throughception) # add all throughception jumps
|
39
|
+
add_reflection_jump(through_ref) # continue
|
40
|
+
else
|
41
|
+
add_jump(through_ref.klass, association)
|
42
|
+
add_reflection_jump(through_ref)
|
43
|
+
end
|
44
|
+
else
|
45
|
+
add_jump(jump_reflection.active_record, jump_reflection.name)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Jump
|
51
|
+
include Concerns::InstanceVariableConcern
|
52
|
+
|
53
|
+
attr_accessor(*%w[ model association query ])
|
54
|
+
|
55
|
+
def reflection
|
56
|
+
model.reflect_on_association(association)
|
57
|
+
end
|
58
|
+
|
59
|
+
def primary_key
|
60
|
+
reflection.macro == :belongs_to ? reflection.foreign_key : reflection.active_record_primary_key
|
61
|
+
end
|
62
|
+
|
63
|
+
def foreign_key
|
64
|
+
reflection.macro == :belongs_to ? reflection.active_record_primary_key : reflection.foreign_key
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_s
|
68
|
+
"#{model}.where(#{primary_key} => #{query.model}.#{foreign_key})"
|
69
|
+
end
|
70
|
+
|
71
|
+
def apply
|
72
|
+
model.unscoped.where(primary_key => query.to_active_record_query.select(foreign_key)).to_seek_query
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Group => Member
|
77
|
+
# jump through MemberGroup.group
|
78
|
+
# jump base Member.member_groups
|
79
|
+
# MemberGroup.where_group_id(in: Group.select_id)
|
80
|
+
def apply
|
81
|
+
association_reflection = base_query.model.reflect_on_association(association)
|
82
|
+
if !association_reflection
|
83
|
+
raise(
|
84
|
+
ArgumentError,
|
85
|
+
"#{base_query.model} does not have an association with the name: #{association.inspect}"
|
86
|
+
)
|
87
|
+
end
|
88
|
+
association_query = association_reflection.klass.unscoped.to_seek_query
|
89
|
+
association_query = association_query.apply(components).to_seek_query
|
90
|
+
JumpPlan.new(reflection: association_reflection).each do |jump|
|
91
|
+
association_query = jump.set(query: association_query).apply
|
92
|
+
end
|
93
|
+
association_query
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module ActiveRecordSeek
|
2
|
+
module Scopes
|
3
|
+
class SeekOrScope < BaseScope
|
4
|
+
|
5
|
+
attr_accessor(*%w[ context_block ])
|
6
|
+
|
7
|
+
def apply(query, *context_arguments, &context_block)
|
8
|
+
query = query.to_seek_query
|
9
|
+
context = Context.new(query.model)
|
10
|
+
context.instance_exec(*context_arguments, &context_block)
|
11
|
+
context.apply(query)
|
12
|
+
end
|
13
|
+
|
14
|
+
class Context
|
15
|
+
attr_accessor(*%w[ model queries ])
|
16
|
+
|
17
|
+
def initialize(model)
|
18
|
+
self.model = model
|
19
|
+
self.queries = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_query(&block)
|
23
|
+
unscoped_query = model.unscoped
|
24
|
+
query = unscoped_query.instance_exec(unscoped_query, &block).to_seek_query
|
25
|
+
queries.push(query) if query.has_where_sql?
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
# combine queries into single OR clause
|
30
|
+
def apply(query)
|
31
|
+
queries_sql = queries.map do |context_query|
|
32
|
+
context_query.to_where_sql(enclose_with_parentheses: queries.size > 1)
|
33
|
+
end.join(" OR ")
|
34
|
+
query.to_active_record_query.where(queries_sql).to_seek_query
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module ActiveRecordScopeConcern
|
39
|
+
|
40
|
+
extend ActiveSupport::Concern
|
41
|
+
|
42
|
+
class_methods do
|
43
|
+
def seek_or(*params, &block)
|
44
|
+
SeekOrScope.new.apply(all, *params, &block).to_active_record_query
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module ActiveRecordSeek
|
2
|
+
module Scopes
|
3
|
+
class SeekScope < BaseScope
|
4
|
+
|
5
|
+
attr_reader(*%w[ seek_query active_record_query components components_hash ])
|
6
|
+
|
7
|
+
def query=(new_query)
|
8
|
+
@seek_query = new_query.to_seek_query
|
9
|
+
@active_record_query = @seek_query.to_active_record_query
|
10
|
+
@seek_query
|
11
|
+
end
|
12
|
+
|
13
|
+
def components_hash=(new_components_hash)
|
14
|
+
@components = new_components_hash.stringify_keys.map do |key, value|
|
15
|
+
Component.new(base_query: self, key: key, value: value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def apply
|
20
|
+
components_for_base_query = components.select(&:is_base_query_component?)
|
21
|
+
components_by_association = components.reject(&:is_base_query_component?).group_by(&:association)
|
22
|
+
self.query = seek_query.apply(components_for_base_query)
|
23
|
+
self.query = active_record_query.seek_or(self) do |this|
|
24
|
+
components_by_association.each do |association, association_components|
|
25
|
+
add_query do
|
26
|
+
AssociationScope.new(
|
27
|
+
base_query: to_seek_query,
|
28
|
+
association: association,
|
29
|
+
components: association_components,
|
30
|
+
).apply
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
seek_query
|
35
|
+
end
|
36
|
+
|
37
|
+
module ActiveRecordScopeConcern
|
38
|
+
|
39
|
+
extend ActiveSupport::Concern
|
40
|
+
|
41
|
+
class_methods do
|
42
|
+
def seek(components_hash = {}, &block)
|
43
|
+
raise(ArgumentError, "#{self.class}#seek does not accept a block") if block
|
44
|
+
SeekScope.new(
|
45
|
+
query: all,
|
46
|
+
components_hash: components_hash,
|
47
|
+
).apply.to_active_record_query
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: active_record_seek
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Griffith Chaffee
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-03-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '12'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '12'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activerecord
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: factory_bot
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '5'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '5'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: byebug
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '11'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '11'
|
83
|
+
description: Build complex ActiveRecord queries using hash syntax. Includes support
|
84
|
+
for Arel operators, OR queries, and assocition subqueries.
|
85
|
+
email:
|
86
|
+
- griffithchaffee@gmail.com
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- ".ruby-version"
|
93
|
+
- ".travis.yml"
|
94
|
+
- CODE_OF_CONDUCT.md
|
95
|
+
- Gemfile
|
96
|
+
- Gemfile.lock
|
97
|
+
- LICENSE.txt
|
98
|
+
- README.md
|
99
|
+
- Rakefile
|
100
|
+
- active_record_seek.gemspec
|
101
|
+
- bin/console
|
102
|
+
- bin/setup
|
103
|
+
- gemfiles/mysql.gemfile
|
104
|
+
- gemfiles/mysql.gemfile.lock
|
105
|
+
- gemfiles/postgresql.gemfile
|
106
|
+
- gemfiles/postgresql.gemfile.lock
|
107
|
+
- gemfiles/sqlite.gemfile
|
108
|
+
- gemfiles/sqlite.gemfile.lock
|
109
|
+
- lib/active_record_seek.rb
|
110
|
+
- lib/active_record_seek/component.rb
|
111
|
+
- lib/active_record_seek/concerns/active_record_concern.rb
|
112
|
+
- lib/active_record_seek/concerns/instance_variable_concern.rb
|
113
|
+
- lib/active_record_seek/middleware.rb
|
114
|
+
- lib/active_record_seek/operators/base_operator.rb
|
115
|
+
- lib/active_record_seek/operators/ci_base_operator.rb
|
116
|
+
- lib/active_record_seek/operators/ci_matches_base_operator.rb
|
117
|
+
- lib/active_record_seek/operators/ci_regexp_base_operator.rb
|
118
|
+
- lib/active_record_seek/operators/matches_base_operator.rb
|
119
|
+
- lib/active_record_seek/operators/regexp_base_operator.rb
|
120
|
+
- lib/active_record_seek/query.rb
|
121
|
+
- lib/active_record_seek/scopes/association_scope.rb
|
122
|
+
- lib/active_record_seek/scopes/base_scope.rb
|
123
|
+
- lib/active_record_seek/scopes/seek_or_scope.rb
|
124
|
+
- lib/active_record_seek/scopes/seek_scope.rb
|
125
|
+
- lib/active_record_seek/version.rb
|
126
|
+
homepage: https://github.com/griffithchaffee/active_record_seek
|
127
|
+
licenses:
|
128
|
+
- MIT
|
129
|
+
metadata: {}
|
130
|
+
post_install_message:
|
131
|
+
rdoc_options: []
|
132
|
+
require_paths:
|
133
|
+
- lib
|
134
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
requirements: []
|
145
|
+
rubygems_version: 3.0.3
|
146
|
+
signing_key:
|
147
|
+
specification_version: 4
|
148
|
+
summary: Build complex ActiveRecord queries using hash syntax.
|
149
|
+
test_files: []
|