Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Announcement recommendations #52

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions db/migrations/20171003103621_create_recommendations.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- +micrate Up
CREATE TABLE recommendations (
id BIGSERIAL PRIMARY KEY,
announcement_id INTEGER NOT NULL,
recommended_id INTEGER NOT NULL,
created_at TIMESTAMP,
updated_at TIMESTAMP
);

-- +micrate Down
DROP TABLE IF EXISTS recommendations;
7 changes: 6 additions & 1 deletion public/stylesheets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,7 @@ a:focus {
position: relative;
text-transform: uppercase;
z-index: 2;
padding: 5% 5% 1%;
}

.post-navigation .post-title {
Expand All @@ -855,6 +856,10 @@ a:focus {
z-index: 2;
}

.nav-links .post-title:not(:last-child) {
border-bottom: 1px solid rgba(51, 51, 51, 0.1);
}

.post-navigation .nav-next,
.post-navigation .nav-previous {
background-position: center;
Expand Down Expand Up @@ -4109,7 +4114,7 @@ div.connect-twitter {
}

.post-navigation a {
padding: 5% 10%;
padding: 4% 10%;
}

.pagination {
Expand Down
10 changes: 9 additions & 1 deletion shard.lock
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ shards:
github: luislavena/radix
version: 0.3.8

recommender:
github: hugoabonizio/recommender.cr
version: 0.1.0

redis:
github: stefanwille/crystal-redis
version: 1.8.0
Expand All @@ -90,7 +94,7 @@ shards:

shell-table:
github: jwaldrip/shell-table.cr
version: 0.9.1
version: 0.9.2

sidekiq:
github: mperham/sidekiq.cr
Expand All @@ -112,6 +116,10 @@ shards:
github: crystal-lang/crystal-sqlite3
version: 0.8.2

stemmer:
github: hugoabonizio/stemmer.cr
commit: 1ca4d8c2023c32da006c9ae138f90b327e939016

string_inflection:
github: mosop/string_inflection
version: 0.2.1
Expand Down
4 changes: 4 additions & 0 deletions shard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ dependencies:
github: crystal-community/autolink.cr
version: 0.1.3

recommender:
github: hugoabonizio/recommender.cr
version: 0.1.0

development_dependencies:
spec2:
github: waterlink/spec2.cr
Expand Down
1 change: 0 additions & 1 deletion src/controllers/announcement_controller.cr
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ class AnnouncementController < ApplicationController

def show
if announcement = Announcement.find params["id"]
newer, older = announcement.next, announcement.prev
render("show.slang")
else
redirect_to "/"
Expand Down
6 changes: 6 additions & 0 deletions src/models/announcement.cr
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class Announcement < Granite::ORM::Base
field description : String
timestamps

has_many :recommendations

validate :title, "is too short",
->(this : Announcement) { this.title.to_s.size >= 5 }

Expand Down Expand Up @@ -102,6 +104,10 @@ class Announcement < Granite::ORM::Base
Autolink.auto_link(Markdown.to_html(description.not_nil!))
end

def load_recommendations
Announcement.all("WHERE id IN(#{recommendations.map(&.recommended_id).join(',')})")
end

def self.random
Announcement.all("ORDER BY RANDOM() LIMIT 1").first?
end
Expand Down
11 changes: 11 additions & 0 deletions src/models/recommendation.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require "granite_orm/adapter/pg"

class Recommendation < Granite::ORM::Base
adapter pg

belongs_to :announcement

field announcement_id : Int32
field recommended_id : Int32
timestamps
end
15 changes: 4 additions & 11 deletions src/views/announcement/show.slang
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,7 @@

nav.navigation.post-navigation role="navigation"
div.nav-links
- if newer
div.nav-next
a href="/announcements/#{newer.id}"
span.meta-nav Previous
span.post-title = newer.title

- if older
div.nav-previous
a href="/announcements/#{older.id}"
span.meta-nav Next
span.post-title = older.title
Copy link
Member

@veelenga veelenga Oct 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may need to remove those unneeded methods from Announcement model, adjust controller and specs.

span.meta-nav See more
- announcement.load_recommendations.each do |recommended|
a.post-title href="/announcements/#{recommended.id}"
= recommended.title
40 changes: 40 additions & 0 deletions src/workers/recommender.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require "recommender"
require "sidekiq"
require "../models/announcement"
require "../models/recommendation"

module Workers
class Recommender
include Sidekiq::Worker

def perform
clear_recommendations
announcements = Announcement.all
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sooner or later this will make memory overflow... I'm not an expert in Recommender, but we need to find a way to do this without full table load.

preprocessed_announcements = announcements.map { |a| "#{a.title} #{a.description}" }
recommender = ::Recommender::ContentBased.new(preprocessed_announcements)

signal = Channel(Nil).new

preprocessed_announcements.each_with_index do |_, i|
ids = recommender.similar_to(i).first(3)
ids.each do |j|
data = {
:announcement_id => announcements[i].id,
:recommended_id => announcements[j].id,
}
recommendation = Recommendation.new(data)
spawn do
recommendation.save
signal.send nil
end
end
end

preprocessed_announcements.size.times { signal.receive }
end

private def clear_recommendations
Recommendation.clear
end
end
end