contentstack 0.6.3.1 → 0.8.0

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/check-branch.yml +20 -0
  3. data/.github/workflows/codeql-analysis.yml +68 -68
  4. data/.github/workflows/jira.yml +28 -28
  5. data/.github/workflows/release-gem.yml +27 -31
  6. data/.github/workflows/sca-scan.yml +15 -15
  7. data/.gitignore +11 -11
  8. data/.yardopts +6 -6
  9. data/CHANGELOG.md +135 -114
  10. data/CODEOWNERS +1 -1
  11. data/CODE_OF_CONDUCT.md +73 -73
  12. data/Gemfile +2 -2
  13. data/Gemfile.lock +84 -76
  14. data/LICENSE.txt +21 -21
  15. data/README.md +197 -197
  16. data/SECURITY.md +27 -27
  17. data/contentstack.gemspec +29 -29
  18. data/lib/contentstack/api.rb +191 -191
  19. data/lib/contentstack/asset.rb +68 -68
  20. data/lib/contentstack/asset_collection.rb +27 -27
  21. data/lib/contentstack/client.rb +127 -92
  22. data/lib/contentstack/content_type.rb +53 -53
  23. data/lib/contentstack/entry.rb +235 -221
  24. data/lib/contentstack/entry_collection.rb +44 -44
  25. data/lib/contentstack/error.rb +6 -6
  26. data/lib/contentstack/query.rb +665 -653
  27. data/lib/contentstack/region.rb +15 -6
  28. data/lib/contentstack/sync_result.rb +29 -29
  29. data/lib/contentstack/version.rb +3 -3
  30. data/lib/contentstack.rb +31 -31
  31. data/lib/util.rb +110 -110
  32. data/rakefile.rb +3 -3
  33. data/spec/asset_collection_spec.rb +15 -15
  34. data/spec/asset_spec.rb +47 -47
  35. data/spec/content_type_spec.rb +80 -80
  36. data/spec/contentstack_spec.rb +69 -39
  37. data/spec/entry_collection_spec.rb +41 -41
  38. data/spec/entry_spec.rb +116 -101
  39. data/spec/fixtures/asset.json +1 -1
  40. data/spec/fixtures/asset_collection.json +1 -1
  41. data/spec/fixtures/category_content_type.json +1 -1
  42. data/spec/fixtures/category_entry.json +1 -1
  43. data/spec/fixtures/category_entry_collection.json +1 -1
  44. data/spec/fixtures/category_entry_collection_without_count.json +1 -1
  45. data/spec/fixtures/content_types.json +1 -1
  46. data/spec/fixtures/product_entry.json +1 -1
  47. data/spec/fixtures/product_entry_collection.json +1 -1
  48. data/spec/fixtures/sync_init.json +2974 -2974
  49. data/spec/query_spec.rb +210 -205
  50. data/spec/spec_helper.rb +180 -180
  51. data/spec/sync_spec.rb +26 -26
  52. metadata +7 -8
  53. data/.github/workflows/sast-scan.yml +0 -11
  54. data/.github/workflows/secrets-scan.yml +0 -11
