Class: Pluckr::Relation
- Inherits:
-
Object
- Object
- Pluckr::Relation
- Includes:
- Enumerable
- Defined in:
- lib/pluckr/relation.rb
Overview
Immutable, chainable wrapper around the root ActiveRecord relation.
Root filtering/ordering/pagination is delegated to ActiveRecord - Pluckr only adds its projections and joins when the query is compiled.
Defined Under Namespace
Classes: WhereChain
Instance Attribute Summary collapse
-
#query_class ⇒ Object
readonly
Returns the value of attribute query_class.
-
#relation ⇒ Object
readonly
Returns the value of attribute relation.
Instance Method Summary collapse
- #any?(*args) ⇒ Boolean
-
#count(*args) ⇒ Object
(also: #size)
Enumerable would answer these by building every result object first, which is the opposite of the point.
- #each(&block) ⇒ Object
- #empty? ⇒ Boolean
- #exists? ⇒ Boolean
- #explain ⇒ Object
-
#fetch ⇒ Object
(also: #to_a)
One statement, one round trip, plain result objects.
-
#find(*args, &block) ⇒ Object
findis the primary key lookup; a block is Enumerable'sfind, which is where ActiveRecord sends it too. -
#find_by(arg, *args) ⇒ Object
The arity is ActiveRecord's: at least one condition, or
ArgumentError. - #find_by!(arg, *args) ⇒ Object
-
#find_each(batch_size: 1_000, &block) ⇒ Object
Reads the whole chain in pages, so an export does not build every result object at once.
-
#first(limit = nil) ⇒ Object
ActiveRecord orders by primary key when the relation has no order of its own, so that
firstmeans something. - #first! ⇒ Object
-
#for(records) ⇒ Object
See Query.for.
- #in_batches(of: 1_000) ⇒ Object
-
#initialize(query_class, relation) ⇒ Relation
constructor
A new instance of Relation.
- #inspect ⇒ Object
-
#last(limit = nil) ⇒ Object
The other end of the same order, read in one
LIMITrather than by hydrating the chain and dropping all but the tail. - #last! ⇒ Object
- #limit(value) ⇒ Object
- #many?(*args) ⇒ Boolean
- #none?(*args) ⇒ Boolean
- #offset(value) ⇒ Object
-
#one?(*args) ⇒ Boolean
Two rows is all it takes to answer either question - unless the chain already limits itself to fewer, in which case that limit is the answer.
- #order ⇒ Object
- #spawn(new_relation) ⇒ Object
-
#take(limit = nil) ⇒ Object
Enumerable's
takewould read the whole chain to keep the front of it. - #take! ⇒ Object
- #to_sql ⇒ Object
-
#where(*args, **options, &block) ⇒ Object
wherewith no arguments returns a chain, sowhere.not(...)works like it does on an ActiveRecord relation.
Constructor Details
#initialize(query_class, relation) ⇒ Relation
Returns a new instance of Relation.
13 14 15 16 17 |
# File 'lib/pluckr/relation.rb', line 13 def initialize(query_class, relation) @query_class = query_class @relation = relation freeze end |
Instance Attribute Details
#query_class ⇒ Object (readonly)
Returns the value of attribute query_class.
11 12 13 |
# File 'lib/pluckr/relation.rb', line 11 def query_class @query_class end |
#relation ⇒ Object (readonly)
Returns the value of attribute relation.
11 12 13 |
# File 'lib/pluckr/relation.rb', line 11 def relation @relation end |
Instance Method Details
#any?(*args) ⇒ Boolean
72 73 74 75 76 |
# File 'lib/pluckr/relation.rb', line 72 def any?(*args) return super if args.any? || block_given? exists? end |
#count(*args) ⇒ Object Also known as: size
Enumerable would answer these by building every result object first, which
is the opposite of the point. They are questions about the root rows, so
ActiveRecord answers them with COUNT(*)/SELECT 1, without Pluckr's
projections.
A block (or an Enumerable pattern) is a question about the results, so it
goes back to Enumerable - exactly where ActiveRecord sends it.
61 62 63 64 65 |
# File 'lib/pluckr/relation.rb', line 61 def count(*args) return super if block_given? relation.count(*args) end |
#each(&block) ⇒ Object
51 52 53 |
# File 'lib/pluckr/relation.rb', line 51 def each(&block) fetch.each(&block) end |
#empty? ⇒ Boolean
84 85 86 |
# File 'lib/pluckr/relation.rb', line 84 def empty? !exists? end |
#exists? ⇒ Boolean
68 69 70 |
# File 'lib/pluckr/relation.rb', line 68 def exists?(...) relation.exists?(...) end |
#explain ⇒ Object
88 89 90 |
# File 'lib/pluckr/relation.rb', line 88 def explain(...) query_class.compiler.apply(relation).explain(...) end |
#fetch ⇒ Object Also known as: to_a
One statement, one round trip, plain result objects.
44 45 46 47 48 |
# File 'lib/pluckr/relation.rb', line 44 def fetch builder = query_class.result_builder rows = Pluckr.select_all(query_class.compiler.connection, to_sql, name: query_class.name) rows.map { |row| builder.build(row) } end |
#find(*args, &block) ⇒ Object
find is the primary key lookup; a block is Enumerable's find, which is
where ActiveRecord sends it too. Several ids (or an array of them) come
back as an array, in the order they were asked for.
148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/pluckr/relation.rb', line 148 def find(*args, &block) return super if block raise ArgumentError, "wrong number of arguments (given 0, expected 1+)" if args.empty? key = single_primary_key ids = args.size == 1 ? args.first : args return for_ids(ids, key) if ids.is_a?(Array) raise ActiveRecord::RecordNotFound, "Couldn't find #{model.name} without an ID" if ids.nil? where(key => ids).first || raise(not_found(key, ids)) end |
#find_by(arg, *args) ⇒ Object
The arity is ActiveRecord's: at least one condition, or ArgumentError.
161 162 163 |
# File 'lib/pluckr/relation.rb', line 161 def find_by(arg, *args) where(arg, *args).first end |
#find_by!(arg, *args) ⇒ Object
165 166 167 168 169 |
# File 'lib/pluckr/relation.rb', line 165 def find_by!(arg, *args) find_by(arg, *args) || raise(ActiveRecord::RecordNotFound, "Couldn't find #{model.name} with #{[arg, *args].map(&:inspect).join(", ")}") end |
#find_each(batch_size: 1_000, &block) ⇒ Object
Reads the whole chain in pages, so an export does not build every result
object at once. Like ActiveRecord's, the batches are ordered by primary
key and any order of your own is ignored - unlike ActiveRecord's, the page
after the first is a keyset seek (id > last), never an OFFSET.
175 176 177 178 179 180 |
# File 'lib/pluckr/relation.rb', line 175 def find_each(batch_size: 1_000, &block) assert_batchable!(batch_size) return to_enum(:find_each, batch_size: batch_size) unless block_given? in_batches(of: batch_size) { |results| results.each(&block) } end |
#first(limit = nil) ⇒ Object
ActiveRecord orders by primary key when the relation has no order of its
own, so that first means something. Same here.
94 95 96 97 98 |
# File 'lib/pluckr/relation.rb', line 94 def first(limit = nil) results = ordered_by_primary_key.limit(within_limit(limit || 1)).fetch limit ? results : results.first end |
#first! ⇒ Object
100 101 102 |
# File 'lib/pluckr/relation.rb', line 100 def first! first || raise(ActiveRecord::RecordNotFound, "Couldn't find #{model.name}") end |
#for(records) ⇒ Object
See Query.for. An unloaded Relation is filtered in SQL (subquery), never loaded into Ruby; a loaded one already has its records, so they are used. A paginated one is resolved to primary keys first - see for_paginated_relation.
207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/pluckr/relation.rb', line 207 def for(records) key = single_primary_key if records.is_a?(self.class) raise ConfigurationError, "`for` expects #{model.name} records, not another Pluckr query; use `fetch`" end return for_relation(records, key) if records.is_a?(ActiveRecord::Relation) && !records.loaded? return for_many(records.to_a, key) if records.is_a?(Enumerable) for_one(records, key) end |
#in_batches(of: 1_000) ⇒ Object
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/pluckr/relation.rb', line 182 def in_batches(of: 1_000) assert_batchable!(of) return to_enum(:in_batches, of: of) unless block_given? key = single_primary_key output = primary_key_output(key) scope = spawn(relation.reorder(key => :asc)) cursor = nil loop do page = cursor ? scope.where(model.arel_table[key].gt(cursor)) : scope results = page.limit(of).fetch break if results.empty? yield results break if results.size < of cursor = results.last.public_send(output) end end |
#inspect ⇒ Object
220 221 222 |
# File 'lib/pluckr/relation.rb', line 220 def inspect "#<Pluckr::Relation #{query_class.name} #{to_sql}>" end |
#last(limit = nil) ⇒ Object
The other end of the same order, read in one LIMIT rather than by
hydrating the chain and dropping all but the tail. A chain that already
pages is the exception ActiveRecord makes too: its own limit cannot be
replaced by ours, so the page is read and the tail taken from it.
108 109 110 111 112 113 114 |
# File 'lib/pluckr/relation.rb', line 108 def last(limit = nil) return last_of_page(limit) if paginated?(relation) results = spawn(ordered_by_primary_key.relation.reverse_order).limit(limit || 1).fetch limit ? results.reverse : results.first end |
#last! ⇒ Object
116 117 118 |
# File 'lib/pluckr/relation.rb', line 116 def last! last || raise(ActiveRecord::RecordNotFound, "Couldn't find #{model.name}") end |
#limit(value) ⇒ Object
31 32 33 |
# File 'lib/pluckr/relation.rb', line 31 def limit(value) spawn(relation.limit(value)) end |
#many?(*args) ⇒ Boolean
139 140 141 142 143 |
# File 'lib/pluckr/relation.rb', line 139 def many?(*args) return super if args.any? || block_given? limited_count > 1 end |
#none?(*args) ⇒ Boolean
78 79 80 81 82 |
# File 'lib/pluckr/relation.rb', line 78 def none?(*args) return super if args.any? || block_given? !exists? end |
#offset(value) ⇒ Object
35 36 37 |
# File 'lib/pluckr/relation.rb', line 35 def offset(value) spawn(relation.offset(value)) end |
#one?(*args) ⇒ Boolean
Two rows is all it takes to answer either question - unless the chain already limits itself to fewer, in which case that limit is the answer.
133 134 135 136 137 |
# File 'lib/pluckr/relation.rb', line 133 def one?(*args) return super if args.any? || block_given? limited_count == 1 end |
#order ⇒ Object
27 28 29 |
# File 'lib/pluckr/relation.rb', line 27 def order(...) spawn(relation.order(...)) end |
#spawn(new_relation) ⇒ Object
224 225 226 |
# File 'lib/pluckr/relation.rb', line 224 def spawn(new_relation) self.class.new(query_class, new_relation) end |
#take(limit = nil) ⇒ Object
Enumerable's take would read the whole chain to keep the front of it.
121 122 123 124 125 |
# File 'lib/pluckr/relation.rb', line 121 def take(limit = nil) results = self.limit(limit || 1).fetch limit ? results : results.first end |
#take! ⇒ Object
127 128 129 |
# File 'lib/pluckr/relation.rb', line 127 def take! take || raise(ActiveRecord::RecordNotFound, "Couldn't find #{model.name}") end |
#to_sql ⇒ Object
39 40 41 |
# File 'lib/pluckr/relation.rb', line 39 def to_sql query_class.compiler.apply(relation).to_sql end |
#where(*args, **options, &block) ⇒ Object
where with no arguments returns a chain, so where.not(...) works like
it does on an ActiveRecord relation.
21 22 23 24 25 |
# File 'lib/pluckr/relation.rb', line 21 def where(*args, **, &block) return WhereChain.new(self) if args.empty? && .empty? && block.nil? spawn(relation.where(*args, **, &block)) end |