occams-record 1.4.0.pre.beta1 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +68 -8
- data/lib/occams-record/batches/offset_limit/raw_query.rb +3 -0
- data/lib/occams-record/batches/offset_limit/scoped.rb +3 -3
- data/lib/occams-record/cursor.rb +4 -4
- data/lib/occams-record/eager_loaders/base.rb +16 -2
- data/lib/occams-record/eager_loaders/context.rb +1 -1
- data/lib/occams-record/eager_loaders/polymorphic_belongs_to.rb +16 -2
- data/lib/occams-record/version.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d09d84ccc4713f32959380098b74112e45fa6a84a47698f1cec2d6fc4c4886a9
|
4
|
+
data.tar.gz: b94244c91ed19b02cfb36ff02c72b6510ab38da73f7d76e13e8104713fa2ed03
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 950a0baa705302727a9ab9d20bb286e2fdc44cdd8b6aee3edb1b02c0a640786beca3492e799bcf4efe87c3a01289c6f4edb2daa07dc24650a5c29d6276733737
|
7
|
+
data.tar.gz: dc07af2830bf268dac941834d36bc1cca3ffe64f5e1e3eadfe901b11fe1fcf545ba9804da4fd683697ee26201faf0936b06aaa203ac661aa14eed69b036e2109
|
data/README.md
CHANGED
@@ -9,6 +9,7 @@ OccamsRecord is a high-efficiency, advanced query library for use alongside Acti
|
|
9
9
|
* 3x-5x faster than ActiveRecord queries, *minimum*.
|
10
10
|
* Uses 1/3 the memory of ActiveRecord query results.
|
11
11
|
* Eliminates the N+1 query problem. (This often exceeds the baseline 3x-5x gain.)
|
12
|
+
* Support for cursors (Postgres only, new in v1.4.0)
|
12
13
|
|
13
14
|
### 2) Supercharged querying & eager loading
|
14
15
|
|
@@ -19,7 +20,9 @@ Continue using ActiveRecord's query builder, but let Occams take over running th
|
|
19
20
|
```ruby
|
20
21
|
OccamsRecord
|
21
22
|
.query(User.active)
|
22
|
-
.eager_load(:orders
|
23
|
+
.eager_load(:orders) {
|
24
|
+
scope { |q| q.where("created_at >= ?", date).order("created_at DESC") }
|
25
|
+
}
|
23
26
|
```
|
24
27
|
|
25
28
|
**Use `ORDER BY` with `find_each`/`find_in_batches`**
|
@@ -32,6 +35,16 @@ OccamsRecord
|
|
32
35
|
}
|
33
36
|
```
|
34
37
|
|
38
|
+
**Use cursors**
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
OccamsRecord
|
42
|
+
.query(Order.order("created_at DESC"))
|
43
|
+
.find_each_with_cursor { |order|
|
44
|
+
...
|
45
|
+
}
|
46
|
+
```
|
47
|
+
|
35
48
|
**Use `find_each`/`find_in_batches` with raw SQL**
|
36
49
|
|
37
50
|
```ruby
|
@@ -147,24 +160,71 @@ orders = OccamsRecord
|
|
147
160
|
.query(q)
|
148
161
|
# Only SELECT the columns you need. Your DBA will thank you.
|
149
162
|
.eager_load(:customer, select: "id, name")
|
150
|
-
|
151
|
-
#
|
152
|
-
|
163
|
+
|
164
|
+
# Or use 'scope' to access the full power of ActiveRecord's query builder.
|
165
|
+
# Here, only 'active' line items will be returned, and in a specific order.
|
166
|
+
.eager_load(:line_items) {
|
167
|
+
scope { |q| q.active.order("created_at") }
|
168
|
+
|
153
169
|
eager_load(:product)
|
154
170
|
eager_load(:something_else)
|
155
171
|
}
|
156
172
|
.run
|
157
173
|
```
|
158
174
|
|
159
|
-
Occams Record also supports loading ad hoc associations using raw SQL. We'll get to that in
|
175
|
+
Occams Record also supports loading ad hoc associations using raw SQL. We'll get to that in a later section.
|
176
|
+
|
177
|
+
## Query with cursors
|
178
|
+
|
179
|
+
`find_each_with_cursor`/`find_in_batches_with_cursor` work like `find_each`/`find_in_batches`, except they use cursors. For large data sets, cursors offer a noticible speed boost. Postgres only.
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
OccamsRecord
|
183
|
+
.query(q)
|
184
|
+
.eager_load(:customer)
|
185
|
+
.find_each_with_cursor do |order|
|
186
|
+
...
|
187
|
+
end
|
188
|
+
```
|
189
|
+
|
190
|
+
The `cursor.open` method allows lower level access to cursor behavior. See `OccamsRecord::Cursor` for more info.
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
orders = OccamsRecord
|
194
|
+
.query(q)
|
195
|
+
.eager_load(:customer)
|
196
|
+
.cursor
|
197
|
+
.open do |cursor|
|
198
|
+
cursor.move(:forward, 300)
|
199
|
+
cursor.fetch(:forward, 100)
|
200
|
+
end
|
201
|
+
```
|
160
202
|
|
161
203
|
## Raw SQL queries
|
162
204
|
|
163
205
|
ActiveRecord has raw SQL escape hatches like `find_by_sql` and `exec_query`, but they give up critical features like eager loading and `find_each`/`find_in_batches`. Occams Record's escape hatches don't make you give up anything.
|
164
206
|
|
165
|
-
**Batched loading**
|
207
|
+
**Batched loading with cursors**
|
208
|
+
|
209
|
+
`find_each_with_cursor`, `find_in_batches_with_cursor`, and `cursor.open` are all available.
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
OccamsRecord
|
213
|
+
.sql("
|
214
|
+
SELECT * FROM orders
|
215
|
+
WHERE order_date > %{date}
|
216
|
+
ORDER BY order_date DESC, id
|
217
|
+
", {
|
218
|
+
date: 10.years.ago
|
219
|
+
})
|
220
|
+
.find_each_with_cursor(batch_size: 1000) do |order|
|
221
|
+
...
|
222
|
+
end
|
223
|
+
```
|
224
|
+
|
225
|
+
**Batched loading without cursors**
|
166
226
|
|
167
|
-
|
227
|
+
If your database doesn't support cursors, you can use `find_each`/`find_in_batches`. Just provide `LIMIT` and `OFFSET` (see below), and Occams will plug in the right numbers.
|
168
228
|
|
169
229
|
```ruby
|
170
230
|
OccamsRecord
|
@@ -299,7 +359,7 @@ bundle install
|
|
299
359
|
bundle exec rake test
|
300
360
|
|
301
361
|
# test against Postgres
|
302
|
-
TEST_DATABASE_URL=
|
362
|
+
TEST_DATABASE_URL=postgresql://postgres@localhost:5432/occams_record bundle exec rake test
|
303
363
|
|
304
364
|
# test against MySQL
|
305
365
|
TEST_DATABASE_URL=mysql2://root:@127.0.0.1:3306/occams_record bundle exec rake test
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module OccamsRecord
|
2
|
-
#
|
3
|
-
# Methods for building batch finding methods. It expects "model" and "scope" methods to be present.
|
4
|
-
#
|
5
2
|
module Batches
|
6
3
|
module OffsetLimit
|
4
|
+
#
|
5
|
+
# Implements batched loading for ActiveRecord model scopes.
|
6
|
+
#
|
7
7
|
class Scoped
|
8
8
|
def initialize(model, scope, use: nil, query_logger: nil, eager_loaders: nil)
|
9
9
|
@model, @scope = model, scope
|
data/lib/occams-record/cursor.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'securerandom'
|
2
2
|
|
3
3
|
module OccamsRecord
|
4
|
-
#
|
5
|
-
# An interface to database cursors. Supported databases:
|
6
|
-
# * PostgreSQL
|
7
|
-
#
|
4
|
+
#
|
5
|
+
# An interface to database cursors. Supported databases:
|
6
|
+
# * PostgreSQL
|
7
|
+
#
|
8
8
|
class Cursor
|
9
9
|
# @private
|
10
10
|
SCROLL = {
|
@@ -19,7 +19,7 @@ module OccamsRecord
|
|
19
19
|
# @yield perform eager loading on *this* association (optional)
|
20
20
|
#
|
21
21
|
def initialize(ref, scope = nil, use: nil, as: nil, optimizer: :select, &builder)
|
22
|
-
@ref, @
|
22
|
+
@ref, @scopes, @use, @as = ref, Array(scope), use, as
|
23
23
|
@model = ref.klass
|
24
24
|
@name = (as || ref.name).to_s
|
25
25
|
@eager_loaders = EagerLoaders::Context.new(@model)
|
@@ -27,6 +27,20 @@ module OccamsRecord
|
|
27
27
|
instance_exec(&builder) if builder
|
28
28
|
end
|
29
29
|
|
30
|
+
#
|
31
|
+
# An alternative to passing a "scope" lambda to the constructor. Your block is passed the query
|
32
|
+
# so you can call select, where, order, etc on it.
|
33
|
+
#
|
34
|
+
# If you call scope multiple times, the results will be additive.
|
35
|
+
#
|
36
|
+
# @yield [ActiveRecord::Relation] a relation to modify with select, where, order, etc
|
37
|
+
# @return self
|
38
|
+
#
|
39
|
+
def scope(&scope)
|
40
|
+
@scopes << scope if scope
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
30
44
|
#
|
31
45
|
# Run the query and merge the results into the given rows.
|
32
46
|
#
|
@@ -71,7 +85,7 @@ module OccamsRecord
|
|
71
85
|
def base_scope
|
72
86
|
q = @ref.klass.all
|
73
87
|
q = q.instance_exec(&@ref.scope) if @ref.scope
|
74
|
-
q = @
|
88
|
+
q = @scopes.reduce(q) { |acc, scope| scope.(acc) }
|
75
89
|
q
|
76
90
|
end
|
77
91
|
end
|
@@ -115,7 +115,7 @@ module OccamsRecord
|
|
115
115
|
|
116
116
|
def build_loader!(assoc, custom_name, scope, select, use, optimizer, builder)
|
117
117
|
build_loader(assoc, custom_name, scope, select, use, optimizer, builder) ||
|
118
|
-
raise("OccamsRecord: No
|
118
|
+
raise("OccamsRecord: No association `:#{assoc}` on `#{@model.name}` or subclasses")
|
119
119
|
end
|
120
120
|
|
121
121
|
def build_loader(assoc, custom_name, scope, select, use, optimizer, builder)
|
@@ -17,7 +17,7 @@ module OccamsRecord
|
|
17
17
|
# @yield perform eager loading on *this* association (optional)
|
18
18
|
#
|
19
19
|
def initialize(ref, scope = nil, use: nil, as: nil, optimizer: nil, &builder)
|
20
|
-
@ref, @
|
20
|
+
@ref, @scopes, @use = ref, Array(scope), use
|
21
21
|
@name = (as || ref.name).to_s
|
22
22
|
@foreign_type = @ref.foreign_type.to_sym
|
23
23
|
@foreign_key = @ref.foreign_key.to_sym
|
@@ -25,6 +25,20 @@ module OccamsRecord
|
|
25
25
|
instance_exec(&builder) if builder
|
26
26
|
end
|
27
27
|
|
28
|
+
#
|
29
|
+
# An alternative to passing a "scope" lambda to the constructor. Your block is passed the query
|
30
|
+
# so you can call select, where, order, etc on it.
|
31
|
+
#
|
32
|
+
# If you call scope multiple times, the results will be additive.
|
33
|
+
#
|
34
|
+
# @yield [ActiveRecord::Relation] a relation to modify with select, where, order, etc
|
35
|
+
# @return self
|
36
|
+
#
|
37
|
+
def scope(&scope)
|
38
|
+
@scopes << scope if scope
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
28
42
|
#
|
29
43
|
# Run the query and merge the results into the given rows.
|
30
44
|
#
|
@@ -83,7 +97,7 @@ module OccamsRecord
|
|
83
97
|
def base_scope(model)
|
84
98
|
q = model.all
|
85
99
|
q = q.instance_exec(&@ref.scope) if @ref.scope
|
86
|
-
q = @
|
100
|
+
q = @scopes.reduce(q) { |acc, scope| scope.(acc) }
|
87
101
|
q
|
88
102
|
end
|
89
103
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: occams-record
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jordan Hollinger
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-02-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -94,11 +94,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
94
94
|
version: 2.3.0
|
95
95
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
96
|
requirements:
|
97
|
-
- - "
|
97
|
+
- - ">="
|
98
98
|
- !ruby/object:Gem::Version
|
99
|
-
version:
|
99
|
+
version: '0'
|
100
100
|
requirements: []
|
101
|
-
rubygems_version: 3.1
|
101
|
+
rubygems_version: 3.4.1
|
102
102
|
signing_key:
|
103
103
|
specification_version: 4
|
104
104
|
summary: The missing high-efficiency query API for ActiveRecord
|