airrel 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/examples/demo.rb +5 -5
- data/lib/airrel/formula_builder.rb +17 -45
- data/lib/airrel/relation.rb +37 -75
- data/lib/airrel/version.rb +1 -1
- data/lib/airrel/where_clause.rb +3 -7
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6db8b37d78ff312a990135ce202982f4e171295e298c23a538f9c6561b899979
|
|
4
|
+
data.tar.gz: df005dbf7fd3259b3947a372e974bebe67712e06df2cbefd0829e8d82f011adf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ad00efa854c772d5ca66d5a7eae1d80ac382fe2c3bfd17f7ced82e253991433442ae36c28ccde1144d165314883d7db032f054e03368575ec8ffa563d7674311
|
|
7
|
+
data.tar.gz: 54124653722d4d3fb13e0d2c5b4e9f892a7e8dda909e6bfdb1a9f542efbf792fa626d3c8ece35a57c89a18e7623b3182044d7fff39defda7387c7c90e3ba41c2
|
data/examples/demo.rb
CHANGED
|
@@ -16,10 +16,10 @@ end
|
|
|
16
16
|
# basic chaining
|
|
17
17
|
puts "=== basic chaining ==="
|
|
18
18
|
relation = Airrel::Relation.new(User)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
.where(role: "admin")
|
|
20
|
+
.where(active: true)
|
|
21
|
+
.order(created_at: :desc)
|
|
22
|
+
.limit(10)
|
|
23
23
|
|
|
24
24
|
puts relation.to_airtable
|
|
25
25
|
puts
|
|
@@ -28,7 +28,7 @@ puts
|
|
|
28
28
|
puts "=== hash to formula ==="
|
|
29
29
|
puts Airrel::FormulaBuilder.hash_to_formula(email: "test@example.com", age: 25)
|
|
30
30
|
puts Airrel::FormulaBuilder.hash_to_formula(age: 18..65)
|
|
31
|
-
puts Airrel::FormulaBuilder.hash_to_formula(role: [
|
|
31
|
+
puts Airrel::FormulaBuilder.hash_to_formula(role: %w[admin moderator])
|
|
32
32
|
puts Airrel::FormulaBuilder.hash_to_formula(deleted_at: nil)
|
|
33
33
|
puts
|
|
34
34
|
|
|
@@ -10,7 +10,7 @@ module Airrel
|
|
|
10
10
|
build_predicate(field_name, value)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
formulas.size == 1 ? formulas.first : "AND(#{formulas.join(
|
|
13
|
+
formulas.size == 1 ? formulas.first : "AND(#{formulas.join(", ")})"
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def build_predicate(field, value)
|
|
@@ -34,7 +34,7 @@ module Airrel
|
|
|
34
34
|
when Array
|
|
35
35
|
# IN query - use OR
|
|
36
36
|
or_conditions = value.map { |v| build_predicate(field, v) }
|
|
37
|
-
"OR(#{or_conditions.join(
|
|
37
|
+
"OR(#{or_conditions.join(", ")})"
|
|
38
38
|
else
|
|
39
39
|
# fallback - convert to string and escape
|
|
40
40
|
"{#{field}} = #{escape_string(value.to_s)}"
|
|
@@ -46,7 +46,7 @@ module Airrel
|
|
|
46
46
|
# so we need to escape backslashes first, then quotes
|
|
47
47
|
def escape_string(str)
|
|
48
48
|
# escape backslashes first (\ -> \\), then quotes (' -> \', " -> \")
|
|
49
|
-
escaped = str.to_s.gsub(
|
|
49
|
+
escaped = str.to_s.gsub("\\", "\\\\\\\\").gsub(/['"]/, '\\\\\0')
|
|
50
50
|
"'#{escaped}'"
|
|
51
51
|
end
|
|
52
52
|
|
|
@@ -70,62 +70,34 @@ module Airrel
|
|
|
70
70
|
|
|
71
71
|
# helper methods for building formulas programmatically
|
|
72
72
|
|
|
73
|
-
def all(*formulas)
|
|
74
|
-
"AND(#{formulas.join(', ')})"
|
|
75
|
-
end
|
|
73
|
+
def all(*formulas) = "AND(#{formulas.join(", ")})"
|
|
76
74
|
|
|
77
|
-
def any(*formulas)
|
|
78
|
-
"OR(#{formulas.join(', ')})"
|
|
79
|
-
end
|
|
75
|
+
def any(*formulas) = "OR(#{formulas.join(", ")})"
|
|
80
76
|
|
|
81
|
-
def none(formula)
|
|
82
|
-
"NOT(#{formula})"
|
|
83
|
-
end
|
|
77
|
+
def none(formula) = "NOT(#{formula})"
|
|
84
78
|
|
|
85
|
-
def eq(field, value)
|
|
86
|
-
build_predicate(field, value)
|
|
87
|
-
end
|
|
79
|
+
def eq(field, value) = build_predicate(field, value)
|
|
88
80
|
|
|
89
|
-
def neq(field, value)
|
|
90
|
-
none(eq(field, value))
|
|
91
|
-
end
|
|
81
|
+
def neq(field, value) = none(eq(field, value))
|
|
92
82
|
|
|
93
|
-
def gt(field, value)
|
|
94
|
-
"{#{field}} > #{value}"
|
|
95
|
-
end
|
|
83
|
+
def gt(field, value) = "{#{field}} > #{value}"
|
|
96
84
|
|
|
97
|
-
def gte(field, value)
|
|
98
|
-
"{#{field}} >= #{value}"
|
|
99
|
-
end
|
|
85
|
+
def gte(field, value) = "{#{field}} >= #{value}"
|
|
100
86
|
|
|
101
|
-
def lt(field, value)
|
|
102
|
-
"{#{field}} < #{value}"
|
|
103
|
-
end
|
|
87
|
+
def lt(field, value) = "{#{field}} < #{value}"
|
|
104
88
|
|
|
105
|
-
def lte(field, value)
|
|
106
|
-
"{#{field}} <= #{value}"
|
|
107
|
-
end
|
|
89
|
+
def lte(field, value) = "{#{field}} <= #{value}"
|
|
108
90
|
|
|
109
|
-
def blank(field)
|
|
110
|
-
"{#{field}} = BLANK()"
|
|
111
|
-
end
|
|
91
|
+
def blank(field) = "{#{field}} = BLANK()"
|
|
112
92
|
|
|
113
|
-
def present(field)
|
|
114
|
-
none(blank(field))
|
|
115
|
-
end
|
|
93
|
+
def present(field) = none(blank(field))
|
|
116
94
|
|
|
117
|
-
def find(field, search_string)
|
|
118
|
-
"FIND(#{escape_string(search_string)}, {#{field}})"
|
|
119
|
-
end
|
|
95
|
+
def find(field, search_string) = "FIND(#{escape_string(search_string)}, {#{field}})"
|
|
120
96
|
|
|
121
|
-
def search(field, search_string)
|
|
122
|
-
"SEARCH(#{escape_string(search_string)}, {#{field}})"
|
|
123
|
-
end
|
|
97
|
+
def search(field, search_string) = "SEARCH(#{escape_string(search_string)}, {#{field}})"
|
|
124
98
|
|
|
125
99
|
# for multi-select fields (checks if array contains value)
|
|
126
|
-
def contains(field, value)
|
|
127
|
-
"FIND(#{escape_string(value.to_s)}, {#{field}})"
|
|
128
|
-
end
|
|
100
|
+
def contains(field, value) = "FIND(#{escape_string(value.to_s)}, {#{field}})"
|
|
129
101
|
|
|
130
102
|
# check if multi-select contains ANY of the values
|
|
131
103
|
def contains_any(field, *values)
|
data/lib/airrel/relation.rb
CHANGED
|
@@ -17,46 +17,36 @@ module Airrel
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
# chaining methods (return new relations)
|
|
20
|
-
|
|
21
|
-
def where(conditions)
|
|
22
|
-
spawn.where!(conditions)
|
|
23
|
-
end
|
|
20
|
+
|
|
21
|
+
def where(conditions) = spawn.where!(conditions)
|
|
24
22
|
|
|
25
23
|
def where!(conditions)
|
|
26
24
|
@where_clause = @where_clause.merge(conditions)
|
|
27
25
|
self
|
|
28
26
|
end
|
|
29
27
|
|
|
30
|
-
def order(*args)
|
|
31
|
-
spawn.order!(*args)
|
|
32
|
-
end
|
|
28
|
+
def order(*args) = spawn.order!(*args)
|
|
33
29
|
|
|
34
30
|
def order!(*args)
|
|
35
31
|
@order_values += parse_order_args(args)
|
|
36
32
|
self
|
|
37
33
|
end
|
|
38
34
|
|
|
39
|
-
def limit(value)
|
|
40
|
-
spawn.limit!(value)
|
|
41
|
-
end
|
|
35
|
+
def limit(value) = spawn.limit!(value)
|
|
42
36
|
|
|
43
37
|
def limit!(value)
|
|
44
38
|
@limit_value = value
|
|
45
39
|
self
|
|
46
40
|
end
|
|
47
41
|
|
|
48
|
-
def offset(value)
|
|
49
|
-
spawn.offset!(value)
|
|
50
|
-
end
|
|
42
|
+
def offset(value) = spawn.offset!(value)
|
|
51
43
|
|
|
52
44
|
def offset!(value)
|
|
53
45
|
@offset_value = value
|
|
54
46
|
self
|
|
55
47
|
end
|
|
56
48
|
|
|
57
|
-
def reorder(*args)
|
|
58
|
-
spawn.reorder!(*args)
|
|
59
|
-
end
|
|
49
|
+
def reorder(*args) = spawn.reorder!(*args)
|
|
60
50
|
|
|
61
51
|
def reorder!(*args)
|
|
62
52
|
@order_values = parse_order_args(args)
|
|
@@ -81,7 +71,7 @@ module Airrel
|
|
|
81
71
|
if @order_values.empty?
|
|
82
72
|
raise ArgumentError, "last requires an order to be specified (use .order(:created_at) or similar)"
|
|
83
73
|
end
|
|
84
|
-
|
|
74
|
+
|
|
85
75
|
reversed = reverse_order
|
|
86
76
|
if limit
|
|
87
77
|
# only load what we need
|
|
@@ -92,13 +82,9 @@ module Airrel
|
|
|
92
82
|
end
|
|
93
83
|
end
|
|
94
84
|
|
|
95
|
-
def find(id)
|
|
96
|
-
klass.find(id)
|
|
97
|
-
end
|
|
85
|
+
def find(id) = klass.find(id)
|
|
98
86
|
|
|
99
|
-
def find_by(conditions)
|
|
100
|
-
where(conditions).first
|
|
101
|
-
end
|
|
87
|
+
def find_by(conditions) = where(conditions).first
|
|
102
88
|
|
|
103
89
|
def find_by!(conditions)
|
|
104
90
|
# Raise error class that will be defined by the consumer (norairrecord)
|
|
@@ -106,19 +92,13 @@ module Airrel
|
|
|
106
92
|
find_by(conditions) || raise(error_class, "Record not found")
|
|
107
93
|
end
|
|
108
94
|
|
|
109
|
-
def all
|
|
110
|
-
spawn
|
|
111
|
-
end
|
|
95
|
+
def all = spawn
|
|
112
96
|
|
|
113
|
-
def to_a
|
|
114
|
-
load_records
|
|
115
|
-
end
|
|
97
|
+
def to_a = load_records
|
|
116
98
|
|
|
117
99
|
alias to_ary to_a
|
|
118
100
|
|
|
119
|
-
def each(&block)
|
|
120
|
-
load_records.each(&block)
|
|
121
|
-
end
|
|
101
|
+
def each(&block) = load_records.each(&block)
|
|
122
102
|
|
|
123
103
|
# batch iteration for large result sets
|
|
124
104
|
def find_each(batch_size: 100, &block)
|
|
@@ -127,43 +107,35 @@ module Airrel
|
|
|
127
107
|
end
|
|
128
108
|
end
|
|
129
109
|
|
|
130
|
-
def find_in_batches(batch_size: 100
|
|
110
|
+
def find_in_batches(batch_size: 100)
|
|
131
111
|
current_offset = 0
|
|
132
|
-
|
|
112
|
+
|
|
133
113
|
loop do
|
|
134
114
|
# create a new relation with limit and offset
|
|
135
115
|
batch_relation = spawn
|
|
136
116
|
batch_relation.instance_variable_set(:@limit_value, batch_size)
|
|
137
117
|
batch_relation.instance_variable_set(:@offset_value, current_offset)
|
|
138
|
-
|
|
118
|
+
|
|
139
119
|
batch = batch_relation.to_a
|
|
140
120
|
break if batch.empty?
|
|
141
|
-
|
|
121
|
+
|
|
142
122
|
yield batch
|
|
143
|
-
|
|
123
|
+
|
|
144
124
|
break if batch.size < batch_size # last batch
|
|
125
|
+
|
|
145
126
|
current_offset += batch_size
|
|
146
127
|
end
|
|
147
128
|
end
|
|
148
129
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
# or using exists? check for any?
|
|
152
|
-
load_records.size
|
|
153
|
-
end
|
|
130
|
+
# airtable doesn't have a count API >:-/ we gotta paginate through EVERYTHING...
|
|
131
|
+
def count = load_records.size
|
|
154
132
|
|
|
155
|
-
def empty?
|
|
156
|
-
!any?
|
|
157
|
-
end
|
|
133
|
+
def empty? = !any?
|
|
158
134
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def exists?
|
|
165
|
-
any?
|
|
166
|
-
end
|
|
135
|
+
# OPTIMIZE: only load 1 record to check existence
|
|
136
|
+
def any? = limit(1).load_records.any?
|
|
137
|
+
|
|
138
|
+
def exists? = any?
|
|
167
139
|
|
|
168
140
|
# inspection
|
|
169
141
|
|
|
@@ -171,16 +143,12 @@ module Airrel
|
|
|
171
143
|
entries = load_records.take(11).map!(&:inspect)
|
|
172
144
|
entries[10] = "..." if entries.size == 11
|
|
173
145
|
|
|
174
|
-
"#<#{self.class.name} [#{entries.join(
|
|
146
|
+
"#<#{self.class.name} [#{entries.join(", ")}]>"
|
|
175
147
|
end
|
|
176
148
|
|
|
177
|
-
def to_airtable
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def to_sql
|
|
182
|
-
to_airtable_params.inspect
|
|
183
|
-
end
|
|
149
|
+
def to_airtable = to_airtable_params
|
|
150
|
+
|
|
151
|
+
def to_sql = to_airtable_params.inspect
|
|
184
152
|
|
|
185
153
|
# execution
|
|
186
154
|
|
|
@@ -194,9 +162,7 @@ module Airrel
|
|
|
194
162
|
load
|
|
195
163
|
end
|
|
196
164
|
|
|
197
|
-
def loaded?
|
|
198
|
-
@loaded
|
|
199
|
-
end
|
|
165
|
+
def loaded? = @loaded
|
|
200
166
|
|
|
201
167
|
def reset
|
|
202
168
|
@loaded = false
|
|
@@ -212,9 +178,7 @@ module Airrel
|
|
|
212
178
|
|
|
213
179
|
protected
|
|
214
180
|
|
|
215
|
-
def spawn
|
|
216
|
-
clone.tap { |r| r.reset }
|
|
217
|
-
end
|
|
181
|
+
def spawn = clone.tap(&:reset)
|
|
218
182
|
|
|
219
183
|
def load_records
|
|
220
184
|
load unless loaded?
|
|
@@ -229,25 +193,23 @@ module Airrel
|
|
|
229
193
|
|
|
230
194
|
def to_airtable_params
|
|
231
195
|
params = {}
|
|
232
|
-
|
|
196
|
+
|
|
233
197
|
# filter
|
|
234
|
-
if @where_clause.any?
|
|
235
|
-
params[:filter] = @where_clause.to_airtable_formula
|
|
236
|
-
end
|
|
198
|
+
params[:filter] = @where_clause.to_airtable_formula if @where_clause.any?
|
|
237
199
|
|
|
238
200
|
# sort
|
|
239
201
|
if @order_values.any?
|
|
240
|
-
params[:sort] = @order_values.map
|
|
202
|
+
params[:sort] = @order_values.map do |field, direction|
|
|
241
203
|
{ field: field.to_s, direction: direction.to_s }
|
|
242
|
-
|
|
204
|
+
end
|
|
243
205
|
end
|
|
244
206
|
|
|
245
207
|
# limit
|
|
246
208
|
params[:max_records] = @limit_value if @limit_value
|
|
247
|
-
|
|
209
|
+
|
|
248
210
|
# offset (airtable calls it offset in pagination)
|
|
249
211
|
params[:offset] = @offset_value if @offset_value
|
|
250
|
-
|
|
212
|
+
|
|
251
213
|
# pagination control
|
|
252
214
|
params[:paginate] = @paginate if defined?(@paginate)
|
|
253
215
|
|
data/lib/airrel/version.rb
CHANGED
data/lib/airrel/where_clause.rb
CHANGED
|
@@ -23,19 +23,15 @@ module Airrel
|
|
|
23
23
|
WhereClause.new(@predicates + new_predicates)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
def any?
|
|
27
|
-
@predicates.any?
|
|
28
|
-
end
|
|
26
|
+
def any? = @predicates.any?
|
|
29
27
|
|
|
30
28
|
def to_airtable_formula
|
|
31
29
|
return nil if @predicates.empty?
|
|
32
30
|
return @predicates.first if @predicates.size == 1
|
|
33
31
|
|
|
34
|
-
"AND(#{@predicates.join(
|
|
32
|
+
"AND(#{@predicates.join(", ")})"
|
|
35
33
|
end
|
|
36
34
|
|
|
37
|
-
def inspect
|
|
38
|
-
@predicates.inspect
|
|
39
|
-
end
|
|
35
|
+
def inspect = @predicates.inspect
|
|
40
36
|
end
|
|
41
37
|
end
|