metal_archives 2.2.3 → 3.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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +59 -12
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +34 -20
  5. data/CHANGELOG.md +16 -1
  6. data/LICENSE.md +17 -4
  7. data/README.md +37 -29
  8. data/bin/console +8 -11
  9. data/config/inflections.rb +7 -0
  10. data/config/initializers/.keep +0 -0
  11. data/docker-compose.yml +10 -1
  12. data/lib/metal_archives.rb +57 -21
  13. data/lib/metal_archives/cache/base.rb +40 -0
  14. data/lib/metal_archives/cache/memory.rb +68 -0
  15. data/lib/metal_archives/cache/null.rb +22 -0
  16. data/lib/metal_archives/cache/redis.rb +49 -0
  17. data/lib/metal_archives/collection.rb +3 -5
  18. data/lib/metal_archives/configuration.rb +28 -21
  19. data/lib/metal_archives/errors.rb +9 -1
  20. data/lib/metal_archives/http_client.rb +42 -46
  21. data/lib/metal_archives/models/artist.rb +55 -26
  22. data/lib/metal_archives/models/band.rb +43 -36
  23. data/lib/metal_archives/models/{base_model.rb → base.rb} +57 -50
  24. data/lib/metal_archives/models/label.rb +7 -8
  25. data/lib/metal_archives/models/release.rb +21 -18
  26. data/lib/metal_archives/parsers/artist.rb +41 -36
  27. data/lib/metal_archives/parsers/band.rb +73 -29
  28. data/lib/metal_archives/parsers/base.rb +14 -0
  29. data/lib/metal_archives/parsers/country.rb +21 -0
  30. data/lib/metal_archives/parsers/date.rb +31 -0
  31. data/lib/metal_archives/parsers/genre.rb +67 -0
  32. data/lib/metal_archives/parsers/label.rb +21 -13
  33. data/lib/metal_archives/parsers/parser.rb +17 -77
  34. data/lib/metal_archives/parsers/release.rb +29 -18
  35. data/lib/metal_archives/parsers/year.rb +31 -0
  36. data/lib/metal_archives/version.rb +3 -3
  37. data/metal_archives.env.example +7 -4
  38. data/metal_archives.gemspec +7 -4
  39. data/nginx/default.conf +2 -2
  40. metadata +76 -32
  41. data/.github/workflows/release.yml +0 -69
  42. data/.rubocop_todo.yml +0 -92
  43. data/lib/metal_archives/lru_cache.rb +0 -61
  44. data/lib/metal_archives/middleware/cache_check.rb +0 -18
  45. data/lib/metal_archives/middleware/encoding.rb +0 -16
  46. data/lib/metal_archives/middleware/headers.rb +0 -38
  47. data/lib/metal_archives/middleware/rewrite_endpoint.rb +0 -38
  48. data/lib/metal_archives/nil_date.rb +0 -91
  49. data/lib/metal_archives/range.rb +0 -69
@@ -8,7 +8,7 @@ module MetalArchives
8
8
  ##
9
9
  # Represents a single performer (but not a solo artist)
10
10
  #
11
- class Artist < MetalArchives::BaseModel
11
+ class Artist < Base
12
12
  ##
13
13
  # :attr_reader: id
14
14
  #
@@ -63,24 +63,24 @@ module MetalArchives
63
63
  ##
64
64
  # :attr_reader: date_of_birth
65
65
  #
66
- # Returns rdoc-ref:NilDate
66
+ # Returns rdoc-ref:Date
67
67
  #
68
68
  # [Raises]
69
69
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
70
70
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
71
71
  #
72
- property :date_of_birth, type: NilDate
72
+ property :date_of_birth, type: Date
73
73
 
74
74
  ##
75
75
  # :attr_reader: date_of_death
76
76
  #
77
- # Returns rdoc-ref:NilDate
77
+ # Returns rdoc-ref:Date
78
78
  #
