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,478 @@
1
+ # Error Handling Best Practices
2
+
3
+ Error handling is a critical aspect of building resilient applications that integrate with external APIs. This guide explores the philosophy and patterns for effective error handling when using the monday_ruby gem.
4
+
5
+ ## Error Handling Philosophy
6
+
7
+ In Ruby, exceptions are the primary mechanism for handling error conditions. The monday_ruby gem embraces this philosophy by providing a rich exception hierarchy that maps to specific API error conditions. This approach follows the principle that **errors should be specific enough to make informed decisions about recovery, but not so granular that they become burdensome to handle**.
8
+
9
+ The alternative—using status codes or error objects—would require checking return values after every API call, leading to verbose, error-prone code. Ruby's exception model allows your "happy path" code to remain clean while still providing robust error handling when needed.
10
+
11
+ ## Why Specific Exception Types Matter
12
+
13
+ The monday_ruby gem provides specific exception classes for different error conditions:
14
+
15
+ ```ruby
16
+ Monday::AuthorizationError # Authentication/authorization failures
17
+ Monday::InvalidRequestError # Malformed requests
18
+ Monday::ResourceNotFoundError # Missing resources
19
+ Monday::ComplexityError # Query complexity exceeded
20
+ ```
21
+
22
+ **Why not just catch a generic `Monday::Error`?** Specific exceptions enable different recovery strategies:
23
+
24
+ - **AuthorizationError**: Might indicate expired credentials → refresh token or prompt re-authentication
25
+ - **ComplexityError**: Query too complex → simplify query or retry with pagination
26
+ - **ResourceNotFoundError**: Item deleted → remove from local cache or skip processing
27
+ - **InvalidRequestError**: Programming error → log for debugging, don't retry
28
+
29
+ Using specific exceptions makes your intent clear and prevents accidentally catching unrelated errors.
30
+
31
+ ## When to Rescue Specific Errors vs Base Classes
32
+
33
+ The decision of which exception level to catch depends on your recovery strategy:
34
+
35
+ ### Catch Specific Exceptions When:
36
+
37
+ ```ruby
38
+ begin
39
+ client.item.query(ids: [item_id])
40
+ rescue Monday::ResourceNotFoundError
41
+ # Specific recovery: remove from local database
42
+ Item.find_by(monday_id: item_id)&.destroy
43
+ rescue Monday::AuthorizationError
44
+ # Specific recovery: refresh credentials
45
+ refresh_monday_token
46
+ retry
47
+ end
48
+ ```
49
+
50
+ This approach is best when **different errors require different recovery actions**.
51
+
52
+ ### Catch Base Exception When:
53
+
54
+ ```ruby
55
+ begin
56
+ client.board.query(ids: [board_id])
57
+ rescue Monday::Error => e
58
+ # Generic recovery: log and notify
59
+ logger.error("Monday API error: #{e.message}")
60
+ notify_monitoring_system(e)
61
+ nil # Return nil to allow graceful degradation
62
+ end
63
+ ```
64
+
65
+ This approach is best when **all API errors should be handled the same way** (logging, monitoring, graceful failure).
66
+
67
+ ### Catch at Multiple Levels:
68
+
69
+ ```ruby
70
+ begin
71
+ sync_monday_data
72
+ rescue Monday::ComplexityError
73
+ # Specific: wait and retry
74
+ sleep(60)
75
+ retry
76
+ rescue Monday::Error => e
77
+ # Catch-all: log and fail gracefully
78
+ logger.error("Sync failed: #{e.message}")
79
+ false
80
+ end
81
+ ```
82
+
83
+ This pattern handles specific cases specially while catching all other API errors generically.
84
+
85
+ ## Error Recovery Patterns
86
+
87
+ ### 1. Immediate Retry (Transient Errors)
88
+
89
+ Some errors are transient—temporary network issues, service hiccups. These warrant immediate retry:
90
+
91
+ ```ruby
92
+ def fetch_with_retry(max_attempts: 3)
93
+ attempts = 0
94
+ begin
95
+ attempts += 1
96
+ client.board.query(ids: [board_id])
97
+ rescue Monday::InternalServerError, Monday::ServiceUnavailableError
98
+ retry if attempts < max_attempts
99
+ raise
100
+ end
101
+ end
102
+ ```
103
+
104
+ **When to use**: Network timeouts, 5xx errors, temporary service issues.
105
+
106
+ **Trade-off**: Immediate retries can compound problems during outages. Use sparingly.
107
+
108
+ ### 2. Exponential Backoff (Rate Limiting)
109
+
110
+ Rate limit errors should be retried with increasing delays:
111
+
112
+ ```ruby
113
+ def fetch_with_backoff(max_attempts: 5)
114
+ attempts = 0
115
+ begin
116
+ attempts += 1
117
+ client.item.query(ids: item_ids)
118
+ rescue Monday::ComplexityError => e
119
+ wait_time = 2 ** attempts # 2, 4, 8, 16, 32 seconds
120
+ sleep(wait_time)
121
+ retry if attempts < max_attempts
122
+ raise
123
+ end
124
+ end
125
+ ```
126
+
127
+ **Why exponential**: Linear backoff (1s, 2s, 3s) doesn't reduce load enough. Exponential backoff gives the service time to recover.
128
+
129
+ **Trade-off**: Long waits can impact user experience. Consider background processing for retries.
130
+
131
+ ### 3. Circuit Breaker (Cascading Failures)
132
+
133
+ When an API is consistently failing, stop making requests to prevent cascading failures:
134
+
135
+ ```ruby
136
+ class MondayCircuitBreaker
137
+ def initialize(failure_threshold: 5, timeout: 60)
138
+ @failure_count = 0
139
+ @failure_threshold = failure_threshold
140
+ @timeout = timeout
141
+ @opened_at = nil
142
+ end
143
+
144
+ def call
145
+ raise CircuitOpenError if circuit_open?
146
+
147
+ begin
148
+ result = yield
149
+ reset_failures
150
+ result
151
+ rescue Monday::Error => e
152
+ record_failure
153
+ raise
154
+ end
155
+ end
156
+
157
+ private
158
+
159
+ def circuit_open?
160
+ @failure_count >= @failure_threshold &&
161
+ (@opened_at.nil? || Time.now - @opened_at < @timeout)
162
+ end
163
+
164
+ def record_failure
165
+ @failure_count += 1
166
+ @opened_at = Time.now if @failure_count == @failure_threshold
167
+ end
168
+
169
+ def reset_failures
170
+ @failure_count = 0
171
+ @opened_at = nil
172
+ end
173
+ end
174
+ ```
175
+
176
+ **When to use**: High-traffic applications where API failures could cascade to other systems.
177
+
178
+ **Trade-off**: Adds complexity. May reject requests even when service has recovered.
179
+
180
+ ### 4. Graceful Degradation
181
+
182
+ Instead of failing completely, provide reduced functionality:
183
+
184
+ ```ruby
185
+ def get_board_data(board_id)
186
+ begin
187
+ client.board.query(ids: [board_id])
188
+ rescue Monday::Error => e
189
+ logger.warn("Failed to fetch live data: #{e.message}")
190
+ # Fall back to cached data
191
+ Rails.cache.read("board_#{board_id}")
192
+ end
193
+ end
194
+ ```
195
+
196
+ **When to use**: User-facing features where some data is better than no data.
197
+
198
+ **Trade-off**: Users get stale data. Must communicate data freshness clearly.
199
+
200
+ ## Retry Strategies: When and How Many Times
201
+
202
+ **How many retries?**
203
+ - **Transient errors**: 2-3 retries (network blips are usually brief)
204
+ - **Rate limiting**: 5-7 retries (may need multiple backoff intervals)
205
+ - **Service outages**: 0-1 retries (unlikely to resolve quickly)
206
+
207
+ **When not to retry:**
208
+ - `InvalidRequestError`: The request is malformed; retrying won't help
209
+ - `AuthorizationError`: Credentials are invalid; retry only after refreshing
210
+ - `ResourceNotFoundError`: The resource doesn't exist; retrying won't create it
211
+
212
+ **Retry budgets**: Consider a total time budget rather than retry count:
213
+
214
+ ```ruby
215
+ def fetch_with_budget(timeout: 30)
216
+ deadline = Time.now + timeout
217
+ attempts = 0
218
+
219
+ begin
220
+ attempts += 1
221
+ client.board.query(ids: [board_id])
222
+ rescue Monday::ComplexityError
223
+ wait_time = 2 ** attempts
224
+ raise if Time.now + wait_time > deadline
225
+ sleep(wait_time)
226
+ retry
227
+ end
228
+ end
229
+ ```
230
+
231
+ ## Logging Errors for Debugging and Monitoring
232
+
233
+ Effective logging balances detail with signal-to-noise ratio.
234
+
235
+ ### What to Log
236
+
237
+ **Always log:**
238
+ - Exception class and message
239
+ - Request details (endpoint, parameters—except secrets)
240
+ - Context (user ID, board ID, operation being performed)
241
+ - Timestamp and correlation ID
242
+
243
+ ```ruby
244
+ begin
245
+ client.item.create(board_id: board_id, item_name: name)
246
+ rescue Monday::Error => e
247
+ logger.error({
248
+ error_class: e.class.name,
249
+ error_message: e.message,
250
+ operation: 'create_item',
251
+ board_id: board_id,
252
+ item_name: name,
253
+ user_id: current_user.id,
254
+ correlation_id: request_id
255
+ }.to_json)
256
+ raise
257
+ end
258
+ ```
259
+
260
+ **Don't log:**
261
+ - API tokens or credentials
262
+ - Sensitive user data (unless required for compliance)
263
+ - Full response bodies (unless debugging a specific issue)
264
+
265
+ ### Log Levels
266
+
267
+ - **ERROR**: Unexpected failures that impact functionality
268
+ - **WARN**: Handled errors (graceful degradation, retries that succeed)
269
+ - **INFO**: Normal API errors that are part of business logic (e.g., validation failures)
270
+ - **DEBUG**: Full request/response details (disable in production)
271
+
272
+ ```ruby
273
+ rescue Monday::ResourceNotFoundError => e
274
+ logger.info("Item not found, skipping: #{item_id}") # Expected condition
275
+ nil
276
+ rescue Monday::InternalServerError => e
277
+ logger.error("Monday API error: #{e.message}") # Unexpected failure
278
+ raise
279
+ ```
280
+
281
+ ## User-Facing vs Internal Error Messages
282
+
283
+ Exception messages serve two audiences:
284
+
285
+ ### Internal Messages (for developers/logs)
286
+
287
+ Include technical details:
288
+ ```ruby
289
+ "Failed to create item: ComplexityError - Query complexity exceeds limit (60/58).
290
+ Reduce query fields or implement pagination."
291
+ ```
292
+
293
+ ### User-Facing Messages (for end users)
294
+
295
+ Be generic and actionable:
296
+ ```ruby
297
+ begin
298
+ client.item.create(...)
299
+ rescue Monday::ComplexityError
300
+ flash[:error] = "The operation is too complex. Please try creating fewer items at once."
301
+ rescue Monday::Error
302
+ flash[:error] = "We couldn't complete this action. Please try again later."
303
+ end
304
+ ```
305
+
306
+ **Why separate them?**
307
+ - Users don't need technical details
308
+ - Exposing internal errors can be a security risk
309
+ - User messages should suggest solutions, not explain implementation
310
+
311
+ ## Graceful Degradation Patterns
312
+
313
+ Graceful degradation means providing partial functionality when full functionality fails.
314
+
315
+ ### Pattern 1: Cache Fallback
316
+
317
+ ```ruby
318
+ def get_board_items(board_id)
319
+ begin
320
+ items = client.item.query_by_board(board_id: board_id)
321
+ Rails.cache.write("board_items_#{board_id}", items, expires_in: 1.hour)
322
+ items
323
+ rescue Monday::Error => e
324
+ logger.warn("API failed, using cache: #{e.message}")
325
+ Rails.cache.read("board_items_#{board_id}") || []
326
+ end
327
+ end
328
+ ```
329
+
330
+ ### Pattern 2: Feature Toggle
331
+
332
+ ```ruby
333
+ def sync_monday_data
334
+ client.board.query(...)
335
+ rescue Monday::Error => e
336
+ logger.error("Sync failed: #{e.message}")
337
+ disable_monday_sync_feature!
338
+ notify_admins
339
+ end
340
+ ```
341
+
342
+ ### Pattern 3: Partial Success
343
+
344
+ ```ruby
345
+ def sync_multiple_boards(board_ids)
346
+ results = { success: [], failed: [] }
347
+
348
+ board_ids.each do |board_id|
349
+ begin
350
+ sync_board(board_id)
351
+ results[:success] << board_id
352
+ rescue Monday::Error => e
353
+ logger.error("Board #{board_id} sync failed: #{e.message}")
354
+ results[:failed] << board_id
355
+ end
356
+ end
357
+
358
+ results
359
+ end
360
+ ```
361
+
362
+ **Trade-offs**: Users may not notice degraded functionality, leading to confusion. Always communicate what's working and what's not.
363
+
364
+ ## Transaction-Like Error Handling
365
+
366
+ APIs don't support transactions like databases, but you can implement transaction-like patterns:
367
+
368
+ ### Pattern 1: Compensation (Undo on Error)
369
+
370
+ ```ruby
371
+ def create_board_with_items(board_name, items)
372
+ board = nil
373
+
374
+ begin
375
+ board = client.board.create(board_name: board_name).dig('data', 'create_board')
376
+
377
+ items.each do |item|
378
+ client.item.create(board_id: board['id'], item_name: item['name'])
379
+ end
380
+
381
+ board
382
+ rescue Monday::Error => e
383
+ # Compensate: delete the board if item creation failed
384
+ client.board.delete(board_id: board['id']) if board
385
+ raise
386
+ end
387
+ end
388
+ ```
389
+
390
+ ### Pattern 2: All-or-Nothing (Validate First)
391
+
392
+ ```ruby
393
+ def bulk_update_items(updates)
394
+ # Validate all updates first
395
+ updates.each do |update|
396
+ validate_update!(update)
397
+ end
398
+
399
+ # If validation passes, perform updates
400
+ updates.map do |update|
401
+ client.item.update(item_id: update[:id], column_values: update[:values])
402
+ end
403
+ rescue Monday::Error => e
404
+ logger.error("Bulk update failed, no changes made: #{e.message}")
405
+ raise
406
+ end
407
+ ```
408
+
409
+ **Limitation**: Between validation and execution, state can change. True atomicity isn't possible with APIs.
410
+
411
+ ## Testing Error Scenarios
412
+
413
+ Error handling code is only as good as its tests.
414
+
415
+ ### Test Each Error Type
416
+
417
+ ```ruby
418
+ RSpec.describe 'error handling' do
419
+ it 'retries on complexity errors' do
420
+ allow(client).to receive(:make_request)
421
+ .and_raise(Monday::ComplexityError)
422
+ .exactly(3).times
423
+ .and_return(mock_response)
424
+
425
+ result = fetch_with_retry
426
+ expect(result).to eq(mock_response)
427
+ end
428
+
429
+ it 'does not retry on invalid request errors' do
430
+ allow(client).to receive(:make_request)
431
+ .and_raise(Monday::InvalidRequestError)
432
+
433
+ expect { fetch_with_retry }.to raise_error(Monday::InvalidRequestError)
434
+ end
435
+ end
436
+ ```
437
+
438
+ ### Test Retry Logic
439
+
440
+ ```ruby
441
+ it 'implements exponential backoff' do
442
+ allow(client).to receive(:make_request)
443
+ .and_raise(Monday::ComplexityError)
444
+
445
+ expect(self).to receive(:sleep).with(2)
446
+ expect(self).to receive(:sleep).with(4)
447
+ expect(self).to receive(:sleep).with(8)
448
+
449
+ expect { fetch_with_backoff(max_attempts: 4) }
450
+ .to raise_error(Monday::ComplexityError)
451
+ end
452
+ ```
453
+
454
+ ### Test Graceful Degradation
455
+
456
+ ```ruby
457
+ it 'falls back to cache on error' do
458
+ allow(client).to receive(:make_request)
459
+ .and_raise(Monday::InternalServerError)
460
+
461
+ Rails.cache.write('board_123', cached_data)
462
+
463
+ result = get_board_data(123)
464
+ expect(result).to eq(cached_data)
465
+ end
466
+ ```
467
+
468
+ ## Key Takeaways
469
+
470
+ 1. **Be specific**: Use specific exception types to enable targeted recovery
471
+ 2. **Retry wisely**: Use exponential backoff for rate limits, limited retries for transient errors
472
+ 3. **Fail gracefully**: Provide reduced functionality when possible
473
+ 4. **Log thoughtfully**: Include context for debugging, but protect sensitive data
474
+ 5. **Separate concerns**: Internal errors ≠ user-facing messages
475
+ 6. **Test thoroughly**: Error handling code needs tests too
476
+ 7. **Monitor**: Track error rates to identify patterns and systemic issues
477
+
478
+ Error handling is not just about preventing crashes—it's about creating resilient systems that degrade gracefully and recover automatically when possible.