n1_loader 2.2.0 → 3.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: 56479004f7a3456664bac05e808268177dabffa616848b57efb734865fd5ec49
4
- data.tar.gz: 2d65c334abe26c5eb9baeb17ebe186d201b6c362b60d628758defd696b3e3522
3
+ metadata.gz: 993f807b671a08f82ed5ad7eff20cae7ab4d8e910772ba944543f8a46343c313
4
+ data.tar.gz: d0489f877243e19caf4c5b8fab6083dbe283aad6e89199386d611745840065ce
5
5
  SHA512:
6
- metadata.gz: 9d83a5cc7742f23b0702f56fbcd2320f46e309731c7734df825406a657709259dae08e2e3ed78db322f815438e5e0db82cbe40799e02bb7d0f7cd469ad453585
7
- data.tar.gz: 225fa5c982e60ceaae8d5a8cbd90ff6ecd8d54cff5105bc1338d45e6fb9969586af5ced3fa6b3a7c6948a453ee7bd7956a30f2972527719d32467116da48f7ce
6
+ metadata.gz: f47c6eb0400062948a5bb9c19cbb71b56cc10615773f5dd031fcc138485303ddbd3bb9b9f0070afc5f5baaaebd917c8da0f2c00dd93a29956bcdfca9d6581a73
7
+ data.tar.gz: 7cbcf9de6b8b20b0e2d100d79f787343f542200da3ec234d0f13ef6353ca0bbf3e05afd06584528d70701ff08bfd461d63c9c83625164f9579123417a092a406
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  N1Loader::Loader.define_method :preloaded_records do
4
- @preloaded_records ||= loaded.values.flatten
4
+ @preloaded_records ||= loaded? && loaded_by_value.values.flatten
5
5
  end
@@ -6,17 +6,22 @@ module N1Loader
6
6
  module LoaderPatch
7
7
  attr_accessor :context_setup
8
8
 
9
- def loaded
10
- return @loaded_by_identity if @already_loaded && @already_context
9
+ def loaded?
10
+ return true if @already_loaded && @already_context
11
11
 
12
12
  super
13
13
 
14
- synchronize do
15
- context_setup&.call(@loaded_by_identity.values.flatten) unless @already_context
16
- end
14
+ synchronize { non_thread_safe_context_setting unless @already_context }
15
+
16
+ true
17
+ end
18
+
19
+ def non_thread_safe_context_setting
20
+ return if @already_context
21
+
22
+ context_setup&.call(loaded_by_identity.values.flatten)
17
23
 
18
24
  @already_context = true
19
- @loaded_by_identity
20
25
  end
21
26
  end
22
27
  end
@@ -33,9 +33,21 @@ module N1Loader
33
33
  end
34
34
 
35
35
  def n1_bind_to(collection)
36
+ unless collection.is_a?(Array) && collection.any? do |obj|
37
+ obj == self || obj.equal?(self)
38
+ end
39
+
40
+ raise InvalidBinding,
41
+ "assigned collection should be array and include object"
42
+ end
43
+
36
44
  @n1_binding = collection
37
45
  end
38
46
 
47
+ def n1_bind_to?
48
+ !@n1_binding.nil?
49
+ end
50
+
39
51
  def n1_loader_reload(name)
40
52
  elements = @n1_binding || [self]
41
53
  collection = LoaderCollection.new(self.class.n1_loaders[name], elements)
@@ -47,14 +47,14 @@ module N1Loader
47
47
  end
48
48
 
49
49
  def for(element)
50
- identity_loaded = loaded
50
+ return unless loaded?
51
51
 
52
- if identity_loaded.empty? && elements.any?
52
+ if loaded_by_identity.empty? && elements.any?
53
53
  raise NotFilled, "Nothing was preloaded, perhaps you forgot to use fulfill method"
54
54
  end
55
55
 
56
- return identity_loaded[element] if identity_loaded.key?(element)
57
- return @loaded_by_value[element] if @loaded_by_value.key?(element)
56
+ return loaded_by_identity[element] if loaded_by_identity.key?(element)
57
+ return loaded_by_value[element] if loaded_by_value.key?(element)
58
58
 
59
59
  raise NotLoaded, "The data was not preloaded for the given element"
60
60
  end
@@ -66,7 +66,7 @@ module N1Loader
66
66
 
67
67
  private
68
68
 
69
- attr_reader :elements, :args
69
+ attr_reader :elements, :args, :loaded_by_value, :loaded_by_identity
70
70
 