79
79
  # [Raises]
80
80
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
81
81
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
82
82
  #
83
- property :date_of_death, type: NilDate
83
+ property :date_of_death, type: Date
84
84
 
85
85
  ##
86
86
  # :attr_reader: cause_of_death
@@ -102,7 +102,7 @@ module MetalArchives
102
102
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
103
103
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
104
104
  #
105
- enum :gender, values: %i(male female)
105
+ enum :gender, values: [:male, :female]
106
106
 
107
107
  ##
108
108
  # :attr_reader: biography
@@ -165,7 +165,7 @@ module MetalArchives
165
165
  # [+bands+]
166
166
  # - +:band+: rdoc-ref:Band
167
167
  # - +:active+: Boolean
168
- # - +:date_active+: +Array+ of rdoc-ref:Range containing rdoc-ref:NilDate
168
+ # - +:years_active+: +Array+ of rdoc-ref:Range containing +Integer+
169
169
  # - +:role+: +String+
170
170
  #
171
171
  property :bands, type: Hash, multiple: true
@@ -173,6 +173,41 @@ module MetalArchives
173
173
  # TODO: guest/session bands
174
174
  # TODO: misc bands
175
175
 
176
+ ##
177
+ # Serialize to hash
178
+ #
179
+ def to_h
180
+ {
181
+ type: "artist",
182
+ id: id,
183
+ name: name,
184
+ aliases: aliases || [],
185
+ country: country&.alpha3,
186
+ location: location,
187
+ date_of_birth: date_of_birth&.iso8601,
188
+ date_of_death: date_of_death&.iso8601,
189
+ cause_of_death: cause_of_death,
190
+ gender: gender,
191
+ biography: biography,
192
+ trivia: trivia,
193
+ photo: photo,
194
+ links: links || [],
195
+ bands: bands || [],
196
+ }
197
+ end
198
+
199
+ ##
200
+ # Deserialize from hash
201
+ #
202
+ def self.from_h(hash)
203
+ return unless hash.fetch(:type) == "artist"
204
+
205
+ new(hash.slice(:id, :name, :aliases, :location, :cause_of_death, :gender, :biography, :trivial, :photo, :links, :bands))
206
+ .tap { |m| m.country = ISO3166::Country[hash[:country]] }
207
+ .tap { |m| m.date_of_birth = Date.parse(hash[:date_of_birth]) if hash[:date_of_birth] }
208
+ .tap { |m| m.date_of_death = Date.parse(hash[:date_of_death]) if hash[:date_of_death] }
209
+ end
210
+
176
211
  protected
177
212
 
178
213
  ##
@@ -184,28 +219,24 @@ module MetalArchives
184
219
  #
185
220
  def assemble # :nodoc:
186
221
  ## Base attributes
187
- url = "#{MetalArchives.config.default_endpoint}artist/view/id/#{id}"
188
- response = HTTPClient.get url
222
+ response = MetalArchives.http.get "/artist/view/id/#{id}"
189
223
 
190
- properties = Parsers::Artist.parse_html response.body
224
+ properties = Parsers::Artist.parse_html response.to_s
191
225
 
192
226
  ## Biography
193
- url = "#{MetalArchives.config.default_endpoint}artist/read-more/id/#{id}/field/biography"
194
- response = HTTPClient.get url
227
+ response = MetalArchives.http.get "/artist/read-more/id/#{id}/field/biography"
195
228
 
196
- properties[:biography] = response.body
229
+ properties[:biography] = response.to_s
197
230
 
198
231
  ## Trivia
199
- url = "#{MetalArchives.config.default_endpoint}artist/read-more/id/#{id}/field/trivia"
200
- response = HTTPClient.get url
232
+ response = MetalArchives.http.get "/artist/read-more/id/#{id}/field/trivia"
201
233
 
202
- properties[:trivia] = response.body
234
+ properties[:trivia] = response.to_s
203
235
 
