How to share Rails i18n messages with React through webpack

Very specific title hu? This time I’m here for sharing a solution for a specific problem I encountered a couple of days ago.

There’s this existing Rails monolithic application. Team and customer decided that time had come for this app to be decoupled in two components: Rails would do its usual work as an administration and API backend, while React would be used for the frontend component. Everything related to the frontend would then be rewritten, keeping the same behaviour and visual design. But there are a lot of translations related to the user experience and that have now to be included in the javascript bundle, while they were before used by the server.

To do so, I was aiming to write a webpack plugin that could give access to the Rails yml translations. Then I discovered the Virtual Module Plugin: During webpack compilation this plugin injects a fake file containing the given text.

Working on it led me to an extension that flattens and joins the various translations, providing them to javascript source files without duplicating data: a Rails Translations Plugin. In our case we didn’t even want to share ALL translations between Rails and React, but just a frontend.yml file (that stores messages related to the user experience), so the resulting webpack.config.js is:

// app/frontend/webpack.config.js

const clientApp = path.resolve(__dirname, 'app');
const webpackConfig = {
  entry: [path.join(clientApp, 'index.js')],
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  plugins: [
    new RailsTranslationsPlugin({
      root       : clientApp,
      localesPath: path.resolve(__dirname, '../backend/config/locales'),
      pattern    : '**/frontend.yml'
    })
  ],
  module: {
    loaders: [
      {
        include : clientApp,
        test    : /\.(js|jsx|es6)$/,
        loader  : 'babel-loader'
      },
      {
        include : clientApp,
        test    : /\.json$/,
        loader  : 'json-loader'
      }
    ]
  }
};

And this allowed us to use them in ReactIntl:

// app/context.jsx
// ...
import { addLocaleData } from 'react-intl';
import translations from 'translations.json';

for (let locale of Object.keys(translations)) {
  addLocaleData(require(`react-intl/locale-data/${locale}`));
}

const locale   = navigator.language || 'en';
const messages = translations[locale];

return (
  <IntlProvider locale={locale} key={locale} messages={messages}>
    <App />
  </IntlProvider>
)

The only downside is that React Intl is not able, obviously, to work with Rails localization formats, and in fact we needed to iterate over the various locales and require its additional data needed for localization (look at the addLocaleData method in the above snippet). I’m now thinking of improving this plugin adding builtin methods that work like Rails’s I18n.t and I18n.l.

From arduino to rails through api

Some years ago I used pachube.com / cosm.com to save a stream of data generated from a sensor attached to an Arduino board. Unfortunately  this service is no more available, but you can build your own data stream repository with rails and heroku.

In this example we read temperatures from an Arduino sensor and then we save data via API using a rails app.

There are several guides to build REST APIs using rails. This tutorial integrates some best practices but you need to focus your work on a smaller subset, depending on your need. Rails and JSON give you the perfect combination for a fast and comprehensive developement.

Rails api controllers

# /app/api/temperatures_controller.rb

module Api
  class TemperaturesController < ActionController::Base
    protect_from_forgery
    skip_before_action :verify_authenticity_token, if: :json_request?

    def index
      temperatures = Temperature.all
      if temperatures.any?
        render json: temperatures
      else
        render json: {}, status: :not_found
      end
    end

    def show
      temperature = Temperature.find(params[:id]) rescue nil
      if temperature
        render json: temperature
      else
        render json: {}, status: :not_found
      end
    end

    def create
      temperature = Temperature.new(temperature_params)
      if temperature.save
        render json: temperature, location: api_temperature_path(temperature.id), status: :created
      else
        render json: { errors: temperature.errors }, status: :unprocessable_entity
      end
    end

    ...
    ...

    protected

    def json_request?
      request.format.json?
    end


    private

    def temperature_params
      params.require(:temperature).permit(:value)
    end
  end
end

Test locally

During your development you can test your API with curl, here are some examples for saving and retrieving JSON data feeds:

