Skip to content

invopop/gobl.ruby

Repository files navigation

GOBL Ruby

GOBL Logo

Go Business Language – Ruby.

Released under the Apache 2.0 LICENSE, Copyright 2021,2022 Invopop Ltd.. Trademark pending.

Introduction

Ruby library for reading, producing, manipulating and operating over GOBL documents and structures.

Features

Documentation

User guide

Installation

Add this line to your application's Gemfile:

gem 'gobl'

And then execute:

bundle install

Instantiating structures

You can instantiate any struct in the library using a hash of nested attributes. For example:

invoice = GOBL::Bill::Invoice.new(
  code: 'SAMPLE-001',
  currency: :eur,
  issue_date: Date.new(2021, 1, 1),
  supplier: { tax_id: { country: :es, code: '55105445K' }, name: 'Provide One S.L.' },
  customer: { tax_id: { country: :es, code: '65870696F' }, name: 'Sample Consumer' },
  lines: [{
    quantity: 20,
    item: { name: 'Development services', price: 90.0 },
    taxes: [ { cat: 'VAT', rate: :standard } ]
  }]
)
invoice #=> #<GOBL::Bill::Invoice uuid=nil code="SAMPLE-001" ...>
invoice.code # => "SAMPLE-001"
invoice.currency # => #<GOBL::Currency::Code _value="EUR">
invoice.date # => #<GOBL::Cal::Date _value="2021-01-01">
invoice.lines.first.item.price #=> #<GOBL::Num::Amount:... @value=900, @exp=1>

Note how the constructor took care of creating every nested object in the attributes hash and also coercing strings, symbols and dates into GOBL objects.

The constructor requires any attribute marked as mandatory and not calculated in the GOBL schema to be present in the input data. Otherwise, it will raise an error. For example:

message = GOBL::Note::Message.new #=> Dry::Struct::Error ([GOBL::Note::Message.new] :content is missing in Hash input)

See also: GOBL::Bill::Invoice#new

Serializing to and deserializing from JSON

GOBL is a JSON format. So, you'll probably need to read or produce valid GOBL JSON at some point. Every struct class in the library provides a .from_json! and a #to_json method for those very purposes:

json = '{"$schema":"https://gobl.org/draft-0/note/message", "content":"Hello World!"}'
document = GOBL::Schema::Object.from_json!(json) #=> #<GOBL::Schema::Object _value={"$schema"=>"https://gobl.org/draft-0/note/message", "content"=>"Hello World!"}>
message = document.extract #=> #<GOBL::Note::Message title=nil content="Hello World!" meta=nil>
message.content #=> "Hello World!"

Note that, in the previous example, we parsed the JSON fragment as a document. A document is one of the fundamental entities of GOBL, and it represents a business document in an abstract way. To get the specific document type instantiated –a message, in the example above–, we needed to call the #extract method of the document object.

The previous instantiation method is useful if you don't know the document type in advance. If you do, you could also instantiate the object in this more direct way:

json = '{"$schema":"https://gobl.org/draft-0/note/message", "content":"Hello World!"}'
message = GOBL::Note::Message.from_json!(json) #=> #<GOBL::Note::Message title=nil content="Hello World!" meta=nil>
message.content #=> "Hello World!"

Conversely, you can generate JSON GOBL from any instantiated object:

message = GOBL::Note::Message.new(content: 'Hello World!')
message.to_json #=> "{\"content\":\"Hello World!\"}"

Note that, in the previous example, the generated JSON doesn't include a $schema attribute. This is because the GOBL schema doesn't require that attribute in a standalone message structure. If you want to use that structure as a document, you will need a $schema to be present. You can get that from your Ruby code by simply embedding the struct in a document:

message = GOBL::Note::Message.new(content: 'Hello World!')
document = GOBL::Schema::Object.embed(message)
document.to_json #=> "{\"content\":\"Hello World!\",\"$schema\":\"https://gobl.org/draft-0/note/message\"}"

See also GOBL::Struct.from_json!, GOBL::Struct#to_json, GOBL::Schema::Object#embed, GOBL::Schema::Object.extract

Handling value objects and enumerations

You can instantiate value objects in the library from a Symbol, a String or something that can be coerced into one via #to_s:

code = GOBL::Org::Code.new('A123') #=> #<GOBL::Org::Code _value="A123">
date = GOBL::Cal::Date.new(Date.today) #=> #<GOBL::Cal::Date _value="2022-09-23">
type = GOBL::Bill::InvoiceType.new(:credit_note) #=> #<GOBL::Bill::InvoiceType _value="credit-note">

