Class: ActivePostgrest::Relation
- Inherits:
-
Object
- Object
- ActivePostgrest::Relation
show all
- Includes:
- SqlBuilder, Enumerable
- Defined in:
- lib/active_postgrest/relation.rb
Defined Under Namespace
Classes: WhereChain
Constant Summary
Constants included
from SqlBuilder
SqlBuilder::FILTER_OPS, SqlBuilder::KNOWN_OP_KEYS, SqlBuilder::NEGATED_OPS
Instance Method Summary
collapse
-
#and_where(conditions) ⇒ Object
and_where([{ age: { gt: 18 } }, { status: “active” }]) → and=(age.gt.18,status.eq.active).
-
#anonymous ⇒ Object
-
#any?(&block) ⇒ Boolean
-
#average(col) ⇒ Object
-
#count(mode = :exact) ⇒ Object
-
#each ⇒ Object
-
#embed(name, fields: []) ⇒ Object
embed(:mother, fields: [:id, :first_name]) — computed relationship.
-
#exists? ⇒ Boolean
-
#explain ⇒ Object
-
#first ⇒ Object
-
#initialize(table, client, model_class) ⇒ Relation
constructor
A new instance of Relation.
-
#inspect ⇒ Object
-
#joins(table, as: nil, foreign_key: nil, select: [], where: {}) ⇒ Object
joins(:companies) → INNER JOIN (excludes rows with no match) joins(:users, as: :mother, foreign_key: :mother_id) → aliased FK join.
-
#last(n = nil) ⇒ Object
-
#left_joins(table, as: nil, foreign_key: nil, select: [], where: {}) ⇒ Object
left_joins(:companies) → LEFT JOIN (includes rows even with no match).
-
#limit(n) ⇒ Object
-
#many? ⇒ Boolean
-
#maximum(col) ⇒ Object
-
#method_missing(name) ⇒ Object
-
#minimum(col) ⇒ Object
-
#none ⇒ Object
-
#none?(&block) ⇒ Boolean
-
#not_where(filters) ⇒ Object
-
#offset(n) ⇒ Object
-
#one?(&block) ⇒ Boolean
-
#or_where(conditions) ⇒ Object
or_where([{ age: { lt: 18 } }, { status: “active” }]) → or=(age.lt.18,status.eq.active).
-
#order(col, dir = :asc, nulls: nil) ⇒ Object
-
#pick(*cols) ⇒ Object
-
#pluck(*cols) ⇒ Object
-
#reorder(col, dir = :asc, nulls: nil) ⇒ Object
-
#respond_to_missing?(name, include_private = false) ⇒ Boolean
-
#select(*cols) ⇒ Object
-
#spread(*tables) ⇒ Object
-
#sum(col) ⇒ Object
-
#to_a ⇒ Object
-
#to_sql ⇒ Object
Returns a human-readable SQL-like representation of the query reconstructed from the relation’s internal state — no database call is made.
-
#to_url ⇒ Object
-
#where(filters = nil) ⇒ Object
where(name: “John”) → name=eq.John where(name: nil) → name=is.null where(active: true) → active=is.true where(id: [1, 2, 3]) → id=in.(1,2,3) where(age: 18..30) → age=gte.18&age=lte.30 where(age: { gt: 18, lt: 65 }) → age=gt.18&age=lt.65 where(companies: { name: “Acme” }) → companies.name=eq.Acme (AR-style joins filter) where.not(name: “John”) → name=not.eq.John.
-
#with_schema(name) ⇒ Object
-
#with_token(jwt) ⇒ Object
Constructor Details
#initialize(table, client, model_class) ⇒ Relation
Returns a new instance of Relation.
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
# File 'lib/active_postgrest/relation.rb', line 26
def initialize(table, client, model_class)
@table = table
@client = client
@model_class = model_class
@selects = []
@joins = []
@filters = {}
@or_conditions = []
@and_conditions = []
@limit_val = nil
@offset_val = nil
@order_val = nil
@null = false
@schema = nil
end
|
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(name) ⇒ Object
206
207
208
209
210
211
212
213
|
# File 'lib/active_postgrest/relation.rb', line 206
def method_missing(name, *, **)
return super unless @model_class.respond_to?(name)
scope = @model_class.public_send(name, *, **)
return super unless scope.is_a?(ActivePostgrest::Relation)
merge(scope)
end
|
Instance Method Details
#and_where(conditions) ⇒ Object
and_where([{ age: { gt: 18 } }, { status: “active” }]) → and=(age.gt.18,status.eq.active)
93
94
95
96
|
# File 'lib/active_postgrest/relation.rb', line 93
def and_where(conditions)
parts = Array(conditions).flat_map { |f| condition_parts(f) }
clone_with { @and_conditions.concat(parts) }
end
|
#anonymous ⇒ Object
110
|
# File 'lib/active_postgrest/relation.rb', line 110
def anonymous = clone_with { @client = @client.anonymous }
|
#any?(&block) ⇒ Boolean
148
|
# File 'lib/active_postgrest/relation.rb', line 148
def any?(&block) = block ? super : count.positive?
|
#average(col) ⇒ Object
154
|
# File 'lib/active_postgrest/relation.rb', line 154
def average(col) = aggregate_value("#{col}.avg()", 'avg')
|
#count(mode = :exact) ⇒ Object
135
136
137
138
139
140
141
142
143
144
145
146
|
# File 'lib/active_postgrest/relation.rb', line 135
def count(mode = :exact)
return 0 if @null
response = @client.get(@table, build_params.merge(limit: 0), count: mode, schema: @schema)
raw = response.['content-range']&.split('/')&.last
total = raw&.delete_prefix('~')
if total.nil? || total == '*'
raise CountNotAvailable, "count=#{mode} not available (Content-Range: #{raw.inspect})"
end
total.to_i
end
|
#each ⇒ Object
114
115
116
|
# File 'lib/active_postgrest/relation.rb', line 114
def each(&)
to_a.each(&)
end
|
#embed(name, fields: []) ⇒ Object
embed(:mother, fields: [:id, :first_name]) — computed relationship
64
65
66
|
# File 'lib/active_postgrest/relation.rb', line 64
def embed(name, fields: [])
add_embed(name.to_s, name.to_s, fields.map(&:to_s), {})
end
|
#exists? ⇒ Boolean
152
|
# File 'lib/active_postgrest/relation.rb', line 152
def exists? = any?
|
#explain ⇒ Object
193
194
195
|
# File 'lib/active_postgrest/relation.rb', line 193
def explain
@client.explain(@table, build_params, schema: @schema)
end
|
#first ⇒ Object
124
125
126
|
# File 'lib/active_postgrest/relation.rb', line 124
def first
limit(1).to_a.first
end
|
#inspect ⇒ Object
219
220
221
|
# File 'lib/active_postgrest/relation.rb', line 219
def inspect
to_a.inspect
end
|
#joins(table, as: nil, foreign_key: nil, select: [], where: {}) ⇒ Object
joins(:companies) → INNER JOIN (excludes rows with no match) joins(:users, as: :mother, foreign_key: :mother_id) → aliased FK join
52
53
54
55
|
# File 'lib/active_postgrest/relation.rb', line 52
def joins(table, as: nil, foreign_key: nil, select: [], where: {})
embed = build_embed(table.to_s, as&.to_s, foreign_key&.to_s, inner: true)
add_embed(embed, table.to_s, select.map(&:to_s), where)
end
|
#last(n = nil) ⇒ Object
128
129
130
131
132
133
|
# File 'lib/active_postgrest/relation.rb', line 128
def last(n = nil)
pk = @model_class.primary_key
return order(pk, :desc).limit(n).to_a.reverse if n
order(pk, :desc).limit(1).to_a.first
end
|
#left_joins(table, as: nil, foreign_key: nil, select: [], where: {}) ⇒ Object
left_joins(:companies) → LEFT JOIN (includes rows even with no match)
58
59
60
61
|
# File 'lib/active_postgrest/relation.rb', line 58
def left_joins(table, as: nil, foreign_key: nil, select: [], where: {})
embed = build_embed(table.to_s, as&.to_s, foreign_key&.to_s, inner: false)
add_embed(embed, table.to_s, select.map(&:to_s), where)
end
|
#limit(n) ⇒ Object
98
|
# File 'lib/active_postgrest/relation.rb', line 98
def limit(n) = clone_with { @limit_val = n }
|
#many? ⇒ Boolean
151
|
# File 'lib/active_postgrest/relation.rb', line 151
def many? = count > 1
|
#maximum(col) ⇒ Object
157
|
# File 'lib/active_postgrest/relation.rb', line 157
def maximum(col) = aggregate_value("#{col}.max()", 'max')
|
#minimum(col) ⇒ Object
156
|
# File 'lib/active_postgrest/relation.rb', line 156
def minimum(col) = aggregate_value("#{col}.min()", 'min')
|
#none ⇒ Object
109
|
# File 'lib/active_postgrest/relation.rb', line 109
def none = clone_with { @null = true }
|
#none?(&block) ⇒ Boolean
149
|
# File 'lib/active_postgrest/relation.rb', line 149
def none?(&block) = block ? super : count.zero?
|
#not_where(filters) ⇒ Object
82
83
84
|
# File 'lib/active_postgrest/relation.rb', line 82
def not_where(filters)
clone_with { encode_filters!(filters, negate: true) }
end
|
#offset(n) ⇒ Object
99
|
# File 'lib/active_postgrest/relation.rb', line 99
def offset(n) = clone_with { @offset_val = n }
|
#one?(&block) ⇒ Boolean
150
|
# File 'lib/active_postgrest/relation.rb', line 150
def one?(&block) = block ? super : count == 1
|
#or_where(conditions) ⇒ Object
or_where([{ age: { lt: 18 } }, { status: “active” }]) → or=(age.lt.18,status.eq.active)
87
88
89
90
|
# File 'lib/active_postgrest/relation.rb', line 87
def or_where(conditions)
parts = Array(conditions).flat_map { |f| condition_parts(f) }
clone_with { @or_conditions.concat(parts) }
end
|
#order(col, dir = :asc, nulls: nil) ⇒ Object
101
102
103
|
# File 'lib/active_postgrest/relation.rb', line 101
def order(col, dir = :asc, nulls: nil)
clone_with { @order_val = build_order(col, dir, nulls) }
end
|
#pick(*cols) ⇒ Object
167
168
169
|
# File 'lib/active_postgrest/relation.rb', line 167
def pick(*cols)
pluck(*cols).first
end
|
#pluck(*cols) ⇒ Object
159
160
161
162
163
164
165
|
# File 'lib/active_postgrest/relation.rb', line 159
def pluck(*cols)
return [] if @null
select(*cols).to_a.map do |record|
cols.length == 1 ? record[cols.first] : cols.map { record[_1] }
end
end
|
#reorder(col, dir = :asc, nulls: nil) ⇒ Object
105
106
107
|
# File 'lib/active_postgrest/relation.rb', line 105
def reorder(col, dir = :asc, nulls: nil)
clone_with { @order_val = build_order(col, dir, nulls) }
end
|
#respond_to_missing?(name, include_private = false) ⇒ Boolean
215
216
217
|
# File 'lib/active_postgrest/relation.rb', line 215
def respond_to_missing?(name, include_private = false)
@model_class.respond_to?(name) || super
end
|
#select(*cols) ⇒ Object
42
43
44
|
# File 'lib/active_postgrest/relation.rb', line 42
def select(*cols)
clone_with { @selects.concat(cols.map(&:to_s)) }
end
|
#spread(*tables) ⇒ Object
46
47
48
|
# File 'lib/active_postgrest/relation.rb', line 46
def spread(*tables)
clone_with { @selects.concat(tables.map { "...#{_1}" }) }
end
|
#sum(col) ⇒ Object
155
|
# File 'lib/active_postgrest/relation.rb', line 155
def sum(col) = aggregate_value("#{col}.sum()", 'sum')
|
#to_a ⇒ Object
118
119
120
121
122
|
# File 'lib/active_postgrest/relation.rb', line 118
def to_a
return [] if @null
Array(@client.get(@table, build_params, schema: @schema).body).map { |attrs| @model_class.new(attrs) }
end
|
#to_sql ⇒ Object
Returns a human-readable SQL-like representation of the query reconstructed from the relation’s internal state — no database call is made.
Limitations vs actual SQL:
-
Embedded resources use PostgREST notation: companies(*) instead of LEFT JOIN companies ON companies.id = users.company_id.
-
Parameters are shown as literal values, not PostgreSQL placeholders ($1, $2).
-
The actual query PostgREST sends is a CTE (WITH pgrst_source AS (…)) and may differ in structure. Use #explain to see the real execution plan.
180
181
182
183
184
185
186
187
188
189
190
191
|
# File 'lib/active_postgrest/relation.rb', line 180
def to_sql
clauses = ["SELECT #{sql_select}", "FROM #{@table}"]
wheres = sql_where_clauses
clauses << "WHERE #{wheres.join("\n AND ")}" if wheres.any?
clauses << "ORDER BY #{sql_order}" if @order_val
clauses << "LIMIT #{@limit_val}" if @limit_val
clauses << "OFFSET #{@offset_val}" if @offset_val
clauses.join("\n")
end
|
#to_url ⇒ Object
197
198
199
200
201
202
203
204
|
# File 'lib/active_postgrest/relation.rb', line 197
def to_url
params = build_params
base = "#{@client.base_url}/#{@table}"
return base if params.empty?
query = params.flat_map { |k, v| Array(v).map { "#{k}=#{_1}" } }.join('&')
"#{base}?#{query}"
end
|
#where(filters = nil) ⇒ Object
where(name: “John”) → name=eq.John where(name: nil) → name=is.null where(active: true) → active=is.true where(id: [1, 2, 3]) → id=in.(1,2,3) where(age: 18..30) → age=gte.18&age=lte.30 where(age: { gt: 18, lt: 65 }) → age=gt.18&age=lt.65 where(companies: { name: “Acme” }) → companies.name=eq.Acme (AR-style joins filter) where.not(name: “John”) → name=not.eq.John
76
77
78
79
80
|
# File 'lib/active_postgrest/relation.rb', line 76
def where(filters = nil)
return WhereChain.new(self) if filters.nil?
clone_with { encode_filters!(filters) }
end
|
#with_schema(name) ⇒ Object
112
|
# File 'lib/active_postgrest/relation.rb', line 112
def with_schema(name) = clone_with { @schema = name }
|
#with_token(jwt) ⇒ Object
111
|
# File 'lib/active_postgrest/relation.rb', line 111
def with_token(jwt) = clone_with { @client = @client.with_token(jwt) }
|