# create
curl -v loalhost:3000/api/temperatures -X POST 
     -H "Accept: application/json" 
     -H "Content-Type: application/json" 
     -d '{"temperature": {"value": 20}}'

# index
curl -v loalhost:3000/api/temperatures

# show
curl -v loalhost:3000/api/temperatures/1

Your goal now is to create a POST request from Arduino just like the one generated with curl.

Arduino post request

To send data to a rails app you need an Anduino Ethernet Shield + Arduino or an Arduino Yun. In this example we are using an Anduino Ethernet Shield and we run on it this code:

#include <SPI.h>
#include <Ethernet.h>

// assign a MAC address for the ethernet controller.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
// fill in your address here:
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};

// fill in an available IP address on your network here,
// for manual configuration:
IPAddress ip(192,168,1,10);
// initialize the library instance:
EthernetClient client;

char server[] = "tantiauguriatutti.herokuapp.com";   

unsigned long lastConnectionTime = 0;       
boolean lastConnected = false;              
const unsigned long postingInterval = 60000;

float temperature;  
int reading;  
int lm35Pin = 5;
float referenceVoltage;

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);

  analogReference(INTERNAL);
  referenceVoltage = 1.1;

  // start the Ethernet connection:
  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    // DHCP failed, so use a fixed IP address:
    Ethernet.begin(mac, ip);
  }
}

void loop() {
  // if there's incoming data from the net connection.
  // send it out the serial port.  This is for debugging
  // purposes only:
  if (client.available()) {
    char c = client.read();
    Serial.print(c);
  }

  // if there's no net connection, but there was one last time
  // through the loop, then stop the client:
  if (!client.connected() && lastConnected) {
    Serial.println();
    Serial.println("disconnecting.");
    client.stop();
  }

  // if you're not connected, and ten seconds have passed since
  // your last connection, then connect again and send data:
  if(!client.connected() && (millis() - lastConnectionTime > postingInterval)) {
    reading = analogRead(lm35Pin);
    temperature = (referenceVoltage * reading) / 1023;
    sendData(temperature);
  }
  // store the state of the connection for next time through
  // the loop:
  lastConnected = client.connected();
}

// this method makes a HTTP connection to the server:
void sendData(int thisData) {
  // if there's a successful connection:
  String JsonData = "{"temperature": {"value": ";
  JsonData = JsonData + thisData;
  JsonData = JsonData + "}}";
  if (client.connect(server, 80)) {
    Serial.println("connecting...");
    // send the HTTP PUT request:
    client.println("POST /api/temperatures HTTP/1.1");
    client.println("Host: tantiauguriatutti.herokuapp.com");
    client.println("User-Agent: Arduino/1.0");
    client.println("Accept: application/json");
    client.print("Content-Length: ");
    client.println(JsonData.length());

    // last pieces of the HTTP PUT request:
    client.println("Content-Type: application/json");
    client.println("Connection: close");
    client.println();
    client.println(JsonData);
  } 
  else {
    // if you couldn't make a connection:
    Serial.println("connection failed");
    Serial.println();
    Serial.println("disconnecting.");
    client.stop();
  }
   // note the time that the connection was made or attempted:
  lastConnectionTime = millis();
}

The example above shows how to manage ethernet configuration, transform reading from analog input sensor and how to send JSON data to our Rails app.

Keep in mind it is possible to use another board to collect and send data, for example Spark Core and Electric Imp are perfect beacause already integrated with WiFi module.

Using spring with pow!

The other day I was a bit sad for I was aware that pow (the web server) wasn’t leveraging spring (the Rails preloader) with its fast load times.

Fixing config.ru

Luckily this deficiency is easily fixed by adding the spring snippet to your config.ru:

# This file is used by Rack-based servers to start the application.

begin
  load File.expand_path('../bin/spring', __FILE__)
rescue LoadError
end

require ::File.expand_path('../config/environment',  __FILE__)
run Rails.application