Creating an SQL based Resource

From Ocean Framework Documentation Wiki
Jump to: navigation, search

Let's create the simplest resource possible. We'll call it Thing. Basically all it contains is a name and a description.

Pearl.jpeg TIP: You do not need to have a full SQL database available during development and testing, as Ocean uses SQLite by default except in production (services should run in full isolation and therefore are developed and tested that way, even by the Continuous Integration servers). Should you want to use a local SQL DB such as MySQL, PostgreSQL or Oracle, simply edit config/database.yml accordingly.

The structure of a Thing is as follows:

attribute name type remarks
id string The UUID of the Thing, e.g. d6a965bc-b174-4292-9ede-757b452c9ae6.
name string The name of the Thing.
description text A description of the Thing.
created_at datetime The time in UTC when the Thing was created.
updated_at datetime The time in UTC when the Thing was last updated.
created_by string The URI of the ApiUser who created the Thing.
updated_by string The URI of the ApiUser who last modified the Thing.
lock_version integer For optimistic locking.

These attributes are common to many Ocean resources by convention, and Ocean includes logic to keep the last five of them updated automatically. The scaffolding and templates are set up for the above attributes, as are all specs.

The Things Migration

To create a Rails migration for Things, issue the following terminal command:

rails g scaffold thing \
  id:string name:string description:text created_by:string updated_by:string lock_version:integer

This is more or less the standard command for creating any resource; it's a good idea to start with a very basic structure as a point of departure. When all tests pass, you simply modify and extend it. You'll find that the ocean_scaffold generator sets up specs for the above set of attributes. You can simply follow the pattern they establish.

We need to modify the migration to prepare the table for using string UUIDs rather than integer IDs, as this is the standard in Ocean. There are several reasons for this, not least the security one: exposing an ID shouldn't let anybody guess any other ID. We also need to add a default value for lock_version.

To do so, open the migration in the db/migrate directory. It should look something like this:

class CreateThings < ActiveRecord::Migration
  def change
    create_table :things do |t|
      t.string :id
      t.string :name
      t.text :description
      t.string :created_by
      t.string :updated_by
      t.integer :lock_version
      t.timestamps
    end
  end
end

First of all insert id: false after the table name (:things). This suppresses Rails' automatic setup of the id attribute. The line should now read

    create_table :things, id: false do |t|

Next, we must add an index to the string primary field. Add the following at the end of the change block:

    add_index :things, :id, unique: true

Finally, change the lock_version line to read

      t.integer :lock_version, null: false, default: 0

Your migration should now read

class CreateThings < ActiveRecord::Migration
  def change
    create_table :things, id: false do |t|
      t.string :id
      t.string :name
      t.text :description
      t.string :created_by
      t.string :updated_by
      t.integer :lock_version, null: false, default: 0
      t.timestamps
    end
    add_index :things, :id, unique: true
  end
end

You're now ready to run

 rake db:migrate
 annotate -i

Ocean Scaffolding and Specs

Now we must run the ocean_scaffold generator to replace the standard scaffolding and specs with Ocean REST specs:

 rails g ocean_scaffold thing

Confirm overwrites with Y.

As mentioned above, ocean_scaffold generates specs for precisely the set of attributes we've set up so far. It's the Ocean common point of departure.

Adjust the Factory

Open spec/factories/things.rb.

Remove the line which declares a default value for the id attribute.

Change the value for created_by and updated_by to a structurally valid ApiUser path, such as "https://api.example.com/v1/api_users/1-2-3-4-5".

Routing

Next, we need to set up routing for the Thing resource. This is done in config/routes.rb. Move the resource declaration into the v1 scope. Add an except: clause to suppress routing of the Rails actions we don't use in a REST API. Also add a constraints clause to specify the structure of the UUIDs we accept:

Sandbox::Application.routes.draw do

  get "/alive" => "alive#index"

  scope "v1" do
    resources :things, except: [:new, :edit],
                       constraints: {id: UUID_REGEX}
  end

end

Running the Tests

At this point, all tests should pass. Verify this by running rspec.

Tailoring Your New Resource

You're now ready to tailor your new resource to your needs by adding more attributes, validations, business logic, etc. Take a look at the Things model to see how attributes are made visible to the outer world, and also at the ThingsController to examine which attributes are required. There's full documentation in the ocean-rails gem for all this, and the Ocean Core services provide many useful examples, particularly the Auth service which is the most comprehensive one.

Generally, when implementing new functionality, the best way of running the tests is section by section, in this order:

  1. rspec spec/routing – to determine if all routing is set up correctly. If not, check your config/routing.rb file.
  2. rspec spec/models – to verify that the model works as it should.
  3. rspec spec/views – to verify that your views construct valid JSON representations of your model data.
  4. rspec spec/controllers – to verify that the controllers access and process data correctly.
  5. rspec spec/requests – to verify correctness end-to-end.