syncano 4.0.0.alpha1 → 4.0.0.alpha2

Sign up to get free protection for your applications and to get access to all the features.
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