occams-record 0.9.0 → 0.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 011e5e6f67a6eeb9ad95fe8e314c443099e9d06c
4
- data.tar.gz: 595a4ed16e94f1abf7fc33e32f1d067308f814fd
3
+ metadata.gz: 9c305e7ef50172b06abc65501fda3a2e323a4b59
4
+ data.tar.gz: 8b3148052f587c1987485527473e99764132130a
5
5
  SHA512:
6
- metadata.gz: ca932af1da71d11622e5cdf077ae48b9a5114c144ed01ef4b3cd590ba06f4916ab539df201fd01fa14477239ebd78598bdd031380d87aaa73b9d5b626a2ed5ca
7
- data.tar.gz: d8755fb3d2c0520531aaea1f7266e5420f755277a394a35138a8a7be6fe3a35c0f061bfbf479ece0900518cb50448d1847a5166eb00c98594bff07f7ada5657a
6
+ metadata.gz: 4907ef959db18d068d58007f6f1df7d2aac60ce69cf1b9af4c91e89c518b8b2eb103d5574b6d03a34964a1456c62f18b372e0c50595a1bc9c2cb3b9a967afbbe
7
+ data.tar.gz: 39cbe4f0a8bb3af17944aba9d6c7839a6751b9b19053adbceb8df924815718d9c9dfaa596733bb63f1b3fb873815059c79264127d99b31a9621ffa69bdb99d4e
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Do not multiply entities beyond necessity. -- Occam's Razor
4
4
 
5
- Occam's Record is a high-efficiency query API for ActiveRecord. When loading thousands of records, ActiveRecord wastes a lot of RAM and CPU cycles on *things you'll never use.* Additionally, eagerly-loaded associations are forced to load each and every column, even if you only need a few.
5
+ Occam's Record is a high-efficiency query API for ActiveRecord. When loading thousands of records, ActiveRecord wastes a lot of RAM and CPU cycles on *things you'll never use.* Additionally, eagerly-loaded associations are forced to load each and every column, each and every record, and all in a certain order.
6
6
 
7
7
  For those stuck with ActiveRecord, OccamsRecord seeks to solve these issues by making some very specific trade-offs:
8
8
 
@@ -10,28 +10,9 @@ For those stuck with ActiveRecord, OccamsRecord seeks to solve these issues by m
10
10
  * OccamsRecord objects are **purely database rows** - they don't have any instance methods from your Rails models.
11
11
  * OccamsRecord queries must specify each association that will be used. Otherwise they simply won't be availble.
12
12
 
13
- **What does this buy you?**
13
+ For more on the rational behind OccamsRecord, see the Rational section at the end of the README. But in short, OccamsRecord is 3x-5x faster, uses 1/3 of the memory, and eliminates the N+1 query problem.
14
14
 
