n1_loader 0.1.2 → 1.0.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: be50b582e5f3bc33bb085b9ceefb620f3c1c49b28618e953103bf47ff343ffe6
4
- data.tar.gz: bc9008a74662754eb20a798dd1969a59551f0127b6484561420335276980acf5
3
+ metadata.gz: e86d093c173eed577feb029f483c8b283f516bba1611391f9036e5675dec91d5
4
+ data.tar.gz: 626e0305af7e75fd6a5dbb23d6970a8972ca292a9449f2f32d61cf7849c57ec9
5
5
  SHA512:
6
- metadata.gz: 0bef5f775c2ac8d31ad828d8b6ef55f7a9915eb19e8f0e2169a8196cf3ed71bd5153b4bbdb36e664b5994a9a0e47b196905772470accfafcd803438d83de43c7
7
- data.tar.gz: d609dab38a23c3d0e87da754345477e5c1eaa00dcab59435b6015dfe010a1fd49f833139aa0610bc318fa467396a654b8134b3a1d2399267e85b090c22b9f995
6
+ metadata.gz: 3b450b20c25c935d3644003bf320e50644376cb6fc77cbda414522947a50857c3b28c5607bbcfd484dd9626a64523726cb4c6dd98b402a9ed3b73f1d6a40785a
7
+ data.tar.gz: 9d07c4a64e89f2c2e9aa230d86c780388aaf720002db5fac37fe9b98ffd6297b914ab49ec54306c728c3457ea7a323a52d66c3c9e78c40ccbaed0a511be1f012
data/.circleci/config.yml CHANGED
@@ -86,8 +86,15 @@ workflows:
86
86
  "latest"
87
87
  ]
88
88
  gemfile: [
89
+ "gemfiles/ar_5_latest.gemfile",
89
90
  "gemfiles/ar_6_latest.gemfile"
90
91
  ]
92
+ exclude:
93
+ - ruby-version: "3.0"
94
+ gemfile: "gemfiles/ar_5_latest.gemfile"
95
+ - ruby-version: "latest"
96
+ gemfile: "gemfiles/ar_5_latest.gemfile"
97
+
91
98
  name: << matrix.gemfile >>-build-ruby-<< matrix.ruby-version >>
92
99
  - rubocop:
93
100
  requires:
data/CHANGELOG.md CHANGED
@@ -1,5 +1,7 @@
1
- ## [Unreleased]
1
+ ## [1.0.0] - 2021-12-26
2
+
3
+ - Various of great features.
2
4
 
3
5
  ## [0.1.0] - 2021-12-16
4
6
 
5
- - Initial release
7
+ - Initial release.
data/README.md CHANGED
@@ -9,11 +9,15 @@ We have a solution for you!
9
9
  [N1Loader][8] is designed to solve the issue for good!
10
10
 
11
11
  It has many benefits:
