monday_ruby 1.1.0 → 1.2.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.env +1 -1
  3. data/.rubocop.yml +2 -1
  4. data/CHANGELOG.md +14 -0
  5. data/CONTRIBUTING.md +104 -0
  6. data/README.md +146 -142
  7. data/docs/.vitepress/config.mjs +255 -0
  8. data/docs/.vitepress/theme/index.js +4 -0
  9. data/docs/.vitepress/theme/style.css +43 -0
  10. data/docs/README.md +80 -0
  11. data/docs/explanation/architecture.md +507 -0
  12. data/docs/explanation/best-practices/errors.md +478 -0
  13. data/docs/explanation/best-practices/performance.md +1084 -0
  14. data/docs/explanation/best-practices/rate-limiting.md +630 -0
  15. data/docs/explanation/best-practices/testing.md +820 -0
  16. data/docs/explanation/column-values.md +857 -0
  17. data/docs/explanation/design.md +795 -0
  18. data/docs/explanation/graphql.md +356 -0
  19. data/docs/explanation/migration/v1.md +808 -0
  20. data/docs/explanation/pagination.md +447 -0
  21. data/docs/guides/advanced/batch.md +1274 -0
  22. data/docs/guides/advanced/complex-queries.md +1114 -0
  23. data/docs/guides/advanced/errors.md +818 -0
  24. data/docs/guides/advanced/pagination.md +934 -0
  25. data/docs/guides/advanced/rate-limiting.md +981 -0
  26. data/docs/guides/authentication.md +286 -0
  27. data/docs/guides/boards/create.md +386 -0
  28. data/docs/guides/boards/delete.md +405 -0
  29. data/docs/guides/boards/duplicate.md +511 -0
  30. data/docs/guides/boards/query.md +530 -0
  31. data/docs/guides/boards/update.md +453 -0
  32. data/docs/guides/columns/create.md +452 -0
  33. data/docs/guides/columns/metadata.md +492 -0
  34. data/docs/guides/columns/query.md +455 -0
  35. data/docs/guides/columns/update-multiple.md +459 -0
  36. data/docs/guides/columns/update-values.md +509 -0
  37. data/docs/guides/files/add-to-column.md +40 -0
  38. data/docs/guides/files/add-to-update.md +37 -0
  39. data/docs/guides/files/clear-column.md +33 -0
  40. data/docs/guides/first-request.md +285 -0
  41. data/docs/guides/folders/manage.md +750 -0
  42. data/docs/guides/groups/items.md +626 -0
  43. data/docs/guides/groups/manage.md +501 -0
  44. data/docs/guides/installation.md +169 -0
  45. data/docs/guides/items/create.md +493 -0
  46. data/docs/guides/items/delete.md +514 -0
  47. data/docs/guides/items/query.md +605 -0
  48. data/docs/guides/items/subitems.md +483 -0
  49. data/docs/guides/items/update.md +699 -0
  50. data/docs/guides/updates/manage.md +619 -0
  51. data/docs/guides/use-cases/dashboard.md +1421 -0
  52. data/docs/guides/use-cases/import.md +1962 -0
  53. data/docs/guides/use-cases/task-management.md +1381 -0
  54. data/docs/guides/workspaces/manage.md +502 -0
  55. data/docs/index.md +69 -0
  56. data/docs/package-lock.json +2468 -0
  57. data/docs/package.json +13 -0
  58. data/docs/reference/client.md +540 -0
  59. data/docs/reference/configuration.md +586 -0
  60. data/docs/reference/errors.md +693 -0
  61. data/docs/reference/resources/account.md +208 -0
  62. data/docs/reference/resources/activity-log.md +369 -0
  63. data/docs/reference/resources/board-view.md +359 -0
  64. data/docs/reference/resources/board.md +393 -0
  65. data/docs/reference/resources/column.md +543 -0
  66. data/docs/reference/resources/file.md +236 -0
  67. data/docs/reference/resources/folder.md +386 -0
  68. data/docs/reference/resources/group.md +507 -0
  69. data/docs/reference/resources/item.md +348 -0
  70. data/docs/reference/resources/subitem.md +267 -0
  71. data/docs/reference/resources/update.md +259 -0
  72. data/docs/reference/resources/workspace.md +213 -0
  73. data/docs/reference/response.md +560 -0
  74. data/docs/tutorial/first-integration.md +713 -0
  75. data/lib/monday/client.rb +24 -0
  76. data/lib/monday/configuration.rb +5 -0
  77. data/lib/monday/request.rb +15 -0
  78. data/lib/monday/resources/base.rb +4 -0
  79. data/lib/monday/resources/file.rb +56 -0
  80. data/lib/monday/util.rb +1 -0
  81. data/lib/monday/version.rb +1 -1
  82. metadata +87 -4
@@ -0,0 +1,1114 @@
1
+ # Complex Queries
2
+
3
+ Build advanced GraphQL queries to retrieve nested data, combine multiple resources, and optimize performance.
4
+
5
+ ## Understanding Field Selection
6
+
7
+ The `select` parameter controls which fields are returned in the response. It supports both arrays and hashes for nested data.
8
+
9
+ ### Array Syntax for Simple Fields
10
+
11
+ ```ruby
12
+ select: ["id", "name", "created_at"]
13
+ ```
14
+
15
+ This retrieves only the specified fields from the resource.
16
+
17
+ ### Hash Syntax for Nested Fields
18
+
19
+ ```ruby
20
+ select: [
21
+ "id",
22
+ "name",
23
+ {
24
+ board: ["id", "name"]
25
+ }
26
+ ]
27
+ ```
28
+
29
+ This retrieves the item's `id` and `name`, plus nested board information.
30
+
31
+ ### How It Works
32
+
33
+ The `Util.format_select` method converts Ruby hashes into GraphQL field selection syntax:
34
+
35
+ - `["id", "name"]` becomes `id name`
36
+ - `{ board: ["id", "name"] }` becomes `board { id name }`
37
+ - `{ column_values: ["id", "text", "type"] }` becomes `column_values { id text type }`
38
+
39
+ ## Nested Field Selection
40
+
41
+ Query items with deeply nested data structures.
42
+
43
+ ### Query Items with Column Values
44
+
45
+ Retrieve items and their column data:
46
+
47
+ ```ruby
48
+ require "monday_ruby"
49
+
50
+ Monday.configure do |config|
51
+ config.token = ENV["MONDAY_TOKEN"]
52
+ end
53
+
54
+ client = Monday::Client.new
55
+
56
+ response = client.item.query(
57
+ args: { ids: [987654321] },
58
+ select: [
59
+ "id",
60
+ "name",
61
+ "created_at",
62
+ {
63
+ column_values: ["id", "text", "type", "value"]
64
+ }
65
+ ]
66
+ )
67
+
68
+ if response.success?
69
+ item = response.body.dig("data", "items", 0)
70
+
71
+ puts "Item: #{item['name']}"
72
+ puts "\nColumn Values:"
73
+
74
+ item["column_values"].each do |col|
75
+ puts " #{col['id']}: #{col['text']} (#{col['type']})"
76
+ end
77
+ end
78
+ ```
79
+
80
+ **Example output:**
81
+ ```
82
+ Item: Q4 Marketing Campaign
83
+ Column Values:
84
+ status: Done (color)
85
+ person: John Doe (people)
86
+ date4: 2024-12-31 (date)
87
+ text: High Priority (text)
88
+ ```
89
+
90
+ ### Query Boards with Groups and Items
91
+
92
+ Retrieve complete board structure:
93
+
94
+ ```ruby
95
+ response = client.board.query(
96
+ args: { ids: [1234567890] },
97
+ select: [
98
+ "id",
99
+ "name",
100
+ "description",
101
+ {
102
+ groups: [
103
+ "id",
104
+ "title",
105
+ "color",
106
+ {
107
+ items_page: {
108
+ items: ["id", "name", "state"]
109
+ }
110
+ }
111
+ ]
112
+ }
113
+ ]
114
+ )
115
+
116
+ if response.success?
117
+ board = response.body.dig("data", "boards", 0)
118
+
119
+ puts "Board: #{board['name']}"
120
+ puts "Description: #{board['description']}"
121
+ puts "\nGroups:"
122
+
123
+ board["groups"].each do |group|
124
+ puts "\n #{group['title']} (#{group['color']})"
125
+
126
+ items = group.dig("items_page", "items") || []
127
+ puts " Items: #{items.length}"
128
+
129
+ items.first(3).each do |item|
130
+ puts " - #{item['name']} [#{item['state']}]"
131
+ end
132
+ end
133
+ end
134
+ ```
135
+
136
+ **Example output:**
137
+ ```
138
+ Board: Marketing Projects
139
+ Description: Track all marketing initiatives
140
+
141
+ Groups:
142
+
143
+ Q1 Planning (blue)
144
+ Items: 5
145
+ - Website Redesign [active]
146
+ - Email Campaign [active]
147
+ - Social Media Strategy [done]
148
+
149
+ Q2 Execution (green)
150
+ Items: 8
151
+ - Content Calendar [active]
152
+ - SEO Optimization [active]
153
+ ```
154
+
155
+ ### Deep Nesting - Items with Subitems and Column Values
156
+
157
+ Retrieve items with their subitems and all column data:
158
+
159
+ ```ruby
160
+ response = client.item.query(
161
+ args: { ids: [987654321] },
162
+ select: [
163
+ "id",
164
+ "name",
165
+ "state",
166
+ {
167
+ board: ["id", "name"],
168
+ group: ["id", "title"],
169
+ column_values: ["id", "text", "type"],
170
+ subitems: [
171
+ "id",
172
+ "name",
173
+ "state",
174
+ {
175
+ column_values: ["id", "text"]
176
+ }
177
+ ]
178
+ }
179
+ ]
180
+ )
181
+
182
+ if response.success?
183
+ item = response.body.dig("data", "items", 0)
184
+ board = item.dig("board")
185
+ group = item.dig("group")
186
+
187
+ puts "Item: #{item['name']}"
188
+ puts "Board: #{board['name']}"
189
+ puts "Group: #{group['title']}"
190
+ puts "State: #{item['state']}"
191
+
192
+ puts "\nColumn Values:"
193
+ item["column_values"].each do |col|
194
+ next if col["text"].nil? || col["text"].empty?
195
+ puts " #{col['id']}: #{col['text']}"
196
+ end
197
+
198
+ puts "\nSubitems:"
199
+ item["subitems"].each do |subitem|
200
+ puts " - #{subitem['name']} [#{subitem['state']}]"
201
+
202
+ subitem["column_values"].each do |col|
203
+ next if col["text"].nil? || col["text"].empty?
204
+ puts " #{col['id']}: #{col['text']}"
205
+ end
206
+ end
207
+ end
208
+ ```
209
+
210
+ ## Query Multiple Resources
211
+
212
+ Combine different resource queries into a single request by making parallel calls or using custom GraphQL queries.
213
+
214
+ ### Query Boards and Workspaces Together
215
+
216
+ Get workspace and board information in one flow:
217
+
218
+ ```ruby
219
+ # First, get workspaces
220
+ workspace_response = client.workspace.query(
221
+ select: [
222
+ "id",
223
+ "name",
224
+ "description"
225
+ ]
226
+ )
227
+
228
+ workspaces = workspace_response.body.dig("data", "workspaces")
229
+
230
+ # Then, get boards for each workspace
231
+ workspaces.each do |workspace|
232
+ board_response = client.board.query(
233
+ args: { workspace_ids: [workspace["id"]] },
234
+ select: [
235
+ "id",
236
+ "name",
237
+ {
238
+ groups: ["id", "title"]
239
+ }
240
+ ]
241
+ )
242
+
243
+ boards = board_response.body.dig("data", "boards")
244
+
245
+ puts "\nWorkspace: #{workspace['name']}"
246
+ puts "Boards: #{boards.length}"
247
+
248
+ boards.each do |board|
249
+ puts " - #{board['name']} (#{board['groups'].length} groups)"
250
+ end
251
+ end
252
+ ```
253
+
254
+ ### Query Items from Multiple Boards
255
+
256
+ Retrieve items from different boards with specific fields:
257
+
258
+ ```ruby
259
+ board_ids = [1234567890, 2345678901, 3456789012]
260
+
261
+ board_ids.each do |board_id|
262
+ response = client.board.items_page(
263
+ board_ids: board_id,
264
+ limit: 50,
265
+ select: [
266
+ "id",
267
+ "name",
268
+ {
269
+ board: ["id", "name"],
270
+ column_values: ["id", "text"]
271
+ }
272
+ ]
273
+ )
274
+
275
+ if response.success?
276
+ board = response.body.dig("data", "boards", 0)
277
+ items = board.dig("items_page", "items")
278
+
279
+ puts "\nBoard: #{board_id}"
280
+ puts "Items: #{items.length}"
281
+
282
+ items.first(5).each do |item|
283
+ puts " - #{item['name']}"
284
+ end
285
+ end
286
+ end
287
+ ```
288
+
289
+ ### Combine Board, Group, and Item Queries
290
+
291
+ Get complete hierarchical data:
292
+
293
+ ```ruby
294
+ # Get board with groups
295
+ board_response = client.board.query(
296
+ args: { ids: [1234567890] },
297
+ select: [
298
+ "id",
299
+ "name",
300
+ {
301
+ workspace: ["id", "name"],
302
+ groups: ["id", "title", "color"],
303
+ columns: ["id", "title", "type"]
304
+ }
305
+ ]
306
+ )
307
+
308
+ board = board_response.body.dig("data", "boards", 0)
309
+ workspace = board.dig("workspace")
310
+
311
+ puts "Workspace: #{workspace['name']}"
312
+ puts "Board: #{board['name']}"
313
+ puts "\nColumns: #{board['columns'].length}"
314
+ board['columns'].each do |col|
315
+ puts " - #{col['title']} (#{col['type']})"
316
+ end
317
+
318
+ puts "\nGroups:"
319
+ board["groups"].each do |group|
320
+ # Get items for each group
321
+ items_response = client.group.items_page(
322
+ board_ids: board["id"],
323
+ group_ids: group["id"],
324
+ limit: 100,
325
+ select: [
326
+ "id",
327
+ "name",
328
+ "state",
329
+ {
330
+ column_values: ["id", "text"]
331
+ }
332
+ ]
333
+ )
334
+
335
+ if items_response.success?
336
+ group_data = items_response.body.dig("data", "boards", 0, "groups", 0)
337
+ items = group_data.dig("items_page", "items") || []
338
+
339
+ puts "\n #{group['title']} (#{group['color']})"
340
+ puts " Items: #{items.length}"
341
+
342
+ items.first(3).each do |item|
343
+ puts " - #{item['name']} [#{item['state']}]"
344
+ end
345
+ end
346
+ end
347
+ ```
348
+
349
+ ## Advanced Filtering with query_params
350
+
351
+ Use complex filtering rules to find specific items.
352
+
353
+ ### Single Rule Filter
354
+
355
+ Filter items by a single column value:
356
+
357
+ ```ruby
358
+ response = client.board.items_page(
359
+ board_ids: 1234567890,
360
+ limit: 100,
361
+ query_params: {
362
+ rules: [
363
+ { column_id: "status", compare_value: [1] }
364
+ ],
365
+ operator: :and
366
+ },
367
+ select: [
368
+ "id",
369
+ "name",
370
+ {
371
+ column_values: ["id", "text"]
372
+ }
373
+ ]
374
+ )
375
+
376
+ if response.success?
377
+ board = response.body.dig("data", "boards", 0)
378
+ items = board.dig("items_page", "items")
379
+
380
+ puts "Items with status index 1: #{items.length}"
381
+ end
382
+ ```
383
+
384
+ ::: tip Column Value Indices
385
+ Status column values use numeric indices (0, 1, 2, etc.) corresponding to their position in the status settings. Query your board's columns to see the available indices.
386
+ :::
387
+
388
+ ### Multiple Rules with AND Logic
389
+
390
+ All rules must match:
391
+
392
+ ```ruby
393
+ response = client.board.items_page(
394
+ board_ids: 1234567890,
395
+ limit: 100,
396
+ query_params: {
397
+ rules: [
398
+ { column_id: "status", compare_value: [1] },
399
+ { column_id: "priority", compare_value: [2] }
400
+ ],
401
+ operator: :and
402
+ }
403
+ )
404
+
405
+ if response.success?
406
+ board = response.body.dig("data", "boards", 0)
407
+ items = board.dig("items_page", "items")
408
+
409
+ puts "High priority items with status=1: #{items.length}"
410
+ end
411
+ ```
412
+
413
+ ### Multiple Rules with OR Logic
414
+
415
+ Any rule can match:
416
+
417
+ ```ruby
418
+ response = client.board.items_page(
419
+ board_ids: 1234567890,
420
+ limit: 100,
421
+ query_params: {
422
+ rules: [
423
+ { column_id: "status", compare_value: [1] },
424
+ { column_id: "status", compare_value: [2] }
425
+ ],
426
+ operator: :or
427
+ },
428
+ select: ["id", "name"]
429
+ )
430
+
431
+ if response.success?
432
+ board = response.body.dig("data", "boards", 0)
433
+ items = board.dig("items_page", "items")
434
+
435
+ puts "Items with status 1 or 2: #{items.length}"
436
+ end
437
+ ```
438
+
439
+ ### Filter by Date Range
440
+
441
+ Filter items by date column:
442
+
443
+ ```ruby
444
+ # Get items due in the next 7 days
445
+ today = Date.today
446
+ next_week = today + 7
447
+
448
+ response = client.board.items_page(
449
+ board_ids: 1234567890,
450
+ limit: 100,
451
+ query_params: {
452
+ rules: [
453
+ {
454
+ column_id: "date4", # Replace with your date column ID
455
+ compare_value: [today.to_s, next_week.to_s]
456
+ }
457
+ ],
458
+ operator: :and
459
+ },
460
+ select: [
461
+ "id",
462
+ "name",
463
+ {
464
+ column_values: ["id", "text"]
465
+ }
466
+ ]
467
+ )
468
+
469
+ if response.success?
470
+ board = response.body.dig("data", "boards", 0)
471
+ items = board.dig("items_page", "items")
472
+
473
+ puts "Items due in next 7 days: #{items.length}"
474
+
475
+ items.each do |item|
476
+ date_col = item["column_values"].find { |cv| cv["id"] == "date4" }
477
+ puts " - #{item['name']}: #{date_col&.dig('text')}"
478
+ end
479
+ end
480
+ ```
481
+
482
+ ### Combine Filtering and Pagination
483
+
484
+ Filter items and paginate through results:
485
+
486
+ ```ruby
487
+ def fetch_filtered_items(client, board_id, query_params)
488
+ all_items = []
489
+ cursor = nil
490
+
491
+ loop do
492
+ response = client.board.items_page(
493
+ board_ids: board_id,
494
+ limit: 100,
495
+ cursor: cursor,
496
+ query_params: cursor.nil? ? query_params : nil, # Only pass on first request
497
+ select: [
498
+ "id",
499
+ "name",
500
+ {
501
+ column_values: ["id", "text"]
502
+ }
503
+ ]
504
+ )
505
+
506
+ break unless response.success?
507
+
508
+ board = response.body.dig("data", "boards", 0)
509
+ items_page = board.dig("items_page")
510
+ items = items_page["items"]
511
+
512
+ break if items.empty?
513
+
514
+ all_items.concat(items)
515
+ cursor = items_page["cursor"]
516
+
517
+ break if cursor.nil?
518
+
519
+ puts "Fetched #{items.length} items, total: #{all_items.length}"
520
+ end
521
+
522
+ all_items
523
+ end
524
+
525
+ # Usage: Get all high-priority items
526
+ items = fetch_filtered_items(
527
+ client,
528
+ 1234567890,
529
+ {
530
+ rules: [{ column_id: "priority", compare_value: [2] }],
531
+ operator: :and
532
+ }
533
+ )
534
+
535
+ puts "\nTotal high-priority items: #{items.length}"
536
+ ```
537
+
538
+ ## Optimize Query Performance
539
+
540
+ Reduce complexity and improve response times.
541
+
542
+ ### Select Only Needed Fields
543
+
544
+ **Bad - Over-fetching:**
545
+ ```ruby
546
+ # Retrieves all default fields plus unnecessary nested data
547
+ response = client.item.query(
548
+ args: { ids: [987654321] },
549
+ select: [
550
+ "id",
551
+ "name",
552
+ "created_at",
553
+ "updated_at",
554
+ "creator_id",
555
+ "state",
556
+ "url",
557
+ {
558
+ board: ["id", "name", "description", "state", "url"],
559
+ group: ["id", "title", "color", "position"],
560
+ column_values: ["id", "text", "type", "value"],
561
+ updates: ["id", "body", "created_at", "creator"],
562
+ subitems: ["id", "name", "state", "created_at"]
563
+ }
564
+ ]
565
+ )
566
+ ```
567
+
568
+ **Good - Minimal fields:**
569
+ ```ruby
570
+ # Only retrieve what you need
571
+ response = client.item.query(
572
+ args: { ids: [987654321] },
573
+ select: [
574
+ "id",
575
+ "name",
576
+ {
577
+ column_values: ["id", "text"]
578
+ }
579
+ ]
580
+ )
581
+ ```
582
+
583
+ ### Use Pagination Instead of Large Queries
584
+
585
+ **Bad - Fetching all items at once:**
586
+ ```ruby
587
+ # This can timeout or hit complexity limits
588
+ response = client.item.query(
589
+ args: { limit: 10000 }
590
+ )
591
+ ```
592
+
593
+ **Good - Paginated approach:**
594
+ ```ruby
595
+ response = client.board.items_page(
596
+ board_ids: 1234567890,
597
+ limit: 100 # Reasonable page size
598
+ )
599
+
600
+ items = response.body.dig("data", "boards", 0, "items_page", "items")
601
+ cursor = response.body.dig("data", "boards", 0, "items_page", "cursor")
602
+
603
+ # Fetch next page if needed
604
+ if cursor
605
+ next_response = client.board.items_page(
606
+ board_ids: 1234567890,
607
+ cursor: cursor
608
+ )
609
+ end
610
+ ```
611
+
612
+ ### Avoid Nested Loops of Queries
613
+
614
+ **Bad - N+1 query problem:**
615
+ ```ruby
616
+ # Gets boards, then queries each board's items separately
617
+ boards_response = client.board.query
618
+ boards = boards_response.body.dig("data", "boards")
619
+
620
+ boards.each do |board|
621
+ # This creates N additional queries!
622
+ items_response = client.board.items_page(board_ids: board["id"])
623
+ items = items_response.body.dig("data", "boards", 0, "items_page", "items")
624
+ puts "#{board['name']}: #{items.length} items"
625
+ end
626
+ ```
627
+
628
+ **Good - Batch query with nested selection:**
629
+ ```ruby
630
+ # Get items in the initial query (for small datasets)
631
+ response = client.board.query(
632
+ args: { ids: [1234567890, 2345678901] },
633
+ select: [
634
+ "id",
635
+ "name",
636
+ {
637
+ items_page: {
638
+ items: ["id", "name"]
639
+ }
640
+ }
641
+ ]
642
+ )
643
+
644
+ boards = response.body.dig("data", "boards")
645
+ boards.each do |board|
646
+ items = board.dig("items_page", "items") || []
647
+ puts "#{board['name']}: #{items.length} items"
648
+ end
649
+ ```
650
+
651
+ ### Limit Column Value Queries
652
+
653
+ **Bad - Getting all column data:**
654
+ ```ruby
655
+ response = client.item.query(
656
+ args: { ids: [987654321] },
657
+ select: [
658
+ "id",
659
+ "name",
660
+ {
661
+ column_values: ["id", "text", "type", "value", "additional_info"]
662
+ }
663
+ ]
664
+ )
665
+ ```
666
+
667
+ **Good - Only needed column fields:**
668
+ ```ruby
669
+ response = client.item.query(
670
+ args: { ids: [987654321] },
671
+ select: [
672
+ "id",
673
+ "name",
674
+ {
675
+ column_values: ["id", "text"] # Just what you need
676
+ }
677
+ ]
678
+ )
679
+ ```
680
+
681
+ ## Working with Related Data
682
+
683
+ Navigate relationships between boards, groups, items, and other resources.
684
+
685
+ ### Board → Groups → Items → Column Values
686
+
687
+ Complete hierarchy traversal:
688
+
689
+ ```ruby
690
+ # Get board with groups
691
+ board_response = client.board.query(
692
+ args: { ids: [1234567890] },
693
+ select: [
694
+ "id",
695
+ "name",
696
+ {
697
+ groups: ["id", "title"]
698
+ }
699
+ ]
700
+ )
701
+
702
+ board = board_response.body.dig("data", "boards", 0)
703
+
704
+ puts "Board: #{board['name']}\n\n"
705
+
706
+ # For each group, get items with column values
707
+ board["groups"].each do |group|
708
+ items_response = client.group.items_page(
709
+ board_ids: board["id"],
710
+ group_ids: group["id"],
711
+ limit: 50,
712
+ select: [
713
+ "id",
714
+ "name",
715
+ {
716
+ column_values: ["id", "text", "type"]
717
+ }
718
+ ]
719
+ )
720
+
721
+ if items_response.success?
722
+ group_data = items_response.body.dig("data", "boards", 0, "groups", 0)
723
+ items = group_data.dig("items_page", "items") || []
724
+
725
+ puts "Group: #{group['title']}"
726
+ puts "Items: #{items.length}\n"
727
+
728
+ items.first(3).each do |item|
729
+ puts " Item: #{item['name']}"
730
+
731
+ item["column_values"].each do |col|
732
+ next if col["text"].nil? || col["text"].empty?
733
+ puts " - #{col['id']}: #{col['text']} (#{col['type']})"
734
+ end
735
+ puts ""
736
+ end
737
+ end
738
+ end
739
+ ```
740
+
741
+ ### Workspace → Folders → Boards
742
+
743
+ Navigate organizational structure:
744
+
745
+ ```ruby
746
+ # Get workspaces
747
+ workspace_response = client.workspace.query(
748
+ select: [
749
+ "id",
750
+ "name",
751
+ "description"
752
+ ]
753
+ )
754
+
755
+ workspaces = workspace_response.body.dig("data", "workspaces")
756
+
757
+ workspaces.each do |workspace|
758
+ puts "\nWorkspace: #{workspace['name']}"
759
+
760
+ # Get boards in this workspace
761
+ boards_response = client.board.query(
762
+ args: { workspace_ids: [workspace["id"]] },
763
+ select: [
764
+ "id",
765
+ "name",
766
+ "description",
767
+ {
768
+ groups: ["id", "title"]
769
+ }
770
+ ]
771
+ )
772
+
773
+ boards = boards_response.body.dig("data", "boards") || []
774
+
775
+ puts "Boards: #{boards.length}\n"
776
+
777
+ boards.each do |board|
778
+ puts " - #{board['name']}"
779
+ puts " Groups: #{board['groups'].length}"
780
+ end
781
+ end
782
+ ```
783
+
784
+ ### Item → Subitems → Updates
785
+
786
+ Get item with all related data:
787
+
788
+ ```ruby
789
+ response = client.item.query(
790
+ args: { ids: [987654321] },
791
+ select: [
792
+ "id",
793
+ "name",
794
+ "created_at",
795
+ {
796
+ board: ["id", "name"],
797
+ group: ["id", "title"],
798
+ creator: ["id", "name", "email"],
799
+ subitems: [
800
+ "id",
801
+ "name",
802
+ "state",
803
+ {
804
+ column_values: ["id", "text"]
805
+ }
806
+ ],
807
+ updates: [
808
+ "id",
809
+ "body",
810
+ "created_at",
811
+ {
812
+ creator: ["name"]
813
+ }
814
+ ]
815
+ }
816
+ ]
817
+ )
818
+
819
+ if response.success?
820
+ item = response.body.dig("data", "items", 0)
821
+
822
+ puts "Item: #{item['name']}"
823
+ puts "Board: #{item.dig('board', 'name')}"
824
+ puts "Group: #{item.dig('group', 'title')}"
825
+ puts "Creator: #{item.dig('creator', 'name')}"
826
+ puts "Created: #{item['created_at']}\n"
827
+
828
+ puts "\nSubitems (#{item['subitems'].length}):"
829
+ item["subitems"].each do |subitem|
830
+ puts " - #{subitem['name']} [#{subitem['state']}]"
831
+ end
832
+
833
+ puts "\nUpdates (#{item['updates'].length}):"
834
+ item["updates"].first(5).each do |update|
835
+ creator = update.dig("creator", "name")
836
+ puts " [#{update['created_at']}] #{creator}:"
837
+ puts " #{update['body']}\n\n"
838
+ end
839
+ end
840
+ ```
841
+
842
+ ## Custom Select Patterns
843
+
844
+ Advanced field selection techniques.
845
+
846
+ ### Mixing Arrays and Hashes
847
+
848
+ Combine simple and nested fields:
849
+
850
+ ```ruby
851
+ select: [
852
+ # Simple fields
853
+ "id",
854
+ "name",
855
+ "created_at",
856
+ "state",
857
+
858
+ # Nested fields with hash syntax
859
+ {
860
+ board: ["id", "name", "url"],
861
+ group: ["id", "title"],
862
+ creator: ["name", "email"]
863
+ },
864
+
865
+ # More simple fields
866
+ "url"
867
+ ]
868
+ ```
869
+
870
+ ### Deeply Nested Structures
871
+
872
+ Multiple levels of nesting:
873
+
874
+ ```ruby
875
+ response = client.board.query(
876
+ args: { ids: [1234567890] },
877
+ select: [
878
+ "id",
879
+ "name",
880
+ {
881
+ workspace: [
882
+ "id",
883
+ "name"
884
+ ],
885
+ groups: [
886
+ "id",
887
+ "title",
888
+ "color",
889
+ {
890
+ items_page: {
891
+ items: [
892
+ "id",
893
+ "name",
894
+ {
895
+ column_values: ["id", "text"],
896
+ subitems: [
897
+ "id",
898
+ "name",
899
+ {
900
+ column_values: ["id", "text"]
901
+ }
902
+ ]
903
+ }
904
+ ]
905
+ }
906
+ }
907
+ ]
908
+ }
909
+ ]
910
+ )
911
+ ```
912
+
913
+ ### Conditional Field Selection
914
+
915
+ Select fields based on resource type:
916
+
917
+ ```ruby
918
+ def query_with_details(client, item_ids, include_updates: false)
919
+ fields = [
920
+ "id",
921
+ "name",
922
+ {
923
+ board: ["id", "name"],
924
+ column_values: ["id", "text"]
925
+ }
926
+ ]
927
+
928
+ # Conditionally add updates
929
+ if include_updates
930
+ fields << {
931
+ updates: ["id", "body", "created_at"]
932
+ }
933
+ end
934
+
935
+ client.item.query(
936
+ args: { ids: item_ids },
937
+ select: fields
938
+ )
939
+ end
940
+
941
+ # Usage
942
+ response = query_with_details(client, [987654321], include_updates: true)
943
+ ```
944
+
945
+ ### Reusable Field Definitions
946
+
947
+ Create reusable field sets:
948
+
949
+ ```ruby
950
+ # Define common field sets
951
+ BASIC_ITEM_FIELDS = ["id", "name", "created_at"].freeze
952
+
953
+ ITEM_WITH_BOARD = [
954
+ *BASIC_ITEM_FIELDS,
955
+ {
956
+ board: ["id", "name"]
957
+ }
958
+ ].freeze
959
+
960
+ ITEM_WITH_COLUMNS = [
961
+ *BASIC_ITEM_FIELDS,
962
+ {
963
+ column_values: ["id", "text", "type"]
964
+ }
965
+ ].freeze
966
+
967
+ FULL_ITEM_FIELDS = [
968
+ *BASIC_ITEM_FIELDS,
969
+ "state",
970
+ "url",
971
+ {
972
+ board: ["id", "name"],
973
+ group: ["id", "title"],
974
+ column_values: ["id", "text", "type"],
975
+ creator: ["name", "email"]
976
+ }
977
+ ].freeze
978
+
979
+ # Use them in queries
980
+ response = client.item.query(
981
+ args: { ids: [987654321] },
982
+ select: FULL_ITEM_FIELDS
983
+ )
984
+ ```
985
+
986
+ ## Complete Example
987
+
988
+ Comprehensive complex query combining multiple techniques:
989
+
990
+ ```ruby
991
+ require "monday_ruby"
992
+ require "dotenv/load"
993
+
994
+ Monday.configure do |config|
995
+ config.token = ENV["MONDAY_TOKEN"]
996
+ end
997
+
998
+ client = Monday::Client.new
999
+
1000
+ # Get board with complete structure
1001
+ board_response = client.board.query(
1002
+ args: {
1003
+ ids: [1234567890],
1004
+ state: :active
1005
+ },
1006
+ select: [
1007
+ "id",
1008
+ "name",
1009
+ "description",
1010
+ {
1011
+ workspace: ["id", "name"],
1012
+ columns: ["id", "title", "type"],
1013
+ groups: ["id", "title", "color"]
1014
+ }
1015
+ ]
1016
+ )
1017
+
1018
+ unless board_response.success?
1019
+ puts "Failed to fetch board"
1020
+ exit 1
1021
+ end
1022
+
1023
+ board = board_response.body.dig("data", "boards", 0)
1024
+
1025
+ puts "\n" + "=" * 70
1026
+ puts "BOARD: #{board['name']}"
1027
+ puts "=" * 70
1028
+ puts "Workspace: #{board.dig('workspace', 'name')}"
1029
+ puts "Description: #{board['description']}"
1030
+ puts "\nColumns (#{board['columns'].length}):"
1031
+ board['columns'].each do |col|
1032
+ puts " - #{col['title']} (#{col['type']})"
1033
+ end
1034
+
1035
+ puts "\n" + "-" * 70
1036
+
1037
+ # For each group, get filtered items
1038
+ board['groups'].each do |group|
1039
+ puts "\nGROUP: #{group['title']} (#{group['color']})"
1040
+
1041
+ # Get items with filters
1042
+ items_response = client.group.items_page(
1043
+ board_ids: board["id"],
1044
+ group_ids: group["id"],
1045
+ limit: 100,
1046
+ query_params: {
1047
+ rules: [
1048
+ { column_id: "status", compare_value: [1] }
1049
+ ],
1050
+ operator: :and
1051
+ },
1052
+ select: [
1053
+ "id",
1054
+ "name",
1055
+ "state",
1056
+ "created_at",
1057
+ {
1058
+ column_values: ["id", "text", "type"],
1059
+ creator: ["name", "email"],
1060
+ subitems: ["id", "name", "state"]
1061
+ }
1062
+ ]
1063
+ )
1064
+
1065
+ next unless items_response.success?
1066
+
1067
+ group_data = items_response.body.dig("data", "boards", 0, "groups", 0)
1068
+ items = group_data.dig("items_page", "items") || []
1069
+ cursor = group_data.dig("items_page", "cursor")
1070
+
1071
+ puts "Items with status=1: #{items.length}"
1072
+
1073
+ items.first(5).each do |item|
1074
+ creator = item.dig("creator")
1075
+ subitems_count = item["subitems"].length
1076
+
1077
+ puts "\n Item: #{item['name']}"
1078
+ puts " State: #{item['state']}"
1079
+ puts " Created: #{item['created_at']}"
1080
+ puts " Creator: #{creator&.dig('name')} (#{creator&.dig('email')})"
1081
+ puts " Subitems: #{subitems_count}"
1082
+
1083
+ # Show column values
1084
+ puts " Columns:"
1085
+ item["column_values"].each do |col|
1086
+ next if col["text"].nil? || col["text"].empty?
1087
+ puts " #{col['id']}: #{col['text']}"
1088
+ end
1089
+ end
1090
+
1091
+ if cursor
1092
+ puts "\n [More items available - use cursor for pagination]"
1093
+ end
1094
+ end
1095
+
1096
+ puts "\n" + "=" * 70
1097
+ ```
1098
+
1099
+ ## Performance Tips
1100
+
1101
+ 1. **Request only what you need**: Minimize fields in `select` to reduce response size
1102
+ 2. **Use pagination**: Always use `items_page` for large datasets instead of `items`
1103
+ 3. **Batch queries**: Combine related data in single queries when possible
1104
+ 4. **Cache results**: Store frequently accessed data to reduce API calls
1105
+ 5. **Filter server-side**: Use `query_params` instead of fetching all items and filtering in Ruby
1106
+ 6. **Monitor complexity**: monday.com has complexity limits - simpler queries are faster
1107
+ 7. **Use cursors**: Cursor-based pagination is more efficient than offset-based
1108
+
1109
+ ## Next Steps
1110
+
1111
+ - [Pagination guide](/guides/advanced/pagination)
1112
+ - [Error handling](/guides/advanced/errors)
1113
+ - [Rate limiting strategies](/guides/advanced/rate-limiting)
1114
+ - [Batch operations](/guides/advanced/batch)