15
- * OccamsRecord results are **one-third the size** of ActiveRecord results.
16
- * OccamsRecord queries run **three to five times faster** than ActiveRecord queries.
17
- * When eager loading associations you may specify which columns to `SELECT`. (This can be a significant performance boost to both your database and Rails app, on top of the above numbers.)
18
- * When eager loading associations you may completely customize the query (`WHERE`, `ORDER BY`, `LIMIT`, etc.)
19
- * By forcing eager loading of associations, OccamsRecord bypasses the primary cause of performance problems in Rails: N+1 queries.
20
- * Forced eager loading also makes you consider the "shape" of your data, which can help you identify areas that need refactored (e.g. redundant foreign keys, more denormalization, etc.)
21
-
22
- **What don't you give up?**
23
-
24
- * You can still write your queries using ActiveRecord's query builder, as well as your existing models' associations & scopes.
25
- * You can still use ActiveRecord for everything else - small queries, creating, updating, and deleting records.
26
- * You can still inject some instance methods into your results, if you must. See below.
27
-
28
- **Is there evidence to back any of this up?**
29
-
30
- Glad you asked. [Look over the results yourself.](https://github.com/jhollinger/occams-record/wiki/Measurements)
31
-
32
- **Why not use a different ORM?**
33
-
34
- That's a great idea; check out [sequel](https://rubygems.org/gems/sequel) or [rom](https://rubygems.org/gems/rom)! But for large, legacy codebases heavily invested in ActiveRecord, switching ORMs often isn't practical. OccamsRecord can help you get some of those wins without a rewrite.
15
+ **BREAKING CHANGE** to `eager_load` in version **0.10.0**. See the examples below or [HISTORY.md](https://github.com/jhollinger/occams-record/blob/v0.10.0/HISTORY.md) for the new usage.
35
16
 
36
17
  ## Usage
37
18
 
@@ -41,6 +22,8 @@ That's a great idea; check out [sequel](https://rubygems.org/gems/sequel) or [ro
41
22
  gem 'occams-record'
42
23
  ```
43
24
 
25
+ Full documentation is available at [rubydoc.info/gems/occams-record](http://www.rubydoc.info/gems/occams-record).
26
+
44
27
  **Simple example**
45
28
 
46
29
  ```ruby
@@ -89,8 +72,8 @@ widgets = OccamsRecord.
89
72
  # load order_items, but only the fields needed to identify which orders go with which widgets
90
73
  eager_load(:order_items, select: "widget_id, order_id") {
91
74
 
92
- # load the orders
93
- eager_load(:orders, -> { select("id, customer_id").order("order_date DESC") }) {
75
+ # load the orders ("q" has all the normal query methods and any scopes defined on Order)
76
+ eager_load(:orders, ->(q) { q.select("id, customer_id").order("order_date DESC") }) {
94
77
 
95
78
  # load the customers who made the orders, but only their names
96
79
  eager_load(:customer, select: "id, name")
@@ -168,6 +151,31 @@ widgets = OccamsRecord.
168
151
  run
169
152
  ```
170
153
 
154
+ ## Rational
155
+
156
+ **What does OccamsRecord buy you?**
157
+
158
+ * OccamsRecord results are **one-third the size** of ActiveRecord results.
159
+ * OccamsRecord queries run **three to five times faster** than ActiveRecord queries.
160
+ * When eager loading associations you may specify which columns to `SELECT`. (This can be a significant performance boost to both your database and Rails app, on top of the above numbers.)
161
+ * When eager loading associations you may completely customize the query (`WHERE`, `ORDER BY`, `LIMIT`, etc.)
162
+ * By forcing eager loading of associations, OccamsRecord bypasses the primary cause of performance problems in Rails: N+1 queries.
163
+ * Forced eager loading also makes you consider the "shape" of your data, which can help you identify areas that need refactored (e.g. redundant foreign keys, more denormalization, etc.)
164
+
165
+ **What don't you give up?**
166
+
167
+ * You can still write your queries using ActiveRecord's query builder, as well as your existing models' associations & scopes.
168
+ * You can still use ActiveRecord for everything else - small queries, creating, updating, and deleting records.
169
+ * You can still inject some instance methods into your results, if you must. See below.
170
+
171
+ **Is there evidence to back any of this up?**
172
+
173
+ Glad you asked. [Look over the results yourself.](https://github.com/jhollinger/occams-record/wiki/Measurements)
174
+
175
+ **Why not use a different ORM?**
176
+
177
+ That's a great idea; check out [sequel](https://rubygems.org/gems/sequel) or [rom](https://rubygems.org/gems/rom)! But for large, legacy codebases heavily invested in ActiveRecord, switching ORMs often isn't practical. OccamsRecord can help you get some of those wins without a rewrite.
178
+
171
179
  ## Unsupported features
172
180
 
173
181
  The following `ActiveRecord` are not supported, and I have no plans to do so. However, I'd be glad to accept pull requests.
@@ -11,6 +11,7 @@ module OccamsRecord
11
11
  # to the ORDER BY clause to help ensure consistent batches.
12
12
  #
13
13
  # @param batch_size [Integer]
14
+ # @yield [OccamsRecord::Results::Row]
14
15
  # @return [Enumerator] will yield each record
15
16
  #
16
17
  def find_each(batch_size: 1000)
@@ -34,6 +35,7 @@ module OccamsRecord
34
35
  # to the ORDER BY clause to help ensure consistent batches.
35
36
  #
36
37
  # @param batch_size [Integer]
38
+ # @yield [OccamsRecord::Results::Row]
37
39
  # @return [Enumerator] will yield each batch
38
40
  #
39
41
  def find_in_batches(batch_size: 1000)
@@ -13,21 +13,22 @@ module OccamsRecord
13
13
  # Methods for adding eager loading to a query.
14
14
  module Builder
15
15
  #
16
- # Specify an association to be eager-loaded. You may optionally pass a block that accepts a scope
17
- # which you may modify to customize the query. For maximum memory savings, always `select` only
18
- # the colums you actually need.
16
+ # Specify an association to be eager-loaded. For maximum memory savings, only SELECT the
17
+ # colums you actually need.
19
18
  #
20
19
  # @param assoc [Symbol] name of association
21
- # @param scope [Proc] a scope to apply to the query (optional)
20
+ # @param scope [Proc] a scope to apply to the query (optional). It will be passed an
21
+ # ActiveRecord::Relation on which you may call all the normal query hethods (select, where, etc) as well as any scopes you've defined on the model.
22
22
  # @param select [String] a custom SELECT statement, minus the SELECT (optional)
23
23
  # @param use [Array<Module>] optional Module to include in the result class (single or array)
24
- # @param eval_block [Proc] a block where you may perform eager loading on *this* association (optional)
24
+ # @param as [Symbol] Load the association usign a different attribute name
25
+ # @yield a block where you may perform eager loading on *this* association (optional)
25
26
  # @return [OccamsRecord::Query] returns self
26
27
  #
27
28
  def eager_load(assoc, scope = nil, select: nil, use: nil, as: nil, &eval_block)
28
29
  ref = @model ? @model.reflections[assoc.to_s] : nil
29
30
  raise "OccamsRecord: No assocation `:#{assoc}` on `#{@model&.name || '<model missing>'}`" if ref.nil?
30
- scope ||= -> { self.select select } if select
31
+ scope ||= ->(q) { q.select select } if select
31
32
  @eager_loaders << eager_loader_for_association(ref).new(ref, scope, use: use, as: as, &eval_block)
32
33
  self
33
34
  end
@@ -13,10 +13,11 @@ module OccamsRecord
13
13
 
14
14
  #
15
15
  # @param ref [ActiveRecord::Association] the ActiveRecord association
16
- # @param scope [Proc] a scope to apply to the query (optional)
16
+ # @param scope [Proc] a scope to apply to the query (optional). It will be passed an
17
+ # ActiveRecord::Relation on which you may call all the normal query hethods (select, where, etc) as well as any scopes you've defined on the model.
17
18
  # @param use [Array(Module)] optional Module to include in the result class (single or array)
18
- # @param as [Symbol] Load the association into this attr instead
19
- # @param eval_block [Proc] a block where you may perform eager loading on *this* association (optional)
19
+ # @param as [Symbol] Load the association usign a different attribute name
20
+ # @yield perform eager loading on *this* association (optional)
20
21
  #
21
22
  def initialize(ref, scope = nil, use: nil, as: nil, &eval_block)
22
23
  @ref, @scope, @use, @as, @eval_block = ref, scope, use, as, eval_block
@@ -54,7 +55,7 @@ module OccamsRecord
54
55
  def base_scope
55
56
  q = @ref.klass.all
56
57
  q = q.instance_exec(&@ref.scope) if @ref.scope
57
- q = q.instance_exec(&@scope) if @scope
58
+ q = @scope.(q) if @scope
58
59
  q
59
60
  end
60
61
  end
@@ -6,6 +6,7 @@ module OccamsRecord
6
6
  # Yield one or more ActiveRecord::Relation objects to a given block.
7
7
  #
8
8
  # @param rows [Array<OccamsRecord::Results::Row>] Array of rows used to calculate the query.
9
+ # @yield
9
10
  #
10
11
  def query(rows)
11
12
  ids = rows.map { |r| r.send @ref.foreign_key }.compact.uniq
@@ -6,6 +6,7 @@ module OccamsRecord
6
6
  # Yield one or more ActiveRecord::Relation objects to a given block.
7
7
  #
8
8
  # @param rows [Array<OccamsRecord::Results::Row>] Array of rows used to calculate the query.
9
+ # @yield
9
10
  #
10
11
  def query(rows)
11
12
  assoc_ids = join_rows(rows).map { |row| row[1] }.compact.uniq
@@ -6,6 +6,7 @@ module OccamsRecord
6
6
  # Yield one or more ActiveRecord::Relation objects to a given block.
7
7
  #
8
8
  # @param rows [Array<OccamsRecord::Results::Row>] Array of rows used to calculate the query.
9
+ # @yield
9
10
  #
10
11
  def query(rows)
11
12
  return if rows.empty?
@@ -11,10 +11,11 @@ module OccamsRecord
11
11
 
12
12
  #
13
13
  # @param ref [ActiveRecord::Association] the ActiveRecord association
14
- # @param scope [Proc] a scope to apply to the query (optional)
14
+ # @param scope [Proc] a scope to apply to the query (optional). It will be passed an
15
+ # ActiveRecord::Relation on which you may call all the normal query hethods (select, where, etc) as well as any scopes you've defined on the model.
15
16
  # @param use [Array<Module>] optional Module to include in the result class (single or array)
16
- # @param as [Symbol] Load the association into this attr instead
17
- # @param eval_block [Proc] a block where you may perform eager loading on *this* association (optional)
17
+ # @param as [Symbol] Load the association usign a different attribute name
18
+ # @yield perform eager loading on *this* association (optional)
18
19
  #
19
20
  def initialize(ref, scope = nil, use: nil, as: nil, &eval_block)
20
21
  @ref, @scope, @use, @eval_block = ref, scope, use, eval_block
@@ -27,6 +28,7 @@ module OccamsRecord
27
28
  # Yield ActiveRecord::Relations to the given block, one for every "type" represented in the given rows.
28
29
  #
29
30
  # @param rows [Array<OccamsRecord::Results::Row>] Array of rows used to calculate the query.
31
+ # @yield
30
32
  #
31
33
  def query(rows)
32
34
  rows_by_type = rows.group_by(&@foreign_type)
@@ -55,7 +57,7 @@ module OccamsRecord
55
57
  def base_scope(model)
56
58
  q = model.all
57
59
  q = q.instance_exec(&@ref.scope) if @ref.scope
58
- q = q.instance_exec(&@scope) if @scope
60
+ q = @scope.(q) if @scope
59
61
  q
60
62
  end
61
63
  end
@@ -17,7 +17,7 @@ module OccamsRecord
17
17
  # }.
18
18
  # run
19
19
  #
20
- # @param query [ActiveRecord::Relation]
20
+ # @param scope [ActiveRecord::Relation]
21
21
  # @param use [Module] optional Module to include in the result class
22
22
  # @param query_logger [Array] (optional) an array into which all queries will be inserted for logging/debug purposes
23
23
  # @return [OccamsRecord::Query]
@@ -46,7 +46,7 @@ module OccamsRecord
46
46
  # @param use [Array<Module>] optional Module to include in the result class (single or array)
47
47
  # @param query_logger [Array] (optional) an array into which all queries will be inserted for logging/debug purposes
48
48
  # @param eager_loaders [OccamsRecord::EagerLoaders::Base]
49
- # @param eval_block [Proc] block that will be eval'd on this instance. Can be used for eager loading. (optional)
49
+ # @yield will be eval'd on this instance. Can be used for eager loading. (optional)
50
50
  #
51
51
  def initialize(scope, use: nil, query_logger: nil, eager_loaders: [], &eval_block)
52
52
  @model = scope.klass
@@ -88,6 +88,7 @@ module OccamsRecord
88
88
  # If you pass a block, each result row will be yielded to it. If you don't,
89
89
  # an Enumerable will be returned.
90
90
  #
91
+ # @yield [OccamsRecord::Results::Row]
91
92
  # @return Enumerable
92
93
  #
93
94
  def each
@@ -25,7 +25,7 @@ module OccamsRecord
25
25
  # eager_load(:category).
26
26
  # run
27
27
  #
28
- # @param sql [String] The SELECT statement to run. Binds should use the built-in Ruby "%{bind_name}" syntax.
28
+ # @param sql [String] The SELECT statement to run. Binds should use Ruby's named string substitution.
29
29
  # @param binds [Hash] Bind values (Symbol keys)
30
30
  # @param use [Array<Module>] optional Module to include in the result class (single or array)
31
31
  # @param query_logger [Array] (optional) an array into which all queries will be inserted for logging/debug purposes
@@ -50,12 +50,11 @@ module OccamsRecord
50
50
  #
51
51
  # Initialize a new query.
52
52
  #
53
- # @param sql [String] The SELECT statement to run. Binds should use the built-in Ruby "%{bind_name}" syntax.
53
+ # @param sql [String] The SELECT statement to run. Binds should use Ruby's named string substitution.
54
54
  # @param binds [Hash] Bind values (Symbol keys)
55
55
  # @param use [Array<Module>] optional Module to include in the result class (single or array)
56
56
  # @param eager_loaders [OccamsRecord::EagerLoaders::Base]
57
57
  # @param query_logger [Array] (optional) an array into which all queries will be inserted for logging/debug purposes
58
- # @param eval_block [Proc] block that will be eval'd on this instance. Can be used for eager loading. (optional)
59
58
  #
60
59
  def initialize(sql, binds, use: nil, eager_loaders: [], query_logger: nil)
61
60
  @sql = sql
@@ -110,7 +109,8 @@ module OccamsRecord
110
109
  # If you pass a block, each result row will be yielded to it. If you don't,
111
110
  # an Enumerable will be returned.
112
111
  #
113
- # @return Enumerable
112
+ # @yield [OccansR::Results::Row]
113
+ # @return [Enumerable]
114
114
  #
115
115
  def each
116
116
  if block_given?
@@ -1,4 +1,5 @@
1
1
  module OccamsRecord
2
+ # Classes and methods for handing query results.
2
3
  module Results
3
4
  # ActiveRecord's internal type casting API changes from version to version.
4
5
  CASTER = case ActiveRecord::VERSION::MAJOR
@@ -98,6 +99,11 @@ module OccamsRecord
98
99
 
99
100
  alias_method :to_hash, :to_h
100
101
 
102
+ #
103
+ # Returns a string with the "real" model name and raw result values.
104
+ #
105
+ # @return [String]
106
+ #
101
107
  def inspect
102
108
  "#<OccamsRecord::Results::Row @model_name=#{self.class.model_name} @raw_values=#{@raw_values}>"
103
109
  end
@@ -3,5 +3,5 @@
3
3
  #
4
4
  module OccamsRecord
5
5
  # Library version
6
- VERSION = '0.9.0'.freeze
6
+ VERSION = '0.10.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: 0.9.0
4
+ version: 0.10.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: 2018-01-19 00:00:00.000000000 Z
11
+ date: 2018-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord