Skip to content

Commit

Permalink
Readme rewrite
Browse files Browse the repository at this point in the history
  • Loading branch information
d-Pixie committed Mar 11, 2016
1 parent d380c97 commit 834faf5
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 106 deletions.
26 changes: 26 additions & 0 deletions CONTRIBUTE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
1. Fork it ( http://github.com/my-codeworks/fortnox-api/fork )
2. Clone your fork (`git clone https://github.com/<your GitHub user
name>/fortnox-api.git`)
3. Create your feature branch (`git checkout -b my-new-feature`)
4. Commit your changes (`git commit -am 'Add some feature'`)
5. Push to the branch (`git push origin my-new-feature`)
6. Create new Pull Request

### Adding upstream remote

If you want to keep contributing, it is a good idea to add the original repo as
remote server to your clone ( `git remote add upstream
https://github.com/my-codeworks/fortnox-api` ). Then you can do something like
this to update your fork:

1. Fetch branches from upstream (`git fetch upstream`)
2. Checkout your master branch (`git checkout master`)
3. Update it (`git rebase upstream/master`)
4. And push it to your fork (`git push origin master`)

If you want to update another branch:
```
git checkout branch-name
git rebase upstream/branch-name
git push origin branch-name
```
210 changes: 104 additions & 106 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,82 @@
# Fortnox::API

Wrapper gem for Fortnox AB's version 3 API.
# Fortnox API
> Wrapper gem for Fortnox AB's version 3 REST(ish) API. If you need to integrate an existing or new Ruby or Rails app against Fortnox this gem will save you a lot of time, you are welcome. Feel free to repay the community with some nice PRs of your own :simple_smile:
# Status
[![Gem Version](https://badge.fury.io/rb/fortnox-api.png)](http://badge.fury.io/rb/fortnox-api)
[![Build Status](https://travis-ci.org/my-codeworks/fortnox-api.png)](https://travis-ci.org/my-codeworks/fortnox-api)
[![Code Climate](https://codeclimate.com/github/my-codeworks/fortnox-api.png)](https://codeclimate.com/github/my-codeworks/fortnox-api)
[![Dependency Status](https://gemnasium.com/my-codeworks/fortnox-api.svg)](https://gemnasium.com/my-codeworks/fortnox-api)

## Features
The rough status of this project is as follows (as of Marsh 2016):
* In active development (just check out the commit log)
* Two developers. At least twice as good as one.
* Basic structure mostly complete. Things like getting customers and invoices, updating and saving etc.
* Advanced features around the corner. Things like filtering entities or sorting, pagination of results etc.
* A few models implemented. Right now it's Customer and Invoice that are the furthest along. Order and Offer will be done quickly once Invoice is completed and adding more models in general is quick and easy, see the developer guid further down.
* Massive refactorings of the code occurs weakly :) This is not at all stable for production yet.

The goal is to have a production ready version that covers at least the Invoice, Order, Offer, Customer, Account and Bookkeeping models by July or August.

# Architecture overview
The gem is structured with three distinct models for the tasks of data, validation and saving state. These are, as is traditional, called: model, validator and repository.

If you come from a Rails background and have not been exposed to other ways of structuring the solution to the CRUD problem this might seem strange to you since ActiveRecord merges these three roles into the `ActiveRecord::Base` class.

To keep it simple: The active record pattern (as implemented by Rails) is easier to work with if you only have one data source, the database, in your application. The data mapper pattern is easier to work with if you have several data sources, such ass different databases, external APIs and flat files on disk etc, in your application. It's also easier to compose the data mapper components into active record like classes than to separate active records parts to get a data mapper style structure.

If you are interested in a more detailed description of the difference between the two architectures you can read this post that explains it well using simple examples: [What’s the difference between Active Record and Data Mapper?](http://culttt.com/2014/06/18/whats-difference-active-record-data-mapper/)

Quick overview of the current status of this gem vs the API it integrates
against:
## Model
The model role classes serve as dumb data objects. They do have some logic to coheres values etc, but they do not contain validation logic nor any business logic at all.

API resource | Status
------------------------------------------ | --------
Exchange AuthorizationCode for AccessToken | :white_check_mark:
Entity base class with immutability | :white_check_mark:
Validator base class based on Vanguard | :white_check_mark:
Customer::Validator | :white_check_mark:
Customers | :hourglass:
Repository READ from API functionality | :hourglass:
### Attribute
Several of the models share attributes. One example is account, as in a bookkeeping account number. These attributes have the same definition, cohesion and validation logic so it makes sense to extract them from the models and put them in separate classes.

## Requirements
You can find several of these under the `/models/attributes` directory where the implementation of the definition and cohesion lives. For the validation part see Validator below.

### Immutability
The model instances are immutable. That means:
```ruby
customer.name # => "Old Name"
customer.name = 'New Name' # => "New Name"

customer.name == "New Name" # => false
```
Normally you would expect an assignment to mutate the instance and update the `name` field. Immutability explicitly means that you can't mutate state this way, any operation that attempts to update state needs to return a new instance with the updated state while leaving the old instance alone.

So you might think you should do this instead:
```ruby
customer = customer.name = 'New Name' # => "New Name"
```
But if you are familiar with chaining assignments in Ruby you will see that this does not work. The result of any assignment, `LHS = RHS`, operation in Ruby is `RHS`. Even if you implement your own `=` method and explicitly return something else. This is a feature of the language and not something we can get around. So instead you have to do:
```ruby
customer.name # => "Old Name"
customer = customer.update( name: 'New Name' ) # => <Fortnox::API::Customer:0x007fdf22949298 ... >
customer.name == "New Name" # => true
```
And note that:
```ruby
customer.name # => "Old Name"
customer.update( name: 'New Name' ) # => <Fortnox::API::Customer:0x007fdf21100b00 ... >
customer.name == "New Name" # => false
```
This is how all the models work, they are all immutable.
## Validator
The validators enforce the constraints on values, lengths and, in some cases, content of the model attributes. You should run the validator on the model before trying to persist it using the repository. You can send any model instance off to the repository and you will get errors back from Fortnox's API server, but using the validator you get a nice list of errors that you can present to the end user instead of pinging the API once for every error and rescuing the exception we throw when we get a 4xx/5xx response from the server.

### Attribute
As the model separates some attributes out into separate classes it makes sense that the validators for these models are similarly composable. The attribute validators can be found in `/validators/attributes` and each match one of the model attributes.

## Repositories
Used to load, update, create and delete model instances. This is what's actually wrapping the HTTP REST API actions.

# Requirements

This gem is build for Ruby 2.0 or higher, it's tested agains Ruby 2.1.8, 2.2.4
and 2.3.0. Since it uses the keywords argument feature it won't work on older
versions.

If you want or need Ruby 1.9 compatability please submit a pull request.
If you want or need Ruby 1.9 compatibility please submit a pull request.
Instructions can be found below :)

## Installation
Expand All @@ -50,126 +99,75 @@ Or install it yourself as:
$ gem install fortnox-api
```

## Usage
# Usage

### Getting an AccessToken
## Getting an AccessToken
To make calls to the API server you need a `ClientSecret` and an `AccessToken`. When you sign up for an API-account with Fortnox you should get a client secret and an authorization code. To get the access token, that is reusable, you need to do a one time exchange with the API-server and exchange your authorization code for an access token. This can be done in several ways but we provide a method for it in the gem that you can use.

Unless you have an AccessToken already you will need to exchange your
AuthorizationCode for one to make calls. In a ruby shell do the following
> :warning: **Do not do this more than once!** If you try to do the auth code/access token exchange more than once, regardless of method, it will lock your API-account! So if you get the token using curl or whatever do not use this method as well. If your account is not working and you think it might be due to this you will have to contact Fortnox support and have them reset the authorization code for you.
```ruby
require 'fortnox' # Load the gem, it needs to be installed first
Fortnox::API.get_access_token(
# Load the special class from the gem. You need to install the gem first ofc.
require 'fortnox/api/access_token'

Fortnox::API::AccessToken.new(
base_url: 'https://api.fortnox.se/3',
client_secret: 'P5K5wE3Kun',
authorization_code: 'ea3862b1-189c-464b-8e25-1b9702365fa1',
client_secret: 'P5K5wE3Kun', # Replace with your client secret
authorization_code: 'ea3862b1-189c-464b-8e25-1b9702365fa1', # Replace with your auth code
)
```
This will output a new token like `3f08d038-f380-4893-94a0a0-8f6e60e67a` that is your access token, **save it!** Set it in the environment by following the instructions in the next step.

This will output a new token like `3f08d038-f380-4893-94a0a0-8f6e60e67a` that is
your access token, **save it!** You will need to save it since it will be used
every time you instansiate a Fortnox::API object.
## Environment variables
The authentication for this gem is stored in the environment. See the documentation for your OS to get instructions for how to set environment variables in it.

### Using environment variables
You can choose to use the [`dotenv` gem](https://github.com/bkeepers/dotenv) that we include for development but it is NOT recommended to use in production. You should set proper environment variables in production so you don't have to commit the environment file to source control.

You can save your settings in environment variables that will be used by the gem
when you instantiate it. The following variables are recognized:

```
FORTNOX_API_BASE_URL
The environment variables we use are:
```bash
FORTNOX_API_CLIENT_SECRET
FORTNOX_API_ACCESS_TOKEN
FORTNOX_API_AUTHORIZATION_CODE
```

Their values should match their name. Note that the authorization code is only
ever used once and only when you call the `get_access_token` method described
above.

### Collections

To get something to work with you instantiate a new Fortnox::API object

```ruby
fortnox = Fortnox::API.new(
base_url: 'https://api.fortnox.se/3',
client_secret: 'P5K5wE3Kun',
access_token: '3f08d038-f380-4893-94a0a0-8f6e60e67a',
)
```
Their values should match their name.

On this you can then call things like
# Usage
## Repositories
Repositories are used to load,save and remove entities from the remote server. The calls are subject to network latency and are blocking. Do make sure to rescue appropriate network errors in your code.

```ruby
# Get a list of all the customers (slow, don't use unless you have to)
fortnox.customer.all #=> [<Fortnox::API::Customer:0x007fdf21033ee8>, <Fortnox::API::Customer:0x007fdf22994310>, ... ]
# Instanciate a repository
repo = Fortnox::API::Repository::Customer.new

# Get single customer by id
fortnox.customer.find( 5 ) #=> <Fortnox::API::Customer:0x007fdf21100b00>
# Get a list of all the entities
repo.all #=> <Fortnox::API::Collection:0x007fdf2104575638 @entities: [<Fortnox::API::Customer::Simple:0x007fdf21033ee8>, <Fortnox::API::Customer::Simple:0x007fdf22994310>, ... ]

# Get single customer by customer number
fortnox.customer.find_by( customer_number: 5 ) #=> <Fortnox::API::Customer:0x007fdf22949298>
# Get entity by id
repo.find( 5 ) #=> <Fortnox::API::Customer:0x007fdf21100b00>

# Get all customers matching some criteria
fortnox.customer.find_all_by( name: 'test' ) #=> [<Fortnox::API::Customer:0x007fdf22949298>, ... ]
# Get entities by attribute
repo.find_by( customer_number: 5 ) #=> <Fortnox::API::Collection:0x007fdf22994310 @entities: [<Fortnox::API::Customer::Simple:0x007fdf22949298>]
```
If you are eagle eyed you might have spotted the different classes for the entities returned in a collection vs the one we get from find. The `Simple` version of a class is used in thouse cases where the API-server doesn't return a full set of attributes for an entity. For customers the simple version has 10 attributes while the full have over 40.

### Entities
You should try to get by using the simple versions for as long as possible. Both the `Collection` and `Simple` classes have a `.full` method that will give you full versions of the entities. Bare in mind though that a collection of 20 simple models that you run `.full` on will call out to the server 20 times, in sequence.

All the above methods return instances or arrays of instances of some resource
## Entities
All the repository methods return instances or collections of instances of some resource
class such as customer, invoice, item, voucher and so on.

Instances are immutable and any update returns a new instance with the
appropriate attributes changed.

> :warning: Ruby implements assignment statements so that `LHS = RHS`
> always returns `RHS`. This is regardless of what an assignment method is
> implemented to return.
>
> This means that assignments like `customer.name = 'Test'` will return
> `'Test'` and not a new instance of `Customer`, which is what you want in this
> case. So assignment methods to update objects is not possible. Use the
> `.update()` method instead.
Each entity class has it's own methods and properties but they also all respond
to a few common methods, such as for persistence.
appropriate attributes changed (see the Immutable section under Architecture above for more details). To change the properties of a model works like this:

```ruby
customer #=> <Fortnox::API::Customer:0x007fdf228db310>
customer.name #=> "Nelly Bloom"
customer.update( name: "Ned Stark" ) #=> <Fortnox::API::Customer:0x0193a456ff0307>
customer.name #=> "Nelly Bloom"
updated_customer = customer.update( name: "Ned Stark" )
updated_customer.name #=> "Nelly Bloom"
updated_customer.save #=> true on success or false on failure
updated_customer.save! #=> true on success or raises Fortnox::API::RequestFailed exception on failure
```

## Contributing

1. Fork it ( http://github.com/my-codeworks/fortnox-api/fork )
2. Clone your fork (`git clone https://github.com/<your GitHub user
name>/fortnox-api.git`)
3. Create your feature branch (`git checkout -b my-new-feature`)
4. Commit your changes (`git commit -am 'Add some feature'`)
5. Push to the branch (`git push origin my-new-feature`)
6. Create new Pull Request

### Adding upstream remote

If you want to keep contributing, it is a good idea to add the original repo as
remote server to your clone ( `git remote add upstream
https://github.com/my-codeworks/fortnox-api` ). Then you can do something like
this to update your fork:

1. Fetch branches from upstream (`git fetch upstream`)
2. Checkout your master branch (`git checkout master`)
3. Update it (`git rebase upstream/master`)
4. And push it to your fork (`git push origin master`)

If you want to update another branch:
```
git checkout branch-name
git rebase upstream/branch-name
git push origin branch-name
updated_customer = customer.update( name: "Ned Stark" ) #=> <Fortnox::API::Customer:0x0193a456fe3791>
updated_customer.name #=> "Ned Stark"
```

The update method takes an implicit hash of attributes to update, so you can update as many as you like in one go.
# Contributing
See the [CONTRIBUTE](CONTRIBUTE.md) readme.

0 comments on commit 834faf5

Please sign in to comment.