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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b7234e9dcb7b3d4690c49318d07af329d46f031e50d63450a24b57303314f389
4
- data.tar.gz: 9517bccc29ba46045530c4028a7be33db00d84d15ad18e6d38561e7d3ec4e83e
3
+ metadata.gz: 1309eaf96974d8b58dcf9129d7562f9a4a768845f1b90ab14281cd71feeb9325
4
+ data.tar.gz: 82eed52c3a1fe90420610c53a8f613ead78d7ae38adfa9da9fc95ae32bc1717b
5
5
  SHA512:
6
- metadata.gz: 27b36deba95df20b5bd391885e4582fa825d91f29a11c3125eab8be2eb2c525803d67dcaadac0cd0debd4bcb7f128a05aa7d43bcfe10e106f4b63ab6bdf22031
7
- data.tar.gz: bd9c2b8406e3f80c87d1267f18756915e81506a64f1df228b19c8240b69818ee9dece97f5774608e5e6403736ea0907f46860962b502fab4a0f98cb3ca2875bd
6
+ metadata.gz: 2881f4c609eed7c8758b0e7f40382e1f432be17fc0df25289d5a7a264bf657c8c9efd0e4801e0020780c2dc12289ff27c58e05a6a41a8fb557ce872cb4465463
7
+ data.tar.gz: 5f45b9b3d261d5c16e40cc5ff4493de991ce5e73a206e2e655f7dde7607c8ee14eae776ac667334eb44c0e67586bdde414208a5bc4850e9c5d74a9f203081acc
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.1.2
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.0.0)
5
- activerecord (>= 5, <= 8)
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
+ [![Gem Version](https://badge.fury.io/rb/activerecord-quick_read.svg)](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
- Skips active record instantiation, query just the results and use them as plain hashes, converted
8
- to structs with the same attributes as your model. The structs will gracefully upgrade to full
9
- version of your model, in the event that you need to update them.
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
- ## Installation
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
- Use `#quick_read` on your ActiveRecord relations (or `#quick_reads` for all scoped records)
25
- Use `#quick_build` on your ActiveRecord models
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
- First, add quickness to your model:
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
- Then you're ready to start going faster!
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
- It's as simple as:
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.where(id: params[:id]).quick_read
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.all.in_batches { |batch| batch.quick_reads.map(&:to_h) }
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
- Gracefully upgrade to a full-fledged ActiveRecord object, and even issue `update` statements:
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
- Report.all.quick_reads.each { |report| report.update(status: "done") if report.message = ?? }
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/[USERNAME]/activerecord-quick_read. 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/[USERNAME]/activerecord-quick_read/blob/main/CODE_OF_CONDUCT.md).
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/[USERNAME]/activerecord-quick_read/blob/main/CODE_OF_CONDUCT.md).
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 subject.respond_to?(name)
5
+ return super unless _ar_instance.respond_to?(name)
6
6
 
7
- subject.send(name, *args, &block)
7
+ _ar_instance.send(name, *args, &block)
8
8
  end
9
9
 
10
10
  def respond_to_missing?(*args)
11
- subject.respond_to?(*args) || super
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 = @subject&.reload
25
- source ||= model.unscoped.where(id: id).select(members.join(', ')).quick_read
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module QuickRead
5
- VERSION = "1.0.0"
5
+ VERSION = "1.2.0"
6
6
  end
7
7
  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
- connection.select_all(current_scope.to_sql).map { |attrs| quick_build(attrs) }
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.0.0
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: 2023-04-22 00:00:00.000000000 Z
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: '5'
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: '5'
30
- - - "<="
31
- - !ruby/object:Gem::Version
32
- version: '8'
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: Makes rails go _faster_. Improve read times 4x!
77
+ summary: Speed up ActiveRecord reads by skipping model instantiation.
81
78
  test_files: []