metal_archives 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 07ba21e5ea2f91a35d841543732f2cad9faf3943
4
+ data.tar.gz: fd48c5f0ae010184778ed9d6137004684beb1c64
5
+ SHA512:
6
+ metadata.gz: ed2595557edfcb18ff9d74822039e6d8d846e9d9348ba73ffd41dd1d4bc8d06dddd48472ab833c2afd17cd120b3cd4e63c1ddb7f081a780a95b87c8bb4279149
7
+ data.tar.gz: 87d80f9d69daa76339513028aba3c6d0179b130847c9e1314ce40c9fe7a1a8f993370dd488e424fb1734bde1dc0fff48cce0540595e5fc8d546059803b01f638
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ # Ruby version
2
+ .ruby-version
3
+ .ruby-gemset
4
+
5
+ # Temp files
6
+ .byebug_history
7
+
8
+ # RDoc
9
+ html/
10
+
11
+ Gemfile.lock
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.0.1
4
+
5
+ Initial release
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
File without changes
data/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # Metal Archives Web Service Wrapper
2
+
3
+ ## Installation
4
+
5
+ ```shell
6
+ $ gem install metal_archives
7
+ ```
8
+
9
+ or add it to your Gemfile
10
+
11
+ ```ruby
12
+ gem 'metal_archives'
13
+ ```
14
+
15
+ ```shell
16
+ $ bundle install
17
+ ```
18
+
19
+ ## Configuration
20
+
21
+ ```ruby
22
+ require 'active_support/cache'
23
+
24
+ MetalArchives.configure do |c|
25
+ # Application identity (required)
26
+ c.app_name = "My App"
27
+ c.app_version = "1.0"
28
+ c.app_contact = "support@mymusicapp.com"
29
+
30
+ # Cache config (optional)
31
+ c.enable_cache = true
32
+ c.cache_store = ActiveSupport::Cache.lookup_store(:file_store, '/tmp/metal_archives-cache')
33
+
34
+ # Request throttling (optional, overrides defaults)
35
+ c.request_rate = 1
36
+ c.request_timeout = 3
37
+
38
+ # Print debugging information
39
+ c.debug = false
40
+ end
41
+ ```
42
+
43
+ ## Usage
44
+
45
+ ```ruby
46
+ require 'metal_archives'
47
+
48
+ # Search for bands
49
+ @alquimia_list = MetalArchives::Band.search('Alquimia')
50
+
51
+ # Find bands by name
52
+ @iron_maiden = MetalArchives::Band.find_by(:name => 'Iron Maiden')
53
+
54
+ # Find bands by attributes
55
+ require 'countries'
56
+
57
+ @bands_in_belgium = MetalArchives::Band.search_by :country => ISO3166::Country['BE']
58
+ @bands_formed_in_1990 = MetalArchives::Band.search_by :year => Range.new(Date.new(1990))
59
+
60
+ # Metal Archives' usual tips apply
61
+
62
+ @bands_containing_hell = MetalArchives::Band.search_by :name => '*hell*'
63
+ @non_melodic_death_bands = MetalArchives::Band.search_by :genre => 'death -melodic'
64
+ ```
65
+
66
+ Refer to the model's RDoc documentation for full documentation.
67
+
68
+ ## Debugging
69
+
70
+ Turn on `debug` in the configuration block to enable logging HTTP requests and responses.
71
+
72
+ ```
73
+ $ irb -r metal_archives
74
+ ```
75
+
76
+ ## Testing
77
+ ```
78
+ $ bundle exec rake test
79
+ ```
80
+
81
+ ## Documentation
82
+ ```
83
+ $ bundle exec rake rdoc
84
+ ```
85
+
86
+ ## Copyright
87
+
88
+ Copyright 2016 Florian Dejonckheere. See `LICENSE` for further details.
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.test_files = FileList['test/*/*_test.rb']
7
+ t.verbose = true
8
+ end
9
+
10
+ require 'rdoc/task'
11
+ RDoc::Task.new do |rdoc|
12
+ rdoc.main = "README.md"
13
+ rdoc.rdoc_files.include("README.md", "lib/**/*.rb")
14
+ end
@@ -0,0 +1,22 @@
1
+ require 'metal_archives/version'
2
+ require 'metal_archives/configuration'
3
+ require 'metal_archives/error'
4
+
5
+ require 'metal_archives/models/base_model'
6
+ require 'metal_archives/models/range'
7
+ require 'metal_archives/models/label'
8
+ require 'metal_archives/models/artist'
9
+ require 'metal_archives/models/band'
10
+
11
+ require 'metal_archives/parsers/parser_helper'
12
+ require 'metal_archives/parsers/label'
13
+ require 'metal_archives/parsers/artist'
14
+ require 'metal_archives/parsers/band'
15
+
16
+ require 'metal_archives/http_client'
17
+
18
+ ##
19
+ # Metal Archives Ruby API
20
+ #
21
+ module MetalArchives
22
+ end
@@ -0,0 +1,81 @@
1
+ module MetalArchives
2
+ class << self
3
+ ##
4
+ # API configuration
5
+ #
6
+ # Instance of rdoc-ref:MetalArchives::Configuration
7
+ #
8
+ attr_accessor :config
9
+
10
+ ##
11
+ # Configure API options.
12
+ #
13
+ # A block must be specified, to which a
14
+ # rdoc-ref:MetalArchives::Configuration parameter will be passed.
15
+ #
16
+ # Raises rdoc-ref:InvalidConfigurationException
17
+ #
18
+ def configure
19
+ raise MetalArchives::Errors::InvalidConfigurationError, 'No configuration block given' unless block_given?
20
+ yield MetalArchives.config ||= MetalArchives::Configuration.new
21
+
22
+ raise MetalArchives::Errors::InvalidConfigurationError, 'app_name has not been configured' unless MetalArchives.config.app_name and not MetalArchives.config.app_name.empty?
23
+ raise MetalArchives::Errors::InvalidConfigurationError, 'app_version has not been configured' unless MetalArchives.config.app_version and not MetalArchives.config.app_version.empty?
24
+ raise MetalArchives::Errors::InvalidConfigurationError, 'app_contact has not been configured' unless MetalArchives.config.app_contact and not MetalArchives.config.app_contact.empty?
25
+
26
+ end
27
+ end
28
+
29
+ ##
30
+ # Contains configuration options
31
+ #
32
+ class Configuration
33
+ ##
34
+ # *Required.* Application name (used in request header)
35
+ #
36
+ attr_accessor :app_name
37
+
38
+ ##
39
+ # *Required.* Application version (used in request header)
40
+ #
41
+ attr_accessor :app_version
42
+
43
+ ##
44
+ # *Required.* Application contact email (used in request header)
45
+ #
46
+ attr_accessor :app_contact
47
+
48
+ ##
49
+ # Whether to enable the cache
50
+ #
51
+ attr_accessor :enable_cache
52
+
53
+ ##
54
+ # ActiveSupport::Cache compatible store
55
+ #
56
+ attr_accessor :cache_store
57
+
58
+ ##
59
+ # Request throttling rate (in seconds per request per path)
60
+ #
61
+ attr_accessor :request_rate
62
+
63
+ ##
64
+ # Request timeout (in seconds per request per path)
65
+ #
66
+ attr_accessor :request_timeout
67
+
68
+ ##
69
+ # Print debug information
70
+ #
71
+ attr_accessor :debug
72
+
73
+ ##
74
+ # Default configuration values
75
+ #
76
+ def initialize
77
+ @throttle_rate = 1
78
+ @throttle_wait = 3
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,35 @@
1
+ module MetalArchives
2
+ ##
3
+ # MetalArchives gem specific errors
4
+ #
5
+ module Errors
6
+ ##
7
+ # Generic error
8
+ #
9
+ class Error < StandardError; end
10
+
11
+ ##
12
+ # Error in data
13
+ #
14
+ class DataError < Error; end
15
+
16
+ ##
17
+ # No or invalid configuration found
18
+ #
19
+ class InvalidConfigurationError < Error; end
20
+
21
+ ##
22
+ # Error parsing value
23
+ #
24
+ class ParserError < Error; end
25
+
26
+ ##
27
+ # Functionality not implemented (yet)
28
+ class NotImplementedError < Error; end
29
+
30
+ ##
31
+ # Error in backend response
32
+ #
33
+ class APIError < Error; end
34
+ end
35
+ end
@@ -0,0 +1,75 @@
1
+ require 'faraday'
2
+ require 'faraday-http-cache'
3
+ require 'faraday_throttler'
4
+
5
+ module MetalArchives
6
+ ##
7
+ # HTTP request client
8
+ #
9
+ class HTTPClient # :nodoc:
10
+ class << self
11
+
12
+ ##
13
+ # Retrieve a HTTP resource
14
+ #
15
+ def get(*params)
16
+ response = client.get *params
17
+
18
+ raise Errors::APIError, response.status if response.status >= 400
19
+
20
+ response
21
+ rescue Faraday::Error::ClientError => e
22
+ raise Errors::APIError, e
23
+ end
24
+
25
+ private
26
+ ##
27
+ # Retrieve a HTTP client
28
+ #
29
+ def client
30
+ raise Errors::InvalidConfigurationError, 'Not configured yet' unless MetalArchives.config
31
+
32
+ @faraday ||= Faraday.new do |f|
33
+ f.request :url_encoded # form-encode POST params
34
+ f.response :logger if !!MetalArchives.config.debug # log requests to STDOUT
35
+
36
+ f.use MetalArchives::Middleware
37
+ f.use Faraday::HttpCache,
38
+ :store => MetalArchives.config.cache_store if !!MetalArchives.config.enable_cache
39
+ f.use :throttler,
40
+ :rate => MetalArchives.config.request_rate,
41
+ :wait => MetalArchives.config.request_timeout
42
+
43
+ f.adapter Faraday.default_adapter
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ ##
50
+ # Faraday middleware
51
+ #
52
+ class Middleware < Faraday::Middleware # :nodoc:
53
+ def call(env)
54
+ env[:request_headers].merge!(
55
+ 'User-Agent' => user_agent_string,
56
+ 'Via' => via_string,
57
+ 'Accept' => accept_string
58
+ )
59
+ @app.call(env)
60
+ end
61
+
62
+ private
63
+ def user_agent_string
64
+ "#{MetalArchives.config.app_name}/#{MetalArchives.config.app_version} ( #{MetalArchives.config.app_contact} )"
65
+ end
66
+
67
+ def accept_string
68
+ 'application/json'
69
+ end
70
+
71
+ def via_string
72
+ "gem metal_archives/#{VERSION}"
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,216 @@
1
+ require 'date'
2
+ require 'countries'
3
+
4
+ module MetalArchives
5
+
6
+ ##
7
+ # Represents a single performer (but not a solo artist)
8
+ #
9
+ class Artist < BaseModel
10
+ ##
11
+ # :attr_reader: id
12
+ #
13
+ # Returns +Integer+
14
+ #
15
+ property :id, :type => Integer
16
+
17
+ ##
18
+ # :attr_reader: name
19
+ #
20
+ # Returns +String+
21
+ #
22
+ property :name
23
+
24
+ ##
25
+ # :attr_reader: aliases
26
+ #
27
+ # Returns +Array+ of +String+
28
+ #
29
+ property :aliases, :multiple => true
30
+
31
+ ##
32
+ # :attr_reader: country
33
+ #
34
+ # Returns +ISO3166::Country+
35
+ #
36
+ property :country, :type => ISO3166::Country
37
+
38
+ ##
39
+ # :attr_reader: location
40
+ #
41
+ # Returns +String+
42
+ #
43
+ property :location
44
+
45
+ ##
46
+ # :attr_reader: date_of_birth
47
+ #
48
+ # Returns +Date+
49
+ #
50
+ property :date_of_birth, :type => Date
51
+
52
+ ##
53
+ # :attr_reader: date_of_death
54
+ #
55
+ # Returns +Date+
56
+ #
57
+ property :date_of_death, :type => Date
58
+
59
+ ##
60
+ # :attr_reader: cause_of_death
61
+ #
62
+ # Returns +String+
63
+ #
64
+ property :cause_of_death
65
+
66
+ ##
67
+ # :attr_reader: gender
68
+ #
69
+ # Returns +Symbol+, either +:male+ or +:female+
70
+ #
71
+ enum :gender, :values => [:male, :female]
72
+
73
+ ##
74
+ # :attr_reader: biography
75
+ #
76
+ # Returns raw HTML +String+
77
+ #
78
+ property :biography
79
+
80
+ ##
81
+ # :attr_reader: trivia
82
+ #
83
+ # Returns raw HTML +String+
84
+ #
85
+ property :trivia
86
+
87
+ ##
88
+ # :attr_reader: links
89
+ #
90
+ # Returns +Array+ of +Hash+ containing the following keys
91
+ #
92
+ # [+similar+]
93
+ # - +:url+: +String+
94
+ # - +:type+: +Symbol+, either +:official+, +:unofficial+ or +:unlisted_bands+
95
+ # - +:title+: +String+
96
+ #
97
+ property :links, :multiple => true
98
+
99
+ # TODO: active bands/albums
100
+ # TODO: past bands/albums
101
+ # TODO: guest bands/albums
102
+ # TODO: misc bands/albums
103
+
104
+ protected
105
+ ##
106
+ # Fetch the data and assemble the model
107
+ #
108
+ # Raises rdoc-ref:MetalArchives::Errors::APIError
109
+ #
110
+ def assemble # :nodoc:
111
+ ## Base attributes
112
+ url = "http://www.metal-archives.com/artist/view/id/#{id}"
113
+ response = HTTPClient.get url
114
+
115
+ properties = Parsers::Artist.parse_html response.body
116
+
117
+ ## Biography
118
+ url = "http://www.metal-archives.com/artist/read-more/id/#{id}/field/biography"
119
+ response = HTTPClient.get url
120
+
121
+ properties[:biography] = response.body
122
+
123
+ ## Trivia
124
+ url = "http://www.metal-archives.com/artist/read-more/id/#{id}/field/trivia"
125
+ response = HTTPClient.get url
126
+
127
+ properties[:trivia] = response.body
128
+
129
+ ## Related links
130
+ url = "http://www.metal-archives.com/link/ajax-list/type/person/id/#{id}"
131
+ response = HTTPClient.get url
132
+
133
+ properties[:links] = Parsers::Artist.parse_links_html response.body
134
+
135
+ ## Use constructor to fill properties
136
+ initialize properties
137
+ end
138
+
139
+ class << self
140
+ ##
141
+ # Find by ID
142
+ #
143
+ # Returns rdoc-ref:Artist, even when ID is invalid (because the data is lazily fetched)
144
+ #
145
+ # [+id+]
146
+ # +Integer+
147
+ #
148
+ def find(id)
149
+ Artist.new :id => id
150
+ end
151
+
152
+ ##
153
+ # Find by attributes
154
+ #
155
+ # Returns rdoc-ref:Artist or nil when ID is invalid
156
+ #
157
+ # [+query+]
158
+ # Hash containing one or more of the following keys:
159
+ # - +:name+: +String+
160
+ #
161
+ def find_by(query)
162
+ url = 'http://www.metal-archives.com/search/ajax-artist-search/'
163
+ params = Parsers::Artist.map_params query
164
+
165
+ response = HTTPClient.get url, params
166
+ json = JSON.parse response.body
167
+
168
+ return nil if json['aaData'].empty?
169
+
170
+ data = json['aaData'].first
171
+ id = Nokogiri::HTML(data.first).xpath('//a/@href').first.value.gsub('\\', '').split('/').last.gsub(/\D/, '').to_i
172
+
173
+ Artist.new :id => id
174
+ rescue Errors::APIError
175
+ nil
176
+ end
177
+
178
+ ##
179
+ # Search by name
180
+ #
181
+ # Returns (possibly empty) +Array+ of rdoc-ref:Artist
182
+ #
183
+ # [+name+]
184
+ # +String+
185
+ #
186
+ def search(name)
187
+ objects = []
188
+
189
+ url = 'http://www.metal-archives.com/search/ajax-artist-search/'
190
+ query = {
191
+ :name => name,
192
+ :iDisplayStart => 0
193
+ }
194
+
195
+ loop do
196
+ params = Parsers::Artist.map_params query
197
+
198
+ response = HTTPClient.get url, params
199
+ json = JSON.parse response.body
200
+
201
+ json['aaData'].each do |data|
202
+ # Create Artist object for every ID in the results list
203
+ id = Nokogiri::HTML(data.first).xpath('//a/@href').first.value.gsub('\\', '').split('/').last.gsub(/\D/, '').to_i
204
+ objects << Artist.new(:id => id)
205
+ end
206
+
207
+ break if objects.length == json['iTotalRecords']
208
+
209
+ query[:iDisplayStart] += 200
210
+ end
211
+
212
+ objects
213
+ end
214
+ end
215
+ end
216
+ end