Refactoring Artist model

This commit is contained in:
Gregory Eremin 2012-07-08 23:46:09 +04:00
parent 23d27756b8
commit ab78050fec
10 changed files with 254 additions and 89 deletions

View File

@ -6,3 +6,8 @@ RSpec::Core::RakeTask.new("spec")
task :default => :spec task :default => :spec
task :test => :spec task :test => :spec
desc "Open an irb session preloaded with this library"
task :console do
sh "irb -rubygems -I lib -r musicbrainz.rb"
end

View File

@ -4,18 +4,25 @@ require "socket"
require "nokogiri" require "nokogiri"
require "cgi" require "cgi"
module MusicBrainz
module Tools
end
module Parsers
end
end
require "version" require "version"
require "deprecated" require "deprecated"
module MusicBrainz require "musicbrainz/base"
autoload :Base, "musicbrainz/base" require "musicbrainz/artist"
autoload :Artist, "musicbrainz/artist" require "musicbrainz/release_group"
autoload :ReleaseGroup, "musicbrainz/release_group" require "musicbrainz/release"
autoload :Release, "musicbrainz/release" require "musicbrainz/track"
autoload :Track, "musicbrainz/track"
module Tools require "tools/cache"
autoload :Cache, "tools/cache" require "tools/proxy"
autoload :Proxy, "tools/proxy"
end require "parsers/base"
end require "parsers/artist"
require "parsers/release_group"

View File

