occams-record 1.1.0 → 1.1.1

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: a403231d294ef71185626a819905f486206a5fdf6b94e2d0cd7eb459393a5be8
4
- data.tar.gz: 0a19d6226484a10da785bcc2dff3d4f8a068b376b8b9443f0f2a1c6eebefe50d
3
+ metadata.gz: 214f1fb4b74fb65d13461419473fdf865ece2fae7797dc5d9c47ba4d1ad14b45
4
+ data.tar.gz: 4a836cb9bbc0e5a694aaf7f5220628d1b9ff4afbd11c4e6c5e1e9af1c6f3e3e9
5
5
  SHA512:
6
- metadata.gz: '05977e1eb8936770d167e200db0925d0b6e93422b72b88925e71ef0d50df703f5e1171b175630f63ac6f4b02696ef9d5320f6b770d7414aea2c2458d1e0038f6'
7
- data.tar.gz: eace78527961d84999ad34e99be7a71be64bcbc64377f9cf907c9cc35f6d25f110586e5991f0e315246bc5b5fb3bd4b59a2550f5b58965aab72fb2ec1e3905ca
6
+ metadata.gz: 112fff206d8d9ea868351b91707b100f0eba99d81f7bb3fcfab9706bb71d611328e7f977796dc36c40c82b029ed1f4df0f38013465c0afeead3ba588b77ca3a9
7
+ data.tar.gz: f938b969b6688b3203d0b98abf58c18835f7b6eba76525794495778bfcbbad970a2f7cfe161a010dadf329dcd0ee00a57ddb27f0cafdd89114dd63be542f7567
data/README.md CHANGED
@@ -2,23 +2,80 @@
2
2
 
3
3
  > Do not multiply entities beyond necessity. -- Occam's Razor
4
4
 
5
- Occam's Record is a high-efficiency, advanced query library for ActiveRecord apps. It is **not** an ORM or an ActiveRecord replacement. Use it to solve pain points in your existing ActiveRecord app. Occams Record gives you two things:
5
+ OccamsRecord is a high-efficiency, advanced query library for use alongside ActiveRecord. It is **not** an ORM or an ActiveRecord replacement. OccamsRecord can breathe fresh life into your ActiveRecord app by giving it two things:
6
6
 
7
- **Performance**
7
+ ### 1) Huge performance gains
8
8
 
9
9
  * 3x-5x faster than ActiveRecord queries, *minimum*.
10
10
  * Uses 1/3 the memory of ActiveRecord query results.
11
- * Eliminates the N+1 query problem.
11
+ * Eliminates the N+1 query problem. (This often exceeds the baseline 3x-5x gain.)
12
12
 
13
- **More powerful queries & eager loading**
13
+ ### 2) Supercharged querying & eager loading
14
14
 
15
- * Customize the SQL used to eager load associations.
16
- * Use `ORDER BY` with `find_each`/`find_in_batches`.
17
- * Use `find_each`/`find_in_batches` with raw SQL.
18
- * Eager load associations when you're writing raw SQL.
19
- * Eager load "ad hoc associations" using raw SQL.
15
+ Continue using ActiveRecord's query builder, but let Occams take over eager loading and raw SQL calls. None of the examples below are possible with ActiveRecord, but OccamsRecord won't limit you. (More complete examples are shown later, but these should whet your appetite.)
20
16
 
21
- [Look over the speed and memory measurements yourself!](https://github.com/jhollinger/occams-record/wiki/Measurements) OccamsRecord achieves all of this by making some very specific trade-offs:
17
+ **Customize the SQL used to eager load associations**
18
+
19
+ ```ruby
20
+ OccamsRecord.
21
+ query(User.active).
22
+ eager_load(:orders, ->(q) { q.where("created_at >= ?", date })
23
+ ```
24
+
25
+ **Use `ORDER BY` with `find_each`/`find_in_batches`**
26
+
27
+ ```ruby
28
+ OccamsRecord.
29
+ query(Order.order("created_at DESC")).
30
+ find_each { |order|
31
+ ...
32
+ }
33
+ ```
34
+
35
+ **Use `find_each`/`find_in_batches` with raw SQL**
36
+
37
+ ```ruby
38
+ OccamsRecord.
39
+ sql("
40
+ SELECT * FROM orders
41
+ WHERE created_at >= %{date}
42
+ LIMIT %{batch_limit}
43
+ OFFSET %{batch_offset}",
44
+ {date: 10.years.ago}
45
+ ).
46
+ find_each { |order|
47
+ ...
48
+ }
49
+ ```
50
+
51
+ **Eager load associations when you're writing raw SQL**
52
+
53
+ ```ruby
54
+ OccamsRecord.
55
+ sql("
56
+ SELECT * FROM users
57
+ LEFT OUTER JOIN ...
58
+ ").
59
+ model(User).
60
+ eager_load(:orders)
61
+ ```
62
+
63
+ **Eager load "ad hoc associations" using raw SQL**
64
+
65
+ Relationships are complicated, and sometimes they can't be expressed in ActiveRecord models. Define your relationship on the fly!
66
+ (Don't worry, there's a full explanation later on.)
67
+
68
+ ```ruby
69
+ OccamsRecord.
70
+ query(User.all).
71
+ eager_load_many(:orders, {:id => :user_id}, "
72
+ SELECT user_id, orders.*
73
+ FROM orders INNER JOIN ...
74
+ WHERE user_id IN (%{ids})
75
+ ")
76
+ ```
77
+
78
+ [Look over the speed and memory measurements yourself!](https://github.com/jhollinger/occams-record/wiki/Measurements) OccamsRecord achieves all of this by making some **very specific trade-offs:**
22
79
 
23
80
  * OccamsRecord results are *read-only*.
24
81
  * OccamsRecord results are *purely database rows* - they don't have any instance methods from your Rails models.
@@ -116,7 +173,7 @@ OccamsRecord.
116
173
  LIMIT %{batch_limit}
117
174
  OFFSET %{batch_offset}
118
175
  ", {
119
- date: 30.days.ago
176
+ date: 10.years.ago
120
177
  }).
