beatport 0.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 (70) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +18 -0
  4. data/Gemfile.lock +44 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +23 -0
  7. data/Rakefile +55 -0
  8. data/VERSION +1 -0
  9. data/beatport.gemspec +129 -0
  10. data/lib/beatport.rb +15 -0
  11. data/lib/beatport/catalog.rb +93 -0
  12. data/lib/beatport/catalog/account_type.rb +11 -0
  13. data/lib/beatport/catalog/artist.rb +20 -0
  14. data/lib/beatport/catalog/audio_format.rb +10 -0
  15. data/lib/beatport/catalog/audio_format_fee.rb +8 -0
  16. data/lib/beatport/catalog/autocomplete.rb +11 -0
  17. data/lib/beatport/catalog/chart.rb +27 -0
  18. data/lib/beatport/catalog/chart_overview.rb +12 -0
  19. data/lib/beatport/catalog/country.rb +16 -0
  20. data/lib/beatport/catalog/currency.rb +9 -0
  21. data/lib/beatport/catalog/feature.rb +16 -0
  22. data/lib/beatport/catalog/genre.rb +32 -0
  23. data/lib/beatport/catalog/home.rb +13 -0
  24. data/lib/beatport/catalog/image.rb +7 -0
  25. data/lib/beatport/catalog/images.rb +13 -0
  26. data/lib/beatport/catalog/item_type.rb +9 -0
  27. data/lib/beatport/catalog/key.rb +7 -0
  28. data/lib/beatport/catalog/keys.rb +7 -0
  29. data/lib/beatport/catalog/label.rb +29 -0
  30. data/lib/beatport/catalog/list.rb +7 -0
  31. data/lib/beatport/catalog/mixed.rb +9 -0
  32. data/lib/beatport/catalog/recommendations.rb +7 -0
  33. data/lib/beatport/catalog/release.rb +32 -0
  34. data/lib/beatport/catalog/search.rb +11 -0
  35. data/lib/beatport/catalog/slide.rb +17 -0
  36. data/lib/beatport/catalog/slideshow.rb +30 -0
  37. data/lib/beatport/catalog/source_type.rb +9 -0
  38. data/lib/beatport/catalog/state.rb +7 -0
  39. data/lib/beatport/catalog/track.rb +43 -0
  40. data/lib/beatport/client.rb +22 -0
  41. data/lib/beatport/collection.rb +37 -0
  42. data/lib/beatport/inflector.rb +44 -0
  43. data/lib/beatport/item.rb +81 -0
  44. data/lib/beatport/parser.rb +8 -0
  45. data/lib/beatport/price.rb +8 -0
  46. data/lib/beatport/query_builder.rb +74 -0
  47. data/spec/catalog/account_type_spec.rb +16 -0
  48. data/spec/catalog/artist_spec.rb +83 -0
  49. data/spec/catalog/audio_format_spec.rb +14 -0
  50. data/spec/catalog/autocomplete_spec.rb +25 -0
  51. data/spec/catalog/chart_overview_spec.rb +15 -0
  52. data/spec/catalog/chart_spec.rb +96 -0
  53. data/spec/catalog/country_spec.rb +22 -0
  54. data/spec/catalog/currency_spec.rb +13 -0
  55. data/spec/catalog/genre_spec.rb +118 -0
  56. data/spec/catalog/home_spec.rb +16 -0
  57. data/spec/catalog/item_type_spec.rb +11 -0
  58. data/spec/catalog/label_spec.rb +103 -0
  59. data/spec/catalog/mixed_spec.rb +26 -0
  60. data/spec/catalog/release_spec.rb +138 -0
  61. data/spec/catalog/search_spec.rb +24 -0
  62. data/spec/catalog/slide_spec.rb +45 -0
  63. data/spec/catalog/source_type_spec.rb +11 -0
  64. data/spec/catalog/track_spec.rb +173 -0
  65. data/spec/collection_spec.rb +11 -0
  66. data/spec/inflector_spec.rb +34 -0
  67. data/spec/item_spec.rb +11 -0
  68. data/spec/query_builder_spec.rb +100 -0
  69. data/spec/spec_helper.rb +19 -0
  70. metadata +251 -0
