n1_loader 0.1.2 → 1.0.0

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: 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