@ -1,81 +1,78 @@
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
module MusicBrainz module MusicBrainz
class Artist < MusicBrainz::Base class Artist < Base
attr_accessor :id, :type, :name, :country, :date_begin, :date_end, :urls
@release_groups field :id, String
field :type, String
field :name, String
field :country, String
field :date_begin, Time
field :date_end, Time
field :urls, Hash
def release_groups def release_groups
if @release_groups.nil? and not self.id.nil? @release_groups ||= nil
@release_groups = [] if @release_groups.nil? and !id.nil?
Nokogiri::XML(self.class.load(:release_group, :artist => self.id)).css('release-group').each do |rg| @release_groups = self.class.load({
@release_groups << MusicBrainz::ReleaseGroup.parse_xml(rg) :parser => :artist_release_groups,
end :create_models => MusicBrainz::ReleaseGroup
}, {
:resource => :release_group,
:artist => id
})
@release_groups.sort!{ |a, b| a.first_release_date <=> b.first_release_date }
end end
@release_groups.sort{ |a, b| a.first_release_date <=> b.first_release_date } @release_groups
end end
def self.find mbid class << self
res = self.load :artist, :id => mbid, :inc => [:url_rels] def find(mbid)
return nil if res.nil? load({
@artist = self.parse_xml(Nokogiri::XML(res)) :parser => :artist_model,
end :create_model => MusicBrainz::Artist
}, {
def self.parse_xml xml :resource => :artist,
@artist = MusicBrainz::Artist.new :id => mbid,
@artist.id = self.safe_get_attr(xml, 'artist', 'id') :inc => [:url_rels]
@artist.type = self.safe_get_attr(xml, 'artist', 'type') })
@artist.name = self.safe_get_value(xml, 'artist > name').gsub(/[`]/, "'")
@artist.country = self.safe_get_value(xml, 'artist > country')
@artist.date_begin = self.safe_get_value(xml, 'artist > life-span > begin')
@artist.date_end = self.safe_get_value(xml, 'artist > life-span > end')
@artist.urls = {}
xml.css('relation-list[target-type="url"] > relation').each do |rel|
@artist.urls[rel.attr('type').downcase.split(' ').join('_').to_sym] = rel.css('target').text
end end
@artist
end
def self.discography mbid def search(name)
artist = self.find(mbid) artists = load({
artist.release_groups.each {|rg| rg.releases.each {|r| r.tracks } } :parser => :artist_search
artist }, {
end :resource => :artist,
:query => CGI.escape(name).gsub(/\!/, '\!') + '~',
:limit => 50
})
def self.find_by_name name artists.each { |artist|
matches = self.search name if artist[:name].downcase == name.downcase
matches.length.zero? ? nil : self.find(matches.first[:mbid]) artist[:weight] += 80
end elsif artist[:name].downcase.gsub(/\s/, "") == name.downcase.gsub(/\s/, "")
artist[:weight] += 25
def self.search name elsif artist[:aliases].include? name
artists = [] artist[:weight] += 20
xml = Nokogiri::XML(self.load(:artist, :query => CGI.escape(name).gsub(/\!/, '\!') + '~', :limit => 50)) elsif artist[:aliases].map { |item| item.downcase }.include?(name.downcase)
xml.css('artist-list > artist').each do |a| artist[:weight] += 10
artist = { elsif artist[:aliases].map { |item| item.downcase.gsub(/\s/, "") }.include?(name.downcase.gsub(/\s/, ""))
:name => a.first_element_child.text.gsub(/[`]/, "'"), artist[:weight] += 5
:sort_name => self.safe_get_value(a, 'sort-name').gsub(/[`]/, "'"), end
:weight => 0,
:desc => self.safe_get_value(a, 'disambiguation'),
:type => self.safe_get_attr(a, nil, 'type'),
:mbid => self.safe_get_attr(a, nil, 'id')
} }
aliases = a.css('alias-list > alias').map{ |item| item.text } artists.sort{ |a, b| b[:weight] <=> a[:weight] }.take(10)
if artist[:name] == name end
artist[:weight] += 100
elsif artist[:name].downcase == name.downcase def discography(mbid)
artist[:weight] += 50 artist = find(mbid)
elsif artist[:name].downcase.gsub(/\s/, '') == name.downcase.gsub(/\s/, '') artist.release_groups.each { |rg| rg.releases.each { |r| r.tracks } }
artist[:weight] += 25 artist
elsif aliases.include? name end
artist[:weight] += 20
elsif aliases.map{ |item| item.downcase }.include? name.downcase def find_by_name(name)
artist[:weight] += 10 matches = search(name)
elsif aliases.map{ |item| item.downcase.gsub(/\s/, '') }.include? name.downcase.gsub(/\s/, '') matches.length.zero? ? nil : find(matches.first[:mbid])
artist[:weight] += 5
end
artists << artist
end end
artists.sort{ |a, b| b[:weight] <=> a[:weight] }.take(10)
end end
end end
end end

View File

@ -1,17 +1,71 @@
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
module MusicBrainz module MusicBrainz
class Base class Base
def self.safe_get_attr(xml, path, name) class << self
node = path.nil? ? xml : (xml.css(path).first unless xml.css(path).empty?) def field(name, type)
node.attr(name) unless node.nil? or node.attr(name).nil? @fields ||= {}
@fields[name] = type
define_method(name) {
instance_variable_get("@#{name}")
}
define_method("#{name}=") { |val|
instance_variable_set("@#{name}", validate_type(val, type))
}
end
def load(params, query)
parser = MusicBrainz::Parsers.get_by_name(params[:parser])
xml = MusicBrainz::Tools::Proxy.load(query)
result = parser[:const].send(parser[:method], Nokogiri::XML(xml))
if params[:create_model]
result_model = params[:create_model].new
result.each { |field, value|
result_model.send("#{field}=".to_sym, value)
}
result_model
elsif params[:create_models]
result_models = []
result.each { |item|
result_model = params[:create_models].new
item.each { |field, value|
result_model.send("#{field}=".to_sym, value)
}
result_models << result_model
}
result_models
else
result
end
end
end end
def self.safe_get_value(xml, path) def initialize
xml.css(path).first.text unless xml.css(path).empty? self.class.instance_variable_get("@fields").each { |name, type|
instance_variable_set("@#{name}", nil)
}
end end
def self.load(*args) def validate_type(val, type)
MusicBrainz::Tools::Proxy.load(*args) if type == Integer
val.to_i
elsif type == Float
val.to_f
elsif type == String
val.to_s
elsif type == Time
if val.nil? or val == ""
val = "2030-12-31"
elsif val.split("-").length == 1
val << "-12-31"
elsif val.split("-").length == 2
val << "-31"
end
Time.utc(*val.split("-"))
else
val
end
end end
end end
end end

View File

@ -1,8 +1,12 @@
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
module MusicBrainz module MusicBrainz
class ReleaseGroup < MusicBrainz::Base class ReleaseGroup < MusicBrainz::Base
attr_accessor :id, :type, :title, :disambiguation, :first_release_date
@releases field :id, String
field :type, String
field :title, String
field :disambiguation, String
field :first_release_date, Time
def releases def releases
if @releases.nil? and not self.id.nil? if @releases.nil? and not self.id.nil?

49
lib/parsers/artist.rb Normal file
View File

@ -0,0 +1,49 @@
# -*- encoding: utf-8 -*-
module MusicBrainz
module Parsers
class Artist < Base
class << self
def model(xml)
res = {
:id => safe_get_attr(xml, "artist", "id"),
:type => safe_get_attr(xml, "artist", "type"),
:name => safe_get_value(xml, "artist > name").gsub(/[`]/, "'"),
:country => safe_get_value(xml, "artist > country"),
:date_begin => safe_get_value(xml, "artist > life-span > begin"),
:date_end => safe_get_value(xml, "artist > life-span > end"),
:urls => {}
}
xml.css("relation-list[target-type='url'] > relation").each { |rel|
res[:urls][rel.attr("type").downcase.split(" ").join("_").to_sym] = rel.css("target").text
}
res
end
def search(xml)
artists = []
xml.css("artist-list > artist").each do |a|
artists << {
:name => a.first_element_child.text.gsub(/[`]/, "'"),
:sort_name => self.safe_get_value(a, "sort-name").gsub(/[`]/, "'"),
:weight => 0,
:desc => self.safe_get_value(a, "disambiguation"),
:type => self.safe_get_attr(a, nil, "type"),
:mbid => self.safe_get_attr(a, nil, "id"),
:aliases => a.css("alias-list > alias").map { |item| item.text }
}
end
artists
end
def release_groups(xml)
release_groups = []
xml.css("release-group").each do |rg|
release_groups << MusicBrainz::Parsers::ReleaseGroup.model(rg)
end
release_groups
end
end
end
end
end

