It's a rake task which searches for an artists and if it exists it will store it with artists albums. I've tried to use a gem but for some reason the gem returns something I don't really need. If I search an artist it works fine though.
result = ITunesSearchAPI.lookup(:id => 372976 , :entity => 'album')
will return this:
{"wrapperType"=>"artist", "artistType"=>"Artist", "artistName"=>"ABBA", "artistLinkUrl"=>"https://itunes.apple.com/us/artist/abba/id372976?uo=4", "artistId"=>372976, "amgArtistId"=>3492, "primaryGenreName"=>"Pop", "primaryGenreId"=>14}
which is not what I need at all. Here's what I should get.
So I decided to code it myself and then I realized that it saves an empty model, everything in my Album is nil. 2 questions:
1) How can I fix it?
2) How can I save ALL albums, not just one?
require 'net/http'
task :artist,[""] => :environment do |t, args|
result = ITunesSearchAPI.search(:term => args.to_s, :entity => 'musicArtist')
if result.empty? then puts "Nothing was found. Try another artist."
puts result
elsif result
uniqueness = Artist.find_by(itunes_id: result[0]["artistId"])
if uniqueness.nil?
Artist.create(name: result[0]["artistName"], itunes_id: result[0]["artistId"])
puts result
else
puts "The artist already exists in database"
end
end
if uniqueness.nil?
album = URI('https://itunes.apple.com/lookup')
album_params = { :id => result[0]['artistId'], :entity => 'album'}
album.query = URI.encode_www_form(album_params)
album_response = Net::HTTP.get_response(album)
puts album_response.body
Album.create!(name: album_response.body[0]["collectionName"], artwork_url_100: album_response.body[0]["artworkUrl100"])
end
end
Schema:
ActiveRecord::Schema.define(version: 20160418120725) do
create_table "albums", force: true do |t|
t.string "name"
t.string "artwork_url_100"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "artists", force: true do |t|
t.string "name"
t.integer "itunes_id"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "artists", ["itunes_id"], name: "index_artists_on_itunes_id", unique: true
end
Answer to part 1. You probably need to add some model validations for uniqueness and presence. In your artist.rb file:
class Artist < ActiveRecord::Base
...
validates :itunes_id, presence: true, uniqueness: true
validates :name, presence: true, uniqueness: true
...
end
That should prevent your model being saved in an invalid state. Each attribute itunes_id
and name
must both be present (not nil) and unique (you cannot have 2 'ABBA' artist records).
More on ActiveRecord validation can be found at: http://guides.rubyonrails.org/active_record_validations.html
Once you've got your validation rules in place then your code to check for existing records and updating them can simplify to:
artist = Artist.where(itunes_id: result[0]["artistId"]).first_or_initialize
artist.name = result[0]["artistName"]
artist.save
Then we get to check for any errors that prevented the record from persisting to the database:
if artist.errors.any?
puts "There were errors preventing the artist being saved:"
artist.errors.full_messages.each do |message|
puts " - #{message}"
end
puts "Result data: #{result}"
exit
end
Once we are past this block (we haven't exited) then we know our artist
object is a valid and persisted model object.
Answer to part 2. You need to have a one-to-many (has_many) association between the Artist and Album model. Then you just need to iterate through the results array creating a new Album for each entry.
Looking at your schema you need to add an integer attribute to the Album model called artist_id
. You can create a migration with the command:
rails g migration AddArtistToAlbums artist:references
The magic command line syntax should generate a correct migration file that should look something like this:
class AddArtistToAlbums < ActiveRecord::Migration
def change
add_reference :albums, :artist, index: true, foreign_key: true
end
end
Run a rake db:migrate
to update the database schema.
In your artist.rb model file you can now add the the following:
class Artist < ActiveRecord::Base
...
has_many :albums
...
end
You can now access albums associated to an artist through it's association attribute albums
.
In your album.rb model file you can now add the the following:
class Album < ActiveRecord::Base
...
belongs_to :artist
...
end
You can now access artist associated to an album through it's association attribute artist
.
Before you dive straight in to interpreting the response body I would probably check to see if I got the right kind of request first:
if !album_response.is_a?(Net::HTTPOK)
puts "There was an error fetching albums."
exit
end
Before you can deal with the response you need to parse the JSON. At the top of the file at require 'json'
then parse the album_response.body
like:
album_response_json = JSON.parse(album_response.body)
After that I'd also check to make sure the body is populated as expected.
if !album_response_json.is_a?(Hash)
puts "Response JSON is not a Hash as expected."
exit
end
You can also check that the response hash has the expected results
array.
if !album_response_json["results"].is_a?(Array)
puts "Response JSON does not contain the expected 'results' array."
exit
end
Next, you were accessing a key value from the hash by index album_response.body[0]
which would be an integer (23) based on your example JSON. I think you meant to access the first element of the results
array.
What you need to do is iterate over the results creating a new model object for each album. I noticed that in your example JSON response that there is a wrapperType
of 'artist' which I presume you want to filter out so the code would look something like this:
album_response_json["results"].each do |album_hash|
next if album_hash["wrapperType"] == "artist"
artist.albums.create!(name: album_hash["collectionName"], artwork_url_100: album_hash["artworkUrl100"])
end
You should now have the albums stored as expected.
Note. I skipped past adding validations to the Album model but it would be a good idea.