# Syncano 4.0 ruby gem


## Installation

Using gems:

```bash
$ gem install syncano --pre
```

## First steps

After installation, you have to set a path for api root for syncano.

If you want to use staging, export:

```bash
$ export API_ROOT=https://api.syncano.rocks
```

If you're less adventurous, use our production api servers:

```bash
$ export=API_ROOT=https://api.syncano.io
```

Ok, now we can start coding!

```ruby
require 'syncano'

syncano = Syncano.connect(api_key: 'your-api-key')

syncano.instances.all.each { |instance| puts instance }
```

You can either pass your API key to the connect method as above or set
`ENV['SYNCANO_API_KEY']` and call just `Syncano.connect`.

## API basics

Syncano API is a nested API - all the endpointes are scoped by an instances, ex.
codeboxes path is `/instance/your_instance_name/codeboxes/`. Syncano instances
is more less a schema is in relation databases. **Your instance name must be
unique across all existing Syncano instnaces, not only limitted to your account.**


# Instances

In order to do anything with Syncano, you have to create an instances. Choose a
globally unique name and call:

```ruby
instances = syncano.instances.create name: 'my_instances_name'       
instance.first

#=> #<Syncano::Resources::Instance created_at: Sun, 26 Apr 2015 18:09:46 +0000, description: "", metadata: {}, name: "my_instance_name", owner: nil, role: "full", updated_at: Sun, 26 Apr 2015 18:09:46 +0000>
```

# Classes and objects