204
236
  ## Related links
205
- url = "#{MetalArchives.config.default_endpoint}link/ajax-list/type/person/id/#{id}"
206
- response = HTTPClient.get url
237
+ response = MetalArchives.http.get "/link/ajax-list/type/person/id/#{id}"
207
238
 
208
- properties[:links] = Parsers::Artist.parse_links_html response.body
239
+ properties[:links] = Parsers::Artist.parse_links_html response.to_s
209
240
 
210
241
  properties
211
242
  end
@@ -220,7 +251,7 @@ module MetalArchives
220
251
  # +Integer+
221
252
  #
222
253
  def find(id)
223
- return cache[id] if cache.include? id
254
+ return MetalArchives.cache[cache(id)] if MetalArchives.cache.include? cache(id)
224
255
 
225
256
  Artist.new id: id
226
257
  end
@@ -262,11 +293,10 @@ module MetalArchives
262
293
  def find_by(query)
263
294
  raise MetalArchives::Errors::ArgumentError unless query.include? :name
264
295
 
265
- url = "#{MetalArchives.config.default_endpoint}search/ajax-artist-search/"
266
296
  params = Parsers::Artist.map_params query
267
297
 
268
- response = HTTPClient.get url, params
269
- json = JSON.parse response.body
298
+ response = MetalArchives.http.get "/search/ajax-artist-search/", params
299
+ json = JSON.parse response.to_s
270
300
 
271
301
  return nil if json["aaData"].empty?
272
302
 
@@ -314,7 +344,6 @@ module MetalArchives
314
344
  def search(name)
315
345
  raise MetalArchives::Errors::ArgumentError unless name.is_a? String
316
346
 
317
- url = "#{MetalArchives.config.default_endpoint}search/ajax-artist-search/"
318
347
  query = { name: name }
319
348
 
320
349
  params = Parsers::Artist.map_params query
@@ -325,8 +354,8 @@ module MetalArchives
325
354
  if @max_items && @start >= @max_items
326
355
  []
327
356
  else
328
- response = HTTPClient.get url, params.merge(iDisplayStart: @start)
329
- json = JSON.parse response.body
357
+ response = MetalArchives.http.get "/search/ajax-artist-search/", params.merge(iDisplayStart: @start)
358
+ json = JSON.parse response.to_s
330
359
 
331
360
  @max_items = json["iTotalRecords"]
332
361
 
@@ -8,7 +8,7 @@ module MetalArchives
8
8
  ##
9
9
  # Represents an band (person or group)
10
10
  #
11
- class Band < MetalArchives::BaseModel
11
+ class Band < Base
12
12
  ##
13
13
  # :attr_reader: id
14
14
  #
@@ -63,24 +63,24 @@ module MetalArchives
63
63
  ##
64
64
  # :attr_reader: date_formed
65
65
  #
66
- # Returns rdoc-ref:NilDate
66
+ # Returns rdoc-ref:Date
67
67
  #
68
68
  # [Raises]
69
69
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
70
70
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
71
71
  #
72
- property :date_formed, type: NilDate
72
+ property :date_formed, type: Date
73
73
 
74
74
  ##
75
- # :attr_reader: date_active
75
+ # :attr_reader: years_active
76
76
  #
77
- # Returns +Array+ of rdoc-ref:Range containing rdoc-ref:NilDate
77
+ # Returns +Array+ of rdoc-ref:Range containing +Integer+
78
78
  #
79
79
  # [Raises]
80
80
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
81
81
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
82
82
  #
83
- property :date_active, type: MetalArchives::Range, multiple: true
83
+ property :years_active, type: Range, multiple: true
84
84
 
85
85
  ##
86
86
  # :attr_reader: genres
@@ -113,7 +113,7 @@ module MetalArchives
113
113
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
114
114
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
115
115
  #
116
- property :label, type: MetalArchives::Label
116
+ property :label, type: Label
117
117
 
