Changelog: Feed Improvements

Josh Puetz - Mar 5 '20 - - Dev Community

We recently completed our first six week cycle here at DEV, and one change I'm particular excited about is making it easier to test and change the way the home page article feed is generated.

First, some background: at the end of January Justin wrote a fantastic deep dive on how the home page article feed is generated. Improving the relevancy of the feed is a big priority for us, but before we could even attempt that there was some housekeeping work to be done. Nick spent a majority of his time this cycle implementing Preact components on the homepage and getting the feed to a place were we (and you!) can make changes to it more easily.

Previously On The Feed

In the past logic to construct the feed existed in both the Rails backend as well as client side JavaScript. StoriesController made an initial query to get some Article objects and ordered them, and initializeFetchFollowedArticles.js.erb did some additional ranking and scoring on that same set of articles. As a result: if one wanted to change the feed to prioritize posts with emojis in their title, changes would need to be made in both StoriesController as well as initializeFetchFollowedArticles.js.erb. Depending on which version of the home page feed a user was looking at (the plain feed versus a time based feed, and if the user is logged in or not), changes might need to be made in additional JavaScript files.

Definitely not ideal for rapid change and experimentation: it's no wonder the home page feed composition has been changed infrequently, and only in small amounts.

Getting it Together

To help make it easier to change how the feed is constructed, we've started to consolidate feed generation logic to a service class: Articles::Feed. Both the legacy StoriesController and a new controller (Stories::FeedController) consume the output of this service when rendering the home page article feed. We've usage of the ranking that happens in initializeFetchFollowedArticles.js.erb from the main home page article feeds for logged in users (the logged out user path and non-preactified feed components still use it. Baby steps!)

TL;DR: if you want to experiment with composition of the home page feed, Articles::Feed is your one stop shop going forward!

Evaluating Feed Performance

It's not enough to simply change how the feed is generated: the goal is to produce a feed that is more relevant and engaging for users. Ben has implemented Andrew Kane's excellent Field Test gem for A/B Testing. This bit of code in Stories::FeedController is where a test is selected:

  def ab_test_user_signed_in_feed(feed)
    test_variant = field_test(:user_home_feed, participant: current_user)
    case test_variant
    when "base"
      feed.default_home_feed(user_signed_in: true)
    when "more_random"
      feed.default_home_feed_with_more_randomness
    when "mix_base_more_random"
      feed.mix_default_and_more_random
    when "more_tag_weight"
      feed.more_tag_weight
    when "more_tag_weight_more_random"
      feed.more_tag_weight_more_random
    else
      feed.default_home_feed(user_signed_in: true)
    end
  end
Enter fullscreen mode Exit fullscreen mode

Each "experiment" gets it's own method in the Articles::Feed service, and users are routed based on the percentage values in field_test.yml. Even better: Field Test tracks which users are getting which home page version, and how often those users are visiting the page and commenting on articles. We're using this information to evaluate which variables of the home page are resonating with users. Always bring data to the conversation!

Up Next

In the coming weeks, we'll monitor and continue to adjust the home page feed algorithm. I'd also like to pull even more instances of feed generation into the Articles::Feed service: feel free to pitch in if you're at all interested!

Happy Coding! ❤️

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .