How I Made Etymologizer, Part 2

In a previous post I explained how I made the Rails API for my Etymologizer app. Now for the next part – the AngularJS front-end component that interacts with the Rails API, gets the correct type of data, and displays it to the user.

For example, the user might type in We hold these truths to be self-evident: that all men are created equal:

etymologizer-user-input

Here is the code that takes the input:

<form ng-submit="getEtymologies()">
    <textarea ng-model="inputText" ng-model-options="{ updateOn: 'submit' }" class="form-control" rows="3" cols="50" id="textArea" placeholder="Enter text here"></textarea>
    <button class="btn btn-primary">
    Show etymology
    </button>
</form>

When the user submits the form, two things happen: the “getEtymologies” function gets called, and the data model updates. (The “ng-model-options” directive means that instead of updating immediately upon the user’s typing some text, it waits until the event specified by “updateOn” occurs – here, the submission of the form.)

The “getEtymologies” function is contained in the controller. (Here’s the whole function.) This function interacts with the Rails API, but before that, it does a few things:

  • creates an empty array to contain the data of all words;
  • calls a “sanitize” function that removes any punctuation, non-letters, line breaks and extra spaces via the JavaScript replace method;
  • combines the words into a space-separated string, which will be the params that get passed to the Rails API;
  • sets “loading” to true, which triggers the display of a spinner on the page while the user waits for the job to complete (via the “ng-show” directive here).

After these preliminaries, it uses $http.get to send the params to the Rails API, with resourceURL previously assigned as the API’s URL:

    $http.get($scope.resourceURL, {
      params: { words: $scope.wordParams }
    })

The response it gets back contains a JSON object for each word typed by the user. The function then iterates through each object, calling three functions each time, but only if the response contains relevant data. The conditionality is expressed with the ternary operator:

this.entry_list.entry ? etymology = etymologyService.findEtymology(this) : etymology = null;

etymology ? etymology = etymologyService.getValidHTML(etymology) : etymology = null;

etymology ? language = originLanguageService.getLanguage(etymology) : language = null;

The functions are contained in a separate services.js file. The findEtymology function digs through the data to find the etymology; the getValidHTML function adds appropriate HTML for displaying on the page; and the getLanguage function determines which language the word comes from by looking for the appropriate keyword in the etymology. (It assigns the newest language first; for example, if a word comes from Latin via Greek, it will be classified as Latin, which is newer than Greek; if a word comes from Anglo-French via Latin, it will be classified as Anglo-French, which is newer than Latin.)

Once it’s found all the appropriate data, the function creates a hash entry for each word, which will be added to the array of etymologies. Finally, now that the data is ready for display, it sets “loading” to false, so the spinner disappears and makes way for the color-coded etymology results.

Back in home.html, the AngularJS code displays the appropriate color for each word. The color classes are defined in the CSS, and ng-class=”entry.language” assigns the correct class for each word’s color.

The final chunk of code displays the detailed etymology of a word when the user mouses over the word.

I enjoyed creating this app, and perhaps eventually I’ll add more features!

How I Made Etymologizer, Part 1

I recently created a web app called Etymologizer. The site allows a user to enter a word or series of words. It then color-codes the words by language of origin and displays the etymology when the user mouses over a word.

Here’s an example of how it looks when you type in We hold these truths to be self-evident: that all men are created equal, and then mouse over the word self:

Screen Shot 2016-04-28 at 12.53.22 PM

I created the back end of this project with Rails, and the front end with AngularJS. In this post, I’ll describe how the Rails back end works, and why I decided to create it in Rails.

I wanted to create the project entirely in Angular, but the etymology data comes from Merriam-Webster’s Dictionary API. Unfortunately, the Dictionary API outputs its data in XML, and Angular needs the data to be JSON. At first I considered using this Angular-XML module, but then I decided it would just be easier (and more fun) to create my own Rails API that could act as a go-between: it could transform the Dictionary API’s data from XML to JSON, and then my Angular app could access that JSON from my own Rails API.

So I created a new Rails project, and the first thing I did was install the rails-api gem. (I used Rails 4; Rails 5 actually lets you create APIs without any additional gems.) I also installed the HTTParty gem, which I could use to interact with the Dictionary API.

I then created a new adapter that I could use elsewhere:

module Adapters
  class DictionaryApiConnection
    include HTTParty

    attr_reader :connection

    def initialize
      @connection = self.class
    end

    def base_URL
      "http://www.dictionaryapi.com/api/v1/references/collegiate/xml"
    end

    def URL(word)
      "#{self.base_URL}/#{word}?key=#{ENV['API_key']}"
    end

    def query(word)
      result = connection.get(self.URL(word))
    end

  end
end

In the adapter, I include the HTTParty gem, initialize the adapter with a connection instance variable, and then define my query. The query URL includes the Dictionary API’s base URL, the desired word, and my API key (which is defined elsewhere in my app and is hidden from the public using the figaro gem).

Next, I access the adapter from my controller:

class WordsController < ApplicationController

  def index
    client = Adapters::DictionaryApiConnection.new

    words = params["words"].split
    json_object = []

    words.each do |word|
      json_word = Crack::XML.parse(client.query(word))
      json_object.push(json_word)
    end

    render json: json_object

  end

end

Here’s how this works. The controller creates a new instance of the adapter, which includes a new connection. The API takes in params from the input that the user has typed into the front-end Angular app. The words variable takes the “words” params and splits it into an array of separate words. Then, for each word, I use the crack gem, a terrific little gem that converts XML to JSON very easily with the format Crack::XML.parse([something]). The something in this case is client.query(word), which goes to the adapter, creates a query URL for a word, gets back the XML, and converts it to JSON.

So json_word is the JSON output for a word, and json_object is an array of all the JSON for all the words the user typed in.

The Angular app accesses this JSON, and then it does its magic… which I’ll explain in another post!