metal_archives 2.2.0 → 3.1.1
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 +5 -5
- data/.github/workflows/ci.yml +93 -0
- data/.gitignore +6 -6
- data/.overcommit.yml +35 -0
- data/.rspec +2 -0
- data/.rubocop.yml +66 -6
- data/CHANGELOG.md +33 -0
- data/Gemfile +1 -1
- data/LICENSE.md +17 -4
- data/README.md +65 -86
- data/Rakefile +8 -7
- data/bin/console +38 -0
- data/bin/setup +8 -0
- data/config/inflections.rb +7 -0
- data/config/initializers/.keep +0 -0
- data/docker-compose.yml +23 -0
- data/lib/metal_archives.rb +82 -27
- data/lib/metal_archives/cache/base.rb +40 -0
- data/lib/metal_archives/cache/memory.rb +68 -0
- data/lib/metal_archives/cache/null.rb +22 -0
- data/lib/metal_archives/cache/redis.rb +49 -0
- data/lib/metal_archives/{utils/collection.rb → collection.rb} +3 -5
- data/lib/metal_archives/configuration.rb +33 -50
- data/lib/metal_archives/{error.rb → errors.rb} +9 -1
- data/lib/metal_archives/http_client.rb +45 -44
- data/lib/metal_archives/models/artist.rb +90 -45
- data/lib/metal_archives/models/band.rb +77 -52
- data/lib/metal_archives/models/base.rb +225 -0
- data/lib/metal_archives/models/label.rb +14 -15
- data/lib/metal_archives/models/release.rb +25 -29
- data/lib/metal_archives/parsers/artist.rb +86 -50
- data/lib/metal_archives/parsers/band.rb +155 -88
- data/lib/metal_archives/parsers/base.rb +14 -0
- data/lib/metal_archives/parsers/country.rb +21 -0
- data/lib/metal_archives/parsers/date.rb +31 -0
- data/lib/metal_archives/parsers/genre.rb +67 -0
- data/lib/metal_archives/parsers/label.rb +39 -31
- data/lib/metal_archives/parsers/parser.rb +18 -63
- data/lib/metal_archives/parsers/release.rb +98 -89
- data/lib/metal_archives/parsers/year.rb +31 -0
- data/lib/metal_archives/version.rb +12 -1
- data/metal_archives.env.example +10 -0
- data/metal_archives.gemspec +43 -28
- data/nginx/default.conf +60 -0
- metadata +179 -74
- data/.travis.yml +0 -12
- data/lib/metal_archives/middleware/cache_check.rb +0 -20
- data/lib/metal_archives/middleware/encoding.rb +0 -16
- data/lib/metal_archives/middleware/headers.rb +0 -38
- data/lib/metal_archives/middleware/rewrite_endpoint.rb +0 -38
- data/lib/metal_archives/models/base_model.rb +0 -215
- data/lib/metal_archives/utils/lru_cache.rb +0 -61
- data/lib/metal_archives/utils/nil_date.rb +0 -99
- data/lib/metal_archives/utils/range.rb +0 -66
- data/spec/configuration_spec.rb +0 -96
- data/spec/factories/artist_factory.rb +0 -37
- data/spec/factories/band_factory.rb +0 -60
- data/spec/factories/nil_date_factory.rb +0 -9
- data/spec/factories/range_factory.rb +0 -8
- data/spec/models/artist_spec.rb +0 -138
- data/spec/models/band_spec.rb +0 -164
- data/spec/models/base_model_spec.rb +0 -219
- data/spec/models/release_spec.rb +0 -133
- data/spec/parser_spec.rb +0 -19
- data/spec/spec_helper.rb +0 -111
- data/spec/support/factory_girl.rb +0 -5
- data/spec/support/metal_archives.rb +0 -33
- data/spec/utils/collection_spec.rb +0 -72
- data/spec/utils/lru_cache_spec.rb +0 -53
- data/spec/utils/nil_date_spec.rb +0 -156
- data/spec/utils/range_spec.rb +0 -62
@@ -0,0 +1,225 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MetalArchives
|
4
|
+
##
|
5
|
+
# Abstract model class
|
6
|
+
#
|
7
|
+
class Base
|
8
|
+
##
|
9
|
+
# Generic shallow copy constructor
|
10
|
+
#
|
11
|
+
def initialize(attributes = {})
|
12
|
+
raise Errors::NotImplementedError, "no :id property in model" unless respond_to? :id
|
13
|
+
|
14
|
+
set(**attributes)
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Set properties
|
19
|
+
#
|
20
|
+
def set(**attributes)
|
21
|
+
attributes.each { |key, value| instance_variable_set(:"@#{key}", value) }
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# Returns true if two objects have the same type and id
|
26
|
+
#
|
27
|
+
def ==(other)
|
28
|
+
other.is_a?(self.class) &&
|
29
|
+
id == other.id
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Fetch, parse and load the data
|
34
|
+
#
|
35
|
+
# [Raises]
|
36
|
+
# - rdoc-ref:Errors::InvalidIDError when no id
|
37
|
+
# - rdoc-ref:Errors::APIError when receiving a status code >= 400 (except 404)
|
38
|
+
#
|
39
|
+
def load!
|
40
|
+
raise Errors::InvalidIDError, "no id present" unless id
|
41
|
+
|
42
|
+
# Use constructor to set attributes
|
43
|
+
set(**assemble)
|
44
|
+
|
45
|
+
@loaded = true
|
46
|
+
MetalArchives.cache[self.class.cache(id)] = self
|
47
|
+
rescue StandardError => e
|
48
|
+
# Don't cache invalid requests
|
49
|
+
MetalArchives.cache.delete self.class.cache(id)
|
50
|
+
raise e
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Whether or not the object is currently loaded
|
55
|
+
#
|
56
|
+
def loaded?
|
57
|
+
@loaded ||= false
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Whether or not the object is currently cached
|
62
|
+
#
|
63
|
+
def cached?
|
64
|
+
loaded? && MetalArchives.cache.include?(self.class.cache(id))
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# String representation
|
69
|
+
#
|
70
|
+
def inspect
|
71
|
+
"#<#{self.class.name} @id=#{@id} @name=\"#{@name}\">"
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
##
|
77
|
+
# Fetch the data and assemble the model
|
78
|
+
#
|
79
|
+
# Override this method
|
80
|
+
#
|
81
|
+
# [Raises]
|
82
|
+
# - rdoc-ref:Errors::InvalidIDError when no or invalid id
|
83
|
+
# - rdoc-ref:Errors::APIError when receiving a status code >= 400 (except 404)
|
84
|
+
#
|
85
|
+
def assemble
|
86
|
+
raise Errors::NotImplementedError, "method :assemble not implemented"
|
87
|
+
end
|
88
|
+
|
89
|
+
class << self
|
90
|
+
##
|
91
|
+
# Declared properties
|
92
|
+
#
|
93
|
+
def properties
|
94
|
+
@properties ||= {}
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# Generate cache key for id
|
99
|
+
#
|
100
|
+
def cache(id)
|
101
|
+
"#{self.class.name}//#{id}"
|
102
|
+
end
|
103
|
+
|
104
|
+
protected
|
105
|
+
|
106
|
+
##
|
107
|
+
# Defines a model property.
|
108
|
+
#
|
109
|
+
# [+name+]
|
110
|
+
# Name of the property
|
111
|
+
#
|
112
|
+
# [+opts+]
|
113
|
+
# [+type+]
|
114
|
+
# Data type of property (a constant)
|
115
|
+
#
|
116
|
+
# Default: +String+
|
117
|
+
#
|
118
|
+
# [+multiple+]
|
119
|
+
# Whether or not the property has multiple values (which
|
120
|
+
# turns it into an +Array+ of +type+)
|
121
|
+
#
|
122
|
+
def property(name, opts = {})
|
123
|
+
properties[name] = opts
|
124
|
+
|
125
|
+
# property
|
126
|
+
define_method(name) do
|
127
|
+
# Load only when not loaded or id property
|
128
|
+
load! unless loaded? || name == :id
|
129
|
+
|
130
|
+
instance_variable_get(:"@#{name}")
|
131
|
+
end
|
132
|
+
|
133
|
+
# property?
|
134
|
+
define_method("#{name}?") do
|
135
|
+
send(name).present?
|
136
|
+
end
|
137
|
+
|
138
|
+
# property=
|
139
|
+
define_method("#{name}=") do |value|
|
140
|
+
return instance_variable_set(:"@#{name}", value) if value.nil?
|
141
|
+
|
142
|
+
# Check value type
|
143
|
+
type = opts[:type] || String
|
144
|
+
if opts[:multiple]
|
145
|
+
raise Errors::TypeError, "invalid type #{value.class}, must be Array for #{name}" unless value.is_a? Array
|
146
|
+
|
147
|
+
value.each do |val|
|
148
|
+
raise Errors::TypeError, "invalid type #{val.class}, must be #{type} for #{name}" unless val.is_a? type
|
149
|
+
end
|
150
|
+
else
|
151
|
+
raise Errors::TypeError, "invalid type #{value.class}, must be #{type} for #{name}" unless value.is_a? type
|
152
|
+
end
|
153
|
+
|
154
|
+
instance_variable_set(:"@#{name}", value)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
##
|
159
|
+
# Defines a model enum property.
|
160
|
+
#
|
161
|
+
# [+name+]
|
162
|
+
# Name of the property
|
163
|
+
#
|
164
|
+
# [+opts+]
|
165
|
+
# [+values+]
|
166
|
+
# Required. An array of possible values
|
167
|
+
#
|
168
|
+
# [+multiple+]
|
169
|
+
# Whether or not the property has multiple values (which
|
170
|
+
# turns it into an +Array+ of +type+)
|
171
|
+
#
|
172
|
+
def enum(name, opts)
|
173
|
+
raise ArgumentError, "opts[:values] is required" unless opts && opts[:values]
|
174
|
+
|
175
|
+
properties[name] = opts
|
176
|
+
|
177
|
+
# property
|
178
|
+
define_method(name) do
|
179
|
+
load! unless loaded?
|
180
|
+
|
181
|
+
instance_variable_get(:"@#{name}")
|
182
|
+
end
|
183
|
+
|
184
|
+
# property?
|
185
|
+
define_method("#{name}?") do
|
186
|
+
load! unless loaded? && instance_variable_defined?("@#{name}")
|
187
|
+
|
188
|
+
property = instance_variable_get(:"@#{name}")
|
189
|
+
property.respond_to?(:empty?) ? !property.empty? : !property.nil?
|
190
|
+
end
|
191
|
+
|
192
|
+
# property=
|
193
|
+
define_method("#{name}=") do |value|
|
194
|
+
# Check enum type
|
195
|
+
if opts[:multiple]
|
196
|
+
raise Errors::TypeError, "invalid enum value #{value}, must be Array for #{name}" unless value.is_a? Array
|
197
|
+
|
198
|
+
value.each do |val|
|
199
|
+
raise Errors::TypeError, "invalid enum value #{val} for #{name}" unless opts[:values].include? val
|
200
|
+
end
|
201
|
+
else
|
202
|
+
raise Errors::TypeError, "invalid enum value #{value} for #{name}" unless opts[:values].include? value
|
203
|
+
end
|
204
|
+
|
205
|
+
instance_variable_set(:"@#{name}", value)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
##
|
210
|
+
# Defines a model boolean property. This method is an alias for <tt>enum name, :values => [true, false]</tt>
|
211
|
+
#
|
212
|
+
# [+name+]
|
213
|
+
# Name of the property
|
214
|
+
#
|
215
|
+
# [+opts+]
|
216
|
+
# [+multiple+]
|
217
|
+
# Whether or not the property has multiple values (which
|
218
|
+
# turns it into an +Array+ of +type+)
|
219
|
+
#
|
220
|
+
def boolean(name, opts = {})
|
221
|
+
enum name, opts.merge(values: [true, false])
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
@@ -1,19 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "date"
|
4
|
+
require "countries"
|
5
5
|
|
6
6
|
module MetalArchives
|
7
7
|
##
|
8
8
|
# Represents a record label
|
9
9
|
#
|
10
|
-
class Label <
|
10
|
+
class Label < Base
|
11
11
|
##
|
12
12
|
# :attr_reader: id
|
13
13
|
#
|
14
14
|
# Returns +Integer+
|
15
15
|
#
|
16
|
-
property :id, :
|
16
|
+
property :id, type: Integer
|
17
17
|
|
18
18
|
##
|
19
19
|
# :attr_reader: name
|
@@ -46,7 +46,7 @@ module MetalArchives
|
|
46
46
|
# - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
|
47
47
|
# - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
|
48
48
|
#
|
49
|
-
property :country, :
|
49
|
+
property :country, type: ISO3166::Country
|
50
50
|
|
51
51
|
##
|
52
52
|
# :attr_reader: phone
|
@@ -68,25 +68,25 @@ module MetalArchives
|
|
68
68
|
# - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
|
69
69
|
# - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
|
70
70
|
#
|
71
|
-
property :specializations, :
|
71
|
+
property :specializations, multiple: true
|
72
72
|
|
73
73
|
##
|
74
74
|
# :attr_reader: date_founded
|
75
75
|
#
|
76
|
-
# Returns +
|
76
|
+
# Returns +Date+
|
77
77
|
#
|
78
78
|
# [Raises]
|
79
79
|
# - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
|
80
80
|
# - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
|
81
81
|
#
|
82
|
-
property :date_founded, :
|
82
|
+
property :date_founded, type: Date
|
83
83
|
|
84
84
|
##
|
85
85
|
# :attr_reader: sub_labels
|
86
86
|
#
|
87
87
|
# Returns +Array+ of rdoc-ref:Label
|
88
88
|
#
|
89
|
-
property :sub_labels, :
|
89
|
+
property :sub_labels, type: Label, multiple: true
|
90
90
|
|
91
91
|
##
|
92
92
|
# :attr_reader: online_shopping
|
@@ -100,14 +100,14 @@ module MetalArchives
|
|
100
100
|
#
|
101
101
|
# Returns +Hash+ with the following keys: +title+, +content+
|
102
102
|
#
|
103
|
-
property :contact, :
|
103
|
+
property :contact, type: Hash, multiple: true
|
104
104
|
|
105
105
|
##
|
106
106
|
# :attr_reader: status
|
107
107
|
#
|
108
108
|
# Returns +:active+, +:closed+ or +:unknown+
|
109
109
|
#
|
110
|
-
enum :status, :
|
110
|
+
enum :status, values: [:active, :closed, :unknown]
|
111
111
|
|
112
112
|
class << self
|
113
113
|
##
|
@@ -116,8 +116,7 @@ module MetalArchives
|
|
116
116
|
# Returns +Array+ of rdoc-ref:Label
|
117
117
|
#
|
118
118
|
def search(_name)
|
119
|
-
|
120
|
-
results
|
119
|
+
[]
|
121
120
|
end
|
122
121
|
|
123
122
|
##
|
@@ -128,8 +127,8 @@ module MetalArchives
|
|
128
127
|
def find_by_name(name, id)
|
129
128
|
client.find_resource(
|
130
129
|
:band,
|
131
|
-
:
|
132
|
-
:
|
130
|
+
name: name,
|
131
|
+
id: id,
|
133
132
|
)
|
134
133
|
end
|
135
134
|
end
|
@@ -1,18 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "date"
|
4
|
+
require "nokogiri"
|
4
5
|
|
5
6
|
module MetalArchives
|
6
7
|
##
|
7
8
|
# Represents a release
|
8
9
|
#
|
9
|
-
class Release <
|
10
|
+
class Release < Base
|
10
11
|
##
|
11
12
|
# :attr_reader: id
|
12
13
|
#
|
13
14
|
# Returns +Integer+
|
14
15
|
#
|
15
|
-
property :id, :
|
16
|
+
property :id, type: Integer
|
16
17
|
|
17
18
|
##
|
18
19
|
# :attr_reader: title
|
@@ -36,18 +37,18 @@ module MetalArchives
|
|
36
37
|
# - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
|
37
38
|
# - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
|
38
39
|
#
|
39
|
-
enum :type, :
|
40
|
+
enum :type, values: [:full_length, :live, :demo, :single, :ep, :video, :boxed_set, :split, :compilation, :split_video, :collaboration]
|
40
41
|
|
41
42
|
##
|
42
43
|
# :attr_reader: date_released
|
43
44
|
#
|
44
|
-
# Returns rdoc-ref:
|
45
|
+
# Returns rdoc-ref:Date
|
45
46
|
#
|
46
47
|
# [Raises]
|
47
48
|
# - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
|
48
49
|
# - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
|
49
50
|
#
|
50
|
-
property :date_released, :
|
51
|
+
property :date_released, type: Date
|
51
52
|
|
52
53
|
##
|
53
54
|
# :attr_reader_: catalog_id
|
@@ -76,7 +77,7 @@ module MetalArchives
|
|
76
77
|
##
|
77
78
|
# :attr_reader: format
|
78
79
|
#
|
79
|
-
# Returns +:cd+, +:cassette+, +:vinyl+, +:vhs+, +:dvd+, +:digital+, +:blu_ray+, +:other+
|
80
|
+
# Returns +:cd+, +:cassette+, +:vinyl+, +:vhs+, +:dvd+, +:digital+, +:blu_ray+, +:other+, +:unknown+
|
80
81
|
#
|
81
82
|
# [Raises]
|
82
83
|
# - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
|
@@ -123,12 +124,9 @@ module MetalArchives
|
|
123
124
|
#
|
124
125
|
def assemble # :nodoc:
|
125
126
|
## Base attributes
|
126
|
-
|
127
|
-
response = HTTPClient.get url
|
127
|
+
response = MetalArchives.http.get "/albums/view/id/#{id}"
|
128
128
|
|
129
|
-
|
130
|
-
|
131
|
-
properties
|
129
|
+
Parsers::Release.parse_html response.to_s
|
132
130
|
end
|
133
131
|
|
134
132
|
class << self
|
@@ -141,9 +139,9 @@ module MetalArchives
|
|
141
139
|
# +Integer+
|
142
140
|
#
|
143
141
|
def find(id)
|
144
|
-
return cache[id] if cache.include? id
|
142
|
+
return MetalArchives.cache[cache(id)] if MetalArchives.cache.include? cache(id)
|
145
143
|
|
146
|
-
Release.new :
|
144
|
+
Release.new id: id
|
147
145
|
end
|
148
146
|
|
149
147
|
##
|
@@ -199,16 +197,15 @@ module MetalArchives
|
|
199
197
|
# - +:formats+: +Array+ of +Symbol+, see rdoc-ref:Release.format
|
200
198
|
#
|
201
199
|
def find_by(query)
|
202
|
-
url = "#{MetalArchives.config.default_endpoint}search/ajax-advanced/searching/albums"
|
203
200
|
params = Parsers::Release.map_params query
|
204
201
|
|
205
|
-
response =
|
206
|
-
json = JSON.parse response.
|
202
|
+
response = MetalArchives.http.get "/search/ajax-advanced/searching/albums", params
|
203
|
+
json = JSON.parse response.to_s
|
207
204
|
|
208
|
-
return nil if json[
|
205
|
+
return nil if json["aaData"].empty?
|
209
206
|
|
210
|
-
data = json[
|
211
|
-
id = Nokogiri::HTML(data[1]).xpath(
|
207
|
+
data = json["aaData"].first
|
208
|
+
id = Nokogiri::HTML(data[1]).xpath("//a/@href").first.value.delete('\\').split("/").last.gsub(/\D/, "").to_i
|
212
209
|
|
213
210
|
find id
|
214
211
|
end
|
@@ -285,8 +282,6 @@ module MetalArchives
|
|
285
282
|
# - +:formats+: +Array+ of +Symbol+, see rdoc-ref:Release.format
|
286
283
|
#
|
287
284
|
def search_by(query)
|
288
|
-
url = "#{MetalArchives.config.default_endpoint}search/ajax-advanced/searching/albums"
|
289
|
-
|
290
285
|
params = Parsers::Release.map_params query
|
291
286
|
|
292
287
|
l = lambda do
|
@@ -295,16 +290,16 @@ module MetalArchives
|
|
295
290
|
if @max_items && @start >= @max_items
|
296
291
|
[]
|
297
292
|
else
|
298
|
-
response =
|
299
|
-
json = JSON.parse response.
|
293
|
+
response = MetalArchives.http.get "/search/ajax-advanced/searching/albums", params.merge(iDisplayStart: @start)
|
294
|
+
json = JSON.parse response.to_s
|
300
295
|
|
301
|
-
@max_items = json[
|
296
|
+
@max_items = json["iTotalRecords"]
|
302
297
|
|
303
298
|
objects = []
|
304
299
|
|
305
|
-
json[
|
300
|
+
json["aaData"].each do |data|
|
306
301
|
# Create Release object for every ID in the results list
|
307
|
-
id = Nokogiri::HTML(data.first).xpath(
|
302
|
+
id = Nokogiri::HTML(data.first).xpath("//a/@href").first.value.delete('\\').split("/").last.gsub(/\D/, "").to_i
|
308
303
|
objects << Release.find(id)
|
309
304
|
end
|
310
305
|
|
@@ -333,7 +328,8 @@ module MetalArchives
|
|
333
328
|
#
|
334
329
|
def search(title)
|
335
330
|
raise MetalArchives::Errors::ArgumentError unless title.is_a? String
|
336
|
-
|
331
|
+
|
332
|
+
search_by title: title
|
337
333
|
end
|
338
334
|
|
339
335
|
##
|
@@ -346,7 +342,7 @@ module MetalArchives
|
|
346
342
|
# - rdoc-ref:MetalArchives::Errors::ParserError when parsing failed. Please report this error.
|
347
343
|
#
|
348
344
|
def all
|
349
|
-
search
|
345
|
+
search ""
|
350
346
|
end
|
351
347
|
end
|
352
348
|
end
|