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
data/Rakefile
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "rake"
|
4
|
+
require "rake/testtask"
|
5
|
+
require "zeitwerk"
|
5
6
|
|
6
7
|
Rake::TestTask.new do |t|
|
7
|
-
t.libs <<
|
8
|
-
t.test_files = FileList[
|
8
|
+
t.libs << "test"
|
9
|
+
t.test_files = FileList["test/*/*_test.rb"]
|
9
10
|
t.verbose = true
|
10
11
|
end
|
11
12
|
|
12
|
-
require
|
13
|
+
require "rdoc/task"
|
13
14
|
RDoc::Task.new do |rdoc|
|
14
|
-
rdoc.main =
|
15
|
-
rdoc.rdoc_files.include(
|
15
|
+
rdoc.main = "README.md"
|
16
|
+
rdoc.rdoc_files.include("README.md", "lib/**/*.rb")
|
16
17
|
end
|
data/bin/console
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "logger"
|
5
|
+
|
6
|
+
require "bundler/setup"
|
7
|
+
require "metal_archives"
|
8
|
+
|
9
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
10
|
+
# with your gem easier. You can also use a different console, if you like.
|
11
|
+
|
12
|
+
MetalArchives.configure do |c|
|
13
|
+
## Application identity (required)
|
14
|
+
c.app_name = "My App"
|
15
|
+
c.app_version = "1.0"
|
16
|
+
c.app_contact = "support@mymusicapp.com"
|
17
|
+
|
18
|
+
## Enable Redis as caching backend (optional, overrides default memory cache)
|
19
|
+
## Available cache strategies: :memory, :redis or :null (disable caching)
|
20
|
+
# c.cache_strategy = :redis
|
21
|
+
# c.cache_options = { url: "redis://redis:6379", ttl: 1.month.to_i }
|
22
|
+
|
23
|
+
## Metal Archives endpoint (optional, overrides default)
|
24
|
+
c.endpoint = ENV["MA_ENDPOINT"] if ENV["MA_ENDPOINT"]
|
25
|
+
c.endpoint_user = ENV["MA_ENDPOINT_USER"] if ENV["MA_ENDPOINT_USER"]
|
26
|
+
c.endpoint_password = ENV["MA_ENDPOINT_PASSWORD"] if ENV["MA_ENDPOINT_PASSWORD"]
|
27
|
+
|
28
|
+
## Custom logger (optional)
|
29
|
+
c.logger = Logger.new $stdout
|
30
|
+
c.logger.level = Logger::WARN
|
31
|
+
end
|
32
|
+
|
33
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
34
|
+
# require "pry"
|
35
|
+
# Pry.start
|
36
|
+
|
37
|
+
require "irb"
|
38
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
File without changes
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
version: "3.7"
|
2
|
+
|
3
|
+
services:
|
4
|
+
nginx:
|
5
|
+
image: nginx:alpine
|
6
|
+
ports:
|
7
|
+
- "0.0.0.0:8080:80"
|
8
|
+
volumes:
|
9
|
+
- ./nginx/:/etc/nginx/conf.d/
|
10
|
+
- nginx:/cache/
|
11
|
+
restart: always
|
12
|
+
|
13
|
+
redis:
|
14
|
+
image: redis:alpine
|
15
|
+
ports:
|
16
|
+
- "0.0.0.0:6379:6379"
|
17
|
+
volumes:
|
18
|
+
- redis:/data/
|
19
|
+
restart: always
|
20
|
+
|
21
|
+
volumes:
|
22
|
+
nginx:
|
23
|
+
redis:
|
data/lib/metal_archives.rb
CHANGED
@@ -1,37 +1,92 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "zeitwerk"
|
4
|
+
require "byebug" if ENV["METAL_ARCHIVES_ENV"] == "development"
|
5
|
+
require "active_support/all"
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
##
|
8
|
+
# Metal Archives Ruby API
|
9
|
+
#
|
10
|
+
module MetalArchives
|
11
|
+
class << self
|
12
|
+
# Code loader instance
|
13
|
+
attr_reader :loader
|
9
14
|
|
10
|
-
|
11
|
-
|
12
|
-
|
15
|
+
##
|
16
|
+
# Root path
|
17
|
+
#
|
18
|
+
def root
|
19
|
+
@root ||= Pathname.new(File.expand_path(File.join("..", ".."), __FILE__))
|
20
|
+
end
|
13
21
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
22
|
+
##
|
23
|
+
# HTTP client
|
24
|
+
#
|
25
|
+
def http
|
26
|
+
@http ||= HTTPClient.new
|
27
|
+
end
|
18
28
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
29
|
+
##
|
30
|
+
# API configuration
|
31
|
+
#
|
32
|
+
# Instance of rdoc-ref:MetalArchives::Configuration
|
33
|
+
#
|
34
|
+
def config
|
35
|
+
@config ||= Configuration.new
|
36
|
+
end
|
24
37
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
38
|
+
##
|
39
|
+
# Cache instance
|
40
|
+
#
|
41
|
+
def cache
|
42
|
+
raise MetalArchives::Errors::InvalidConfigurationError, "cache has not been configured" unless config.cache_strategy
|
30
43
|
|
31
|
-
|
44
|
+
@cache ||= Cache
|
45
|
+
.const_get(loader.inflector.camelize(config.cache_strategy, root))
|
46
|
+
.new(config.cache_options)
|
47
|
+
end
|
32
48
|
|
33
|
-
##
|
34
|
-
#
|
35
|
-
#
|
36
|
-
|
49
|
+
##
|
50
|
+
# Configure API options.
|
51
|
+
#
|
52
|
+
# A block must be specified, to which a
|
53
|
+
# rdoc-ref:MetalArchives::Configuration parameter will be passed.
|
54
|
+
#
|
55
|
+
# [Raises]
|
56
|
+
# - rdoc-ref:InvalidConfigurationException
|
57
|
+
#
|
58
|
+
def configure
|
59
|
+
raise Errors::InvalidConfigurationError, "no configuration block given" unless block_given?
|
60
|
+
|
61
|
+
yield config
|
62
|
+
|
63
|
+
config.validate!
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# Set up application framework
|
68
|
+
#
|
69
|
+
def setup
|
70
|
+
@loader = Zeitwerk::Loader.for_gem
|
71
|
+
|
72
|
+
# Register inflections
|
73
|
+
require root.join("config/inflections.rb")
|
74
|
+
|
75
|
+
# Set up code loader
|
76
|
+
loader.enable_reloading if ENV["METAL_ARCHIVES_ENV"] == "development"
|
77
|
+
loader.collapse(root.join("lib/metal_archives/models"))
|
78
|
+
loader.do_not_eager_load(root.join("lib/metal_archives/cache"))
|
79
|
+
loader.setup
|
80
|
+
loader.eager_load
|
81
|
+
|
82
|
+
# Load initializers
|
83
|
+
Dir[root.join("config/initializers/*.rb")].sort.each { |f| require f }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def reload!
|
89
|
+
MetalArchives.loader.reload
|
37
90
|
end
|
91
|
+
|
92
|
+
MetalArchives.setup
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MetalArchives
|
4
|
+
module Cache
|
5
|
+
##
|
6
|
+
# Generic cache interface
|
7
|
+
#
|
8
|
+
class Base
|
9
|
+
attr_accessor :options
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@options = options
|
13
|
+
|
14
|
+
validate!
|
15
|
+
end
|
16
|
+
|
17
|
+
def validate!; end
|
18
|
+
|
19
|
+
def []
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
|
23
|
+
def []=(_key, _value)
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
def clear
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
def include?(_key)
|
32
|
+
raise NotImplementedError
|
33
|
+
end
|
34
|
+
|
35
|
+
def delete(_key)
|
36
|
+
raise NotImplementedError
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MetalArchives
|
4
|
+
module Cache
|
5
|
+
##
|
6
|
+
# Generic LRU memory cache
|
7
|
+
#
|
8
|
+
class Memory < Base
|
9
|
+
def validate!
|
10
|
+
raise Errors::InvalidConfigurationError, "size has not been configured" if options[:size].blank?
|
11
|
+
raise Errors::InvalidConfigurationError, "size must be a number" unless options[:size].is_a? Integer
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](key)
|
15
|
+
if keys.include? key
|
16
|
+
MetalArchives.config.logger.debug "Cache hit for #{key}"
|
17
|
+
keys.delete key
|
18
|
+
keys << key
|
19
|
+
else
|
20
|
+
MetalArchives.config.logger.debug "Cache miss for #{key}"
|
21
|
+
end
|
22
|
+
|
23
|
+
cache[key]
|
24
|
+
end
|
25
|
+
|
26
|
+
def []=(key, value)
|
27
|
+
cache[key] = value
|
28
|
+
|
29
|
+
keys.delete key if keys.include? key
|
30
|
+
|
31
|
+
keys << key
|
32
|
+
|
33
|
+
pop if keys.size > options[:size]
|
34
|
+
end
|
35
|
+
|
36
|
+
def clear
|
37
|
+
cache.clear
|
38
|
+
keys.clear
|
39
|
+
end
|
40
|
+
|
41
|
+
def include?(key)
|
42
|
+
cache.include? key
|
43
|
+
end
|
44
|
+
|
45
|
+
def delete(key)
|
46
|
+
cache.delete key
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def cache
|
52
|
+
# Underlying data store
|
53
|
+
@cache ||= {}
|
54
|
+
end
|
55
|
+
|
56
|
+
def keys
|
57
|
+
# Array of keys in order of insertion
|
58
|
+
@keys ||= []
|
59
|
+
end
|
60
|
+
|
61
|
+
def pop
|
62
|
+
to_remove = keys.shift(keys.size - options[:size])
|
63
|
+
|
64
|
+
to_remove.each { |key| cache.delete key }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MetalArchives
|
4
|
+
module Cache
|
5
|
+
##
|
6
|
+
# Null cache
|
7
|
+
#
|
8
|
+
class Null < Base
|
9
|
+
def [](_key); end
|
10
|
+
|
11
|
+
def []=(_key, _value); end
|
12
|
+
|
13
|
+
def clear; end
|
14
|
+
|
15
|
+
def include?(_key)
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
def delete(_key); end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redis"
|
4
|
+
|
5
|
+
module MetalArchives
|
6
|
+
module Cache
|
7
|
+
##
|
8
|
+
# Redis-backed cache
|
9
|
+
#
|
10
|
+
class Redis < Base
|
11
|
+
def initialize(options = {})
|
12
|
+
super
|
13
|
+
|
14
|
+
# Default TTL is 1 month
|
15
|
+
options[:ttl] ||= (30 * 24 * 60 * 60)
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](key)
|
19
|
+
redis.get cache_key_for(key)
|
20
|
+
end
|
21
|
+
|
22
|
+
def []=(key, value)
|
23
|
+
redis.set cache_key_for(key), value, ex: options[:ttl]
|
24
|
+
end
|
25
|
+
|
26
|
+
def clear
|
27
|
+
redis.keys(cache_key_for("*")).each { |key| redis.del key }
|
28
|
+
end
|
29
|
+
|
30
|
+
def include?(key)
|
31
|
+
redis.exists? cache_key_for(key)
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete(key)
|
35
|
+
redis.del cache_key_for(key)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def cache_key_for(key)
|
41
|
+
"metal_archives.cache.#{key}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def redis
|
45
|
+
@redis ||= ::Redis.new(**options.except(:ttl))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -22,15 +22,13 @@ module MetalArchives
|
|
22
22
|
# Calls the given block once for each element, passing that element as a parameter.
|
23
23
|
# If no block is given, an Enumerator is returned.
|
24
24
|
#
|
25
|
-
def each
|
26
|
-
return to_enum :each unless
|
25
|
+
def each(&block)
|
26
|
+
return to_enum :each unless block
|
27
27
|
|
28
28
|
loop do
|
29
29
|
items = instance_exec(&@proc)
|
30
30
|
|
31
|
-
items.each
|
32
|
-
yield item
|
33
|
-
end
|
31
|
+
items.each(&block)
|
34
32
|
|
35
33
|
break if items.empty?
|
36
34
|
end
|
@@ -1,37 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
class << self
|
5
|
-
##
|
6
|
-
# API configuration
|
7
|
-
#
|
8
|
-
# Instance of rdoc-ref:MetalArchives::Configuration
|
9
|
-
#
|
10
|
-
def config
|
11
|
-
raise MetalArchives::Errors::InvalidConfigurationError, 'Gem has not been configured' unless @config
|
3
|
+
require "logger"
|
12
4
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
##
|
17
|
-
# Configure API options.
|
18
|
-
#
|
19
|
-
# A block must be specified, to which a
|
20
|
-
# rdoc-ref:MetalArchives::Configuration parameter will be passed.
|
21
|
-
#
|
22
|
-
# [Raises]
|
23
|
-
# - rdoc-ref:InvalidConfigurationException
|
24
|
-
#
|
25
|
-
def configure
|
26
|
-
raise MetalArchives::Errors::InvalidConfigurationError, 'No configuration block given' unless block_given?
|
27
|
-
@config = MetalArchives::Configuration.new
|
28
|
-
yield @config
|
29
|
-
|
30
|
-
raise MetalArchives::Errors::InvalidConfigurationError, 'app_name has not been configured' unless MetalArchives.config.app_name && !MetalArchives.config.app_name.empty?
|
31
|
-
raise MetalArchives::Errors::InvalidConfigurationError, 'app_version has not been configured' unless MetalArchives.config.app_version && !MetalArchives.config.app_version.empty?
|
32
|
-
raise MetalArchives::Errors::InvalidConfigurationError, 'app_contact has not been configured' unless MetalArchives.config.app_contact && !MetalArchives.config.app_contact.empty?
|
33
|
-
end
|
34
|
-
end
|
5
|
+
module MetalArchives
|
6
|
+
CACHE_STRATEGIES = %w(memory redis).freeze
|
35
7
|
|
36
8
|
##
|
37
9
|
# Contains configuration options
|
@@ -53,45 +25,56 @@ module MetalArchives
|
|
53
25
|
attr_accessor :app_contact
|
54
26
|
|
55
27
|
##
|
56
|
-
# Override Metal Archives endpoint (defaults to
|
28
|
+
# Override Metal Archives endpoint (defaults to https://www.metal-archives.com/)
|
57
29
|
#
|
58
30
|
attr_accessor :endpoint
|
59
|
-
attr_reader :default_endpoint
|
60
31
|
|
61
32
|
##
|
62
|
-
#
|
33
|
+
# Endpoint HTTP Basic authentication
|
63
34
|
#
|
64
|
-
attr_accessor :
|
35
|
+
attr_accessor :endpoint_user
|
36
|
+
attr_accessor :endpoint_password
|
65
37
|
|
66
38
|
##
|
67
|
-
#
|
39
|
+
# Logger instance
|
68
40
|
#
|
69
|
-
attr_accessor :
|
41
|
+
attr_accessor :logger
|
70
42
|
|
71
43
|
##
|
72
|
-
#
|
44
|
+
# Cache strategy
|
73
45
|
#
|
74
|
-
attr_accessor :
|
46
|
+
attr_accessor :cache_strategy
|
75
47
|
|
76
48
|
##
|
77
|
-
#
|
49
|
+
# Cache strategy options
|
78
50
|
#
|
79
|
-
attr_accessor :
|
51
|
+
attr_accessor :cache_options
|
80
52
|
|
81
53
|
##
|
82
|
-
#
|
54
|
+
# Default configuration values
|
83
55
|
#
|
84
|
-
|
56
|
+
def initialize(**attributes)
|
57
|
+
attributes.each { |key, value| send(:"#{key}=", value) }
|
58
|
+
|
59
|
+
@endpoint ||= "https://www.metal-archives.com/"
|
60
|
+
@logger ||= Logger.new $stdout
|
61
|
+
|
62
|
+
@cache_strategy ||= "memory"
|
63
|
+
@cache_options ||= { size: 100 }
|
64
|
+
end
|
85
65
|
|
86
66
|
##
|
87
|
-
#
|
67
|
+
# Validate configuration
|
68
|
+
#
|
69
|
+
# [Raises]
|
70
|
+
# - rdoc-ref:MetalArchives::Errors::ConfigurationError when configuration is invalid
|
88
71
|
#
|
89
|
-
def
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
72
|
+
def validate!
|
73
|
+
raise Errors::InvalidConfigurationError, "app_name has not been configured" if app_name.blank?
|
74
|
+
raise Errors::InvalidConfigurationError, "app_version has not been configured" if app_version.blank?
|
75
|
+
raise Errors::InvalidConfigurationError, "app_contact has not been configured" if app_contact.blank?
|
76
|
+
raise Errors::InvalidConfigurationError, "cache_strategy has not been configured" if cache_strategy.blank?
|
77
|
+
raise Errors::InvalidConfigurationError, "cache_strategy must be one of: #{CACHE_STRATEGIES.join(', ')}" if CACHE_STRATEGIES.exclude?(cache_strategy.to_s)
|
95
78
|
end
|
96
79
|
end
|
97
80
|
end
|