lotrb 0.1.0 → 0.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/DESIGN.md +36 -0
- data/Gemfile.lock +1 -1
- data/README.md +24 -4
- data/lib/lotrb/TODO.md +1 -4
- data/lib/lotrb/client.rb +2 -8
- data/lib/lotrb/list_object.rb +6 -2
- data/lib/lotrb/resources/base_resource.rb +28 -9
- data/lib/lotrb/resources/book.rb +6 -0
- data/lib/lotrb/resources/character.rb +6 -0
- data/lib/lotrb/resources/movie.rb +6 -0
- data/lib/lotrb/version.rb +1 -1
- data/lib/lotrb.rb +0 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 52385f21be1c97b5fe3f27c83990285962232bee6d63037e123b9beff3e406fb
|
|
4
|
+
data.tar.gz: 1219ce4918de4e07a2b8117ab02db08e760492a820008d8d8b78c13ae9d842cb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 164f3c60d7429801220da1e19a68c8fd6a08cc3de1eeaf6f2c37425dd39be619071bf3ab4bc0be8dcd45b5d7cb203c2abda89f9496e8d5f775e4d67bd2a59f07
|
|
7
|
+
data.tar.gz: 02ca9a45196f3a5456a0ab23a864698d13d9c434035ec360a32b2d5bd51d27c11f26114d0255ea293257e70ff86519d21c5df4d19b8675dd9d3a39d688e1317e
|
data/DESIGN.md
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# SDK Design Notes
|
|
2
|
+
|
|
3
|
+
My goals in writing `lotrb` were the following:
|
|
4
|
+
1) get something _working_ -- in truth, I've never written a Ruby gem OR any sort of SDK before
|
|
5
|
+
2) build something with a relatively simple, straightforward interface
|
|
6
|
+
3) write as little code as possible, within reason
|
|
7
|
+
|
|
8
|
+
The SDK mirrors the API fairly closely. I took a good amount of inspiration from [Stripe's ruby library](https://github.com/stripe/stripe-ruby), because it's one of the most pleasant I've worked with.
|
|
9
|
+
|
|
10
|
+
The most satisfying part of this SDK so far is the way it maps fields in the objects from the JSON response into Ruby objects without going crazy with any explicit mapping or config, etc. I do this by a bit of metaprogramming to convert the JSON field names into `attr_accessor`s on the relevant class and then set each field's value as it's "hydrated," if you will. For example, the `Book` class doesn't have a `name` field defined in code -- it's dynamically defined and assigned when we hydrate each `Book` instance from an API response.
|
|
11
|
+
|
|
12
|
+
We can list any resource type (Book, Movie, Chapter, Character, and Quote) by calling the class method `list` on the corresponding class. This will return a `ListObject` containing an array of the corresponding resource objects along with the pagination fields:
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
characters = Lotrb::Character.list # This will give you a ListObject
|
|
16
|
+
characters.results # This will give you the array of Character objects
|
|
17
|
+
characters.pages # This will give you the number of pages in the result of you list request
|
|
18
|
+
|
|
19
|
+
# Since `characters.results` is a plain Ruby array, we can access it like any old array:
|
|
20
|
+
characters.results.first.name
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Likewise, we can fetch a single resource by ID by calling the class method `retrieve` on the corresponding class. This will return an instance of the class itself.
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
quote = Lotrb::Quote.retrieve("5cd96e05de30eff6ebccebca") # This returns an actual Quote object
|
|
27
|
+
quote.dialog # This returns the dialog field of the quote
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Finally (and this is a piece I'm not very happy with yet), classes for which the API allows fetching nested resources each have a `list_blahs` method, where `blah` is the nested resource. This `list_blahs` method quacks just like a `list` method on one of the resource classes, returning a `ListObject`. For example...
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
book = Lotrb::Book.list.results.first # Returns a ListObject
|
|
34
|
+
book.list_chapters.results # Returns an array of Chapter objects
|
|
35
|
+
```
|
|
36
|
+
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -30,10 +30,30 @@ require "lotrb"
|
|
|
30
30
|
Lotrb.access_token = "your_access_token_here"
|
|
31
31
|
|
|
32
32
|
# List all the movies
|
|
33
|
-
|
|
33
|
+
# This returns a `ListObject` containing an array of the corresponding resource objects along with the pagination fields
|
|
34
|
+
movies_response = Lotrb::Movie.list
|
|
35
|
+
movies_response.results
|
|
36
|
+
movies_response.pages
|
|
34
37
|
|
|
35
38
|
# Fetch a specific movie
|
|
36
|
-
Lotrb::Movie.retrieve("
|
|
39
|
+
Lotrb::Movie.retrieve("5cd95395de30eff6ebccde5d")
|
|
40
|
+
|
|
41
|
+
# Fetch the chapters from a book
|
|
42
|
+
book = Lotrb::Book.list.results.last
|
|
43
|
+
book.list_chapters
|
|
44
|
+
|
|
45
|
+
# Pagination, sorting, and filtering all work by passing a hash of
|
|
46
|
+
# params to any `list` or `list_blahs` call.
|
|
47
|
+
# These params map precisely to those described in [the API docs](https://the-one-api.dev/documentation)
|
|
48
|
+
#
|
|
49
|
+
# Paginate!
|
|
50
|
+
Lotrb::Quote.list({ limit: 5, page: 75 })
|
|
51
|
+
|
|
52
|
+
# Sort!
|
|
53
|
+
Lotrb::Book.list({ sort: "name:asc" })
|
|
54
|
+
|
|
55
|
+
# Filter!
|
|
56
|
+
Lotrb::Character.list({ race: "Hobbit,Human" })
|
|
37
57
|
```
|
|
38
58
|
|
|
39
59
|
## Development
|
|
@@ -44,7 +64,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
|
44
64
|
|
|
45
65
|
## Contributing
|
|
46
66
|
|
|
47
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
67
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/danielmklein/lotrb. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/danielmklein/lotrb/blob/main/CODE_OF_CONDUCT.md).
|
|
48
68
|
|
|
49
69
|
## License
|
|
50
70
|
|
|
@@ -52,4 +72,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
|
52
72
|
|
|
53
73
|
## Code of Conduct
|
|
54
74
|
|
|
55
|
-
Everyone interacting in the
|
|
75
|
+
Everyone interacting in the lotrb project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/danielmklein/lotrb/blob/main/CODE_OF_CONDUCT.md).
|
data/lib/lotrb/TODO.md
CHANGED
data/lib/lotrb/client.rb
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
require "faraday"
|
|
2
2
|
require "faraday_middleware"
|
|
3
3
|
|
|
4
|
-
# client.connection.get("movie", {}, { Authorization: "Bearer #{Lotrb.access_token}" }).body["docs"]
|
|
5
|
-
|
|
6
4
|
module Lotrb
|
|
7
5
|
class Client
|
|
8
6
|
BASE_URL = "https://the-one-api.dev/v2/"
|
|
@@ -26,12 +24,8 @@ module Lotrb
|
|
|
26
24
|
end
|
|
27
25
|
end
|
|
28
26
|
|
|
29
|
-
def
|
|
30
|
-
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def get(path, opts = {})
|
|
34
|
-
connection.get(path, opts, auth_header)
|
|
27
|
+
def get(path, params = {})
|
|
28
|
+
connection.get(path, params , auth_header)
|
|
35
29
|
end
|
|
36
30
|
|
|
37
31
|
private
|
data/lib/lotrb/list_object.rb
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
module Lotrb
|
|
2
|
+
# A data structure to hold list results from an API request
|
|
3
|
+
# `results` holds the actual POROs representing the data,
|
|
4
|
+
# while `total`, `limit`, `offset`, `page`, and `pages`
|
|
5
|
+
# hold the corresponding pagination values from the API.
|
|
2
6
|
class ListObject
|
|
3
|
-
attr_reader :
|
|
7
|
+
attr_reader :results, :total, :limit, :offset, :page, :pages
|
|
4
8
|
|
|
5
9
|
def initialize(result, klass)
|
|
6
10
|
@total = result["total"]
|
|
@@ -8,7 +12,7 @@ module Lotrb
|
|
|
8
12
|
@offset = result["offset"]
|
|
9
13
|
@page = result["page"]
|
|
10
14
|
@pages = result["pages"]
|
|
11
|
-
@
|
|
15
|
+
@results = result["docs"].map do |hash|
|
|
12
16
|
klass.from_result_hash(hash)
|
|
13
17
|
end
|
|
14
18
|
end
|
|
@@ -1,26 +1,45 @@
|
|
|
1
1
|
module Lotrb
|
|
2
|
-
class BaseResource
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
class BaseResource
|
|
3
|
+
# The fundamental method for fetching a list of resources from the API.
|
|
4
|
+
def self.list(params = {})
|
|
5
|
+
child_class = self
|
|
6
|
+
path = self::RESOURCE_NAME
|
|
7
|
+
internal_get(path: path, params: params, result_class: child_class)
|
|
5
8
|
end
|
|
6
9
|
|
|
10
|
+
# The fundamental method for fetching a specific resource from the API, by ID.
|
|
7
11
|
def self.retrieve(id)
|
|
12
|
+
child_class = self
|
|
8
13
|
path = "#{self::RESOURCE_NAME}/#{id}"
|
|
9
|
-
|
|
14
|
+
internal_get(path: path, result_class: child_class).results.first
|
|
10
15
|
end
|
|
11
16
|
|
|
12
17
|
# Take a hash that represents an object (from JSON),
|
|
13
18
|
# convert the field names to snake case (to be ruby-ish)
|
|
14
19
|
# and metaprogram those fields onto object, and return it.
|
|
20
|
+
#
|
|
21
|
+
# This is used throughout for taking a single entry from an API result
|
|
22
|
+
# and mapping it into a Ruby object with the fields we might expect.
|
|
23
|
+
# The metaprogramming here allows us to avoid having to explicitly
|
|
24
|
+
# specify the fields we are expecting in the response -- the resulting
|
|
25
|
+
# Ruby object will have all the fields that the response object did.
|
|
15
26
|
def self.from_result_hash(result_hash)
|
|
16
|
-
|
|
27
|
+
poro = new
|
|
17
28
|
result_hash.each do |(field_name, field_value)|
|
|
18
29
|
snake_case_field_name = Util.to_snake_case(field_name)
|
|
19
|
-
|
|
20
|
-
|
|
30
|
+
poro.class.attr_accessor(snake_case_field_name)
|
|
31
|
+
poro.send("#{snake_case_field_name}=", field_value)
|
|
21
32
|
end
|
|
22
33
|
|
|
23
|
-
|
|
34
|
+
poro
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# All `list`, `retrieve`, and `list_blahs` calls go through this method.
|
|
38
|
+
# Use the API client to get the response (validating the params along the way),
|
|
39
|
+
# then map the results to the right class.
|
|
40
|
+
def self.internal_get(path:, params: {}, result_class:)
|
|
41
|
+
response_body = Client.instance.get(path, params).body
|
|
42
|
+
ListObject.new(response_body, result_class)
|
|
24
43
|
end
|
|
25
44
|
end
|
|
26
|
-
end
|
|
45
|
+
end
|
data/lib/lotrb/resources/book.rb
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
module Lotrb
|
|
2
2
|
class Book < BaseResource
|
|
3
3
|
RESOURCE_NAME = "book"
|
|
4
|
+
|
|
5
|
+
def list_chapters(params = {})
|
|
6
|
+
child_class = Chapter
|
|
7
|
+
path = "#{RESOURCE_NAME}/#{_id}/#{child_class::RESOURCE_NAME}"
|
|
8
|
+
self.class.internal_get(path: path, params: params, result_class: child_class)
|
|
9
|
+
end
|
|
4
10
|
end
|
|
5
11
|
end
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
module Lotrb
|
|
2
2
|
class Character < BaseResource
|
|
3
3
|
RESOURCE_NAME = "character"
|
|
4
|
+
|
|
5
|
+
def list_quotes(params = {})
|
|
6
|
+
child_class = Quote
|
|
7
|
+
path = "#{RESOURCE_NAME}/#{_id}/#{child_class::RESOURCE_NAME}"
|
|
8
|
+
self.class.internal_get(path: path, params: params, result_class: child_class)
|
|
9
|
+
end
|
|
4
10
|
end
|
|
5
11
|
end
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
module Lotrb
|
|
2
2
|
class Movie < BaseResource
|
|
3
3
|
RESOURCE_NAME = "movie"
|
|
4
|
+
|
|
5
|
+
def list_quotes(params = {})
|
|
6
|
+
child_class = Quote
|
|
7
|
+
path = "#{RESOURCE_NAME}/#{_id}/#{child_class::RESOURCE_NAME}"
|
|
8
|
+
self.class.internal_get(path: path, params: params, result_class: child_class)
|
|
9
|
+
end
|
|
4
10
|
end
|
|
5
11
|
end
|
data/lib/lotrb/version.rb
CHANGED
data/lib/lotrb.rb
CHANGED