yax-fauna 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,147 @@
1
+ module Fauna
2
+ ##
3
+ # A Ref.
4
+ #
5
+ # Reference: {FaunaDB Special Types}[https://fauna.com/documentation/queries#values-special_types]
6
+ class Ref
7
+ # The raw attributes of ref.
8
+ attr_accessor :id, :class_, :database
9
+
10
+ ##
11
+ # Creates a Ref object.
12
+ #
13
+ # :call-seq:
14
+ # Ref.new('prydain', Native.databases)
15
+ #
16
+ # +id+: A string.
17
+ # +class_+: A Ref.
18
+ # +database+: A Ref.
19
+ def initialize(id, class_ = nil, database = nil)
20
+ fail ArgumentError.new 'id cannot be nil' if id.nil?
21
+
22
+ @id = id
23
+ @class_ = class_ unless class_.nil?
24
+ @database = database unless database.nil?
25
+ end
26
+
27
+ # Converts the Ref to a string
28
+ def to_s
29
+ cls = class_.nil? ? '' : ",class=#{class_.to_s}"
30
+ db = database.nil? ? '' : ",database=#{database.to_s}"
31
+ "Ref(id=#{id}#{cls}#{db})"
32
+ end
33
+
34
+ # Returns +true+ if +other+ is a Ref and contains the same value.
35
+ def ==(other)
36
+ return false unless other.is_a? Ref
37
+ id == other.id && class_ == other.class_ && database == other.database
38
+ end
39
+
40
+ alias_method :eql?, :==
41
+ end
42
+
43
+ class Native
44
+ @@natives = %w(classes indexes databases functions keys tokens credentials).freeze
45
+
46
+ @@natives.each do |id|
47
+ instance_variable_set "@#{id}", Ref.new(id).freeze
48
+ self.class.send :attr_reader, id.to_sym
49
+ end
50
+
51
+ def self.from_name(id)
52
+ return Ref.new(id) unless @@natives.include? id
53
+ send id.to_sym
54
+ end
55
+ end
56
+
57
+ ##
58
+ # A SetRef.
59
+ #
60
+ # Reference: {FaunaDB Special Types}[https://fauna.com/documentation/queries#values-special_types]
61
+ class SetRef
62
+ # The raw set hash.
63
+ attr_accessor :value
64
+
65
+ ##
66
+ # Creates a new SetRef with the given parameters.
67
+ #
68
+ # +params+:: Hash of parameters to build the SetRef with.
69
+ #
70
+ # Reference: {FaunaDB Special Types}[https://fauna.com/documentation/queries#values-special_types]
71
+ def initialize(params = {})
72
+ self.value = params
73
+ end
74
+
75
+ # Returns +true+ if +other+ is a SetRef and contains the same value.
76
+ def ==(other)
77
+ return false unless other.is_a? SetRef
78
+ value == other.value
79
+ end
80
+
81
+ alias_method :eql?, :==
82
+ end
83
+
84
+ ##
85
+ # A Bytes wrapper.
86
+ #
87
+ # Reference: {FaunaDB Special Types}[https://fauna.com/documentation/queries#values-special_types]
88
+ class Bytes
89
+ # The raw bytes.
90
+ attr_accessor :bytes
91
+
92
+ ##
93
+ # Creates a new Bytes wrapper with the given parameters.
94
+ #
95
+ # +bytes+:: The bytes to be wrapped by the Bytes object.
96
+ #
97
+ # Reference: {FaunaDB Special Types}[https://fauna.com/documentation/queries#values-special_types]
98
+ def initialize(bytes)
99
+ self.bytes = bytes
100
+ end
101
+
102
+ # Converts the Bytes to base64-encoded form.
103
+ def to_base64
104
+ Base64.urlsafe_encode64(bytes)
105
+ end
106
+
107
+ # Returns +true+ if +other+ is a Bytes and contains the same bytes.
108
+ def ==(other)
109
+ return false unless other.is_a? Bytes
110
+ bytes == other.bytes
111
+ end
112
+
113
+ alias_method :eql?, :==
114
+
115
+ # Create new Bytes object from Base64 encoded bytes.
116
+ def self.from_base64(enc)
117
+ new(Base64.urlsafe_decode64(enc))
118
+ end
119
+ end
120
+
121
+ ##
122
+ # A QueryV.
123
+ #
124
+ # Reference: {FaunaDB Special Types}[https://fauna.com/documentation/queries-values-special_types]
125
+ class QueryV
126
+ # The raw query hash.
127
+ attr_accessor :value
128
+
129
+ ##
130
+ # Creates a new QueryV with the given parameters.
131
+ #
132
+ # +params+:: Hash of parameters to build the QueryV with.
133
+ #
134
+ # Reference: {FaunaDB Special Types}[https://fauna.com/documentation/queries-values-special_types]
135
+ def initialize(params = {})
136
+ self.value = params
137
+ end
138
+
139
+ # Returns +true+ if +other+ is a QueryV and contains the same value.
140
+ def ==(other)
141
+ return false unless other.is_a? QueryV
142
+ value == other.value
143
+ end
144
+
145
+ alias_method :eql?, :==
146
+ end
147
+ end
@@ -0,0 +1,374 @@
1
+ module Fauna
2
+ ##
3
+ # Helper for handling pagination over sets.
4
+ #
5
+ # Given a client and a set, allows you to iterate as well as individually move page by page over a set.
6
+ #
7
+ # Pages lazily load the contents of the page. Loading will occur when +data+, +before+, or +after+ are first accessed
8
+ # for a new page. Additionally this will occur when calling +page_before+ or +page_after+ without calling one of the
9
+ # data methods first (as the first page must be checked to find the next page). Pages created by builders will unload
10
+ # any data from the current page. Pages will always proceed in the requested direction.
11
+ #
12
+ # Explicit paging is done via the +page_after+ and +page_before+ methods. Iteration can be done via the +each+ and
13
+ # +reverse_each+ enumerators. A single page can be retrieved by passing a cursor and then accessing it's data.
14
+ #
15
+ # Examples:
16
+ #
17
+ # Paging over a class index
18
+ #
19
+ # page = Page.new(client, Query.match(Query.index('items')))
20
+ #
21
+ # Paging over a class index 5 at a time, mapping the refs to the +data.value+ for each instance
22
+ #
23
+ # page = Page.new(client, Query.match(Query.index('items')), size: 5) do |ref|
24
+ # select ['data', 'value'], get(ref)
25
+ # end
26
+ #
27
+ # # Same thing, but using builders instead
28
+ #
29
+ # page = Page.new(client, Query.match(Query.index('items'))).with_params(size: 5).map do |ref|
30
+ # select ['data', 'value'], get(ref)
31
+ # end
32
+ #
33
+ # Paging over a class index, mapping refs to the +data.value+ for each instance, filtering out odd numbers, and
34
+ # multiplying the value:
35
+ #
36
+ # page = Page.new(client, Query.match(Query.index('items'))).map do |ref|
37
+ # select ['data', 'value'], get(ref)
38
+ # end.filter do |value|
39
+ # equals modulo(value, 2), 0
40
+ # end.map do |value|
41
+ # multiply value, 2
42
+ # end
43
+ class Page
44
+ ##
45
+ # Creates a pagination helper for paging/iterating over a set.
46
+ #
47
+ # +client+:: Client to execute queries with.
48
+ # +set+:: A set query to paginate over.
49
+ # +params+:: A list of parameters to pass to {paginate}[https://fauna.com/documentation/queries#read_functions-paginate_set].
50
+ # +lambda+:: Optional lambda to map the generated paginate query with. The block will be run in a query context.
51
+ # An element from the current page will be passed into the block as an argument. See #map for more info.
52
+ def initialize(client, set, params = {}, &lambda)
53
+ @client = client
54
+ @set = set
55
+ @params = params.dup
56
+ @fauna_funcs = []
57
+ @postprocessing_map = nil
58
+
59
+ @fauna_funcs << proc { |query| map(query, &lambda) } unless lambda.nil?
60
+
61
+ unload_page
62
+ @params.freeze
63
+ end
64
+
65
+ # Returns +true+ if +other+ is a Page and contains the same configuration and data.
66
+ def ==(other)
67
+ return false unless other.is_a? Page
68
+ @populated == other.instance_variable_get(:@populated) &&
69
+ @data == other.instance_variable_get(:@data) &&
70
+ @before == other.instance_variable_get(:@before) &&
71
+ @after == other.instance_variable_get(:@after) &&
72
+ @client == other.instance_variable_get(:@client) &&
73
+ @set == other.instance_variable_get(:@set) &&
74
+ @params == other.instance_variable_get(:@params) &&
75
+ @fauna_funcs == other.instance_variable_get(:@fauna_funcs) &&
76
+ @postprocessing_map == other.instance_variable_get(:@postprocessing_map)
77
+ end
78
+
79
+ alias_method :eql?, :==
80
+
81
+ # The configured params used for the current pagination.
82
+ attr_reader :params
83
+
84
+ ##
85
+ # Explicitly loads data for the current page if it has not already been loaded.
86
+ #
87
+ # Returns +true+ if the data was just loaded and +false+ if it was already loaded.
88
+ def load!
89
+ if @populated
90
+ false
91
+ else
92
+ load_page(get_page(@params))
93
+ true
94
+ end
95
+ end
96
+
97
+ # :section: Data
98
+
99
+ ##
100
+ # Data contained within the current page.
101
+ #
102
+ # Lazily loads the page data if it has not already been loaded.
103
+ def data
104
+ load!
105
+ @data
106
+ end
107
+
108
+ ##
109
+ # Before cursor for the current page.
110
+ #
111
+ # Lazily loads the page data if it has not already been loaded.
112
+ def before
113
+ load!
114
+ @before
115
+ end
116
+
117
+ ##
118
+ # After cursor for the current page.
119
+ #
120
+ # Lazily loads the page data if it has not already been loaded.
121
+ def after
122
+ load!
123
+ @after
124
+ end
125
+
126
+ # :section: Builders
127
+
128
+ ##
129
+ # Returns a copy of the page with the given +params+ set.
130
+ #
131
+ # See {paginate}[https://fauna.com/documentation/queries#read_functions-paginate_set] for more details.
132
+ def with_params(params = {})
133
+ with_dup do |page|
134
+ page_params = page.instance_variable_get(:@params)
135
+
136
+ if CURSOR_KEYS.any? { |key| params.include? key }
137
+ # Remove previous cursor
138
+ CURSOR_KEYS.each { |key| page_params.delete key }
139
+ end
140
+
141
+ # Update params
142
+ page_params.merge!(params)
143
+ end
144
+ end
145
+
146
+ ##
147
+ # Returns a copy of the page with a fauna +map+ using the given lambda chained onto the paginate query.
148
+ #
149
+ # The lambda will be passed into a +map+ function that wraps the generated paginate query. Additional collection
150
+ # functions may be combined by chaining them together.
151
+ #
152
+ # The lambda will be run in a Query.expr context, and passed an element from the current page as an argument.
153
+ #
154
+ # Example of mapping a set of refs to their instances:
155
+ #
156
+ # page.map { |ref| get ref }
157
+ def map(&lambda)
158
+ with_dup do |page|
159
+ page.instance_variable_get(:@fauna_funcs) << proc { |query| map(query, &lambda) }
160
+ end
161
+ end
162
+
163
+ ##
164
+ # Returns a copy of the page with a fauna +filter+ using the given lambda chained onto the paginate query.
165
+ #
166
+ # The lambda will be passed into a +filter+ function that wraps the generated paginate query. Additional collection
167
+ # functions may be combined by chaining them together.
168
+ #
169
+ # The lambda will be run in a Query.expr context, and passed an element from the current page as an argument.
170
+ #
171
+ # Example of filtering out odd numbers from a set of numbers:
172
+ #
173
+ # page.filter { |value| equals(modulo(value, 2), 0) }
174
+ def filter(&lambda)
175
+ with_dup do |page|
176
+ page.instance_variable_get(:@fauna_funcs) << proc { |query| filter(query, &lambda) }
177
+ end
178
+ end
179
+
180
+ ##
181
+ # Returns a copy of the page with the given ruby block set.
182
+ #
183
+ # The block will be used to map the returned data elements from the executed fauna query. Only one postprocessing
184
+ # map can be configured at a time.
185
+ #
186
+ # Intended for use when the elements in a page need to be converted within ruby (ie loading into a model). Wherever
187
+ # the operation can be performed from within FaunaDB, +map+ should be used instead.
188
+ #
189
+ # The block will be passed the each element as a parameter from the data of the page currently being loaded.
190
+ #
191
+ # Example of loading instances into your own model:
192
+ #
193
+ # page.postprocessing_map { |instance| YourModel.load(instance) }
194
+ def postprocessing_map(&block)
195
+ with_dup do |page|
196
+ page.instance_variable_set(:@postprocessing_map, block)
197
+ end
198
+ end
199
+
200
+ # :section: Pagination
201
+
202
+ ##
203
+ # The page after the current one in the set.
204
+ #
205
+ # Returns +nil+ when there are no more pages after the current page. Lazily loads the current page if it has not
206
+ # already been loaded in order to determine the page after.
207
+ def page_after
208
+ new_page(:after)
209
+ end
210
+
211
+ ##
212
+ # The page before the current one in the set.
213
+ #
214
+ # Returns +nil+ when there are no more pages before the current page. Lazily loads the current page if it has not
215
+ # already been loaded in order to determine the page before.
216
+ def page_before
217
+ new_page(:before)
218
+ end
219
+
220
+ ##
221
+ # Returns an enumerator that iterates in the +after+ direction.
222
+ #
223
+ # When a block is provided, the return of the block will always be +nil+ (to avoid loading large sets into memory).
224
+ def each
225
+ return enum_for(:each) unless block_given?
226
+
227
+ # Return current page
228
+ yield data
229
+
230
+ # Begin returning pages after
231
+ page = self.page_after
232
+ until page.nil?
233
+ yield page.data
234
+ page = page.page_after
235
+ end
236
+ end
237
+
238
+ ##
239
+ # Returns an enumerator that iterates in the +before+ direction.
240
+ #
241
+ # When a block is provided, the return of the block will always be +nil+ (to avoid loading large sets into memory).
242
+ #
243
+ # While the paging will occur in the reverse direction, the data returned will still be in the normal direction.
244
+ def reverse_each
245
+ return enum_for(:reverse_each) unless block_given?
246
+
247
+ # Return current page
248
+ yield data
249
+
250
+ # Begin returning pages before
251
+ page = self.page_before
252
+ until page.nil?
253
+ yield page.data
254
+ page = page.page_before
255
+ end
256
+ end
257
+
258
+ ##
259
+ # Returns the flattened contents of the set as an array.
260
+ #
261
+ # Ideal for when you need the full contents of a set with a known small size. If you need to iterate over a set
262
+ # of large or unknown size, it is recommended to use +each+ instead.
263
+ #
264
+ # The set is paged in the +after+ direction.
265
+ def all
266
+ each.flat_map { |x| x }
267
+ end
268
+
269
+ ##
270
+ # Iterates over the entire set, applying the configured lambda in a foreach block, and discarding the result.
271
+ #
272
+ # Ideal for performing a +foreach+ over an entire set (like deleting all instances in a set). The set is iterated in
273
+ # the +after+ direction. The +foreach+ will be chained on top of any previously configured collection functions.
274
+ #
275
+ # Example of deleting every instance in a set:
276
+ #
277
+ # page.foreach! { |ref| delete ref }
278
+ def foreach!(&lambda)
279
+ # Create new page with foreach block
280
+ iter_page = with_dup do |page|
281
+ page.instance_variable_get(:@fauna_funcs) << proc { |query| foreach(query, &lambda) }
282
+ end
283
+
284
+ # Apply to all pages in the set
285
+ until iter_page.nil?
286
+ iter_page.load!
287
+ iter_page = iter_page.page_after
288
+ end
289
+ nil
290
+ end
291
+
292
+ def dup # :nodoc:
293
+ page = super
294
+ page.instance_variable_set(:@params, @params.dup)
295
+ page.instance_variable_set(:@fauna_funcs, @fauna_funcs.dup)
296
+ page
297
+ end
298
+
299
+ private
300
+
301
+ CURSOR_KEYS = [:before, :after].freeze # :nodoc:
302
+
303
+ def with_dup
304
+ # Create a copy and drop loaded data
305
+ page = self.dup
306
+ page.send(:unload_page)
307
+
308
+ # Yield page for manipulation
309
+ yield page
310
+
311
+ # Freeze params and return page
312
+ page.params.freeze
313
+ page
314
+ end
315
+
316
+ def get_page(params)
317
+ # Create query
318
+ query = Query.paginate @set, params
319
+
320
+ unless @fauna_funcs.empty?
321
+ # Wrap paginate query with the fauna maps
322
+ dsl = Query::QueryDSLContext.new
323
+ @fauna_funcs.each do |proc|
324
+ query = DSLContext.eval_dsl(dsl, query, &proc)
325
+ end
326
+ query = Query::Expr.wrap query
327
+ end
328
+
329
+ # Execute query
330
+ result = @client.query query
331
+
332
+ unless @postprocessing_map.nil?
333
+ # Map the resulting data with the ruby block
334
+ result[:data].map! { |element| @postprocessing_map.call(element) }
335
+ end
336
+
337
+ # Return result
338
+ result
339
+ end
340
+
341
+ def load_page(page)
342
+ # Not initial after the first page
343
+ @populated = true
344
+
345
+ # Update the page fields
346
+ @data = page[:data]
347
+ @before = page[:before]
348
+ @after = page[:after]
349
+ end
350
+
351
+ def unload_page
352
+ # Reset paging
353
+ @populated = false
354
+
355
+ # Reset data
356
+ @data = nil
357
+ @before = nil
358
+ @after = nil
359
+ end
360
+
361
+ def new_page(direction)
362
+ fail "Invalid direction; must be one of #{CURSOR_KEYS}" unless CURSOR_KEYS.include?(direction)
363
+
364
+ cursor = self.send(direction)
365
+
366
+ # If there is no next cursor, we have reached the end of the set.
367
+ # Return +nil+.
368
+ return nil if cursor.nil?
369
+
370
+ # Use the configured cursor to fetch the first page.
371
+ with_params(direction => cursor)
372
+ end
373
+ end
374
+ end