syncano 4.0.0.alpha4 → 4.0.0.pre
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.
- 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
|