##Models:
- Album
- Artist
- Favorite
- Follow
- Genre
- Like
- PlaylistSongs
- PlaylistTags
- Playlist
- Song
- StaffPick
- Tag
- User
##Schema:
ActiveRecord::Schema.define(version: 20160717230059) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "albums", force: :cascade do |t|
t.string "title"
t.integer "artist_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "albums", ["artist_id"], name: "index_albums_on_artist_id", using: :btree
create_table "artists", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "genre_id"
end
add_index "artists", ["genre_id"], name: "index_artists_on_genre_id", using: :btree
create_table "genres", force: :cascade do |t|
t.string "type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "playlist_songs", force: :cascade do |t|
t.integer "playlist_id"
t.integer "song_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "playlist_songs", ["playlist_id"], name: "index_playlist_songs_on_playlist_id", using: :btree
add_index "playlist_songs", ["song_id"], name: "index_playlist_songs_on_song_id", using: :btree
create_table "playlist_tags", force: :cascade do |t|
t.integer "playlist_id"
t.integer "tag_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "playlist_tags", ["playlist_id"], name: "index_playlist_tags_on_playlist_id", using: :btree
add_index "playlist_tags", ["tag_id"], name: "index_playlist_tags_on_tag_id", using: :btree
create_table "playlists", force: :cascade do |t|
t.string "name"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "staff_pick"
end
add_index "playlists", ["user_id"], name: "index_playlists_on_user_id", using: :btree
create_table "songs", force: :cascade do |t|
t.string "title"
t.integer "artist_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "album_id"
end
add_index "songs", ["album_id"], name: "index_songs_on_album_id", using: :btree
add_index "songs", ["artist_id"], name: "index_songs_on_artist_id", using: :btree
create_table "tags", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.string "password_digest"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_foreign_key "albums", "artists"
add_foreign_key "artists", "genres"
add_foreign_key "playlist_songs", "playlists"
add_foreign_key "playlist_songs", "songs"
add_foreign_key "playlist_tags", "playlists"
add_foreign_key "playlist_tags", "tags"
add_foreign_key "playlists", "users"
add_foreign_key "songs", "albums"
add_foreign_key "songs", "artists"
end
###How it works:
-
After login, the homepage is loaded @ 'home#index'.
class HomeController < ApplicationController def index @suggestions = current_user.fetch_suggestions end end
-
The 'fetch_suggestions' in the user model gathers attributes of likes, follows, favorites, and staff picks, and cycles through creating suggestions until 50 have been made. The suggestions are then saved to a redis store with an expiration of 3 hours.
class User < ActiveRecord::Base has_many :likes has_many :favorites has_many :followers has_secure_password def fetch_suggestions suggestions = $redis.get('suggestions') if suggestions.nil? suggestions = gather_suggestions.to_json $redis.set('suggestions', suggestions) $redis.expire('suggestions', 3.hour.to_i) end JSON.load suggestions end def gather_suggestions suggestions = [] while suggestions.count <= 50 current_attributes = attributes_array[suggestions.count%4] suggestions << current_attributes[-1].generate_suggestion current_attributes.rotate!(-1) end suggestions end def attributes_array array ||= [self.favorites, self.likes, self.follows, StaffPicks.all].select {|el| !el.empty? || el != nil} array end end
-
Each different model type in the 'attributes_array' has a 'generate_suggestion' method that varies depending on what process is needed.
-
Example 1: Like
class Like < ActiveRecord::Base belongs_to :user belongs_to :playlist def generate_suggestion suggestion = SuggestionService.find_suggestion(self.playlist.tags) { reason: 'Because you liked "#{self.playlist.name}", playlist: suggestion} end end
-
Example 2: StaffPick
class StaffPicks < ActiveRecord::Base def generate_suggestion suggestion = User.find_by(name: 'staff_picks').last { reason: 'Staff Pick', playlist: suggestion } end end
-
Example 3: Favorite
class Favorite < ActiveRecord::Base belongs_to :user belongs_to :song def generate_suggestion tags = [self.artist.name] + [self.artist.genres] suggestion = SuggestionService.find_suggestions(tags) { reason: "Because you favorited #{self.title} by #{self.song.artist"}, playlist: suggestion } end end
(Note: The arguments passed through SuggestionService.find_suggestions(argument), would be treated as the name of a tag on the other end. From there similar playlists would be found through similar tags throughout 8tracks playlists.
- These suggestions are returned to the view and iterated over as cards.
###Optimization:
- Dynamic Pagination: Instead of loading all 50 suggestions to the homepage at once, implementing a dynamic scroll based pagination could speed things up considerably. An AJAX call would load the first 10 suggested playlists (page 1) onto the view. Once the user scrolls to the bottom of the page, an AJAX call would be made for page 2 of results and so on.
- AJAX call:
$.ajax({
url: "/api/v1/home/" + pageNumber,
type: "GET",
success: function(response){
console.log("Success!", response.suggestions)
appendSuggestions(response)
}
});
- API Controller
class Api::V1::HomeController < Api::ApiController
respond_to :json
def index
respond_with current_user.get_suggestions(params[:page])
end
end
-
With the pagination above, using 'ActionContoller::Metal' rather than 'Base' could speed up the load times for scrolling considerably.
-
Grabbing suggestions in batches rather than individually by changing 'generate_suggestion', to 'generate_suggestions(amount)'. This would speed up the initail suggestion grab, but would end in a less hetergeneous feed.