comic_vine 0.1.5 → 1.0.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
- SHA1:
3
- metadata.gz: 5282482a38f24a137b4df6914ae771b6d3c02ee8
4
- data.tar.gz: b6439f9bbf3ccd569910b8fac4a5c74105fb3d0c
2
+ SHA256:
3
+ metadata.gz: 73bf9362c8e1a01699796797ec6173bf728515de3d87f7e9d00643eb9da79a8d
4
+ data.tar.gz: bb9a531d2ba69cc53bbc8933e70f49527892367d79f6c1c6f8c3fcc57a82f573
5
5
  SHA512:
6
- metadata.gz: 2db1172216e1a4cce6f619d7daf800b4ecae51673c1adb2d5618b33e5a1e385ed5de8a91ece3b395b75323ac126b6bb9c537eb792cd3cbed94e85585732eb2bd
7
- data.tar.gz: e40e0b8b5ff5d04f24667ed1d28098434780d8a14a29d064cc337bd7081bf9826c389673893f8052b34033f2847b5e6db3095325751364aaba85c4f817f95ed0
6
+ metadata.gz: 94931bde6c6ece327e406c8cddb65c6049b13c78b16d346aaa06b87ac74d3c6decae2b7adb9dd26abd0bd9a07367de44c36ecdfdfb00daf519e0e1a9eea237fd
7
+ data.tar.gz: ccd3b165209efde156dc98cc6a9de098578f4f5a805dc0bcfeb540fe7aa5e95de9bd3e3368d367acc14c893f45f7c269319c83aebe6b08cd2b590a66109f1b7d
data/CHANGELOG.md ADDED
@@ -0,0 +1,104 @@
1
+ # Changelog
2
+
3
+ Notable changes to this project are documented in this file. The format is
4
+ based on [Keep a Changelog](https://keepachangelog.com/); dates for releases
5
+ before 2026 are taken from the git history.
6
+
7
+ ## [1.0.0] — 2026-07-08
8
+
9
+ First stable release. **Breaking**: requires Ruby >= 3.4, and error handling
10
+ and pagination behavior have changed as described below.
11
+
12
+ ### Added
13
+
14
+ - Error hierarchy under `ComicVine::CVError`: `CVAPIError`, `CVHTTPError`
15
+ (with `#status`), `CVRateLimitError`, `CVConnectionError`, and `CVParseError`.
16
+ - Connection timeouts (`open_timeout` 10s, `read_timeout` 30s), configurable via
17
+ `ComicVine::API.open_timeout = ...` / `read_timeout = ...`.
18
+ - Automatic retries with exponential backoff on HTTP 420/429/5xx and connection
19
+ failures; honors `Retry-After`. Configurable via `max_retries` and
20
+ `retry_base_delay`.
21
+ - Custom User-Agent header, overridable via `ComicVine::API.user_agent = ...`.
22
+ - `respond_to_missing?` on `ComicVine::API` and `CVObject`, so `respond_to?`
23
+ and `method(...)` work for dynamic methods.
24
+ - `ComicVine::API.reset_types_cache!` for tests.
25
+ - RuboCop and YARD configuration; API documentation comments.
26
+
27
+ ### Changed
28
+
29
+ - JSON parsing uses the Ruby standard library; the gem now has **no runtime
30
+ dependencies** (`multi_json`, `oj`, and `gson` removed).
31
+ - Class variables replaced with class-level instance variables; the `/types/`
32
+ cache is guarded by a mutex and no longer poisoned by a failed fetch.
33
+ - `next_page`/`prev_page` return `self` on success and `nil` at either end,
34
+ and preserve the original `filter`/`sort`/`field_list` options.
35
+ - Modernized gemspec (metadata URIs, `Dir` glob instead of `git ls-files`),
36
+ RSpec 3 configuration, and WebMock-stubbed specs.
37
+
38
+ ### Fixed
39
+
40
+ - Query values are URL-encoded (`CGI.escape`); search queries are no longer
41
+ double-encoded.
42
+ - `search` no longer mutates the caller's options hash.
43
+ - `prev_page` offset math stepped by the current page's size instead of `limit`.
44
+ - `get_details` with an unknown type raises a descriptive `CVError` instead of
45
+ `NoMethodError`.
46
+ - `CVObject#get_*` on a plain value returns the value instead of `nil`.
47
+ - `CVObject` readers are defined on the singleton class instead of polluting
48
+ the shared class.
49
+
50
+ ### Removed
51
+
52
+ - Travis CI configuration.
53
+ - Committed build artifacts (`coverage/`, packaged `.gem`, `.DS_Store`).
54
+
55
+ ## [0.1.5] — 2017-03-25
56
+
57
+ - Use ComicVine URL with SSL.
58
+
59
+ ## [0.1.4] — 2016-02-12
60
+
61
+ - Update ComicVine API URL.
62
+
63
+ ## [0.1.1] — 2012-04-27
64
+
65
+ - Escape query on search; remove spaces from resources.
66
+
67
+ ## [0.1.0] — 2012-04-24
68
+
69
+ - Fix small bug with types cache; full coverage with RSpec.
70
+
71
+ ## [0.0.8] — 2012-04-18
72
+
73
+ - Fix bug exposing API key.
74
+
75
+ ## [0.0.7] — 2012-04-18
76
+
77
+ - Fix bug with search.
78
+
79
+ ## [0.0.6] — 2012-04-18
80
+
81
+ - Expose the helper methods; add `get_details_by_url`; allow `get_*` for any of
82
+ the resource items returned in the body.
83
+
84
+ ## [0.0.5] — 2012-04-12
85
+
86
+ - Behind-the-scenes cleanup. Pure Ruby implementation; `API.key` is set
87
+ manually instead of read from a config file. Removed generator and railties.
88
+
89
+ ## [0.0.4] — 2012-04-11
90
+
91
+ - Add `CVObjectList` to carry count vars from result. Simple pagination.
92
+ Include `Enumerable` in the list classes.
93
+
94
+ ## [0.0.3] — 2012-04-10
95
+
96
+ - Simple associations; error check on ComicVine response; allow options.
97
+
98
+ ## [0.0.2] — 2012-04-09
99
+
100
+ - Create `CVObject`s from resource returns.
101
+
102
+ ## [0.0.1] — 2012-04-09
103
+
104
+ - First check-in.
data/README.md CHANGED
@@ -1,79 +1,137 @@
1
- [![Build Status](https://secure.travis-ci.org/Jakanapes/ComicVine.png?branch=master)](http://travis-ci.org/Jakanapes/ComicVine)
2
-
3
1
  # ComicVine
4
2
 
5
- VERY simple first cut at a gem to interface with the ComicVine api. http://api.comicvine.com/
3
+ A simple Ruby interface to the [ComicVine API](https://comicvine.gamespot.com/api/).
4
+ Search for volumes, issues, characters, and other resources, or fetch them by id.
6
5
 
7
6
  ## Installation
8
7
 
9
8
  Add this line to your application's Gemfile:
10
9
 
11
- gem 'comic_vine'
12
-
13
- And then execute:
10
+ ```ruby
11
+ gem "comic_vine"
12
+ ```
14
13
 
15
- $ bundle install
14
+ And then run `bundle install`, or install it directly:
16
15
 
17
- Or install it yourself as:
16
+ ```console
17
+ $ gem install comic_vine
18
+ ```
18
19
 
19
- $ gem install comic_vine
20
-
21
- You will also need to have a ComicVine API key.
20
+ You will also need a [ComicVine API key](https://comicvine.gamespot.com/api/).
22
21
 
23
22
  ## Usage
24
23
 
25
- Requires that API key be set manually. This is a breaking change from 0.0.4.
24
+ Set your API key first:
25
+
26
+ ```ruby
27
+ ComicVine::API.key = "your-api-key"
28
+ ```
29
+
30
+ Calls to plural resources return a `CVObjectList`, which carries the result
31
+ array along with the values from the response (`total_count`, `page_count`,
32
+ `offset`, `limit`):
33
+
34
+ ```ruby
35
+ chars = ComicVine::API.characters
36
+ ```
37
+
38
+ `CVObjectList` includes `Enumerable`, so lists can be looped:
39
+
40
+ ```ruby
41
+ chars.each { |c| puts c.name }
42
+ ```
26
43
 
27
- ComicVine::API.key = xxxxxx
44
+ Pagination updates the list in place and returns the list, or returns `nil` at
45
+ either end, so it can be used in a loop:
28
46
 
47
+ ```ruby
48
+ chars.next_page
49
+ chars.prev_page
50
+ ```
29
51
 
30
- Calls to plurals return a CVObjectList, which contains the result array as well as the values from the return (total_count, offset, limit, resource).
52
+ Calls to singular resources take an id and return a `CVObject`:
31
53
 
32
- chars = ComicVine::API.characters
54
+ ```ruby
55
+ ComicVine::API.volume 766
56
+ ```
33
57
 
34
- CVObjectLists include enumerable, so they can be looped.
58
+ `search` takes a resource type or types (comma-separated, e.g.
59
+ `"volume,issue"`) and a query string, and returns a `CVSearchList` (also
60
+ paginated):
35
61
 
36
- chars.each do |c|
37
-
38
- Pagination will return nil if you are at either end of the list, otherwise it will update the object allowing for looping
39
-
40
- chars.next_page
41
- chars.prev_page
62
+ ```ruby
63
+ results = ComicVine::API.search "volume", "batman"
64
+ ```
42
65
 
43
- Calls to singulars require an id and return a CVObject:
66
+ Call `fetch` to retrieve the full object behind a search result or association:
44
67
 
45
- ComicVine::API.volume 766
68
+ ```ruby
69
+ results.first.fetch
70
+ ```
46
71
 
47
- Search takes a resource type or types, separated by a comma(ex. "volume,issue"), and a query string and returns a CVSearchList (also with pagination)
72
+ ### Options: `limit`, `offset`, `filter`, `sort`, `field_list`
48
73
 
49
- results = ComicVine::API.search 'volume', 'batman'
50
-
51
- Call fetch to retrieve the full object
52
-
53
- results.first.fetch
74
+ List and detail calls accept an options hash, passed through to the API as
75
+ query parameters (values are URL-encoded for you). The most useful ones:
54
76
 
55
- Pass in options as a hash
77
+ ```ruby
78
+ # Page size and starting offset
79
+ ComicVine::API.characters(limit: 5, offset: 10)
56
80
 
57
- ComicVine::API.characters {:limit=>5, :offset=>10}
81
+ # Filter results — field:value pairs, comma-separated
82
+ ComicVine::API.volumes(filter: "name:Walking Dead")
58
83
 
59
- There are associations. Call the association by the key name, prefaced by get_ and the gem will return either a CVList or a CVObject from the API.
84
+ # Sort by a field field:asc or field:desc
85
+ ComicVine::API.issues(sort: "cover_date:desc")
60
86
 
61
- volume = ComicVine::API.volume 766
62
- issues = volume.get_issues
63
- chars = volume.get_character_credits
87
+ # Only return the fields you need (faster, smaller responses)
88
+ ComicVine::API.characters(field_list: "name,id,image")
89
+ ```
64
90
 
65
- Error responses from the API will raise a CVError with the error message
91
+ These options are remembered across `next_page`/`prev_page`, so a filtered or
92
+ sorted list stays filtered and sorted while you page through it. See the
93
+ [ComicVine API documentation](https://comicvine.gamespot.com/api/documentation)
94
+ for the fields each resource supports.
66
95
 
96
+ ### Associations
67
97
 
68
- This gem is released under the MIT license, if you find it useful in your application, drop me a line and I'll post a link!
98
+ Call an association by its key name, prefaced by `get_`, and the gem will
99
+ return either an array of fetched objects or a `CVObject` from the API:
69
100
 
101
+ ```ruby
102
+ volume = ComicVine::API.volume 766
103
+ issues = volume.get_issues
104
+ chars = volume.get_character_credits
105
+ ```
70
106
 
71
- ## ToDos
72
- More Error checking
107
+ ### Errors
73
108
 
74
- Documentation
109
+ All errors raised by the gem inherit from `ComicVine::CVError`:
75
110
 
76
- Tests
111
+ | Class | Raised when |
112
+ | --- | --- |
113
+ | `CVAPIError` | the API answers with an error status code (bad key, not found, …) |
114
+ | `CVHTTPError` | the server answers with a non-2xx HTTP status (`#status` has the code) |
115
+ | `CVRateLimitError` | HTTP 420/429 persists after retries (~200 requests/resource/hour) |
116
+ | `CVConnectionError` | the connection fails or times out after all retries |
117
+ | `CVParseError` | the response body is not valid JSON |
118
+
119
+ ### Configuration
120
+
121
+ Timeouts, retries, and the User-Agent header can be tuned on `ComicVine::API`:
122
+
123
+ ```ruby
124
+ ComicVine::API.open_timeout = 10 # seconds (default 10)
125
+ ComicVine::API.read_timeout = 30 # seconds (default 30)
126
+ ComicVine::API.max_retries = 3 # retries on 420/429/5xx and connection errors
127
+ ComicVine::API.retry_base_delay = 1.0 # exponential backoff base, honors Retry-After
128
+ ComicVine::API.user_agent = "my-app/1.0"
129
+ ```
130
+
131
+ ## Development
132
+
133
+ Run the test suite with `bundle exec rake spec`, the linter with
134
+ `bundle exec rubocop`, and generate API docs with `bundle exec yard`.
77
135
 
78
136
  ## Contributing
79
137
 
@@ -82,3 +140,8 @@ Tests
82
140
  3. Commit your changes (`git commit -am 'Added some feature'`)
83
141
  4. Push to the branch (`git push origin my-new-feature`)
84
142
  5. Create new Pull Request
143
+
144
+ ## License
145
+
146
+ This gem is released under the [MIT license](LICENSE). If you find it useful
147
+ in your application, drop me a line and I'll post a link!
@@ -1,83 +1,139 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ComicVine
4
+ # Base class for paginated result lists. Carries the count values from the
5
+ # API response (`total_count`, `page_count`, `offset`, `limit`) and includes
6
+ # `Enumerable` over the contained {CVObject}s.
2
7
  class CVList
3
8
  include Enumerable
4
-
9
+
10
+ # @return [Integer] total results across all pages
5
11
  attr_reader :total_count
12
+ # @return [Integer] results on the current page
6
13
  attr_reader :page_count
14
+ # @return [Integer] offset of the current page
7
15
  attr_reader :offset
16
+ # @return [Integer] page size
8
17
  attr_reader :limit
18
+ # @return [Array<CVObject>] the objects on the current page
9
19
  attr_reader :cvos
10
-
11
- def initialize(resp)
20
+
21
+ # @param resp [Hash] a list response from the API
22
+ def initialize(resp)
12
23
  @total_count = resp['number_of_total_results']
13
24
  @page_count = resp['number_of_page_results']
14
25
  @offset = resp['offset']
15
26
  @limit = resp['limit']
16
27
  end
17
-
18
- def each
19
- @cvos.each { |c| yield c }
28
+
29
+ # Yields each {CVObject} on the current page.
30
+ def each(&)
31
+ @cvos.each(&)
20
32
  end
21
-
33
+
34
+ # @return [CVObject, nil] the last object on the current page
22
35
  def last
23
36
  @cvos.last
24
37
  end
25
-
38
+
39
+ # @return [Integer] the current page number (1-based)
26
40
  def page
27
41
  (@offset / @limit) + 1
28
42
  end
29
-
43
+
30
44
  protected
31
- def update_ivals(new_cvol)
32
- @total_count = new_cvol.total_count
33
- @offset = new_cvol.offset
34
- @limit = new_cvol.limit
35
45
 
36
- @cvos = new_cvol.cvos
37
- end
46
+ def update_ivals(new_cvol)
47
+ @total_count = new_cvol.total_count
48
+ @page_count = new_cvol.page_count
49
+ @offset = new_cvol.offset
50
+ @limit = new_cvol.limit
51
+
52
+ @cvos = new_cvol.cvos
53
+ end
38
54
  end
39
-
55
+
56
+ # A paginated list of resources (e.g. from `ComicVine::API.characters`).
57
+ # {#next_page} and {#prev_page} update the list in place, preserving the
58
+ # original options (`:filter`, `:sort`, `:field_list`, ...).
40
59
  class CVObjectList < CVList
60
+ # @return [String] the plural resource name this list was fetched from
41
61
  attr_reader :resource
42
-
43
- def initialize(resp, resc)
62
+
63
+ # @param resp [Hash] a list response from the API
64
+ # @param resc [String] the plural resource name
65
+ # @param opts [Hash] the options the list was fetched with
66
+ def initialize(resp, resc, opts = {})
44
67
  super(resp)
45
-
68
+
46
69
  @resource = resc
47
- @cvos = resp['results'].map{ |r| ComicVine::CVObject.new(r)}
70
+ @opts = opts || {}
71
+ @cvos = resp['results'].map { |r| ComicVine::CVObject.new(r) }
48
72
  end
49
-
73
+
74
+ # Advances to the next page, updating the list in place.
75
+ #
76
+ # @return [CVObjectList, nil] `self`, or `nil` if already on the last page
77
+ # @raise [CVError]
50
78
  def next_page
51
79
  return nil if (@offset + @page_count) >= @total_count
52
- update_ivals(ComicVine::API.send(@resource, {:limit => @limit, :offset => (@offset + @page_count)}))
80
+
81
+ update_ivals(ComicVine::API.send(@resource, @opts.merge(:limit => @limit, :offset => (@offset + @limit))))
82
+ self
53
83
  end
54
-
84
+
85
+ # Steps back to the previous page, updating the list in place.
86
+ #
87
+ # @return [CVObjectList, nil] `self`, or `nil` if already on the first page
88
+ # @raise [CVError]
55
89
  def prev_page
56
90
  return nil if @offset == 0
57
- update_ivals(ComicVine::API.send(@resource, {:limit => @limit, :offset => (@offset - @page_count)}))
91
+
92
+ update_ivals(ComicVine::API.send(@resource, @opts.merge(:limit => @limit, :offset => [@offset - @limit, 0].max)))
93
+ self
58
94
  end
59
95
  end
60
-
96
+
97
+ # A paginated list of search results (from {ComicVine::API.search}).
61
98
  class CVSearchList < CVList
99
+ # @return [String] the resource type(s) searched
62
100
  attr_reader :resource
101
+ # @return [String] the search query
63
102
  attr_reader :query
64
-
65
- def initialize(resp, resc, query)
103
+
104
+ # @param resp [Hash] a search response from the API
105
+ # @param resc [String] the resource type(s) searched
106
+ # @param query [String] the search query
107
+ # @param opts [Hash] the options the search was made with
108
+ def initialize(resp, resc, query, opts = {})
66
109
  super(resp)
67
-
110
+
68
111
  @resource = resc
69
112
  @query = query
70
- @cvos = resp['results'].map{ |r| ComicVine::CVObject.new(r)}
113
+ @opts = opts || {}
114
+ @cvos = resp['results'].map { |r| ComicVine::CVObject.new(r) }
71
115
  end
72
-
116
+
117
+ # Advances to the next page of results, updating the list in place.
118
+ #
119
+ # @return [CVSearchList, nil] `self`, or `nil` if already on the last page
120
+ # @raise [CVError]
73
121
  def next_page
74
122
  return nil if (@offset + @page_count) >= @total_count
75
- update_ivals(ComicVine::API.search(@resource, @query, {:limit => @limit, :page => (((@offset + @page_count) / @limit) + 1)}))
123
+
124
+ update_ivals(ComicVine::API.search(@resource, @query, @opts.merge(:limit => @limit, :page => page + 1)))
125
+ self
76
126
  end
77
-
127
+
128
+ # Steps back to the previous page of results, updating the list in place.
129
+ #
130
+ # @return [CVSearchList, nil] `self`, or `nil` if already on the first page
131
+ # @raise [CVError]
78
132
  def prev_page
79
133
  return nil if @offset == 0
80
- update_ivals(ComicVine::API.search(@resource, @query, {:limit => @limit, :page => (@offset / @limit)}))
134
+
135
+ update_ivals(ComicVine::API.search(@resource, @query, @opts.merge(:limit => @limit, :page => page - 1)))
136
+ self
81
137
  end
82
138
  end
83
- end
139
+ end
@@ -1,39 +1,52 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ComicVine
4
+ # Wraps a single resource returned by the API. Every key in the response
5
+ # becomes a reader method; nested resources that carry an `api_detail_url`
6
+ # become {CVObject}s themselves and can be expanded with `get_<key>`.
2
7
  class CVObject
8
+ # @param args [Hash] a resource hash from the API response
3
9
  def initialize(args)
4
- args.each do |k,v|
5
- self.class.class_eval { attr_reader k }
6
- v.collect! { |i| CVObject.new i } if v.kind_of?(Array) && !v.empty? && v.first.key?("api_detail_url")
7
- v = CVObject.new v if v.kind_of?(Hash) && v.key?("api_detail_url")
10
+ args.each do |k, v|
11
+ singleton_class.class_eval { attr_reader k } unless respond_to?(k)
12
+ v.collect! { |i| CVObject.new i } if v.is_a?(Array) && v.first.is_a?(Hash) && v.first.key?("api_detail_url")
13
+ v = CVObject.new v if v.is_a?(Hash) && v.key?("api_detail_url")
8
14
  instance_variable_set "@#{k}", v
9
15
  end
10
16
  end
11
-
17
+
18
+ # Retrieves the full object from this object's `api_detail_url`.
19
+ #
20
+ # @return [CVObject]
21
+ # @raise [CVError]
12
22
  def fetch
13
23
  ComicVine::API.get_details_by_url(@api_detail_url)
14
24
  end
15
-
16
- def method_missing(method_sym, *arguments, &block)
25
+
26
+ # Resolves `get_<key>` calls: fetches the full object(s) behind an
27
+ # association, or returns the plain value if it isn't an association.
28
+ def method_missing(method_sym, *arguments, &)
17
29
  if method_sym.to_s =~ /^get_(.*)$/
18
- key = method_sym.to_s.sub "get_", ""
30
+ key = $1
19
31
  if instance_variable_defined?("@#{key}")
20
32
  item = instance_variable_get("@#{key}")
21
- if item.kind_of?(Array) && item.first.kind_of?(CVObject)
22
- res = []
23
- item.each do |i|
24
- res << i.fetch
25
- end
26
- return res
27
- end
28
- if item.kind_of?(CVObject)
29
- return item.fetch
33
+ if item.is_a?(Array) && item.first.is_a?(CVObject)
34
+ item.map(&:fetch)
35
+ elsif item.is_a?(CVObject)
36
+ item.fetch
37
+ else
38
+ item
30
39
  end
31
40
  else
32
41
  super
33
42
  end
34
- elsif
43
+ else
35
44
  super
36
45
  end
37
46
  end
47
+
48
+ def respond_to_missing?(method_sym, include_private = false)
49
+ method_sym.to_s =~ /^get_(.*)$/ ? instance_variable_defined?("@#{$1}") : super
50
+ end
38
51
  end
39
- end
52
+ end
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ComicVine
2
- VERSION = "0.1.5"
4
+ # The version of the comic_vine gem.
5
+ VERSION = "1.0.0"
3
6
  end