Module: Pipeloader::Batch::Model::ClassMethods
- Defined in:
- lib/pipeloader/batch/model.rb
Constant Summary collapse
- AGGREGATE_DEFAULTS =
Sentinel marking “no explicit default given” so we can pick a sensible per-function default (0 for count/sum, nil for min/max/average).
{ count: 0, sum: 0 }.freeze
Instance Method Summary collapse
- #_pipeloader_batch_association(name) ⇒ Object
- #_pipeloader_batch_register_aggregate(name, relationship) ⇒ Object
- #_pipeloader_batch_register_fetcher(name, getter, loader, default) ⇒ Object
-
#batch(name, key: nil, default: nil, &loader) ⇒ Object
Define ‘name` as a value loaded ONCE across every live sibling.
-
#batch_aggregate(name, function:, of: nil, column: nil, class_name: nil, foreign_key: nil, primary_key: nil, default: AGGREGATE_DEFAULTS) ⇒ Object
function: :sum / :average / :minimum / :maximum (or :count).
- #batch_belongs_to(name, scope = nil, **options, &extension) ⇒ Object
-
#batch_count(name, of: nil, class_name: nil, foreign_key: nil, primary_key: nil, default: 0) ⇒ Object
— Scalar aggregates: COUNT / SUM / AVERAGE / MINIMUM / MAXIMUM —.
-
#batch_has_many(name, scope = nil, **options, &extension) ⇒ Object
— Collection: a real has_many, but the reader returns a BatchProxy —.
-
#batch_has_one(name, scope = nil, **options, &extension) ⇒ Object
— Singular: a real association whose load is batched (single record) —.
- #pipeloader_batched_association?(name) ⇒ Boolean
Instance Method Details
#_pipeloader_batch_association(name) ⇒ Object
124 125 126 127 |
# File 'lib/pipeloader/batch/model.rb', line 124 def _pipeloader_batch_association(name) # merge (not mutate) so subclasses get their own copy — STI-safe. self._pipeloader_batch_associations = _pipeloader_batch_associations + [name] end |
#_pipeloader_batch_register_aggregate(name, relationship) ⇒ Object
129 130 131 |
# File 'lib/pipeloader/batch/model.rb', line 129 def _pipeloader_batch_register_aggregate(name, relationship) _pipeloader_batch_register_fetcher(name, relationship.getter, relationship.loader, relationship.default) end |
#_pipeloader_batch_register_fetcher(name, getter, loader, default) ⇒ Object
133 134 135 136 137 138 |
# File 'lib/pipeloader/batch/model.rb', line 133 def _pipeloader_batch_register_fetcher(name, getter, loader, default) self._pipeloader_batch_fetchers = _pipeloader_batch_fetchers.merge( name => Pipeloader::Batch::Fetcher.new(self, name, getter, loader, default: default) ) define_method(name) { batch_load(name) } end |
#batch(name, key: nil, default: nil, &loader) ⇒ Object
Define ‘name` as a value loaded ONCE across every live sibling. The loader gets the array of owner keys (the `key:` column, default the primary key) and returns a `{ key => value }` Hash; each instance reads its own value, or `default` when its key is absent. This is the escape hatch behind batch_count / batch_aggregate, surfaced for everything that isn’t a plain association — existence checks, viewer-scoped flags, lookups by a non-PK column, derived rows:
batch :viewer_has_starred, default: false do |repo_ids|
Star.where(user_id: Current.user.id, repo_id: repo_ids)
.pluck(:repo_id).index_with(true) # missing -> false
end
117 118 119 120 121 122 |
# File 'lib/pipeloader/batch/model.rb', line 117 def batch(name, key: nil, default: nil, &loader) raise Pipeloader::Batch::Error, "batch #{name.inspect} requires a loader block" unless loader getter = (key || primary_key).to_sym _pipeloader_batch_register_fetcher(name, getter, ->(keys, _fetcher) { loader.call(keys) }, default) end |
#batch_aggregate(name, function:, of: nil, column: nil, class_name: nil, foreign_key: nil, primary_key: nil, default: AGGREGATE_DEFAULTS) ⇒ Object
function: :sum / :average / :minimum / :maximum (or :count). Empty groups default to 0 for count/sum and nil for the rest, override with :default. For anything a plain GROUP BY can’t express, use ‘batch` with a loader.
88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/pipeloader/batch/model.rb', line 88 def batch_aggregate(name, function:, of: nil, column: nil, class_name: nil, foreign_key: nil, primary_key: nil, default: AGGREGATE_DEFAULTS) if function != :count && column.nil? raise Pipeloader::Batch::Error, "batch_aggregate #{name.inspect} (#{function}) requires a :column" end resolved_default = default.equal?(AGGREGATE_DEFAULTS) ? AGGREGATE_DEFAULTS[function] : default _pipeloader_batch_register_aggregate(name, Relationship.aggregate( self, name, of: of, function: function, column: column, class_name: class_name, foreign_key: foreign_key, primary_key: primary_key, default: resolved_default )) end |
#batch_belongs_to(name, scope = nil, **options, &extension) ⇒ Object
65 66 67 68 |
# File 'lib/pipeloader/batch/model.rb', line 65 def batch_belongs_to(name, scope = nil, **, &extension) belongs_to(name, scope, **, &extension) _pipeloader_batch_association(name) end |
#batch_count(name, of: nil, class_name: nil, foreign_key: nil, primary_key: nil, default: 0) ⇒ Object
— Scalar aggregates: COUNT / SUM / AVERAGE / MINIMUM / MAXIMUM —
76 77 78 79 80 81 82 83 |
# File 'lib/pipeloader/batch/model.rb', line 76 def batch_count(name, of: nil, class_name: nil, foreign_key: nil, primary_key: nil, default: 0) _pipeloader_batch_register_aggregate(name, Relationship.aggregate( self, name, of: of, function: :count, column: nil, class_name: class_name, foreign_key: foreign_key, primary_key: primary_key, default: default )) end |
#batch_has_many(name, scope = nil, **options, &extension) ⇒ Object
— Collection: a real has_many, but the reader returns a BatchProxy —
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/pipeloader/batch/model.rb', line 40 def batch_has_many(name, scope = nil, **, &extension) has_many(name, scope, **, &extension) reflection = reflect_on_association(name) if reflection.through_reflection # A :through collection's target has no direct FK to the owner, so the # flat-FK BatchProxy can't build its query. Batch it through AR's # Preloader (which walks the join), filling every live sibling at once; # the reader returns a plain loaded array (no chainable proxy). define_method(name) do assoc = association(name) Pipeloader::Batch.fill_association(self, name) unless assoc.loaded? assoc.load_target end else define_method(name) { Pipeloader::Batch::BatchProxy.new(self, reflection) } end end |
#batch_has_one(name, scope = nil, **options, &extension) ⇒ Object
— Singular: a real association whose load is batched (single record) —
60 61 62 63 |
# File 'lib/pipeloader/batch/model.rb', line 60 def batch_has_one(name, scope = nil, **, &extension) has_one(name, scope, **, &extension) _pipeloader_batch_association(name) end |
#pipeloader_batched_association?(name) ⇒ Boolean
70 71 72 |
# File 'lib/pipeloader/batch/model.rb', line 70 def pipeloader_batched_association?(name) _pipeloader_batch_associations.include?(name) end |