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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/README.md +1 -13
  4. data/circle.yml +1 -1
  5. data/lib/active_attr/dirty.rb +26 -0
  6. data/lib/active_attr/typecasting/hash_typecaster.rb +34 -0
  7. data/lib/active_attr/typecasting_override.rb +29 -0
  8. data/lib/syncano.rb +9 -55
  9. data/lib/syncano/api.rb +2 -20
  10. data/lib/syncano/connection.rb +47 -48
  11. data/lib/syncano/model/associations.rb +121 -0
  12. data/lib/syncano/model/associations/base.rb +38 -0
  13. data/lib/syncano/model/associations/belongs_to.rb +30 -0
  14. data/lib/syncano/model/associations/has_many.rb +75 -0
  15. data/lib/syncano/model/associations/has_one.rb +22 -0
  16. data/lib/syncano/model/base.rb +257 -0
  17. data/lib/syncano/model/callbacks.rb +49 -0
  18. data/lib/syncano/model/scope_builder.rb +158 -0
  19. data/lib/syncano/query_builder.rb +7 -11
  20. data/lib/syncano/resources/base.rb +66 -91
  21. data/lib/syncano/schema.rb +159 -10
  22. data/lib/syncano/schema/attribute_definition.rb +0 -75
  23. data/lib/syncano/schema/resource_definition.rb +2 -24
  24. data/lib/syncano/version.rb +1 -1
  25. data/spec/integration/syncano_spec.rb +26 -268
  26. data/spec/spec_helper.rb +1 -3
  27. data/spec/unit/connection_spec.rb +74 -34
  28. data/spec/unit/query_builder_spec.rb +2 -2
  29. data/spec/unit/resources_base_spec.rb +64 -125
  30. data/spec/unit/schema/resource_definition_spec.rb +3 -24
  31. data/spec/unit/schema_spec.rb +55 -5
  32. data/spec/unit/syncano_spec.rb +9 -45
  33. data/syncano.gemspec +0 -5
  34. metadata +14 -87
  35. data/lib/syncano/api/endpoints.rb +0 -17
  36. data/lib/syncano/poller.rb +0 -55
  37. data/lib/syncano/resources.rb +0 -158
  38. data/lib/syncano/resources/paths.rb +0 -48
  39. data/lib/syncano/resources/resource_invalid.rb +0 -15
  40. data/lib/syncano/response.rb +0 -55
  41. data/lib/syncano/schema/endpoints_whitelist.rb +0 -40
  42. data/lib/syncano/upload_io.rb +0 -7
  43. data/spec/unit/resources/paths_spec.rb +0 -21
  44. data/spec/unit/response_spec.rb +0 -75
  45. 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: 287313441ace89449cf5ee73f1898c02aa01e976
4
- data.tar.gz: 309aea98ddc29cc1f4ff5113a68e054a4316c62a
3
+ metadata.gz: 29165fa581b83fb889acd45abf0f730e047038a0
4
+ data.tar.gz: dc7f5504c6d99d6710c15216d8fa743dd245d6b9
5
5
  SHA512:
6
- metadata.gz: c33792ed712d4df96439c58d85199230fc20141c8ac6f91bf2352f90fb1e58248ad7bc819fa9a2c0584bc2c8ac80e5a404309e5ced9abd991c6dbe20f84b3246
7
- data.tar.gz: e9b22b114d55855ba23ce49bf0a343b0b30ae270558b87f699eddb3f3d361bd010a547c7ec8d0e63798c3d9ca27c634992860265ad38a307c1e3f90e290031de
6
+ metadata.gz: cb9d843a8201bddf2e3b302c70547c23d6155003bbd1ad2654ee922d6c83db58454bf0d9f604b38a09ab4f903c2eb6a823f533c26d9f2a4efe0ccdc6aef0798a
7
+ data.tar.gz: e8e95b8073d24d819ba9e070d5aa6513ca2ea6c4d30356378b172684a1b6a41a46e5a255dba1e6f2feacda10d5215182a903eeae228207d218056e08c2e89dfc
@@ -1 +1 @@
1
- 2.2.3
1
+ ruby-2.1.5
data/README.md CHANGED
@@ -1,18 +1,6 @@
1
- # Syncano 4.0 Ruby Gem
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
@@ -2,7 +2,7 @@ machine:
2
2
  ruby:
3
3
  version: 2.1.5
4
4
  environment:
5
- API_ROOT: https://api.syncano.rocks
5
+ API_ROOT: https://v4.hydraengine.com
6
6
  dependencies:
7
7
  override:
8
8
  - gem install bundler -v 1.7
@@ -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
@@ -1,42 +1,28 @@
1
1
  $: << Dir.pwd
2
2
 
3
- require 'active_attr'
4
- require 'active_model'
5
- require 'active_support/concern'
6
- require 'active_support/core_ext/class/attribute.rb'
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
- options.reverse_merge(api_key: ENV['SYNCANO_API_KEY']))
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 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
-
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
@@ -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
- 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
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
@@ -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
- attr_accessor :api_key
10
- attr_accessor :user_key
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
- 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
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
- api_key = request(:post, AUTH_PATH,
41
- email: email,
42
- password: password)['account_key']
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 request(method, path, params = {})
47
- raise %{Unsupported method "#{method}"} unless METHODS.include? method
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
- Syncano::Response.handle ResponseWrapper.new(raw_response)
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
- private
59
-
60
- class ResponseWrapper < BasicObject
61
- def initialize(response)
62
- @response = response
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
- def method_missing(name, *args, &block)
66
- @response.__send__(name, *args, &block)
67
- end
60
+ private
68
61
 
69
- def status
70
- Status.new @response.status
71
- end
62
+ def parse_response(response)
63
+ JSON.parse(response.body)
64
+ end
72
65
 
73
- private
66
+ class Status
67
+ class << self
68
+ def successful
69
+ ->(response) { (200...300).include? response.status }
70
+ end
74
71
 
75
- class Status
76
- attr_accessor :code
72
+ def client_error
73
+ ->(response) { (400...500).include? response.status }
74
+ end
77
75
 
78
- def initialize(code)
79
- self.code = code
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