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