@@ -0,0 +1,37 @@
1
+ require 'forwardable'
2
+
3
+ module Beatport
4
+ class Collection
5
+ include Enumerable
6
+ extend Forwardable
7
+
8
+ def_delegators :@results, :each, :'<=>', :length, :==, :===, :to_a
9
+
10
+ attr_reader :host, :path, :query, :next_query
11
+ attr_reader :per_page_options, :page, :per_page, :total_pages, :count
12
+ attr_reader :facets, :applied_facets, :spellcheck
13
+ attr_reader :date_filters, :applied_date_filters
14
+
15
+ def initialize(klass, data)
16
+ raise ArgumentError, "Invalid data passed to Collection.new" unless data['results'].is_a?(Array)
17
+
18
+ data['metadata'].each do |k, v|
19
+ instance_variable_set(:"@#{k}", v)
20
+ end
21
+
22
+ @results = if klass == :auto
23
+ data['results'].map do |r|
24
+ item_klass = Inflector.constantize("Beatport::Catalog::#{r['type'].capitalize}")
25
+ item_klass.new(r)
26
+ end
27
+ else
28
+ data['results'].map { |r| klass.new(r) }
29
+ end
30
+ end
31
+
32
+ def grouped
33
+ group_by { |i| i.type.capitalize }
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,44 @@
1
+ module Beatport
2
+ module Inflector
3
+ def self.constantize(camel_cased_word)
4
+ names = camel_cased_word.split('::')
5
+ names.shift if names.empty? || names.first.empty?
6
+
7
+ constant = Object
8
+ names.each do |name|
9
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
10
+ end
11
+ constant
12
+ end
13
+
14
+ def self.camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
15
+ if first_letter_in_uppercase
16
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
17
+ else
18
+ lower_case_and_underscored_word.to_s[0].chr.downcase + camelize(lower_case_and_underscored_word)[1..-1]
19
+ end
20
+ end
21
+
22
+ def self.underscore(camel_cased_word)
23
+ word = camel_cased_word.to_s.dup
24
+ word.gsub!(/::/, '/')
25
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
26
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
27
+ word.tr!("-", "_")
28
+ word.downcase!
29
+ word
30
+ end
31
+
32
+ def self.process_keys(obj, &block)
33
+ case obj
34
+ when Hash
35
+ Hash[obj.map {|k, v| [yield(k), process_keys(v, &block)]}]
36
+ when Array
37
+ obj.map {|o| process_keys(o, &block)}
38
+ else
39
+ obj
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,81 @@
1
+ module Beatport
2
+
3
+ class Item < OpenStruct
4
+ class << self
5
+ def associations
6
+ @associations ||= {}
7
+ end
8
+
9
+ def has_one(var, klass)
10
+ lazy_accessor(var)
11
+ self.associations[var] = {:list => false, :klass => klass}
12
+ end
13
+
14
+ def has_many(var, klass)
15
+ lazy_accessor(var)
16
+ self.associations[var] = {:list => true, :klass => klass}
17
+ end
18
+
19
+ def lazy_accessor(var)
20
+ return if respond_to?(var)
21
+ class_eval "def #{var}; @#{var}; end"
22
+ end
23
+ end
24
+
25
+ def initialize(data = {})
26
+ raise ArgumentError, "Invalid data passed to Item.new: #{data.inspect}" unless data.is_a?(Hash)
27
+
28
+ # OpenStruct doesn't like id or type, so we store them after the call to super
29
+ id = data.delete('id')
30
+ type = data.delete('type')
31
+
32
+ self.class.associations.each do |k, v|
33
+ associate(data, k, v[:list], v[:klass])
34
+ end
35
+
36
+ super(data)
37
+
38
+ data.each do |k, v|
39
+ @table[k.to_sym] = Date.parse(v) if k =~ /_date$/
40
+ @table[k.to_sym] = Regexp.new(v.to_s) if k =~ /_regex$/
41
+ end
42
+
43
+ @table['id'] = id if id
44
+ @table['type'] = type if type
45
+ end
46
+
47
+ def id
48
+ @table['id']
49
+ end
50
+
51
+ def type
52
+ @table['type']
53
+ end
54
+
55
+ # Allow treating the item as a hash
56
+ def [](key)
57
+ send(key) if respond_to?(key)
58
+ end
59
+
60
+ def associate(data, var, collection = false, klass = Item)
61
+ a = data.delete(var.to_s)
62
+
63
+ return unless a
64
+
65
+ if collection && a.is_a?(Array)
66
+ a = a.compact.map { |g| klass.new(g) }
67
+ elsif !collection && a.is_a?(Hash)
68
+ a = klass.new(a)
69
+ elsif a == [] # || !a
70
+ # In some cases, when there's no data returned for an association it'll be an empty array instead of a hash
71
+ a = nil
72
+ else
73
+ raise ArgumentError, "Invalid data for association: '#{var}' = #{a.inspect}"
74
+ end
75
+
76
+ instance_variable_set(:"@#{var}", a)
77
+ end
78
+
79
+
80
+ end
81
+ end
@@ -0,0 +1,8 @@
1
+ module Beatport
2
+ # A custom HTTParter parse that underscores the keys of the result
3
+ class Parser < HTTParty::Parser
4
+ def parse
5
+ Inflector.process_keys(super) { |k| Inflector.underscore(k) }
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Beatport
2
+ class Price < Money
3
+
4
+ def initialize(data = {})
5
+ super(data['value'] || 0, data['code'])
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,74 @@
1
+ module Beatport
2
+ # Converts a set of arguments into a format that beatport will understand
3
+ class QueryBuilder
4
+ SPECIAL_OPTIONS = ['sortBy', 'facets', 'returnFacets']
5
+
6
+ def self.process(*args)
7
+ new.process(*args)
8
+ end
9
+
10
+ def special_option?(key)
11
+ SPECIAL_OPTIONS.include?(key)
12
+ end
13
+
14
+ def process(*args)
15
+ options = args.last.is_a?(Hash) ? args.pop : {}
16
+
17
+ key = options.delete(:key) || (args.length > 1 ? args.compact : args.first)
18
+
19
+ case key
20
+ when Integer
21
+ options[:id] = key
22
+ when String, Symbol
23
+ options[:slug] = key.to_s
24
+ when Array
25
+ options[:ids] = key
26
+ end
27
+
28
+ options = camelize_keys(options)
29
+
30
+ options.map do |key, value|
31
+ options[key] = send(Inflector.underscore("process_#{key}"), value) if special_option?(key)
32
+ end
33
+
34
+ options
35
+ end
36
+
37
+ # Camelizes all the keys in the options hash
38
+ def camelize_keys(options)
39
+ Inflector.process_keys(options) { |k| Inflector.camelize(k.to_s, false) }
40
+ end
41
+
42
+ def process_sort_by(values)
43
+ map_values(values) do |value|
44
+ split_value(value, " ").join(" ")
45
+ end
46
+ end
47
+
48
+ def process_facets(values)
49
+ map_values(values) do |value|
50
+ k, v = split_value(value, ':')
51
+ v.to_a.map {|v| "#{k}:#{v}"}.join(',')
52
+ end
53
+ end
54
+
55
+ def process_return_facets(values)
56
+ map_values(values) do |value|
57
+ Inflector.camelize(value, false)
58
+ end
59
+ end
60
+
61
+ def map_values(values)
62
+ values = values.split(/,\s*/) if values.is_a?(String)
63
+ values.map do |value|
64
+ yield value
65
+ end.join(",")
66
+ end
67
+
68
+ def split_value(value, seperator)
69
+ value = value.split(seperator) if value.is_a?(String)
70
+
71
+ [Inflector.camelize(value.first, false), value.last]
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ module Beatport::Catalog
4
+ describe AccountType do
5
+ describe 'structure' do
6
+ before(:all) { @account_type = AccountType.all.first }
7
+
8
+ it { @account_type.code.should == "AMEX" }
9
+ it { @account_type.bpid.should == "1" }
10
+ it { @account_type.cybersource_card_type.should == "003" }
11
+ it { @account_type.issue_number_or_start_date_required.should == false }
12
+ it { @account_type.validation_regex.should == /^3[47][0-9]{13}$/ }
13
+ it { @account_type.images.small.url.should == "https://ak-secure-beatport.bpddn.com/images/creditcard/logo_cc_amex_37x23.gif"}
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+
3
+ module Beatport::Catalog
4
+ describe Artist do
5
+ describe 'structure' do
6
+ before(:all) { @artist = Artist.find(7181) }
7
+
8
+ it { @artist.id.should == 7181 }
9
+ it { @artist.type.should == "artist" }
10
+ it { @artist.name.should == "Above & Beyond" }
11
+ it { @artist.slug.should == "above-and-beyond" }
12
+ it { @artist.last_publish_date.should == Date.new(2011, 12, 15)}
13
+ it { @artist.biography.should == "" }
14
+ it { @artist.genres.length.should be > 1 }
15
+ it { @artist.sub_genres.length.should be > 1 }
16
+ it { @artist.top_downloads.length.should be > 1 }
17
+ it { @artist.images.small.url.should == "http://geo-media.beatport.com/items/imageCatalog/0/400000/90000/1000/500/20/491527.jpg" }
18
+ it { @artist.images.medium.url.should == "http://geo-media.beatport.com/items/imageCatalog/0/400000/90000/1000/500/30/491530.jpg" }
19
+ it { @artist.images.large.url.should == "http://geo-media.beatport.com/items/imageCatalog/4000000/600000/80000/6000/400/20/4686424.jpg" }
20
+ it { @artist.featured_releases.length.should be > 1 }
21
+ end
22
+
23
+ describe '.find' do
24
+ it "should find Above & Beyond when given id 7181" do
25
+ artist = Artist.find(7181)
26
+ artist.id.should == 7181
27
+ end
28
+ end
29
+
30
+ describe '.all' do
31
+ it "should get arbitrary artists" do
32
+ artists = Artist.all
33
+ artists.length.should == 10
34
+ end
35
+
36
+ it "should get the first page with 5 artists per page" do
37
+ artists = Artist.all :per_page => 5, :page => 1
38
+ artists.length.should == 5
39
+ artists.page.should == 1
40
+ artists.per_page.should == 5
41
+ end
42
+
43
+ it "should get the first page with 5 artists per page, sorted by publish date and artist id, for the House genre" do
44
+ artists = Artist.all(:sort_by=> {:publish_date => 'asc', :artist_id => 'asc'}, :genre_id=> 5, :per_page=>5, :page=>1)
45
+ artists.length.should == 5
46
+
47
+ old_id = nil
48
+ old_date = artists.first.last_publish_date
49
+
50
+ artists.each do |artist|
51
+ old_id = nil if old_date.to_s != artist.last_publish_date.to_s
52
+
53
+ # beatport has some bad genre data?
54
+ # artist.genres.map(&:id).should include(5)
55
+ artist.id.should be >= old_id if old_id
56
+ artist.last_publish_date.should be >= old_date if old_date
57
+
58
+ old_id = artist.id
59
+ old_date = artist.publish_date
60
+ end
61
+ end
62
+
63
+ it "should get arbitrary artists with filter metadata for all genre names and artist names" do
64
+ artists = Artist.all :return_facets => ['genre_name', 'performer_name']
65
+
66
+ artists.facets['fields']['performer_name'].count.should be > 1
67
+ artists.facets['fields']['genre_name'].count.should be > 1
68
+ end
69
+
70
+ it "should get all trance artists for above & beyond" do
71
+ artists = Artist.all :facets => {:genre_name => ['Trance', 'Progessive House']}
72
+
73
+ artists.each do |artist|
74
+ artists = artist['artists'].map { |a| a['name'] }
75
+ artists.should include("Above & Beyond")
76
+
77
+ genres = artist['genres'].map { |a| a['name'] }
78
+ genres.should include('Trance')
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ module Beatport::Catalog
4
+ describe "AudioFormat" do
5
+ describe 'structure' do
6
+ before(:all) { @audio_format = AudioFormat.all.first }
7
+
8
+ it { @audio_format.id.should == 1 }
9
+ it { @audio_format.name.should == "mp3" }
10
+ it { @audio_format.person_preference_visibility.should == true }
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ module Beatport::Catalog
4
+ describe Autocomplete do
5
+ describe 'stucture' do
6
+ before(:all) { @result = Autocomplete.query('lutzen').first }
7
+ it { @result.name.downcase.should match(/lutzen/) }
8
+ end
9
+
10
+ describe 'collection' do
11
+ before(:all) { @collection = Autocomplete.query('lutzen') }
12
+ it { @collection.host.should == "api.beatport.com" }
13
+ it { @collection.path.should == "/catalog/autocomplete" }
14
+ it { @collection.query.should == "query=lutzen" }
15
+ it { @collection.page.should == 1 }
16
+ it { @collection.per_page.should == 10 }
17
+ it { @collection.count.should be > 1 }
18
+ it { @collection.total_pages.should be > 1 }
19
+ it { @collection.next_query.should == "query=lutzen&page=2"}
20
+ it { @collection.per_page_options.should_not be_nil }
21
+ it { @collection.facets.should_not be_nil }
22
+ it { @collection.spellcheck.should_not be_nil }
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ module Beatport::Catalog
4
+ describe ChartOverview do
5
+ describe '.get' do
6
+ it "should have 16 newest charts and 4 featured charts" do
7
+ overview = ChartOverview.get
8
+
9
+ overview.newest.length.should == 16
10
+ overview.featured.length.should == 4
11
+ end
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,96 @@
1
+ require 'spec_helper'
2
+
3
+ module Beatport::Catalog
4
+ describe Chart do
5
+
6
+ describe 'structure' do
7
+ before(:all) { @chart = Chart.find(15722) }
8
+ it { @chart.id.should == 15722 }
9
+ it { @chart.type.should == "chart" }
10
+ it { @chart.name.should == "Nitrous Oxide – April Top 10" }
11
+ it { @chart.slug.should == "nitrous-oxide-april-top-10" }
12
+ it { @chart.description.should == "" }
13
+ it { @chart.publish_date.should == Date.new(2009, 05, 12) }
14
+ it { @chart.price.to_s.should == '13.91' }
15
+ it { @chart.audio_format_fee.wav.to_s.should == "9.00" }
16
+ it { @chart.audio_format_fee.aiff.to_s.should == "9.00" }
17
+ it { @chart.genres.map(&:name).should == ["Trance"] }
18
+ it { @chart.images.small.url.should == "http://geo-media.beatport.com/items/imageCatalog/0/400000/90000/1000/500/30/491534.jpg"}
19
+ it { @chart.images.medium.url.should == "http://geo-media.beatport.com/items/imageCatalog/0/400000/10000/2000/900/20/412921.jpg"}
20
+ it { @chart.images.large.url.should == "http://geo-media.beatport.com/items/imageCatalog/0/400000/10000/2000/900/20/412922.jpg"}
21
+
22
+ it { @chart.tracks.length.should == 9 }
23
+ end
24
+
25
+ describe '.find' do
26
+ chart = Chart.find(15722)
27
+ chart.id.should == 15722
28
+ end
29
+
30
+ describe '.all' do
31
+ it "should get arbitrary charts" do
32
+ charts = Chart.all
33
+ charts.length.should == 10
34
+ end
35
+
36
+ it "should get the first page with 5 charts per page" do
37
+ charts = Chart.all :per_page => 5, :page => 1
38
+ charts.length.should == 5
39
+ charts.page.should == 1
40
+ charts.per_page.should == 5
41
+ end
42
+
43
+ it "should get the first page with 5 charts per page, sorted by publish date and chart id, for the House genre" do
44
+ charts = Chart.all(:sort_by=> ['publishDate asc', 'chartId asc'], :genre_id=> 5, :per_page=>5, :page=>1)
45
+ charts.length.should == 5
46
+
47
+ old_id = nil
48
+ old_date = charts.first.publish_date
49
+
50
+ charts.each do |chart|
51
+ old_id = nil if old_date.to_s != chart.publish_date.to_s
52
+
53
+ # it's possible that the genre is a subgenre of house, so we'll disable this test for now
54
+ # chart.genres.map(&:id).should include(5)
55
+ chart.id.should be >= old_id if old_id
56
+ chart.publish_date.should be >= old_date if old_date
57
+
58
+ old_id = chart.id
59
+ old_date = chart.publish_date
60
+ end
61
+ end
62
+
63
+ it "should get arbitrary charts with filter metadata for all genre names and artist names" do
64
+ charts = Chart.all :return_facets => ['genre_name', 'performer_name']
65
+
66
+ # despite what the beatport api doc says, the query deoesn't return any performer names...
67
+ # charts.facets['fields']['performer_name'].count.should be > 1
68
+ charts.facets['fields']['genre_name'].count.should be > 1
69
+ end
70
+
71
+ it "should get all charts for trance and progressive house" do
72
+ charts = Chart.all :facets => {:genre_name => ['Trance', 'Progessive House']}
73
+
74
+ charts.each do |chart|
75
+ genres = chart['genres'].map { |a| a['name'] }
76
+ pp genres
77
+ end
78
+ end
79
+ end
80
+
81
+ describe '.featured' do
82
+ it "should get the featured charts for the Home page" do
83
+ charts = Chart.featured
84
+ charts.length.should be > 1
85
+ end
86
+
87
+ it "should get the featured charts for the Trance page" do
88
+ charts = Chart.featured :genre_id => 7
89
+ charts.length.should be > 1
90
+ charts.each do |chart|
91
+ chart.genres.map(&:id).should include(7)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end