71
71
  def check_missing_arguments!
72
72
  return unless (arguments = self.class.arguments)
@@ -107,22 +107,19 @@ module N1Loader
107
107
  end
108
108
 
109
109
  def fulfill(element, value)
110
- @loaded_by_identity[element] = value
111
- @loaded_by_value[element] = value
110
+ loaded_by_identity[element] = value
111
+ loaded_by_value[element] = value
112
112
  end
113
113
 
114
- def ensure_loaded
115
- return if @already_loaded
114
+ def loaded?
115
+ return true if @already_loaded
116
116
 
117
- synchronize { non_thread_safe_loaded unless @already_loaded }
118
- end
117
+ synchronize { non_thread_safe_loading unless @already_loaded }
119
118
 
120
- def loaded
121
- ensure_loaded
122
- @loaded_by_identity
119
+ true
123
120
  end
124
121
 
125
- def non_thread_safe_loaded # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
122
+ def non_thread_safe_loading # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
126
123
  return if @already_loaded
127
124
 
128
125
  check_arguments!
@@ -133,8 +130,13 @@ module N1Loader
133
130
  if respond_to?(:single) && elements.size == 1
134
131
  fulfill(elements.first, single(elements.first))
135
132
  elsif elements.any?
136
- elements.each { |el| el.n1_bind_to(elements) if el.respond_to?(:n1_bind_to) }
137
133
  perform(elements)
134
+
135
+ # propagate context to loaded objects only when it was set
136
+ if elements.first.respond_to?(:n1_bind_to?) && elements.first.n1_bind_to?
137
+ loaded_objects = loaded_by_identity.values.flatten
138
+ loaded_objects.each { |el| el.n1_bind_to(loaded_objects) if el.respond_to?(:n1_bind_to) }
139
+ end
138
140
  end
139
141
 
140
142
  @already_loaded = true
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Returns cached N1Loader::LoaderCollection from context for a loader.
4
+ # In case there is none yet, saves passed block to a cache.
5
+ Goldiloader::AutoIncludeContext.define_method :fetch_n1_loader_collection do |loader, &block|
6
+ (@n1_loader_collections ||= {})[loader] ||= block.call
7
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module N1Loader
4
+ module Goldiloader
5
+ # Context adapter for injected N1Loader loaders.
6
+ class ContextAdapter
7
+ attr_reader :context
8
+
9
+ def initialize(context)
10
+ @context = context
11
+ end
12
+
13
+ # Trigger preloading for +association_name+ across all models in the context.
14
+ def try_preload_lazily(association_name)
15
+ perform_preloading(association_name) if context
16
+ end
17
+
18
+ # Initialize preloader for +association_name+ with context builder callback.
19
+ # The callback will be executed when records are loaded.
20
+ def perform_preloading(association_name)
21
+ context_setup = lambda { |records|
22
+ ar_records = records.flatten(1).select { |record| record.respond_to?(:auto_include_context=) }
23
+ ::Goldiloader::AutoIncludeContext.register_models(ar_records) unless ar_records.empty?
24
+ }
25
+
26
+ N1Loader::Preloader.new(context.models, context_setup).preload(association_name)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module N1Loader
4
+ module Goldiloader
5
+ module Loadable # :nodoc:
6
+ def n1_loader(name)
7
+ return n1_loaders[name] if n1_loaders[name]
8
+
9
+ ContextAdapter.new(auto_include_context).try_preload_lazily(name) if respond_to?(:auto_include_context)
10
+
11
+ super
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Raised when a single object without Goldiloader context support was passed to an isolated loader.
4
+ N1Loader::Loader::UnsupportedGoldiloader = Class.new(StandardError)
5
+
6
+ # Defines a singleton method that allows isolated loaders
7
+ # to use Goldiloader context without passing sibling records.
8
+ N1Loader::Loader.define_singleton_method(:for) do |element, **args|
9
+ # It is required to have a Goldiloader context supported
10
+ raise N1Loader::Loader::UnsupportedGoldiloader unless element.respond_to?(:auto_include_context)
11
+
12
+ context = element.auto_include_context
13
+
14
+ # Fetch or initialize loader from Goldiloader context
15
+ loader_collection = context.fetch_n1_loader_collection(self) do
16
+ context_setup = lambda { |records|
17
+ ar_records = records.flatten(1).select { |record| record.respond_to?(:auto_include_context=) }
18
+ ::Goldiloader::AutoIncludeContext.register_models(ar_records) unless ar_records.empty?
19
+ }
20
+
21
+ N1Loader::LoaderCollection.new(self, context.models).tap do |collection|
22
+ collection.context_setup = context_setup
23
+ end
24
+ end
25
+
26
+ # Fetch value from loader
27
+ loader_collection.with(**args).for(element)
28
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module N1Loader
4
+ module Goldiloader
5
+ # A patch to {N1Loader::LoaderCollection} to setup lazy context lazily.
6
+ module LoaderCollectionPatch
7
+ attr_accessor :context_setup
8
+
9
+ def with(**args)
10
+ result = super
11
+
12
+ result.context_setup = context_setup if context_setup && result.context_setup.nil?
13
+
14
+ result
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module N1Loader
4
+ module Goldiloader
5
+ # A patch to {N1Loader::Loader} to setup lazy context lazily.
6
+ module LoaderPatch
7
+ attr_accessor :context_setup
8
+
9
+ def loaded?
10
+ return true if @already_loaded && @already_context
11
+
12
+ super
13
+
14
+ synchronize { non_thread_safe_context_setting unless @already_context }
15
+
16
+ true
17
+ end
18
+
19
+ def non_thread_safe_context_setting
20
+ return if @already_context
21
+
22
+ context_setup&.call(loaded_by_identity.values.flatten)
23
+
24
+ @already_context = true
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module N1Loader
4
+ module Goldiloader
5
+ # A patch to {N1Loader::Preloader} to setup lazy context lazily.
6
+ module PreloaderPatch
7
+ def initialize(elements, context_setup = nil)
8
+ super(elements)
9
+ @context_setup = context_setup
10
+ end
11
+
12
+ def preload(*keys)
13
+ super.each do |loader_collection|
14
+ loader_collection.context_setup = context_setup
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :context_setup
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Load core library
4
+ require_relative "active_record"
5
+
6
+ # Load integration dependency
7
+ require "goldiloader"
8
+
9
+ # Library integration
10
+ require_relative "goldiloader/loadable"
11
+ require_relative "goldiloader/context_adapter"
12
+ require_relative "goldiloader/loader_collection_patch"
13
+ require_relative "goldiloader/preloader_patch"
14
+ require_relative "goldiloader/loader_patch"
15
+ require_relative "goldiloader/loader"
16
+ require_relative "goldiloader/context"
17
+
18
+ N1Loader::Loadable.prepend(N1Loader::Goldiloader::Loadable)
19
+ N1Loader::Preloader.prepend(N1Loader::Goldiloader::PreloaderPatch)
20
+ N1Loader::Loader.prepend(N1Loader::Goldiloader::LoaderPatch)
21
+ N1Loader::LoaderCollection.prepend(N1Loader::Goldiloader::LoaderCollectionPatch)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module N1Loader
4
- VERSION = "2.2.0"
4
+ VERSION = "3.0.0"
5
5
  end
data/lib/n1_loader.rb CHANGED
@@ -15,4 +15,5 @@ module N1Loader # :nodoc:
15
15
  class NotFilled < Error; end
16
16
  class MissingArgument < Error; end
17
17
  class InvalidArgument < Error; end
18
+ class InvalidBinding < Error; end
18
19
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: n1_loader
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 3.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: 2026-03-16 00:00:00.000000000 Z
11
+ date: 2026-03-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mutex_m
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0.11'
69
+ - !ruby/object:Gem::Dependency
70
+ name: goldiloader
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '3'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: graphql
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -179,6 +193,14 @@ files:
179
193
  - lib/n1_loader/core/loader_builder.rb
180
194
  - lib/n1_loader/core/loader_collection.rb
181
195
  - lib/n1_loader/core/preloader.rb
196
+ - lib/n1_loader/goldiloader.rb
197
+ - lib/n1_loader/goldiloader/context.rb
198
+ - lib/n1_loader/goldiloader/context_adapter.rb
199
+ - lib/n1_loader/goldiloader/loadable.rb
200
+ - lib/n1_loader/goldiloader/loader.rb
201
+ - lib/n1_loader/goldiloader/loader_collection_patch.rb
202
+ - lib/n1_loader/goldiloader/loader_patch.rb
203
+ - lib/n1_loader/goldiloader/preloader_patch.rb
182
204
  - lib/n1_loader/version.rb
183
205
  homepage: https://github.com/djezzzl/n1_loader
184
206
  licenses: