Class: ActivePostgrest::Relation
- Inherits:
-
Object
- Object
- ActivePostgrest::Relation
show all
- Includes:
- Mutations, 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(other) ⇒ Object
where(a: 1).or(where(b: 2)) → or=(a.eq.1,b.eq.2) where(a: 1).where(c: 3).or(where(b: 2)) → or=(and(a.eq.1,c.eq.3),b.eq.2).
-
#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
Methods included from Mutations
#create, #create!, #delete_all, #insert, #insert_all, #update_all, #upsert, #upsert_all
Constructor Details
#initialize(table, client, model_class) ⇒ Relation
Returns a new instance of Relation.
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
# File 'lib/active_postgrest/relation.rb', line 18
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
216
217
218
219
220
221
222
223
|
# File 'lib/active_postgrest/relation.rb', line 216
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)
102
103
104
105
|
# File 'lib/active_postgrest/relation.rb', line 102
def and_where(conditions)
parts = Array(conditions).flat_map { |f| condition_parts(f) }
clone_with { @and_conditions.concat(parts) }
end
|
#anonymous ⇒ Object
119
|
# File 'lib/active_postgrest/relation.rb', line 119
def anonymous = clone_with { @client = @client.anonymous }
|
#any?(&block) ⇒ Boolean
158
|
# File 'lib/active_postgrest/relation.rb', line 158
def any?(&block) = block ? super : count.positive?
|
#average(col) ⇒ Object
164
|
# File 'lib/active_postgrest/relation.rb', line 164
def average(col) = aggregate_value("#{col}.avg()", 'avg')
|
#count(mode = :exact) ⇒ Object
145
146
147
148
149
150
151
152
153
154
155
156
|
# File 'lib/active_postgrest/relation.rb', line 145
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
123
124
125
|
# File 'lib/active_postgrest/relation.rb', line 123
def each(&)
to_a.each(&)
end
|
#embed(name, fields: []) ⇒ Object
embed(:mother, fields: [:id, :first_name]) — computed relationship
56
57
58
|
# File 'lib/active_postgrest/relation.rb', line 56
def embed(name, fields: [])
add_embed(name.to_s, name.to_s, fields.map(&:to_s), {})
end
|
#exists? ⇒ Boolean
162
|
# File 'lib/active_postgrest/relation.rb', line 162
def exists? = any?
|
#explain ⇒ Object
203
204
205
|
# File 'lib/active_postgrest/relation.rb', line 203
def explain
@client.explain(@table, build_params, schema: @schema)
end
|
#first ⇒ Object
134
135
136
|
# File 'lib/active_postgrest/relation.rb', line 134
def first
limit(1).to_a.first
end
|
#inspect ⇒ Object
229
230
231
|
# File 'lib/active_postgrest/relation.rb', line 229
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
44
45
46
47
|
# File 'lib/active_postgrest/relation.rb', line 44
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
138
139
140
141
142
143
|
# File 'lib/active_postgrest/relation.rb', line 138
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)
50
51
52
53
|
# File 'lib/active_postgrest/relation.rb', line 50
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
107
|
# File 'lib/active_postgrest/relation.rb', line 107
def limit(n) = clone_with { @limit_val = n }
|
#many? ⇒ Boolean
161
|
# File 'lib/active_postgrest/relation.rb', line 161
def many? = count > 1
|
#maximum(col) ⇒ Object
167
|
# File 'lib/active_postgrest/relation.rb', line 167
def maximum(col) = aggregate_value("#{col}.max()", 'max')
|
#minimum(col) ⇒ Object
166
|
# File 'lib/active_postgrest/relation.rb', line 166
def minimum(col) = aggregate_value("#{col}.min()", 'min')
|
#none ⇒ Object
118
|
# File 'lib/active_postgrest/relation.rb', line 118
def none = clone_with { @null = true }
|
#none?(&block) ⇒ Boolean
159
|
# File 'lib/active_postgrest/relation.rb', line 159
def none?(&block) = block ? super : count.zero?
|
#not_where(filters) ⇒ Object
74
75
76
|
# File 'lib/active_postgrest/relation.rb', line 74
def not_where(filters)
clone_with { encode_filters!(filters, negate: true) }
end
|
#offset(n) ⇒ Object
108
|
# File 'lib/active_postgrest/relation.rb', line 108
def offset(n) = clone_with { @offset_val = n }
|
#one?(&block) ⇒ Boolean
160
|
# File 'lib/active_postgrest/relation.rb', line 160
def one?(&block) = block ? super : count == 1
|
#or(other) ⇒ Object
where(a: 1).or(where(b: 2)) → or=(a.eq.1,b.eq.2) where(a: 1).where(c: 3).or(where(b: 2)) → or=(and(a.eq.1,c.eq.3),b.eq.2)
86
87
88
89
90
91
92
93
94
95
96
97
98
99
|
# File 'lib/active_postgrest/relation.rb', line 86
def or(other)
other_or = other.instance_variable_get(:@or_conditions)
other_and = other.instance_variable_get(:@and_conditions)
if @or_conditions.any? || @and_conditions.any? || other_or.any? || other_and.any?
raise ArgumentError, '#or does not support a relation with existing or_where/and_where conditions'
end
left = or_group(encoded_filter_conditions)
right = or_group(other.encoded_filter_conditions)
clone_with do
@filters = {}
@or_conditions.concat([left, right].compact)
end
end
|
#or_where(conditions) ⇒ Object
or_where([{ age: { lt: 18 } }, { status: “active” }]) → or=(age.lt.18,status.eq.active)
79
80
81
82
|
# File 'lib/active_postgrest/relation.rb', line 79
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
110
111
112
|
# File 'lib/active_postgrest/relation.rb', line 110
def order(col, dir = :asc, nulls: nil)
clone_with { @order_val = build_order(col, dir, nulls) }
end
|
#pick(*cols) ⇒ Object
177
178
179
|
# File 'lib/active_postgrest/relation.rb', line 177
def pick(*cols)
pluck(*cols).first
end
|
#pluck(*cols) ⇒ Object
169
170
171
172
173
174
175
|
# File 'lib/active_postgrest/relation.rb', line 169
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
114
115
116
|
# File 'lib/active_postgrest/relation.rb', line 114
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
225
226
227
|
# File 'lib/active_postgrest/relation.rb', line 225
def respond_to_missing?(name, include_private = false)
@model_class.respond_to?(name) || super
end
|
#select(*cols) ⇒ Object
34
35
36
|
# File 'lib/active_postgrest/relation.rb', line 34
def select(*cols)
clone_with { @selects.concat(cols.map(&:to_s)) }
end
|
#spread(*tables) ⇒ Object
38
39
40
|
# File 'lib/active_postgrest/relation.rb', line 38
def spread(*tables)
clone_with { @selects.concat(tables.map { "...#{_1}" }) }
end
|
#sum(col) ⇒ Object
165
|
# File 'lib/active_postgrest/relation.rb', line 165
def sum(col) = aggregate_value("#{col}.sum()", 'sum')
|
#to_a ⇒ Object
127
128
129
130
131
132
|
# File 'lib/active_postgrest/relation.rb', line 127
def to_a
return [] if @null
Array(@client.get(@table, build_params, schema: @schema).body)
.map { |attrs| @model_class.new(attrs, true, @client) }
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.
190
191
192
193
194
195
196
197
198
199
200
201
|
# File 'lib/active_postgrest/relation.rb', line 190
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
207
208
209
210
211
212
213
214
|
# File 'lib/active_postgrest/relation.rb', line 207
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
68
69
70
71
72
|
# File 'lib/active_postgrest/relation.rb', line 68
def where(filters = nil)
return WhereChain.new(self) if filters.nil?
clone_with { encode_filters!(filters) }
end
|
#with_schema(name) ⇒ Object
121
|
# File 'lib/active_postgrest/relation.rb', line 121
def with_schema(name) = clone_with { @schema = name }
|
#with_token(jwt) ⇒ Object
120
|
# File 'lib/active_postgrest/relation.rb', line 120
def with_token(jwt) = clone_with { @client = @client.with_token(jwt) }
|