mic-css / 3scale-tech-test

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

3scale coding exercise

Description

The purpose of this coding exercise was to implement a currency selector for a basic product list that would fetch exchange rate information from a third-party API and dynamically convert all of the product prices.

Instructions

Documentation

Generate documentation with ‘rdoc` and open the `index.html` file (automatically opens in a new browser tab)

$ rdoc doc:app
$ open doc/app/index.html

Build

Install and update dependencies by running ‘bundle`

$ bundle

Seed the database with sample data using ‘rake`

$ rake db:seed

Run

Start the Rails server and visit ‘localhost:3000` in your browser

$ rails s

Test

Important: due to an issue with the ‘WebMock` gem, running all tests at once causes one of the tests to fail due to an unstubbed HTTP request.

Run all unit/integration tests with the following command (the ‘~` is an exclusion command):

$ rspec --tag ~type:feature

Optionally, run feature tests (this will automatically open and close a new browser window):

$ rspec --tag type:feature

Approach

Design

In order to dynamically update prices from the view, the application uses a jQuery event listener for a change in the selector. This triggers an AJAX request to the custom ‘products/prices` route, which renders a JSON object containing the selected currency and a `prices` object with the list of product ids and prices. The AJAX request callback then updates the view by matching product ids.

The preferred method for this would have been to allow ‘ProductController#index` and `ProductController#show` to respond to both HTML and JSON requests, basically providing an API for retrieving product information, and use this to update the view, resulting in a much skinnier controller. However, I was unable to find a clean way to render all product information with prices updated per request (see Challenges below) so I opted to fetch only converted price information.

Currency conversion is handled by the ‘CurrencyConverter` class, that uses class methods to emulate singleton behaviour without restricting instantiation to avoid unexpected behaviour. The class uses memoization to store an instance of the exchange rate information and avoid redundant API calls every time the `self.convert` method is called.

I opted to explore this approach over hard-coding currency methods in the ‘Product` model to provide a more programmatic approach to currency conversion that, with some much-needed refinement, would be much easier to extend when changing the list of supported currencies.

Testing

Development of the application was test-driven using RSpec, starting from unit tests. A feature test validating the view changing upon currency selection was added along the way as I deemed this to be an important part of the testing pyramid for this exercise; it could, however, be debated that the feature tests are slow and cumbersome and might be unnecessary in a different testing strategy.

Challenges

CurrencyConverter: DI and Singleton Behaviour

Initially, I intended to inject an instance of the ‘CurrencyConverter` into the `Product` objects to remove the coupling between the two classes, as well as only use one instance of the converter across all products without forcing singleton behaviour. I was, however, unable to find a way to efficiently do this in Rails; to limit the effect of this hard dependency, all calls within to the converter in other classes have been wrapped in private query methods to isolate the dependency and make changing its implementation easier to maintain.

This change in design meant that the converter had to implement some kind of singleton behaviour to avoid redundant API calls for each product. I considered both implementing it as a pure Singleton or as a module that could be mixed in; both, however, proved to be cumbersome, difficult to test and with unexpected behaviour. As such, I decided to implement singleton-like behaviour by providing a class-level public interface.

Returning JSON

As mentioned in the Design section, I was unable to find a way to provide a JSON API while updating the prices based on the AJAX query parameters; for example, overriding the built-in ‘as_json` or `to_json`, while also discouraged, could only be done at a class level and not at controller level, where the currency parameters would be available.

Persisting State

With more time I would look into improved persistence of the selected currency across requests (perhaps through a cache or browser cookies): currently, navigating to the product detail page resets the currency to the default, and using the browser’s back button to navigate back to the product list page resets the prices to the default but does not reset the selector.

RDoc

I had to spend some time researching RDoc conventions as I had never used it before and was not accustomed to documenting Ruby code (“the code should speak for itself”, and so on..). In future I would write the documentation as I developed the application.

Improvements

  • Rake tasks for documentation, testing, etc.

  • Improved error handling, especially for end-users

  • Currency state persistence across multiple requests

  • Improve JSON implementation with a better understanding of Rails and performance issues

About


Languages

Language:Ruby 83.7%Language:HTML 11.2%Language:JavaScript 2.6%Language:CSS 2.5%