118
118
  ##
119
119
  # :attr_reader: independent
@@ -146,7 +146,7 @@ module MetalArchives
146
146
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
147
147
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
148
148
  #
149
- enum :status, values: %i(active split_up on_hold unknown changed_name disputed)
149
+ enum :status, values: [:active, :split_up, :on_hold, :unknown, :changed_name, :disputed]
150
150
 
151
151
  ##
152
152
  # :attr_reader: releases
@@ -157,9 +157,24 @@ module MetalArchives
157
157
  # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
158
158
  # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
159
159
  #
160
- property :releases, type: MetalArchives::Release, multiple: true
160
+ property :releases, type: Release, multiple: true
161
161
 
162
- # TODO: members
162
+ ##
163
+ # :attr_reader: members
164
+ #
165
+ # Returns +Array+ of +Hash+ containing the following keys
166
+ #
167
+ # [Raises]
168
+ # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
169
+ # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
170
+ #
171
+ # [+members+]
172
+ # - +:artist+: rdoc-ref:Artist
173
+ # - +:current+: Boolean
174
+ # - +:years_active+: +Array+ of rdoc-ref:Range containing +Integer+
175
+ # - +:role+: +String+
176
+ #
177
+ property :members, type: Artist, multiple: true
163
178
 
164
179
  ##
165
180
  # :attr_reader: similar
@@ -225,34 +240,29 @@ module MetalArchives
225
240
  #
226
241
  def assemble # :nodoc:
227
242
  ## Base attributes
228
- url = "#{MetalArchives.config.default_endpoint}band/view/id/#{id}"
229
- response = HTTPClient.get url
243
+ response = MetalArchives.http.get "/band/view/id/#{id}"
230
244
 
231
- properties = Parsers::Band.parse_html response.body
245
+ properties = Parsers::Band.parse_html response.to_s
232
246
 
233
247
  ## Comment
234
- url = "#{MetalArchives.config.default_endpoint}band/read-more/id/#{id}"
235
- response = HTTPClient.get url
248
+ response = MetalArchives.http.get "/band/read-more/id/#{id}"
236
249
 
237
- properties[:comment] = response.body
250
+ properties[:comment] = response.to_s
238
251
 
239
252
  ## Similar artists
240
- url = "#{MetalArchives.config.default_endpoint}band/ajax-recommendations/id/#{id}"
241
- response = HTTPClient.get url
253
+ response = MetalArchives.http.get "/band/ajax-recommendations/id/#{id}"
242
254
 
243
- properties[:similar] = Parsers::Band.parse_similar_bands_html response.body
255
+ properties[:similar] = Parsers::Band.parse_similar_bands_html response.to_s
244
256
 
245
257
  ## Related links
246
- url = "#{MetalArchives.config.default_endpoint}link/ajax-list/type/band/id/#{id}"
247
- response = HTTPClient.get url
258
+ response = MetalArchives.http.get "/link/ajax-list/type/band/id/#{id}"
248
259
 
249
- properties[:links] = Parsers::Band.parse_related_links_html response.body
260
+ properties[:links] = Parsers::Band.parse_related_links_html response.to_s
250
261
 
251
262
  ## Releases
252
- url = "#{MetalArchives.config.default_endpoint}band/discography/id/#{id}/tab/all"
253
- response = HTTPClient.get url
263
+ response = MetalArchives.http.get "/band/discography/id/#{id}/tab/all"
254
264
 
255
- properties[:releases] = Parsers::Band.parse_releases_html response.body
265
+ properties[:releases] = Parsers::Band.parse_releases_html response.to_s
256
266
 
257
267
  properties
258
268
  end
@@ -267,7 +277,7 @@ module MetalArchives
267
277
  # +Integer+
268
278
  #
269
279
  def find(id)
270
- return cache[id] if cache.include? id
280
+ return MetalArchives.cache[cache(id)] if MetalArchives.cache.include? cache(id)
271
281
 