121
178
  find_each(batch_size: 1000) do |order|
122
179
  ...
@@ -2,7 +2,7 @@ require 'active_record'
2
2
  require 'occams-record/version'
3
3
  require 'occams-record/merge'
4
4
  require 'occams-record/measureable'
5
- require 'occams-record/eager_loaders'
5
+ require 'occams-record/eager_loaders/eager_loaders'
6
6
  require 'occams-record/results/results'
7
7
  require 'occams-record/results/row'
8
8
  require 'occams-record/query'
@@ -71,7 +71,6 @@ module OccamsRecord
71
71
  @use = use
72
72
  @eager_loaders = eager_loaders || EagerLoaders::Context.new
73
73
  @query_logger, @measurements = query_logger, measurements
74
- @conn = @eager_loaders.model&.connection || ActiveRecord::Base.connection
75
74
  end
76
75
 
77
76
  #
@@ -100,10 +99,10 @@ module OccamsRecord
100
99
  result = if measure?
101
100
  record_start_time!
102
101
  measure!(table_name, _escaped_sql) {
103
- @conn.exec_query _escaped_sql
102
+ conn.exec_query _escaped_sql
104
103
  }
105
104
  else
106
- @conn.exec_query _escaped_sql
105
+ conn.exec_query _escaped_sql
107
106
  end
108
107
  row_class = OccamsRecord::Results.klass(result.columns, result.column_types, @eager_loaders.names, model: @eager_loaders.model, modules: @use)
109
108
  rows = result.rows.map { |row| row_class.new row }
@@ -136,9 +135,9 @@ module OccamsRecord
136
135
  return sql if binds.empty?
137
136
  sql % binds.reduce({}) { |a, (col, val)|
138
137
  a[col.to_sym] = if val.is_a? Array
139
- val.map { |x| @conn.quote x }.join(', ')
138
+ val.map { |x| conn.quote x }.join(', ')
140
139
  else
141
- @conn.quote val
140
+ conn.quote val
142
141
  end
143
142
  a
144
143
  }
@@ -163,8 +162,8 @@ module OccamsRecord
163
162
  end
164
163
 
165
164
  Enumerator.new do |y|