In order to save objects in Syncano, first you need to create a class. A class
defines objects' attributes in the class' schema. The attribute definition has two
mandatory (`name`, `type`) and two optional fields (`filter_index`, `order_index`).
What these fields are for is rather obvious - `name` defines objects' attribute
name, and `type` defines type (you can read more about available types in the 
[API docs](http://docs.syncano.com/v0.1/docs/instancesinstanceclasses-2)). `*_index`
fields are indexing. `order_index` allows you order returned collections, 
`filter_index` allows filtering in a various ways. There will be a few examples
in this README, but you can read in the 
[API docs](http://docs.syncano.com/v0.1/docs/filtering-data-objects).

```ruby
  stock = instance.classes.create name: 'stock_items',
                                  schema: [{ name: 'name', type: 'string',
                                             filter_index: true },
                                           { name: 'amount', type: 'integer',
                                             filter_index: true,
                                             order_index: true }]
```

Once we have a class, we can start creating objects. 

```ruby
  chorizo = stock.objects.create name: 'Chorizo', amount: 100
  black_pudding = stock.objects.create name: 'Black pudding', amount: 200
  curry_wurts = stock.objects.create name: 'Curry wurst', amount: 150
  kabanos = stock.objects.create name: 'Kabanos' 
  something = stock.objects.create amount: 3
```

Now we have a few items in stock, let's try filtering. 

```ruby
  stock.objects.all(order_by: '-amount', query: { amount: { _lte: 150 }, name: { _exists: true } })
  #=> #<Syncano::Resources::Collection:0x007fc18b9c7698 @next=false, @prev=false, @collection=[#<Syncano::Resources::Object amount: 150, channel: nil, channel_room: nil, created_at: Mon, 27 Apr 2015 05:21:31 +0000, group: nil, group_permissions: "none", id: 12, name: "Curry wurst", other_permissions: "none", owner: nil, owner_permissions: "none", revision: 1, updated_at: Mon, 27 Apr 2015 05:21:31 +0000>, #<Syncano::Resources::Object amount: 100, channel: nil, channel_room: nil, created_at: Mon, 27 Apr 2015 05:21:30 +0000, group: nil, group_permissions: "none", id: 10, name: "Chorizo", other_permissions: "none", owner: nil, owner_permissions: "none", revision: 1, updated_at: Mon, 27 Apr 2015 05:21:30 +0000>]> 
```

Let's give `something` a name and try again.

```ruby
  something.name = 'Unidentified sausage' 
  something.save
  
  stock.objects.all(order_by: '-amount', query: { amount: { _lte: 150 }, name: { _exists: true } })
  #=> #<Syncano::Resources::Collection:0x007fc18d58a628 @next=false, @prev=false, @collection=[#<Syncano::Resources::Object amount: 150, channel: nil, channel_room: nil, created_at: Mon, 27 Apr 2015 05:21:31 +0000, group: nil, group_permissions: \"none\", id: 12, name: \"Curry wurst\", other_permissions: \"none\", owner: nil, owner_permissions: \"none\", revision: 1, updated_at: Mon, 27 Apr 2015 05:21:31 +0000>, #<Syncano::Resources::Object amount: 100, channel: nil, channel_room: nil, created_at: Mon, 27 Apr 2015 05:21:30 +0000, group: nil, group_permissions: \"none\", id: 10, name: \"Chorizo\", other_permissions: \"none\", owner: nil, owner_permissions: \"none\", revision: 1, updated_at: Mon, 27 Apr 2015 05:21:30 +0000>, #<Syncano::Resources::Object amount: 3, channel: nil, channel_room: nil, created_at: Mon, 27 Apr 2015 05:30:18 +0000, group: nil, group_permissions: \"none\", id: 15, name: \"Unidentified sausage\", other_permissions: \"none\", owner: nil, owner_permissions: \"none\", revision: 2, updated_at: Mon, 27 Apr 2015 05:30:48 +0000>]>
```

Now it matches the query and appears in the result.

# Codeboxes

Codeboxes are small pieces of code that run on Syncano servers. You can run them
manually using the API, you can create a schedule to run them periodically, you 
can create a Webhook (and optionally make it public) to run them from the web, 
you can create a trigger to run one after a class' object is created, updated or 
deleted. There are three runtimes available: Ruby, Python and Node. This gem is 
available in Ruby runtime (just needs to be required). Let's create a simple 
codebox and run it. 

```ruby
clock = instance.codeboxes.create(name: 'clock', source: 'puts Time.now', runtime_name: 'ruby')
#=> #<Syncano::Resources::CodeBox config: {}, created_at: Thu, 30 Apr 2015 05:50:09 +0000, description: "", id: 1, name: "clock", runtime_name: "ruby", source: "puts Time.now", updated_at: Thu, 30 Apr 2015 05:50:09 +0000>
clock.run 
#=> {"status"=>"pending", "links"=>{"self"=>"gv1/instances/a523b7e842dea927d8c306ec0a9a7a4ac30191c2cd034b11d/codeboxes/1/traces/1/"}, "executed_at"=>nil, "result"=>"", "duration"=>nil, "id"=>1}
```

When you schedule a codebox run, it returns the trace. Immediately after the 
call it's status is pending, so you need to check the trace. 

```ruby
clock.traces.first
=> #<Syncano::Resources::CodeBoxTrace duration: 526, executed_at: Thu, 30 Apr 2015 05:25:14 +0000, id: 1, result: "2015-04-30 05:25:14 +0000", status: "success">
```

The run method is asynchronous and returns immediately. You should use this to
run codeboxes when you don't care about results at this very moment. If you 
want to run the codebox and get results in one call, you should use webhooks.

# Webhooks 

You can use webhooks to run codeboxes synchronously. Webhooks can be either 
public or private. You have to provide your API key when calling private ones, 
public are public, you can call them with curl, connect with third party
services,  etc. Ruby:


```ruby
webhook = @instance.webhooks.create slug: 'clock-webhook', codebox: clock.primary_key, public: true
#=> #<Syncano::Resources::Webhook codebox: 1, public: true, public_link: "a20b0ae122b53b2f2c445f6a7a202b274c3631ad", slug: "clock-webhook">

webhook.run['result']
#=> "2015-04-30 05:51:45 +0000"
```

and curl

```bash
$ curl "https://api.syncano.rocks/v1/instances//af248d3e8b92e6e7aaa42dfc41de80c66c90d620cbe3fcd19/webhooks/p/a20b0ae122b53b2f2c445f6a7a202b274c3631ad/"
{"status": "success", "duration": 270, "result": "2015-04-30 06:11:08 +0000", "executed_at": "2015-04-30T06:11:08.607389Z"}
```

## Contributing

1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request