Overview
The website is migrating from being a pure Rails project to using React for frontend and Rails for backend. We want to implement this in a decoupled manner - that is to say:
- these systems only communiate via an API
- the frontend should not be aware of how data is stored in the backend
- the backend should not be aware of how data is collected in the frontend
- we should be able to replace frontend/backend with no change necessary to the other component (assuming API endpoints remain the same)
In some cases, it isn’t possible to introduce the necessary GET API endpoints yet. In these cases, data from Rails is serialized (converted) into JSON, and passed directly to the react_component
function in the View file.
Reading data from Rails
Process Overview
Diagrams in this section are available for editing to WST members here.
To understand how React will receive data from Rails, we start by reviewing how Views are currently rendered to Users in Rails. As the below diagram demonstrates:
- a request is routed to the Controller via
routes.rb
- the Controller requests data from the Model and provides it to the View
- the View generates HTML which Rails sends to the user
When using React for the frontend, a 2-step process is used to render the data to the user.
- The React page is served to the user
- The React page populates itself with data request from the Rails backend
While this may look inefficient, it is the best practice as it enables a clean, loosely-coupled interface between the frontend and backend. It also allows optimizations such as caching of the API data, instead of loading the data fresh every time a page is requested by the user.
Note that these diagrams describe best practice. In some cases, it may be necessary to provide data to React component directly from the view - this is discussed in “Use Rails data serialization”
The previous diagram demonstrated a situation where no specific information about the user’s request was needed for the API call - the ListComps React component will make the same API request every time. In many cases, however, the user will be trying to view specific data about a particular Model object. In this case, the id
of the data the user is requesting is provided to the React component during Step 1, which it will use to make an API call for a specific resource in Step 2.
Sourcing the data for your React component
As shown above, the goal is for your React app to receive a JSON payload containing the information it is looking for, ideally via an API call. That API may exist, and if it doesn’t, you may be able to expose an endpoint for the data you need.
These steps should be worked through in order, to prevent unnecessary effort or inefficient solutions. They are explained in detail below.
- Find existing API endpoints: Look at
routes.rb
for existing API endpoints and query them to see what data they provide - Use WCIF: If no endpoint exists, and your component is related to a competition, check if your data exists in WCIF.
- Create a new endpoint: If you can’t use WCIF (data doesn’t relate to a competition, or WCIF is too big/slow), try and create a new API endpoint
- Use Rails data serialization: If you can’t create your own endpoint, you’ll need to use [Rails data serialization].
Find existing API endpoints
- Find/open routes.rb
- Search for
namespace :api do
- everything indented under this line defines an API endpoint- Each
namespace
orresources
name adds to the URL string. For example to access the/me
endpoint undernamespace :api
andnamespace :v0
, use/api/v0/me
- For resources, be sure to include the resource ID. Example:
get /wcif
underresources: competitions
becomes/api/v0/competitions/{competition_id}/wcif
competition_id
is found in the URL of a competition -wc2023
is thecompetition_id
of “https://www.worldcubeassociation.org/competitions/wc2023”
api.worldcubeassociation.org
is the base URL for API requests
- Each
- Once you have an endpoint you want to query, send a request to it and inspect the payload to see if it contains the information you want
- In some cases, endpoints may require authentication - in this case you can inspect the controller function, run the repo locally, or contact WST
- Requests can be sent using
curl
, or an API resource likePostman
- If the endpoint doesn’t contain the data you want, but you think it should (eg,
/competitions
doesn’t contain thevenue
field), you may suggest adding that field to the API output
Use WCIF
- Request WCIF for a competition with
api.worldcubeassociation.org/api/v0/competitions/{competition_id}/wcif
- Inspect the WCIF payload to see if it contains the field you’re looking for
WCIF tends to produce large files which are slow to render. If you find that using WCIF leads to issues, you should consider using a dedicated API endpoint instead.
Create a new endpoint
- Consider whether the endpoint should be internal or public, and what data it should return
- Create a new route under the
namespace :api do
section inroutes.rb
, and specify a controller function for it (refer to other API endpoints for guidance) - Write the controller function which renders the JSON you want the API to return
More detail is outside the scope of this documentation at present. Please consult WST for further help.
Use Rails data serialization
By default, Rails makes instance variables created in the Controller available to Views. We can make use of this to pass data (in the form of instance variables) to the react_component
function.
react_component
cannot understand the data in a Rails instance variable, because it is in a Rails-specific format. The data must be converted (“serialized”) from this Rails-specific format to JSON so that react_component
can understand it. For this, the serializable_hash
method is invoked1
If serializable_hash
doesn’t produce data in the format you require, you should either try and use to_wcif
, or write a new method that converts your data into a JSON payload. serializable_hash
should not be edited without a very good reason for editing it.
Writing data to Rails
Diagrams in this section are available for editing to WST members here.
As seen below, writing data to Rails is very similar to the best practice for reading data from Rails. Essentially, the Rails backend exposes an API endpoint which the React frontend can make POST/PATCH calls to.
The process of finding an API endpoint to write data to is the same as the previous section:
- Check if the endpoint already exists
- If yes, modify it if necessary and make use of it
- If no, create one (see “Create a new endpoint” for details)
-
serializable_hash
can serialize Rails data to formats other than JSON, but for our purposes we will only focus on the JSON serialization. ↩