Similarly, you can compare value objects to symbols, strings or any object coercible into one:

GOBL::Org::Code.new('A123') == 'A123' #=> true
GOBL::Org::Code.new('2022-01-01') == Date.new(2022,1,1) #=> true
GOBL::Bill::InvoiceType.new('credit-note') == :credit_note #=> true

The GOBL schema uses composition with certain value objects to delimit the range of allowed values. For those enumerated types, the library provides additional convenience methods:

# `.all` class method
GOBL::Bill::InvoiceType.all #=> Returns an array with all the objects in the enumeration

# Inquirers
type = GOBL::Bill::InvoiceType.new('credit-note')
type.proforma? #=> false
type.credit_note? #=> true

# Descriptions
type = GOBL::Org::Unit.new('kg')
type.description #=> "Metric kilograms"

See also GOBL::Org::Code, GOBL::Bill::InvoiceType

Running operations

The library also provides an interface to run operations over GOBL structs in the GOBL CLI service. To run those operations, you'll need the CLI installed and the service running:

$ gobl serve -p 3033

Then, in your app, you need to configure the host and the port where the service is listening:

GOBL.config.service_host = '127.0.0.1'
GOBL.config.service_port = 3033

And then you run your operations! For instance, you can build an invoice document, which will validate and calculate it:

invoice = GOBL::Bill::Invoice.new(
  code: 'SAMPLE-001',
  currency: :eur,
  issue_date: Date.new(2021, 1, 1),
  supplier: { tax_id: { country: :es, code: '55105445K' }, name: 'Provide One S.L.' },
  customer: { tax_id: { country: :es, code: '65870696F' }, name: 'Sample Consumer' },
  lines: [{
    quantity: 20,
    item: { name: 'Development services', price: 90.0 },
    taxes: [ { cat: 'VAT', rate: :standard } ]
  }]
)
document = GOBL::Schema::Object.embed(invoice)
calculated_document = GOBL.build(document)
calculated_invoice = calculated_document.extract
calculated_invoice.totals.total_with_tax #=> "2178.00"

Note how the #build operation expects and returns either document or envelope structs. That's why we needed to embed our invoice in a document and extract the calculated invoice from the returned document. See the operations API reference for more details about this and the rest of the available operations.

See also GOBL::Operations

Developer guide

Project Structure

lib

Contains the gobl library components to be imported into other projects.

All the auto-generated files from the JSON schema are also defined here. You must not modify files containing an auto-generate header should not be modified. Under the hood, the gem uses Zeitwerk to load.

tasks

The tasks directory contains code able to parse (tasks/parser) GOBL JSON Schema and generate (tasks/generators) the Ruby versions of the GOBL structures.

Development tasks

The project provides a mage.go file with a set of Mage tasks to be used by developers of the library. All these tasks run within a Docker container.

You can avoid mage and the docker container if you prefer so. Take a look at the mage.go file to learn the commands it invokes under the hood and run them yourself.

Installation

The command mage setup fetches and installs all the required dependencies to use the gem.

If you run into any problems with dependencies, don't forget this is a library, so the Gemfile.lock is not included. Delete any local versions you may already have and re-run.

Code generation

Ensure all the GOBL JSON Schema and Regime files are available by manually copying the base GOBL project's build/ path to the data/ path in this repository. For example, assuming you have GOBL at ../gobl/, you can run this:

rm -rf ./data/*
cd ../gobl
go generate .
find ./data -name '*.json' -type f | cpio -pdm ../gobl.ruby/
cd ../gobl.ruby

Please note that schemas is .gitignored as we only need it to generate the code. However, the previous command also copies regimes which content is used by the library and so, it is part of the git repo and the gem.

Now, with the help of the GOBL generator, you can regenerate the code. For example, assuming the GOBL generator is at ../gobl.generator, you can run this:

rm -rf lib/generated/gobl
../gobl.generator/bin/generate -l ruby -o lib/generated/gobl/

Tests

Use the mage spec command to run the entire test suite.

Console

The command mage console spins up an IRB session with the library required.

YARD documentation

The library is fully documented using YARD. You can spin up a YARD server with the command mage yardserver.

Releases

When releasing a new version of the GOBL Ruby library:

  1. Update lib/gobl/version.rb and commit the change (no need to push)
  2. Run rake release which generates the tag, pushes everything to github and rubygems