166
- if use_transaction and @conn.open_transactions == 0
167
- @conn.transaction {
165
+ if use_transaction and conn.open_transactions == 0
166
+ conn.transaction {
168
167
  run_batches y, of
169
168
  }
170
169
  else
@@ -186,5 +185,9 @@ module OccamsRecord
186
185
  offset += results.size
187
186
  end
188
187
  end
188
+
189
+ def conn
190
+ @conn ||= @eager_loaders.model&.connection || ActiveRecord::Base.connection
191
+ end
189
192
  end
190
193
  end
@@ -9,8 +9,8 @@ module OccamsRecord
9
9
  end
10
10
 
11
11
  #
12
- # Dynamically build a class for a specific set of result rows. It inherits from OccamsRecord::Results::Row, and optionall includes
13
- # a user-defined module.
12
+ # Dynamically build a class for a specific set of result rows. It inherits from OccamsRecord::Results::Row, and optionall prepends
13
+ # user-defined modules.
14
14
  #
15
15
  # @param column_names [Array<String>] the column names in the result set. The order MUST match the order returned by the query.
16
16
  # @param column_types [Hash] Column name => type from an ActiveRecord::Result
@@ -25,6 +25,7 @@ module OccamsRecord
25
25
 
26
26
  self.columns = column_names.map(&:to_s)
27
27
  self.associations = association_names.map(&:to_s)
28
+ self._model = model
28
29
  self.model_name = model ? model.name : nil
29
30
  self.table_name = model ? model.table_name : nil
30
31
  self.primary_key = if model&.primary_key and (pkey = model.primary_key.to_s) and columns.include?(pkey)
@@ -34,20 +35,6 @@ module OccamsRecord
34
35
  # Build getters & setters for associations. (We need setters b/c they're set AFTER the row is initialized
35
36
  attr_accessor(*association_names)
36
37
 
37
- # Build id getters for associations, e.g. "widget_ids" for "widgets"
38
- self.associations.each do |assoc|
39
- if (ref = model.reflections[assoc]) and !ref.polymorphic? and (ref.macro == :has_many or ref.macro == :has_and_belongs_to_many)
40
- pkey = ref.association_primary_key.to_sym
41
- define_method "#{assoc.singularize}_ids" do
42
- begin
43
- self.send(assoc).map(&pkey).uniq
44
- rescue NoMethodError => e
45
- raise MissingColumnError.new(self, e.name)
46
- end
47
- end
48
- end
49
- end if model
50
-
51
38
  # Build a getter for each attribute returned by the query. The values will be type converted on demand.
52
39
  model_column_types = model ? model.attributes_builder.types : {}
53
40
  self.columns.each_with_index do |col, idx|
@@ -12,6 +12,7 @@ module OccamsRecord
12
12
  attr_accessor :columns
13
13
  # Array of associations names
14
14
  attr_accessor :associations
15
+ attr_accessor :_model
15
16
  # Name of Rails model
16
17
  attr_accessor :model_name
17
18
  # Name of originating database table
@@ -106,18 +107,50 @@ module OccamsRecord
106
107
  "#<#{self.class.model_name || "Anonymous"} #{self.class.primary_key}: #{id}>"
107
108
  end
108
109
 
110
+ IDS_SUFFIX = /_ids$/
109
111
  def method_missing(name, *args, &block)
110
- return super if args.any? or !block.nil? or self.class.model_name.nil?
111
- model = self.class.model_name.constantize
112
+ model = self.class._model
113
+ return super if args.any? or !block.nil? or model.nil?
112
114
 
113
- if model.reflections.has_key? name.to_s
115
+ name_str = name.to_s
116
+ assoc = name_str.sub(IDS_SUFFIX, "").pluralize
117
+ if name_str =~ IDS_SUFFIX and can_define_ids_reader? assoc
118
+ define_ids_reader! assoc
119
+ send name
120
+ elsif model.reflections.has_key? name_str
114
121
  raise MissingEagerLoadError.new(self, name)
115
- elsif model.columns_hash.has_key? name.to_s
122
+ elsif model.columns_hash.has_key? name_str
116
123
  raise MissingColumnError.new(self, name)
117
124
  else
118
125
  super
119
126
  end
120
127
  end
128
+
129
+ private
130
+
131
+ def can_define_ids_reader?(assoc)
132
+ model = self.class._model
133
+ self.class.associations.include?(assoc) &&
134
+ (ref = model.reflections[assoc]) &&
135
+ !ref.polymorphic? &&
136
+ (ref.macro == :has_many || ref.macro == :has_and_belongs_to_many)
137
+ end
138
+
139
+ def define_ids_reader!(assoc)
140
+ model = self.class._model
141
+ ref = model.reflections[assoc]
142
+ pkey = ref.association_primary_key.to_sym
143
+
144
+ self.class.class_eval do
145
+ define_method "#{assoc.singularize}_ids" do
146
+ begin
147
+ self.send(assoc).map(&pkey).uniq
148
+ rescue NoMethodError => e
149
+ raise MissingColumnError.new(self, e.name)
150
+ end
151
+ end
152
+ end
153
+ end
121
154
  end
122
155
  end
123
156
  end
@@ -3,5 +3,5 @@
3
3
  #
4
4
  module OccamsRecord
5
5
  # Library version
6
- VERSION = "1.1.0".freeze
6
+ VERSION = "1.1.1".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.1.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jordan Hollinger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-29 00:00:00.000000000 Z
11
+ date: 2019-10-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -40,7 +40,6 @@ files:
40
40
  - README.md
41
41
  - lib/occams-record.rb
42
42
  - lib/occams-record/batches.rb
43
- - lib/occams-record/eager_loaders.rb
44
43
  - lib/occams-record/eager_loaders/ad_hoc_base.rb
45
44
  - lib/occams-record/eager_loaders/ad_hoc_many.rb
46
45
  - lib/occams-record/eager_loaders/ad_hoc_one.rb
@@ -48,6 +47,7 @@ files:
48
47
  - lib/occams-record/eager_loaders/belongs_to.rb
49
48
  - lib/occams-record/eager_loaders/builder.rb
50
49
  - lib/occams-record/eager_loaders/context.rb
50
+ - lib/occams-record/eager_loaders/eager_loaders.rb
51
51
  - lib/occams-record/eager_loaders/habtm.rb
52
52
  - lib/occams-record/eager_loaders/has_many.rb
53
53
  - lib/occams-record/eager_loaders/has_one.rb