12
- - it loads data lazily (even when you initialized preloading)
13
- - it supports shared loaders between multiple classes
12
+ - it can be [isolated](#isolated-loaders)
13
+ - it loads data [lazily](#lazy-loading)
14
+ - it supports [shareable loaders](#shareable-loaders) between multiple classes
15
+ - it supports [reloading](#reloading)
16
+ - it supports optimized [single object loading](#optimized-single-case)
14
17
  - it has an integration with [ActiveRecord][5] which makes it brilliant ([example](#activerecord))
15
18
  - it has an integration with [ArLazyPreload][6] which makes it excellent ([example](#arlazypreload))
16
19
 
20
+ ... and even more features to come! Stay tuned!
17
21
 
18
22
  ## Installation
19
23
 
@@ -25,16 +29,18 @@ gem 'n1_loader'
25
29
 
26
30
  You can add integration with [ActiveRecord][5] by:
27
31
  ```ruby
28
- require 'n1_loader/active_record'
32
+ gem 'n1_loader', require: 'n1_loader/active_record'
29
33
  ```
30
34
 
31
35
  You can add the integration with [ActiveRecord][5] and [ArLazyPreload][6] by:
32
36
  ```ruby
33
- require 'n1_loader/ar_lazy_preload'
37
+ gem 'n1_loader', require: 'n1_loader/ar_lazy_preload'
34
38
  ```
35
39
 
36
40
  ## Usage
37
41
 
42
+ **Supported Ruby version:** 2.5, 2.6, 2.7, 3.0, and latest.
43
+
38
44
  ```ruby
39
45
  class Example
40
46
  include N1Loader::Loadable
@@ -67,8 +73,132 @@ N1Loader::Preloader.new(objects).preload(:anything)
67
73
  objects.map(&:anything)
68
74
  ```
69
75
 
76
+ ### Lazy loading
77
+
78
+ ```ruby
79
+ class Example
80
+ include N1Loader::Loadable
81
+
82
+ n1_loader :anything do |elements|
83
+ # Has to return a hash that has keys as element from elements
84
+ elements.group_by(&:itself)
85
+ end
86
+ end
87
+
88
+ object = Example.new # => nothing was done for loading
89
+ object.anything # => first time loading
90
+
91
+ objects = [Example.new, Example.new] # => nothing was done for loading
92
+ N1Loader::Preloader.new([objects]).preload(:anything) # => we only initial loader but didn't perform it yet
93
+ objects.map(&:anything) # => loading happen for the first time (without N+1)
94
+ ```
95
+
96
+
97
+ ### Shareable loaders
98
+
99
+ ```ruby
100
+ class MyLoader < N1Loader::Loader
101
+ def perform(elements)
102
+ # Has to return a hash that has keys as element from elements
103
+ elements.group_by(&:itself)
104
+ end
105
+ end
106
+
107
+ class A
108
+ include N1Loader::Loadable
109
+
110
+ n1_loader :anything, MyLoader
111
+ end
112
+
113
+ class B
114
+ include N1Loader::Loadable
115
+
116
+ n1_loader :something, MyLoader
117
+ end
118
+
119
+ A.new.anything # => works
120
+ B.new.something # => works
121
+ ```
122
+
123
+ ### Reloading
124
+
125
+ ```ruby
126
+ class Example
127
+ include N1Loader::Loadable
128
+
129
+ # with inline loader
130
+ n1_loader :anything do |elements|
131
+ # Has to return a hash that has keys as element from elements
132
+ elements.group_by(&:itself)
133
+ end
134
+ end
135
+
136
+ object = Example.new
137
+ object.anything # => loader is executed first time and value was cached
138
+ object.anything(reload: true) # => loader is executed again and a new value was cached
139
+
140
+ objects = [Example.new, Example.new]
141
+
142
+ N1Loader::Preloader.new(objects).preload(:anything) # => loader was initialized but not yet executed
143
+ objects.map(&:anything) # => loader was executed first time without N+1 issue and values were cached
144
+
145
+ N1Loader::Preloader.new(objects).preload(:anything) # => loader was initialized again but not yet executed
146
+ objects.map(&:anything) # => new loader was executed first time without N+1 issue and new values were cached
147
+ ```
148
+
149
+ ### Isolated loaders
150
+
151
+ ```ruby
152
+ class MyLoader < N1Loader::Loader
153
+ def perform(elements)
154
+ # Has to return a hash that has keys as element from elements
155
+ elements.group_by(&:itself)
156
+ end
157
+ end
158
+
159
+ objects = [1, 2, 3, 4]
160
+ loader = MyLoader.new(objects)
161
+ objects.each do |object|
162
+ loader.for(object) # => it has no N+1 and it doesn't require to be injected in the class
163
+ end
164
+ ```
165
+
166
+ ### Optimized single case
167
+
168
+ ```ruby
169
+ class Example
170
+ include N1Loader::Loadable
171
+
172
+ n1_loader :something do # no arguments passed to the block, so we can override both perform and single.
173
+ def perform(elements)
174
+ # Has to return a hash that has keys as element from elements
175
+ elements.group_by(&:itself)
176
+ end
177
+
178
+ # Optimized for single object loading
179
+ def single(element)
180
+ # Just return a value you want to have for this element
181
+ element
182
+ end
183
+ end
184
+ end
185
+
186
+ object = Example.new
187
+ object.something # single will be used here
188
+
189
+ objects = [Example.new, Example.new]
190
+ N1Loader::Preloader.new(objects).preload(:something)
191
+ objects.map(&:something) # perform will be used once without N+1
192
+ ```
193
+
194
+ ## Integrations
195
+
70
196
  ### [ActiveRecord][5]
71
197
 
198
+ **Supported versions**: 5, 6.
199
+
200
+ _Note_: Please open an issue if you interested in support of version 7 or other.
201
+
72
202
  ```ruby
73
203
  class User < ActiveRecord::Base
74
204
  include N1Loader::Loadable
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 5"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module N1Loader
4
+ module ActiveRecord
5
+ module Associations
6
+ module Preloader # :nodoc:
7
+ N1LoaderReflection = Struct.new(:key, :loader) do
8
+ def options
9
+ {}
10
+ end
11
+ end
12
+
13
+ def preloaders_for_one(association, records, scope)
14
+ grouped_records(association, records).flat_map do |reflection, klasses|
15
+ next N1Loader::Preloader.new(records).preload(reflection.key) if reflection.is_a?(N1LoaderReflection)
16
+
17
+ klasses.map do |rhs_klass, rs|
18
+ loader = preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope)
19
+ loader.run self
20
+ loader
21
+ end
22
+ end
23
+ end
24
+
25
+ def grouped_records(association, records)
26
+ n1_load_records, records = records.partition do |record|
27
+ record.class.respond_to?(:n1_loader_defined?) && record.class.n1_loader_defined?(association)
28
+ end
29
+
30
+ hash = n1_load_records.group_by do |record|
31
+ N1LoaderReflection.new(association, record.class.n1_loader(association))
32
+ end
33
+
34
+ hash.merge(super)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -4,8 +4,13 @@ require_relative "../n1_loader"
4
4
 
5
5
  require "active_record"
6
6
 
7
- require_relative "active_record/associations_preloader"
8
-
9
7
  ActiveSupport.on_load(:active_record) do
8
+ case ActiveRecord::VERSION::MAJOR
9
+ when 6
10
+ require_relative "active_record/associations_preloader_v6"
11
+ else
12
+ require_relative "active_record/associations_preloader_v5"
13
+ end
14
+
10
15
  ActiveRecord::Associations::Preloader.prepend(N1Loader::ActiveRecord::Associations::Preloader)
11
16
  end
@@ -6,7 +6,7 @@ module N1Loader
6
6
  class ContextAdapter
7
7
  attr_reader :context
8
8
 
9
- delegate :records, :auto_preload?, to: :context
9
+ delegate_missing_to :context
10
10
 
11
11
  def initialize(context)
12
12
  @context = context
@@ -47,9 +47,13 @@ module N1Loader
47
47
  respond_to?("#{name}_loader")
48
48
  end
49
49
 
50
- def n1_load(name, loader = nil, &block) # rubocop:disable Metrics/MethodLength
50
+ def n1_load(name, loader = nil, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
51
51
  loader ||= Class.new(N1Loader::Loader) do
52
- define_method(:perform, &block)
52
+ if block && block.arity == 1
53
+ define_method(:perform, &block)
54
+ else
55
+ class_eval(&block)
56
+ end
53
57
  end
54
58
 
55
59
  loader_name = "#{name}_loader"
@@ -59,16 +63,21 @@ module N1Loader
59
63
  loader
60
64
  end
61
65
 
66
+ define_method("#{loader_name}_reload") do
67
+ instance_variable_set(loader_variable_name, self.class.send(loader_name).new([self]))
68
+ end
69
+
62
70
  define_method("#{loader_name}=") do |loader_instance|
63
71
  instance_variable_set(loader_variable_name, loader_instance)
64
72
  end
65
73
 
66
74
  define_method(loader_name) do
67
- instance_variable_get(loader_variable_name) ||
68
- instance_variable_set(loader_variable_name, self.class.send(loader_name).new([self]))
75
+ instance_variable_get(loader_variable_name) || send("#{loader_name}_reload")
69
76
  end
70
77
 
71
- define_method(name) do
78
+ define_method(name) do |reload: false|
79
+ send("#{loader_name}_reload") if reload
80
+
72
81
  send(loader_name).for(self)
73
82
  end
74
83
 
@@ -15,7 +15,11 @@ module N1Loader
15
15
  end
16
16
 
17
17
  def loaded
18
- @loaded ||= perform(elements)
18
+ @loaded ||= if elements.size == 1 && respond_to?(:single)
19
+ { elements.first => single(elements.first) }
20
+ else
21
+ perform(elements)
22
+ end
19
23
  end
20
24
 
21
25
  def preloaded_records
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module N1Loader
4
- VERSION = "0.1.2"
4
+ VERSION = "1.0.0"
5
5
  end
data/n1_loader.gemspec CHANGED
@@ -22,10 +22,10 @@ Gem::Specification.new do |spec|
22
22
  end
23
23
  spec.require_paths = ["lib"]
24
24
 
25
- spec.add_development_dependency "activerecord", "~> 6.0"
25
+ spec.add_development_dependency "activerecord", ">= 5"
26
26
  spec.add_development_dependency "ar_lazy_preload", "~> 0.7"
27
27
  spec.add_development_dependency "db-query-matchers", "~> 0.10"
28
- spec.add_development_dependency "rails", "~> 6.0"
28
+ spec.add_development_dependency "rails", ">= 5"
29
29
  spec.add_development_dependency "rspec", "~> 3.0"
30
30
  spec.add_development_dependency "rspec_junit_formatter", "~> 0.4"
31
31
  spec.add_development_dependency "rubocop", "~> 1.7"
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: n1_loader
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evgeniy Demin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-21 00:00:00.000000000 Z
11
+ date: 2021-12-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '6.0'
19
+ version: '5'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '6.0'
26
+ version: '5'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: ar_lazy_preload
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -56,16 +56,16 @@ dependencies:
56
56
  name: rails
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '6.0'
61
+ version: '5'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '6.0'
68
+ version: '5'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -141,10 +141,12 @@ files:
141
141
  - Rakefile
142
142
  - bin/console
143
143
  - bin/setup
144
+ - gemfiles/ar_5_latest.gemfile
144
145
  - gemfiles/ar_6_latest.gemfile
145
146
  - lib/n1_loader.rb
146
147
  - lib/n1_loader/active_record.rb
147
- - lib/n1_loader/active_record/associations_preloader.rb
148
+ - lib/n1_loader/active_record/associations_preloader_v5.rb
149
+ - lib/n1_loader/active_record/associations_preloader_v6.rb
148
150
  - lib/n1_loader/ar_lazy_preload.rb
149
151
  - lib/n1_loader/ar_lazy_preload/associated_context_builder.rb
150
152
  - lib/n1_loader/ar_lazy_preload/context_adapter.rb