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