activerecord-quick_read 1.0.0 → 1.2.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/.ruby-version +1 -1
- data/CHANGELOG.md +7 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +14 -2
- data/README.md +108 -16
- data/activerecord-quick_read.gemspec +40 -0
- data/lib/activerecord/quick_read/lite_base.rb +16 -14
- data/lib/activerecord/quick_read/version.rb +1 -1
- data/lib/activerecord/quick_read.rb +4 -3
- metadata +9 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1309eaf96974d8b58dcf9129d7562f9a4a768845f1b90ab14281cd71feeb9325
|
|
4
|
+
data.tar.gz: 82eed52c3a1fe90420610c53a8f613ead78d7ae38adfa9da9fc95ae32bc1717b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2881f4c609eed7c8758b0e7f40382e1f432be17fc0df25289d5a7a264bf657c8c9efd0e4801e0020780c2dc12289ff27c58e05a6a41a8fb557ce872cb4465463
|
|
7
|
+
data.tar.gz: 5f45b9b3d261d5c16e40cc5ff4493de991ce5e73a206e2e655f7dde7607c8ee14eae776ac667334eb44c0e67586bdde414208a5bc4850e9c5d74a9f203081acc
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.4.7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.2.0] - 2026-08-26
|
|
4
|
+
|
|
5
|
+
- Added support for older Rails 5 (`parent` fallback for `module_parent`)
|
|
6
|
+
- Removed the `activerecord` version bounds, enabling Rails 8.x (e.g. 8.0.5.1) that were previously excluded by `<= 8`
|
|
7
|
+
- Fixed `quick_read` and `quick_reads` when called directly on a model
|
|
8
|
+
- Expanded the README and test suite
|
|
9
|
+
|
|
3
10
|
## [0.1.0] - 2023-04-21
|
|
4
11
|
|
|
5
12
|
- Initial release
|
data/Gemfile
CHANGED
|
@@ -9,4 +9,12 @@ gem "rake", "~> 13.0"
|
|
|
9
9
|
|
|
10
10
|
gem "rspec", "~> 3.0"
|
|
11
11
|
|
|
12
|
+
gem "sqlite3", "~> 1.6"
|
|
13
|
+
|
|
12
14
|
gem "standard", "~> 1.3"
|
|
15
|
+
|
|
16
|
+
# Gems extracted from Ruby's stdlib in Ruby 3.4+ that Rails 7.0 still requires
|
|
17
|
+
gem "mutex_m"
|
|
18
|
+
gem "base64"
|
|
19
|
+
gem "bigdecimal"
|
|
20
|
+
gem "racc"
|
data/Gemfile.lock
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
activerecord-quick_read (1.
|
|
5
|
-
activerecord
|
|
4
|
+
activerecord-quick_read (1.2.0)
|
|
5
|
+
activerecord
|
|
6
6
|
|
|
7
7
|
GEM
|
|
8
8
|
remote: https://rubygems.org/
|
|
@@ -18,16 +18,21 @@ GEM
|
|
|
18
18
|
minitest (>= 5.1)
|
|
19
19
|
tzinfo (~> 2.0)
|
|
20
20
|
ast (2.4.2)
|
|
21
|
+
base64 (0.3.0)
|
|
22
|
+
bigdecimal (4.1.2)
|
|
21
23
|
concurrent-ruby (1.2.2)
|
|
22
24
|
diff-lcs (1.5.0)
|
|
23
25
|
i18n (1.12.0)
|
|
24
26
|
concurrent-ruby (~> 1.0)
|
|
25
27
|
json (2.6.3)
|
|
26
28
|
language_server-protocol (3.17.0.3)
|
|
29
|
+
mini_portile2 (2.8.9)
|
|
27
30
|
minitest (5.18.0)
|
|
31
|
+
mutex_m (0.3.0)
|
|
28
32
|
parallel (1.23.0)
|
|
29
33
|
parser (3.2.2.0)
|
|
30
34
|
ast (~> 2.4.1)
|
|
35
|
+
racc (1.8.1)
|
|
31
36
|
rainbow (3.1.1)
|
|
32
37
|
rake (13.0.6)
|
|
33
38
|
regexp_parser (2.8.0)
|
|
@@ -61,6 +66,8 @@ GEM
|
|
|
61
66
|
rubocop (>= 1.7.0, < 2.0)
|
|
62
67
|
rubocop-ast (>= 0.4.0)
|
|
63
68
|
ruby-progressbar (1.13.0)
|
|
69
|
+
sqlite3 (1.7.3)
|
|
70
|
+
mini_portile2 (~> 2.8.0)
|
|
64
71
|
standard (1.27.0)
|
|
65
72
|
language_server-protocol (~> 3.17.0.2)
|
|
66
73
|
rubocop (~> 1.50.2)
|
|
@@ -75,8 +82,13 @@ PLATFORMS
|
|
|
75
82
|
|
|
76
83
|
DEPENDENCIES
|
|
77
84
|
activerecord-quick_read!
|
|
85
|
+
base64
|
|
86
|
+
bigdecimal
|
|
87
|
+
mutex_m
|
|
88
|
+
racc
|
|
78
89
|
rake (~> 13.0)
|
|
79
90
|
rspec (~> 3.0)
|
|
91
|
+
sqlite3 (~> 1.6)
|
|
80
92
|
standard (~> 1.3)
|
|
81
93
|
|
|
82
94
|
BUNDLED WITH
|
data/README.md
CHANGED
|
@@ -1,15 +1,37 @@
|
|
|
1
1
|
# ActiveRecord::QuickRead
|
|
2
2
|
|
|
3
|
+
[](https://badge.fury.io/rb/activerecord-quick_read)
|
|
4
|
+
|
|
3
5
|
Makes rails go _faster_. Improve read times 4x!
|
|
4
6
|
|
|
5
7
|
### How does it work?
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
A normal ActiveRecord query does more than run SQL. Once the rows come back, ActiveRecord
|
|
10
|
+
instantiates a model object for **every** row: allocating the object, running `initialize`,
|
|
11
|
+
typecasting each column, setting up dirty tracking, and evaluating any `after_initialize` callbacks.
|
|
12
|
+
For large result sets that per-row work adds up fast, and you end up paying for features you may not
|
|
13
|
+
even use (like attribute change tracking) when all you wanted was to *read* the data.
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
`quick_read` sidesteps all of that. It still runs the exact same scoped SQL through
|
|
16
|
+
`connection.select_all`, which returns each row as a plain hash. Then, instead of building model
|
|
17
|
+
objects, each hash is wrapped in a lightweight `Struct` whose members mirror the model's columns:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
# the Lite class generated for your model, e.g.:
|
|
21
|
+
Report::Lite = Struct.new(:id, :message, :status, :created_at, :updated_at)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Because structs are plain Ruby objects there's no typecasting, no dirty tracking, no
|
|
25
|
+
`after_initialize` callbacks, and far less allocation. The result is the same data you asked for,
|
|
26
|
+
returned 4x faster.
|
|
12
27
|
|
|
28
|
+
The structs are *upgradeable*, not throwaway. Each `Lite` instance knows its source model and can
|
|
29
|
+
lazily materialize a full ActiveRecord object on demand (via `method_missing`). So when you call a
|
|
30
|
+
method the struct doesn't have (like `save`, `update`, or an association such as `report.author`) it
|
|
31
|
+
transparently builds the real model behind the scenes and delegates to it. You only pay the cost of
|
|
32
|
+
instantiation when you actually need to write, not when you're just reading.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
13
35
|
|
|
14
36
|
Install the gem and add to the application's Gemfile by executing:
|
|
15
37
|
|
|
@@ -21,32 +43,102 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
|
21
43
|
|
|
22
44
|
## Usage
|
|
23
45
|
|
|
24
|
-
|
|
25
|
-
Use `#quick_build` on your
|
|
46
|
+
Add quickness to your models, then call `#quick_read` on an ActiveRecord relation to get a single
|
|
47
|
+
record, or `#quick_reads` to get all scoped records. Use `#quick_build` on your models to build a
|
|
48
|
+
lite instance from a hash.
|
|
26
49
|
|
|
27
|
-
|
|
28
|
-
|
|
50
|
+
### Enable on a single model
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
29
53
|
class Report < ApplicationRecord
|
|
30
54
|
extend ActiveRecord::QuickRead
|
|
31
55
|
end
|
|
32
56
|
```
|
|
33
57
|
|
|
34
|
-
|
|
58
|
+
### Enable for every model via ApplicationRecord
|
|
59
|
+
|
|
60
|
+
Instead of extending each model individually, extend `ApplicationRecord` once and every subclass
|
|
61
|
+
inherits the quick read behavior:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
class ApplicationRecord < ActiveRecord::Base
|
|
65
|
+
self.abstract_class = true
|
|
66
|
+
|
|
67
|
+
extend ActiveRecord::QuickRead
|
|
68
|
+
end
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Because models aren't fully defined until boot, the `Lite` structs are built lazily after Rails
|
|
72
|
+
initializes (via the included railtie). Any class that inherits from `ApplicationRecord`, even ones
|
|
73
|
+
defined in engines or gems, automatically gets a `Lite` struct.
|
|
74
|
+
|
|
75
|
+
### Reading
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
# A single record
|
|
79
|
+
report = Report.where(id: params[:id]).quick_read
|
|
80
|
+
report.id # => 1
|
|
81
|
+
report.message # => "All done"
|
|
82
|
+
report.class # => Report::Lite
|
|
83
|
+
|
|
84
|
+
# All scoped records
|
|
85
|
+
reports = Report.where(status: "done").quick_reads
|
|
86
|
+
reports.first.title
|
|
87
|
+
```
|
|
35
88
|
|
|
36
|
-
|
|
89
|
+
The returned `Report::Lite` is a struct with the same attributes (and `#to_h`) as your model:
|
|
37
90
|
|
|
38
91
|
```ruby
|
|
39
|
-
Report.
|
|
92
|
+
Report.order(:created_at).quick_reads.map(&:to_h)
|
|
40
93
|
```
|
|
41
94
|
|
|
95
|
+
### Batching
|
|
96
|
+
|
|
42
97
|
Load your batches 4x quicker:
|
|
98
|
+
|
|
43
99
|
```ruby
|
|
44
|
-
Report.
|
|
100
|
+
Report.in_batches do |batch|
|
|
101
|
+
batch.quick_reads.each do |report|
|
|
102
|
+
puts report.message
|
|
103
|
+
end
|
|
104
|
+
end
|
|
45
105
|
```
|
|
46
106
|
|
|
47
|
-
|
|
107
|
+
### Building
|
|
108
|
+
|
|
109
|
+
Build a lite instance from attributes without hitting the database:
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
draft = Report.quick_build(id: 1, message: "hello")
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Upgrading to a full Active Record object
|
|
116
|
+
|
|
117
|
+
Lite instances lazily upgrade to full-fledged ActiveRecord objects on demand. Calling any method the
|
|
118
|
+
struct doesn't have (like `save`, `update`, or association accessors) transparently materializes the
|
|
119
|
+
underlying model:
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
Report.where(status: "queued").quick_reads.each do |report|
|
|
123
|
+
report.update(status: "done")
|
|
124
|
+
end
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
report = Report.first.quick_read
|
|
129
|
+
report.author # => loads the belongs_to association on the full model
|
|
130
|
+
report.save!
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Reloading
|
|
134
|
+
|
|
135
|
+
Refresh a lite instance with fresh data from the database:
|
|
136
|
+
|
|
48
137
|
```ruby
|
|
49
|
-
|
|
138
|
+
report = Report.first.quick_read
|
|
139
|
+
# ... the underlying row changes elsewhere ...
|
|
140
|
+
report.reload
|
|
141
|
+
report.message # => updated value
|
|
50
142
|
```
|
|
51
143
|
|
|
52
144
|
It just works.
|
|
@@ -59,7 +151,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
|
59
151
|
|
|
60
152
|
## Contributing
|
|
61
153
|
|
|
62
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
154
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ridiculous/activerecord-quick_reads. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/ridiculous/activerecord-quick_reads/blob/main/CODE_OF_CONDUCT.md).
|
|
63
155
|
|
|
64
156
|
## License
|
|
65
157
|
|
|
@@ -67,4 +159,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
|
67
159
|
|
|
68
160
|
## Code of Conduct
|
|
69
161
|
|
|
70
|
-
Everyone interacting in the ActiveRecord::QuickRead project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/
|
|
162
|
+
Everyone interacting in the ActiveRecord::QuickRead project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/ridiculous/activerecord-quick_reads/blob/main/CODE_OF_CONDUCT.md).
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/activerecord/quick_read/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "activerecord-quick_read"
|
|
7
|
+
spec.version = ActiveRecord::QuickRead::VERSION
|
|
8
|
+
spec.authors = ["Ryan Buckley"]
|
|
9
|
+
spec.email = ["ridiculous@hey.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Speed up ActiveRecord reads by skipping model instantiation."
|
|
12
|
+
spec.description = "Faster ActiveRecord reads. Skips model instantiation and returns lightweight objects (with untypecast values) " \
|
|
13
|
+
"just for reading. Fallback to full-fledged ActiveRecords on method_missing."
|
|
14
|
+
spec.homepage = "https://github.com/ridiculous/activerecord-quick_reads"
|
|
15
|
+
spec.license = "MIT"
|
|
16
|
+
spec.required_ruby_version = ">= 2.6.0"
|
|
17
|
+
|
|
18
|
+
# spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
|
|
19
|
+
|
|
20
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
21
|
+
spec.metadata["source_code_uri"] = "https://github.com/ridiculous/activerecord-quick_reads"
|
|
22
|
+
spec.metadata["changelog_uri"] = "https://github.com/ridiculous/activerecord-quick_reads/CHANGELOG.md"
|
|
23
|
+
|
|
24
|
+
# Specify which files should be added to the gem when it is released.
|
|
25
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
26
|
+
spec.files = Dir.chdir(__dir__) do
|
|
27
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
28
|
+
(File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
spec.bindir = "exe"
|
|
32
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
33
|
+
spec.require_paths = ["lib"]
|
|
34
|
+
|
|
35
|
+
# Uncomment to register a new dependency of your gem
|
|
36
|
+
spec.add_dependency "activerecord"
|
|
37
|
+
|
|
38
|
+
# For more information and examples about making a new gem, check out our
|
|
39
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
|
40
|
+
end
|
|
@@ -2,32 +2,34 @@ module ActiveRecord
|
|
|
2
2
|
module QuickRead
|
|
3
3
|
module LiteBase
|
|
4
4
|
def method_missing(name, *args, &block)
|
|
5
|
-
return super unless
|
|
5
|
+
return super unless _ar_instance.respond_to?(name)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
_ar_instance.send(name, *args, &block)
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def respond_to_missing?(*args)
|
|
11
|
-
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def subject
|
|
15
|
-
@subject ||= model.from_hash(to_h)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# Since the struct that includes this module is defined within the model's namespace
|
|
19
|
-
def model
|
|
20
|
-
self.class.module_parent
|
|
11
|
+
_ar_instance.respond_to?(*args) || super
|
|
21
12
|
end
|
|
22
13
|
|
|
23
14
|
def reload(*)
|
|
24
|
-
source = @
|
|
25
|
-
source ||=
|
|
15
|
+
source = @_ar_instance&.reload
|
|
16
|
+
source ||= _ar_model.unscoped.where(id: id).quick_read
|
|
26
17
|
return false unless source
|
|
27
18
|
|
|
28
19
|
members.each { |attr| send(:"#{attr}=", source.send(attr)) }
|
|
29
20
|
self
|
|
30
21
|
end
|
|
22
|
+
|
|
23
|
+
# Private
|
|
24
|
+
|
|
25
|
+
def _ar_instance
|
|
26
|
+
@_ar_instance ||= _ar_model.from_hash(to_h)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Since the struct that includes this module is defined within the model's namespace
|
|
30
|
+
def _ar_model
|
|
31
|
+
self.class.respond_to?(:module_parent) ? self.class.module_parent : self.class.parent
|
|
32
|
+
end
|
|
31
33
|
end
|
|
32
34
|
end
|
|
33
35
|
end
|
|
@@ -35,7 +35,7 @@ module ActiveRecord
|
|
|
35
35
|
|
|
36
36
|
ActiveRecord::Base.logger.debug("QuickRead") { "Defining #{QuickRead.models.size} quick models" }
|
|
37
37
|
time = Benchmark.realtime do
|
|
38
|
-
while model = models.pop
|
|
38
|
+
while (model = models.pop)
|
|
39
39
|
define_lite_struct(model)
|
|
40
40
|
end
|
|
41
41
|
end
|
|
@@ -64,11 +64,12 @@ module ActiveRecord
|
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
def quick_reads
|
|
67
|
-
|
|
67
|
+
scope = current_scope || all
|
|
68
|
+
connection.select_all(scope.to_sql).map { |attrs| quick_build(attrs) }
|
|
68
69
|
end
|
|
69
70
|
|
|
70
71
|
def quick_read
|
|
71
|
-
current_scope.limit(1).quick_reads.first
|
|
72
|
+
(current_scope || all).limit(1).quick_reads.first
|
|
72
73
|
end
|
|
73
74
|
|
|
74
75
|
# Instantiate a new ActiveRecord object from a plain hash, marked as persisted, no changes, typecast
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activerecord-quick_read
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ryan Buckley
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-08-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -16,21 +16,17 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
20
|
-
- - "<="
|
|
21
|
-
- !ruby/object:Gem::Version
|
|
22
|
-
version: '8'
|
|
19
|
+
version: '0'
|
|
23
20
|
type: :runtime
|
|
24
21
|
prerelease: false
|
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
26
23
|
requirements:
|
|
27
24
|
- - ">="
|
|
28
25
|
- !ruby/object:Gem::Version
|
|
29
|
-
version: '
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
description: Makes rails go _faster_. Improve read times 4x!
|
|
26
|
+
version: '0'
|
|
27
|
+
description: Faster ActiveRecord reads. Skips model instantiation and returns lightweight
|
|
28
|
+
objects (with untypecast values) just for reading. Fallback to full-fledged ActiveRecords
|
|
29
|
+
on method_missing.
|
|
34
30
|
email:
|
|
35
31
|
- ridiculous@hey.com
|
|
36
32
|
executables: []
|
|
@@ -48,6 +44,7 @@ files:
|
|
|
48
44
|
- LICENSE.txt
|
|
49
45
|
- README.md
|
|
50
46
|
- Rakefile
|
|
47
|
+
- activerecord-quick_read.gemspec
|
|
51
48
|
- lib/activerecord/quick_read.rb
|
|
52
49
|
- lib/activerecord/quick_read/lite_base.rb
|
|
53
50
|
- lib/activerecord/quick_read/railtie.rb
|
|
@@ -77,5 +74,5 @@ requirements: []
|
|
|
77
74
|
rubygems_version: 3.3.7
|
|
78
75
|
signing_key:
|
|
79
76
|
specification_version: 4
|
|
80
|
-
summary:
|
|
77
|
+
summary: Speed up ActiveRecord reads by skipping model instantiation.
|
|
81
78
|
test_files: []
|