monday_ruby 1.0.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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/.env +1 -1
  3. data/.rspec +0 -1
  4. data/.rubocop.yml +19 -0
  5. data/.simplecov +1 -0
  6. data/CHANGELOG.md +49 -0
  7. data/CONTRIBUTING.md +165 -0
  8. data/README.md +167 -88
  9. data/docs/.vitepress/config.mjs +255 -0
  10. data/docs/.vitepress/theme/index.js +4 -0
  11. data/docs/.vitepress/theme/style.css +43 -0
  12. data/docs/README.md +80 -0
  13. data/docs/explanation/architecture.md +507 -0
  14. data/docs/explanation/best-practices/errors.md +478 -0
  15. data/docs/explanation/best-practices/performance.md +1084 -0
  16. data/docs/explanation/best-practices/rate-limiting.md +630 -0
  17. data/docs/explanation/best-practices/testing.md +820 -0
  18. data/docs/explanation/column-values.md +857 -0
  19. data/docs/explanation/design.md +795 -0
  20. data/docs/explanation/graphql.md +356 -0
  21. data/docs/explanation/migration/v1.md +808 -0
  22. data/docs/explanation/pagination.md +447 -0
  23. data/docs/guides/advanced/batch.md +1274 -0
  24. data/docs/guides/advanced/complex-queries.md +1114 -0
  25. data/docs/guides/advanced/errors.md +818 -0
  26. data/docs/guides/advanced/pagination.md +934 -0
  27. data/docs/guides/advanced/rate-limiting.md +981 -0
  28. data/docs/guides/authentication.md +286 -0
  29. data/docs/guides/boards/create.md +386 -0
  30. data/docs/guides/boards/delete.md +405 -0
  31. data/docs/guides/boards/duplicate.md +511 -0
  32. data/docs/guides/boards/query.md +530 -0
  33. data/docs/guides/boards/update.md +453 -0
  34. data/docs/guides/columns/create.md +452 -0
  35. data/docs/guides/columns/metadata.md +492 -0
  36. data/docs/guides/columns/query.md +455 -0
  37. data/docs/guides/columns/update-multiple.md +459 -0
  38. data/docs/guides/columns/update-values.md +509 -0
  39. data/docs/guides/files/add-to-column.md +40 -0
  40. data/docs/guides/files/add-to-update.md +37 -0
  41. data/docs/guides/files/clear-column.md +33 -0
  42. data/docs/guides/first-request.md +285 -0
  43. data/docs/guides/folders/manage.md +750 -0
  44. data/docs/guides/groups/items.md +626 -0
  45. data/docs/guides/groups/manage.md +501 -0
  46. data/docs/guides/installation.md +169 -0
  47. data/docs/guides/items/create.md +493 -0
  48. data/docs/guides/items/delete.md +514 -0
  49. data/docs/guides/items/query.md +605 -0
  50. data/docs/guides/items/subitems.md +483 -0
  51. data/docs/guides/items/update.md +699 -0
  52. data/docs/guides/updates/manage.md +619 -0
  53. data/docs/guides/use-cases/dashboard.md +1421 -0
  54. data/docs/guides/use-cases/import.md +1962 -0
  55. data/docs/guides/use-cases/task-management.md +1381 -0
  56. data/docs/guides/workspaces/manage.md +502 -0
  57. data/docs/index.md +69 -0
  58. data/docs/package-lock.json +2468 -0
  59. data/docs/package.json +13 -0
  60. data/docs/reference/client.md +540 -0
  61. data/docs/reference/configuration.md +586 -0
  62. data/docs/reference/errors.md +693 -0
  63. data/docs/reference/resources/account.md +208 -0
  64. data/docs/reference/resources/activity-log.md +369 -0
  65. data/docs/reference/resources/board-view.md +359 -0
  66. data/docs/reference/resources/board.md +393 -0
  67. data/docs/reference/resources/column.md +543 -0
  68. data/docs/reference/resources/file.md +236 -0
  69. data/docs/reference/resources/folder.md +386 -0
  70. data/docs/reference/resources/group.md +507 -0
  71. data/docs/reference/resources/item.md +348 -0
  72. data/docs/reference/resources/subitem.md +267 -0
  73. data/docs/reference/resources/update.md +259 -0
  74. data/docs/reference/resources/workspace.md +213 -0
  75. data/docs/reference/response.md +560 -0
  76. data/docs/tutorial/first-integration.md +713 -0
  77. data/lib/monday/client.rb +41 -2
  78. data/lib/monday/configuration.rb +13 -0
  79. data/lib/monday/deprecation.rb +23 -0
  80. data/lib/monday/error.rb +5 -2
  81. data/lib/monday/request.rb +19 -1
  82. data/lib/monday/resources/base.rb +4 -0
  83. data/lib/monday/resources/board.rb +52 -0
  84. data/lib/monday/resources/column.rb +6 -0
  85. data/lib/monday/resources/file.rb +56 -0
  86. data/lib/monday/resources/folder.rb +55 -0
  87. data/lib/monday/resources/group.rb +66 -0
  88. data/lib/monday/resources/item.rb +62 -0
  89. data/lib/monday/util.rb +33 -1
  90. data/lib/monday/version.rb +1 -1
  91. data/lib/monday_ruby.rb +1 -0
  92. metadata +92 -11
  93. data/monday_ruby.gemspec +0 -39
