Community Post

Build a Cryptocurrency Comparison Site using Ruby on Rails and TDD

We are going to build a web application visualizing the value of three currencies: BitCoin (BTC), Ethereum (ETH) and Nasdaq. We are going to do it by first writing tests, and then the implementation part, or as it is famously known, Test Driven Development (TDD).

We will use Rails as our framework of choice, and HighCharts JavaScript library for the graphs.

Before we begin, you are supposed to have your Rails development environment already set up. If you have already set up, then you are good to go.

Table of Contents

    Let’s start by first creating a new Rails project. I am going to create this project using suspenders gem. It is the base Rails application used at thoughtbot and comes with many gems that we will be using and also best-practice configuration options. You can create your project the usual way and will most likely have no issues, but would need to do extra changes that you may find in the GitHub repository of this project.

    First, let’s install this gem:

    gem install suspenders

    After you have done that, go to any directory that you want to have your project and type suspenders and the name of the project. I am using the name cryptocurrenciesgraph:

    suspenders cryptocurrencies_graph

    Now change the directory to the name of the project that you just created, and install all the gems that come with suspenders:

    cd cryptocurrencies_graph
    bundle install

    Let’s run the server and see our first view:

    rails s

    If we visit http://localhost:3000/, we will have an error telling that there isn’t any database created yet. We need to create the database and run the migrations:

    bundle exec rake db:migrate

    If we try to open up our application now, we will have a pleasant greeting:

    We will use Git to track our changes so that we also have the ability to review and go back if things go wrong. Let’s commit the files that suspenders generator has created:

    git add .
    git commit -m “Initialize repository”

    Let’s move on. We have a lot of work to do.

    I will be using branches for my changes. It is a good practice even if you are the only one working on a project.

    Let’s create a new branch called graphs-setup:

    git checkout -b graphs-setup

    Now we will start to write our first feature test. Run the following command:

    touch spec/features/user_visits_homepage_spec.rb

    Go to this new file that we just created and add the following:

    #spec/features/user_visits_homepage_spec.rb
    require "rails_helper"
    
    feature "User views homepage" do 
      scenario "successfully" do
        visit root_path
        expect(page).to have_headline_text 
      end 
    
      private
        def have_headline_text
          have_text ("Values of CryptoCurrencies")
        end
    end
    

    This spec will fail because we have not defined a root_path yet:

    rspec spec/features/user_views_homepage_spec.rb

    Let’s implement this. Create a new file for our controller:

    touch app/controllers/currencies_controller.rb

    Add a minimalistic controller there:

    #app/controllers/currencies_controller.rb
    class CurrenciesController < ApplicationController
      def show
      end
    end

    We need to add root_path at config/routes.rb:

    root "currencies#show", id: "bitcoin"

    We now need to add a new view file:

    mkdir app/views/currencies && touch app/views/currencies/show.html.erb

    We should go to index.html.erb file that we just created and add the text we expect to have mentioned at spec:

    <h1>Values of CryptoCurrencies</h1>

    If we run this spec now, we will see that our first spec has just passed:

    rspec spec/features/user_views_homepage_spec.rb

    Let’s commit the changes:

    git add .
    git commit -m “User views homepage”

    Now, we need to add three buttons that will be used to trigger the display of graphs. Let’s write the spec first:

    touch spec/features/user_views_graph_page_spec.rb

    We will test whether we have hyperlinks, so we will add the following at

    #spec/features/user_views_graph_page_spec.rb
    require "rails_helper"
    
    feature "User views graph links" do 
      scenario "successfully" do
        visit root_path
        expect(page).to have_link("Bitcoin")
        expect(page).to have_link("Ethereum")
        expect(page).to have_link("Nasdaq")
      end  
    end 

    Add this line at config/routes.rb:

    resources :currencies, only: :show

    If we run this spec, we will have an error:

    We need to go to app/views/graphs/show.html.erb and add the following:

    #app/views/graphs/show.html.erb
    <a class=”btn btn-primary” href=<%= currency_path(‘Bitcoin’) %> role=”button”>Bitcoin</a>
    <a class=”btn btn-primary” href=<%=currency_path(‘Etherum’) %> role=”button”>Etherum</a>
    <a class=”btn btn-primary” href=<%=currency_path(‘Nasdaq’) %> role=”button”>Nasdaq</a>

    If we run the above spec again, it will pass successfully.

    Let’s test something else in the same page: We want to display the name of the currency whose graph is being currently displayed, which redirects to a Wikipedia page for each currency. We want to make sure that every link is correctly put. Let’s add the spec below:

    #spec/features/user_visits_graph_page_spec.rb
    
    feature “User views Wikipedia links of” do
    
    scenario “Bitcoin” do
      visit currency_path(“Bitcoin”)
      expect(page).to have_link(“Bitcoin”, :href => “https://en.wikipedia.org/wiki/Bitcoin")
    end
    scenario “Ethereum” do
     visit currency_path(“Ethereum”)
     expect(page).to have_link(“Ethereum”, :href => “https://en.wikipedia.org/wiki/Ethereum")
    end
    scenario “Nasdaq” do
      visit currency_path(“Nasdaq”)
      expect(page).to have_link(“Nasdaq”, :href =>       “https://en.wikipedia.org/wiki/Nasdaq")
      end
    end

    Of course, these specs will fail, as we have not put our links yet. Let’s go and do that:

    <%= link_to_if(params[:id], “Graph of #{params[:id]}”, “https://en.wikipedia.org/wiki/#{params[:id]}") %>

    If we run our specs again, we will see the green color:

    Awesome. Just one thing though: We have repeated ourselves several times at the above specs. We should not repeat the same code several times, so let’s do some refactorings:

    #spec/features/user_views_graph_spec.rb
    require "rails_helper"
    
    feature "User views graph links" do 
      scenario "successfully" do
        visit root_path
        expect(page).to have_link("Bitcoin")
        expect(page).to have_link("Ethereum")
        expect(page).to have_link("Nasdaq")
      end  
    end  
    
    feature "User views Wikipedia page of" do 
      scenario "Bitcoin" do
        expect_page_to_have_link("Bitcoin")
      end
    
      scenario "Ethereum" do
        expect_page_to_have_link("Ethereum")
      end
    
      scenario "Nasdaq" do
        expect_page_to_have_link("Nasdaq")
      end  
    end  
    
    private
    
      def expect_page_to_have_link(currency)
        visit currency_path("#{currency}")
        expect(page).to have_link("#{currency}", :href => "https://en.wikipedia.org/wiki/#{currency}") 
      end
    

    If we run our specs again, they will pass.

    Let’s commit our changes that we have made so far:

    git add .
    git commit -m “Add user views graphs specs”

    We want to make our page look a bit prettier. Let’s add some bootstrap. Let’s add the following line at app/views/layouts/application.html.erb

    <link rel=”stylesheet” href=”https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css">

    I like the green color. That’s why I am changing the class of buttons. Then let’s also wrap our buttons around the div below:

    #app/views/layouts/application.html.erb
    <div class="btn-toolbar" style="display: flex; margin-top: 15px;"> 
     <div style="margin: auto;"> 
      <a class="btn btn-success" href=<%= currency_path('Bitcoin') %> role="button">Bitcoin</a>
      <a class="btn btn-success" href=<%= currency_path('Ethereum') %> role="button">Ethereum</a>
      <a class="btn btn-success" href=<%= currency_path('Nasdaq') %> role="button">Nasdaq</a>
     </div>
    </div>

    Let’s use a bootstrap for the title too:

    #app/views/layouts/application.html.erb
    <div class=”page-header”>
      <h1>Values of CryptoCurrencies</h1>
    </div>

    We will edit a little text as well:

    #app/views/currencies/show.html.erb
    <% if params[:id] %>
    <h3> Graph of <%= link_to(“#{params[:id]}”, “https://en.wikipedia.org/wiki/#{params[:id]}") %> </h3>
    <% else %>
    <h3> Choose a currency please: </h3>
    <% end %>

    Let’s put our whole page in a beautiful bootstrap container.

    #app/views/layouts/application.html.erb
    <body>
    <div class=”container”>
    <%= yield %>
    </div>
    </body>

    Now we can commit our changes.

    git add .
    git commit -m “Add styles to graphs page”

    We may consider what we have done already as part of a git branch. We need to create a pull request for these changes to be merged into the master branch, and create a new one for the implementation of the remaining parts of our application.

    I will be using GitHub, but you are free to choose BitBucket or GitLab as well.

    We are first going to create a new GitHub repository and do a Pull Request on GitHub. Go to this link and create a new repository. After you have done that, you will see a similar screen as below:

    We now need to reference this remote repository with the one that we have in our local machine. To do that, we simply need to follow the tips that GitHub suggests for us. In my particular case:

    git remote add origin https://github.com/fatosmorina/cryptocurrencies_graphs.git
    git push -u origin master

    After we have done that, we need to push our local branch. In my case, my current branch is graphs-setup:

    git push origin graphs-setup

    Now, we are going to create a new Pull Request, by going at Pull Request tab of the repository and hitting New pull request. We can then choose our local branch:

    We can see on overview of the changes that we have committed to this branch. We are sure that everything is already, as we have done test-driven-development, so we can hit Create pull request. We will be asked to write a optional description for the changes that we want to be merged in the master branch. After writing a short description, we are good to move further. We can then Merge pull request by going at Pull requests tab at the repository and confirming this merge.

    Let’s create a new local git branch and continue our work in there. I am going to call this new branch api-integration, as we will need to interact with external APIs to get the value of the crypto currencies:

    git checkout -b api-integration

    We need to use a gem to help us interact with REST APIs. We can either HTTParty or RestClient among many others. I find it easier to use RestClient, because it is simpler to mock its response that we will need for our tests, but you can choose any that you want.

    If you want to use HTTParty, add this line to the Gemfile:

    gem 'httparty'

    If you want to continue with my choice, then you need to add this:

    gem 'rest-client'

    In either case, we need to install this new gem:

    bundle install

    Let’s create a new config file that we will use for the API URLs.

    Create load_api_config.rb under config/initializers and also api_config.yml under config directory:

    touch config/initializers/load_api_config.rb && touch config/api_config.yml

    Add the following configurations:

    #config/initializers/load_api_config.rb
    CONFIG_PATH=”#{Rails.root}/config/api_config.yml”
    APP_CONFIG = YAML.load_file(CONFIG_PATH)[Rails.env]

    Now go to config/api_config.yml and paste the following lines:

    #config/api_config.yml
    roduction:
      bitcoin_api_url: "https://min-api.cryptocompare.com/data/histoday?fsym=BTC&tsym=USD&limit=60&aggregate=1&e=CCCAGG"
      ethereum_api_url: "https://min-api.cryptocompare.com/data/histoday?fsym=ETH&tsym=USD&limit=60&aggregate=1&e=CCCAGG"
      nasdaq_api_url: "https://fred.stlouisfed.org/graph/graph-data.php?recession_bars=on&mode=fred&id=NASDAQCOM&fq=Daily&fam=avg&transformation=lin&nd=1971-02-05&ost=-99999&oet=99999&lsv=&lev=&fml=a&fgst=lin&fgsnd=2009-06-01&mma=0&scale=left&line_color=%234572a7&line_style=solid&vintage_date=&revision_date=&chart_type=line&stacking=&drp=0&cosd=&coed=&log_scales="
    
      development:
        bitcoin_api_url: "https://min-api.cryptocompare.com/data/histoday?fsym=BTC&tsym=USD&limit=60&aggregate=1&e=CCCAGG"
        ethereum_api_url: "https://min-api.cryptocompare.com/data/histoday?fsym=ETH&tsym=USD&limit=60&aggregate=1&e=CCCAGG"
        nasdaq_api_url: "https://fred.stlouisfed.org/graph/graph-data.php?recession_bars=on&mode=fred&id=NASDAQCOM&fq=Daily&fam=avg&transformation=lin&nd=1971-02-05&ost=-99999&oet=99999&lsv=&lev=&fml=a&fgst=lin&fgsnd=2009-06-01&mma=0&scale=left&line_color=%234572a7&line_style=solid&vintage_date=&revision_date=&chart_type=line&stacking=&drp=0&cosd=&coed=&log_scales="
    
        test:
          bitcoin_api_url: "https://min-api.cryptocompare.com/data/histoday?fsym=BTC&tsym=USD&limit=60&aggregate=1&e=CCCAGG"
          ethereum_api_url: "https://min-api.cryptocompare.com/data/histoday?fsym=ETH&tsym=USD&limit=60&aggregate=1&e=CCCAGG"
          nasdaq_api_url: "https://fred.stlouisfed.org/graph/graph-data.php?recession_bars=on&mode=fred&id=NASDAQCOM&fq=Daily&fam=avg&transformation=lin&nd=1971-02-05&ost=-99999&oet=99999&lsv=&lev=&fml=a&fgst=lin&fgsnd=2009-06-01&mma=0&scale=left&line_color=%234572a7&line_style=solid&vintage_date=&revision_date=&chart_type=line&stacking=&drp=0&cosd=&coed=&log_scales="
    

    It is a good practice to add these config files that we just created under .gitignore:

    config/initializers/load_api_config.rb
    config/api_config.yml

    Let’s commit the changes that we have just made:

    git add .
    git commit -m “Add HTTParty and API config files”

    It is a good practice to not rely on third party services, as they may slow up our application. They may respond slowly, or may even not respond at all sometimes, so we want to make sure we are using background jobs that automatically run and deal with these services and save their values in the database.

    Let’s create a new model called Currency. First, let’s write the tests:

    rails generate model Currency currency_type:integer value:float date:date

    Now we need to create a new database table for the model that we just created:

    rails db:migrate

    Let’s write our specs at spec/models/currencyspec.rb:

    #spec/models/currency_spec.rb
    require 'rails_helper'
    
    RSpec.describe Currency, type: :model do
      it { should validate_presence_of(:date) }
      it { should validate_presence_of(:currency_type) }

    If we run this, we will have these specs failing:

    Let’s write the validations at app/models/currency.rb:

    #app/models/currency.rb
    validates :date, presence: true
    validates :currency_type, presence: true

    Now we want to do a validation for the value that the attribute type gets. We want to have only the type: bitcoin, nasdaq and ethereum, so let’s write a spec first and then implement this validation:

    #spec/models/currency_spec.rb
      it 'should throw error for invalid value of currency_type' do
        expect { build(:currency, currency_type: "invalid") }.to raise_error(ArgumentError).with_message(/is not a valid currency_type/)
      end

    In the model we add:

    app/models/currency.rb

      enum currency_type: [:bitcoin, :ethereum, :nasdaq]

    Let’s commit our changes:

    git add .
    git commit -m “Add Currency model”

    Now we need to add a new helper that we will use to interact with the external APIs from which we will get the values of our chosen crypto currencies:

    mkdir lib/currencies && touch lib/currencies/currency_client.rb

    We will use three methods one for each currency, which will then call from the rake task that we will execute daily.

    Let’s start writing the specs first. We do not want to make real API requests while doing our tests, so we can stub some responses. These are the private methods that we can use for this purpose:

    #spec/lib/currencies/currencies_client_spec.rb
    private
      def get_successful_bitcoin_response
        {
          "Data": [
            {
              "time": 1497744000,
              "close": 2560.21,
              "high": 2676.04,
              "low": 2570.81,
              "open": 2655.1,
              "volumefrom": 27323.02,
              "volumeto": 71535198.81
            }
          ]
        }.to_json
      end
      def get_successful_ethereum_response
        {
          "Data": [
            {
              "time": 1497916800,
              "close": 362.65,
              "high": 365.13,
              "low": 356.94,
              "open": 358.2,
              "volumefrom": 106370.4,
              "volumeto": 38429650.7
            }
          ]
        }.to_json
      end
      def get_successful_nasdaq_response
        {
          "seriess": [
            {
              "obs": [
                [
                  1497589200000,
                  6151.76
                ],
              ]
            }
          ]
        }.to_json
      end
    

    They are simple excerpts from the actual responses from the APIs that we have defined at config/initializers/apiconfig.rb: We need to do these imports before we move on:

    #config/initializers/api_config.rb
    require “rails_helper”
    require “#{Rails.root}/lib/currencies/currency_client.rb”

    Let’s start writing the spec for Bitcoins:

    #spec/lib/currencies/currencies_client_spec.rb
    it ‘returns Bitcoins successfully’ do
      allow(RestClient).to  receive(:get).and_return(get_successful_bitcoin_response) #we are  stubbing the result that would otherwise be returned from the API
      actual_response = CurrencyClient.get_bitcoins #we are getting the  response from our yet-to-be implemented implementation
      expect(actual_response.keys[0]).to eq(“Sun, 18 Jun 2017 00:00:00 +0000”) #we are waiting to get these values equal based on the  stubbed date for bitcoin above
      expect(actual_response.values[0]).to eq(2560.21) #we are waiting to  get these values equal based on the stubbed bitcoin value above
    end
    

    If we run our spec, it will fail. Let’s implement this method:

    #lib/currencies/currency_client_spec.rb
    module CurrencyClient
      def self.get_bitcoins(api_url)
        response = RestClient.get(APP_CONFIG.fetch(‘bitcoin_api_url’))#This  is another best practice that we should follow when getting  variables, as can be seen here  https://github.com/thoughtbot/guides/tree/master/best-practices
        json_response = JSON.parse(response)
        json_response["Data"].inject({}) do |new_element, current_element|
          key = DateTime.strptime(current_element["time"].to_s, "%s")
          value = current_element["close"]
          new_element[key] = val
          new_element
        end
      end
    

    Our spec passes now.

    We can continue similarly with the spec and implementation for Ethereums:

    #spec/lib/currencies/currencies_client_spec.rb
    it ‘returns Ethereums successfully’ do
      allow(RestClient).to  receive(:get).and_return(get_successful_ethereum_response)
      actual_response = CurrencyClient.get_ethereums
      expect(actual_response.keys[0]).to eq(“Tue, 20 Jun 2017 00:00:00 +0000”)
      expect(actual_response.values[0]).to eq(362.65)
    end
    def self.get_ethereums
      response = RestClient.get(APP_CONFIG.fetch(‘ethereum_api_url’))
      json_response = JSON.parse(response)
      json_response[‘Data’].inject({}) do |new_element, current_element|
        key = DateTime.strptime(current_element[‘time’].to_s, ‘%s’)
        value = current_element[‘close’]
        new_element[key] = val
        new_element
      end
    end
    

    Last, but not least, we implement the Nasdaq:

    #spec/lib/currencies/currencies_client_spec.rb
    it ‘returns Nasdaq values successfully’ do
      allow(RestClient).to   receive(:get).and_return(get_successful_nasdaq_response)
      actual_response = CurrencyClient.get_nasdaqs
      expect(actual_response.keys[0]).to eq(“Fri, 16 Jun 2017 05:00:00  +0000”)
      expect(actual_response.values[0]).to eq(6151.76)
    end
    def self.get_nasdaqs
      response = RestClient.get(APP_CONFIG.fetch("nasdaq_api_url"))
      json_response = JSON.parse(response)
      values = json_response[‘seriess’].first[‘obs’]
      values.inject({}) do |new_element, current_element|
        key = DateTime.strptime((current_element[0] / 1000).to_s, "%s")
        value = current_element[1]
        new_element[key] = value
        new_element
      end
    

    We are done implementing these methods, but if we look back, we notice many repetitions of the same things, in both specs and in the actual implementations. We do not want to repeat ourselves. Let’s do some refactorings in our specs first.

    We see that in every spec, we do a stub for a HTTP Get Request and stub the response, so we can start with that:

    #spec/lib/currencies/currencies_client_spec.rb
    allow(RestClient).to receive(:get).and_return(send(“get_successful_#{currency}_response”))

    We can get the value for currency dynamically via a method argument. Here we are using the powerful Metaprogramming that Ruby offers: We are calling a method name based on the argument that we have put. As the chief designer of Ruby, Yukihiro Matsumoto mentioned at RubyConf 2001, “Ruby is dynamic, like human nature.” (Unfortunately I was only 8 years old and could not attend this event, but read this quote from the book “The well grounded Rubyist”. )

    Let’s continue with our refactoring.

    We are getting a stubbed API response, so we need to add that as well:

    response = CurrencyClient.send(“get_#{currency}s”)

    Now comes the comparison part. We want to compare the values based on the method arguments that we get:

    expect(response.keys[0]).to eq(date)
    expect(response.values[0]).to eq(value)

    That’s all we need in our general method that we can call for each spec. Here it is how it looks like:

    #spec/lib/currencies/currencies_client_spec.rb
        def expect_successful_response(currency, date, value)
          allow(RestClient).to receive(:get).and_return(send("get_successful_#{currency}_response"))
          response = CurrencyClient.send("get_#{currency}s")
          expect(response.keys[0]).to eq(date)
          expect(response.values[0]).to eq(value)
        end

    This is how our spec is simplified:

    #spec/lib/currencies/currencies_client_spec.rb
    require "rails_helper"
    require "#{Rails.root}/lib/currencies/currency_client.rb"
    
    describe CurrencyClient do
    
      context "when communicating with external services" do
        it 'returns Bitcoins successfully' do
          expect_successful_response('bitcoin', "Sun, 18 Jun 2017 00:00:00 +0000", 2560.21)
        end
    
        it 'returns Ethereums successfully' do
          expect_successful_response('ethereum', "Tue, 20 Jun 2017 00:00:00 +0000", 362.65)
        end
    
        it 'returns Nasdaq values successfully' do
          expect_successful_response('nasdaq', "Fri, 16 Jun 2017 05:00:00 +0000", 6151.76)
        end
      end
    
      private
    
        def expect_successful_response(currency, date, value)
          allow(RestClient).to receive(:get).and_return(send("get_successful_#{currency}_response"))
          response = CurrencyClient.send("get_#{currency}s")
          expect(response.keys[0]).to eq(date)
          expect(response.values[0]).to eq(value)
        end
    
        def get_successful_bitcoin_response
          {
            "Data": [
              {
                "time": 1497744000,
                "close": 2560.21,
                "high": 2676.04,
                "low": 2570.81,
                "open": 2655.1,
                "volumefrom": 27323.02,
                "volumeto": 71535198.81
              }
            ]
          }.to_json
        end
    
        def get_successful_ethereum_response
          {
            "Data": [
              {
                "time": 1497916800,
                "close": 362.65,
                "high": 365.13,
                "low": 356.94,
                "open": 358.2,
                "volumefrom": 106370.4,
                "volumeto": 38429650.7
              }
            ]
          }.to_json
        end
    
        def get_successful_nasdaq_response
          {
            "seriess": [
              {
                "obs": [
                  [
                    1497589200000,
                    6151.76
                  ],
                ]
              }
            ]
          }.to_json
        end
    
    end
    

    Beautiful, isn’t it?

    If we run our specs again, they still pass:

    Now comes the refactoring of CurrencyClient module.

    We see that for both bitcoins and ethereums, we have the same structure of API response, so we can simply write:

    #lib/currencies/currency_client.rb
      def self.get_bitcoins
        get_currencies(APP_CONFIG.fetch('bitcoin_api_url'))
      end
    
      def self.get_ethereums
        get_currencies(APP_CONFIG.fetch('ethereum_api_url'))
      end
    
    private
      def self.get_currencies(api_url)
        response =RestClient.get(api_url)
        json_response = JSON.parse(response)
        json_response[‘Data’].inject({}) do |new_element, current_element|
          key = DateTime.strptime(current_element[‘time’].to_s, ‘%s’)
          value = current_element[‘close’]
          new_element[key] = value
          new_element
        end

    We are repeating getting and the parsing process of the API response, so we can do add another private method:

    #lib/currencies/currency_client.rb
      def self.get_json_response(api_url)
        response = RestClient.get(api_url)
        json = JSON.parse(response)
        json
      end

    This is how our CurrencyClient looks like now:

    #lib/currencies/currency_client.rb
    module CurrencyClient
      def self.get_bitcoins
        get_currencies(APP_CONFIG.fetch('bitcoin_api_url'))
      end
    
      def self.get_ethereums
        get_currencies(APP_CONFIG.fetch('ethereum_api_url'))
      end
    
      def self.get_nasdaqs
        json = get_json_response(APP_CONFIG.fetch('nasdaq_api_url'))
        values = json['seriess'].first['obs']
        values.inject({}) do |new_element, current_element|
          key = DateTime.strptime((current_element[0] / 1000).to_s, '%s')
          value = current_element[1]
          new_element[key] = value
          new_element
        end
      end
    
    private
    
      def self.get_currencies(api_url)
        json_response = get_json_response(api_url)
        json_response['Data'].inject({}) do |new_element, current_element|
          key = DateTime.strptime(current_element['time'].to_s, '%s')
          value = current_element['close']
          new_element[key] = value
          new_element
        end
      end
    
      def self.get_json_response(api_url)
        response = RestClient.get(api_url)
        json = JSON.parse(response)
        json
      end
    end

    Let’s commit our changes so far.

    git add .
    git commit -m “Implement CurrencyClient”

    We are going to write a rake task that will seed the database with the latest data. It should be run once in the beginning when you set up the environment.

    We first start importing the CurrencyClient that we have implemented earlier. Then we will be interacting with the database and produce new instances of crypto currencies from the data that we get from the APIs that we will then insert in the database.

    We also want to capture any exception that may be thrown when trying to do the interaction with the database. I have also made a general method that is used for each currency, so that we do not need to repeat the same rows several times for each currency:

    #lib/tasks/dev.rake
    def insert_currencies(type, currencies)
      currencies.each do |date, value|
        ActiveRecord::Base.transaction do
          Currency.create(currency_type: type, date: date, value: value)
          print_currency(type.capitalize, date, value)
        end
      end   
    end

    Here is the whole implementation:

    #lib/tasks/dev.rake
    require "currencies/currency_client"
    
    namespace :dev do
      desc "Insert all crypto currencies in the database"
      task seed: "db:seed" do
        include CurrencyClient
    
        begin
          bitcoins = CurrencyClient.get_bitcoins
          ethereums = CurrencyClient.get_ethereums
          nasdaqs = CurrencyClient.get_nasdaqs
    
          insert_currencies('bitcoin', bitcoins)
          insert_currencies('ethereum', ethereums)
          insert_currencies('nasdaq', nasdaqs)
    
          puts "Congratulations! You have all crypto currencies values in your database now!"
    
        rescue ActiveRecord::StatementInvalid
          # ...which we ignore.
        end
      end
    end
    
    def insert_currencies(type, currencies)
      currencies.each do |date, value|
        ActiveRecord::Base.transaction do
          Currency.create(currency_type: type, date: date, value: value)
          print_currency(type.capitalize, date, value)
        end
      end   
    end
    
    def print_currency(currency, date, value)
      puts "#{currency}'s value on #{date}: #{value} USD"
    end
    

    We can invoke this by running:

    rake dev:seed

    If you have the same implementation as mine, you should see something similar in the console:

    These are only some print outs of successful insertions of new rows in the database.

    Now we will implement a background job that will run daily and insert the values for our cryptocurrencies in the database. Before we schedule it, let’s first write a rake task.

    Let’s create a new file:

    touch lib/tasks/currencies_warehouse.rake

    We need to import CurrencyClient:

    require “currencies/currency_client”

    Then we want to insert only the values that have not been added yet in the database. As a result, we will need to reverse the result from the API and only get the latest values that have been added recently in the API responses which we use through CurrencyClient. When we reach a currency that has been already inserted in the database before, we stop the insertion for that currency.

    Here is the above description converted in Ruby:

    #lib/tasks/currencies_warehouse.rake
    def insert_new_recent_rows(currencies, currency_type)
      Hash[currencies.sort_by{|k,v| k}.reverse].each do |currency|
        date = currency[0]
        value = currency[1]
        currency = Currency.where(date: date, currency_type: currency_type)
        break if currency.present?
        ActiveRecord::Base.transaction do
          currency = Currency.create(currency_type: currency_type, date: date, value: value)
          print_currency_inserterd(currency_type.capitalize, date, value)
        end
      end
    end

    We call this method for each currency:

    ['bitcoin', 'ethereum', 'nasdaq'].each do |currency|
      insert_new_recent_rows(CurrencyClient.send("get_#{currency}s"), "#{currency}")
    end

    Here is the whole implementation:

    #lib/tasks/currencies_warehouse.rake
    require "currencies/currency_client"
    
    namespace :currency do
      desc "Get daily values for crypto currencies"
      task seed: "db:seed" do
        include CurrencyClient
        begin
    
          ['bitcoin', 'ethereum', 'nasdaq'].each do |currency|
            insert_new_recent_rows(CurrencyClient.send("get_#{currency}s"), "#{currency}")
          end
    
        rescue ActiveRecord::StatementInvalid
          # ...which we ignore.
        end
      end
    end
    
    def insert_new_recent_rows(currencies, currency_type)
      Hash[currencies.sort_by{|k,v| k}.reverse].each do |currency|
        date = currency[0]
        value = currency[1]
        currency = Currency.where(date: date, currency_type: currency_type)
        break if currency.present?
        ActiveRecord::Base.transaction do
          currency = Currency.create(currency_type: currency_type, date: date, value: value)
          print_currency_inserterd(currency_type.capitalize, date, value)
        end
      end
    end
    
    def print_currency_inserterd(currency, date, value)
      puts "New row added: #{currency}'s value on #{date}: #{value} USD"
    end
    

    We might not be able to see any change if we run this rake task now, but we can delete a few lines from the database and see this rake task is doing the insertion successfully. Type:

    rails c

    Then type

    3.times do
     Currency.last.delete
    end

    You could also write

    Currency.last.delete

    If we execute this new task that we just added, then we will see it inserting new rows in the database:

    Pretty cool, yeah.

    Let’s commit our latest changes:

    git add .
    git commit -m “Add new rake task that inserts new rows in the database daily”

    Let’s schedule this task so that it gets executed daily.

    Add the following gems to your Gemfile:

    gem 'whenever', require: false

    Run:

    bundle

    Now type:

    wheneverize .

    We want to test our scheduled job first.

    Let’s add a new file that we will use for our specs:

    touch spec/lib/tasks/whenever_spec.rb

    Let’s also add a new file that we will be using to help us test the scheduler:

    touch spec/support/rspec_matchers.rb

    Add the following which is written by Matthew Gerrior:

    #spec/support/rspec_matchers.rb
    RSpec::Matchers.define :schedule_rake do |task|
      match do |whenever|
        jobs = whenever.instance_variable_get("@jobs")
        key = @duration.is_a?(ActiveSupport::Duration) ? @duration.to_i : @duration
    
        if jobs.has_key?(key)
          jobs[key].any? do |job|
            options = job.instance_variable_get("@options")
    
            options[:task] == task && (@time.present? ? job.at == @time : true)
          end
        else
          false
        end
      end
    
      chain :every do |duration|
        @duration = duration
      end
    
      chain :at do |time|
        @time = time
      end
    
      failure_message do |actual|
        "Expected whenever to schedule #{ task } every #{ @duration } seconds"
      end
    
      failure_message_when_negated do |actual|
        "Expected whenever not to schedule #{ task } every #{ @duration } seconds"
      end
    end
    
    RSpec::Matchers.alias_matcher :schedule_runner, :schedule_rake
    RSpec::Matchers.alias_matcher :schedule_command, :schedule_rake
    

    Our spec should look something as below:

    #spec/lib/tasks/whenever_spec.rb
    require 'rails_helper'
    require 'whenever'
    
    describe 'task' do
      it 'makes sure `runner` statements exist' do
        whenever = Whenever::JobList.new(file: Rails.root.join("config", "schedule.rb").to_s)
        expect(whenever).to schedule_rake("currency:seed")
          .every(:day)
          .at("1am")
      end
    end

    So we want to make sure that our task is executed every day at 1 am. If we run this spec, it will obviously fail:

    At config/schedule.rb file that has just been created add the following:

    #lib/tasks/whenever_spec.rb
    every :day, :at => '1am' do 
      rake "currency:seed"
    end

    That’s all what we need to do. Now when others are binge-watching Netflix series, our modest task tirelessly seeds the database with the latest data.

    If we run our spec again, it will pass.

    Now we can commit our changes:

    git add .
    git commit -m “Schedule currency:seed task”

    Now we will continue to add the gems that we will use to display the graphs in our browsers:

    gem ‘highcharts-rails’
    gem ‘chartkick’
    gem ‘groupdate’

    Add the following gem to development and test group:

    gem 'rails-controller-testing'

    We need to add the following lines at app/assets/javascript/application.js:

    #app/assets/javascript/application.js
    //= require highcharts
    //= require highcharts/highcharts-more
    //= require chartkick

    We will first write our spec for the controller:

    touch spec/controllers/currencies_controller_spec.rb

    Start adding the usual line:

    require ‘rails_helper’

    We will test whether the show page is rendered properly:

    #spec/controllers/currencies_controller_spec.rb
    def should_display_charts(currency)
      get :show, params: { id: currency }
      expect(response.status).to eq(200)
      expect(response).to render_template(:show)
      expect(response).to be_success
    end
    

    Now we can call this method for each currency:

    #spec/controllers/currencies_controller_spec.rb
      it 'should display the bitcoin charts' do
        should_display_charts('bitcoin')
      end
    
      it 'should display the ethereum charts' do
        should_display_charts('ethereum')
      end
    
      it 'should display the nasdaq charts' do
        should_display_charts('nasdaq')
      end

    In our controller, we will first make sure the id is a valid value. Then we will use it to prepare the hash response:

    #app/controllers/currencies_controller.rb
      def show
        @currencies = params[:id] ? params[:id].downcase : ''
        case @currencies
        when  !Currency.currency_types.include?(@currencies) 
          @currencies = {}
        else
          @currencies = set_currencies(@currencies)
        end
      end
    
    private
    
      def set_currencies(currency_type)
        @currencies = Currency.where(currency_type: Currency.currency_types["#{currency_type}"])
        @currencies.inject({}) do |new_element, current_element|
          date = current_element.date
          value = current_element.value
          new_element[date] = value
          new_element
        end
      end

    Now we simply need to go to views/currencies/show.html.erb and add the following line:

    <%= line_chart @currencies %>

    Make sure the following line

    <%= render “javascript” %>

    is rendered before this one:

    <%= yield %>

    at application.html.erb. Namely,

    #views/layouts/application.html.erb
      <body class="<%= body_class %> jumbotron vertical-center">
        <%= render "javascript" %>
        <div class="container">
          <%= yield %>
        </div>  
        <%= render "css_overrides" %>
      </body>

    You don’t need anything else really. If you have successfully installed all the gem files mentioned earlier and restarted your server, you should be able to see fancy graphs in your browser:

    Awesome, really awesome!

    Let’s commit our changes:

    git add .
    git commit -m “Implement CurrenciesController#show and display graphs”

    Let’s push our local branch:

    git push — set-upstream origin api-integration

    We can now go and create a new Pull Request, as we want to merge our changes on this branch with the master branch.

    We can now simply deploy our app to Heroku.

    Change the branch to the master branch:

    git checkout master

    Let’s prepare our project before we deploy it with this suspenders command:

    suspenders app --heroku true

    This may take a while until is finished.

    Then we can push our changes to heroku (I suppose you are already registered and have set up heroku command line in your local environment before — if you haven’t, go and follow this clear guide):

    git push heroku master

    This may also take a while.

    We then need to run:

    heroku run rails db:migrate

    Then we need to seed our database with the data from external APIs:

    heroku run rails dev:seed

    You will probably see something similar to the screenshot that I have taken below:

    Now we can rename the subdomain to a more human readable phrase:

    heroku apps:rename new_name

    Now you can simply run the following command which will open our deployed project in our browsers:

    heroku open

    This concludes this tutorial. I hope you find it helpful!

    Fatos Morina

    1 post

    I am a Passionate Software Engineer, currently serving as a Team Lead Developer for a group of enthusiastic developers that specialize in developing web and mobile applications, mostly using Ruby on Rails and React JS.

    I am currently looking for a remote Ruby on Rails job. Please contact me for new opportunities.