Skip to content

Instantly share code, notes, and snippets.

@Jlawlzz
Created July 18, 2016 07:21
Show Gist options
  • Save Jlawlzz/6c275e054564e62e145e50dabab4fcff to your computer and use it in GitHub Desktop.
Save Jlawlzz/6c275e054564e62e145e50dabab4fcff to your computer and use it in GitHub Desktop.

##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:

  1. After login, the homepage is loaded @ 'home#index'.

       class HomeController < ApplicationController
         
         def index
           @suggestions = current_user.fetch_suggestions
         end
       
       end
    
  2. 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
    
  3. 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.

  1. These suggestions are returned to the view and iterated over as cards.

###Optimization:

  1. 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
  1. With the pagination above, using 'ActionContoller::Metal' rather than 'Base' could speed up the load times for scrolling considerably.

  2. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment