syncano 4.0.0.alpha4 → 4.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/README.md +1 -13
- data/circle.yml +1 -1
- data/lib/active_attr/dirty.rb +26 -0
- data/lib/active_attr/typecasting/hash_typecaster.rb +34 -0
- data/lib/active_attr/typecasting_override.rb +29 -0
- data/lib/syncano.rb +9 -55
- data/lib/syncano/api.rb +2 -20
- data/lib/syncano/connection.rb +47 -48
- data/lib/syncano/model/associations.rb +121 -0
- data/lib/syncano/model/associations/base.rb +38 -0
- data/lib/syncano/model/associations/belongs_to.rb +30 -0
- data/lib/syncano/model/associations/has_many.rb +75 -0
- data/lib/syncano/model/associations/has_one.rb +22 -0
- data/lib/syncano/model/base.rb +257 -0
- data/lib/syncano/model/callbacks.rb +49 -0
- data/lib/syncano/model/scope_builder.rb +158 -0
- data/lib/syncano/query_builder.rb +7 -11
- data/lib/syncano/resources/base.rb +66 -91
- data/lib/syncano/schema.rb +159 -10
- data/lib/syncano/schema/attribute_definition.rb +0 -75
- data/lib/syncano/schema/resource_definition.rb +2 -24
- data/lib/syncano/version.rb +1 -1
- data/spec/integration/syncano_spec.rb +26 -268
- data/spec/spec_helper.rb +1 -3
- data/spec/unit/connection_spec.rb +74 -34
- data/spec/unit/query_builder_spec.rb +2 -2
- data/spec/unit/resources_base_spec.rb +64 -125
- data/spec/unit/schema/resource_definition_spec.rb +3 -24
- data/spec/unit/schema_spec.rb +55 -5
- data/spec/unit/syncano_spec.rb +9 -45
- data/syncano.gemspec +0 -5
- metadata +14 -87
- data/lib/syncano/api/endpoints.rb +0 -17
- data/lib/syncano/poller.rb +0 -55
- data/lib/syncano/resources.rb +0 -158
- data/lib/syncano/resources/paths.rb +0 -48
- data/lib/syncano/resources/resource_invalid.rb +0 -15
- data/lib/syncano/response.rb +0 -55
- data/lib/syncano/schema/endpoints_whitelist.rb +0 -40
- data/lib/syncano/upload_io.rb +0 -7
- data/spec/unit/resources/paths_spec.rb +0 -21
- data/spec/unit/response_spec.rb +0 -75
- data/spec/unit/schema/attribute_definition_spec.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 29165fa581b83fb889acd45abf0f730e047038a0
|
4
|
+
data.tar.gz: dc7f5504c6d99d6710c15216d8fa743dd245d6b9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb9d843a8201bddf2e3b302c70547c23d6155003bbd1ad2654ee922d6c83db58454bf0d9f604b38a09ab4f903c2eb6a823f533c26d9f2a4efe0ccdc6aef0798a
|
7
|
+
data.tar.gz: e8e95b8073d24d819ba9e070d5aa6513ca2ea6c4d30356378b172684a1b6a41a46e5a255dba1e6f2feacda10d5215182a903eeae228207d218056e08c2e89dfc
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
ruby-2.1.5
|
data/README.md
CHANGED
@@ -1,18 +1,6 @@
|
|
1
|
-
# Syncano 4.0
|
2
|
-
|
3
|
-
## Ruby QuickStart Guide
|
4
|
-
---
|
5
|
-
|
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))
|
7
|
-
|
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).
|
9
|
-
|
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
|
-
|
12
|
-
In case you need help working with the library - email us at libraries@syncano.com - we will be happy to help!
|
1
|
+
# Syncano 4.0 ruby gem
|
13
2
|
|
14
3
|
## Contributing
|
15
|
-
---
|
16
4
|
|
17
5
|
1. Fork it
|
18
6
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
data/circle.yml
CHANGED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_model/dirty'
|
3
|
+
require 'active_attr'
|
4
|
+
|
5
|
+
# Overwritting ActiveAttr module
|
6
|
+
module ActiveAttr
|
7
|
+
# Overwritting ActiveAttr::Dirty module
|
8
|
+
module Dirty
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
include ActiveModel::Dirty
|
11
|
+
|
12
|
+
# Class methods for ActiveAttr::Dirty module
|
13
|
+
module ClassMethods
|
14
|
+
# Overwritten attribute! method
|
15
|
+
# @param [Symbol] name
|
16
|
+
# @param [Hash] options
|
17
|
+
def attribute!(name, options = {})
|
18
|
+
super(name, options)
|
19
|
+
define_method("#{name}=") do |value|
|
20
|
+
send("#{name}_will_change!") unless value == read_attribute(name)
|
21
|
+
super(value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
2
|
+
|
3
|
+
module ActiveAttr
|
4
|
+
module Typecasting
|
5
|
+
# Typecasts an Object to a HashWithInddifferentAccess
|
6
|
+
#
|
7
|
+
# @example Usage
|
8
|
+
# typecaster = HashTypecaster.new
|
9
|
+
# typecaster.call([[:foo, :bar]]) #=> { foo: :bar }
|
10
|
+
#
|
11
|
+
# @since 0.5.0
|
12
|
+
class HashTypecaster
|
13
|
+
# Typecasts an object to a HashWithInddifferentAccess
|
14
|
+
#
|
15
|
+
# Attempts to convert using #to_h.
|
16
|
+
#
|
17
|
+
# @example Typecast an Array
|
18
|
+
# typecaster.call([[:foo, :bar]]) #=> { foo: :bar }
|
19
|
+
#
|
20
|
+
# @param [Object, #to_h] value The object to typecast
|
21
|
+
#
|
22
|
+
# @return [HashWithInddifferentAccess] The result of typecasting
|
23
|
+
#
|
24
|
+
# @since 0.5.0
|
25
|
+
def call(value)
|
26
|
+
if value.respond_to? :to_h
|
27
|
+
HashWithIndifferentAccess.new(value.to_h)
|
28
|
+
else
|
29
|
+
HashWithIndifferentAccess.new
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'active_attr/typecasting/big_decimal_typecaster'
|
2
|
+
require 'active_attr/typecasting/boolean'
|
3
|
+
require 'active_attr/typecasting/boolean_typecaster'
|
4
|
+
require 'active_attr/typecasting/date_time_typecaster'
|
5
|
+
require 'active_attr/typecasting/date_typecaster'
|
6
|
+
require 'active_attr/typecasting/float_typecaster'
|
7
|
+
require 'active_attr/typecasting/integer_typecaster'
|
8
|
+
require 'active_attr/typecasting/object_typecaster'
|
9
|
+
require 'active_attr/typecasting/string_typecaster'
|
10
|
+
require 'active_attr/typecasting/hash_typecaster'
|
11
|
+
require 'active_attr/typecasting/unknown_typecaster_error'
|
12
|
+
|
13
|
+
module ActiveAttr
|
14
|
+
module Typecasting
|
15
|
+
remove_const(:TYPECASTER_MAP) if defined?(TYPECASTER_MAP)
|
16
|
+
|
17
|
+
TYPECASTER_MAP = {
|
18
|
+
BigDecimal => BigDecimalTypecaster,
|
19
|
+
Boolean => BooleanTypecaster,
|
20
|
+
Date => DateTypecaster,
|
21
|
+
DateTime => DateTimeTypecaster,
|
22
|
+
Float => FloatTypecaster,
|
23
|
+
Integer => IntegerTypecaster,
|
24
|
+
Object => ObjectTypecaster,
|
25
|
+
String => StringTypecaster,
|
26
|
+
Hash => HashTypecaster
|
27
|
+
}.freeze
|
28
|
+
end
|
29
|
+
end
|
data/lib/syncano.rb
CHANGED
@@ -1,42 +1,28 @@
|
|
1
1
|
$: << Dir.pwd
|
2
2
|
|
3
|
-
require '
|
4
|
-
require '
|
5
|
-
require '
|
6
|
-
require '
|
3
|
+
require 'faraday'
|
4
|
+
require 'active_attr/model'
|
5
|
+
require 'active_attr/dirty'
|
6
|
+
require 'active_attr/typecasting_override'
|
7
7
|
require 'active_support/core_ext/hash/indifferent_access'
|
8
|
+
require 'active_support/core_ext/class/attribute.rb'
|
8
9
|
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'
|
17
10
|
require 'syncano/version'
|
18
11
|
require 'syncano/api'
|
19
|
-
require 'syncano/api/endpoints'
|
20
12
|
require 'syncano/connection'
|
21
13
|
require 'syncano/schema'
|
22
14
|
require 'syncano/scope'
|
23
|
-
require 'syncano/poller'
|
24
|
-
require 'syncano/resources'
|
25
15
|
require 'syncano/resources/base'
|
26
16
|
require 'syncano/resources/collection'
|
27
|
-
require 'syncano/resources/paths'
|
28
|
-
require 'syncano/resources/resource_invalid'
|
29
17
|
require 'syncano/resources/space'
|
30
|
-
require 'syncano/response'
|
31
|
-
require 'syncano/upload_io'
|
32
18
|
require 'syncano/query_builder'
|
19
|
+
require 'syncano/model/base'
|
33
20
|
|
34
21
|
module Syncano
|
35
22
|
class << self
|
36
23
|
def connect(options = {})
|
37
|
-
connection = Connection.new(
|
38
|
-
|
39
|
-
connection.authenticate unless connection.authenticated?
|
24
|
+
connection = Connection.new(options)
|
25
|
+
connection.authenticate! unless connection.authenticated?
|
40
26
|
|
41
27
|
API.new connection
|
42
28
|
end
|
@@ -46,24 +32,7 @@ module Syncano
|
|
46
32
|
|
47
33
|
class RuntimeError < StandardError; end
|
48
34
|
|
49
|
-
class
|
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
|
-
|
35
|
+
class ClientError < StandardError
|
67
36
|
attr_accessor :body, :original_response
|
68
37
|
|
69
38
|
def initialize(body, original_response)
|
@@ -77,19 +46,4 @@ module Syncano
|
|
77
46
|
|
78
47
|
alias :to_s :inspect
|
79
48
|
end
|
80
|
-
|
81
|
-
class ClientError < HTTPErrorWithBody; end
|
82
|
-
class ServerError < HTTPErrorWithBody; end
|
83
|
-
|
84
|
-
class UnsupportedStatusError < StandardError
|
85
|
-
attr_accessor :original_response
|
86
|
-
|
87
|
-
def initialize(original_response)
|
88
|
-
self.original_response = original_response
|
89
|
-
end
|
90
|
-
|
91
|
-
def inspect
|
92
|
-
"The server returned unsupported status code #{original_response.status}"
|
93
|
-
end
|
94
|
-
end
|
95
49
|
end
|
data/lib/syncano/api.rb
CHANGED
@@ -1,31 +1,13 @@
|
|
1
1
|
module Syncano
|
2
2
|
class API
|
3
|
-
|
4
3
|
def initialize(connection)
|
5
4
|
self.connection = connection
|
6
|
-
|
7
|
-
|
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
|
5
|
+
schema = ::Syncano::Schema.new(connection)
|
6
|
+
schema.process!
|
24
7
|
end
|
25
8
|
|
26
9
|
private
|
27
10
|
|
28
11
|
attr_accessor :connection
|
29
|
-
cattr_accessor :initialized
|
30
12
|
end
|
31
13
|
end
|
data/lib/syncano/connection.rb
CHANGED
@@ -6,81 +6,80 @@ module Syncano
|
|
6
6
|
AUTH_PATH = 'account/auth/'
|
7
7
|
METHODS = Set.new [:get, :post, :put, :delete, :head, :patch, :options]
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
class << self
|
13
|
-
def api_root
|
14
|
-
"https://api.syncano.io"
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def http_fetcher
|
19
|
-
HttpFetcher.new api_key, user_key
|
9
|
+
def self.api_root
|
10
|
+
ENV['API_ROOT']
|
20
11
|
end
|
21
12
|
|
22
13
|
def initialize(options = {})
|
23
14
|
self.api_key = options[:api_key]
|
24
15
|
self.email = options[:email]
|
25
16
|
self.password = options[:password]
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
faraday.adapter Faraday.default_adapter
|
32
|
-
end
|
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
|
33
22
|
end
|
34
23
|
|
35
24
|
def authenticated?
|
36
25
|
!api_key.nil?
|
37
26
|
end
|
38
27
|
|
39
|
-
def authenticate
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
self.api_key = api_key
|
28
|
+
def authenticate(email, password)
|
29
|
+
self.email = email
|
30
|
+
self.password = password
|
31
|
+
authenticate!
|
44
32
|
end
|
45
33
|
|
46
|
-
def
|
47
|
-
|
48
|
-
|
49
|
-
conn.headers['X-API-KEY'] = api_key if api_key
|
50
|
-
conn.headers['X-USER-KEY'] = user_key if user_key
|
51
|
-
conn.headers['User-Agent'] = "Syncano Ruby Gem #{Syncano::VERSION}"
|
52
|
-
|
53
|
-
raw_response = conn.send(method, path, params)
|
34
|
+
def authenticate!
|
35
|
+
response = conn.post(AUTH_PATH, email: email, password: password)
|
36
|
+
body = parse_response(response)
|
54
37
|
|
55
|
-
|
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
|
56
44
|
end
|
57
45
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
46
|
+
def request(method, path, params = {})
|
47
|
+
raise %{Unsupported method "#{method}"} unless METHODS.include? method
|
48
|
+
conn.headers['X-API-KEY'] = api_key
|
49
|
+
response = conn.send(method, path, params)
|
50
|
+
|
51
|
+
case response
|
52
|
+
when Status.no_content
|
53
|
+
when Status.successful
|
54
|
+
parse_response response
|
55
|
+
when Status.client_error # TODO figure out if we want to raise an excpetion on not found or not
|
56
|
+
raise ClientError.new(response.body, response)
|
63
57
|
end
|
58
|
+
end
|
64
59
|
|
65
|
-
|
66
|
-
@response.__send__(name, *args, &block)
|
67
|
-
end
|
60
|
+
private
|
68
61
|
|
69
|
-
|
70
|
-
|
71
|
-
|
62
|
+
def parse_response(response)
|
63
|
+
JSON.parse(response.body)
|
64
|
+
end
|
72
65
|
|
73
|
-
|
66
|
+
class Status
|
67
|
+
class << self
|
68
|
+
def successful
|
69
|
+
->(response) { (200...300).include? response.status }
|
70
|
+
end
|
74
71
|
|
75
|
-
|
76
|
-
|
72
|
+
def client_error
|
73
|
+
->(response) { (400...500).include? response.status }
|
74
|
+
end
|
77
75
|
|
78
|
-
def
|
79
|
-
|
76
|
+
def no_content
|
77
|
+
->(response) { response.status == 204 }
|
80
78
|
end
|
81
79
|
end
|
82
80
|
end
|
83
81
|
|
82
|
+
attr_accessor :api_key
|
84
83
|
attr_accessor :api_root
|
85
84
|
attr_accessor :email
|
86
85
|
attr_accessor :password
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'syncano/model/associations/belongs_to'
|
2
|
+
require 'syncano/model/associations/has_many'
|
3
|
+
require 'syncano/model/associations/has_one'
|
4
|
+
|
5
|
+
module Syncano
|
6
|
+
module Model
|
7
|
+
# Module with associations functionality for Syncano::Model
|
8
|
+
module Associations
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
included do
|
12
|
+
private
|
13
|
+
|
14
|
+
class_attribute :_associations
|
15
|
+
end
|
16
|
+
|
17
|
+
# Class methods for Syncano::Model::Associations module
|
18
|
+
module ClassMethods
|
19
|
+
# Lists hash with associations
|
20
|
+
# @return [HashWithIndifferentAccess]
|
21
|
+
def associations
|
22
|
+
self._associations ||= HashWithIndifferentAccess.new
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# Defines belongs_to association
|
28
|
+
# @param [Symbol] object_name
|
29
|
+
def belongs_to(object_name, options = {})
|
30
|
+
association = Syncano::Model::Association::BelongsTo.new(self, object_name, options)
|
31
|
+
associations[object_name] = association
|
32
|
+
|
33
|
+
define_method(object_name) do
|
34
|
+
association = self.class.associations[object_name]
|
35
|
+
id = send(association.foreign_key)
|
36
|
+
scope = scope_builder(association.associated_model).find(id)
|
37
|
+
end
|
38
|
+
|
39
|
+
define_method("#{object_name}=") do |object|
|
40
|
+
association = self.class.associations[object_name]
|
41
|
+
|
42
|
+
unless object.is_a?(association.associated_model)
|
43
|
+
raise "Object should be an instance of #{association.associated_model} class"
|
44
|
+
end
|
45
|
+
send("#{association.foreign_key}=", object.try(:id))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Defines has_one association
|
50
|
+
# @param [Symbol] object_name
|
51
|
+
def has_one(object_name, options = {})
|
52
|
+
association = Syncano::Model::Association::HasOne.new(self, object_name, options)
|
53
|
+
associations[object_name] = association
|
54
|
+
|
55
|
+
define_method(object_name) do
|
56
|
+
association = self.class.associations[object_name]
|
57
|
+
|
58
|
+
scope = scope_builder.new(association.associated_model)
|
59
|
+
scope.where("#{association.foreign_key} = ?", id).first if id.present?
|
60
|
+
end
|
61
|
+
|
62
|
+
define_method("#{object_name}=") do |object|
|
63
|
+
association = self.class.associations[object_name]
|
64
|
+
|
65
|
+
unless object.is_a?(association.associated_model)
|
66
|
+
raise "Object should be an instance of #{association.associated_model} class"
|
67
|
+
end
|
68
|
+
|
69
|
+
object.send("#{association.foreign_key}=", id)
|
70
|
+
object.save unless object.new_record?
|
71
|
+
object
|
72
|
+
end
|
73
|
+
|
74
|
+
define_method("build_#{object_name}") do |attributes = {}|
|
75
|
+
association = self.class.associations[object_name]
|
76
|
+
association.associated_model.new(attributes)
|
77
|
+
end
|
78
|
+
|
79
|
+
define_method("create_#{object_name}") do |attributes = {}|
|
80
|
+
association = self.class.associations[object_name]
|
81
|
+
association.associated_model.create(attributes)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Defines has_many association
|
86
|
+
# @param [Symbol] collection_name
|
87
|
+
def has_many(collection_name, options = {})
|
88
|
+
association = Syncano::Model::Association::HasMany.new(self, collection_name, options)
|
89
|
+
associations[collection_name] = association
|
90
|
+
|
91
|
+
define_method(collection_name) do
|
92
|
+
association = self.class.associations[collection_name]
|
93
|
+
association.scope_builder(self)
|
94
|
+
end
|
95
|
+
|
96
|
+
define_method("#{collection_name}=") do |collection|
|
97
|
+
association = self.class.associations[collection_name]
|
98
|
+
objects_ids = {}
|
99
|
+
|
100
|
+
collection.each do |object|
|
101
|
+
"Object should be an instance of #{association.associated_model} class" unless object.is_a?(association.associated_model)
|
102
|
+
objects_ids[object.id] = true
|
103
|
+
end
|
104
|
+
|
105
|
+
send(collection_name).all.each do |object|
|
106
|
+
unless objects_ids[object.id]
|
107
|
+
object.send("#{association.foreign_key}=", nil)
|
108
|
+
object.save unless object.new_record?
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
collection.each do |object|
|
113
|
+
object.send("#{association.foreign_key}=", id)
|
114
|
+
object.save unless object.new_record?
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|