@@ -1,654 +1,666 @@
1
- require 'contentstack/entry_collection'
2
- require 'util'
3
-
4
- module Contentstack
5
- # A class that defines a query that is used to query for Entry instance.
6
- class Query
7
- using Utility
8
- # @!attribute [r] query
9
- # Attribute which has all the information about the query which will be executed against Contentstack API
10
-
11
- # @!attribute [r] content_type
12
- # Denotes which `content_type` should the query be executed for
13
-
14
- attr_reader :query, :content_type
15
-
16
- # Initialize the Query instance
17
- # @param [String] content_type
18
- #
19
- # Example:
20
- # @query = @stack.content_type('blog').query
21
- # @entries = @query.where('author', 'John Doe').fetch
22
- #
23
- # @return [Contentstack::Query]
24
- def initialize(content_type)
25
- @content_type = content_type
26
- @query = {
27
- query: "{}",
28
- include_count: false,
29
- skip: 0,
30
- count: 10,
31
- desc: 'created_at'
32
- }
33
- end
34
-
35
- # Add a custom query against specified key.
36
- # @param [String] field_uid
37
- # @param [String/Number/Boolean/Hash] value
38
- #
39
- # Example:
40
- # @query = @stack.content_type('blog').query
41
- # @query.add_query('author', "Jane Doe")
42
- #
43
- # @return [Contentstack::Query]
44
- def add_query(field_uid, value)
45
- add_query_hash({:"#{field_uid}" => value})
46
- end
47
-
48
- # Remove provided query key from custom query if exist.
49
- # @param [String] field_uid
50
- #
51
- # Example:
52
- # @query = @stack.content_type('blog').query
53
- # @query.remove_query('author')
54
- #
55
- # @return [Contentstack::Query]
56
- def remove_query(field_uid)
57
- q = ActiveSupport::JSON.decode(@query[:query])
58
- q.delete(field_uid)
59
- @query[:query] = ActiveSupport::JSON.encode(q)
60
- self
61
- end
62
-
63
- # Add a constraint to fetch all entries that contains given value against specified key.
64
- # @param [Hash] query_hash
65
- #
66
- # Example:
67
- # @query = @stack.content_type('blog').query
68
- # @query.where({:author => "Jane Doe"})
69
- #
70
- # @return [Contentstack::Query]
71
- def where(query_hash)
72
- add_query_hash(query_hash)
73
- end
74
-
75
- # Add a regular expression constraint for finding string values that match the provided regular expression. This may be slow for large data sets.
76
- # @param [String] field_uid The key to be constrained.
77
- # @param [String] pattern The regular expression pattern to match.
78
- # @param [String] options Regex options
79
- #
80
- # Example:
81
- # @query = @stack.content_type('product').query
82
- # @query.regex('title', '.*Mobile.*', 'i') # Search without case sensitivity
83
- #
84
- # @return [Contentstack::Query]
85
- def regex(field_uid, pattern, options="")
86
- hash = {
87
- "#{field_uid}" => {
88
- "$regex": pattern
89
- }
90
- }
91
-
92
- hash["#{field_uid}"]["$options"] = options if !options.empty? || !options.nil?
93
-
94
- add_query_hash(hash)
95
- end
96
-
97
- # Add a constraint that requires, a specified key exists in response.
98
- # @param [String] field_uid The key to be constrained.
99
- #
100
- # Example:
101
- # @query = @stack.content_type('product').query
102
- # @query.exists?('product_image') # only fetch products which have a `product_image`
103
- #
104
- # @return [Contentstack::Query]
105
- def exists?(field_uid)
106
- add_query_hash({:"#{field_uid}" => {"$exists" => true}})
107
- end
108
-
109
- # Add a constraint that requires, a specified key does not exists in response.
110
- # @param [String] field_uid The key to be constrained.
111
- #
112
- # Example:
113
- # @query = @stack.content_type('product').query
114
- # @query.not_exists?('product_image') # only fetch products which do not have a `product_image`
115
- #
116
- # @return [Contentstack::Query]
117
- def not_exists?(field_uid)
118
- add_query_hash({:"#{field_uid}" => {"$exists" => false}})
119
- self
120
- end
121
-
122
- # Combines all the queries together using AND operator.
123
- #
124
- # @param [Array] queries Array of instances of the Query class
125
- #
126
- # Each query should be an instance of the Contentstack::Query class, and belong to the same `content_type`
127
- # Example:
128
- # @query1 = @stack.content_type('category').query
129
- # @query1.where('title', 'Electronics')
130
- #
131
- # @query2 = @stack.content_type('category').query
132
- # @query2.regex('description', '.*Electronics.*')
133
- #
134
- # query_array = [@query1, @query2]
135
- #
136
- # @query = @stack.content_type('category').query
137
- # @query.and(query_array)
138
- #
139
- # @return [Contentstack::Query]
140
- def and(queries)
141
- add_query_hash({"$and" => concat_queries(queries)})
142
- self
143
- end
144
-
145
- # Combines all the queries together using OR operator.
146
- #
147
- # @param [Array] queries Array of instances of the Query class
148
- #
149
- # Each query should be an instance of the Contentstack::Query class, and belong to the same `content_type`
150
- # Example:
151
- # @query1 = @stack.content_type('category').query
152
- # @query1.where('title', 'Electronics')
153
- #
154
- # @query2 = @stack.content_type('category').query
155
- # @query2.where('title', 'Apparel')
156
- #
157
- # query_array = [@query1, @query2]
158
- #
159
- # @query = @stack.content_type('category').query
160
- # @query.or(query_array)
161
- #
162
- # @return [Contentstack::Query]
163
- def or(queries)
164
- add_query_hash({"$or" => concat_queries(queries)})
165
- self
166
- end
167
-
168
- # Add a constraint to the query that requires a particular key entry to be less than the provided value.
169
- #
170
- # @param [String] field_uid UID of the field for which query should be executed
171
- #
172
- # @param [String/Number] value Value that provides an upper bound
173
- #
174
- # Example
175
- # @query = @stack.content_type('product').query
176
- # @query.less_than('price', '100')
177
- #
178
- # @return [Contentstack::Query]
179
- def less_than(field_uid, value)
180
- add_query_hash({:"#{field_uid}" => {"$lt" => value}})
181
- self
182
- end
183
-
184
- # Add a constraint to the query that requires a particular key entry to be less than or equal to the provided value.
185
- #
186
- # @param [String] field_uid UID of the field for which query should be executed
187
- #
188
- # @param [String/Number] value Value that provides an upper bound
189
- #
190
- # Example
191
- # @query = @stack.content_type('product').query
192
- # @query.less_than_or_equal('price', '100')
193
- #
194
- # @return [Contentstack::Query]
195
- def less_than_or_equal(field_uid, value)
196
- add_query_hash({:"#{field_uid}" => {"$lte" => value}})
197
- self
198
- end
199
-
200
- # Add a constraint to the query that requires a particular key entry to be greater than the provided value.
201
- #
202
- # @param [String] field_uid UID of the field for which query should be executed
203
- #
204
- # @param [String/Number] value Value that provides a lower bound
205
- #
206
- # Example
207
- # @query = @stack.content_type('product').query
208
- # @query.greater_than('price', '100')
209
- #
210
- # @return [Contentstack::Query]
211
- def greater_than(field_uid, value)
212
- add_query_hash({:"#{field_uid}" => {"$gt" => value}})
213
- self
214
- end
215
-
216
- # Add a constraint to the query that requires a particular key entry to be greater than or equal to the provided value.
217
- #
218
- # @param [String] field_uid UID of the field for which query should be executed
219
- #
220
- # @param [String/Number] value Value that provides a lower bound
221
- #
222
- # Example
223
- # @query = @stack.content_type('product').query
224
- # @query.greater_than_or_equal('price', '100')
225
- #
226
- # @return [Contentstack::Query]
227
- def greater_than_or_equal(field_uid, value)
228
- add_query_hash({:"#{field_uid}" => {"$gte" => value}})
229
- self
230
- end
231
-
232
- # Add a constraint to the query that requires a particular key's entry to be not equal to the provided value.
233
- #
234
- # @param [String] field_uid UID of the field for which query should be executed
235
- # @param [String] value The object that must not be equaled.
236
- #
237
- # Example
238
- # @query = @stack.content_type('product').query
239
- # @query.not_equal_to('price', '100')
240
- #
241
- # @return [Contentstack::Query]
242
- def not_equal_to(field_uid, value)
243
- add_query_hash({:"#{field_uid}" => {"$ne" => value}})
244
- self
245
- end
246
-
247
- # Add a constraint to the query that requires a particular key's entry to be contained in the provided array.
248
- #
249
- # @param [String] field_uid UID of the field for which query should be executed
250
- # @param [String] values The possible values for the key's object
251
- #
252
- # Example 1 - Array Equals Operator Within Group
253
- # @query = @stack.content_type('category').query
254
- # @query.contained_in("title", ["Electronics", "Apparel"])
255
- #
256
- # Example 2 - Array Equals Operator Within Modular Blocks
257
- # @query = @stack.content_type('category').query
258
- # @query.contained_in("additional_info.deals.deal_name", ["Christmas Deal", "Summer Deal"])
259
- #
260
- # @return [Contentstack::Query]
261
- def contained_in(field_uid, values)
262
- add_query_hash({:"#{field_uid}" => {"$in" => values}})
263
- self
264
- end
265
-
266
- # Add a constraint to the query that requires a particular key entry's value not be contained in the provided array.
267
- #
268
- # @param [String] field_uid UID of the field for which query should be executed
269
- # @param [String] values The possible values for the key's object
270
- #
271
- # Example 1 - Array Not-equals Operator Within Group
272
- # @query = @stack.content_type('category').query
273
- # @query.not_contained_in("title", ["Electronics", "Apparel"])
274
- #
275
- # Example 2 - Array Not-equals Operator Within Modular Blocks
276
- # @query = @stack.content_type('category').query
277
- # @query.not_contained_in("additional_info.deals.deal_name", ["Christmas Deal", "Summer Deal"])
278
- #
279
- # @return [Contentstack::Query]
280
- def not_contained_in(field_uid, values)
281
- add_query_hash({:"#{field_uid}" => {"$nin" => values}})
282
- self
283
- end
284
-
285
- # The number of objects to skip before returning any.
286
- #
287
- # @param [Number] count of objects to skip from resulset.
288
- #
289
- # Example
290
- # @query = @stack.content_type('category').query
291
- # @query.skip(50)
292
- #
293
- # @return [Contentstack::Query]
294
- def skip(count)
295
- @query[:skip] = count
296
- self
297
- end
298
-
299
- # This method provides only the entries matching the specified value.
300
- # @deprecated since version 0.5.0
301
- # @param [String] text value used to match or compare
302
- #
303
- # Example
304
- # @query = @stack.content_type('product').query
305
- # @query.search("This is an awesome product")
306
- #
307
- # @return [Contentstack::Query]
308
- def search(text)
309
- @query[:typeahead] = text
310
- self
311
- end
312
-
313
- # A limit on the number of objects to return.
314
- #
315
- # @param [Number] count of objects to limit in resulset.
316
- #
317
- # Example
318
- # @query = @stack.content_type('category').query
319
- # @query.limit(50)
320
- #
321
- # @return [Contentstack::Query]
322
- def limit(count=10)
323
- @query[:limit] = count
324
- self
325
- end
326
-
327
- # Retrieve only count of entries in result.
328
- #
329
- # Example
330
- # @query = @stack.content_type('category').query
331
- # @query.count
332
- #
333
- # @return [Integer]
334
- def count
335
- include_count
336
- fetch.count
337
- end
338
-
339
- # Retrieve count and data of objects in result.
340
- #
341
- # Example
342
- # @query = @stack.content_type('category').query
343
- # @query.include_count
344
- #
345
- # @return [Contentstack::Query]
346
- def include_count(flag=true)
347
- @query[:include_count] = flag
348
- self
349
- end
350
-
351
- # Sort the results in ascending order with the given key.
352
- # Sort the returned entries in ascending order of the provided key.
353
- #
354
- # @param [String] field_uid The key to order by
355
- #
356
- # Example
357
- # @query = @stack.content_type('category').query
358
- # @query.ascending
359
- #
360
- # @return [Contentstack::Query]
361
- def ascending(field_uid)
362
- @query.delete(:desc)
363
- @query[:asc] = field_uid
364
- self
365
- end
366
-
367
- # Sort the results in descending order with the given key.
368
- # Sort the returned entries in descending order of the provided key.
369
- #
370
- # @param [String] field_uid The key to order by
371
- #
372
- # Example
373
- # @query = @stack.content_type('category').query
374
- # @query.descending
375
- #
376
- # @return [Contentstack::Query]
377
- def descending(field_uid)
378
- @query.delete(:asc)
379
- @query[:desc] = field_uid
380
- self
381
- end
382
-
383
- # Get entries from the specified locale.
384
- #
385
- # @param [String] code The locale code of the entry
386
- #
387
- # Example
388
- # Change language method
389
- # @query = @stack.content_type('category').query
390
- # @query.locale('en-us')
391
- #
392
- # @return [Contentstack::Query]
393
- def locale(code)
394
- @query[:locale] = code
395
- self
396
- end
397
-
398
- # Specifies an array of 'only' keys in BASE object that would be 'included' in the response.
399
- #
400
- # @param [Array] fields Array of the 'only' reference keys to be included in response.
401
- # @param [Array] fields_with_base Can be used to denote 'only' fields of the reference class
402
- #
403
- # Example
404
- # # Include only title and description field in response
405
- # @query = @stack.content_type('category').query
406
- # @query.only(['title', 'description'])
407
- #
408
- # # Query product and include only the title and description from category reference
409
- # @query = @stack.content_type('product').query
410
- # @query.include_reference('category')
411
- # .only('category', ['title', 'description'])
412
- #
413
- # @return [Contentstack::Query]
414
- def only(fields, fields_with_base=nil)
415
- q = {}
416
- if [Array, String].include?(fields_with_base.class)
417
- fields_with_base = [fields_with_base] if fields_with_base.class == String
418
- q[fields.to_sym] = fields_with_base
419
- else
420
- fields = [fields] if fields.class == String
421
- q = {BASE: fields}
422
- end
423
-
424
- @query[:only] = q
425
- self
426
- end
427
-
428
- # Specifies list of field uids that would be 'excluded' from the response.
429
- #
430
- # @param [Array] fields Array of field uid which get 'excluded' from the response.
431
- # @param [Array] fields_with_base Can be used to denote 'except' fields of the reference class
432
- #
433
- # Example
434
- # # Exclude 'description' field in response
435
- # @query = @stack.content_type('category').query
436
- # @query.except(['description'])
437
- #
438
- # # Query product and exclude the 'description' from category reference
439
- # @query = @stack.content_type('product').query
440
- # @query.include_reference('category')
441
- # .except('category', ['description'])
442
- #
443
- # @return [Contentstack::Query]
444
- def except(fields, fields_with_base=nil)
445
- q = {}
446
- if [Array, String].include?(fields_with_base.class)
447
- fields_with_base = [fields_with_base] if fields_with_base.class == String
448
- q[fields.to_sym] = fields_with_base
449
- else
450
- fields = [fields] if fields.class == String
451
- q = {BASE: fields}
452
- end
453
-
454
- @query[:except] = q
455
- self
456
- end
457
-
458
- # Add a constraint that requires a particular reference key details.
459
- #
460
- # @param [String/Array] reference_field_uids Pass string or array of reference fields that must be included in the response
461
- #
462
- # Example
463
- #
464
- # # Include reference of 'category'
465
- # @query = @stack.content_type('product').query
466
- # @query.include_reference('category')
467
- #
468
- # # Include reference of 'category' and 'reviews'
469
- # @query = @stack.content_type('product').query
470
- # @query.include_reference(['category', 'reviews'])
471
- #
472
- # @return [Contentstack::Query]
473
- def include_reference(reference_field_uids)
474
- self.include(reference_field_uids)
475
- end
476
-
477
- # Include schemas of all returned objects along with objects themselves.
478
- #
479
- # Example
480
- #
481
- # @query = @stack.content_type('product').query
482
- # @query.include_schema
483
- #
484
- # @return [Contentstack::Query]
485
- def include_schema(flag=true)
486
- @query[:include_schema] = flag
487
- self
488
- end
489
-
490
- # Include object owner's profile in the objects data.
491
- #
492
- # Example
493
- #
494
- # @query = @stack.content_type('product').query
495
- # @query.include_owner
496
- #
497
- # @return [Contentstack::Query]
498
- def include_owner(flag=true)
499
- @query[:include_owner] = flag
500
- self
501
- end
502
-
503
- # Include object's content_type in response
504
- #
505
- # Example
506
- #
507
- # @query = @stack.content_type('product').query
508
- # @query.include_content_type
509
- #
510
- # @return [Contentstack::Query]
511
- def include_content_type(flag=true)
512
- @query[:include_content_type] = flag
513
- self
514
- end
515
-
516
-
517
- # Include the fallback locale publish content, if specified locale content is not publish.
518
- #
519
- # Example
520
- #
521
- # @query = @stack.content_type('product').query
522
- # @query.include_fallback
523
- #
524
- # @return [Contentstack::Query]
525
- def include_fallback(flag=true)
526
- @query[:include_fallback] = flag
527
- self
528
- end
529
-
530
- # Include the branch for publish content.
531
- #
532
- # Example
533
- #
534
- # @query = @stack.content_type('product').query
535
- # @query.include_branch
536
- #
537
- # @return [Contentstack::Entry]
538
- def include_branch(flag=true)
539
- @query[:include_branch] = flag
540
- self
541
- end
542
-
543
- # Include Embedded Objects (Entries and Assets) along with entry/entries details.
544
- #
545
- # Example
546
- #
547
- # @query = @stack.content_type('product').query
548
- # @query.include_embedded_items
549
- #
550
- # @return [Contentstack::Query]
551
- def include_embedded_items()
552
- @query[:include_embedded_items] = ['BASE']
553
- self
554
- end
555
-
556
- # Include objects in 'Draft' mode in response
557
- #
558
- # Example
559
- #
560
- # @query = @stack.content_type('product').query
561
- # @query.include_draft
562
- #
563
- # @return [Contentstack::Query]
564
- def include_draft(flag=true)
565
- @query[:include_draft] = flag
566
- self
567
- end
568
-
569
-
570
- #
571
- # @return [Contentstack::Query]
572
- def include(field_uids)
573
- field_uids = [field_uids] if field_uids.class == String
574
- @query[:include] ||= []
575
- @query[:include] = @query[:include] | field_uids
576
- self
577
- end
578
-
579
- # Include tags with which to search entries.
580
- #
581
- # @param [Array] tags_array Array of tags using which search must be performed
582
- #
583
- # Example
584
- #
585
- # @query = @stack.content_type('product').query
586
- # @query.tags(["tag1", "tag2"])
587
- #
588
- # @return [Contentstack::Query]
589
- def tags(tags_array)
590
- @query[:tags] = tags_array
591
- self
592
- end
593
-
594
-
595
- # Execute query
596
- #
597
- # Example
598
- #
599
- # @query = @stack.content_type('product').query
600
- # @query.tags(["tag1", "tag2"])
601
- # .fetch
602
- #
603
- # @return [Contentstack::EntryCollection]
604
- def fetch
605
- entries = API.fetch_entries(@content_type, @query)
606
- EntryCollection.new(entries, @content_type)
607
- end
608
-
609
- # Execute a Query and get the single matching object
610
- #
611
- # Example
612
- #
613
- # @query = @stack.content_type('product').query
614
- # @query.tags(["tag1", "tag2"])
615
- # .find_one
616
- #
617
- # @return [Contentstack::Entry]
618
- def find_one
619
- limit 1
620
- fetch.first
621
- end
622
-
623
- alias_method :find, :fetch
624
- alias_method :in, :contained_in
625
- alias_method :not_in, :not_contained_in
626
-
627
- private
628
- def add_query_hash(query_hash)
629
- q = ActiveSupport::JSON.decode(@query[:query])
630
- q.merge!(query_hash)
631
- @query[:query] = ActiveSupport::JSON.encode(q)
632
- self
633
- end
634
-
635
- def concat_queries(queries)
636
- this_queries = []
637
- this_query = ActiveSupport::JSON.decode(@query[:query])
638
- if this_query.keys.length > 0
639
- this_queries = [this_query]
640
- end
641
-
642
- if queries.class == Array
643
- queries.map do |query_object|
644
- if query_object.class == Contentstack::Query && query_object.content_type == @content_type
645
- q = ActiveSupport::JSON.decode(query_object.query[:query])
646
- this_queries.push(q.symbolize_keys)
647
- end
648
- end
649
- end
650
-
651
- this_queries
652
- end
653
- end
1
+ require 'contentstack/entry_collection'
2
+ require 'util'
3
+
4
+ module Contentstack
5
+ # A class that defines a query that is used to query for Entry instance.
6
+ class Query
7
+ using Utility
8
+ # @!attribute [r] query
9
+ # Attribute which has all the information about the query which will be executed against Contentstack API
10
+
11
+ # @!attribute [r] content_type
12
+ # Denotes which `content_type` should the query be executed for
13
+
14
+ attr_reader :query, :content_type
15
+
16
+ # Initialize the Query instance
17
+ # @param [String] content_type
18
+ #
19
+ # Example:
20
+ # @query = @stack.content_type('blog').query
21
+ # @entries = @query.where('author', 'John Doe').fetch
22
+ #
23
+ # @return [Contentstack::Query]
24
+ def initialize(content_type)
25
+ @content_type = content_type
26
+ @query = {
27
+ query: "{}",
28
+ include_count: false,
29
+ skip: 0,
30
+ count: 10,
31
+ desc: 'created_at'
32
+ }
33
+ end
34
+
35
+ # Add a custom query against specified key.
36
+ # @param [String] field_uid
37
+ # @param [String/Number/Boolean/Hash] value
38
+ #
39
+ # Example:
40
+ # @query = @stack.content_type('blog').query
41
+ # @query.add_query('author', "Jane Doe")
42
+ #
43
+ # @return [Contentstack::Query]
44
+ def add_query(field_uid, value)
45
+ add_query_hash({:"#{field_uid}" => value})
46
+ end
47
+
48
+ # Remove provided query key from custom query if exist.
49
+ # @param [String] field_uid
50
+ #
51
+ # Example:
52
+ # @query = @stack.content_type('blog').query
53
+ # @query.remove_query('author')
54
+ #
55
+ # @return [Contentstack::Query]
56
+ def remove_query(field_uid)
57
+ q = ActiveSupport::JSON.decode(@query[:query])
58
+ q.delete(field_uid)
59
+ @query[:query] = ActiveSupport::JSON.encode(q)
60
+ self
61
+ end
62
+
63
+ # Add a constraint to fetch all entries that contains given value against specified key.
64
+ # @param [Hash] query_hash
65
+ #
66
+ # Example:
67
+ # @query = @stack.content_type('blog').query
68
+ # @query.where({:author => "Jane Doe"})
69
+ #
70
+ # @return [Contentstack::Query]
71
+ def where(query_hash)
72
+ add_query_hash(query_hash)
73
+ end
74
+
75
+ # Add a regular expression constraint for finding string values that match the provided regular expression. This may be slow for large data sets.
76
+ # @param [String] field_uid The key to be constrained.
77
+ # @param [String] pattern The regular expression pattern to match.
78
+ # @param [String] options Regex options
79
+ #
80
+ # Example:
81
+ # @query = @stack.content_type('product').query
82
+ # @query.regex('title', '.*Mobile.*', 'i') # Search without case sensitivity
83
+ #
84
+ # @return [Contentstack::Query]
85
+ def regex(field_uid, pattern, options="")
86
+ hash = {
87
+ "#{field_uid}" => {
88
+ "$regex": pattern
89
+ }
90
+ }
91
+
92
+ hash["#{field_uid}"]["$options"] = options if !options.empty? || !options.nil?
93
+
94
+ add_query_hash(hash)
95
+ end
96
+
97
+ # Add a constraint that requires, a specified key exists in response.
98
+ # @param [String] field_uid The key to be constrained.
99
+ #
100
+ # Example:
101
+ # @query = @stack.content_type('product').query
102
+ # @query.exists?('product_image') # only fetch products which have a `product_image`
103
+ #
104
+ # @return [Contentstack::Query]
105
+ def exists?(field_uid)
106
+ add_query_hash({:"#{field_uid}" => {"$exists" => true}})
107
+ end
108
+
109
+ # Add a constraint that requires, a specified key does not exists in response.
110
+ # @param [String] field_uid The key to be constrained.
111
+ #
112
+ # Example:
113
+ # @query = @stack.content_type('product').query
114
+ # @query.not_exists?('product_image') # only fetch products which do not have a `product_image`
115
+ #
116
+ # @return [Contentstack::Query]
117
+ def not_exists?(field_uid)
118
+ add_query_hash({:"#{field_uid}" => {"$exists" => false}})
119
+ self
120
+ end
121
+
122
+ # Combines all the queries together using AND operator.
123
+ #
124
+ # @param [Array] queries Array of instances of the Query class
125
+ #
126
+ # Each query should be an instance of the Contentstack::Query class, and belong to the same `content_type`
127
+ # Example:
128
+ # @query1 = @stack.content_type('category').query
129
+ # @query1.where('title', 'Electronics')
130
+ #
131
+ # @query2 = @stack.content_type('category').query
132
+ # @query2.regex('description', '.*Electronics.*')
133
+ #
134
+ # query_array = [@query1, @query2]
135
+ #
136
+ # @query = @stack.content_type('category').query
137
+ # @query.and(query_array)
138
+ #
139
+ # @return [Contentstack::Query]
140
+ def and(queries)
141
+ add_query_hash({"$and" => concat_queries(queries)})
142
+ self
143
+ end
144
+
145
+ # Combines all the queries together using OR operator.
146
+ #
147
+ # @param [Array] queries Array of instances of the Query class
148
+ #
149
+ # Each query should be an instance of the Contentstack::Query class, and belong to the same `content_type`
150
+ # Example:
151
+ # @query1 = @stack.content_type('category').query
152
+ # @query1.where('title', 'Electronics')
153
+ #
154
+ # @query2 = @stack.content_type('category').query
155
+ # @query2.where('title', 'Apparel')
156
+ #
157
+ # query_array = [@query1, @query2]
158
+ #
159
+ # @query = @stack.content_type('category').query
160
+ # @query.or(query_array)
161
+ #
162
+ # @return [Contentstack::Query]
163
+ def or(queries)
164
+ add_query_hash({"$or" => concat_queries(queries)})
165
+ self
166
+ end
167
+
168
+ # Add a constraint to the query that requires a particular key entry to be less than the provided value.
169
+ #
170
+ # @param [String] field_uid UID of the field for which query should be executed
171
+ #
172
+ # @param [String/Number] value Value that provides an upper bound
173
+ #
174
+ # Example
175
+ # @query = @stack.content_type('product').query
176
+ # @query.less_than('price', '100')
177
+ #
178
+ # @return [Contentstack::Query]
179
+ def less_than(field_uid, value)
180
+ add_query_hash({:"#{field_uid}" => {"$lt" => value}})
181
+ self
182
+ end
183
+
184
+ # Add a constraint to the query that requires a particular key entry to be less than or equal to the provided value.
185
+ #
186
+ # @param [String] field_uid UID of the field for which query should be executed
187
+ #
188
+ # @param [String/Number] value Value that provides an upper bound
189
+ #
190
+ # Example
191
+ # @query = @stack.content_type('product').query
192
+ # @query.less_than_or_equal('price', '100')
193
+ #
194
+ # @return [Contentstack::Query]
195
+ def less_than_or_equal(field_uid, value)
196
+ add_query_hash({:"#{field_uid}" => {"$lte" => value}})
197
+ self
198
+ end
199
+
200
+ # Add a constraint to the query that requires a particular key entry to be greater than the provided value.
201
+ #
202
+ # @param [String] field_uid UID of the field for which query should be executed
203
+ #
204
+ # @param [String/Number] value Value that provides a lower bound
205
+ #
206
+ # Example
207
+ # @query = @stack.content_type('product').query
208
+ # @query.greater_than('price', '100')
209
+ #
210
+ # @return [Contentstack::Query]
211
+ def greater_than(field_uid, value)
212
+ add_query_hash({:"#{field_uid}" => {"$gt" => value}})
213
+ self
214
+ end
215
+
216
+ # Add a constraint to the query that requires a particular key entry to be greater than or equal to the provided value.
217
+ #
218
+ # @param [String] field_uid UID of the field for which query should be executed
219
+ #
220
+ # @param [String/Number] value Value that provides a lower bound
221
+ #
222
+ # Example
223
+ # @query = @stack.content_type('product').query
224
+ # @query.greater_than_or_equal('price', '100')
225
+ #
226
+ # @return [Contentstack::Query]
227
+ def greater_than_or_equal(field_uid, value)
228
+ add_query_hash({:"#{field_uid}" => {"$gte" => value}})
229
+ self
230
+ end
231
+
232
+ # Add a constraint to the query that requires a particular key's entry to be not equal to the provided value.
233
+ #
234
+ # @param [String] field_uid UID of the field for which query should be executed
235
+ # @param [String] value The object that must not be equaled.
236
+ #
237
+ # Example
238
+ # @query = @stack.content_type('product').query
239
+ # @query.not_equal_to('price', '100')
240
+ #
241
+ # @return [Contentstack::Query]
242
+ def not_equal_to(field_uid, value)
243
+ add_query_hash({:"#{field_uid}" => {"$ne" => value}})
244
+ self
245
+ end
246
+
247
+ # Add a constraint to the query that requires a particular key's entry to be contained in the provided array.
248
+ #
249
+ # @param [String] field_uid UID of the field for which query should be executed
250
+ # @param [String] values The possible values for the key's object
251
+ #
252
+ # Example 1 - Array Equals Operator Within Group
253
+ # @query = @stack.content_type('category').query
254
+ # @query.contained_in("title", ["Electronics", "Apparel"])
255
+ #
256
+ # Example 2 - Array Equals Operator Within Modular Blocks
257
+ # @query = @stack.content_type('category').query
258
+ # @query.contained_in("additional_info.deals.deal_name", ["Christmas Deal", "Summer Deal"])
259
+ #
260
+ # @return [Contentstack::Query]
261
+ def contained_in(field_uid, values)
262
+ add_query_hash({:"#{field_uid}" => {"$in" => values}})
263
+ self
264
+ end
265
+
266
+ # Add a constraint to the query that requires a particular key entry's value not be contained in the provided array.
267
+ #
268
+ # @param [String] field_uid UID of the field for which query should be executed
269
+ # @param [String] values The possible values for the key's object
270
+ #
271
+ # Example 1 - Array Not-equals Operator Within Group
272
+ # @query = @stack.content_type('category').query
273
+ # @query.not_contained_in("title", ["Electronics", "Apparel"])
274
+ #
275
+ # Example 2 - Array Not-equals Operator Within Modular Blocks
276
+ # @query = @stack.content_type('category').query
277
+ # @query.not_contained_in("additional_info.deals.deal_name", ["Christmas Deal", "Summer Deal"])
278
+ #
279
+ # @return [Contentstack::Query]
280
+ def not_contained_in(field_uid, values)
281
+ add_query_hash({:"#{field_uid}" => {"$nin" => values}})
282
+ self
283
+ end
284
+
285
+ # The number of objects to skip before returning any.
286
+ #
287
+ # @param [Number] count of objects to skip from resulset.
288
+ #
289
+ # Example
290
+ # @query = @stack.content_type('category').query
291
+ # @query.skip(50)
292
+ #
293
+ # @return [Contentstack::Query]
294
+ def skip(count)
295
+ @query[:skip] = count
296
+ self
297
+ end
298
+
299
+ # This method provides only the entries matching the specified value.
300
+ # @deprecated since version 0.5.0
301
+ # @param [String] text value used to match or compare
302
+ #
303
+ # Example
304
+ # @query = @stack.content_type('product').query
305
+ # @query.search("This is an awesome product")
306
+ #
307
+ # @return [Contentstack::Query]
308
+ def search(text)
309
+ @query[:typeahead] = text
310
+ self
311
+ end
312
+
313
+ # A limit on the number of objects to return.
314
+ #
315
+ # @param [Number] count of objects to limit in resulset.
316
+ #
317
+ # Example
318
+ # @query = @stack.content_type('category').query
319
+ # @query.limit(50)
320
+ #
321
+ # @return [Contentstack::Query]
322
+ def limit(count=10)
323
+ @query[:limit] = count
324
+ self
325
+ end
326
+
327
+ # Retrieve only count of entries in result.
328
+ #
329
+ # Example
330
+ # @query = @stack.content_type('category').query
331
+ # @query.count
332
+ #
333
+ # @return [Integer]
334
+ def count
335
+ include_count
336
+ fetch.count
337
+ end
338
+
339
+ # Retrieve count and data of objects in result.
340
+ #
341
+ # Example
342
+ # @query = @stack.content_type('category').query
343
+ # @query.include_count
344
+ #
345
+ # @return [Contentstack::Query]
346
+ def include_count(flag=true)
347
+ @query[:include_count] = flag
348
+ self
349
+ end
350
+
351
+ # Retrieve count and data of objects in result.
352
+ #
353
+ # Example
354
+ # @query = @stack.content_type('category').query
355
+ # @query.include_metadata
356
+ #
357
+ # @return [Contentstack::Query]
358
+ def include_metadata(flag=true)
359
+ @query[:include_metadata] = flag
360
+ self
361
+ end
362
+
363
+ # Sort the results in ascending order with the given key.
364
+ # Sort the returned entries in ascending order of the provided key.
365
+ #
366
+ # @param [String] field_uid The key to order by
367
+ #
368
+ # Example
369
+ # @query = @stack.content_type('category').query
370
+ # @query.ascending
371
+ #
372
+ # @return [Contentstack::Query]
373
+ def ascending(field_uid)
374
+ @query.delete(:desc)
375
+ @query[:asc] = field_uid
376
+ self
377
+ end
378
+
379
+ # Sort the results in descending order with the given key.
380
+ # Sort the returned entries in descending order of the provided key.
381
+ #
382
+ # @param [String] field_uid The key to order by
383
+ #
384
+ # Example
385
+ # @query = @stack.content_type('category').query
386
+ # @query.descending
387
+ #
388
+ # @return [Contentstack::Query]
389
+ def descending(field_uid)
390
+ @query.delete(:asc)
391
+ @query[:desc] = field_uid
392
+ self
393
+ end
394
+
395
+ # Get entries from the specified locale.
396
+ #
397
+ # @param [String] code The locale code of the entry
398
+ #
399
+ # Example
400
+ # Change language method
401
+ # @query = @stack.content_type('category').query
402
+ # @query.locale('en-us')
403
+ #
404
+ # @return [Contentstack::Query]
405
+ def locale(code)
406
+ @query[:locale] = code
407
+ self
408
+ end
409
+
410
+ # Specifies an array of 'only' keys in BASE object that would be 'included' in the response.
411
+ #
412
+ # @param [Array] fields Array of the 'only' reference keys to be included in response.
413
+ # @param [Array] fields_with_base Can be used to denote 'only' fields of the reference class
414
+ #
415
+ # Example
416
+ # # Include only title and description field in response
417
+ # @query = @stack.content_type('category').query
418
+ # @query.only(['title', 'description'])
419
+ #
420
+ # # Query product and include only the title and description from category reference
421
+ # @query = @stack.content_type('product').query
422
+ # @query.include_reference('category')
423
+ # .only('category', ['title', 'description'])
424
+ #
425
+ # @return [Contentstack::Query]
426
+ def only(fields, fields_with_base=nil)
427
+ q = {}
428
+ if [Array, String].include?(fields_with_base.class)
429
+ fields_with_base = [fields_with_base] if fields_with_base.class == String
430
+ q[fields.to_sym] = fields_with_base
431
+ else
432
+ fields = [fields] if fields.class == String
433
+ q = {BASE: fields}
434
+ end
435
+
436
+ @query[:only] = q
437
+ self
438
+ end
439
+
440
+ # Specifies list of field uids that would be 'excluded' from the response.
441
+ #
442
+ # @param [Array] fields Array of field uid which get 'excluded' from the response.
443
+ # @param [Array] fields_with_base Can be used to denote 'except' fields of the reference class
444
+ #
445
+ # Example
446
+ # # Exclude 'description' field in response
447
+ # @query = @stack.content_type('category').query
448
+ # @query.except(['description'])
449
+ #
450
+ # # Query product and exclude the 'description' from category reference
451
+ # @query = @stack.content_type('product').query
452
+ # @query.include_reference('category')
453
+ # .except('category', ['description'])
454
+ #
455
+ # @return [Contentstack::Query]
456
+ def except(fields, fields_with_base=nil)
457
+ q = {}
458
+ if [Array, String].include?(fields_with_base.class)
459
+ fields_with_base = [fields_with_base] if fields_with_base.class == String
460
+ q[fields.to_sym] = fields_with_base
461
+ else
462
+ fields = [fields] if fields.class == String
463
+ q = {BASE: fields}
464
+ end
465
+
466
+ @query[:except] = q
467
+ self
468
+ end
469
+
470
+ # Add a constraint that requires a particular reference key details.
471
+ #
472
+ # @param [String/Array] reference_field_uids Pass string or array of reference fields that must be included in the response
473
+ #
474
+ # Example
475
+ #
476
+ # # Include reference of 'category'
477
+ # @query = @stack.content_type('product').query
478
+ # @query.include_reference('category')
479
+ #
480
+ # # Include reference of 'category' and 'reviews'
481
+ # @query = @stack.content_type('product').query
482
+ # @query.include_reference(['category', 'reviews'])
483
+ #
484
+ # @return [Contentstack::Query]
485
+ def include_reference(reference_field_uids)
486
+ self.include(reference_field_uids)
487
+ end
488
+
489
+ # Include schemas of all returned objects along with objects themselves.
490
+ #
491
+ # Example
492
+ #
493
+ # @query = @stack.content_type('product').query
494
+ # @query.include_schema
495
+ #
496
+ # @return [Contentstack::Query]
497
+ def include_schema(flag=true)
498
+ @query[:include_schema] = flag
499
+ self
500
+ end
501
+
502
+ # Include object owner's profile in the objects data.
503
+ #
504
+ # Example
505
+ #
506
+ # @query = @stack.content_type('product').query
507
+ # @query.include_owner
508
+ #
509
+ # @return [Contentstack::Query]
510
+ def include_owner(flag=true)
511
+ @query[:include_owner] = flag
512
+ self
513
+ end
514
+
515
+ # Include object's content_type in response
516
+ #
517
+ # Example
518
+ #
519
+ # @query = @stack.content_type('product').query
520
+ # @query.include_content_type
521
+ #
522
+ # @return [Contentstack::Query]
523
+ def include_content_type(flag=true)
524
+ @query[:include_content_type] = flag
525
+ self
526
+ end
527
+
528
+
529
+ # Include the fallback locale publish content, if specified locale content is not publish.
530
+ #
531
+ # Example
532
+ #
533
+ # @query = @stack.content_type('product').query
534
+ # @query.include_fallback
535
+ #
536
+ # @return [Contentstack::Query]
537
+ def include_fallback(flag=true)
538
+ @query[:include_fallback] = flag
539
+ self
540
+ end
541
+
542
+ # Include the branch for publish content.
543
+ #
544
+ # Example
545
+ #
546
+ # @query = @stack.content_type('product').query
547
+ # @query.include_branch
548
+ #
549
+ # @return [Contentstack::Entry]
550
+ def include_branch(flag=true)
551
+ @query[:include_branch] = flag
552
+ self
553
+ end
554
+
555
+ # Include Embedded Objects (Entries and Assets) along with entry/entries details.
556
+ #
557
+ # Example
558
+ #
559
+ # @query = @stack.content_type('product').query
560
+ # @query.include_embedded_items
561
+ #
562
+ # @return [Contentstack::Query]
563
+ def include_embedded_items()
564
+ @query[:include_embedded_items] = ['BASE']
565
+ self
566
+ end
567
+
568
+ # Include objects in 'Draft' mode in response
569
+ #
570
+ # Example
571
+ #
572
+ # @query = @stack.content_type('product').query
573
+ # @query.include_draft
574
+ #
575
+ # @return [Contentstack::Query]
576
+ def include_draft(flag=true)
577
+ @query[:include_draft] = flag
578
+ self
579
+ end
580
+
581
+
582
+ #
583
+ # @return [Contentstack::Query]
584
+ def include(field_uids)
585
+ field_uids = [field_uids] if field_uids.class == String
586
+ @query[:include] ||= []
587
+ @query[:include] = @query[:include] | field_uids
588
+ self
589
+ end
590
+
591
+ # Include tags with which to search entries.
592
+ #
593
+ # @param [Array] tags_array Array of tags using which search must be performed
594
+ #
595
+ # Example
596
+ #
597
+ # @query = @stack.content_type('product').query
598
+ # @query.tags(["tag1", "tag2"])
599
+ #
600
+ # @return [Contentstack::Query]
601
+ def tags(tags_array)
602
+ @query[:tags] = tags_array
603
+ self
604
+ end
605
+
606
+
607
+ # Execute query
608
+ #
609
+ # Example
610
+ #
611
+ # @query = @stack.content_type('product').query
612
+ # @query.tags(["tag1", "tag2"])
613
+ # .fetch
614
+ #
615
+ # @return [Contentstack::EntryCollection]
616
+ def fetch
617
+ entries = API.fetch_entries(@content_type, @query)
618
+ EntryCollection.new(entries, @content_type)
619
+ end
620
+
621
+ # Execute a Query and get the single matching object
622
+ #
623
+ # Example
624
+ #
625
+ # @query = @stack.content_type('product').query
626
+ # @query.tags(["tag1", "tag2"])
627
+ # .find_one
628
+ #
629
+ # @return [Contentstack::Entry]
630
+ def find_one
631
+ limit 1
632
+ fetch.first
633
+ end
634
+
635
+ alias_method :find, :fetch
636
+ alias_method :in, :contained_in
637
+ alias_method :not_in, :not_contained_in
638
+
639
+ private
640
+ def add_query_hash(query_hash)
641
+ q = ActiveSupport::JSON.decode(@query[:query])
642
+ q.merge!(query_hash)
643
+ @query[:query] = ActiveSupport::JSON.encode(q)
644
+ self
645
+ end
646
+
647
+ def concat_queries(queries)
648
+ this_queries = []
649
+ this_query = ActiveSupport::JSON.decode(@query[:query])
650
+ if this_query.keys.length > 0
651
+ this_queries = [this_query]
652
+ end
653
+
654
+ if queries.class == Array
655
+ queries.map do |query_object|
656
+ if query_object.class == Contentstack::Query && query_object.content_type == @content_type
657
+ q = ActiveSupport::JSON.decode(query_object.query[:query])
658
+ this_queries.push(q.symbolize_keys)
659
+ end
660
+ end
661
+ end
662
+
663
+ this_queries
664
+ end
665
+ end
654
666
  end