Class: Cybertrain::Relation
- Inherits:
-
Object
- Object
- Cybertrain::Relation
- Defined in:
- cybertrain/relation.rb
Overview
A lazily built SELECT over one table: nothing runs until a method that
needs rows (#count, #exists?, to_a, first, each, …).
Each generated model gets its own subclass, <Model>Relation, which adds
the chainable query methods (where, where_sql, order, order_sql,
limit, offset) and the methods that return records (to_a, each,
first, last, find, find_by, size); see ArticleRelation. The
methods documented here are the ones every relation shares.
The chain methods change the relation they are called on and return it (they do not copy it), so keep one relation per query:
scope = Comment.where(article_id: article.id)
recent = scope.order("created_at DESC").limit(10).to_a
# scope itself is now ordered and limited too
Implementation: the subclass’s chain methods wrap the setters below and
return self, so Post.where(...).first is typed Post|nil: a base-class
method that returned self would be typed as the base (spikes/NOTES.md
rule 5), which is why these setters return nil.
Direct Known Subclasses
Instance Method Summary collapse
-
#count ⇒ Integer
Counts the rows the relation would return: with a limit or offset the count runs over the windowed subquery (Post.limit(2).count is 2).
-
#delete_all ⇒ Integer
Deletes the matching rows and returns how many went, with one DELETE: no model is loaded and no callback runs.
-
#exists? ⇒ Boolean
Whether the relation matches any row (a
SELECT 1 ... LIMIT 1). -
#to_sql ⇒ String
The SELECT this relation runs, with
?placeholders (seebinds).
Instance Method Details
#count ⇒ Integer
Counts the rows the relation would return: with a limit or offset the count runs over the windowed subquery (Post.limit(2).count is 2).
175 176 177 178 179 180 181 182 183 184 |
# File 'cybertrain/relation.rb', line 175 def count sql = if @limit >= 0 || @offset > 0 "SELECT COUNT(*) AS n FROM (#{select_sql("1", "", @limit, @offset)})" else select_sql("COUNT(*) AS n", "", -1, 0) end binds = @binds found = Cybertrain::DB.with { |c| c.execute(sql, binds) } Cast.int(found[0]["n"]) end |
#delete_all ⇒ Integer
Deletes the matching rows and returns how many went, with one DELETE:
no model is loaded and no callback runs. With a limit or
offset only the rows of that window go (Post.limit(1).delete_all is one
row, as count/exists? read the same window): SQLite has no DELETE …
LIMIT in a default build, so the window is picked by an id subselect
(the quoted primary key); the where binds appear once, inside it.
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'cybertrain/relation.rb', line 209 def delete_all sql = +"DELETE FROM #{Relation.quote_ident(@table)}" if @limit >= 0 || @offset > 0 # By id when the relation has no order, as first_row/last_row do, so # Post.limit(1).delete_all removes the row Post.limit(1).first returns # rather than whichever row SQLite scans first. id = Relation.quote_ident("id") order = @order == "" ? id : @order sql << " WHERE #{id} IN (" << select_sql(id, order, @limit, @offset) << ")" else sql << " WHERE " << @wheres.join(" AND ") unless @wheres.empty? end binds = @binds Cybertrain::DB.with do |c| c.execute(sql, binds) c.changes end end |
#exists? ⇒ Boolean
Whether the relation matches any row (a SELECT 1 ... LIMIT 1).
Conditions go on the relation, not the call: Post.where(slug: s).exists?.
190 191 192 193 194 195 196 197 |
# File 'cybertrain/relation.rb', line 190 def exists? return false if @limit == 0 sql = select_sql("1 AS one", "", 1, @offset) binds = @binds found = Cybertrain::DB.with { |c| c.execute(sql, binds) } !found.empty? end |
#to_sql ⇒ String
The SELECT this relation runs, with ? placeholders (see binds).
Handy in a test or a log line.
156 157 158 |
# File 'cybertrain/relation.rb', line 156 def to_sql select_sql("*", @order, @limit, @offset) end |