Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
Diego Salazar committed Jan 25, 2017
1 parent 28f6988 commit 9d79377
Show file tree
Hide file tree
Showing 67 changed files with 1,265,864 additions and 120 deletions.
Binary file modified .DS_Store
Binary file not shown.
8 changes: 6 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
source 'https://rubygems.org'

ruby "2.3.0"

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '4.1.4'
Expand Down Expand Up @@ -39,12 +39,16 @@ gem 'spring', group: :development
# gem 'debugger', group: [:development, :test]

gem 'bundler'

gem 'rails_12factor'
gem 'bootstrap-sass', '3.3.5'
gem 'bootstrap-sass-extras'
gem 'binding_of_caller'
gem 'tzinfo-data'
gem 'validates_formatting_of'
gem 'haml-rails'
gem 'kaminari'
gem 'bootstrap-kaminari-views'
gem 'bulk_insert'

group :development do
gem 'pry-rails'
Expand Down
40 changes: 40 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,17 @@ GEM
rack (>= 0.9.0)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
bootstrap-kaminari-views (0.0.5)
kaminari (>= 0.13)
rails (>= 3.1)
bootstrap-sass (3.3.5)
autoprefixer-rails (>= 5.0.0.1)
sass (>= 3.2.19)
bootstrap-sass-extras (0.0.7)
rails (>= 3.1.0)
builder (3.2.3)
bulk_insert (1.3.0)
activerecord (>= 4.1.0)
coderay (1.1.1)
coffee-rails (4.0.1)
coffee-script (>= 2.2.0)
Expand All @@ -53,7 +58,20 @@ GEM
debug_inspector (0.0.2)
erubis (2.7.0)
execjs (2.7.0)
haml (4.0.7)
tilt
haml-rails (0.9.0)
actionpack (>= 4.0.1)
activesupport (>= 4.0.1)
haml (>= 4.0.6, < 5.0)
html2haml (>= 1.0.1)
railties (>= 4.0.1)
hike (1.2.3)
html2haml (2.0.0)
erubis (~> 2.7.0)
haml (~> 4.0.0)
nokogiri (~> 1.6.0)
ruby_parser (~> 3.5)
i18n (0.7.0)
jbuilder (2.6.1)
activesupport (>= 3.0.0, < 5.1)
Expand All @@ -62,13 +80,19 @@ GEM
railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0)
json (1.8.6)
kaminari (0.17.0)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
mail (2.5.4)
mime-types (~> 1.16)
treetop (~> 1.4.8)
method_source (0.8.2)
mime-types (1.25.1)
mini_portile2 (2.1.0)
minitest (5.10.1)
multi_json (1.12.1)
nokogiri (1.6.8.1)
mini_portile2 (~> 2.1.0)
pg (0.19.0)
polyglot (0.3.5)
pry (0.10.4)
Expand All @@ -90,13 +114,20 @@ GEM
bundler (>= 1.3.0, < 2.0)
railties (= 4.1.4)
sprockets-rails (~> 2.0)
rails_12factor (0.0.3)
rails_serve_static_assets
rails_stdout_logging
rails_serve_static_assets (0.0.5)
rails_stdout_logging (0.0.5)
railties (4.1.4)
actionpack (= 4.1.4)
activesupport (= 4.1.4)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rake (12.0.0)
rdoc (4.3.0)
ruby_parser (3.8.4)
sexp_processor (~> 4.1)
sass (3.2.19)
sass-rails (4.0.5)
railties (>= 4.0.0, < 5.0)
Expand All @@ -106,6 +137,7 @@ GEM
sdoc (0.4.2)
json (~> 1.7, >= 1.7.7)
rdoc (~> 4.0)
sexp_processor (4.7.0)
slop (3.6.0)
spring (1.7.2)
sprockets (2.12.4)
Expand Down Expand Up @@ -141,19 +173,27 @@ PLATFORMS
DEPENDENCIES
better_errors
binding_of_caller
bootstrap-kaminari-views
bootstrap-sass (= 3.3.5)
bootstrap-sass-extras
bulk_insert
bundler
coffee-rails (~> 4.0.0)
haml-rails
jbuilder (~> 2.0)
jquery-rails
kaminari
pg
pry-rails
rails (= 4.1.4)
rails_12factor
sass-rails (~> 4.0.3)
sdoc (~> 0.4.0)
spring
turbolinks
tzinfo-data
uglifier (>= 1.3.0)
validates_formatting_of

