Skip to content
Shopify 2019 Backend Developer Challenge
Ruby
Branch: master
Clone or download
zpv Merge pull request #2 from zpv/dependabot/bundler/nokogiri-1.10.4
Bump nokogiri from 1.10.1 to 1.10.4
Latest commit fe3861c Oct 22, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
app Additional input validation Jan 21, 2019
bin Refactor Cart / Product to follow Rubocop suggestions Jan 20, 2019
config Rename docs + add GraphiQL to production Jan 21, 2019
db Polish schema, update tests, and add descriptions Jan 20, 2019
docs
lib
log Initial commit Jan 10, 2019
test Polish schema, update tests, and add descriptions Jan 20, 2019
tmp Initial commit Jan 10, 2019
.gitignore
.rubocop.yml Refactor Cart / Product to follow Rubocop suggestions Jan 20, 2019
.ruby-version Initial commit Jan 10, 2019
Gemfile
Gemfile.lock Bump nokogiri from 1.10.1 to 1.10.4 Oct 21, 2019
README.md Update README.md Jan 21, 2019
Rakefile Initial commit Jan 10, 2019
config.ru
package.json Initial commit Jan 10, 2019

README.md

Shop logo

Mini-Market

Shopify Backend Challenge 2019
Explore mini-market docs »
Demo it now »


Getting Started

To get the Rails server running locally:

  1. Clone the repository
  2. bundle install to install required dependencies
  3. rails db:migrate to build database migrations
  4. For mock data, use rails db:seed
  5. rails s to start the server
  6. Visit http://localhost:3000/ to access the demo

Tests

  1. cd into the project directory
  2. Run tests using rails test

Usage

GraphQL Endpoint

Unlike a RESTFul API, mini-market has a single endpoint:

POST https://mini-market.zhao.io/graphql

Using the demo interface, you can follow executing some commands below:

Queries

Viewing Products

Example Request:

{
  products(onlyAvailable: true) {
    id
    title
    price
    inventoryCount
  }
}

Response:

{
  "data": {
    "products": [
      {
        "id": "1",
        "title": "Iron Helmet",
        "price": 79.99,
        "inventoryCount": 25
      },
      {
        "id": "2",
        "title": "Hylian Shield",
        "price": 1333.37,
        "inventoryCount": 1
      },
      {
        "id": "3",
        "title": "Leather Boots",
        "price": 16.99,
        "inventoryCount": 10
      }
    ]
  }
}
Argument Type Default Description
onlyAvailable Boolean false Show only products with available inventory
Current Cart

Example Request:

{
  cart {
    cartItems {
      product{
        title
      }
      price
      quantity
    }
  }
}

Response:

{
  "data": {
    "cart": {
      "cartItems": [
        {
          "product": {
            "title": "Iron Helmet"
          },
          "price": 79.99,
          "quantity": 3
        }
      ]
    }
  }
}

Mutations

Adding to Cart

Similarly to how Shopify handles mutation errors, you should always be including userErrors in your mutation queries. Otherwise, if an error occurs, it's likely you won't see it without it.

Example Request:

mutation {
  addToCart(productId: 1, quantity: 3) {
    cart{
      cartItems {
        product {
          title
        }
        price
        quantity
      }
      subtotal
    }
    userErrors {
      message
      path
    }
  }
}

Response:

{
  "data": {
    "addToCart": {
      "cart": {
        "cartItems": [
          {
            "product": {
              "title": "Iron Helmet"
            },
            "price": 79.99,
            "quantity": 3
          }
        ],
        "subtotal": 239.97
      },
      "userErrors": []
    }
  }
}
Argument Type Default Description
productId Integer Product ID of the item to be added
quantity Integer Number of items to be added
Checkout Cart

Checkout the current session's cart. You must be be logged in before performing this action.

Example Request:

mutation {
  checkoutCart {
    receipt {
      receiptItems {
        price
        product {
          title
        }
        quantity
      }
      subtotal
    }
    userErrors {
      message
      path
    }
  }
}

Response:

{
  "data": {
    "checkoutCart": {
      "receipt": {
        "receiptItems": [
          {
            "price": 79.99,
            "product": {
              "title": "Iron Helmet"
            },
            "quantity": 3
          }
        ],
        "subtotal": 239.97
      },
      "userErrors": []
    }
  }
}

Authentication

Creating a user

Due to the limitations of this project, tokens are currently stored in the session. In practice, this should be avoided in favour of JWT and other stateless authentication methods.

Before you are able to checkout your cart, you must be logged in and authenticated.

Example Request:

mutation {
  createUser(name: "Steven Z.", email: "steven@zhao.io", password: "hunter2") {
    user {
      id
    }
    userErrors {
      message
      path
    }
  }
}

Response:

{
  "data": {
    "createUser": {
      "user": {
        "id": "1"
      },
      "userErrors": []
    }
  }
}
Sign-in

Once you have an account, you must sign-in to generate an authentication token.

Example Request:

mutation {
  signInUser(email: "steven@zhao.io", password: "hunter2") {
    token
  }
}

Response:

{
  "data": {
    "signInUser": {
      "token": "TI3MuWQMvB37N/xls/CqXeTrvw==--dJwQyWh1WW+ATSIT--+eAMWr6FZxx31CQ3GqonLw=="
    }
  }
}

Final Remarks

This project was a great learning experience. I had nil experience working with Ruby/Rails nor GraphQL prior to this challenge. Takeaways:

  1. Ruby is very idiomatic. It's simple and beautiful when it works. I spent a lot of time researching best practices before I dove in and was always surprised how readable Ruby can get. I will need to improve on this front.
  2. Juggling between Rails/GraphQL types was daunting at first, but now I really appreciate how well they work together.
  3. Linting saves lives! Rubocop helped guide me away from taking practices from other languages over when I first started out.
You can’t perform that action at this time.