toc_doc 1.2.0 → 1.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dcac91743cd8f8d50d032638c3a690f88c786a369960d8b5df8bdead6006149c
4
- data.tar.gz: d8df90b16b2ff3e2311af4751396ff1ae54173575e4507989df0e7c910bd82eb
3
+ metadata.gz: a6c39bc2d41f1be6ae2b12b23074de967614fc1c6067d217316c7cb8bb9f0c31
4
+ data.tar.gz: 6c3a8732a25916bb75ebb18c7f53a79ff272a0c8b646477160a8381890404dcf
5
5
  SHA512:
6
- metadata.gz: e8886203725b1dd9bbd59e62ee56aaf4f0965c908b31d478568c9d0149a7062f7059509f5692f18273e08b4a05d90d8a6d6e8bb02e89342ab2d1a18e92b2b16a
7
- data.tar.gz: fd9271676246e8da0e7e626ebc3c10316f5b86b2ab6fc811a67986bb276aa1e817c74f7d17fec64372cfb15048638b1aac32e08b2dce981142646ed4a1289977
6
+ metadata.gz: dde234124b736d0149ac1f45bb6ab654487fb4e0c91bffb6c1b2462f51fa50a94cb59c5e9d4c4c5ef688eb12b9a8f8f321a0b5384b7d4bebe375506f81001b87
7
+ data.tar.gz: 1fd5632c3e5c73f89ae8db6f844269d5674141332ea9fd1956691695c3ae7242a7d7f706b3b0742f361df6b7a6a8ed931dc25f6d4b2c01426c6cc0ee379dacde
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.3.0] - 2026-03-15
4
+
5
+ ### Added
6
+
7
+ - **`TocDoc::Speciality`** — new `Resource`-based model representing a speciality returned by the autocomplete endpoint; exposes `#value`, `#slug`, and `#name` via dot-notation
8
+ - **`TocDoc::Profile`** — new `Resource`-based model for search profile results; `Profile.build(attrs)` factory returns a `Profile::Practitioner` or `Profile::Organization` based on the `owner_type` field; provides `#practitioner?` and `#organization?` predicates
9
+ - **`TocDoc::Profile::Practitioner`** and **`TocDoc::Profile::Organization`** — typed profile subclasses
10
+ - **`TocDoc::Search`** — new service class for the autocomplete endpoint (`/api/searchbar/autocomplete.json`); `Search.where(query:, type: nil, **options)` fetches results and returns a `Search::Result`, or a filtered array when `type:` is one of `'profile'`, `'practitioner'`, `'organization'`, or `'speciality'`
11
+ - **`TocDoc::Search::Result`** — envelope returned by `Search.where`; exposes `#profiles` (typed via `Profile.build`) and `#specialities`; `#filter_by_type` narrows to a specific kind
12
+ - **`TocDoc.search`** — top-level shortcut delegating to `TocDoc::Search.where`
13
+
3
14
  ## [1.2.0] - 2026-03-08
4
15
 
5
16
  ### Added
