Skip to content

Commit fd2f1f7

Browse files
authored
fix: show a working save_record override for HTTP resources (#659)
* fix: show a working save_record override for HTTP resources The example built its request from a fictional client and a relative path. Copied as written that request has no host — HTTParty hands it to Net::HTTP, which fails on a nil address (AVO-1745, seen in production on avodemo). It now builds from the resource's own endpoint and evaluates its headers, and both pages state that the endpoint must be absolute. Also documents that headers reach create/update/destroy, which they now do. * Bound the example's hand-rolled request with a timeout * Encode the example's id like the client does
1 parent 2d2dc9d commit fd2f1f7

2 files changed

Lines changed: 24 additions & 7 deletions

File tree

docs/4.0/http-resource-api.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ self.http_adapter = {
5151
Unlike the other adapter options, `endpoint` is used verbatim — it does not accept a proc and is not evaluated through `Avo::ExecutionContext`.
5252
:::
5353

54+
:::warning Must be absolute
55+
The endpoint needs a host. A missing, blank, or relative value (`nil`, `""`, `"/users"`) raises [`Avo::HttpError`](./http-resource.html#handle-api-errors) before any request is sent, so the controller shows a flash message instead of failing inside `Net::HTTP` on a nil address.
56+
:::
57+
5458
:::info Request behavior
5559
Index requests always carry `page` and `per_page` query parameters — the names are not configurable. Use [`query_params`](#query_params) to send additional parameters. Requests time out after 10 seconds and raise an error.
5660
:::

docs/4.0/http-resource.md

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ self.http_adapter = {
8787

8888
If the headers must be computed at request time — rotating tokens, per-user credentials — pass a proc returning the hash instead.
8989

90+
Every request carries them: index, show, and count, as well as the create, update, and destroy requests the controller makes.
91+
9092
## Map sorting and filtering to query params
9193

9294
If you want Avo's sorting UI (or any UI state) forwarded to the API, build the query string with [`query_params`](./http-resource-api.html#query_params). The proc has access to controller `params`, and its result is merged into the request's query string:
@@ -136,7 +138,7 @@ The controller rescues the exception and displays the message as a flash error i
136138

137139
## Customize create, update, and destroy
138140

139-
Out of the box, the HTTP controller persists changes through the resource's client — `POST` to the endpoint on create, `PATCH` to `endpoint/:id` on update, and `DELETE` to `endpoint/:id` on destroy. The default implementation looks like this:
141+
Out of the box, the HTTP controller persists changes through the resource's client — `POST` to the endpoint on create, `PATCH` to `endpoint/:id` on update, and `DELETE` to `endpoint/:id` on destroy, each carrying the resource's [`headers`](#send-authentication-headers). The default implementation looks like this:
140142

141143
```ruby
142144
def save_record
@@ -162,21 +164,32 @@ If your API needs different paths, extra parameters, or conditional logic, overr
162164
# app/controllers/avo/authors_controller.rb
163165
class Avo::AuthorsController < Avo::Core::Controllers::Http
164166
def save_record
165-
auth_headers = {
166-
"Authorization" => "Bearer #{ENV.fetch("API_KEY")}"
167-
}
168-
167+
# Build the URL from the resource's own endpoint, and evaluate its headers
168+
# the same way the client does — `headers` may be a proc.
169+
endpoint = resource.endpoint
170+
headers = Avo::ExecutionContext.new(target: resource.headers).handle
171+
body = { author: @record.as_json }
172+
173+
# HTTParty has no default timeout — the client sets one on its own class, so
174+
# a hand-rolled request has to bound itself.
169175
response = if action_name == "create"
170-
MyCustomApi.post("/authors", body: @record.as_json, headers: auth_headers)
176+
HTTParty.post(endpoint, body: body, headers: headers, timeout: 10)
171177
else
172-
MyCustomApi.patch("/authors/#{@record.id}", body: @record.as_json, headers: auth_headers)
178+
# `to_param` + encoding, like the client: a raw id breaks the moment a
179+
# resource obfuscates it (see `model_class_eval`) or it contains a
180+
# reserved character.
181+
HTTParty.patch("#{endpoint}/#{ERB::Util.url_encode(@record.to_param)}", body: body, headers: headers, timeout: 10)
173182
end
174183

175184
response.success?
176185
end
177186
end
178187
```
179188

189+
:::warning Request an absolute URL
190+
Avo configures no `base_uri`, so a relative path — `HTTParty.post("/authors", ...)` — has no host to connect to and fails inside `Net::HTTP` on a nil address, long after the form was submitted. Always request the resource's full `endpoint`.
191+
:::
192+
180193
## Debug console
181194

182195
HTTP Resources ship with an interactive debug console for inspecting exactly what your resource sends and receives. Visit `<avo_root>/http-resource/debug` (e.g. `/avo/http-resource/debug`), pick a resource and an action (`index`, `show`, `count`, `create`, `update`, or `delete`), and fire the request.

0 commit comments

Comments
 (0)