Ruby on Rails is a web framework heavy on conventions over configuration. All else equal, we should try to follow Rails convention. We are currently on version 5.2.3, due for an upgrade to 6.x.x.
To decrease loading time, we use edge-caching extensively. Taking advantage of edge-caching means that we do not go all the way to the server to render every page. However this means that, on cached pages, we don't have access to helper methods like
current_user. A page is edge-cached through our CDN (Fastly) if the controller contains this line for the relevant action:
We also use server-side caching: Rails caching. If you see
<%= cache ... %>, this is code affected in production by caching.
To avoid blocking the initial render, we frequently write critical CSS inline, and we use the
We make use of serviceworkers to cache portions of the page.
We cache styles here as well as script fingerprinting, so we should increment the number in
Serviceworkers can be controlled in the
application tab of Chrome. Serviceworkers are a reverse proxy that runs in the browser in a non-blocking thread, supported by most major browsers. You may want to disable or bypass Serviceworkers in development while making changes to avoid having everything cached.
The most widespread elements of technical debt in this application reside on the frontend. We use both the "old" approach (files in the
/assets folder) and "new" approach (files in the
We also have overgrown and inconsistent CSS. This is an area we'd love to see contributions from the community.
We also have inconsistencies and issues with how we bust caching on the edge. Ideally, we could practice resource-based purging as described in the Fastly Rails docs, but we bust specific URLs via
The home feed is based on a combination of recent collective posts that are cached and delivered the same to everyone in the HTML, and additional articles fetched from an Elasticsearch index after page load. To determine which posts a user sees, they are ranked based on the user's followed tags, followed users, and relative weights for each tag. Additional fetched articles also follow this general pattern.
Currently, the top post on the home feed, which must have a cover image, is shared among all users.
Forem uses a variation of "instant click", via InstantClick, which swaps out page content instead of making full-page requests. This approach is similar to the one used by the Rails gem
Turbolinks, but our approach is more lightweight. The library is modified to work specifically with this Rails app and does not swap out reused elements like the navigation bar or the footer. The code for this functionality is viewable in
There are a few caveats regarding this approach. Using our approach means a non-trivial amount of functionality is reloaded on page change. A similar amount of reloading occurs when using
window.InstantClick.on('change', someFunction). This results in code that looks something like this:
initPreview(); window.InstantClick.on('change', initPreview);
Abstracting and removing these caveats is a long term goal, and contribution on that front is welcome!
Articles are the primary form of user generated content in the application. An Article has many comments and taggings through the acts-as-taggable gem, belongs to a single user (and possibly an organization), and is the core unit of content.
Comments belong to articles or other content (they are generally polymorphic). They belong first and foremost to the user in our design, which is reflected by the URL (
/username/tag-slug), but they are present in communal areas of the application. They are threaded, but they flatten out gradually to avoid infinitely branching threads.
The user is the authorization/identity component of logging into the app. It is also the public profile/authorship/etc. belonging to the people who use the app.
Tags are used to organize user generated content. Each tag has a set of rules which are used for moderation. Each tag is a de facto community complete with community moderators.
Some tags behave as "flare," highlighting certain articles when viewed from the index page. Tags that act as "flare" are defined in the
FlareTag object. In cases of multiple flare tags, the tag displayed is determined by its hierarchy.
Listings are classified ads. They are similar to posts in some ways, but with ore limitations. They are designed to be categorized into market areas. They also make use of tags.
Credits are the currency of the platform which users can use to buy listings. The functionality of credits may be expanded in the future.
Users can belong to organizations, which have their own profile pages where posts can be published etc. This can be any group endeavor such as a company, an open source project, or any standalone publication on Forem.
Hearts, unicorns, and bookmarks. Reactions are the medium for displaying appreciation for content. Bookmarks have the unique functionality of saving an article in the user's reading list.
How a user keeps track of the tags, users, or articles they care about. Follows impact a user's home feed and notifications.
An organization is a collection of users who can author under one umbrella. An organization could be a company or perhaps just a publication on-site.
Notes are an internal tool admins can use to leave information about things. Example: "This user was warned for spammy content".
Pages in the admin dashboard represent static pages to be served on the site. Admins are in full control to create and customize them to their needs using markdown or custom HTML. Pages are configured with a
slug and they will be served on either the
In order to ease development of custom HTML Pages in local environments the rake task
pages:sync is available. It will listen to changes made to a local HTML file and sync its contents to an existing Page in the database with the matching
This is far from a complete view of the app, but it covers a few core concepts.