syncano 4.0.0.alpha1 → 4.0.0.alpha2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -157
  3. data/circle.yml +1 -1
  4. data/lib/syncano.rb +38 -11
  5. data/lib/syncano/api.rb +20 -2
  6. data/lib/syncano/api/endpoints.rb +17 -0
  7. data/lib/syncano/connection.rb +46 -54
  8. data/lib/syncano/poller.rb +55 -0
  9. data/lib/syncano/resources.rb +48 -16
  10. data/lib/syncano/resources/base.rb +46 -56
  11. data/lib/syncano/resources/paths.rb +48 -0
  12. data/lib/syncano/resources/resource_invalid.rb +15 -0
  13. data/lib/syncano/response.rb +55 -0
  14. data/lib/syncano/schema.rb +10 -29
  15. data/lib/syncano/schema/attribute_definition.rb +2 -2
  16. data/lib/syncano/schema/endpoints_whitelist.rb +40 -0
  17. data/lib/syncano/schema/resource_definition.rb +5 -0
  18. data/lib/syncano/upload_io.rb +7 -0
  19. data/lib/syncano/version.rb +1 -1
  20. data/spec/integration/syncano_spec.rb +220 -15
  21. data/spec/spec_helper.rb +3 -1
  22. data/spec/unit/connection_spec.rb +34 -97
  23. data/spec/unit/resources/paths_spec.rb +21 -0
  24. data/spec/unit/resources_base_spec.rb +77 -16
  25. data/spec/unit/response_spec.rb +75 -0
  26. data/spec/unit/schema/resource_definition_spec.rb +10 -0
  27. data/spec/unit/schema_spec.rb +5 -55
  28. data/syncano.gemspec +4 -0
  29. metadata +69 -13
  30. data/lib/active_attr/dirty.rb +0 -26
  31. data/lib/active_attr/typecasting/hash_typecaster.rb +0 -34
  32. data/lib/active_attr/typecasting_override.rb +0 -29
  33. data/lib/syncano/model/associations.rb +0 -121
  34. data/lib/syncano/model/associations/base.rb +0 -38
  35. data/lib/syncano/model/associations/belongs_to.rb +0 -30
  36. data/lib/syncano/model/associations/has_many.rb +0 -75
  37. data/lib/syncano/model/associations/has_one.rb +0 -22
  38. data/lib/syncano/model/base.rb +0 -257
  39. data/lib/syncano/model/callbacks.rb +0 -49
  40. data/lib/syncano/model/scope_builder.rb +0 -158
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f06e086fb01f6bdd1a005c7ab4618d75babee13e
4
- data.tar.gz: b906104e4fa06db0cb09e3808d3fa924dcb8d5ce
3
+ metadata.gz: bd053f463d602ab8927be7cd6f1da7080cd50e03
4
+ data.tar.gz: 672affd48e958f2387e1e01a3b74e5a8324a95d2
5
5
  SHA512:
6
- metadata.gz: 0174413a95cfca8ed8fc4c05409877a0ca76b2f623da5331374a46263e85a50acf38c6b5bb507fa991f2343f58b8ea6f9e0d71e6dbcfbdb9330bfcea4fd450f5
7
- data.tar.gz: ac1b5fc9689cac452c336462f7ca71d56b83c67da684e2149ba1ae40bca3c1a51e6a450ae12dc02e582023832a6f15813ab3a5e07b8187a72bc46b76602e8969
6
+ metadata.gz: 35fc5c588f6b5d90a0b8cd318bf9be218a432985cd7703fe0ab98d1f4c9d01838685e2b137f5e39e3bbd6424f72de3f611e654f01c95d31b3b6c86e0f569f8d9
7
+ data.tar.gz: 7b4373ec897884764c0a70c0ea1a03504eb72d8841dad4f64653b01a47c4c2b1186a9bf16b74b16d3ca067ffbb679d3602b12321a22fdff84a6c8a0f3033f3ba
data/README.md CHANGED
@@ -1,167 +1,18 @@
1
- # Syncano 4.0 ruby gem
1
+ # Syncano 4.0 Ruby Gem
2
2
 
3
+ ## Ruby QuickStart Guide
4
+ ---
3
5
 
