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.
Files changed (71) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +93 -0
  3. data/.gitignore +6 -6
  4. data/.overcommit.yml +35 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +66 -6
  7. data/CHANGELOG.md +33 -0
  8. data/Gemfile +1 -1
  9. data/LICENSE.md +17 -4
  10. data/README.md +65 -86
  11. data/Rakefile +8 -7
  12. data/bin/console +38 -0
  13. data/bin/setup +8 -0
  14. data/config/inflections.rb +7 -0
  15. data/config/initializers/.keep +0 -0
  16. data/docker-compose.yml +23 -0
  17. data/lib/metal_archives.rb +82 -27
  18. data/lib/metal_archives/cache/base.rb +40 -0
  19. data/lib/metal_archives/cache/memory.rb +68 -0
  20. data/lib/metal_archives/cache/null.rb +22 -0
  21. data/lib/metal_archives/cache/redis.rb +49 -0
  22. data/lib/metal_archives/{utils/collection.rb → collection.rb} +3 -5
  23. data/lib/metal_archives/configuration.rb +33 -50
  24. data/lib/metal_archives/{error.rb → errors.rb} +9 -1
  25. data/lib/metal_archives/http_client.rb +45 -44
  26. data/lib/metal_archives/models/artist.rb +90 -45
  27. data/lib/metal_archives/models/band.rb +77 -52
  28. data/lib/metal_archives/models/base.rb +225 -0
  29. data/lib/metal_archives/models/label.rb +14 -15
  30. data/lib/metal_archives/models/release.rb +25 -29
  31. data/lib/metal_archives/parsers/artist.rb +86 -50
  32. data/lib/metal_archives/parsers/band.rb +155 -88
  33. data/lib/metal_archives/parsers/base.rb +14 -0
  34. data/lib/metal_archives/parsers/country.rb +21 -0
  35. data/lib/metal_archives/parsers/date.rb +31 -0
  36. data/lib/metal_archives/parsers/genre.rb +67 -0
  37. data/lib/metal_archives/parsers/label.rb +39 -31
  38. data/lib/metal_archives/parsers/parser.rb +18 -63
  39. data/lib/metal_archives/parsers/release.rb +98 -89
  40. data/lib/metal_archives/parsers/year.rb +31 -0
  41. data/lib/metal_archives/version.rb +12 -1
  42. data/metal_archives.env.example +10 -0
  43. data/metal_archives.gemspec +43 -28
  44. data/nginx/default.conf +60 -0
  45. metadata +179 -74
  46. data/.travis.yml +0 -12
  47. data/lib/metal_archives/middleware/cache_check.rb +0 -20
  48. data/lib/metal_archives/middleware/encoding.rb +0 -16
  49. data/lib/metal_archives/middleware/headers.rb +0 -38
  50. data/lib/metal_archives/middleware/rewrite_endpoint.rb +0 -38
  51. data/lib/metal_archives/models/base_model.rb +0 -215
  52. data/lib/metal_archives/utils/lru_cache.rb +0 -61
  53. data/lib/metal_archives/utils/nil_date.rb +0 -99
  54. data/lib/metal_archives/utils/range.rb +0 -66
  55. data/spec/configuration_spec.rb +0 -96
  56. data/spec/factories/artist_factory.rb +0 -37
  57. data/spec/factories/band_factory.rb +0 -60
  58. data/spec/factories/nil_date_factory.rb +0 -9
  59. data/spec/factories/range_factory.rb +0 -8
  60. data/spec/models/artist_spec.rb +0 -138
  61. data/spec/models/band_spec.rb +0 -164
  62. data/spec/models/base_model_spec.rb +0 -219
  63. data/spec/models/release_spec.rb +0 -133
  64. data/spec/parser_spec.rb +0 -19
  65. data/spec/spec_helper.rb +0 -111
  66. data/spec/support/factory_girl.rb +0 -5
  67. data/spec/support/metal_archives.rb +0 -33
  68. data/spec/utils/collection_spec.rb +0 -72
  69. data/spec/utils/lru_cache_spec.rb +0 -53
  70. data/spec/utils/nil_date_spec.rb +0 -156
  71. 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 'date'
4
- require 'countries'
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 < MetalArchives::BaseModel
10
+ class Label < Base
11
11
  ##
12
12
  # :attr_reader: id
13
13
  #
14
14
  # Returns +Integer+
15
15
  #
16
- property :id, :type => Integer
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, :type => ISO3166::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, :multiple => true
71
+ property :specializations, multiple: true
72
72
 
73
73
  ##
74
74
  # :attr_reader: date_founded
75
75
  #
76
- # Returns +NilDate+
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, :type => NilDate
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, :type => MetalArchives::Label, :multiple => true
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, :type => Hash, :multiple => true
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, :values => %i[active closed unknown]
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
- results = []
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
- :name => name,
132
- :id => id
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 'date'
3
+ require "date"
4
+ require "nokogiri"
4
5
 
5
6
  module MetalArchives
6
7
  ##
7
8
  # Represents a release
8
9
  #
9
- class Release < MetalArchives::BaseModel
10
+ class Release < Base
10
11
  ##
11
12
  # :attr_reader: id
12
13
  #
13
14
  # Returns +Integer+
14
15
  #
15
- property :id, :type => Integer
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, :values => %i[full_length live demo single ep video boxed_set split compilation split_video collaboration]
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:NilDate
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, :type => NilDate
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
- url = "#{MetalArchives.config.default_endpoint}albums/view/id/#{id}"
127
- response = HTTPClient.get url
127
+ response = MetalArchives.http.get "/albums/view/id/#{id}"
128
128
 
129
- properties = Parsers::Release.parse_html response.body
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 :id => id
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 = HTTPClient.get url, params
206
- json = JSON.parse response.body
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['aaData'].empty?
205
+ return nil if json["aaData"].empty?
209
206
 
210
- data = json['aaData'].first
211
- id = Nokogiri::HTML(data[1]).xpath('//a/@href').first.value.delete('\\').split('/').last.gsub(/\D/, '').to_i
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 = HTTPClient.get url, params.merge(:iDisplayStart => @start)
299
- json = JSON.parse response.body
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['iTotalRecords']
296
+ @max_items = json["iTotalRecords"]
302
297
 
303
298
  objects = []
304
299
 
305
- json['aaData'].each do |data|
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('//a/@href').first.value.delete('\\').split('/').last.gsub(/\D/, '').to_i
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
- search_by :title => title
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