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
@@ -0,0 +1,38 @@
1
+ require 'syncano/model/scope_builder'
2
+
3
+ module Syncano
4
+ module Model
5
+ # Module with associations functionality for Syncano::ActiveRecord
6
+ module Association
7
+ # Base class for all associations
8
+ class Base
9
+ # Constructor for association
10
+ # @param [Class] source_model
11
+ # @param [Symbol] name
12
+ def initialize(source_model, name, options = {})
13
+ self.source_model = source_model
14
+ self.associated_model = name.to_s.classify.constantize
15
+ self.foreign_key = options[:foreign_key] || source_model.name.foreign_key
16
+ end
17
+
18
+ # Checks if association is belongs_to type
19
+ # @return [FalseClass]
20
+ def belongs_to?
21
+ false
22
+ end
23
+
24
+ # Checks if association is has_one type
25
+ # @return [FalseClass]
26
+ def has_one?
27
+ false
28
+ end
29
+
30
+ # Checks if association is has_many type
31
+ # @return [FalseClass]
32
+ def has_many?
33
+ false
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,30 @@
1
+ require 'syncano/model/associations/base'
2
+
3
+ module Syncano
4
+ module Model
5
+ module Association
6
+ # Class for belongs to association
7
+ class BelongsTo < Syncano::Model::Association::Base
8
+ attr_reader :associated_model, :foreign_key, :source_model
9
+
10
+ # Constructor for belongs_to association
11
+ # @param [Class] source_model
12
+ # @param [Symbol] name
13
+ def initialize(source_model, name, options = {})
14
+ super
15
+ self.foreign_key = options[:foreign_key] || associated_model.name.foreign_key
16
+ end
17
+
18
+ # Checks if association is belongs_to type
19
+ # @return [TrueClass]
20
+ def belongs_to?
21
+ true
22
+ end
23
+
24
+ private
25
+
26
+ attr_writer :associated_model, :foreign_key, :source_model
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,75 @@
1
+ require 'syncano/model/associations/base'
2
+
3
+ module Syncano
4
+ module Model
5
+ module Association
6
+ # Class for has many association
7
+ class HasMany < Syncano::Model::Association::Base
8
+ attr_reader :associated_model, :foreign_key, :source_model
9
+
10
+ # Checks if association is has_many type
11
+ # @return [TrueClass]
12
+ def has_many?
13
+ true
14
+ end
15
+
16
+ # Returns new associaton object with source object set
17
+ # @param [Object] source
18
+ # @return [Syncano::ActiveRecord::Association::HasMany]
19
+ def scope_builder(source)
20
+ association = self.dup
21
+ association.source = source
22
+ association
23
+ end
24
+
25
+ # Builds new associated object
26
+ # @return [Object]
27
+ def build(attributes = {})
28
+ new(attributes)
29
+ end
30
+
31
+ def new(attributes = {})
32
+ associated_model.new(attributes.merge(foreign_key => source.id))
33
+ end
34
+
35
+ # Creates new associated object
36
+ # @return [Object]
37
+ def create(attributes = {})
38
+ associated_model.create(attributes.merge(foreign_key => source.id))
39
+ end
40
+
41
+ # Adds object to the related collection by setting foreign key
42
+ # @param [Object] object
43
+ # @return [Object]
44
+ def <<(object)
45
+ "Object should be an instance of #{associated_model} class" unless object.is_a?(associated_model)
46
+
47
+ object.send("#{foreign_key}=", source.id)
48
+ object.save unless object.new_record?
49
+ object
50
+ end
51
+
52
+ protected
53
+
54
+ attr_accessor :source
55
+
56
+ private
57
+
58
+ attr_writer :associated_model, :foreign_key, :source_model
59
+
60
+ # Overwritten method_missing for handling scope methods
61
+ # @param [String] name
62
+ # @param [Array] args
63
+ def method_missing(name, *args)
64
+ scope_builder = Syncano::Model::ScopeBuilder.new(associated_model).where("#{foreign_key} = ?", source.id)
65
+
66
+ if scope_builder.respond_to?(name) || !source.scopes[name].nil?
67
+ scope_builder.send(name, *args)
68
+ else
69
+ super
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,22 @@
1
+ require 'syncano/model/associations/base'
2
+
3
+ module Syncano
4
+ module Model
5
+ module Association
6
+ # Class for has one association
7
+ class HasOne < Syncano::Model::Association::Base
8
+ attr_reader :associated_model, :foreign_key, :source_model
9
+
10
+ # Checks if association is has_one type
11
+ # @return [TrueClass]
12
+ def has_one?
13
+ true
14
+ end
15
+
16
+ private
17
+
18
+ attr_writer :associated_model, :foreign_key, :source_model
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,257 @@
1
+ require 'syncano/model/scope_builder'
2
+ require 'syncano/model/associations'
3
+ require 'syncano/model/callbacks'
4
+
5
+ module Syncano
6
+ # Scope for modules and classes integrating ActiveRecord functionality
7
+ module Model
8
+ # Class for integrating ActiveRecord functionality
9
+ class Base
10
+ include ActiveAttr::Model
11
+ include ActiveAttr::Dirty
12
+
13
+ include ActiveModel::ForbiddenAttributesProtection
14
+ include Syncano::Model::Associations
15
+ include Syncano::Model::Callbacks
16
+
17
+ attribute :id, type: Integer
18
+ attribute :created_at, type: DateTime
19
+ attribute :updated_at, type: DateTime
20
+
21
+ # Constructor for model
22
+ # @param [Hash] params
23
+ def initialize(params = {})
24
+ if params.is_a?(Syncano::Resources::Object)
25
+ self.syncano_object = params
26
+ self.attributes = syncano_object_merged_attributes
27
+ mark_as_saved!
28
+ else
29
+ self.syncano_object = self.class.syncano_class.objects.new
30
+ self.attributes = params
31
+ end
32
+ end
33
+
34
+ # Gets collection with all objects
35
+ # @return [Array]
36
+ def self.all
37
+ scope_builder.all
38
+ end
39
+
40
+ # Returns first object or collection of first x objects
41
+ # @param [Integer] amount
42
+ # @return [Object, Array]
43
+ def self.first(amount = nil)
44
+ scope_builder.first(amount)
45
+ end
46
+
47
+ # Returns last object or collection of last x objects
48
+ # @param [Integer] amount
49
+ # @return [Object, Array]
50
+ def self.last(amount = nil)
51
+ scope_builder.last(amount)
52
+ end
53
+
54
+ # Returns scope builder with condition passed as arguments
55
+ # @param [String] condition
56
+ # @param [Array] params
57
+ # @return [Syncano::ActiveRecord::ScopeBuilder]
58
+ def self.where(condition, *params)
59
+ scope_builder.where(condition, *params)
60
+ end
61
+
62
+ # Returns scope builder with order passed as first argument
63
+ # @param [String] order
64
+ # @return [Syncano::ActiveRecord::ScopeBuilder]
65
+ def self.order(order)
66
+ scope_builder.order(order)
67
+ end
68
+
69
+ # Returns one object found by id
70
+ # @param [Integer] id
71
+ # @return [Object]
72
+ def self.find(id)
73
+ scope_builder.find(id)
74
+ end
75
+
76
+ def reload!
77
+ syncano_object.reload!
78
+ self.attributes = syncano_object_merged_attributes
79
+ mark_as_saved!
80
+ self
81
+ end
82
+
83
+ # Creates new object with specified attributes
84
+ # @param [Hash] attributes
85
+ # @return [Object]
86
+ def self.create(attributes)
87
+ new_object = self.new(attributes)
88
+ new_object.save
89
+ new_object
90
+ end
91
+
92
+ # Saves object in Syncano
93
+ # @return [TrueClass, FalseClass]
94
+ def save
95
+ if valid?
96
+ was_persisted = persisted?
97
+
98
+ process_callbacks(:before_save)
99
+ process_callbacks(was_persisted ? :before_update : :before_create)
100
+
101
+ syncano_object.custom_attributes = attributes_to_sync
102
+ syncano_object.save
103
+ self.attributes = syncano_object_merged_attributes
104
+ mark_as_saved!
105
+
106
+ process_callbacks(was_persisted ? :after_update : :after_create)
107
+ process_callbacks(:after_save)
108
+ end
109
+
110
+ saved?
111
+ end
112
+
113
+ def self.syncano_class
114
+ syncano_class
115
+ end
116
+
117
+ # Updates object with specified attributes
118
+ # @param [Hash] attributes
119
+ # @return [TrueClass, FalseClass]
120
+ def update_attributes(attributes)
121
+ self.attributes = attributes
122
+ self.save
123
+ end
124
+
125
+ # Returns scope builder with limit parameter set to parameter
126
+ # @param [Integer] amount
127
+ # @return [Syncano::ActiveRecord::ScopeBuilder]
128
+ def self.limit(amount)
129
+ scope_builder.limit(amount)
130
+ end
131
+
132
+ # Returns hash with scopes
133
+ # @return [HashWithIndifferentAccess]
134
+ def self.scopes
135
+ self._scopes ||= HashWithIndifferentAccess.new
136
+ end
137
+
138
+ # Overwritten equality operator
139
+ # @param [Object] object
140
+ # @return [TrueClass, FalseClass]
141
+ def ==(object)
142
+ self.class == object.class && id == object.id
143
+ end
144
+
145
+ # Performs validations
146
+ # @return [TrueClass, FalseClass]
147
+ def valid?
148
+ process_callbacks(:before_validation)
149
+ process_callbacks(:after_validation) if result = super
150
+ result
151
+ end
152
+
153
+ # Deletes object from Syncano
154
+ # @return [TrueClass, FalseClass]
155
+ def destroy
156
+ process_callbacks(:before_destroy)
157
+ syncano_object.destroy
158
+ process_callbacks(:after_destroy) if syncano_object.destroyed?
159
+ end
160
+
161
+ def destroyed?
162
+ syncano_object.destroyed?
163
+ end
164
+
165
+ # Checks if object has not been saved in Syncano yet
166
+ # @return [TrueClass, FalseClass]
167
+ def new_record?
168
+ !persisted?
169
+ end
170
+
171
+ # Checks if object has been already saved in Syncano
172
+ # @return [TrueClass, FalseClass]
173
+ def persisted?
174
+ !syncano_object.new_record?
175
+ end
176
+
177
+ def saved?
178
+ !new_record? && !changed?
179
+ end
180
+
181
+ private
182
+
183
+ class_attribute :syncano_class, :_scopes
184
+ attr_accessor :syncano_object
185
+
186
+ def mark_as_saved!
187
+ raise(Syncano::Error.new('primary key is blank')) if new_record?
188
+
189
+ @previously_changed = changes
190
+ @changed_attributes.clear
191
+ self
192
+ end
193
+
194
+ # Setter for scopes attribute
195
+ def self.scopes=(hash)
196
+ self._scopes = hash
197
+ end
198
+
199
+ # Returns scope builder for current model
200
+ # @return [Syncano::ActiveRecord::ScopeBuilder]
201
+ def self.scope_builder
202
+ Syncano::Model::ScopeBuilder.new(self)
203
+ end
204
+
205
+ # Defines model scope
206
+ # @param [Symbol] name
207
+ # @param [Proc] procedure
208
+ def self.scope(name, procedure)
209
+ scopes[name] = procedure
210
+ end
211
+
212
+ # Overwritten method_missing for handling calling defined scopes
213
+ # @param [String] name
214
+ # @param [Array] args
215
+ def self.method_missing(name, *args)
216
+ if scopes[name].nil?
217
+ super
218
+ else
219
+ scope_builder.send(name.to_sym, *args)
220
+ end
221
+ end
222
+
223
+ # Returns scope builder for specified class
224
+ # @return [Syncano::ActiveRecord::ScopeBuilder]
225
+ def scope_builder(object_class)
226
+ Syncano::Model::ScopeBuilder.new(object_class)
227
+ end
228
+
229
+ def attributes_to_sync
230
+ attributes_names = self.class.attributes_to_sync
231
+ attributes.select{ |name, value| attributes_names.include?(name.to_sym) }
232
+ end
233
+
234
+ def self.attributes_to_sync
235
+ syncano_class.schema.collect{ |attribute| attribute[:name].to_sym }
236
+ end
237
+
238
+ def syncano_object_merged_attributes
239
+ syncano_object.attributes.except(:custom_attributes).merge(syncano_object.custom_attributes)
240
+ end
241
+
242
+ def self.inherited(child_class)
243
+ # Load schema and generate attributes
244
+ child_class_name = child_class.name.demodulize.tableize.singularize
245
+ syncano_class = MODEL_SCHEMA.find{ |syncano_class| syncano_class.name == child_class_name }
246
+
247
+ syncano_class.schema.each do |attribute_schema|
248
+ attribute attribute_schema['name'], type: String
249
+ end
250
+
251
+ child_class.syncano_class = syncano_class
252
+
253
+ super
254
+ end
255
+ end
256
+ end
257
+ end
@@ -0,0 +1,49 @@
1
+ module Syncano
2
+ module Model
3
+ # Module with callbacks functionality for Syncano::ActiveRecord
4
+ module Callbacks
5
+ extend ActiveSupport::Concern
6
+
7
+ # Class methods for Syncano::ActiveRecord::Callbacks module
8
+ module ClassMethods
9
+ private
10
+
11
+ [:validation, :save, :create, :update, :destroy].each do |action|
12
+ [:before, :after].each do |type|
13
+ define_method("prepend_#{type}_#{action}") do |argument|
14
+ send("#{type}_#{action}_callbacks").unshift(argument)
15
+ end
16
+
17
+ define_method("#{type}_#{action}") do |argument|
18
+ send("#{type}_#{action}_callbacks") << argument
19
+ end
20
+ end
21
+ end
22
+
23
+ def inherited(subclass)
24
+ # Initializes chains for all types of callbacks
25
+ [:validation, :save, :create, :update, :destroy].each do |action|
26
+ [:before, :after].each do |type|
27
+ chain_name = "#{type}_#{action}_callbacks"
28
+
29
+ subclass.class_attribute chain_name
30
+ subclass.send("#{chain_name}=", [])
31
+ end
32
+ end
33
+
34
+ super
35
+ end
36
+ end
37
+
38
+ # Processes callbacks with specified type
39
+ # @param [Symbol, String] type
40
+ def process_callbacks(type)
41
+ if respond_to?("#{type}_callbacks")
42
+ send("#{type}_callbacks").each do |callback_name|
43
+ send(callback_name)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end