30
lib/parsers/base.rb Normal file
View File

@ -0,0 +1,30 @@
# -*- encoding: utf-8 -*-
module MusicBrainz
module Parsers
class << self
def get_by_name(name)
case name
when :artist_model
return { :const => MusicBrainz::Parsers::Artist, :method => :model }
when :artist_search
return { :const => MusicBrainz::Parsers::Artist, :method => :search }
when :artist_release_groups
return { :const => MusicBrainz::Parsers::Artist, :method => :release_groups }
end
end
end
class Base
class << self
def safe_get_attr(xml, path, name)
node = path.nil? ? xml : (xml.css(path).first unless xml.css(path).empty?)
node.attr(name) unless node.nil? or node.attr(name).nil?
end
def safe_get_value(xml, path)
xml.css(path).first.text unless xml.css(path).empty?
end
end
end
end
end

View File

@ -0,0 +1,19 @@
# -*- encoding: utf-8 -*-
module MusicBrainz
module Parsers
class ReleaseGroup < Base
class << self
def model(xml)
{
:id => safe_get_attr(xml, nil, "id"),
:type => safe_get_attr(xml, nil, "type"),
:title => safe_get_value(xml, "title"),
:disambiguation => safe_get_value(xml, "disambiguation"),
:first_release_date => safe_get_value(xml, "first-release-date")
}
end
end
end
end
end

View File

@ -21,8 +21,8 @@ module MusicBrainz
@@tries_limit = num.to_i @@tries_limit = num.to_i
end end
def self.load(resourse, params = {}) def self.load(params = {})
url = WEB_SERVICE_URL + resourse.to_s.gsub('_', '-') + '/' + (params[:id].to_s || '') url = WEB_SERVICE_URL + params[:resource].to_s.gsub('_', '-') + '/' + (params[:id].to_s || '')
params.delete(:id) unless params[:id].nil? params.delete(:id) unless params[:id].nil?
url << '?' + params.map{ |k, v| url << '?' + params.map{ |k, v|
k = k.to_s.gsub('_', '-') k = k.to_s.gsub('_', '-')

View File

@ -21,7 +21,7 @@ describe MusicBrainz::Artist do
it "finds name first than alias" do it "finds name first than alias" do
matches = MusicBrainz::Artist.search('Chris Martin') matches = MusicBrainz::Artist.search('Chris Martin')
matches.length.should be > 0 matches.length.should be > 0
matches.first[:name].should == "Chris Martin" matches.first[:mbid].should == "af2ab893-3212-4226-9e73-73a1660b6952"
end end
it "gets correct result by name" do it "gets correct result by name" do
@ -35,7 +35,7 @@ describe MusicBrainz::Artist do
artist.type.should == "Group" artist.type.should == "Group"
artist.name.should == "Kasabian" artist.name.should == "Kasabian"
artist.country.should == "GB" artist.country.should == "GB"
artist.date_begin.should == "1999" artist.date_begin.year.should == 1999
end end
it "gets correct artist's release groups" do it "gets correct artist's release groups" do