4
- ## Installation
6
+ Syncano ruby gem provides communication with Syncano platform - ([www.syncano.io](http://www.syncano.io/?utm_source=github&utm_medium=readme&utm_campaign=syncano-ruby))
5
7
 
6
- Using gems:
8
+ You can find quick start on installing and using Syncano's Ruby library in our [documentation](http://docs.syncano.io/docs/ruby/?utm_source=github&utm_medium=readme&utm_campaign=syncano-js).
7
9
 
8
- ```bash
9
- $ gem install syncano --pre
10
- ```
10
+ For more detailed information on how to use Syncano and its features - our [Developer Manual](http://docs.syncano.io/docs/getting-started-with-syncano/?utm_source=github&utm_medium=readme&utm_campaign=syncano-js) should be very helpful.
11
11
 
12
- ## First steps
13
-
14
- After installation, you have to set a path for api root for syncano.
15
-
16
- If you want to use staging, export:
17
-
18
- ```bash
19
- $ export API_ROOT=https://api.syncano.rocks
20
- ```
21
-
22
- If you're less adventurous, use our production api servers:
23
-
24
- ```bash
25
- $ export=API_ROOT=https://api.syncano.io
26
- ```
27
-
28
- Ok, now we can start coding!
29
-
30
- ```ruby
31
- require 'syncano'
32
-
33
- syncano = Syncano.connect(api_key: 'your-api-key')
34
-
35
- syncano.instances.all.each { |instance| puts instance }
36
- ```
37
-
38
- You can either pass your API key to the connect method as above or set
39
- `ENV['SYNCANO_API_KEY']` and call just `Syncano.connect`.
40
-
41
- ## API basics
42
-
43
- Syncano API is a nested API - all the endpointes are scoped by an instances, ex.
44
- codeboxes path is `/instance/your_instance_name/codeboxes/`. Syncano instances
45
- is more less a schema is in relation databases. **Your instance name must be
46
- unique across all existing Syncano instnaces, not only limitted to your account.**
47
-
48
-
49
- # Instances
50
-
51
- In order to do anything with Syncano, you have to create an instances. Choose a
52
- globally unique name and call:
53
-
54
- ```ruby
55
- instances = syncano.instances.create name: 'my_instances_name'
56
- instance.first
57
-
58
- #=> #<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>
59
- ```
60
-
61
- # Classes and objects
62
-
63
- In order to save objects in Syncano, first you need to create a class. A class
64
- defines objects' attributes in the class' schema. The attribute definition has two
65
- mandatory (`name`, `type`) and two optional fields (`filter_index`, `order_index`).
66
- What these fields are for is rather obvious - `name` defines objects' attribute
67
- name, and `type` defines type (you can read more about available types in the
68
- [API docs](http://docs.syncano.com/v0.1/docs/instancesinstanceclasses-2)). `*_index`
69
- fields are indexing. `order_index` allows you order returned collections,
70
- `filter_index` allows filtering in a various ways. There will be a few examples
71
- in this README, but you can read in the
72
- [API docs](http://docs.syncano.com/v0.1/docs/filtering-data-objects).
73
-
74
- ```ruby
75
- stock = instance.classes.create name: 'stock_items',
76
- schema: [{ name: 'name', type: 'string',
77
- filter_index: true },
78
- { name: 'amount', type: 'integer',
79
- filter_index: true,
80
- order_index: true }]
81
- ```
82
-
83
- Once we have a class, we can start creating objects.
84
-
85
- ```ruby
86
- chorizo = stock.objects.create name: 'Chorizo', amount: 100
87
- black_pudding = stock.objects.create name: 'Black pudding', amount: 200
88
- curry_wurts = stock.objects.create name: 'Curry wurst', amount: 150
89
- kabanos = stock.objects.create name: 'Kabanos'
90
- something = stock.objects.create amount: 3
91
- ```
92
-
93
- Now we have a few items in stock, let's try filtering.
94
-
95
- ```ruby
96
- stock.objects.all(order_by: '-amount', query: { amount: { _lte: 150 }, name: { _exists: true } })
97
- #=> #<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>]>
98
- ```
99
-
100
- Let's give `something` a name and try again.
101
-
102
- ```ruby
103
- something.name = 'Unidentified sausage'
104
- something.save
105
-
106
- stock.objects.all(order_by: '-amount', query: { amount: { _lte: 150 }, name: { _exists: true } })
107
- #=> #<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>]>
108
- ```
109
-
110
- Now it matches the query and appears in the result.
111
-
112
- # Codeboxes
113
-
114
- Codeboxes are small pieces of code that run on Syncano servers. You can run them
115
- manually using the API, you can create a schedule to run them periodically, you
116
- can create a Webhook (and optionally make it public) to run them from the web,
117
- you can create a trigger to run one after a class' object is created, updated or
118
- deleted. There are three runtimes available: Ruby, Python and Node. This gem is
119
- available in Ruby runtime (just needs to be required). Let's create a simple
120
- codebox and run it.
121
-
122
- ```ruby
123
- clock = instance.codeboxes.create(name: 'clock', source: 'puts Time.now', runtime_name: 'ruby')
124
- #=> #<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>
125
- clock.run
126
- #=> {"status"=>"pending", "links"=>{"self"=>"gv1/instances/a523b7e842dea927d8c306ec0a9a7a4ac30191c2cd034b11d/codeboxes/1/traces/1/"}, "executed_at"=>nil, "result"=>"", "duration"=>nil, "id"=>1}
127
- ```
128
-
129
- When you schedule a codebox run, it returns the trace. Immediately after the
130
- call it's status is pending, so you need to check the trace.
131
-
132
- ```ruby
133
- clock.traces.first
134
- => #<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">
135
- ```
136
-
137
- The run method is asynchronous and returns immediately. You should use this to
138
- run codeboxes when you don't care about results at this very moment. If you
139
- want to run the codebox and get results in one call, you should use webhooks.
140
-
141
- # Webhooks
142
-
143
- You can use webhooks to run codeboxes synchronously. Webhooks can be either
144
- public or private. You have to provide your API key when calling private ones,
145
- public are public, you can call them with curl, connect with third party
146
- services, etc. Ruby:
147
-
148
-
149
- ```ruby
150
- webhook = @instance.webhooks.create slug: 'clock-webhook', codebox: clock.primary_key, public: true
151
- #=> #<Syncano::Resources::Webhook codebox: 1, public: true, public_link: "a20b0ae122b53b2f2c445f6a7a202b274c3631ad", slug: "clock-webhook">
152
-
153
- webhook.run['result']
154
- #=> "2015-04-30 05:51:45 +0000"
155
- ```
156
-
157
- and curl
158
-
159
- ```bash
160
- $ curl "https://api.syncano.rocks/v1/instances//af248d3e8b92e6e7aaa42dfc41de80c66c90d620cbe3fcd19/webhooks/p/a20b0ae122b53b2f2c445f6a7a202b274c3631ad/"
161
- {"status": "success", "duration": 270, "result": "2015-04-30 06:11:08 +0000", "executed_at": "2015-04-30T06:11:08.607389Z"}
162
- ```
12
+ In case you need help working with the library - email us at libraries@syncano.com - we will be happy to help!
163
13
 
164
14
  ## Contributing
15
+ ---
165
16
 
166
17
  1. Fork it
167
18
  2. Create your feature branch (`git checkout -b my-new-feature`)
data/circle.yml CHANGED
@@ -2,7 +2,7 @@ machine:
2
2
  ruby:
3
3
  version: 2.1.5
4
4
  environment:
5
- API_ROOT: https://v4.hydraengine.com
5
+ API_ROOT: https://api.syncano.rocks
6
6
  dependencies:
7
7
  override:
8
8
  - gem install bundler -v 1.7
@@ -1,30 +1,42 @@
1
1
  $: << Dir.pwd
2
2
 
3
- require 'faraday'
4
- require 'active_attr/model'
5
- require 'active_attr/dirty'
6
- require 'active_attr/typecasting_override'
7
- require 'active_support/core_ext/hash/indifferent_access'
3
+ require 'active_attr'
4
+ require 'active_model'
5
+ require 'active_support/concern'
8
6
  require 'active_support/core_ext/class/attribute.rb'
7
+ require 'active_support/core_ext/hash/indifferent_access'
9
8
  require 'active_support/inflector'
9
+ require 'celluloid/future'
10
+ require 'celluloid/io'
11
+ require 'faraday'
12
+ require 'http'
13
+ require 'yaml'
14
+ require 'mimetype_fu'
15
+
16
+ require 'syncano/query_builder'
10
17
  require 'syncano/version'
11
18
  require 'syncano/api'
19
+ require 'syncano/api/endpoints'
12
20
  require 'syncano/connection'
13
21
  require 'syncano/schema'
14
22
  require 'syncano/scope'
23
+ require 'syncano/poller'
15
24
  require 'syncano/resources'
16
25
  require 'syncano/resources/base'
17
26
  require 'syncano/resources/collection'
27
+ require 'syncano/resources/paths'
28
+ require 'syncano/resources/resource_invalid'
18
29
  require 'syncano/resources/space'
30
+ require 'syncano/response'
31
+ require 'syncano/upload_io'
19
32
  require 'syncano/query_builder'
20
- require 'syncano/model/base'
21
33
 
22
34
  module Syncano
23
35
  class << self
24
36
  def connect(options = {})
25
37
  connection = Connection.new(
26
38
  options.reverse_merge(api_key: ENV['SYNCANO_API_KEY']))
27
- connection.authenticate! unless connection.authenticated?
39
+ connection.authenticate unless connection.authenticated?
28
40
 
29
41
  API.new connection
30
42
  end
@@ -35,6 +47,23 @@ module Syncano
35
47
  class RuntimeError < StandardError; end
36
48
 
37
49
  class HTTPError < StandardError
50
+ end
51
+
52
+ class NotFound < HTTPError
53
+ attr_accessor :path, :method_name
54
+
55
+ def initialize(path, method_name)
56
+ self.path = path
57
+ self.method_name = method_name
58
+ end
59
+
60
+ def inspect
61
+ %{#{self.class.name} path: "#{path}" method: "#{method_name}"}
62
+ end
63
+ end
64
+
65
+ class HTTPErrorWithBody < HTTPError
66
+
38
67
  attr_accessor :body, :original_response
39
68
 
40
69
  def initialize(body, original_response)
@@ -49,8 +78,8 @@ module Syncano
49
78
  alias :to_s :inspect
50
79
  end
51
80
 
52
- class ClientError < HTTPError; end
53
- class ServerError < HTTPError; end
81
+ class ClientError < HTTPErrorWithBody; end
82
+ class ServerError < HTTPErrorWithBody; end
54
83
 
55
84
  class UnsupportedStatusError < StandardError
56
85
  attr_accessor :original_response
@@ -62,7 +91,5 @@ module Syncano
62
91
  def inspect
63
92
  "The server returned unsupported status code #{original_response.status}"
64
93
  end
65
-
66
- alias :to_s :inspect
67
94
  end
68
95
  end
@@ -1,13 +1,31 @@
1
1
  module Syncano
2
2
  class API
3
+
3
4
  def initialize(connection)
4
5
  self.connection = connection
5
- schema = ::Syncano::Schema.new(connection)
6
- schema.process!
6
+
7
+ self.class.initialize(connection) unless self.class.initialized?
8
+ end
9
+
10
+ class << self
11
+ def initialize(connection)
12
+ endpoints = Schema::EndpointsWhitelist.new(Schema.new(connection))
13
+
14
+ resources_definitions = Resources.build_definitions(endpoints)
15
+
16
+ include Syncano::API::Endpoints.definition(resources_definitions)
17
+
18
+ self.initialized = true
19
+ end
20
+
21
+ def initialized?
22
+ initialized
23
+ end
7
24
  end
8
25
 
9
26
  private
10
27
 
11
28
  attr_accessor :connection
29
+ cattr_accessor :initialized
12
30
  end
13
31
  end
@@ -0,0 +1,17 @@
1
+ module Syncano
2
+ class API
3
+ module Endpoints
4
+ def self.definition(resources_definition)
5
+ Module.new do
6
+ resources_definition.each do |resource_definition|
7
+ resource_class = ::Syncano::Resources.define_resource_class(resource_definition)
8
+
9
+ define_method(resource_definition.name.tableize) do
10
+ ::Syncano::QueryBuilder.new(connection, resource_class)
11
+ end if resource_definition.top_level?
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -6,89 +6,81 @@ module Syncano
6
6
  AUTH_PATH = 'account/auth/'
7
7
  METHODS = Set.new [:get, :post, :put, :delete, :head, :patch, :options]
8
8
 
9
- def self.api_root
10
- ENV['API_ROOT']
9
+ attr_accessor :api_key
10
+ attr_accessor :user_key
11
+
12
+ class << self
13
+ def api_root
14
+ ENV['API_ROOT']
15
+ end
16
+ end
17
+
18
+ def http_fetcher
19
+ HttpFetcher.new api_key, user_key
11
20
  end
12
21
 
13
22
  def initialize(options = {})
14
23
  self.api_key = options[:api_key]
15
24
  self.email = options[:email]
16
25
  self.password = options[:password]
17
-
18
- # TODO: take it easy with SSL for development only, temporary solution
19
- self.conn = Faraday.new(self.class.api_root, ssl: { verify: false })
20
- conn.path_prefix = API_VERSION
21
- conn.request :url_encoded
26
+ self.user_key = options[:user_key]
27
+ self.conn = Faraday.new(self.class.api_root) do |faraday|
28
+ faraday.path_prefix = API_VERSION
29
+ faraday.request :multipart
30
+ faraday.request :url_encoded
31
+ faraday.adapter Faraday.default_adapter
32
+ end
22
33
  end
23
34
 
24
35
  def authenticated?
25
36
  !api_key.nil?
26
37
  end
27
38
 
28
- def authenticate(email, password)
29
- self.email = email
30
- self.password = password
31
- authenticate!
32
- end
33
-
34
- def authenticate!
35
- response = conn.post(AUTH_PATH, email: email, password: password)
36
- body = parse_response(response)
37
-
38
- case response
39
- when Status.successful
40
- self.api_key = body['account_key']
41
- when Status.client_error
42
- raise ClientError.new(body, response)
43
- end
39
+ def authenticate
40
+ api_key = request(:post, AUTH_PATH,
41
+ email: email,
42
+ password: password)['account_key']
43
+ self.api_key = api_key
44
44
  end
45
45
 
46
46
  def request(method, path, params = {})
47
47
  raise %{Unsupported method "#{method}"} unless METHODS.include? method
48
- conn.headers['X-API-KEY'] = api_key
48
+
49
+ conn.headers['X-API-KEY'] = api_key if api_key
50
+ conn.headers['X-USER-KEY'] = user_key if user_key
49
51
  conn.headers['User-Agent'] = "Syncano Ruby Gem #{Syncano::VERSION}"
50
- response = conn.send(method, path, params)
51
-
52
- case response
53
- when Status.no_content
54
- when Status.successful
55
- parse_response response
56
- when Status.client_error # TODO figure out if we want to raise an exception on not found or not
57
- raise ClientError.new(response.body, response)
58
- when Status.server_error
59
- raise ServerError.new(response.body, response)
60
- else
61
- raise UnsupportedStatusError.new(response)
62
- end
52
+
53
+ raw_response = conn.send(method, path, params)
54
+
55
+ Syncano::Response.handle ResponseWrapper.new(raw_response)
63
56
  end
64
57
 
65
58
  private
66
59
 
67
- def parse_response(response)
68
- JSON.parse(response.body)
69
- end
60
+ class ResponseWrapper < BasicObject
61
+ def initialize(response)
62
+ @response = response
63
+ end
70
64
 
71
- class Status
72
- class << self
73
- def successful
74
- ->(response) { (200...300).include? response.status }
75
- end
65
+ def method_missing(name, *args, &block)
66
+ @response.__send__(name, *args, &block)
67
+ end
76
68
 
77
- def client_error
78
- ->(response) { (400...500).include? response.status }
79
- end
69
+ def status
70
+ Status.new @response.status
71
+ end
80
72
 
81
- def no_content
82
- ->(response) { response.status == 204 }
83
- end
73
+ private
74
+
75
+ class Status
76
+ attr_accessor :code
84
77
 
85
- def server_error
86
- ->(response) { response.status >= 500 }
78
+ def initialize(code)
79
+ self.code = code
87
80
  end
88
81
  end
89
82
  end
90
83
 
91
- attr_accessor :api_key
92
84
  attr_accessor :api_root
93
85
  attr_accessor :email
94
86
  attr_accessor :password