272
282
  Band.new id: id
273
283
  end
@@ -309,7 +319,7 @@ module MetalArchives
309
319
  # - +:exact+: +Boolean+
310
320
  # - +:genre+: +String+
311
321
  # - +:country+: +ISO3166::Country+
312
- # - +:year_formation+: rdoc-ref:Range containing rdoc-ref:NilDate
322
+ # - +:year_formation+: rdoc-ref:Range containing rdoc-ref:Date
313
323
  # - +:comment+: +String+
314
324
  # - +:status+: see rdoc-ref:Band.status
315
325
  # - +:lyrical_themes+: +String+
@@ -318,11 +328,10 @@ module MetalArchives
318
328
  # - +:independent+: boolean
319
329
  #
320
330
  def find_by(query)
321
- url = "#{MetalArchives.config.default_endpoint}search/ajax-advanced/searching/bands"
322
331
  params = Parsers::Band.map_params query
323
332
 
324
- response = HTTPClient.get url, params
325
- json = JSON.parse response.body
333
+ response = MetalArchives.http.get "/search/ajax-advanced/searching/bands", params
334
+ json = JSON.parse response.to_s
326
335
 
327
336
  return nil if json["aaData"].empty?
328
337
 
@@ -349,7 +358,7 @@ module MetalArchives
349
358
  # - +:exact+: +Boolean+
350
359
  # - +:genre+: +String+
351
360
  # - +:country+: +ISO3166::Country+
352
- # - +:year_formation+: rdoc-ref:Range containing rdoc-ref:NilDate
361
+ # - +:year_formation+: rdoc-ref:Range containing rdoc-ref:Date
353
362
  # - +:comment+: +String+
354
363
  # - +:status+: see rdoc-ref:Band.status
355
364
  # - +:lyrical_themes+: +String+
@@ -381,7 +390,7 @@ module MetalArchives
381
390
  # - +:exact+: +Boolean+
382
391
  # - +:genre+: +String+
383
392
  # - +:country+: +ISO3166::Country+
384
- # - +:year_formation+: rdoc-ref:Range containing rdoc-ref:NilDate
393
+ # - +:year_formation+: rdoc-ref:Range containing rdoc-ref:Date
385
394
  # - +:comment+: +String+
386
395
  # - +:status+: see rdoc-ref:Band.status
387
396
  # - +:lyrical_themes+: +String+
@@ -390,8 +399,6 @@ module MetalArchives
390
399
  # - +:independent+: boolean
391
400
  #
392
401
  def search_by(query)
393
- url = "#{MetalArchives.config.default_endpoint}search/ajax-advanced/searching/bands"
394
-
395
402
  params = Parsers::Band.map_params query
396
403
 
397
404
  l = lambda do
@@ -400,8 +407,8 @@ module MetalArchives
400
407
  if @max_items && @start >= @max_items
401
408
  []
402
409
  else
403
- response = HTTPClient.get url, params.merge(iDisplayStart: @start)
404
- json = JSON.parse response.body
410
+ response = MetalArchives.http.get "/search/ajax-advanced/searching/bands", params.merge(iDisplayStart: @start)
411
+ json = JSON.parse response.to_s
405
412
 
406
413
  @max_items = json["iTotalRecords"]
407
414
 
@@ -4,48 +4,49 @@ module MetalArchives
4
4
  ##
5
5
  # Abstract model class
6
6
  #
7
- class BaseModel
7
+ class Base
8
8
  ##
9
9
  # Generic shallow copy constructor
10
10
  #
11
- def initialize(hash = {})
12
- raise Errors::NotImplementedError, "no :id property in model" unless respond_to? :id?, true
11
+ def initialize(attributes = {})
12
+ raise Errors::NotImplementedError, "no :id property in model" unless respond_to? :id
13
13
 
