Disco: collaborative filtering recommendations inside a Rails app
Recommendations for Ruby and Rails using collaborative filtering
At a glance
- What is it?
- Disco is a Ruby gem that fits matrix factorization models over explicit or implicit feedback and serves user-based, item-based and similar-item recommendations from a Rails application, with a generator for persisting results.
- Who is it for?
- Disco fits teams already running Rails who want recommendations trained on their own interaction data and can accept cold start returning nothing. Do not pick it when you need content signals for brand new items or a hosted service with no Ruby integration, and check how the serialized recommender stores user activity before you persist one.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 153 days ago.
- What is it written in?
- Mainly Ruby, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A recommender gem for apps that already hold interaction data
Disco targets a specific situation: a Ruby or Rails application that already records what its users did, and wants to turn that history into suggestions. The scope is narrow and stated in the project's own summary line, recommendations for Ruby and Rails using collaborative filtering. There is no service to host, no API to call and no database schema to design before training, because the data you feed it is an array of hashes you build yourself. That makes the gem a good fit for teams whose recommendation source of truth is their own database, and a poor fit for anyone expecting a turnkey product catalogue. The credit notes point at LIBMF, and the algorithmic references are to the original matrix factorization papers, so the implementation leans on well studied numerical methods rather than inventing a scoring scheme of its own. Adding it is a one line change to the Gemfile, and the library is MIT licensed.
Explicit and implicit feedback take different fitting paths
The first decision you make is what kind of feedback you have. If users rate items directly, that is explicit feedback, and each row carries a rating:
recommender.fit([
{user_id: 1, item_id: 1, rating: 5},
{user_id: 2, item_id: 1, rating: 3}
])If users never rate anything but simply buy items or read posts, that is implicit feedback, and you leave the rating key out:
recommender.fit([
{user_id: 1, item_id: 1},
{user_id: 2, item_id: 1}
])The difference is not cosmetic. The documentation states that explicit feedback is fitted with stochastic gradient descent and implicit feedback with coordinate descent, so the same call trains a different model depending on which keys your hashes contain. Identifiers can be integers, strings, or any other data type, which is convenient when you want to key on database ids. One constraint is worth reading twice: each user_id and item_id combination should only appear once in the data you fit, so a naive join that emits one row per view will not work without aggregation.
user_recs and item_recs answer two different questions
Once a model is fitted, the query surface is small and the semantics of each method differ. User-based recommendations answer the users like you also liked question, while item-based recommendations answer the users who liked this item also liked question:
recommender.user_recs(user_id)
recommender.item_recs(item_id)The default result count is five, and the count option changes it:
recommender.user_recs(user_id, count: 3)You can also ask for predicted ratings on specific user and item pairs, which is useful when you want a number to sort by rather than a list:
recommender.predict([{user_id: 1, item_id: 2}, {user_id: 2, item_id: 4}])Similar users are available as well through similar_users. The training data itself comes in as an array of hashes or as a Rover data frame, so a ratings file can be read directly:
Rover.read_csv("ratings.csv")For a quick first look, the gem ships a MovieLens loader, and the example fits with twenty factors before asking for similar movies for a title string:
recommender = Disco::Recommender.new(factors: 20)
recommender.fit(data)
recommender.item_recs("Star Wars (1977)")Factors and epochs are the only tuning knobs
The model size is set at construction time:
Disco::Recommender.new(factors: 8, epochs: 20)The default number of factors is eight, and the documentation is unusually direct that this is a starting point rather than an optimum, since three may suit some applications and three hundred others. If recommendations look wrong, the suggested response is to change the factor count and retrain. There is no separate learning-rate parameter or regularisation knob described in the documentation, so tuning is coarse. That is the main practical constraint of the library: you can scale model capacity and training length, but you cannot reach into the optimiser. You can, however, hold out data and pass a validation set to fit:
recommender.fit(data, validation_set: validation_set)The validation set lets you compare factor counts against each other instead of guessing, which is the more defensible way to pick a value than trying three by hand.
Cold start returns an empty array rather than a guess
Collaborative filtering cannot recommend for a user or item it has never seen, and Disco does not pretend otherwise. The documentation names this the cold start problem and states that it is problematic for new users and items. The behaviour is blunt:
recommender.user_recs(new_user_id) # returns empty arrayAn empty array reaching your view is a failure unless you plan for it, so a new user path needs a fallback. The suggested options are to show new users the most popular items for the user-based case, or to make content-based recommendations for the item-based case, with a gem such as tf-idf-similarity named as the tool for the latter. Disco helps with the popular-items path by offering a top_items mode:
recommender = Disco::Recommender.new(top_items: true)
recommender.fit(data)
recommender.top_itemsThat ranking uses Wilson score for explicit feedback and item frequency for implicit feedback, so the ordering differs depending on your feedback type. If your catalogue rotates in fresh items with no interaction history, this gem will not rank them well and you need content signals alongside it.
The generator writes recommendation columns onto your models
For Rails applications the library ships a generator, so the persistence path is a normal Rails workflow rather than custom glue:
rails generate disco:recommendation
rails db:migrateUser-based recommendations are then declared on the model with has_recommended, pointing at the model you want to recommend:
class User < ApplicationRecord
has_recommended :products
endThe documentation notes that the symbol should be changed to match the model you are recommending. Recommendations are written by iterating over users and calling the update method, and read back through the matching reader:
User.find_each do |user|
recs = recommender.user_recs(user.id)
user.update_recommended_products(recs)
enduser.recommended_productsIf you need more than one kind of recommendation on a single model, several can be declared with different names, and each gets its own update and read pair, such as update_recommended_products_v2 and recommended_products_v2. Note that the computation happens in your job, on the full user base, which means the cost of generating recommendations scales with your user count and should live in a background task rather than a web request.
The serialized model carries user activity and should be treated as sensitive
If you would rather compute recommendations at request time than store them, the model itself can be serialized and reloaded:
json = recommender.to_json
File.write("recommender.json", json)json = File.read("recommender.json")
recommender = Disco::Recommender.load_json(json)The security note matters here. The documentation states that the serialized recommender includes user activity from the training data, which it does to avoid recommending items a user has already rated, and says to protect it. In other words, the file is not just model weights. Anyone who reads it learns which users interacted with which items. Store it somewhere with access control, and remember that an alternative exists: keep only the factors and use a library such as Neighbor instead, which the documentation points at for exactly this case. That path also comes with a constraint, because user and item IDs should then be integers or strings.
Faiss optimization changes how the model is searched, not how it is trained
For large user or item counts, the documentation points to an approximate nearest neighbors library such as Faiss to improve the performance of certain methods. Adding it is another Gemfile line, gem "faiss", and the speedups are enabled per method:
recommender.optimize_user_recsrecommender.optimize_item_recsrecommender.optimize_similar_usersThese calls must happen after fitting or loading the recommender, which is an easy ordering mistake to make in a boot sequence. The trade-off is inherent to the approach: approximate search gives up exactness for speed, and the documentation does not describe a quality metric for the loss. For a catalogue of a few thousand items the standard path is likely fine. If you are dealing with a large catalogue and care about ranking quality, compare the optimized and unoptimized output on a sample before switching. Also note the repository has no GitHub releases, so upgrades arrive through the gem rather than through tagged versions, and the last push was on 2026-05-23.
Editorial conclusion
Disco fits teams already running Rails who want recommendations trained on their own interaction data and can accept cold start returning nothing. Do not pick it when you need content signals for brand new items or a hosted service with no Ruby integration, and check how the serialized recommender stores user activity before you persist one.
Frequently asked questions
How do I add Disco to a Rails application?
Add the gem to the application's Gemfile, then create a recommender with Disco::Recommender.new and fit it with your interaction data. For storing results, run the rails generate disco:recommendation generator and then rails db:migrate.
What is the difference between user_recs and item_recs in Disco?
user_recs returns items for a given user, based on what similar users liked, while item_recs returns items similar to a given item, based on what other users who liked it also liked. Both return five results by default, and the count option changes that number.
How does Disco handle new users with no activity?
It returns an empty array, because collaborative filtering cannot recommend for a user or item it has never seen. The documentation suggests showing the most popular items to new users, using a recommender created with top_items: true, or adding content-based recommendations for items.
Can Disco work with implicit feedback such as page views?
Yes. Fit it with rows that carry user_id and item_id but no rating key, which the documentation describes as implicit feedback. Each user_id and item_id combination should only appear once, so views have to be grouped before they are passed in.
What license is Disco released under?
The project is MIT licensed. The gem is published as disco, and the documentation credits LIBMF and the original matrix factorization papers for the underlying methods.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/ankane-disco)Was this useful?