plain_query 0.1.1 → 0.2.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 +4 -4
- data/README.md +21 -23
- data/lib/plain_query/builder.rb +22 -0
- data/lib/plain_query/builder_options.rb +17 -0
- data/lib/plain_query/class_methods.rb +15 -0
- data/lib/plain_query/instance_methods.rb +53 -0
- data/lib/plain_query/version.rb +4 -2
- data/lib/plain_query.rb +32 -2
- data/plain_query.gemspec +2 -2
- metadata +11 -6
- data/lib/plain_query/logic.rb +0 -74
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 70c15cb5669c0f7794a193a259ba966f97f6017fafa8db1d6b97ce63d4e2ef17
|
4
|
+
data.tar.gz: 37d7a7c0e1109f138a0e9860dd956002b6b68de6eb6b54cac501b62783b01bff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d24c959954d7cf9a25fb512ab3c603c46a168891e84a120c980fda12675e398a81af9362cd1cc203b3f97342f611b7446663277ad793fb8f03fe6d01045d808c
|
7
|
+
data.tar.gz: 9cfb9811e1c2bd1ff8c8832481f4b1d084da6526c6562ef8547f4b48759ae669d58cabe00e1a4d97fdf9f6248e0f53d2c4c1a23c58990068bf30289e6bda1658
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# PlainQuery
|
1
|
+
# PlainQuery 
|
2
2
|
|
3
3
|
PlainQuery is a simple gem that helps you write clear and flexible query objects.
|
4
4
|
Main task for this gem is performing complex querying on active record relation, make it intuitive.
|
@@ -32,26 +32,22 @@ Or install it yourself as:
|
|
32
32
|
|
33
33
|
## Usage
|
34
34
|
### Setting up a query object
|
35
|
-
For setting up a query object you need to
|
35
|
+
For setting up a query object you need to include PlainQuery::Base into your your query class.
|
36
36
|
Then you need to describe query steps by using query_step method.
|
37
37
|
Query steps perform in writing order.
|
38
38
|
|
39
39
|
```rb
|
40
|
-
class UsersQuery
|
41
|
-
model User
|
40
|
+
class UsersQuery
|
41
|
+
include PlainQuery::Base(model: User)
|
42
42
|
|
43
43
|
query_step :filter_by_activity, if: -> { options[:only_active] }
|
44
|
-
query_step :filter_by_phone_presence
|
44
|
+
query_step :filter_by_phone_presence, query: UsersWithPhoneQuery
|
45
45
|
query_step :order_by_name
|
46
46
|
|
47
47
|
def filter_by_activity
|
48
48
|
relation.where(active: true)
|
49
49
|
end
|
50
50
|
|
51
|
-
def filter_by_phone_presence
|
52
|
-
relation.where.not(phone: nil)
|
53
|
-
end
|
54
|
-
|
55
51
|
def order_by_name
|
56
52
|
relation.order(name: :asc)
|
57
53
|
end
|
@@ -67,11 +63,11 @@ users = UsersQuery.call(User.all, only_active: true)
|
|
67
63
|
Query object implements `#call` method with two arguments:
|
68
64
|
|
69
65
|
`relation` - Base scope which will be mutated inside query object. (`User.all` in example).
|
70
|
-
If you dont pass it - will be used default scope from model declaration
|
66
|
+
If you dont pass it - will be used default scope from model declaration from PlainQuery::Base module include `include PlainQuery::Base(model: User)`.
|
71
67
|
|
72
68
|
`options` - Any data which will be used inside query steps or execution conditions. (`only_active: true` in example).
|
73
69
|
|
74
|
-
Query object returns scope builded by query steps execution.
|
70
|
+
Query object returns scope builded by ordered query steps execution.
|
75
71
|
|
76
72
|
### query_step
|
77
73
|
`query_step` is a main part of query building.
|
@@ -81,13 +77,15 @@ It declares which query change method will be executed, condition of execution a
|
|
81
77
|
It has several arguments:
|
82
78
|
|
83
79
|
```rb
|
84
|
-
query_step STEP_NAME,
|
80
|
+
query_step STEP_NAME, EXECUTION_OPTIONS
|
85
81
|
```
|
86
82
|
|
87
83
|
`STEP_NAME` is a name of method which will be executed.
|
88
84
|
|
89
|
-
`
|
90
|
-
|
85
|
+
`EXECUTION_OPTIONS` is a collection of options related with step execution.
|
86
|
+
Available two types of options:
|
87
|
+
`Condition` - allows or denieds execution of query step. Type of condition can be `if:` or `unless:`. Condition definition can be proc (lambda) or some query object method name. Result of query object method used in condition will be used as a boolean value.
|
88
|
+
`Nested query` - realises DI for current query object by `query` key. It can execute any class which has call method and returns relation.
|
91
89
|
|
92
90
|
### Using in Active Record model scope.
|
93
91
|
First of all you need to set correct model name inside query object.
|
@@ -95,18 +93,12 @@ First of all you need to set correct model name inside query object.
|
|
95
93
|
It uses for correct base scope building without passing relation to query object.
|
96
94
|
|
97
95
|
```rb
|
98
|
-
model User
|
99
|
-
```
|
100
|
-
|
101
|
-
```rb
|
102
|
-
class User < ActiveRecord::Base
|
103
|
-
scope :active_clients, ActiveClientsQuery
|
104
|
-
end
|
96
|
+
include PlainQuery::Base(model: User)
|
105
97
|
```
|
106
98
|
|
107
99
|
```rb
|
108
|
-
class ActiveClientsQuery
|
109
|
-
model User
|
100
|
+
class ActiveClientsQuery
|
101
|
+
include PlainQuery::Base(model: User)
|
110
102
|
|
111
103
|
query_step :filter_by_activity
|
112
104
|
query_step :filter_by_role
|
@@ -121,6 +113,12 @@ class ActiveClientsQuery < PlainQuery
|
|
121
113
|
end
|
122
114
|
```
|
123
115
|
|
116
|
+
```rb
|
117
|
+
class User < ActiveRecord::Base
|
118
|
+
scope :active_clients, ActiveClientsQuery
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
124
122
|
And then you can use scope from model:
|
125
123
|
|
126
124
|
```rb
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'plain_query/builder_options'
|
4
|
+
require 'plain_query/class_methods'
|
5
|
+
require 'plain_query/instance_methods'
|
6
|
+
require 'plain_query/version'
|
7
|
+
|
8
|
+
module PlainQuery
|
9
|
+
class Builder < Module
|
10
|
+
attr_reader :builder_options
|
11
|
+
|
12
|
+
def initialize(model: nil)
|
13
|
+
@builder_options = BuilderOptions.new(model: model)
|
14
|
+
end
|
15
|
+
|
16
|
+
def included(klass)
|
17
|
+
klass.extend(builder_options)
|
18
|
+
klass.extend(ClassMethods)
|
19
|
+
klass.include(InstanceMethods)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlainQuery
|
4
|
+
class BuilderOptions < Module
|
5
|
+
def initialize(model: nil)
|
6
|
+
define_model(model)
|
7
|
+
end
|
8
|
+
|
9
|
+
def define_model(model)
|
10
|
+
module_exec(model) do |model_name|
|
11
|
+
define_method(:model) do
|
12
|
+
model_name
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module PlainQuery
|
2
|
+
module ClassMethods
|
3
|
+
def call(relation = model&.all, options = {})
|
4
|
+
new(relation, options).exec_query
|
5
|
+
end
|
6
|
+
|
7
|
+
def steps
|
8
|
+
@steps ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
def query_step(step_name, params = {})
|
12
|
+
steps << [step_name, params]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlainQuery
|
4
|
+
module InstanceMethods
|
5
|
+
attr_reader :relation, :options
|
6
|
+
|
7
|
+
def initialize(relation, options)
|
8
|
+
@relation = relation
|
9
|
+
@options = options
|
10
|
+
@steps = self.class.steps
|
11
|
+
|
12
|
+
# Validates initial relation format. Allowed only ActiveRecord::Relation.
|
13
|
+
unless @relation.is_a?(ActiveRecord::Relation)
|
14
|
+
raise(StandardError, 'Queries accept only ActiveRecord::Relation as input')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def exec_query
|
19
|
+
@steps.each do |step_name, params|
|
20
|
+
# Handles if condition
|
21
|
+
if (params[:if] && !exec_condition(params[:if]))
|
22
|
+
next
|
23
|
+
end
|
24
|
+
|
25
|
+
# Handles unless condition
|
26
|
+
if (params[:unless] && exec_condition(params[:unless]))
|
27
|
+
next
|
28
|
+
end
|
29
|
+
|
30
|
+
# Executes query mutation and checks that step returns ActiveRecord::Relation
|
31
|
+
mutated_query = params[:query] ? params[:query].call(@relation, @options) : send(step_name)
|
32
|
+
unless mutated_query.is_a?(ActiveRecord::Relation)
|
33
|
+
raise(StandardError, 'Scope must be ActiveRecord::Relation')
|
34
|
+
end
|
35
|
+
|
36
|
+
@relation = mutated_query
|
37
|
+
end
|
38
|
+
|
39
|
+
@relation
|
40
|
+
end
|
41
|
+
|
42
|
+
# Executes if and unless conditions, conditions must contain object method name or proc (lambda)
|
43
|
+
def exec_condition(condition)
|
44
|
+
if [String, Symbol].member?(condition.class)
|
45
|
+
!!send(condition)
|
46
|
+
elsif condition.is_a?(Proc)
|
47
|
+
!!instance_exec(&condition)
|
48
|
+
else
|
49
|
+
raise(StandardError, 'Condition must be method name or proc')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/plain_query/version.rb
CHANGED
data/lib/plain_query.rb
CHANGED
@@ -1,2 +1,32 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'plain_query/builder'
|
4
|
+
|
5
|
+
module PlainQuery
|
6
|
+
# Realises methods for scope building by execution of sequence of steps
|
7
|
+
# Query class defines query steps:
|
8
|
+
# query_step :filter_by_status, if: -> { options[:status].present? }
|
9
|
+
#
|
10
|
+
# def filter_by_status
|
11
|
+
# relation.where(status: options[:status])
|
12
|
+
# end
|
13
|
+
module Base
|
14
|
+
def self.included(klass)
|
15
|
+
klass.include(PlainQuery::Base())
|
16
|
+
end
|
17
|
+
end
|
18
|
+
# This method allows to declare model parameter associated with this query object.
|
19
|
+
# Model parameter is useful for model scopes building
|
20
|
+
# Example:
|
21
|
+
# class UsersQuery
|
22
|
+
# include PlainQuery::Base(model: User)
|
23
|
+
# ...
|
24
|
+
# end
|
25
|
+
# class User < ActiveRecord::Base
|
26
|
+
# scope :available, UsersQuery
|
27
|
+
# end
|
28
|
+
def self.Base(model: nil)
|
29
|
+
PlainQuery::Builder.new(model: model)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
data/plain_query.gemspec
CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["gl-pv"]
|
10
10
|
spec.email = ["gleeb812812@yandex.ru"]
|
11
11
|
|
12
|
-
spec.summary = %q{
|
13
|
-
spec.description = %q{PlainQuery
|
12
|
+
spec.summary = %q{PlainQuery is a simple gem that helps you write clear and flexible query objects}
|
13
|
+
spec.description = %q{PlainQuery helps in decomposing your fat ActiveRecord models and keeping your code slim and readable by extracting complex SQL queries or scopes into the separated classes that are easy to write, read and use.}
|
14
14
|
spec.homepage = "https://github.com/gl-pv/plain_query"
|
15
15
|
spec.license = "MIT"
|
16
16
|
spec.required_ruby_version = Gem::Requirement.new('>= 2.4.0')
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: plain_query
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- gl-pv
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-12-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -94,8 +94,9 @@ dependencies:
|
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: 1.4.2
|
97
|
-
description: PlainQuery
|
98
|
-
|
97
|
+
description: PlainQuery helps in decomposing your fat ActiveRecord models and keeping
|
98
|
+
your code slim and readable by extracting complex SQL queries or scopes into the
|
99
|
+
separated classes that are easy to write, read and use.
|
99
100
|
email:
|
100
101
|
- gleeb812812@yandex.ru
|
101
102
|
executables: []
|
@@ -113,7 +114,10 @@ files:
|
|
113
114
|
- bin/console
|
114
115
|
- bin/setup
|
115
116
|
- lib/plain_query.rb
|
116
|
-
- lib/plain_query/
|
117
|
+
- lib/plain_query/builder.rb
|
118
|
+
- lib/plain_query/builder_options.rb
|
119
|
+
- lib/plain_query/class_methods.rb
|
120
|
+
- lib/plain_query/instance_methods.rb
|
117
121
|
- lib/plain_query/version.rb
|
118
122
|
- plain_query.gemspec
|
119
123
|
homepage: https://github.com/gl-pv/plain_query
|
@@ -140,5 +144,6 @@ requirements: []
|
|
140
144
|
rubygems_version: 3.0.9
|
141
145
|
signing_key:
|
142
146
|
specification_version: 4
|
143
|
-
summary:
|
147
|
+
summary: PlainQuery is a simple gem that helps you write clear and flexible query
|
148
|
+
objects
|
144
149
|
test_files: []
|
data/lib/plain_query/logic.rb
DELETED
@@ -1,74 +0,0 @@
|
|
1
|
-
# Realises methods for scope building by execution of sequence of steps
|
2
|
-
# Query class defines query steps:
|
3
|
-
# query_step :filter_by_status, if: -> { options[:status].present? }
|
4
|
-
#
|
5
|
-
# def filter_by_status
|
6
|
-
# relation.where(status: options[:status])
|
7
|
-
# end
|
8
|
-
class PlainQuery
|
9
|
-
attr_reader :relation, :options
|
10
|
-
|
11
|
-
def initialize(relation, options)
|
12
|
-
@relation = relation
|
13
|
-
@options = options
|
14
|
-
@steps = self.class.steps
|
15
|
-
|
16
|
-
# Validates initial relation format. Allowed only ActiveRecord::Relation.
|
17
|
-
unless relation.is_a?(ActiveRecord::Relation)
|
18
|
-
raise(RelationRequired, 'Queries accept only ActiveRecord::Relation as input')
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def exec_query
|
23
|
-
@steps.each do |step_name, params|
|
24
|
-
# Handles if condition
|
25
|
-
if (params[:if] && !exec_condition(params[:if]))
|
26
|
-
next
|
27
|
-
end
|
28
|
-
|
29
|
-
# Handles unless condition
|
30
|
-
if (params[:unless] && exec_condition(params[:unless]))
|
31
|
-
next
|
32
|
-
end
|
33
|
-
|
34
|
-
# Executes query mutation and checks that step returns ActiveRecord::Relation
|
35
|
-
mutated_query = send(step_name)
|
36
|
-
unless mutated_query.is_a?(ActiveRecord::Relation)
|
37
|
-
raise(RelationIsIncorrect, 'Scope must be ActiveRecord::Relation')
|
38
|
-
end
|
39
|
-
|
40
|
-
@relation = mutated_query
|
41
|
-
end
|
42
|
-
|
43
|
-
@relation
|
44
|
-
end
|
45
|
-
|
46
|
-
# Executes if and unless conditions, conditions must contain object method name or proc (lambda)
|
47
|
-
def exec_condition(condition)
|
48
|
-
if [String, Symbol].member?(condition.class)
|
49
|
-
!!send(condition)
|
50
|
-
elsif condition.is_a?(Proc)
|
51
|
-
!!instance_exec(&condition)
|
52
|
-
else
|
53
|
-
raise(ConditionFormatIsIncorrect, 'Condition must be method name or proc')
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
class << self
|
58
|
-
def call(relation = @model&.all, options = {})
|
59
|
-
new(relation, options).exec_query
|
60
|
-
end
|
61
|
-
|
62
|
-
def query_step(step_name, params = {})
|
63
|
-
steps << [step_name, params]
|
64
|
-
end
|
65
|
-
|
66
|
-
def steps
|
67
|
-
@steps ||= []
|
68
|
-
end
|
69
|
-
|
70
|
-
def model(model)
|
71
|
-
@model = model
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|