simple_query 0.1.0 → 0.3.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 +4 -4
- data/README.md +100 -2
- data/lib/simple_query/builder.rb +128 -68
- data/lib/simple_query/clauses/distinct_clause.rb +22 -0
- data/lib/simple_query/clauses/group_having_clause.rb +30 -0
- data/lib/simple_query/clauses/join_clause.rb +35 -0
- data/lib/simple_query/clauses/limit_offset_clause.rb +30 -0
- data/lib/simple_query/clauses/order_clause.rb +32 -0
- data/lib/simple_query/clauses/where_clause.rb +38 -0
- data/lib/simple_query/read_model.rb +25 -0
- data/lib/simple_query/version.rb +1 -1
- data/lib/simple_query.rb +53 -4
- metadata +18 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f15e71d4ac10ed8d359f177e46f36f12b9721da6f9f51c75d8a0571cf245aac0
|
4
|
+
data.tar.gz: 8090bd8f93899061f15a048a7c57a73e48e8805e7fee3807bb39ba91e0012b1f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 903a9d4754ed89bf718918de17d94bf09969dc5a725d933b68c101a71737d397e90a89ac3ab141b4e18f855b39a81d62a27982c207dca20e7df7f0c6d8571746
|
7
|
+
data.tar.gz: 2965ff1462a4036112ba0bfe974087c72ba5e8f9b8db15d39a5f5879394dbe074cf9942682f9b50732c61f0d25749c514d38f1e11e2c8e0414308ee676f14ba1
|
data/README.md
CHANGED
@@ -20,6 +20,30 @@ Or install it yourself as:
|
|
20
20
|
gem install simple_query
|
21
21
|
```
|
22
22
|
|
23
|
+
## Configuration
|
24
|
+
|
25
|
+
By default, `SimpleQuery` does **not** automatically patch `ActiveRecord::Base`. You can **manually** include the module in individual models or in a global initializer:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
# Manual include (per model)
|
29
|
+
class User < ActiveRecord::Base
|
30
|
+
include SimpleQuery
|
31
|
+
end
|
32
|
+
|
33
|
+
# or do it globally
|
34
|
+
ActiveRecord::Base.include(SimpleQuery)
|
35
|
+
```
|
36
|
+
If you prefer a “just works” approach (i.e., every model has `.simple_query`), you can opt in:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
# config/initializers/simple_query.rb
|
40
|
+
SimpleQuery.configure do |config|
|
41
|
+
config.auto_include_ar = true
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
This tells SimpleQuery to automatically do `ActiveRecord::Base.include(SimpleQuery)` for you.
|
46
|
+
|
23
47
|
## Usage
|
24
48
|
|
25
49
|
SimpleQuery offers an intuitive interface for building queries with joins, conditions, and aggregations. Here are some examples:
|
@@ -58,6 +82,74 @@ User.simple_query
|
|
58
82
|
.lazy_execute
|
59
83
|
```
|
60
84
|
|
85
|
+
## Custom Read Models
|
86
|
+
By default, SimpleQuery returns results as `Struct` objects for maximum speed. However, you can also define a lightweight model class for more explicit attribute handling or custom logic.
|
87
|
+
|
88
|
+
**Create a read model** inheriting from `SimpleQuery::ReadModel`:
|
89
|
+
```ruby
|
90
|
+
class MyUserReadModel < SimpleQuery::ReadModel
|
91
|
+
attribute :identifier, column: :id
|
92
|
+
attribute :full_name, column: :name
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
**Map query results** to your read model:
|
97
|
+
```ruby
|
98
|
+
results = User.simple_query
|
99
|
+
.select("users.id AS id", "users.name AS name")
|
100
|
+
.where(active: true)
|
101
|
+
.map_to(MyUserReadModel)
|
102
|
+
.execute
|
103
|
+
|
104
|
+
results.each do |user|
|
105
|
+
puts user.identifier # => user.id from the DB
|
106
|
+
puts user.full_name # => user.name from the DB
|
107
|
+
end
|
108
|
+
```
|
109
|
+
This custom read model approach provides more clarity or domain-specific logic while still being faster than typical ActiveRecord instantiation.
|
110
|
+
|
111
|
+
## Named Scopes
|
112
|
+
SimpleQuery now supports named scopes, allowing you to reuse common query logic in a style similar to ActiveRecord’s built-in scopes. To define a scope, use the simple_scope class method in your model:
|
113
|
+
```ruby
|
114
|
+
class User < ActiveRecord::Base
|
115
|
+
include SimpleQuery
|
116
|
+
|
117
|
+
simple_scope :active do
|
118
|
+
where(active: true)
|
119
|
+
end
|
120
|
+
|
121
|
+
simple_scope :admins do
|
122
|
+
where(admin: true)
|
123
|
+
end
|
124
|
+
|
125
|
+
simple_scope :by_name do |name|
|
126
|
+
where(name: name)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
```
|
130
|
+
You can then chain these scopes seamlessly with the normal SimpleQuery DSL:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
# Parameterless scopes
|
134
|
+
results = User.simple_query.active.admins.execute
|
135
|
+
|
136
|
+
# Parameterized scope
|
137
|
+
results = User.simple_query.by_name("Jane Doe").execute
|
138
|
+
|
139
|
+
# Mixing scopes with other DSL calls
|
140
|
+
results = User.simple_query
|
141
|
+
.by_name("John")
|
142
|
+
.active
|
143
|
+
.select(:id, :name)
|
144
|
+
.order(name: :asc)
|
145
|
+
.execute
|
146
|
+
```
|
147
|
+
### How It Works
|
148
|
+
|
149
|
+
Each scope block (e.g. by_name) is evaluated in the context of the SimpleQuery builder, so you can call any DSL method (where, order, etc.) inside it.
|
150
|
+
Parameterized scopes accept arguments — passed directly to the block (e.g. |name| above).
|
151
|
+
Scopes return self, so you can chain multiple scopes or mix them with standard query methods.
|
152
|
+
|
61
153
|
## Features
|
62
154
|
|
63
155
|
- Efficient query building
|
@@ -67,7 +159,10 @@ User.simple_query
|
|
67
159
|
- Aggregations
|
68
160
|
- LIMIT and OFFSET
|
69
161
|
- ORDER BY clause
|
162
|
+
- Having and Grouping
|
70
163
|
- Subqueries
|
164
|
+
- Custom Read models
|
165
|
+
- Named Scopes
|
71
166
|
|
72
167
|
## Performance
|
73
168
|
|
@@ -75,9 +170,12 @@ SimpleQuery is designed to potentially outperform standard ActiveRecord queries
|
|
75
170
|
|
76
171
|
```
|
77
172
|
🚀 Performance Results (100,000 records):
|
78
|
-
ActiveRecord Query:
|
79
|
-
SimpleQuery Execution: 0.
|
173
|
+
ActiveRecord Query: 0.47441 seconds
|
174
|
+
SimpleQuery Execution (Struct): 0.05346 seconds
|
175
|
+
SimpleQuery Execution (Read model): 0.14408 seconds
|
80
176
|
```
|
177
|
+
- The **Struct-based** approach is the fastest.
|
178
|
+
- The **Read model** approach is still significantly faster than ActiveRecord, while letting you define custom logic or domain-specific attributes.
|
81
179
|
|
82
180
|
## Development
|
83
181
|
|
data/lib/simple_query/builder.rb
CHANGED
@@ -2,21 +2,24 @@
|
|
2
2
|
|
3
3
|
module SimpleQuery
|
4
4
|
class Builder
|
5
|
-
attr_reader :model, :arel_table
|
5
|
+
attr_reader :model, :arel_table
|
6
6
|
|
7
7
|
def initialize(source)
|
8
8
|
@model = source
|
9
9
|
@arel_table = @model.arel_table
|
10
|
+
|
10
11
|
@selects = []
|
11
|
-
@wheres =
|
12
|
-
@joins =
|
13
|
-
@
|
14
|
-
@
|
15
|
-
@
|
16
|
-
@distinct_flag =
|
12
|
+
@wheres = WhereClause.new(@arel_table)
|
13
|
+
@joins = JoinClause.new
|
14
|
+
@group_having = GroupHavingClause.new(@arel_table)
|
15
|
+
@orders = OrderClause.new(@arel_table)
|
16
|
+
@limits = LimitOffsetClause.new
|
17
|
+
@distinct_flag = DistinctClause.new
|
18
|
+
|
17
19
|
@query_cache = {}
|
18
|
-
@result_struct = nil
|
19
20
|
@query_built = false
|
21
|
+
@read_model_class = nil
|
22
|
+
@result_struct = nil
|
20
23
|
end
|
21
24
|
|
22
25
|
def select(*fields)
|
@@ -26,58 +29,75 @@ module SimpleQuery
|
|
26
29
|
end
|
27
30
|
|
28
31
|
def where(condition)
|
29
|
-
@wheres.
|
32
|
+
@wheres.add(condition)
|
30
33
|
reset_query
|
31
34
|
self
|
32
35
|
end
|
33
36
|
|
34
37
|
def join(table1, table2, foreign_key:, primary_key:)
|
35
|
-
@joins
|
36
|
-
table1: arel_table(table1),
|
37
|
-
table2: arel_table(table2),
|
38
|
-
foreign_key: foreign_key,
|
39
|
-
primary_key: primary_key
|
40
|
-
}
|
38
|
+
@joins.add(table1, table2, foreign_key: foreign_key, primary_key: primary_key)
|
41
39
|
reset_query
|
42
40
|
self
|
43
41
|
end
|
44
42
|
|
45
43
|
def order(order_conditions)
|
46
|
-
@orders.
|
44
|
+
@orders.add(order_conditions)
|
47
45
|
reset_query
|
48
46
|
self
|
49
47
|
end
|
50
48
|
|
51
49
|
def limit(number)
|
52
|
-
|
53
|
-
@limits = number
|
50
|
+
@limits.with_limit(number)
|
54
51
|
reset_query
|
55
52
|
self
|
56
53
|
end
|
57
54
|
|
58
55
|
def offset(number)
|
59
|
-
|
60
|
-
@offsets = number
|
56
|
+
@limits.with_offset(number)
|
61
57
|
reset_query
|
62
58
|
self
|
63
59
|
end
|
64
60
|
|
65
61
|
def distinct
|
66
|
-
@distinct_flag
|
62
|
+
@distinct_flag.set_distinct
|
63
|
+
reset_query
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
def group(*fields)
|
68
|
+
@group_having.add_group(*fields)
|
69
|
+
reset_query
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
def having(condition)
|
74
|
+
@group_having.add_having(condition)
|
75
|
+
reset_query
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
79
|
+
def map_to(klass)
|
80
|
+
@read_model_class = klass
|
67
81
|
reset_query
|
68
82
|
self
|
69
83
|
end
|
70
84
|
|
71
85
|
def execute
|
72
86
|
records = ActiveRecord::Base.connection.select_all(cached_sql)
|
73
|
-
|
87
|
+
build_result_objects_from_rows(records)
|
74
88
|
end
|
75
89
|
|
76
90
|
def lazy_execute
|
77
91
|
Enumerator.new do |yielder|
|
78
92
|
records = ActiveRecord::Base.connection.select_all(cached_sql)
|
79
|
-
|
80
|
-
|
93
|
+
if @read_model_class
|
94
|
+
build_read_models_enumerator(records, yielder)
|
95
|
+
else
|
96
|
+
struct = result_struct(records.columns)
|
97
|
+
records.rows.each do |row_array|
|
98
|
+
yielder << struct.new(*row_array)
|
99
|
+
end
|
100
|
+
end
|
81
101
|
end
|
82
102
|
end
|
83
103
|
|
@@ -87,10 +107,11 @@ module SimpleQuery
|
|
87
107
|
@query = Arel::SelectManager.new(Arel::Table.engine)
|
88
108
|
@query.from(@arel_table)
|
89
109
|
@query.project(*(@selects.empty? ? [@arel_table[Arel.star]] : @selects))
|
90
|
-
@query.distinct if @distinct_flag
|
91
110
|
|
111
|
+
apply_distinct
|
92
112
|
apply_where_conditions
|
93
113
|
apply_joins
|
114
|
+
apply_group_and_having
|
94
115
|
apply_order_conditions
|
95
116
|
apply_limit_and_offset
|
96
117
|
|
@@ -106,78 +127,117 @@ module SimpleQuery
|
|
106
127
|
end
|
107
128
|
|
108
129
|
def cached_sql
|
109
|
-
|
130
|
+
key = [
|
131
|
+
@selects,
|
132
|
+
@wheres.conditions,
|
133
|
+
@joins.joins,
|
134
|
+
@group_having.group_fields,
|
135
|
+
@group_having.having_conditions,
|
136
|
+
@orders.orders,
|
137
|
+
@limits.limit_value,
|
138
|
+
@limits.offset_value,
|
139
|
+
@distinct_flag.use_distinct?
|
140
|
+
]
|
141
|
+
|
142
|
+
@query_cache[key] ||= build_query.to_sql
|
143
|
+
end
|
144
|
+
|
145
|
+
def build_result_objects_from_rows(records)
|
146
|
+
if @read_model_class
|
147
|
+
build_read_models_from_arrays(records)
|
148
|
+
else
|
149
|
+
struct = result_struct(records.columns)
|
150
|
+
records.rows.map { |row_array| struct.new(*row_array) }
|
151
|
+
end
|
110
152
|
end
|
111
153
|
|
112
|
-
def
|
113
|
-
|
114
|
-
|
154
|
+
def build_read_models_from_arrays(records)
|
155
|
+
columns = records.columns
|
156
|
+
column_map = columns.each_with_index.to_h
|
157
|
+
rows = records.rows
|
115
158
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
159
|
+
rows.map do |row_array|
|
160
|
+
obj = @read_model_class.allocate
|
161
|
+
@read_model_class.attributes.each do |attr_name, col_name|
|
162
|
+
idx = column_map[col_name]
|
163
|
+
obj.instance_variable_set(:"@#{attr_name}", row_array[idx]) if idx
|
164
|
+
end
|
165
|
+
obj
|
123
166
|
end
|
124
167
|
end
|
125
168
|
|
126
|
-
def
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
169
|
+
def build_read_models_enumerator(records, yielder)
|
170
|
+
columns = records.columns
|
171
|
+
column_map = columns.each_with_index.to_h
|
172
|
+
records.rows.each do |row_array|
|
173
|
+
obj = @read_model_class.allocate
|
174
|
+
@read_model_class.attributes.each do |attr_name, col_name|
|
175
|
+
idx = column_map[col_name]
|
176
|
+
obj.instance_variable_set(:"@#{attr_name}", row_array[idx]) if idx
|
177
|
+
end
|
178
|
+
yielder << obj
|
131
179
|
end
|
132
180
|
end
|
133
181
|
|
134
|
-
def
|
135
|
-
|
182
|
+
def result_struct(columns)
|
183
|
+
@result_struct ||= Struct.new(*columns.map(&:to_sym))
|
136
184
|
end
|
137
185
|
|
138
|
-
def
|
139
|
-
|
140
|
-
validate_order_direction(direction)
|
141
|
-
@arel_table[field].send(direction)
|
142
|
-
end
|
186
|
+
def apply_distinct
|
187
|
+
@distinct_flag.apply_to(@query)
|
143
188
|
end
|
144
189
|
|
145
|
-
def
|
146
|
-
|
190
|
+
def apply_where_conditions
|
191
|
+
condition = @wheres.to_arel
|
192
|
+
@query.where(condition) if condition
|
193
|
+
end
|
147
194
|
|
148
|
-
|
195
|
+
def apply_joins
|
196
|
+
@joins.apply_to(@query)
|
149
197
|
end
|
150
198
|
|
151
|
-
def
|
152
|
-
|
199
|
+
def apply_group_and_having
|
200
|
+
@group_having.apply_to(@query)
|
153
201
|
end
|
154
202
|
|
155
|
-
def
|
156
|
-
|
203
|
+
def apply_order_conditions
|
204
|
+
@orders.apply_to(@query)
|
157
205
|
end
|
158
206
|
|
159
|
-
def
|
160
|
-
|
161
|
-
records.rows.map { |row| struct.new(*row) }
|
207
|
+
def apply_limit_and_offset
|
208
|
+
@limits.apply_to(@query)
|
162
209
|
end
|
163
210
|
|
164
|
-
def
|
165
|
-
|
211
|
+
def parse_select_field(field)
|
212
|
+
case field
|
213
|
+
when Symbol
|
214
|
+
@arel_table[field]
|
215
|
+
when String
|
216
|
+
Arel.sql(field)
|
217
|
+
when Arel::Nodes::Node
|
218
|
+
field
|
219
|
+
else
|
220
|
+
raise ArgumentError, "Unsupported select field type: #{field.class}"
|
221
|
+
end
|
166
222
|
end
|
167
223
|
|
168
|
-
def
|
169
|
-
|
170
|
-
|
224
|
+
def method_missing(method_name, *args, &block)
|
225
|
+
if (scope_block = find_scope(method_name))
|
226
|
+
instance_exec(*args, &scope_block)
|
227
|
+
self
|
228
|
+
else
|
229
|
+
super
|
171
230
|
end
|
172
231
|
end
|
173
232
|
|
174
|
-
def
|
175
|
-
|
233
|
+
def respond_to_missing?(method_name, include_private = false)
|
234
|
+
!!find_scope(method_name) || super
|
176
235
|
end
|
177
236
|
|
178
|
-
def
|
179
|
-
|
180
|
-
|
237
|
+
def find_scope(method_name)
|
238
|
+
return unless model.respond_to?(:_simple_scopes)
|
239
|
+
|
240
|
+
model._simple_scopes[method_name.to_sym]
|
181
241
|
end
|
182
242
|
end
|
183
243
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SimpleQuery
|
4
|
+
class DistinctClause
|
5
|
+
def initialize
|
6
|
+
@use_distinct = false
|
7
|
+
end
|
8
|
+
|
9
|
+
def use_distinct?
|
10
|
+
@use_distinct
|
11
|
+
end
|
12
|
+
|
13
|
+
def set_distinct
|
14
|
+
@use_distinct = true
|
15
|
+
end
|
16
|
+
|
17
|
+
def apply_to(query)
|
18
|
+
query.distinct if @use_distinct
|
19
|
+
query
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SimpleQuery
|
4
|
+
class GroupHavingClause
|
5
|
+
attr_reader :group_fields, :having_conditions
|
6
|
+
|
7
|
+
def initialize(table)
|
8
|
+
@table = table
|
9
|
+
@group_fields = []
|
10
|
+
@having_conditions = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_group(*fields)
|
14
|
+
@group_fields.concat(fields.map { |f| @table[f] })
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_having(condition)
|
18
|
+
@having_conditions << condition
|
19
|
+
end
|
20
|
+
|
21
|
+
def apply_to(query)
|
22
|
+
@group_fields.each { |g| query.group(g) }
|
23
|
+
if @having_conditions.any?
|
24
|
+
combined = @having_conditions.inject { |c, a| c.and(a) }
|
25
|
+
query.having(combined)
|
26
|
+
end
|
27
|
+
query
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SimpleQuery
|
4
|
+
class JoinClause
|
5
|
+
attr_reader :joins
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@joins = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def add(table1, table2, foreign_key:, primary_key:)
|
12
|
+
@joins << {
|
13
|
+
table1: to_arel_table(table1),
|
14
|
+
table2: to_arel_table(table2),
|
15
|
+
foreign_key: foreign_key,
|
16
|
+
primary_key: primary_key
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def apply_to(query)
|
21
|
+
@joins.each do |join|
|
22
|
+
query.join(join[:table2])
|
23
|
+
.on(join[:table2][join[:foreign_key]]
|
24
|
+
.eq(join[:table1][join[:primary_key]]))
|
25
|
+
end
|
26
|
+
query
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def to_arel_table(obj)
|
32
|
+
obj.is_a?(Arel::Table) ? obj : Arel::Table.new(obj)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SimpleQuery
|
4
|
+
class LimitOffsetClause
|
5
|
+
attr_reader :limit_value, :offset_value
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@limit_value = nil
|
9
|
+
@offset_value = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def with_limit(limit)
|
13
|
+
raise ArgumentError, "LIMIT must be a positive integer" unless limit.is_a?(Integer) && limit.positive?
|
14
|
+
|
15
|
+
@limit_value = limit
|
16
|
+
end
|
17
|
+
|
18
|
+
def with_offset(offset)
|
19
|
+
raise ArgumentError, "OFFSET must be a non-negative integer" unless offset.is_a?(Integer) && offset >= 0
|
20
|
+
|
21
|
+
@offset_value = offset
|
22
|
+
end
|
23
|
+
|
24
|
+
def apply_to(query)
|
25
|
+
query.take(@limit_value) if @limit_value
|
26
|
+
query.skip(@offset_value) if @offset_value
|
27
|
+
query
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SimpleQuery
|
4
|
+
class OrderClause
|
5
|
+
attr_reader :orders
|
6
|
+
|
7
|
+
def initialize(table)
|
8
|
+
@table = table
|
9
|
+
@orders = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def add(order_conditions)
|
13
|
+
order_conditions.each do |field, direction|
|
14
|
+
validate_order_direction(direction)
|
15
|
+
@orders << @table[field].send(direction)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def apply_to(query)
|
20
|
+
@orders.each { |order_node| query.order(order_node) }
|
21
|
+
query
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def validate_order_direction(direction)
|
27
|
+
return if [:asc, :desc].include?(direction)
|
28
|
+
|
29
|
+
raise ArgumentError, "Invalid order direction: #{direction}. Use :asc or :desc."
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SimpleQuery
|
4
|
+
class WhereClause
|
5
|
+
attr_reader :conditions
|
6
|
+
|
7
|
+
def initialize(table)
|
8
|
+
@table = table
|
9
|
+
@conditions = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def add(condition)
|
13
|
+
parsed_conditions = parse_condition(condition)
|
14
|
+
@conditions.concat(parsed_conditions)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_arel
|
18
|
+
return nil if @conditions.empty?
|
19
|
+
|
20
|
+
@conditions.inject do |combined, current|
|
21
|
+
combined.and(current)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def parse_condition(condition)
|
28
|
+
case condition
|
29
|
+
when Hash
|
30
|
+
condition.map { |field, value| @table[field].eq(value) }
|
31
|
+
when Arel::Nodes::Node
|
32
|
+
[condition]
|
33
|
+
else
|
34
|
+
[Arel.sql(condition.to_s)]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SimpleQuery
|
4
|
+
class ReadModel
|
5
|
+
def self.attribute(attr_name, column: attr_name)
|
6
|
+
@attributes ||= {}
|
7
|
+
@attributes[attr_name] = column.to_s
|
8
|
+
attr_reader attr_name
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.attributes
|
12
|
+
@attributes || {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.build_from_row(row_hash)
|
16
|
+
obj = allocate
|
17
|
+
attributes.each do |attr_name, column_name|
|
18
|
+
obj.instance_variable_set(:"@#{attr_name}", row_hash[column_name])
|
19
|
+
end
|
20
|
+
obj
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize; end
|
24
|
+
end
|
25
|
+
end
|
data/lib/simple_query/version.rb
CHANGED
data/lib/simple_query.rb
CHANGED
@@ -1,16 +1,65 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/concern"
|
3
4
|
require "active_record"
|
4
|
-
|
5
|
+
|
6
|
+
require_relative "simple_query/builder"
|
7
|
+
require_relative "simple_query/read_model"
|
8
|
+
require_relative "simple_query/clauses/where_clause"
|
9
|
+
require_relative "simple_query/clauses/join_clause"
|
10
|
+
require_relative "simple_query/clauses/order_clause"
|
11
|
+
require_relative "simple_query/clauses/distinct_clause"
|
12
|
+
require_relative "simple_query/clauses/limit_offset_clause"
|
13
|
+
require_relative "simple_query/clauses/group_having_clause"
|
5
14
|
|
6
15
|
module SimpleQuery
|
7
16
|
extend ActiveSupport::Concern
|
8
17
|
|
18
|
+
class Configuration
|
19
|
+
attr_accessor :auto_include_ar
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@auto_include_ar = false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.configure
|
27
|
+
yield config
|
28
|
+
auto_include! if config.auto_include_ar
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.config
|
32
|
+
@config ||= Configuration.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.auto_include!
|
36
|
+
ActiveRecord::Base.include(SimpleQuery)
|
37
|
+
end
|
38
|
+
|
39
|
+
class_methods do
|
40
|
+
def _simple_scopes
|
41
|
+
@_simple_scopes ||= {}
|
42
|
+
end
|
43
|
+
|
44
|
+
# A reusable scope that can be applied to a SimpleQuery::Builder instance
|
45
|
+
# Example:
|
46
|
+
# simple_scope :active do
|
47
|
+
# where(active: true)
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# Parameterized scope:
|
51
|
+
# simple_scope :by_name do |name|
|
52
|
+
# where(name: name)
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
def simple_scope(name, &block)
|
56
|
+
_simple_scopes[name.to_sym] = block
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
9
60
|
included do
|
10
61
|
def self.simple_query
|
11
|
-
|
62
|
+
Builder.new(self)
|
12
63
|
end
|
13
64
|
end
|
14
65
|
end
|
15
|
-
|
16
|
-
ActiveRecord::Base.include SimpleQuery
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple_query
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Kholodniak
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-02-
|
11
|
+
date: 2025-02-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,20 +16,20 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
20
|
-
- - "
|
19
|
+
version: '7.0'
|
20
|
+
- - "<="
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: '
|
22
|
+
version: '8.0'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
requirements:
|
27
27
|
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: '
|
30
|
-
- - "
|
29
|
+
version: '7.0'
|
30
|
+
- - "<="
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '
|
32
|
+
version: '8.0'
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: rake
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -100,14 +100,21 @@ files:
|
|
100
100
|
- README.md
|
101
101
|
- lib/simple_query.rb
|
102
102
|
- lib/simple_query/builder.rb
|
103
|
+
- lib/simple_query/clauses/distinct_clause.rb
|
104
|
+
- lib/simple_query/clauses/group_having_clause.rb
|
105
|
+
- lib/simple_query/clauses/join_clause.rb
|
106
|
+
- lib/simple_query/clauses/limit_offset_clause.rb
|
107
|
+
- lib/simple_query/clauses/order_clause.rb
|
108
|
+
- lib/simple_query/clauses/where_clause.rb
|
109
|
+
- lib/simple_query/read_model.rb
|
103
110
|
- lib/simple_query/version.rb
|
104
|
-
homepage: https://
|
111
|
+
homepage: https://github.com/kholdrex/simple_query
|
105
112
|
licenses:
|
106
113
|
- MIT
|
107
114
|
metadata:
|
108
|
-
homepage_uri: https://
|
115
|
+
homepage_uri: https://github.com/kholdrex/simple_query
|
109
116
|
source_code_uri: https://github.com/kholdrex/simple_query
|
110
|
-
changelog_uri: https://github.com/kholdrex/simple_query/blob/
|
117
|
+
changelog_uri: https://github.com/kholdrex/simple_query/blob/master/CHANGELOG.md
|
111
118
|
post_install_message:
|
112
119
|
rdoc_options: []
|
113
120
|
require_paths:
|