@@ -0,0 +1,507 @@
1
+ # Architecture Overview
2
+
3
+ This document explains the architectural design of the monday_ruby gem, exploring how its components work together and why they're designed this way.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Overall Architecture](#overall-architecture)
8
+ - [The Client-Resource Pattern](#the-client-resource-pattern)
9
+ - [Dynamic Resource Initialization](#dynamic-resource-initialization)
10
+ - [GraphQL Query Building](#graphql-query-building)
11
+ - [Request-Response Flow](#request-response-flow)
12
+ - [Error Handling Architecture](#error-handling-architecture)
13
+ - [Alternative Approaches](#alternative-approaches)
14
+
15
+ ## Overall Architecture
16
+
17
+ The monday_ruby gem is built around a **client-resource architecture** that wraps the monday.com GraphQL API in Ruby classes. Rather than exposing raw GraphQL queries or providing REST-style endpoints, the gem presents a resource-oriented interface that feels natural to Ruby developers.
18
+
19
+ The architecture consists of three main layers:
20
+
21
+ 1. **Client Layer** - The entry point that handles authentication, configuration, and HTTP communication
22
+ 2. **Resource Layer** - Domain-specific classes for each monday.com resource (Board, Item, Group, etc.)
23
+ 3. **Utility Layer** - Shared components for GraphQL query construction, error handling, and HTTP requests
24
+
25
+ This layered approach creates clear separation of concerns: resources focus on domain logic and query construction, the client handles communication, and utilities provide shared functionality.
26
+
27
+ ## The Client-Resource Pattern
28
+
29
+ ### Why This Pattern?
30
+
31
+ The client-resource pattern emerged from several design goals:
32
+
33
+ **1. Encapsulation of GraphQL Complexity**
34
+
35
+ monday.com's API is built on GraphQL, which is powerful but verbose. While GraphQL gives developers fine-grained control over data fetching, it requires understanding schema structure, query syntax, and field relationships. The client-resource pattern shields users from this complexity.
36
+
37
+ Instead of writing:
38
+ ```ruby
39
+ query = "query{boards(ids: [123]){id name description items{id name}}}"
40
+ # Now figure out how to execute this...
41
+ ```
42
+
43
+ Users work with Ruby methods:
44
+ ```ruby
45
+ client = Monday::Client.new(token: "...")
46
+ client.board.query(args: {ids: [123]}, select: ["id", "name", "description"])
47
+ ```
48
+
49
+ **2. Natural Organization by Domain**
50
+
51
+ monday.com has distinct concepts: boards, items, groups, columns, updates, workspaces, etc. By creating a resource class for each concept, the gem mirrors monday.com's domain model. Users can discover functionality by exploring resources that match their mental model of monday.com.
52
+
53
+ **3. Shared Infrastructure**
54
+
55
+ All resources share common needs: authentication, error handling, HTTP communication, and query building. The client-resource pattern allows resources to inherit shared behavior from `Resources::Base` while delegating infrastructure concerns to the client.
56
+
57
+ ### How It Works
58
+
59
+ The pattern involves two main components:
60
+
61
+ **The Client** (`/Users/sanifhimani/Development/monday_ruby/lib/monday/client.rb`)
62
+
63
+ The client is initialized with configuration and creates instances of all resource classes:
64
+
65
+ ```ruby
66
+ def initialize(config_args = {})
67
+ @config = configure(config_args)
68
+ Resources.initialize(self)
69
+ end
70
+ ```
71
+
72
+ The client provides:
73
+ - Configuration management (tokens, endpoints, timeouts)
74
+ - Request execution (`make_request`)
75
+ - Response handling and error raising
76
+ - HTTP header construction
77
+
78
+ **Resources** (`/Users/sanifhimani/Development/monday_ruby/lib/monday/resources/`)
79
+
80
+ Each resource class inherits from `Resources::Base` and receives a reference to the client:
81
+
82
+ ```ruby
83
+ class Base
84
+ attr_reader :client
85
+
86
+ def initialize(client)
87
+ @client = client
88
+ end
89
+
90
+ protected
91
+
92
+ def make_request(query)
93
+ client.make_request(query)
94
+ end
95
+ end
96
+ ```
97
+
98
+ Resources build GraphQL queries and delegate execution to the client. For example, `Board#query`:
99
+
100
+ ```ruby
101
+ def query(args: {}, select: DEFAULT_SELECT)
102
+ request_query = "query{boards#{Util.format_args(args)}{#{Util.format_select(select)}}}"
103
+ make_request(request_query)
104
+ end
105
+ ```
106
+
107
+ This separation means resources focus on "what" to query, while the client handles "how" to execute it.
108
+
109
+ ## Dynamic Resource Initialization
110
+
111
+ One of the most interesting aspects of the architecture is how resources are automatically discovered and attached to the client.
112
+
113
+ ### The Mechanism
114
+
115
+ The `Resources` module (`/Users/sanifhimani/Development/monday_ruby/lib/monday/resources.rb`) uses Ruby metaprogramming to dynamically initialize resources:
116
+
117
+ ```ruby
118
+ def self.initialize(client)
119
+ constants.each do |constant|
120
+ resource_class = const_get(constant)
121
+ resource_name = constant.to_s.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
122
+ client.instance_variable_set("@#{resource_name}", resource_class.new(client))
123
+ define_resource_accessor(client, resource_name) unless client.class.method_defined?(resource_name)
124
+ end
125
+ end
126
+ ```
127
+
128
+ This code:
129
+ 1. Iterates over all constants in the `Resources` module (which are the resource classes)
130
+ 2. Converts class names to snake_case (`Board` → `board`, `BoardView` → `board_view`)
131
+ 3. Creates an instance of each resource, passing the client
132
+ 4. Sets it as an instance variable on the client
133
+ 5. Defines an attr_reader for accessing the resource
134
+
135
+ ### Why This Approach?
136
+
137
+ This dynamic initialization provides several benefits:
138
+
139
+ **1. Automatic Registration**
140
+
141
+ Adding a new resource is as simple as creating a file in `lib/monday/resources/`. The file is automatically loaded (via `Dir[File.join(__dir__, "resources", "*.rb")].sort.each { |file| require file }`), and the resource becomes available on the client. No manual registration needed.
142
+
143
+ **2. Consistent Interface**
144
+
145
+ All resources follow the same naming convention automatically. `Board` becomes `client.board`, `ActivityLog` becomes `client.activity_log`. Developers can predict the accessor name from the class name.
146
+
147
+ **3. Single Responsibility**
148
+
149
+ Resources don't need to know about initialization logic. They simply inherit from `Base` and implement their methods. The `Resources` module handles the metaprogramming.
150
+
151
+ **4. Extensibility**
152
+
153
+ Third-party code could extend the `Resources` module with additional classes, and they would automatically be initialized and available on the client.
154
+
155
+ ### Trade-offs
156
+
157
+ This approach has some trade-offs:
158
+
159
+ - **Implicit Behavior**: The client's interface isn't explicitly defined in code. You must look at the resources directory to know what methods are available.
160
+ - **IDE Support**: Some IDEs struggle with dynamically defined methods, making autocomplete harder.
161
+ - **Debugging**: Metaprogramming can make stack traces more complex.
162
+
163
+ However, the benefits of automatic registration and consistency outweigh these concerns for a library like this, where resources map cleanly to domain concepts.
164
+
165
+ ## GraphQL Query Building
166
+
167
+ The gem translates Ruby method calls into GraphQL queries through the `Util` class (`/Users/sanifhimani/Development/monday_ruby/lib/monday/util.rb`).
168
+
169
+ ### The Challenge
170
+
171
+ GraphQL has specific syntax requirements:
172
+
173
+ ```graphql
174
+ query {
175
+ boards(ids: [123, 456], limit: 10) {
176
+ id
177
+ name
178
+ items {
179
+ id
180
+ name
181
+ }
182
+ }
183
+ }
184
+ ```
185
+
186
+ The gem needs to convert Ruby hashes and arrays into this format while handling:
187
+ - Arguments: `{ids: [123, 456]}` → `(ids: [123, 456])`
188
+ - Field selection: `["id", "name", {"items" => ["id"]}]` → `id name items { id }`
189
+ - Value formatting: strings need quotes, integers don't, symbols are literals
190
+ - Nested structures: arrays within hashes, hashes within arrays
191
+
192
+ ### The Solution
193
+
194
+ The `Util` class provides two main methods:
195
+
196
+ **`Util.format_args`** - Converts Ruby hashes to GraphQL arguments:
197
+
198
+ ```ruby
199
+ Util.format_args({board_name: "My Board", workspace_id: 123})
200
+ # => "(board_name: \"My Board\", workspace_id: 123)"
201
+ ```
202
+
203
+ It handles:
204
+ - String escaping (wrapping strings in quotes)
205
+ - Integer preservation (no quotes)
206
+ - JSON encoding for complex hashes (double-encoded for monday.com's API)
207
+ - Array formatting
208
+ - Symbol pass-through (for GraphQL enum values)
209
+
210
+ **`Util.format_select`** - Converts Ruby arrays/hashes to field selection:
211
+
212
+ ```ruby
213
+ Util.format_select(["id", "name", {"items" => ["id", "name"]}])
214
+ # => "id name items { id name }"
215
+ ```
216
+
217
+ It recursively processes arrays and hashes to create nested field selections.
218
+
219
+ ### Why String Building?
220
+
221
+ The gem builds queries as strings rather than using a GraphQL client library like `graphql-client`. This design choice has important implications:
222
+
223
+ **Advantages:**
224
+ - **Simplicity**: No external GraphQL dependencies, just string manipulation
225
+ - **Transparency**: Generated queries are easy to debug (just print the string)
226
+ - **Flexibility**: Can support any GraphQL feature monday.com adds without library updates
227
+ - **Performance**: No query parsing or AST construction overhead
228
+
229
+ **Disadvantages:**
230
+ - **No validation**: Malformed queries aren't caught until the API responds
231
+ - **String escaping**: Must manually handle special characters and injection risks
232
+ - **No type safety**: Can't verify field names or argument types at build time
233
+
234
+ For this gem, simplicity and transparency outweigh validation benefits. The monday.com API provides clear error messages when queries are malformed, and the gem's error handling surfaces these to users.
235
+
236
+ ## Request-Response Flow
237
+
238
+ Understanding how requests flow through the system illuminates the architecture's design.
239
+
240
+ ### The Complete Flow
241
+
242
+ 1. **User calls a resource method**
243
+ ```ruby
244
+ client.board.query(args: {ids: [123]}, select: ["id", "name"])
245
+ ```
246
+
247
+ 2. **Resource builds a GraphQL query string**
248
+ ```ruby
249
+ # Inside Board#query
250
+ request_query = "query{boards(ids: [123]){id name}}"
251
+ ```
252
+
253
+ 3. **Resource calls `make_request` (inherited from Base)**
254
+ ```ruby
255
+ make_request(request_query)
256
+ # Delegates to client.make_request(request_query)
257
+ ```
258
+
259
+ 4. **Client executes the HTTP request**
260
+ ```ruby
261
+ # Inside Client#make_request
262
+ response = Request.post(uri, body, request_headers, ...)
263
+ ```
264
+
265
+ 5. **Request.post wraps Net::HTTP**
266
+ ```ruby
267
+ # Inside Request.post
268
+ http = Net::HTTP.new(uri.host, uri.port)
269
+ request = Net::HTTP::Post.new(uri.request_uri, headers)
270
+ request.body = {"query" => query}.to_json
271
+ http.request(request)
272
+ ```
273
+
274
+ 6. **Response is wrapped in Monday::Response**
275
+ ```ruby
276
+ # Back in Client#make_request
277
+ handle_response(Response.new(response))
278
+ ```
279
+
280
+ 7. **Client checks for errors**
281
+ ```ruby
282
+ # Inside Client#handle_response
283
+ return response if response.success?
284
+ raise_errors(response)
285
+ ```
286
+
287
+ 8. **Response returned to user**
288
+ ```ruby
289
+ response.body # => {"data" => {"boards" => [...]}}
290
+ ```
291
+
292
+ ### Why This Flow?
293
+
294
+ This multi-step flow might seem complex, but each layer has a purpose:
295
+
296
+ **Resources**: Know the domain and GraphQL schema structure
297
+ **Client**: Manages authentication and error handling
298
+ **Request**: Abstracts HTTP details
299
+ **Response**: Provides a consistent interface to raw HTTP responses
300
+
301
+ This separation allows each component to change independently. For example:
302
+ - Resources can be added without changing the client
303
+ - The HTTP library could be swapped without touching resources
304
+ - Error handling logic is centralized in one place
305
+
306
+ ## Error Handling Architecture
307
+
308
+ monday.com's API returns errors in multiple ways, and the gem's error handling architecture deals with this complexity.
309
+
310
+ ### The Problem
311
+
312
+ Errors can appear as:
313
+
314
+ 1. **HTTP status codes**: 401, 403, 404, 429, 500
315
+ 2. **Error codes in the response body**: `ComplexityException`, `InvalidBoardIdException`, etc.
316
+ 3. **GraphQL errors array**: `{"errors": [{"message": "...", "extensions": {"code": "..."}}]}`
317
+
318
+ Additionally, monday.com returns HTTP 200 for some errors (like malformed GraphQL queries), with error details in the response body.
319
+
320
+ ### The Solution
321
+
322
+ The gem uses a two-tier error detection system:
323
+
324
+ **Tier 1: HTTP Status Codes** (`Client#default_exception`)
325
+
326
+ ```ruby
327
+ def default_exception(response)
328
+ Util.status_code_exceptions_mapping(response.status).new(response: response)
329
+ end
330
+ ```
331
+
332
+ Maps status codes to exception classes:
333
+ - 401/403 → `AuthorizationError`
334
+ - 404 → `ResourceNotFoundError`
335
+ - 429 → `RateLimitError`
336
+ - 500 → `InternalServerError`
337
+
338
+ **Tier 2: Response Body Error Codes** (`Client#response_exception`)
339
+
340
+ ```ruby
341
+ def response_exception(response)
342
+ error_code = response_error_code(response)
343
+ exception_klass, code = Util.response_error_exceptions_mapping(error_code)
344
+ exception_klass.new(message: error_code, response: response, code: code)
345
+ end
346
+ ```
347
+
348
+ Extracts error codes from multiple possible locations:
349
+ - `response.body["error_code"]`
350
+ - `response.body.dig("errors", 0, "extensions", "code")`
351
+ - `response.body.dig("errors", 0, "extensions", "error_code")`
352
+
353
+ Then maps them to specific exceptions like `ComplexityError`, `InvalidBoardIdException`, etc.
354
+
355
+ ### Success Detection
356
+
357
+ The `Response#success?` method combines both checks:
358
+
359
+ ```ruby
360
+ def success?
361
+ (200..299).cover?(status) && !errors?
362
+ end
363
+
364
+ def errors?
365
+ (parse_body.keys & ERROR_OBJECT_KEYS).any?
366
+ end
367
+ ```
368
+
369
+ A response is only successful if:
370
+ 1. HTTP status is 2xx
371
+ 2. Response body doesn't contain `errors`, `error_code`, or `error_message` keys
372
+
373
+ This handles monday.com's quirk of returning 200 for GraphQL errors.
374
+
375
+ ### Exception Hierarchy
376
+
377
+ All exceptions inherit from `Monday::Error`, allowing users to rescue all monday.com errors with a single rescue clause:
378
+
379
+ ```ruby
380
+ begin
381
+ client.board.query(...)
382
+ rescue Monday::Error => e
383
+ puts "API error: #{e.message}"
384
+ puts "Response: #{e.response.body}"
385
+ end
386
+ ```
387
+
388
+ Specific exceptions allow targeted error handling:
389
+
390
+ ```ruby
391
+ rescue Monday::RateLimitError => e
392
+ sleep(60)
393
+ retry
394
+ rescue Monday::AuthorizationError => e
395
+ refresh_token
396
+ retry
397
+ ```
398
+
399
+ ### Why This Complexity?
400
+
401
+ The error handling is complex because it must handle:
402
+ - monday.com's evolving API (new error codes appear)
403
+ - Multiple error code formats (monday.com changed formats over time)
404
+ - HTTP vs. application-level errors
405
+ - User expectations (want specific exception types, but also a catch-all)
406
+
407
+ The architecture provides specificity when needed, but gracefully degrades to generic `Monday::Error` for unknown error codes.
408
+
409
+ ## Alternative Approaches
410
+
411
+ To understand why monday_ruby is designed this way, it's useful to consider alternatives.
412
+
413
+ ### Alternative 1: Direct GraphQL Client
414
+
415
+ The gem could expose a thin wrapper around a GraphQL client:
416
+
417
+ ```ruby
418
+ client = Monday::Client.new(token: "...")
419
+ query = <<~GRAPHQL
420
+ query {
421
+ boards(ids: [123]) {
422
+ id
423
+ name
424
+ }
425
+ }
426
+ GRAPHQL
427
+
428
+ response = client.execute(query)
429
+ ```
430
+
431
+ **Trade-offs:**
432
+ - ✅ Maximum flexibility - can use any GraphQL feature
433
+ - ✅ Familiar to GraphQL users
434
+ - ❌ Requires GraphQL knowledge
435
+ - ❌ Verbose for common operations
436
+ - ❌ No Ruby-idiomatic interface
437
+ - ❌ Error handling less structured
438
+
439
+ This approach is better for users who already know GraphQL and want full control. monday_ruby prioritizes ease of use for Ruby developers who may not know GraphQL.
440
+
441
+ ### Alternative 2: REST-Style Interface
442
+
443
+ The gem could mimic REST APIs:
444
+
445
+ ```ruby
446
+ client = Monday::Client.new(token: "...")
447
+ boards = client.get("/boards", params: {ids: [123]})
448
+ item = client.post("/items", body: {name: "New Item", board_id: 456})
449
+ ```
450
+
451
+ **Trade-offs:**
452
+ - ✅ Familiar to REST API users
453
+ - ✅ Simple HTTP-style interface
454
+ - ❌ Doesn't match monday.com's GraphQL nature
455
+ - ❌ Hard to represent nested field selection
456
+ - ❌ Inefficient data fetching (over-fetching or under-fetching)
457
+
458
+ This works well for REST APIs but fights against GraphQL's strength of precise data fetching.
459
+
460
+ ### Alternative 3: Active Record Pattern
461
+
462
+ The gem could model monday.com resources as ActiveRecord-style objects:
463
+
464
+ ```ruby
465
+ board = Monday::Board.find(123)
466
+ board.name = "New Name"
467
+ board.save
468
+
469
+ item = board.items.create(name: "New Item")
470
+ ```
471
+
472
+ **Trade-offs:**
473
+ - ✅ Familiar to Rails developers
474
+ - ✅ Object-oriented interface
475
+ - ❌ Implies more than the API provides (no change tracking, associations, etc.)
476
+ - ❌ Requires maintaining object state
477
+ - ❌ Hides GraphQL's explicit field selection
478
+ - ❌ More complex implementation
479
+
480
+ While appealing, this pattern doesn't fit monday.com's API model. The API is query-based, not CRUD-based, and doesn't support partial updates or lazy loading the way ActiveRecord expects.
481
+
482
+ ### The Chosen Approach
483
+
484
+ monday_ruby's client-resource pattern strikes a balance:
485
+
486
+ - More accessible than raw GraphQL (Ruby methods vs. query strings)
487
+ - More powerful than REST-style (explicit field selection, complex queries)
488
+ - Simpler than ActiveRecord (stateless, explicit requests)
489
+ - Flexible enough to support monday.com's evolving API
490
+
491
+ The design prioritizes:
492
+ 1. **Ease of use** for developers new to monday.com or GraphQL
493
+ 2. **Explicit behavior** over magic (you call methods, get responses)
494
+ 3. **Extensibility** (easy to add resources and methods)
495
+ 4. **Transparency** (clear what queries are being executed)
496
+
497
+ ## Conclusion
498
+
499
+ The monday_ruby architecture emerged from practical constraints and design goals:
500
+
501
+ - **Client-Resource Pattern**: Organizes code around monday.com's domain model
502
+ - **Dynamic Resource Initialization**: Enables automatic registration and consistent naming
503
+ - **String-Based Query Building**: Balances simplicity with flexibility
504
+ - **Layered Request Flow**: Separates concerns while maintaining clarity
505
+ - **Robust Error Handling**: Deals with monday.com's varied error formats
506
+
507
+ These decisions create a gem that feels natural to Ruby developers while effectively wrapping a complex GraphQL API. The architecture isn't the only way to solve this problem, but it's well-suited to the gem's goals of accessibility, maintainability, and power.