data/README.md CHANGED
@@ -27,6 +27,7 @@ A Ruby gem for interacting with the (unofficial) Doctolib API. A thin, Faraday-b
27
27
  - [ENV variables](#environment-variable-overrides)
28
28
  4. [Endpoints](#endpoints)
29
29
  - [Availabilities](#availabilities)
30
+ - [Search](#search)
30
31
  5. [Response objects](#response-objects)
31
32
  6. [Pagination](#pagination)
32
33
  7. [Error handling](#error-handling)
@@ -127,7 +128,7 @@ client.get('/availabilities.json', query: { visit_motive_ids: '123', agenda_ids:
127
128
  | Option | Default | Description |
128
129
  |---|---|---|
129
130
  | `api_endpoint` | `https://www.doctolib.fr` | Base URL. Change to `.de` / `.it` for other countries. |
130
- | `user_agent` | `TocDoc Ruby Gem 1.2.0` | `User-Agent` header sent with every request. |
131
+ | `user_agent` | `TocDoc Ruby Gem 1.3.0` | `User-Agent` header sent with every request. |
131
132
  | `default_media_type` | `application/json` | `Accept` and `Content-Type` headers. |
132
133
  | `per_page` | `15` | Default number of availability dates per request (capped at `15`). |
133
134
  | `middleware` | Retry + RaiseError + JSON + adapter | Full Faraday middleware stack. Override to customise completely. |
@@ -180,6 +181,34 @@ TocDoc::Availability.where(
180
181
 
181
182
  **Return value:** a `TocDoc::Availability::Collection` (see [Response objects](#response-objects)).
182
183
 
184
+ ### Search
185
+
186
+ Query the Doctolib autocomplete endpoint to look up practitioners, organizations, and specialities.
187
+
188
+ ```ruby
189
+ result = TocDoc::Search.where(query: 'dentiste')
190
+ result.profiles # => [#<TocDoc::Profile::Practitioner ...>, ...]
191
+ result.specialities # => [#<TocDoc::Speciality ...>, ...]
192
+ ```
193
+
194
+ Pass `type:` to receive a filtered array directly:
195
+
196
+ ```ruby
197
+ # Only specialities
198
+ TocDoc::Search.where(query: 'cardio', type: 'speciality')
199
+ # => [#<TocDoc::Speciality name="Cardiologue">, ...]
200
+
201
+ # Only practitioners
202
+ TocDoc::Search.where(query: 'dupont', type: 'practitioner')
203
+ # => [#<TocDoc::Profile::Practitioner ...>, ...]
204
+ ```
205
+
206
+ Valid `type:` values: `'profile'` (all profiles), `'practitioner'`, `'organization'`, `'speciality'`.
207
+
208
+ `TocDoc.search(...)` is a module-level shortcut with the same signature.
209
+
210
+ **Return value:** a `TocDoc::Search::Result` when `type:` is omitted, or a filtered `Array` otherwise (see [Response objects](#response-objects)).
211
+
183
212
  ---
184
213
 
185
214
  ## Response objects
@@ -229,6 +258,51 @@ collection.to_h
229
258
  # }
230
259
  ```
231
260
 
261
+ ### `TocDoc::Search::Result`
262
+
263
+ Returned by `TocDoc::Search.where` when `type:` is omitted.
264
+
265
+ | Method | Type | Description |
266
+ |---|---|---|
267
+ | `#profiles` | `Array<TocDoc::Profile::Practitioner, TocDoc::Profile::Organization>` | All profile results, typed via `Profile.build`. |
268
+ | `#specialities` | `Array<TocDoc::Speciality>` | All speciality results. |
269
+ | `#filter_by_type(type)` | `Array` | Narrows results to `'profile'`, `'practitioner'`, `'organization'`, or `'speciality'`. |
270
+
271
+ ### `TocDoc::Profile`
272
+
273
+ Represents a search profile result (practitioner or organization). Use `Profile.build(attrs)` to obtain the correctly typed subclass instance.
274
+
275
+ | Method | Type | Description |
276
+ |---|---|---|
277
+ | `Profile.build(attrs)` | `Profile::Practitioner \| Profile::Organization` | Factory: returns `Practitioner` when `owner_type` is `"Account"`, `Organization` otherwise. |
278
+ | `#practitioner?` | `Boolean` | `true` when this is a `Profile::Practitioner`. |
279
+ | `#organization?` | `Boolean` | `true` when this is a `Profile::Organization`. |
280
+
281
+ `TocDoc::Profile::Practitioner` and `TocDoc::Profile::Organization` are thin subclasses that inherit dot-notation attribute access from `TocDoc::Resource`.
282
+
283
+ ### `TocDoc::Speciality`
284
+
285
+ Represents a speciality returned by the autocomplete endpoint. Inherits dot-notation attribute access from `TocDoc::Resource`.
286
+
287
+ | Method | Type | Description |
288
+ |---|---|---|
289
+ | `#value` | `Integer` | Numeric speciality identifier. |
290
+ | `#slug` | `String` | URL-friendly identifier. |
291
+ | `#name` | `String` | Human-readable speciality name. |
292
+
293
+ **Example:**
294
+
295
+ ```ruby
296
+ result = TocDoc::Search.where(query: 'dermato')
297
+
298
+ result.profiles.first.class # => TocDoc::Profile::Practitioner
299
+ result.profiles.first.practitioner? # => true
300
+ result.profiles.first.name # => "Dr. Jane Smith"
301
+
302
+ result.specialities.first.slug # => "dermatologue"
303
+ result.specialities.first.name # => "Dermatologue"
304
+ ```
305
+
232
306
  ---
233
307
 
234
308
  ## Pagination
data/TODO.md CHANGED
@@ -2,12 +2,6 @@
2
2
 
3
3
  [POTENTIAL_ENDPOINTS][POTENTIAL_ENDPOINTS.md]
4
4
 
5
- ## 1.3
6
-
7
- - [ ] Search (autocomplete)
8
- - [ ] search profile : https://www.doctolib.fr/api/searchbar/autocomplete.json?search=devun
9
- - [ ] search specialty : https://www.doctolib.fr/api/searchbar/autocomplete.json?search=dentiste
10
-
11
5
  ## 1.4
12
6
 
13
7
  - [ ] Profile
@@ -33,8 +27,27 @@
33
27
  - [ ] Authentication module + headers
34
28
  - [ ] Auth specs
35
29
 
30
+ # ???
31
+
32
+ - [ ] Figure what is `organization_statuses` in the autocomplete endpoint and what to do with it.
33
+
36
34
  # DONE & RELEASED
37
35
 
36
+ ## 1.3
37
+
38
+ - [x] Search (autocomplete)
39
+ - [x] search profile : https://www.doctolib.fr/api/searchbar/autocomplete.json?search=devun
40
+ - [x] search specialty : https://www.doctolib.fr/api/searchbar/autocomplete.json?search=dentiste
41
+
42
+ ## 1.2
43
+
44
+ - [x] Rework Availability's client, model and collection architecture.
45
+
46
+ ## 1.1
47
+
48
+ ### Parse raw API data
49
+ - [x] Parse date / datetime
50
+
38
51
  ## 1.0
39
52
 
40
53
  ### 1 – Skeleton & Tooling
@@ -88,13 +101,4 @@
88
101
  - [x] on rubygem
89
102
  - [x] release on GH
90
103
  - [x] gem.coop/@maxime
91
- - [x] Add test coverage tool
92
-
93
- ## 1.1
94
-
95
- ### Parse raw API data
96
- - [x] Parse date / datetime
97
-
98
- ## 1.2
99
-
100
- - [x] Rework Availability's client, model and collection architecture.
104
+ - [x] Add test coverage tool
@@ -4,5 +4,5 @@ module TocDoc
4
4
  # The current version of the TocDoc gem.
5
5
  #
6
6
  # @return [String]
7
- VERSION = '1.2.0'
7
+ VERSION = '1.3.0'
8
8
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TocDoc
4
+ class Profile
5
+ # An organization profile.
6
+ class Organization < Profile; end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TocDoc
4
+ class Profile
5
+ # A practitioner profile (raw +owner_type: "Account"+).
6
+ class Practitioner < Profile; end
7
+ end
8
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TocDoc
4
+ # Represents a search profile result (practitioner or organization).
5
+ # Inherits dot-notation attribute access from +TocDoc::Resource+.
6
+ #
7
+ # Use +Profile.build+ to obtain the correctly typed subclass instance.
8
+ #
9
+ # @example
10
+ # profile = TocDoc::Profile.build('owner_type' => 'Account', 'name' => 'Dr Smith')
11
+ # profile.class #=> TocDoc::Profile::Practitioner
12
+ # profile.practitioner? #=> true
13
+ # profile.name #=> "Dr Smith"
14
+ class Profile < Resource
15
+ # Factory — returns a +Profile::Practitioner+ or +Profile::Organization+
16
+ # depending on the +owner_type+ field of the raw attribute hash.
17
+ #
18
+ # @param attrs [Hash] raw attribute hash from the API response
19
+ # @return [Profile::Practitioner, Profile::Organization]
20
+ def self.build(attrs = {})
21
+ case attrs['owner_type'] || attrs[:owner_type]
22
+ when 'Account'
23
+ Practitioner.new(attrs)
24
+ else
25
+ Organization.new(attrs)
26
+ end
27
+ end
28
+
29
+ # @return [Boolean] true when this profile is a practitioner
30
+ def practitioner?
31
+ is_a?(Practitioner)
32
+ end
33
+
34
+ # @return [Boolean] true when this profile is an organization
35
+ def organization?
36
+ is_a?(Organization)
37
+ end
38
+ end
39
+ end
40
+
41
+ require 'toc_doc/models/profile/practitioner'
42
+ require 'toc_doc/models/profile/organization'
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'toc_doc/models/profile'
4
+ require 'toc_doc/models/speciality'
5
+
6
+ module TocDoc
7
+ class Search
8
+ # Envelope returned by {TocDoc::Search.where} when no +type:+ filter is given.
9
+ #
10
+ # Wraps the raw API response and exposes typed collections for profiles and
11
+ # specialities. Unlike {TocDoc::Availability::Collection} this class does
12
+ # NOT include +Enumerable+ — the dual-type nature of the result does not
13
+ # lend itself to a single iteration interface.
14
+ #
15
+ # @example
16
+ # result = TocDoc::Search.where(query: 'dentiste')
17
+ # result.profiles #=> [#<TocDoc::Profile::Practitioner>, ...]
18
+ # result.specialities #=> [#<TocDoc::Speciality>, ...]
19
+ class Result
20
+ # @param data [Hash] raw parsed response body from the autocomplete endpoint
21
+ def initialize(data)
22
+ @profiles = build_profiles(data['profiles'])
23
+ @specialities = build_specialities(data['specialities'])
24
+ end
25
+
26
+ # All profile results, typed as {TocDoc::Profile::Practitioner} or
27
+ # {TocDoc::Profile::Organization} via {TocDoc::Profile.build}.
28
+ #
29
+ # @return [Array<TocDoc::Profile::Practitioner, TocDoc::Profile::Organization>]
30
+ attr_reader :profiles
31
+
32
+ # All speciality results as {TocDoc::Speciality} instances.
33
+ #
34
+ # @return [Array<TocDoc::Speciality>]
35
+ attr_reader :specialities
36
+
37
+ # Returns a subset of results narrowed to the given type.
38
+ #
39
+ # @param type [String] one of +'profile'+, +'practitioner'+, +'organization'+, +'speciality'+
40
+ # @return [Array<TocDoc::Profile>, Array<TocDoc::Speciality>]
41
+ def filter_by_type(type)
42
+ case type
43
+ when 'profile' then profiles
44
+ when 'practitioner' then profiles.select(&:practitioner?)
45
+ when 'organization' then profiles.select(&:organization?)
46
+ when 'speciality' then specialities
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def build_profiles(raw)
53
+ Array(raw).map { |attrs| TocDoc::Profile.build(attrs) }
54
+ end
55
+
56
+ def build_specialities(raw)
57
+ Array(raw).map { |attrs| TocDoc::Speciality.new(attrs) }
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'toc_doc/models/search/result'
4
+
5
+ module TocDoc
6
+ # Entry point for the autocomplete / search endpoint.
7
+ #
8
+ # Unlike {TocDoc::Availability}, +Search+ is not itself a resource — it is a
9
+ # plain service class that wraps the API call and returns a typed result.
10
+ #
11
+ # @example Fetch everything
12
+ # result = TocDoc::Search.where(query: 'dentiste')
13
+ # result #=> #<TocDoc::Search::Result>
14
+ # result.profiles #=> [#<TocDoc::Profile::Practitioner>, ...]
15
+ #
16
+ # @example Filter by type
17
+ # TocDoc::Search.where(query: 'dentiste', type: 'practitioner')
18
+ # #=> [#<TocDoc::Profile::Practitioner>, ...]
19
+ class Search
20
+ PATH = '/api/searchbar/autocomplete.json'
21
+ VALID_TYPES = %w[profile practitioner organization speciality].freeze
22
+
23
+ class << self
24
+ # Queries the autocomplete endpoint and returns a {Search::Result} or a
25
+ # filtered array.
26
+ #
27
+ # The +type:+ keyword is handled client-side only — it is never forwarded
28
+ # to the API. The full response is always fetched; narrowing happens after.
29
+ #
30
+ # @param query [String] the search term
31
+ # @param type [String, nil] optional filter; one of +'profile'+,
32
+ # +'practitioner'+, +'organization'+, +'speciality'+
33
+ # @param options [Hash] additional query params forwarded verbatim to the API
34
+ # @return [Search::Result] when +type:+ is +nil+
35
+ # @return [Array<TocDoc::Profile>] when +type:+ is +'profile'+, +'practitioner'+,
36
+ # or +'organization'+
37
+ # @return [Array<TocDoc::Speciality>] when +type:+ is +'speciality'+
38
+ # @raise [ArgumentError] if +type:+ is not +nil+ and not in {VALID_TYPES}
39
+ #
40
+ # @example
41
+ # TocDoc::Search.where(query: 'derma', type: 'speciality')
42
+ # #=> [#<TocDoc::Speciality name="Dermatologue">, ...]
43
+ def where(query:, type: nil, **options)
44
+ if !type.nil? && !VALID_TYPES.include?(type)
45
+ raise ArgumentError, "Invalid type #{type.inspect}. Must be one of: #{VALID_TYPES.join(', ')}"
46
+ end
47
+
48
+ data = TocDoc.client.get(PATH, query: { search: query, **options })
49
+ result = Result.new(data)
50
+
51
+ type.nil? ? result : result.filter_by_type(type)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TocDoc
4
+ # Represents a speciality returned by the autocomplete endpoint.
5
+ #
6
+ # All fields (+value+, +slug+, +name+) are primitives and are accessed via
7
+ # dot-notation inherited from {TocDoc::Resource}.
8
+ #
9
+ # @example
10
+ # speciality = TocDoc::Speciality.new('value' => 228, 'slug' => 'homeopathe', 'name' => 'Homéopathe')
11
+ # speciality.value #=> 228
12
+ # speciality.slug #=> "homeopathe"
13
+ # speciality.name #=> "Homéopathe"
14
+ class Speciality < Resource
15
+ end
16
+ end
@@ -1,5 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'toc_doc/models/resource'
4
+ require 'toc_doc/models/search'
5
+
4
6
  require 'toc_doc/models/availability'
5
7
  require 'toc_doc/models/availability/collection'
8
+
9
+ require 'toc_doc/models/profile'
10
+ require 'toc_doc/models/speciality'
data/lib/toc_doc.rb CHANGED
@@ -76,6 +76,19 @@ module TocDoc
76
76
  TocDoc::Availability.where(**)
77
77
  end
78
78
 
79
+ # Queries the autocomplete / search endpoint.
80
+ #
81
+ # Delegates to {TocDoc::Search.where} — see that method for full
82
+ # parameter documentation.
83
+ #
84
+ # @return [TocDoc::Search::Result] when called without +type:+
85
+ # @return [Array<TocDoc::Profile>] when +type:+ is +'profile'+,
86
+ # +'practitioner'+, or +'organization'+
87
+ # @return [Array<TocDoc::Speciality>] when +type:+ is +'speciality'+
88
+ def search(**)
89
+ TocDoc::Search.where(**)
90
+ end
91
+
79
92
  # @!visibility private
80
93
  def method_missing(method_name, ...)
81
94
  if client.respond_to?(method_name)
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.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - 01max
@@ -76,7 +76,13 @@ files:
76
76
  - lib/toc_doc/models.rb
77
77
  - lib/toc_doc/models/availability.rb
78
78
  - lib/toc_doc/models/availability/collection.rb
79
+ - lib/toc_doc/models/profile.rb
80
+ - lib/toc_doc/models/profile/organization.rb
81
+ - lib/toc_doc/models/profile/practitioner.rb
79
82
  - lib/toc_doc/models/resource.rb
83
+ - lib/toc_doc/models/search.rb
84
+ - lib/toc_doc/models/search/result.rb
85
+ - lib/toc_doc/models/speciality.rb
80
86
  - sig/toc_doc.rbs
81
87
  homepage: https://github.com/01max/toc_doc
82
88
  licenses: