toc_doc 1.6.0 → 1.7.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/CHANGELOG.md +18 -0
- data/README.md +2 -1
- data/TODO.md +9 -8
- data/lib/toc_doc/core/configurable.rb +3 -0
- data/lib/toc_doc/core/connection.rb +17 -1
- data/lib/toc_doc/core/default.rb +37 -17
- data/lib/toc_doc/core/version.rb +1 -1
- data/lib/toc_doc/http/middleware/logging.rb +85 -0
- data/lib/toc_doc/models/availability/collection.rb +4 -3
- data/lib/toc_doc/models/availability.rb +14 -3
- data/lib/toc_doc/models/booking_info.rb +37 -7
- data/lib/toc_doc/models/place.rb +2 -0
- data/lib/toc_doc/models/profile/organization.rb +3 -1
- data/lib/toc_doc/models/profile.rb +22 -0
- data/lib/toc_doc/models/resource.rb +77 -8
- data/lib/toc_doc/models/speciality.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a839823e2a82c7dd112617d6b969f275d7d88826e58f53b9ec9a5265701f277e
|
|
4
|
+
data.tar.gz: 6a3c3d24b598f0d2f85b2c0bd5f8faad939eeba847578692b072f0070fb5d3bb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2580cf022a39c4f8e8d73c3b795927bf249ed3d4c9e6b36d051d326680d071ec4569e6f8da07143270584c7f65519f9df9c9efe824c7b623959d7be73f10f27f
|
|
7
|
+
data.tar.gz: 19ab224e5729cb04789e7739d1c0a6d9de16b02b2e32d850c1bc6fe3024c58b2c01d4ac9c1b7ffbfed45fb2d7e3498c537d169f9c77733b54d01da009b692047
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.7.0] - 2026-04-04
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`TocDoc::Middleware::Logging`** — new Faraday middleware that logs every outgoing request URL and the response status; plugged in automatically between the retry and raise-error layers; enabled via the new `logger` config key (accepts any Logger-compatible object; `nil` disables logging)
|
|
8
|
+
- **`logger` config key** — module-level and per-client option; also available as a `TocDoc::Default::LOGGER` constant (defaults to `nil`); each client instance carries its own logger, so multiple clients can log to different destinations simultaneously
|
|
9
|
+
- **`TocDoc::Resource#attribute_names`** — instance method returning the sorted list of attribute keys present on a resource instance
|
|
10
|
+
- **`TocDoc::Resource` singleton method definition on first access** — reader methods are now defined as true singleton methods on the instance the first time an attribute is accessed, eliminating repeated `respond_to?` / `method_missing` dispatch on subsequent calls
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **`TocDoc::Resource#to_h`** — now performs a deep conversion: nested `Resource` objects and arrays of `Resource` objects are recursively converted; previously returned a shallow hash with raw attribute values
|
|
15
|
+
- **`TocDoc::Resource#to_json`** — delegates to the new deep `to_h`, so JSON output is fully recursive
|
|
16
|
+
- **`TocDoc::BookingInfo#to_h`** and **`#to_json`** — override `Resource`'s implementation to include all typed sub-objects (`profile`, `specialities`, `visit_motives`, `agendas`, `places`, `practitioners`) as deeply converted hashes
|
|
17
|
+
- **`TocDoc::BookingInfo#agendas`** — visit-motive resolution changed from O(n*m) nested iteration to O(n+m) hash lookup; no change to the public interface
|
|
18
|
+
- **`TocDoc::Availability::Collection#filtered_entries`** — result is now memoized; cache is invalidated automatically when `#merge_page!` appends a new page, preventing redundant filtering on repeated `#each` / `#to_a` calls
|
|
19
|
+
- **`#inspect`** for `TocDoc::Availability`, `TocDoc::Place`, `TocDoc::Speciality`, and `TocDoc::Profile::Organization`** — `main_attrs` declared on each class so inspect output stays concise; `TocDoc::Profile` and `TocDoc::Resource` inspect also improved
|
|
20
|
+
|
|
3
21
|
## [1.6.0] - 2026-03-30
|
|
4
22
|
|
|
5
23
|
### Added
|
data/README.md
CHANGED
|
@@ -140,11 +140,12 @@ client.get('/availabilities.json', query: { visit_motive_ids: '123', agenda_ids:
|
|
|
140
140
|
| Option | Default | Description |
|
|
141
141
|
|---|---|---|
|
|
142
142
|
| `api_endpoint` | `https://www.doctolib.fr` | Base URL. Change to `.de` / `.it` for other countries. |
|
|
143
|
-
| `user_agent` | `TocDoc Ruby Gem 1.
|
|
143
|
+
| `user_agent` | `TocDoc Ruby Gem 1.7.0` | `User-Agent` header sent with every request. |
|
|
144
144
|
| `default_media_type` | `application/json` | `Accept` and `Content-Type` headers. |
|
|
145
145
|
| `per_page` | `15` | Default number of availability dates per request (capped at `15`). Emits a warning if the value exceeds the cap. |
|
|
146
146
|
| `connect_timeout` | `5` | TCP connect timeout in seconds, passed to Faraday as `open_timeout`. Override via `TOCDOC_CONNECT_TIMEOUT`. |
|
|
147
147
|
| `read_timeout` | `10` | Response read timeout in seconds, passed to Faraday as `timeout`. Override via `TOCDOC_READ_TIMEOUT`. |
|
|
148
|
+
| `logger` | `nil` | Logger-compatible object (e.g. `Logger.new($stdout)`). When set, `TocDoc::Middleware::Logging` logs each request URL and response status. `nil` disables logging. |
|
|
148
149
|
| `middleware` | Retry + RaiseError + JSON + adapter | Full Faraday middleware stack. Override to customise completely. |
|
|
149
150
|
| `connection_options` | `{}` | Options passed directly to `Faraday.new`. |
|
|
150
151
|
|
data/TODO.md
CHANGED
|
@@ -3,14 +3,6 @@
|
|
|
3
3
|
[POTENTIAL_ENDPOINTS][POTENTIAL_ENDPOINTS.md]
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
## 1.7 — DevX
|
|
7
|
-
|
|
8
|
-
- [ ] Logging middleware (`:logger` config key)
|
|
9
|
-
- [ ] Resource: `define_singleton_method` on first access + `#attribute_names`
|
|
10
|
-
- [ ] Deep `to_h` and `to_json` on `Resource` and `BookingInfo`
|
|
11
|
-
- [ ] `Collection#filtered_entries` memoization
|
|
12
|
-
- [ ] `BookingInfo#agendas` O(n*m) → hash lookup
|
|
13
|
-
|
|
14
6
|
## 1.8 — HTTP Layer Robustness
|
|
15
7
|
|
|
16
8
|
- [ ] Configurable availability pagination depth + `Collection#more?` / `#fetch_next_page`
|
|
@@ -41,6 +33,15 @@
|
|
|
41
33
|
|
|
42
34
|
# DONE & RELEASED
|
|
43
35
|
|
|
36
|
+
## 1.7
|
|
37
|
+
|
|
38
|
+
- [x] Logging middleware (`:logger` config key)
|
|
39
|
+
- [x] Resource: `define_singleton_method` on first access + `#attribute_names`
|
|
40
|
+
- [x] Deep `to_h` and `to_json` on `Resource` and `BookingInfo`
|
|
41
|
+
- [x] `Collection#filtered_entries` memoization
|
|
42
|
+
- [x] `BookingInfo#agendas` O(n*m) → hash lookup
|
|
43
|
+
- [x] Improve `#inspect` for `Availability`, `Place`, `Speciality`, `Profile::Organization`
|
|
44
|
+
|
|
44
45
|
## 1.6
|
|
45
46
|
|
|
46
47
|
### Default connection timeouts
|
|
@@ -31,6 +31,7 @@ module TocDoc
|
|
|
31
31
|
per_page
|
|
32
32
|
connect_timeout
|
|
33
33
|
read_timeout
|
|
34
|
+
logger
|
|
34
35
|
].freeze
|
|
35
36
|
|
|
36
37
|
# @!attribute [rw] api_endpoint
|
|
@@ -47,6 +48,8 @@ module TocDoc
|
|
|
47
48
|
# @return [Integer] TCP connect timeout in seconds
|
|
48
49
|
# @!attribute [rw] read_timeout
|
|
49
50
|
# @return [Integer] read (response) timeout in seconds
|
|
51
|
+
# @!attribute [rw] logger
|
|
52
|
+
# @return [Logger, :stdout, nil] logger for HTTP request logging; +nil+ disables logging
|
|
50
53
|
attr_accessor(*VALID_CONFIG_KEYS)
|
|
51
54
|
|
|
52
55
|
# Set the number of results per page, clamped to
|
|
@@ -92,11 +92,27 @@ module TocDoc
|
|
|
92
92
|
# @return [Hash] merged Faraday connection options
|
|
93
93
|
def faraday_options
|
|
94
94
|
opts = connection_options.dup
|
|
95
|
-
opts[:builder] =
|
|
95
|
+
opts[:builder] = effective_middleware
|
|
96
96
|
opts[:request] = { timeout: read_timeout, open_timeout: connect_timeout }
|
|
97
97
|
opts
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
+
# Returns the appropriate middleware stack for this client.
|
|
101
|
+
#
|
|
102
|
+
# When a logger is configured, builds a fresh stack with the logger injected
|
|
103
|
+
# so that the shared memoized default stack is never mutated.
|
|
104
|
+
# Falls back to the memoized {TocDoc::Default.middleware} when no logger is
|
|
105
|
+
# set.
|
|
106
|
+
#
|
|
107
|
+
# @return [Faraday::RackBuilder]
|
|
108
|
+
def effective_middleware
|
|
109
|
+
if logger
|
|
110
|
+
TocDoc::Default.build_middleware(logger: logger)
|
|
111
|
+
else
|
|
112
|
+
middleware
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
100
116
|
# Sets default HTTP headers on a Faraday connection.
|
|
101
117
|
#
|
|
102
118
|
# @param conn [Faraday::Connection]
|
data/lib/toc_doc/core/default.rb
CHANGED
|
@@ -4,6 +4,7 @@ require 'faraday'
|
|
|
4
4
|
require 'faraday/retry'
|
|
5
5
|
|
|
6
6
|
require 'toc_doc/http/middleware/raise_error'
|
|
7
|
+
require 'toc_doc/http/middleware/logging'
|
|
7
8
|
|
|
8
9
|
module TocDoc
|
|
9
10
|
# Provides sensible default values for every configurable option.
|
|
@@ -43,16 +44,9 @@ module TocDoc
|
|
|
43
44
|
#
|
|
44
45
|
# @return [Hash{Symbol => Object}]
|
|
45
46
|
def options
|
|
46
|
-
{
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
default_media_type: default_media_type,
|
|
50
|
-
per_page: per_page,
|
|
51
|
-
middleware: middleware,
|
|
52
|
-
connection_options: connection_options,
|
|
53
|
-
connect_timeout: connect_timeout,
|
|
54
|
-
read_timeout: read_timeout
|
|
55
|
-
}
|
|
47
|
+
{ api_endpoint:, user_agent:, default_media_type:, per_page:,
|
|
48
|
+
middleware:, connection_options:, connect_timeout:, read_timeout:,
|
|
49
|
+
logger: nil }
|
|
56
50
|
end
|
|
57
51
|
|
|
58
52
|
# The base API endpoint URL.
|
|
@@ -97,7 +91,7 @@ module TocDoc
|
|
|
97
91
|
PER_PAGE
|
|
98
92
|
end
|
|
99
93
|
|
|
100
|
-
# The default Faraday middleware stack.
|
|
94
|
+
# The default (memoized) Faraday middleware stack, built without a logger.
|
|
101
95
|
#
|
|
102
96
|
# Stack order (outermost first): RaiseError, retry, JSON parsing, adapter.
|
|
103
97
|
# RaiseError is outermost so it wraps retry and maps the final response or
|
|
@@ -108,6 +102,29 @@ module TocDoc
|
|
|
108
102
|
@middleware ||= build_middleware
|
|
109
103
|
end
|
|
110
104
|
|
|
105
|
+
# Builds a Faraday middleware stack, optionally injecting a logger.
|
|
106
|
+
#
|
|
107
|
+
# When +logger+ is non-nil, {TocDoc::Middleware::Logging} is inserted
|
|
108
|
+
# between {TocDoc::Middleware::RaiseError} and the retry middleware so
|
|
109
|
+
# each logical request is logged exactly once after all retries are
|
|
110
|
+
# exhausted.
|
|
111
|
+
#
|
|
112
|
+
# Accepts +:stdout+ as a shorthand that writes to +$stdout+.
|
|
113
|
+
#
|
|
114
|
+
# @param logger [Logger, :stdout, nil] the logger to inject; +nil+ omits
|
|
115
|
+
# the logging middleware entirely
|
|
116
|
+
# @return [Faraday::RackBuilder]
|
|
117
|
+
def build_middleware(logger: nil)
|
|
118
|
+
resolved = resolve_logger(logger)
|
|
119
|
+
Faraday::RackBuilder.new do |builder|
|
|
120
|
+
builder.use TocDoc::Middleware::RaiseError
|
|
121
|
+
builder.use TocDoc::Middleware::Logging, logger: resolved if resolved
|
|
122
|
+
builder.request :retry, retry_options
|
|
123
|
+
builder.response :json, content_type: /\bjson$/
|
|
124
|
+
builder.adapter Faraday.default_adapter
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
111
128
|
# Default Faraday connection options (empty by default).
|
|
112
129
|
#
|
|
113
130
|
# @return [Hash]
|
|
@@ -153,12 +170,15 @@ module TocDoc
|
|
|
153
170
|
|
|
154
171
|
private
|
|
155
172
|
|
|
156
|
-
def
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
173
|
+
def resolve_logger(logger)
|
|
174
|
+
case logger
|
|
175
|
+
when :stdout
|
|
176
|
+
require 'logger'
|
|
177
|
+
Logger.new($stdout, progname: 'TocDoc')
|
|
178
|
+
when nil, false
|
|
179
|
+
nil
|
|
180
|
+
else
|
|
181
|
+
logger
|
|
162
182
|
end
|
|
163
183
|
end
|
|
164
184
|
|
data/lib/toc_doc/core/version.rb
CHANGED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'faraday'
|
|
4
|
+
|
|
5
|
+
module TocDoc
|
|
6
|
+
module Middleware
|
|
7
|
+
# Faraday middleware that logs the outcome of every HTTP request.
|
|
8
|
+
#
|
|
9
|
+
# Placed between {RaiseError} (outermost) and the retry middleware so that
|
|
10
|
+
# each logical request is logged exactly once — after all retry attempts have
|
|
11
|
+
# been exhausted — rather than once per attempt.
|
|
12
|
+
#
|
|
13
|
+
# Stack order (outermost first):
|
|
14
|
+
# RaiseError > Logging > retry > JSON parse > adapter
|
|
15
|
+
#
|
|
16
|
+
# Log format:
|
|
17
|
+
# TocDoc: GET /path.json -> 200 (42ms) # success → logger.info
|
|
18
|
+
# TocDoc: GET /path.json -> error: Msg (42ms) # failure → logger.warn
|
|
19
|
+
#
|
|
20
|
+
# When no logger is provided the middleware is a no-op.
|
|
21
|
+
#
|
|
22
|
+
# @example Attach a custom logger
|
|
23
|
+
# TocDoc.configure { |c| c.logger = Logger.new($stdout) }
|
|
24
|
+
#
|
|
25
|
+
# @example Use the :stdout shorthand
|
|
26
|
+
# TocDoc.configure { |c| c.logger = :stdout }
|
|
27
|
+
class Logging < Faraday::Middleware
|
|
28
|
+
# @param app [#call] the next middleware in the stack
|
|
29
|
+
# @param logger [Logger, nil] the logger to write to; +nil+ disables logging
|
|
30
|
+
def initialize(app, logger: nil)
|
|
31
|
+
super(app)
|
|
32
|
+
@logger = logger
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Calls the next middleware, measures elapsed time, and logs the outcome.
|
|
36
|
+
#
|
|
37
|
+
# @param env [Faraday::Env] the Faraday request environment
|
|
38
|
+
# @return [Faraday::Response] the response on success
|
|
39
|
+
# @raise [StandardError] re-raises any exception after logging it
|
|
40
|
+
def call(env)
|
|
41
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
42
|
+
response = @app.call(env)
|
|
43
|
+
duration = duration_ms(start)
|
|
44
|
+
log_request(env, response.status, duration)
|
|
45
|
+
response
|
|
46
|
+
rescue StandardError => e
|
|
47
|
+
duration = duration_ms(start)
|
|
48
|
+
log_error(env, e, duration)
|
|
49
|
+
raise
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
# @param start [Float] monotonic clock time at request start
|
|
55
|
+
# @return [Integer] elapsed time in milliseconds
|
|
56
|
+
def duration_ms(start)
|
|
57
|
+
((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Logs a successful HTTP response at +info+ level.
|
|
61
|
+
#
|
|
62
|
+
# @param env [Faraday::Env] the Faraday request environment
|
|
63
|
+
# @param status [Integer] the HTTP response status code
|
|
64
|
+
# @param duration [Integer] elapsed time in milliseconds
|
|
65
|
+
# @return [void]
|
|
66
|
+
def log_request(env, status, duration)
|
|
67
|
+
return unless @logger
|
|
68
|
+
|
|
69
|
+
@logger.info("TocDoc: #{env.method.to_s.upcase} #{env.url.path} -> #{status} (#{duration}ms)")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Logs a failed request at +warn+ level.
|
|
73
|
+
#
|
|
74
|
+
# @param env [Faraday::Env] the Faraday request environment
|
|
75
|
+
# @param error [StandardError] the exception that was raised
|
|
76
|
+
# @param duration [Integer] elapsed time in milliseconds
|
|
77
|
+
# @return [void]
|
|
78
|
+
def log_error(env, error, duration)
|
|
79
|
+
return unless @logger
|
|
80
|
+
|
|
81
|
+
@logger.warn("TocDoc: #{env.method.to_s.upcase} #{env.url.path} -> error: #{error.message} (#{duration}ms)")
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -88,15 +88,16 @@ module TocDoc
|
|
|
88
88
|
def merge_page!(page_data)
|
|
89
89
|
@data['availabilities'] = @data.fetch('availabilities', []) + page_data.fetch('availabilities', [])
|
|
90
90
|
@data['total'] = @data.fetch('total', 0) + page_data.fetch('total', 0)
|
|
91
|
+
@filtered_entries = nil
|
|
91
92
|
self
|
|
92
93
|
end
|
|
93
94
|
|
|
94
95
|
private
|
|
95
96
|
|
|
96
97
|
def filtered_entries
|
|
97
|
-
Array(@data['availabilities'])
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
@filtered_entries ||= Array(@data['availabilities'])
|
|
99
|
+
.select { |entry| Array(entry['slots']).any? }
|
|
100
|
+
.map { |entry| TocDoc::Availability.new(entry) }
|
|
100
101
|
end
|
|
101
102
|
end
|
|
102
103
|
end
|
|
@@ -9,13 +9,19 @@ module TocDoc
|
|
|
9
9
|
# @example
|
|
10
10
|
# avail = TocDoc::Availability.new('date' => '2026-02-28', 'slots' => ['2026-02-28T10:00:00.000+01:00'])
|
|
11
11
|
# avail.date #=> #<Date: 2026-02-28>
|
|
12
|
-
# avail
|
|
12
|
+
# avail['date'] #=> "2026-02-28"
|
|
13
13
|
# avail.slots #=> [#<DateTime: 2026-02-28T10:00:00.000+01:00>]
|
|
14
|
-
# avail
|
|
14
|
+
# avail['slots'] #=> ["2026-02-28T10:00:00.000+01:00"]
|
|
15
15
|
class Availability < Resource
|
|
16
|
+
main_attrs :date, :slots
|
|
17
|
+
|
|
16
18
|
extend TocDoc::UriUtils
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
# @return [Date, nil] the parsed availability date
|
|
21
|
+
attr_reader :date
|
|
22
|
+
|
|
23
|
+
# @return [Array<DateTime>] the parsed slot datetimes
|
|
24
|
+
attr_reader :slots
|
|
19
25
|
|
|
20
26
|
# API path for the availabilities endpoint.
|
|
21
27
|
# @return [String]
|
|
@@ -96,6 +102,11 @@ module TocDoc
|
|
|
96
102
|
|
|
97
103
|
private
|
|
98
104
|
|
|
105
|
+
# Extracts the +date+ and +slots+ keys from the raw attribute hash,
|
|
106
|
+
# providing an empty array fallback for missing slots.
|
|
107
|
+
#
|
|
108
|
+
# @param attrs [Hash{String => Object}] normalized attribute hash
|
|
109
|
+
# @return [Hash{String => Object}]
|
|
99
110
|
def build_raw(attrs)
|
|
100
111
|
{
|
|
101
112
|
'date' => attrs['date'],
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'json'
|
|
3
4
|
require 'toc_doc/models/profile'
|
|
4
5
|
require 'toc_doc/models/speciality'
|
|
5
6
|
require 'toc_doc/models/place'
|
|
@@ -78,14 +79,19 @@ module TocDoc
|
|
|
78
79
|
|
|
79
80
|
# All agendas for this booking context.
|
|
80
81
|
#
|
|
82
|
+
# Visit motives are resolved via a hash index keyed by ID, so lookup is O(1)
|
|
83
|
+
# per motive rather than O(n) per agenda. Unknown visit_motive_ids are
|
|
84
|
+
# silently dropped.
|
|
85
|
+
#
|
|
81
86
|
# @return [Array<TocDoc::Agenda>]
|
|
82
87
|
def agendas
|
|
83
|
-
@agendas ||=
|
|
84
|
-
|
|
85
|
-
agenda_attrs['visit_motive_ids'].include?(vm.id)
|
|
86
|
-
end
|
|
88
|
+
@agendas ||= begin
|
|
89
|
+
vm_index = visit_motives.to_h { |vm| [vm.id, vm] }
|
|
87
90
|
|
|
88
|
-
|
|
91
|
+
Array(@data['agendas']).map do |agenda_attrs|
|
|
92
|
+
agenda_visit_motives = Array(agenda_attrs['visit_motive_ids']).filter_map { |id| vm_index[id] }
|
|
93
|
+
Agenda.new(agenda_attrs.merge('visit_motives' => agenda_visit_motives))
|
|
94
|
+
end
|
|
89
95
|
end
|
|
90
96
|
end
|
|
91
97
|
|
|
@@ -120,11 +126,35 @@ module TocDoc
|
|
|
120
126
|
profile.organization?
|
|
121
127
|
end
|
|
122
128
|
|
|
123
|
-
# Returns the raw data hash.
|
|
129
|
+
# Returns the raw data hash as received from the API.
|
|
124
130
|
#
|
|
125
131
|
# @return [Hash]
|
|
126
|
-
def
|
|
132
|
+
def raw
|
|
127
133
|
@data
|
|
128
134
|
end
|
|
135
|
+
|
|
136
|
+
# Returns a hydrated hash with all typed collections serialized to plain
|
|
137
|
+
# Hashes. Unlike {#raw}, nested objects are converted via their own
|
|
138
|
+
# +#to_h+ methods.
|
|
139
|
+
#
|
|
140
|
+
# @return [Hash{String => Object}]
|
|
141
|
+
def to_h
|
|
142
|
+
{
|
|
143
|
+
'profile' => profile.to_h,
|
|
144
|
+
'specialities' => specialities.map(&:to_h),
|
|
145
|
+
'visit_motives' => visit_motives.map(&:to_h),
|
|
146
|
+
'agendas' => agendas.map(&:to_h),
|
|
147
|
+
'places' => places.map(&:to_h),
|
|
148
|
+
'practitioners' => practitioners.map(&:to_h)
|
|
149
|
+
}
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Serialize the booking info to a JSON string.
|
|
153
|
+
#
|
|
154
|
+
# @param args [Array] forwarded to +Hash#to_json+
|
|
155
|
+
# @return [String]
|
|
156
|
+
def to_json(*)
|
|
157
|
+
to_h.to_json(*)
|
|
158
|
+
end
|
|
129
159
|
end
|
|
130
160
|
end
|
data/lib/toc_doc/models/place.rb
CHANGED
|
@@ -27,6 +27,8 @@ module TocDoc
|
|
|
27
27
|
# place.latitude #=> 44.8386722
|
|
28
28
|
# place.elevator #=> true
|
|
29
29
|
class Place < Resource
|
|
30
|
+
main_attrs :id, :city, :full_address
|
|
31
|
+
|
|
30
32
|
# Returns the geographic coordinates of the place.
|
|
31
33
|
#
|
|
32
34
|
# @return [Array(Float, Float)] +[latitude, longitude]+
|
|
@@ -165,6 +165,28 @@ module TocDoc
|
|
|
165
165
|
def organization?
|
|
166
166
|
is_a?(Organization)
|
|
167
167
|
end
|
|
168
|
+
|
|
169
|
+
# Returns the profile ID, falling back to the +value+ key used in
|
|
170
|
+
# autocomplete responses when +id+ is absent.
|
|
171
|
+
#
|
|
172
|
+
# @return [Integer, String, nil]
|
|
173
|
+
def id
|
|
174
|
+
self['id'] || self['value']
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Replaces this profile's attributes with those from the full profile page,
|
|
178
|
+
# making a network request only when the profile is currently partial.
|
|
179
|
+
# Returns +self+ for chaining.
|
|
180
|
+
#
|
|
181
|
+
# @return [self]
|
|
182
|
+
def load_full_profile!
|
|
183
|
+
return unless partial
|
|
184
|
+
|
|
185
|
+
full_profile = self.class.find(id)
|
|
186
|
+
@attrs = full_profile.instance_variable_get(:@attrs)
|
|
187
|
+
@partial = false
|
|
188
|
+
self
|
|
189
|
+
end
|
|
168
190
|
end
|
|
169
191
|
end
|
|
170
192
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
3
5
|
module TocDoc
|
|
4
6
|
# A lightweight wrapper providing dot-notation access to response fields.
|
|
5
7
|
# Backed by a Hash, with +method_missing+ for attribute access and +#to_h+ for
|
|
@@ -14,6 +16,8 @@ module TocDoc
|
|
|
14
16
|
# resource[:date] #=> "2026-02-28"
|
|
15
17
|
# resource.to_h #=> { "date" => "2026-02-28", "slots" => [] }
|
|
16
18
|
class Resource
|
|
19
|
+
attr_reader :attrs
|
|
20
|
+
|
|
17
21
|
class << self
|
|
18
22
|
# Normalises a raw attribute hash to string keys, mirroring what
|
|
19
23
|
# {#initialize} does internally. Useful in class-level factory methods
|
|
@@ -73,11 +77,20 @@ module TocDoc
|
|
|
73
77
|
@attrs[key.to_s] = value
|
|
74
78
|
end
|
|
75
79
|
|
|
76
|
-
# Return a plain Hash representation
|
|
80
|
+
# Return a plain Hash representation with all nested {Resource} values
|
|
81
|
+
# recursively converted to plain Hashes.
|
|
77
82
|
#
|
|
78
83
|
# @return [Hash{String => Object}]
|
|
79
84
|
def to_h
|
|
80
|
-
@attrs.
|
|
85
|
+
@attrs.transform_values { |v| deep_convert(v) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Serialize the resource to a JSON string.
|
|
89
|
+
#
|
|
90
|
+
# @param args [Array] forwarded to +Hash#to_json+
|
|
91
|
+
# @return [String]
|
|
92
|
+
def to_json(*)
|
|
93
|
+
to_h.to_json(*)
|
|
81
94
|
end
|
|
82
95
|
|
|
83
96
|
# Equality comparison.
|
|
@@ -100,24 +113,80 @@ module TocDoc
|
|
|
100
113
|
@attrs.key?(method_name.to_s) || super
|
|
101
114
|
end
|
|
102
115
|
|
|
116
|
+
# Returns the list of attribute names present on this resource.
|
|
117
|
+
#
|
|
118
|
+
# @return [Array<String>] attribute names as strings
|
|
119
|
+
#
|
|
120
|
+
# @example
|
|
121
|
+
# resource = TocDoc::Resource.new('date' => '2026-02-28', 'slots' => [])
|
|
122
|
+
# resource.attribute_names #=> ["date", "slots"]
|
|
123
|
+
def attribute_names
|
|
124
|
+
@attrs.keys
|
|
125
|
+
end
|
|
126
|
+
|
|
103
127
|
# Provides dot-notation access to response fields.
|
|
104
128
|
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
129
|
+
# On first access, defines a singleton method so that subsequent calls
|
|
130
|
+
# bypass +method_missing+ entirely. The defined method reads live from
|
|
131
|
+
# +@attrs+, so mutations via +[]=+ are always reflected.
|
|
107
132
|
#
|
|
108
133
|
# @param method_name [Symbol] the method name
|
|
109
134
|
# @return [Object] the attribute value
|
|
110
135
|
# @raise [NoMethodError] when the key does not exist
|
|
111
136
|
def method_missing(method_name, *_args)
|
|
112
137
|
key = method_name.to_s
|
|
113
|
-
@attrs.key?(key)
|
|
138
|
+
if @attrs.key?(key)
|
|
139
|
+
define_singleton_method(key) { @attrs[key] }
|
|
140
|
+
@attrs[key]
|
|
141
|
+
else
|
|
142
|
+
super
|
|
143
|
+
end
|
|
114
144
|
end
|
|
115
145
|
|
|
116
|
-
#
|
|
146
|
+
# Returns a human-readable representation of the resource showing only the
|
|
147
|
+
# declared {.main_attrs} (or all attrs when none are declared).
|
|
148
|
+
#
|
|
149
|
+
# @return [String]
|
|
117
150
|
def inspect
|
|
118
|
-
|
|
119
|
-
|
|
151
|
+
pairs = inspect_hash.map do |key, value|
|
|
152
|
+
"@#{key}=#{value.inspect}"
|
|
153
|
+
end.join(', ')
|
|
154
|
+
|
|
120
155
|
"#<#{self.class} #{pairs}>"
|
|
121
156
|
end
|
|
157
|
+
|
|
158
|
+
private
|
|
159
|
+
|
|
160
|
+
# Builds the key/value pairs used by {#inspect}.
|
|
161
|
+
#
|
|
162
|
+
# For each target key, the raw value from +@attrs+ is used when present.
|
|
163
|
+
# When the key is absent from +@attrs+ but the resource responds to the
|
|
164
|
+
# method (e.g. a computed attribute defined by a subclass), the method
|
|
165
|
+
# return value is used as a fallback.
|
|
166
|
+
#
|
|
167
|
+
# @return [Hash{String => Object}]
|
|
168
|
+
def inspect_hash
|
|
169
|
+
target_keys = self.class.main_attrs || @attrs.keys
|
|
170
|
+
|
|
171
|
+
target_keys.to_h do |target_key|
|
|
172
|
+
value = @attrs[target_key.to_s]
|
|
173
|
+
value = send(target_key) if value.nil? && respond_to?(target_key)
|
|
174
|
+
[target_key, value]
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Recursively converts {Resource} instances, Hashes, and Arrays to plain
|
|
179
|
+
# Ruby structures so that +to_h+ is fully serializable.
|
|
180
|
+
#
|
|
181
|
+
# @param value [Object]
|
|
182
|
+
# @return [Object]
|
|
183
|
+
def deep_convert(value)
|
|
184
|
+
case value
|
|
185
|
+
when Resource then value.to_h
|
|
186
|
+
when Hash then value.transform_values { |v| deep_convert(v) }
|
|
187
|
+
when Array then value.map { |v| deep_convert(v) }
|
|
188
|
+
else value
|
|
189
|
+
end
|
|
190
|
+
end
|
|
122
191
|
end
|
|
123
192
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: toc_doc
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- 01max
|
|
@@ -70,6 +70,7 @@ files:
|
|
|
70
70
|
- lib/toc_doc/core/error.rb
|
|
71
71
|
- lib/toc_doc/core/uri_utils.rb
|
|
72
72
|
- lib/toc_doc/core/version.rb
|
|
73
|
+
- lib/toc_doc/http/middleware/logging.rb
|
|
73
74
|
- lib/toc_doc/http/middleware/raise_error.rb
|
|
74
75
|
- lib/toc_doc/middleware/.keep
|
|
75
76
|
- lib/toc_doc/models.rb
|