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.
- checksums.yaml +4 -4
- data/.env +1 -1
- data/.rubocop.yml +2 -1
- data/CHANGELOG.md +14 -0
- data/CONTRIBUTING.md +104 -0
- data/README.md +146 -142
- data/docs/.vitepress/config.mjs +255 -0
- data/docs/.vitepress/theme/index.js +4 -0
- data/docs/.vitepress/theme/style.css +43 -0
- data/docs/README.md +80 -0
- data/docs/explanation/architecture.md +507 -0
- data/docs/explanation/best-practices/errors.md +478 -0
- data/docs/explanation/best-practices/performance.md +1084 -0
- data/docs/explanation/best-practices/rate-limiting.md +630 -0
- data/docs/explanation/best-practices/testing.md +820 -0
- data/docs/explanation/column-values.md +857 -0
- data/docs/explanation/design.md +795 -0
- data/docs/explanation/graphql.md +356 -0
- data/docs/explanation/migration/v1.md +808 -0
- data/docs/explanation/pagination.md +447 -0
- data/docs/guides/advanced/batch.md +1274 -0
- data/docs/guides/advanced/complex-queries.md +1114 -0
- data/docs/guides/advanced/errors.md +818 -0
- data/docs/guides/advanced/pagination.md +934 -0
- data/docs/guides/advanced/rate-limiting.md +981 -0
- data/docs/guides/authentication.md +286 -0
- data/docs/guides/boards/create.md +386 -0
- data/docs/guides/boards/delete.md +405 -0
- data/docs/guides/boards/duplicate.md +511 -0
- data/docs/guides/boards/query.md +530 -0
- data/docs/guides/boards/update.md +453 -0
- data/docs/guides/columns/create.md +452 -0
- data/docs/guides/columns/metadata.md +492 -0
- data/docs/guides/columns/query.md +455 -0
- data/docs/guides/columns/update-multiple.md +459 -0
- data/docs/guides/columns/update-values.md +509 -0
- data/docs/guides/files/add-to-column.md +40 -0
- data/docs/guides/files/add-to-update.md +37 -0
- data/docs/guides/files/clear-column.md +33 -0
- data/docs/guides/first-request.md +285 -0
- data/docs/guides/folders/manage.md +750 -0
- data/docs/guides/groups/items.md +626 -0
- data/docs/guides/groups/manage.md +501 -0
- data/docs/guides/installation.md +169 -0
- data/docs/guides/items/create.md +493 -0
- data/docs/guides/items/delete.md +514 -0
- data/docs/guides/items/query.md +605 -0
- data/docs/guides/items/subitems.md +483 -0
- data/docs/guides/items/update.md +699 -0
- data/docs/guides/updates/manage.md +619 -0
- data/docs/guides/use-cases/dashboard.md +1421 -0
- data/docs/guides/use-cases/import.md +1962 -0
- data/docs/guides/use-cases/task-management.md +1381 -0
- data/docs/guides/workspaces/manage.md +502 -0
- data/docs/index.md +69 -0
- data/docs/package-lock.json +2468 -0
- data/docs/package.json +13 -0
- data/docs/reference/client.md +540 -0
- data/docs/reference/configuration.md +586 -0
- data/docs/reference/errors.md +693 -0
- data/docs/reference/resources/account.md +208 -0
- data/docs/reference/resources/activity-log.md +369 -0
- data/docs/reference/resources/board-view.md +359 -0
- data/docs/reference/resources/board.md +393 -0
- data/docs/reference/resources/column.md +543 -0
- data/docs/reference/resources/file.md +236 -0
- data/docs/reference/resources/folder.md +386 -0
- data/docs/reference/resources/group.md +507 -0
- data/docs/reference/resources/item.md +348 -0
- data/docs/reference/resources/subitem.md +267 -0
- data/docs/reference/resources/update.md +259 -0
- data/docs/reference/resources/workspace.md +213 -0
- data/docs/reference/response.md +560 -0
- data/docs/tutorial/first-integration.md +713 -0
- data/lib/monday/client.rb +24 -0
- data/lib/monday/configuration.rb +5 -0
- data/lib/monday/request.rb +15 -0
- data/lib/monday/resources/base.rb +4 -0
- data/lib/monday/resources/file.rb +56 -0
- data/lib/monday/util.rb +1 -0
- data/lib/monday/version.rb +1 -1
- metadata +87 -4
|
@@ -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.
|