14
- hash.each do |property, value|
15
- instance_variable_set("@#{property}", value) if self.class.properties.include? property
16
- end
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) }
17
22
  end
18
23
 
19
24
  ##
20
25
  # Returns true if two objects have the same type and id
21
26
  #
22
- def ==(obj)
23
- obj.instance_of?(self.class) && id == obj.id
27
+ def ==(other)
28
+ other.is_a?(self.class) &&
29
+ id == other.id
24
30
  end
25
31
 
26
32
  ##
27
33
  # Fetch, parse and load the data
28
34
  #
29
35
  # [Raises]
30
- # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no id
31
- # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
36
+ # - rdoc-ref:Errors::InvalidIDError when no id
37
+ # - rdoc-ref:Errors::APIError when receiving a status code >= 400 (except 404)
32
38
  #
33
39
  def load!
34
40
  raise Errors::InvalidIDError, "no id present" unless id
35
41
 
36
42
  # Use constructor to set attributes
37
- initialize assemble
38
-
39
- # Set empty properties to nil
40
- self.class.properties.each do |prop|
41
- instance_variable_set("@#{prop}", nil) unless instance_variable_defined? "@#{prop}"
42
- end
43
+ set(**assemble)
43
44
 
44
45
  @loaded = true
45
- self.class.cache[id] = self
46
+ MetalArchives.cache[self.class.cache(id)] = self
46
47
  rescue StandardError => e
47
48
  # Don't cache invalid requests
48
- self.class.cache.delete id
49
+ MetalArchives.cache.delete self.class.cache(id)
49
50
  raise e
50
51
  end
51
52
 
@@ -53,14 +54,21 @@ module MetalArchives
53
54
  # Whether or not the object is currently loaded
54
55
  #
55
56
  def loaded?
56
- !@loaded.nil?
57
+ @loaded ||= false
57
58
  end
58
59
 
59
60
  ##
60
61
  # Whether or not the object is currently cached
61
62
  #
62
63
  def cached?
63
- loaded? && self.class.cache.include?(id)
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}\">"
64
72
  end
65
73
 
66
74
  protected
@@ -71,8 +79,8 @@ module MetalArchives
71
79
  # Override this method
72
80
  #
73
81
  # [Raises]
74
- # - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
75
- # - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
82
+ # - rdoc-ref:Errors::InvalidIDError when no or invalid id
83
+ # - rdoc-ref:Errors::APIError when receiving a status code >= 400 (except 404)
76
84
  #
77
85
  def assemble
78
86
  raise Errors::NotImplementedError, "method :assemble not implemented"
@@ -80,15 +88,17 @@ module MetalArchives
80
88
 
81
89
  class << self
82
90
  ##
83
- # +Array+ of declared properties
91
+ # Declared properties
84
92
  #
85
- attr_accessor :properties
93
+ def properties
94
+ @properties ||= {}
95
+ end
86
96
 
87
97
  ##
88
- # Get class-level object cache
98
+ # Generate cache key for id
89
99
  #
90
- def cache
91
- @cache ||= MetalArchives::LRUCache.new MetalArchives.config.cache_size
100
+ def cache(id)
101
+ "#{self.class.name}//#{id}"
92
102
  end
93
103
 
94
104
  protected
@@ -110,42 +120,38 @@ module MetalArchives
110
120
  # turns it into an +Array+ of +type+)
111
121
  #
112
122
  def property(name, opts = {})
113
- (@properties ||= []) << name
123
+ properties[name] = opts
114
124
 
115
125
  # property
116
126
  define_method(name) do
117
- load! unless loaded? && instance_variable_defined?("@#{name}") || name == :id
118
- instance_variable_get("@#{name}")
127
+ # Load only when not loaded or id property
128
+ load! unless loaded? || name == :id
129
+
130
+ instance_variable_get(:"@#{name}")
119
131
  end
120
132
 
121
133
  # property?
