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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 398451af707693c91bb01a514d7fc2904503eb7346d6c27469e166bfaf8290eb
4
- data.tar.gz: 7fa3490c0ee78f67c73d8b87c0f99ea7f17d01b7ceed4032e21408f233dbbe56
3
+ metadata.gz: 52385f21be1c97b5fe3f27c83990285962232bee6d63037e123b9beff3e406fb
4
+ data.tar.gz: 1219ce4918de4e07a2b8117ab02db08e760492a820008d8d8b78c13ae9d842cb
5
5
  SHA512:
6
- metadata.gz: 0125e24d31abbca650a9081697fc9c433e9f8906d85f452ea51aa59b3d103a4aac93eaba6f2c60bd478dd81f24bd68abae40a8f4cbd88b95712040f95c819601
7
- data.tar.gz: c1ae860fd3c1f2e81f78f0c70991826135a2ec47d238c36eee82716a937cf1d36a9747182d1ebfe73f3218e37635f428d3ac390b99ee3ee38f9744a172e0a33e
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lotrb (0.1.0)
4
+ lotrb (0.2.0)
5
5
  faraday (~> 1.7)
6
6
  faraday_middleware (~> 1.1)
7
7
 
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
- Lotrb::Movie.list()
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("some_movie_id")
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/[USERNAME]/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/[USERNAME]/lotrb/blob/main/CODE_OF_CONDUCT.md).
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 Lotrb project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/lotrb/blob/main/CODE_OF_CONDUCT.md).
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
@@ -1,6 +1,3 @@
1
1
  # TODOS
2
- - pagination stuff
3
- - other actions than list
4
- - other resources than movie
5
2
  - write tests
6
- -
3
+ - more elegant nested object support
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 inspect
30
- "#<Lotrb::Client>"
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
@@ -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 :objects, :total, :limit, :offset, :page, :pages
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
- @objects = result["docs"].map do |hash|
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
- def self.list
4
- ListObject.new(Client.instance.get(self::RESOURCE_NAME).body, self)
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
- ListObject.new(Client.instance.get(path).body, self).objects.first
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
- object = new
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
- object.class.attr_accessor(snake_case_field_name)
20
- object.send("#{snake_case_field_name}=", field_value)
30
+ poro.class.attr_accessor(snake_case_field_name)
31
+ poro.send("#{snake_case_field_name}=", field_value)
21
32
  end
22
33
 
23
- object
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lotrb
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/lotrb.rb CHANGED
@@ -4,7 +4,6 @@ require_relative "lotrb/version"
4
4
 
5
5
  require "lotrb/list_object"
6
6
  require "lotrb/resources"
7
-
8
7
  require "lotrb/util"
9
8
 
10
9
  module Lotrb
@@ -13,6 +12,5 @@ module Lotrb
13
12
 
14
13
  class << self
15
14
  attr_accessor :access_token
16
-
17
15
  end
18
16
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lotrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Klein