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.
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