122
134
  define_method("#{name}?") do
123
- load! unless loaded? && instance_variable_defined?("@#{name}") || name == :id
124
-
125
- property = instance_variable_get("@#{name}")
126
- property.respond_to?(:empty?) ? !property.empty? : !property.nil?
135
+ send(name).present?
127
136
  end
128
137
 
129
138
  # property=
130
139
  define_method("#{name}=") do |value|
131
- if value.nil?
132
- instance_variable_set "@#{name}", value
133
- return
134
- end
140
+ return instance_variable_set(:"@#{name}", value) if value.nil?
135
141
 
136
142
  # Check value type
137
143
  type = opts[:type] || String
138
144
  if opts[:multiple]
139
- raise MetalArchives::Errors::TypeError, "invalid type #{value.class}, must be Array for #{name}" unless value.is_a? Array
145
+ raise Errors::TypeError, "invalid type #{value.class}, must be Array for #{name}" unless value.is_a? Array
140
146
 
141
147
  value.each do |val|
142
- raise MetalArchives::Errors::TypeError, "invalid type #{val.class}, must be #{type} for #{name}" unless val.is_a? type
148
+ raise Errors::TypeError, "invalid type #{val.class}, must be #{type} for #{name}" unless val.is_a? type
143
149
  end
144
150
  else
145
- raise MetalArchives::Errors::TypeError, "invalid type #{value.class}, must be #{type} for #{name}" unless value.is_a? type
151
+ raise Errors::TypeError, "invalid type #{value.class}, must be #{type} for #{name}" unless value.is_a? type
146
152
  end
147
153
 
148
- instance_variable_set "@#{name}", value
154
+ instance_variable_set(:"@#{name}", value)
149
155
  end
150
156
  end
151
157
 
@@ -166,19 +172,20 @@ module MetalArchives
166
172
  def enum(name, opts)
167
173
  raise ArgumentError, "opts[:values] is required" unless opts && opts[:values]
168
174
 
169
- (@properties ||= []) << name
175
+ properties[name] = opts
170
176
 
171
177
  # property
172
178
  define_method(name) do
173
- load! unless loaded? && instance_variable_defined?("@#{name}")
174
- instance_variable_get("@#{name}")
179
+ load! unless loaded?
180
+
181
+ instance_variable_get(:"@#{name}")
175
182
  end
176
183
 
177
184
  # property?
178
185
  define_method("#{name}?") do
179
186
  load! unless loaded? && instance_variable_defined?("@#{name}")
180
187
 
181
- property = instance_variable_get("@#{name}")
188
+ property = instance_variable_get(:"@#{name}")
182
189
  property.respond_to?(:empty?) ? !property.empty? : !property.nil?
183
190
  end
184
191
 
@@ -186,16 +193,16 @@ module MetalArchives
186
193
  define_method("#{name}=") do |value|
187
194
  # Check enum type
188
195
  if opts[:multiple]
189
- raise MetalArchives::Errors::TypeError, "invalid enum value #{value}, must be Array for #{name}" unless value.is_a? Array
196
+ raise Errors::TypeError, "invalid enum value #{value}, must be Array for #{name}" unless value.is_a? Array
190
197
 
191
198
  value.each do |val|
192
- raise MetalArchives::Errors::TypeError, "invalid enum value #{val} for #{name}" unless opts[:values].include? val
199
+ raise Errors::TypeError, "invalid enum value #{val} for #{name}" unless opts[:values].include? val
193
200
  end
194
201
  else
195
- raise MetalArchives::Errors::TypeError, "invalid enum value #{value} for #{name}" unless opts[:values].include? value
202
+ raise Errors::TypeError, "invalid enum value #{value} for #{name}" unless opts[:values].include? value
196
203
  end
197
204
 
198
- instance_variable_set name, value
205
+ instance_variable_set(:"@#{name}", value)
199
206
  end
200
207
  end
201
208