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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 662bd65e909a1d957ebdbbd2d3bec3fe96fb45aac2117a786c9a3ad02b03259b
4
- data.tar.gz: abeb69ff706dbb11f3fae3672ae60c407080f418fce7330779a7b550915aa886
3
+ metadata.gz: d09d84ccc4713f32959380098b74112e45fa6a84a47698f1cec2d6fc4c4886a9
4
+ data.tar.gz: b94244c91ed19b02cfb36ff02c72b6510ab38da73f7d76e13e8104713fa2ed03
5
5
  SHA512:
6
- metadata.gz: 8981d9c820b64ebeca28dcbe3ab7ac44e35d8ec7e14df201e890ccf6cf1cf1888d6f78ba6cac001f1a9c458f604189b1aafaf7ff644288499faf5d5afeb896c3
7
- data.tar.gz: cdd61c50313a964db4ebdb271dbb875fc4ea888dd0b57df78be78d043e655e04e821e0cef8d9b60034e90866289a0d764f72a7728a18f46c778e511101b72f4a
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, ->(q) { q.where("created_at >= ?", date).order("created_at DESC") })
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
- # A Proc can use ActiveRecord's query builder
152
- .eager_load(:line_items, ->(q) { q.active.order("created_at") }) {
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 the next section.
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
- To use `find_each`/`find_in_batches` you must provide the limit and offset statements yourself; Occams will provide the values. Also, notice that the binding syntax is a bit different (it uses Ruby's built-in named string substitution).
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=postgres://postgres@localhost:5432/occams_record bundle exec rake test
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,6 +1,9 @@
1
1
  module OccamsRecord
2
2
  module Batches
3
3
  module OffsetLimit
4
+ #
5
+ # Implements batched loading for pure SQL.
6
+ #
4
7
  class RawQuery
5
8
  def initialize(conn, sql, binds, use: nil, query_logger: nil, eager_loaders: nil)
6
9
  @conn, @sql, @binds = conn, sql, binds
@@ -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
@@ -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, @scope, @use, @as = ref, scope, use, as
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 = @scope.(q) if @scope
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 assocation `:#{assoc}` on `#{@model.name}` or subclasses")
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, @scope, @use = ref, scope, use
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 = @scope.(q) if @scope
100
+ q = @scopes.reduce(q) { |acc, scope| scope.(acc) }
87
101
  q
88
102
  end
89
103
  end
@@ -3,5 +3,5 @@
3
3
  #
4
4
  module OccamsRecord
5
5
  # @private
6
- VERSION = "1.4.0-beta1".freeze
6
+ VERSION = "1.5.0".freeze
7
7
  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.0.pre.beta1
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: 2022-05-22 00:00:00.000000000 Z
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: 1.3.1
99
+ version: '0'
100
100
  requirements: []
101
- rubygems_version: 3.1.6
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