BUNDLED WITH
1.11.2
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,40 @@ To receive JSON run:
curl -H"ACCEPT: application/json" http://simplehabitrecommender.herokuapp.com?recommendations?subtopic=XYZ
```

## Challenge Instructions

For this exercise, we want to be able to build a simple recommendation engine for Simple Habit meditators. To do this, we’ve included a list of all the meditations that have been completed by users on our site — this file is called ‘listens.json’. The format of this file is a JSON array that contains objects that contain three properties:

subtopic: a UUID of the subtopic that was listened to
listenDate: an ISO date string of when the listen occurred
user: a UUID of the user who listened to the meditation

For reference, there is also a table of subtopics that can translate the subtopic UUIDs into real names and descriptions. The format of this document is a JSON array that contains objects that match:

id: The UUID of the subtopic
name: The name of the subtopic
description: A description of the subtopic

Subtopics correspond to the multi-day series on our platform.

We want to build a very simple recommendation engine here. After a user finishes a meditation, we want to recommend them other series to listen to. The most basic algorithm here is conditional probability. Basically, for every subtopic S, we want to determine the most popular subtopics that users who listened to S also listened to. So for example, if the listens look like:

User A: listened to 1, 4, 5
User B: listened to 1, 3, 5
User C: listened to 2, 3, 5

A recommendation for meditation 1 would be meditation 5, since of the two users who listened to meditation 1, 100% of them also listened to meditation 5. Similarly, the recommendations for meditation 5 would be meditations 1 or 3, based on co-occurrences. (There are definitely better recommendation engines than this, but this is the simplest to explain.)

The deliverable here will be a server that will service one route: /recommendations?subtopic=X that will return a list of subtopics to recommend, given that a user just listened to X.

- Feel free to use any language you feel comfortable using.
- Feel free also to use any third party libraries or external services you feel comfortable using, for example using a MySQL or Mongo engine is fine.
- The return format of the API should be a JSON list of the top 4 subtopic UUIDs that the engine would recommend. If there are not enough recommendations, then showing fewer is fine.
- The startup time of the server can take as long as you need.
- No need to support live updates to the recommendation engine — assume this data is all that is needed.
- In terms of performance, we want the service times of the API call to be as fast as possible.
- When you have a server implementation, create a .zip file of it and whatever other scripts you produced and send it back via email.

## Getting Started

1. Install Rails at the command prompt if you haven't yet:
Expand Down
3 changes: 3 additions & 0 deletions app/assets/javascripts/listens.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
3 changes: 3 additions & 0 deletions app/assets/javascripts/recommendations.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
3 changes: 3 additions & 0 deletions app/assets/javascripts/sub_topics.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
3 changes: 3 additions & 0 deletions app/assets/javascripts/users.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
4 changes: 0 additions & 4 deletions app/assets/stylesheets/application.css.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,9 @@
*= require_self
*/
@import "bootstrap_configuration";

@import "bootstrap-sprockets";

@import "bootstrap";

//->Prelang (ui_framework:twitter_bootstrap)

// Give everything in 'yield' margin to separate it.
.yield {
margin: 30px;
Expand Down
3 changes: 3 additions & 0 deletions app/assets/stylesheets/listens.css.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Place all the styles related to the listens controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
3 changes: 3 additions & 0 deletions app/assets/stylesheets/recommendations.css.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Place all the styles related to the recommendations controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
3 changes: 3 additions & 0 deletions app/assets/stylesheets/sub_topics.css.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Place all the styles related to the sub_topics controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
3 changes: 3 additions & 0 deletions app/assets/stylesheets/users.css.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Place all the styles related to the users controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
4 changes: 0 additions & 4 deletions app/controllers/home_controller.rb

This file was deleted.

5 changes: 5 additions & 0 deletions app/controllers/listens_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class ListensController < ApplicationController
def index
@listens = Listen.all.page params[:page]
end
end
18 changes: 18 additions & 0 deletions app/controllers/recommendations_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class RecommendationsController < ApplicationController
def index
if params[:subtopic].blank?
flash[:alert] = "Please provide a subtopic parameter: '/recommendations?subtopic=X' or paste the subtopic into the search field above."
redirect_to root_path and return
end

@sub_topic = SubTopic.find_by_subtopic_id params[:subtopic]
@recommender = Recommender.new @sub_topic, params.fetch(:per, 4), params[:page]

respond_to do |format|
format.html
format.json do
render json: { recommended_subtopics: @recommender.ask.map(&:first) }
end
end
end
end
9 changes: 9 additions & 0 deletions app/controllers/sub_topics_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class SubTopicsController < ApplicationController
def index
@sub_topics = SubTopic.all.page(params[:page]).per 20
end

def show
@sub_topic = SubTopic.find params[:id]
end
end
9 changes: 9 additions & 0 deletions app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class UsersController < ApplicationController
def index
@users = User.all.includes(:listens).page(params[:page]).per 20
end

def show
@user = User.find params[:id]
end
end
3 changes: 3 additions & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
module ApplicationHelper
def paginate(objects, options = {})
super objects, options.reverse_merge!(theme: 'twitter-bootstrap-3')
end
end
2 changes: 2 additions & 0 deletions app/helpers/listens_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module ListensHelper
end
2 changes: 2 additions & 0 deletions app/helpers/recommendations_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module RecommendationsHelper
end
2 changes: 2 additions & 0 deletions app/helpers/sub_topics_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module SubTopicsHelper
end
2 changes: 2 additions & 0 deletions app/helpers/users_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module UsersHelper
end
21 changes: 21 additions & 0 deletions app/models/listen.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,23 @@
# == Schema Information
#
# Table name: listens
#
# id :integer not null, primary key
# user_id :string(255)
# listen_date :datetime
# subtopic_id :string(255)
# created_at :datetime
# updated_at :datetime
#

class Listen < ActiveRecord::Base
belongs_to :user, primary_key: "user_id"
belongs_to :sub_topic, primary_key: :subtopic_id, foreign_key: :subtopic_id
scope :uniq_by_user, -> { select("DISTINCT ON (listens.user_id) listens.*").order :user_id }
default_scope -> { order listen_date: :desc }
paginates_per 20

def self.uniq_user_count
count "DISTINCT user_id"
end
end
13 changes: 13 additions & 0 deletions app/models/recommendation.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,15 @@
# == Schema Information
#
# Table name: recommendations
#
# id :integer not null, primary key
# subtopic_id :string(255)
# recommended_subtopic_ids :text
# created_at :datetime
# updated_at :datetime
#

class Recommendation < ActiveRecord::Base
belongs_to :subtopic
serialize :recommended_subtopic_ids, Array
end
13 changes: 13 additions & 0 deletions app/models/sub_topic.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,15 @@
# == Schema Information
#
# Table name: sub_topics
#
# id :integer not null, primary key
# subtopic_id :string(255)
# name :string(255)
# description :text
# created_at :datetime
# updated_at :datetime
#

class SubTopic < ActiveRecord::Base
has_many :listens, primary_key: :subtopic_id, foreign_key: :subtopic_id
end
17 changes: 17 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,19 @@
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# user_id :string(255)
# created_at :datetime
# updated_at :datetime
#

class User < ActiveRecord::Base
has_many :listens, primary_key: :user_id
validates :user_id, uniqueness: true

scope :who_listened_to, -> subtopic_id {
joins("RIGHT JOIN listens ON listens.user_id = users.user_id").
where listens: { subtopic_id: subtopic_id }
}
end
Loading

0 comments on commit 9d79377

Please sign in to comment.