Class: Pipeloader::Batch::BatchProxy
- Inherits:
-
Object
- Object
- Pipeloader::Batch::BatchProxy
- Includes:
- Enumerable
- Defined in:
- lib/pipeloader/batch/batch_proxy.rb
Overview
A lazy, chainable stand-in for a has_many association whose LOAD is batched across all live siblings of the owner. Query builders (where/order/limit/ select/…) accumulate an ActiveRecord::Relation; materializing (each/to_a/ map/…) runs ONE query for every sibling, applies the accumulated scope, partitions rows by foreign key, and slices limit/offset PER GROUP.
It duck-types the common Relation + Enumerable surface (~80% of a real CollectionProxy). Anything not handled here falls through to the real AR association, so writers (<<, build, create, …) still work.
Defined Under Namespace
Classes: WhereChain
Constant Summary collapse
- QUERY_METHODS =
Pure query refinements that map 1:1 onto AR::Relation and return a new proxy. ‘where` is defined explicitly (below) so `where.not(…)` chains too.
%i[ rewhere order reorder reselect distinct group having joins left_outer_joins includes preload eager_load references unscope readonly ].freeze
- WRITE_METHODS =
The ONLY methods delegated to the real AR association. These are writes, so they can’t escape the (batched) read path. Everything not handled by this proxy raises NoMethodError instead of silently falling through to a per-record query.
%i[ << concat push create create! build new delete destroy delete_all destroy_all clear replace ].freeze
Instance Attribute Summary collapse
-
#limit_value ⇒ Object
readonly
Returns the value of attribute limit_value.
-
#offset_value ⇒ Object
readonly
Returns the value of attribute offset_value.
-
#owner ⇒ Object
readonly
Returns the value of attribute owner.
Instance Method Summary collapse
- #[](*args) ⇒ Object
-
#cache_signature ⇒ Object
Signature of the accumulated scope (without the per-owner FK filter), so siblings asking for the same scope share one batch.
- #each(&block) ⇒ Object
- #empty? ⇒ Boolean
- #exists?(*args, **kwargs) ⇒ Boolean
-
#find_by(*args, **kwargs) ⇒ Object
Batched, under our control: both reuse the batched scope rather than issuing a per-owner query.
- #foreign_key ⇒ Object
- #ids ⇒ Object
-
#initialize(owner, reflection, relation: nil, limit_value: nil, offset_value: nil) ⇒ BatchProxy
constructor
A new instance of BatchProxy.
- #inspect ⇒ Object
- #last(*args) ⇒ Object
-
#limit(value) ⇒ Object
limit/offset are honored PER GROUP (top-N per owner), applied after the single batched query — not as a global SQL LIMIT that would collapse every owner into one window.
- #name ⇒ Object
- #offset(value) ⇒ Object
- #owner_key ⇒ Object
- #pluck(*columns) ⇒ Object
- #records ⇒ Object (also: #to_a, #to_ary, #load)
- #relation ⇒ Object
-
#select(*columns, &block) ⇒ Object
select(:cols) refines the query; select { block } filters loaded records (matching ActiveRecord::Relation#select’s dual behavior).
- #size ⇒ Object (also: #length)
-
#where(*args, **kwargs, &block) ⇒ Object
where(conditions) refines the batched query; bare ‘where` returns a chain so `where.not(…)` works like a relation’s.
Constructor Details
#initialize(owner, reflection, relation: nil, limit_value: nil, offset_value: nil) ⇒ BatchProxy
Returns a new instance of BatchProxy.
33 34 35 36 37 38 39 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 33 def initialize(owner, reflection, relation: nil, limit_value: nil, offset_value: nil) @owner = owner @reflection = reflection @relation = relation @limit_value = limit_value @offset_value = offset_value end |
Instance Attribute Details
#limit_value ⇒ Object (readonly)
Returns the value of attribute limit_value.
41 42 43 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 41 def limit_value @limit_value end |
#offset_value ⇒ Object (readonly)
Returns the value of attribute offset_value.
41 42 43 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 41 def offset_value @offset_value end |
#owner ⇒ Object (readonly)
Returns the value of attribute owner.
41 42 43 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 41 def owner @owner end |
Instance Method Details
#[](*args) ⇒ Object
124 125 126 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 124 def [](*args) records[*args] end |
#cache_signature ⇒ Object
Signature of the accumulated scope (without the per-owner FK filter), so siblings asking for the same scope share one batch.
150 151 152 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 150 def cache_signature [relation.to_sql, @limit_value, @offset_value] end |
#each(&block) ⇒ Object
98 99 100 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 98 def each(&block) records.each(&block) end |
#empty? ⇒ Boolean
116 117 118 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 116 def empty? records.empty? end |
#exists?(*args, **kwargs) ⇒ Boolean
142 143 144 145 146 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 142 def exists?(*args, **kwargs) return !empty? if args.empty? && kwargs.empty? where(*args, **kwargs).any? end |
#find_by(*args, **kwargs) ⇒ Object
Batched, under our control: both reuse the batched scope rather than issuing a per-owner query.
138 139 140 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 138 def find_by(*args, **kwargs) where(*args, **kwargs).first end |
#foreign_key ⇒ Object
47 48 49 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 47 def foreign_key @reflection.foreign_key.to_s end |
#ids ⇒ Object
128 129 130 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 128 def ids records.map(&:id) end |
#inspect ⇒ Object
154 155 156 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 154 def inspect records.inspect end |
#last(*args) ⇒ Object
120 121 122 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 120 def last(*args) records.last(*args) end |
#limit(value) ⇒ Object
limit/offset are honored PER GROUP (top-N per owner), applied after the single batched query — not as a global SQL LIMIT that would collapse every owner into one window.
74 75 76 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 74 def limit(value) spawn(limit_value: value) end |
#name ⇒ Object
43 44 45 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 43 def name @reflection.name end |
#offset(value) ⇒ Object
78 79 80 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 78 def offset(value) spawn(offset_value: value) end |
#owner_key ⇒ Object
51 52 53 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 51 def owner_key @reflection.active_record_primary_key.to_sym end |
#pluck(*columns) ⇒ Object
132 133 134 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 132 def pluck(*columns) records.map { |record| columns.one? ? record[columns.first] : columns.map { |column| record[column] } } end |
#records ⇒ Object Also known as: to_a, to_ary, load
102 103 104 105 106 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 102 def records return @records if defined?(@records) @records = Pipeloader::Batch::BatchLoader.load(self) end |
#relation ⇒ Object
55 56 57 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 55 def relation @relation ||= apply_reflection_scope(@reflection.klass.all) end |
#select(*columns, &block) ⇒ Object
select(:cols) refines the query; select { block } filters loaded records (matching ActiveRecord::Relation#select’s dual behavior).
92 93 94 95 96 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 92 def select(*columns, &block) return records.select(&block) if block spawn(relation: relation.select(*columns)) end |
#size ⇒ Object Also known as: length
111 112 113 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 111 def size records.size end |
#where(*args, **kwargs, &block) ⇒ Object
where(conditions) refines the batched query; bare ‘where` returns a chain so `where.not(…)` works like a relation’s.
84 85 86 87 88 |
# File 'lib/pipeloader/batch/batch_proxy.rb', line 84 def where(*args, **kwargs, &block) return WhereChain.new(self) if args.empty? && kwargs.empty? && block.nil? spawn(relation: relation.where(*args, **kwargs, &block)) end |