syncano 3.1.4 → 4.0.0.alpha
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 +7 -0
- data/.gitignore +4 -1
- data/.rspec +2 -0
- data/.rubocop.yml +3 -0
- data/.ruby-version +1 -0
- data/Gemfile +25 -1
- data/Guardfile +22 -4
- data/README.md +68 -447
- data/Rakefile +48 -5
- data/circle.yml +10 -0
- data/lib/active_attr/dirty.rb +3 -17
- data/lib/active_attr/typecasting/hash_typecaster.rb +34 -0
- data/lib/active_attr/typecasting_override.rb +29 -0
- data/lib/syncano.rb +53 -92
- data/lib/syncano/api.rb +13 -0
- data/lib/syncano/connection.rb +97 -0
- data/lib/syncano/model/associations.rb +121 -0
- data/lib/syncano/{active_record/association → model/associations}/base.rb +5 -5
- data/lib/syncano/{active_record/association → model/associations}/belongs_to.rb +6 -6
- data/lib/syncano/{active_record/association → model/associations}/has_many.rb +15 -9
- data/lib/syncano/{active_record/association → model/associations}/has_one.rb +4 -4
- data/lib/syncano/model/base.rb +257 -0
- data/lib/syncano/{active_record → model}/callbacks.rb +16 -13
- data/lib/syncano/{active_record → model}/scope_builder.rb +53 -69
- data/lib/syncano/query_builder.rb +19 -129
- data/lib/syncano/resources.rb +126 -0
- data/lib/syncano/resources/base.rb +304 -300
- data/lib/syncano/resources/collection.rb +19 -223
- data/lib/syncano/resources/space.rb +29 -0
- data/lib/syncano/schema.rb +86 -0
- data/lib/syncano/schema/attribute_definition.rb +83 -0
- data/lib/syncano/schema/resource_definition.rb +36 -0
- data/lib/syncano/scope.rb +10 -0
- data/lib/syncano/version.rb +3 -4
- data/spec/integration/syncano_spec.rb +228 -0
- data/spec/spec_helper.rb +15 -9
- data/spec/unit/api_spec.rb +5 -0
- data/spec/unit/connection_spec.rb +137 -0
- data/spec/unit/query_builder_spec.rb +75 -0
- data/spec/unit/resources/collection_spec.rb +36 -0
- data/spec/unit/resources/space_spec.rb +28 -0
- data/spec/unit/resources_base_spec.rb +185 -0
- data/spec/unit/schema/attribute_definition_spec.rb +18 -0
- data/spec/unit/schema/resource_definition_spec.rb +25 -0
- data/spec/unit/schema_spec.rb +3532 -0
- data/spec/unit/syncano_spec.rb +63 -0
- data/syncano.gemspec +8 -14
- metadata +85 -210
- data/lib/generators/syncano/install_generator.rb +0 -17
- data/lib/generators/syncano/templates/initializers/syncano.rb +0 -7
- data/lib/syncano/active_record/associations.rb +0 -112
- data/lib/syncano/active_record/base.rb +0 -318
- data/lib/syncano/batch_queue.rb +0 -58
- data/lib/syncano/batch_queue_element.rb +0 -33
- data/lib/syncano/clients/base.rb +0 -123
- data/lib/syncano/clients/rest.rb +0 -79
- data/lib/syncano/clients/sync.rb +0 -164
- data/lib/syncano/errors.rb +0 -17
- data/lib/syncano/jimson_client.rb +0 -66
- data/lib/syncano/packets/auth.rb +0 -27
- data/lib/syncano/packets/base.rb +0 -70
- data/lib/syncano/packets/call.rb +0 -34
- data/lib/syncano/packets/call_response.rb +0 -33
- data/lib/syncano/packets/error.rb +0 -19
- data/lib/syncano/packets/message.rb +0 -30
- data/lib/syncano/packets/notification.rb +0 -39
- data/lib/syncano/packets/ping.rb +0 -12
- data/lib/syncano/resources/admin.rb +0 -26
- data/lib/syncano/resources/api_key.rb +0 -108
- data/lib/syncano/resources/data_object.rb +0 -316
- data/lib/syncano/resources/folder.rb +0 -88
- data/lib/syncano/resources/notifications/base.rb +0 -103
- data/lib/syncano/resources/notifications/create.rb +0 -20
- data/lib/syncano/resources/notifications/destroy.rb +0 -20
- data/lib/syncano/resources/notifications/message.rb +0 -9
- data/lib/syncano/resources/notifications/update.rb +0 -24
- data/lib/syncano/resources/project.rb +0 -96
- data/lib/syncano/resources/role.rb +0 -11
- data/lib/syncano/resources/subscription.rb +0 -12
- data/lib/syncano/resources/user.rb +0 -65
- data/lib/syncano/response.rb +0 -22
- data/lib/syncano/sync_connection.rb +0 -133
- data/spec/admins_spec.rb +0 -16
- data/spec/api_keys_spec.rb +0 -34
- data/spec/collections_spec.rb +0 -67
- data/spec/data_objects_spec.rb +0 -113
- data/spec/folders_spec.rb +0 -39
- data/spec/notifications_spec.rb +0 -43
- data/spec/projects_spec.rb +0 -35
- data/spec/roles_spec.rb +0 -13
- data/spec/sync_resources_spec.rb +0 -35
- data/spec/syncano_spec.rb +0 -9
@@ -1,152 +1,42 @@
|
|
1
|
-
|
2
|
-
# Proxy class for creating proper requests to api through ActiveRecord pattern
|
1
|
+
module Syncano
|
3
2
|
class QueryBuilder
|
4
|
-
|
5
|
-
|
6
|
-
# @param [String] resource_class
|
7
|
-
# @param [Hash] scope_parameters
|
8
|
-
def initialize(client, resource_class, scope_parameters = {})
|
9
|
-
self.client = client
|
3
|
+
def initialize(connection, resource_class, scope_parameters = {})
|
4
|
+
self.connection = connection
|
10
5
|
self.resource_class = resource_class
|
11
6
|
self.scope_parameters = scope_parameters
|
12
7
|
end
|
13
8
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
# @return [Syncano::BatchQueueElement]
|
18
|
-
def batch
|
19
|
-
::Syncano::BatchQueueElement.new(self)
|
9
|
+
def all(query_params = {})
|
10
|
+
query_params[:query] = query_params[:query].to_json if query_params[:query].try(:any?)
|
11
|
+
resource_class.all(connection, scope_parameters, query_params)
|
20
12
|
end
|
21
13
|
|
22
|
-
|
23
|
-
|
24
|
-
# @return [Array] collection of Syncano::Resources::Base objects
|
25
|
-
def all(conditions = {})
|
26
|
-
resource_class.all(client, conditions.merge(scope_parameters))
|
14
|
+
def first(query_params = {})
|
15
|
+
resource_class.first(connection, scope_parameters, query_params)
|
27
16
|
end
|
28
17
|
|
29
|
-
|
30
|
-
|
31
|
-
# @return [Integer]
|
32
|
-
def count(conditions = {})
|
33
|
-
resource_class.count(client, conditions.merge(scope_parameters))
|
18
|
+
def last(query_params = {})
|
19
|
+
resource_class.last(connection, scope_parameters, query_params)
|
34
20
|
end
|
35
21
|
|
36
|
-
|
37
|
-
|
38
|
-
# @return [Syncano::Resources::Base]
|
39
|
-
def first(conditions = {})
|
40
|
-
all(conditions).first
|
22
|
+
def find(key = nil)
|
23
|
+
resource_class.find(connection, scope_parameters, key)
|
41
24
|
end
|
42
25
|
|
43
|
-
# Returns last element from all returned by "all" method
|
44
|
-
# @param [Hash] conditions
|
45
|
-
# @return [Syncano::Resources::Base]
|
46
|
-
def last(conditions = {})
|
47
|
-
all(conditions).last
|
48
|
-
end
|
49
|
-
|
50
|
-
# Proxy for calling "find" method on the resource object
|
51
|
-
# @param [Integer, String] key
|
52
|
-
# @param [Hash] conditions
|
53
|
-
# @return [Syncano::Resources::Base]
|
54
|
-
def find(key = nil, conditions = {})
|
55
|
-
resource_class.find(client, key, scope_parameters, conditions)
|
56
|
-
end
|
57
|
-
|
58
|
-
# Proxy for calling "find_by_key" method on the resource object
|
59
|
-
# @param [String] key
|
60
|
-
# @param [Hash] conditions
|
61
|
-
# @return [Syncano::Resources::Base]
|
62
|
-
def find_by_key(key, conditions = {})
|
63
|
-
resource_class.find_by_key(client, key, scope_parameters, conditions)
|
64
|
-
end
|
65
|
-
|
66
|
-
# Proxy for calling "find_by_name" method on the resource object
|
67
|
-
# @param [String] name
|
68
|
-
# @param [Hash] conditions
|
69
|
-
# @return [Syncano::Resources::Base]
|
70
|
-
def find_by_name(name, conditions = {})
|
71
|
-
resource_class.find_by_name(client, name, scope_parameters, conditions)
|
72
|
-
end
|
73
|
-
|
74
|
-
# Proxy for calling "find_by_email" method on the resource object
|
75
|
-
# @param [String] email
|
76
|
-
# @param [Hash] conditions
|
77
|
-
# @return [Syncano::Resources::Base]
|
78
|
-
def find_by_email(email, conditions = {})
|
79
|
-
resource_class.find_by_email(client, email, scope_parameters, conditions)
|
80
|
-
end
|
81
|
-
|
82
|
-
# Proxy for calling "new" method on the resource object
|
83
|
-
# @param [Hash] attributes
|
84
|
-
# @return [Syncano::Resources::Base]
|
85
26
|
def new(attributes = {})
|
86
|
-
resource_class.new(
|
87
|
-
end
|
88
|
-
|
89
|
-
# Proxy for calling "create" method on the resource object
|
90
|
-
# @param [Hash] attributes
|
91
|
-
# @return [Syncano::Resources::Base]
|
92
|
-
def create(attributes)
|
93
|
-
resource_class.create(client, attributes.merge(scope_parameters))
|
94
|
-
end
|
95
|
-
|
96
|
-
# Proxy for calling "batch_create" method on the resource object
|
97
|
-
# @param [Jimson::Client] batch_client
|
98
|
-
# @param [Hash] attributes
|
99
|
-
# @return [Syncano::Response]
|
100
|
-
def batch_create(batch_client, attributes)
|
101
|
-
resource_class.batch_create(batch_client, client, attributes.merge(scope_parameters))
|
102
|
-
end
|
103
|
-
|
104
|
-
# Proxy for calling "copy" method on the resource object
|
105
|
-
# @param [Array] ids
|
106
|
-
# @return [Array] collection of Syncano::Resource objects
|
107
|
-
def copy(ids)
|
108
|
-
resource_class.copy(client, scope_parameters, ids)
|
109
|
-
end
|
110
|
-
|
111
|
-
# Proxy for calling "batch_copy" method on the resource object
|
112
|
-
# @param [Jimson::Client] batch_client
|
113
|
-
# @param [Array] ids
|
114
|
-
# @return [Syncano::Response]
|
115
|
-
def batch_copy(batch_client, ids)
|
116
|
-
resource_class.batch_copy(batch_client, scope_parameters, ids)
|
117
|
-
end
|
118
|
-
|
119
|
-
# Proxy for calling "move" method on the resource object
|
120
|
-
# @param [Array] ids
|
121
|
-
# @param [Hash] conditions
|
122
|
-
# @param [String] new_folder
|
123
|
-
# @param [String] new_state
|
124
|
-
# @return [Array] collection of Syncano::Resource objects
|
125
|
-
def move(ids, conditions = {}, new_folder = nil, new_state = nil)
|
126
|
-
resource_class.move(client, scope_parameters, ids, conditions, new_folder, new_state)
|
27
|
+
resource_class.new(connection, scope_parameters, attributes)
|
127
28
|
end
|
128
29
|
|
129
|
-
|
130
|
-
|
131
|
-
# @param [Array] ids
|
132
|
-
# @param [Hash] conditions
|
133
|
-
# @param [String] new_folder
|
134
|
-
# @param [String] new_state
|
135
|
-
# @return [Syncano::Response]
|
136
|
-
def batch_move(batch_client, ids, conditions = {}, new_folder = nil, new_state = nil)
|
137
|
-
resource_class.batch_move(batch_client, scope_parameters, ids, conditions, new_folder, new_state)
|
30
|
+
def create(attributes = {})
|
31
|
+
resource_class.create(connection, scope_parameters, attributes)
|
138
32
|
end
|
139
33
|
|
140
|
-
|
141
|
-
|
142
|
-
# @param [String] password
|
143
|
-
# @return [Array] collection of Syncano::Resource objects
|
144
|
-
def login(username = nil, password = nil)
|
145
|
-
resource_class.login(client, username, password)
|
34
|
+
def space(at, options = {})
|
35
|
+
Syncano::Resources::Space.new(at, self, options)
|
146
36
|
end
|
147
37
|
|
148
38
|
private
|
149
39
|
|
150
|
-
attr_accessor :
|
40
|
+
attr_accessor :connection, :resource_class, :scope_parameters
|
151
41
|
end
|
152
|
-
end
|
42
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'dirty_hashy'
|
2
|
+
|
3
|
+
module Syncano
|
4
|
+
module Resources
|
5
|
+
class << self
|
6
|
+
def define_resource_class(resource_definition)
|
7
|
+
const_set resource_definition.name, new_resource_class(resource_definition)
|
8
|
+
end
|
9
|
+
|
10
|
+
def new_resource_class(definition)
|
11
|
+
attributes_definitions = definition.attributes
|
12
|
+
|
13
|
+
::Class.new(::Syncano::Resources::Base) do
|
14
|
+
self.create_writable_attributes = []
|
15
|
+
self.update_writable_attributes = []
|
16
|
+
|
17
|
+
attributes_definitions.each do |attribute_definition|
|
18
|
+
attribute attribute_definition.name,
|
19
|
+
type: attribute_definition.type,
|
20
|
+
default: attribute_definition.default,
|
21
|
+
force_default: attribute_definition.force_default?
|
22
|
+
|
23
|
+
if attribute_definition.required?
|
24
|
+
validates attribute_definition.name, presence: true
|
25
|
+
end
|
26
|
+
|
27
|
+
validates attribute_definition.name, length: attribute_definition.required_length
|
28
|
+
|
29
|
+
if inclusion = attribute_definition.required_values_inclusion
|
30
|
+
validates attribute_definition.name, inclusion: inclusion
|
31
|
+
end
|
32
|
+
|
33
|
+
self.create_writable_attributes << attribute_definition.name.to_sym if attribute_definition.writable?
|
34
|
+
self.update_writable_attributes << attribute_definition.name.to_sym if attribute_definition.updatable?
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
if definition.name == 'Object' #TODO: extract to a separate module + spec
|
39
|
+
def save(options = {})
|
40
|
+
options.assert_valid_keys :overwrite
|
41
|
+
overwrite = options[:overwrite] == true
|
42
|
+
|
43
|
+
if new_record? || !overwrite
|
44
|
+
super()
|
45
|
+
else
|
46
|
+
response = connection.request(:post, member_path, attributes)
|
47
|
+
initialize! response, true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize!(_attributes = {}, _from_database = false)
|
52
|
+
to_return = super
|
53
|
+
|
54
|
+
custom_attributes.clean_up!
|
55
|
+
|
56
|
+
to_return
|
57
|
+
end
|
58
|
+
|
59
|
+
def select_changed_attributes
|
60
|
+
custom_attributes.changes.inject(super) do |changed, (key, (_was, is))|
|
61
|
+
changed[key] = is
|
62
|
+
changed
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def attributes=(new_attributes)
|
67
|
+
super
|
68
|
+
|
69
|
+
self.custom_attributes = new_attributes.select { |k, v| !self.class.attributes.keys.include?(k) }
|
70
|
+
end
|
71
|
+
|
72
|
+
def attributes
|
73
|
+
super.merge custom_attributes
|
74
|
+
end
|
75
|
+
|
76
|
+
def changed
|
77
|
+
super + custom_attributes.changes.keys
|
78
|
+
end
|
79
|
+
|
80
|
+
def custom_attributes
|
81
|
+
@custom_attributes ||= DirtyHashy.new
|
82
|
+
end
|
83
|
+
|
84
|
+
def custom_attributes=(value)
|
85
|
+
@custom_attributes = value.is_a?(DirtyHashy) ?
|
86
|
+
value : DirtyHashy.new(value)
|
87
|
+
end
|
88
|
+
|
89
|
+
def method_missing(method_name, *args, &block)
|
90
|
+
if method_name.to_s =~ /=$/
|
91
|
+
custom_attributes[method_name.to_s.gsub(/=$/, '')] = args.first
|
92
|
+
else
|
93
|
+
if custom_attributes.has_key? method_name.to_s
|
94
|
+
custom_attributes[method_name.to_s]
|
95
|
+
else
|
96
|
+
super
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
(definition[:associations]['links'] || []).each do |association_schema|
|
103
|
+
if association_schema['type'] == 'list'
|
104
|
+
define_method(association_schema['name']) do
|
105
|
+
has_many_association(association_schema['name'])
|
106
|
+
end
|
107
|
+
elsif association_schema['type'] == 'detail' && association_schema['name'] != 'self'
|
108
|
+
define_method(association_schema['name']) do
|
109
|
+
belongs_to_association(association_schema['name'])
|
110
|
+
end
|
111
|
+
elsif association_schema['type'] == 'run'
|
112
|
+
define_method(association_schema['name']) do |config = nil|
|
113
|
+
custom_method association_schema['name'], config
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
self.resource_definition = definition
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -1,380 +1,384 @@
|
|
1
|
-
|
2
|
-
# Module used as a scope for classes representing resources
|
1
|
+
module Syncano
|
3
2
|
module Resources
|
4
|
-
# Base resource used for inheritance
|
5
3
|
class Base
|
6
|
-
|
7
|
-
|
4
|
+
include ActiveAttr::Model
|
5
|
+
include ActiveAttr::Dirty
|
8
6
|
|
9
|
-
|
10
|
-
# @param [Syncano::Clients::Base] client
|
11
|
-
# @param [Hash] attributes used in making requests to api (ie. parent id)
|
12
|
-
def initialize(client, attributes = {})
|
13
|
-
super()
|
7
|
+
PARAMETER_REGEXP = /\{([^}]+)\}/
|
14
8
|
|
15
|
-
|
16
|
-
|
17
|
-
|
9
|
+
class << self
|
10
|
+
def all(connection, scope_parameters, query_params = {})
|
11
|
+
check_resource_method_existance!(:index)
|
18
12
|
|
19
|
-
|
13
|
+
response = connection.request(:get, collection_path(scope_parameters), query_params)
|
14
|
+
scope = Syncano::Scope.new(connection, scope_parameters)
|
15
|
+
Syncano::Resources::Collection.from_database(response, scope, self)
|
16
|
+
end
|
17
|
+
|
18
|
+
def first(connection, scope_parameters, query_params = {})
|
19
|
+
all(connection, scope_parameters, query_params).first
|
20
|
+
end
|
21
|
+
|
22
|
+
def last(connection, scope_parameters, query_params = {})
|
23
|
+
all(connection, scope_parameters, query_params).last
|
24
|
+
end
|
25
|
+
|
26
|
+
def find(connection, scope_parameters, pk)
|
27
|
+
check_resource_method_existance!(:show)
|
28
|
+
return unless pk.present?
|
29
|
+
|
30
|
+
response = connection.request(:get, member_path(pk, scope_parameters))
|
31
|
+
new(connection, scope_parameters, response, true)
|
32
|
+
end
|
33
|
+
|
34
|
+
def create(connection, scope_parameters, attributes)
|
35
|
+
check_resource_method_existance!(:create)
|
36
|
+
|
37
|
+
new(connection, scope_parameters, attributes).save
|
38
|
+
end
|
39
|
+
|
40
|
+
def map_attributes_values(attributes)
|
41
|
+
attributes.each do |name, value|
|
42
|
+
attributes[name] = value.to_json if value.is_a?(Array) || value.is_a?(Hash)
|
43
|
+
end
|
44
|
+
|
45
|
+
attributes
|
46
|
+
end
|
20
47
|
|
21
|
-
|
48
|
+
def extract_scope_parameters(path)
|
49
|
+
return {} if scope_parameters_names.empty?
|
50
|
+
|
51
|
+
pattern = collection_path_schema.sub('/', '\/')
|
52
|
+
|
53
|
+
scope_parameters_names.each do |parameter_name|
|
54
|
+
pattern.sub!("{#{parameter_name}}", '([^\/]+)')
|
55
|
+
end
|
56
|
+
|
57
|
+
pattern = Regexp.new(pattern)
|
58
|
+
parameter_values = path.scan(pattern).first
|
59
|
+
|
60
|
+
Hash[*scope_parameters_names.zip(parameter_values).flatten]
|
61
|
+
end
|
62
|
+
|
63
|
+
def extract_primary_key(path)
|
64
|
+
return nil if path.blank?
|
65
|
+
|
66
|
+
pattern = member_path_schema.gsub('/', '\/')
|
67
|
+
|
68
|
+
scope_parameters_names.each do |parameter_name|
|
69
|
+
pattern.sub!("{#{parameter_name}}", '([^\/]+)')
|
70
|
+
end
|
71
|
+
|
72
|
+
pattern.sub!("{#{primary_key_name}}", '([^\/]+)')
|
73
|
+
|
74
|
+
pattern = Regexp.new(pattern)
|
75
|
+
parameter_values = path.scan(pattern).first
|
76
|
+
parameter_values.last
|
77
|
+
end
|
22
78
|
end
|
23
79
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
80
|
+
def initialize(connection, scope_parameters, attributes, from_database = false)
|
81
|
+
self.connection = connection
|
82
|
+
self.scope_parameters = scope_parameters
|
83
|
+
|
84
|
+
initialize!(attributes, from_database)
|
29
85
|
end
|
30
86
|
|
31
|
-
|
32
|
-
|
33
|
-
# @return [Object]
|
34
|
-
def [](attribute_name)
|
35
|
-
attributes[attribute_name]
|
87
|
+
def primary_key
|
88
|
+
self.class.extract_primary_key(association_paths[:self])
|
36
89
|
end
|
37
90
|
|
38
|
-
|
39
|
-
|
40
|
-
# @param [Object] attribute_value
|
41
|
-
# @return [Object]
|
42
|
-
def []=(attribute_name, attribute_value)
|
43
|
-
attributes[attribute_name] = attribute_value
|
91
|
+
def new_record?
|
92
|
+
primary_key.blank?
|
44
93
|
end
|
45
94
|
|
46
|
-
|
47
|
-
|
48
|
-
# which invokes batch_update method on resource object
|
49
|
-
# @return [Syncano::BatchQueueElement]
|
50
|
-
def batch
|
51
|
-
::Syncano::BatchQueueElement.new(self)
|
95
|
+
def saved?
|
96
|
+
!new_record? && !changed?
|
52
97
|
end
|
53
98
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
def self.all(client, scope_parameters = {}, conditions = {})
|
61
|
-
response = perform_all(client, scope_parameters, conditions)
|
62
|
-
response.data.to_a.collect do |attributes|
|
63
|
-
new(client, attributes.merge(scope_parameters))
|
64
|
-
end
|
99
|
+
def update_attributes(attributes)
|
100
|
+
check_resource_method_existance!(:update)
|
101
|
+
raise(Syncano::Error.new('record is not saved')) if new_record?
|
102
|
+
|
103
|
+
self.attributes = attributes
|
104
|
+
save
|
65
105
|
end
|
66
106
|
|
67
|
-
# Returns amount of elements returned from all method
|
68
|
-
# @param [Syncano::Clients::Base] client
|
69
|
-
# @param [Hash] scope_parameters
|
70
|
-
# @param [Hash] conditions
|
71
|
-
# @return [Integer]
|
72
|
-
def self.count(client, scope_parameters = {}, conditions = {})
|
73
|
-
perform_count(client, scope_parameters, conditions)
|
74
|
-
end
|
75
|
-
|
76
|
-
# Wrapper for api "get_one" method
|
77
|
-
# Returns one object from Syncano
|
78
|
-
# @param [Syncano::Clients::Base] client
|
79
|
-
# @param [Integer, String] key
|
80
|
-
# @param [Hash] scope_parameters
|
81
|
-
# @param [Hash] conditions
|
82
|
-
def self.find(client, key, scope_parameters = {}, conditions = {})
|
83
|
-
response = perform_find(client, primary_key_name, key, scope_parameters, conditions)
|
84
|
-
new(client, scope_parameters.merge(response.data))
|
85
|
-
end
|
86
|
-
|
87
|
-
# Wrapper for api "new" method
|
88
|
-
# Creates object in Syncano
|
89
|
-
# @param [Syncano::Clients::Base] client
|
90
|
-
# @param [Hash] attributes
|
91
|
-
# @return [Syncano::Resources::Base]
|
92
|
-
def self.create(client, attributes)
|
93
|
-
response = perform_create(client, nil, attributes)
|
94
|
-
new(client, map_to_scope_parameters(attributes).merge(response.data))
|
95
|
-
end
|
96
|
-
|
97
|
-
# Batch version of "create" method
|
98
|
-
# @param [Jimson::BatchClient] batch_client
|
99
|
-
# @param [Syncano::Clients::Base] client
|
100
|
-
# @param [Hash] attributes
|
101
|
-
# @return [Syncano::Response]
|
102
|
-
def self.batch_create(batch_client, client, attributes)
|
103
|
-
perform_create(client, batch_client, attributes)
|
104
|
-
end
|
105
|
-
|
106
|
-
# Wrapper for api "update" method
|
107
|
-
# Updates object in Syncano
|
108
|
-
# @param [Hash] attributes
|
109
|
-
# @return [Syncano::Resources::Base]
|
110
|
-
def update(attributes)
|
111
|
-
response = perform_update(nil, attributes)
|
112
|
-
response.data.delete('id')
|
113
|
-
self.attributes = scope_parameters.merge(response.data)
|
114
|
-
mark_as_saved!
|
115
|
-
end
|
116
|
-
|
117
|
-
# Batch version of "update" method
|
118
|
-
# @param [Jimson::BatchClient] batch_client
|
119
|
-
# @param [Hash] attributes
|
120
|
-
# @return [Syncano::Response]
|
121
|
-
def batch_update(batch_client, attributes)
|
122
|
-
perform_update(batch_client, attributes)
|
123
|
-
end
|
124
|
-
|
125
|
-
# Invokes create or update methods
|
126
|
-
# @return [Syncano::Resources::Base]
|
127
107
|
def save
|
128
|
-
|
129
|
-
response_data = ActiveSupport::HashWithIndifferentAccess.new(response.data)
|
108
|
+
# TODO: Call validation here
|
130
109
|
|
131
110
|
if new_record?
|
132
|
-
|
133
|
-
|
134
|
-
self.id = created_object.id
|
135
|
-
self.attributes.merge!(created_object.attributes)
|
111
|
+
apply_forced_defaults!
|
112
|
+
response = connection.request(:post, collection_path, select_create_attributes)
|
136
113
|
else
|
137
|
-
|
114
|
+
response = connection.request(:patch, member_path, select_changed_attributes)
|
138
115
|
end
|
139
116
|
|
140
|
-
|
141
|
-
self
|
117
|
+
initialize!(response, true)
|
142
118
|
end
|
143
119
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
perform_save(batch_client)
|
120
|
+
def destroy
|
121
|
+
check_resource_method_existance!(:destroy)
|
122
|
+
connection.request(:delete, member_path)
|
123
|
+
mark_as_destroyed!
|
149
124
|
end
|
150
125
|
|
151
|
-
|
152
|
-
|
153
|
-
# @return [Syncano::Resources::Base] marked as destroyed
|
154
|
-
def destroy
|
155
|
-
response = perform_destroy(nil)
|
156
|
-
self.destroyed = response.status
|
157
|
-
self
|
126
|
+
def destroyed?
|
127
|
+
!!destroyed
|
158
128
|
end
|
159
129
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
130
|
+
def reload!
|
131
|
+
raise(Syncano::Error.new('record is not saved')) if new_record?
|
132
|
+
|
133
|
+
response = connection.request(:get, member_path)
|
134
|
+
initialize!(response)
|
165
135
|
end
|
166
136
|
|
167
|
-
|
168
|
-
|
169
|
-
def new_record?
|
170
|
-
id.nil?
|
137
|
+
def attribute_definitions
|
138
|
+
self.class.resource_definition.attributes
|
171
139
|
end
|
172
140
|
|
173
|
-
|
174
|
-
|
175
|
-
def saved?
|
176
|
-
!new_record? && attributes == saved_attributes
|
141
|
+
def attribute_definitions_map
|
142
|
+
Hash[ attribute_definitions.map { |attr| [attr.name, attr] } ]
|
177
143
|
end
|
178
144
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
145
|
+
def select_create_attributes
|
146
|
+
attributes = self.attributes.select { |name, _|
|
147
|
+
begin
|
148
|
+
attribute_definitions_map[name].writable?
|
149
|
+
rescue NoMethodError
|
150
|
+
if custom_attributes.has_key?(name)
|
151
|
+
true
|
152
|
+
else
|
153
|
+
raise
|
154
|
+
end
|
155
|
+
end
|
156
|
+
}
|
157
|
+
attributes = custom_attributes.merge(attributes) if respond_to?(:custom_attributes)
|
158
|
+
self.class.map_attributes_values(attributes)
|
183
159
|
end
|
184
160
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
self.attributes.clear
|
191
|
-
self.attributes = reloaded_object.attributes
|
192
|
-
mark_as_saved!
|
193
|
-
end
|
161
|
+
def select_update_attributes
|
162
|
+
attributes = updatable_attributes
|
163
|
+
attributes = custom_attributes.merge(attributes) if respond_to?(:custom_attributes)
|
164
|
+
self.class.map_attributes_values(attributes)
|
165
|
+
end
|
194
166
|
|
195
|
-
|
167
|
+
def select_changed_attributes
|
168
|
+
updatable_attributes
|
169
|
+
end
|
170
|
+
|
171
|
+
def updatable_attributes
|
172
|
+
attributes = self.attributes.select do |name, _value|
|
173
|
+
self.class.update_writable_attributes.include?(name.to_sym)
|
174
|
+
end
|
175
|
+
self.class.map_attributes_values attributes
|
196
176
|
end
|
197
177
|
|
198
178
|
private
|
199
179
|
|
200
|
-
class_attribute :
|
201
|
-
|
202
|
-
self.syncano_model_name = nil
|
203
|
-
self.scope_parameters = []
|
204
|
-
self.crud_class_methods = [:all, :find, :new, :create, :count]
|
205
|
-
self.crud_instance_methods = [:save, :update, :destroy]
|
206
|
-
self.primary_key = :id
|
207
|
-
|
208
|
-
attr_accessor :client, :saved_attributes
|
209
|
-
attr_writer :id, :destroyed
|
210
|
-
|
211
|
-
# Executes proper all request
|
212
|
-
# @param [Syncano::Clients::Base] client
|
213
|
-
# @param [Hash] scope_parameters
|
214
|
-
# @param [Hash] conditions
|
215
|
-
# @return [Syncano::Response]
|
216
|
-
def self.perform_all(client, scope_parameters, conditions)
|
217
|
-
check_class_method_existance!(:all)
|
218
|
-
make_request(client, nil, :all, conditions.merge(scope_parameters))
|
219
|
-
end
|
220
|
-
|
221
|
-
# Executes proper count request
|
222
|
-
# @param [Syncano::Clients::Base] client
|
223
|
-
# @param [Hash] scope_parameters
|
224
|
-
# @param [Hash] conditions
|
225
|
-
# @return [Syncano::Response]
|
226
|
-
def self.perform_count(client, scope_parameters, conditions)
|
227
|
-
check_class_method_existance!(:count)
|
228
|
-
all(client, scope_parameters, conditions).count
|
229
|
-
end
|
230
|
-
|
231
|
-
# Executes proper find request
|
232
|
-
# @param [Syncano::Clients::Base] client
|
233
|
-
# @param [Symbol, String] key_name
|
234
|
-
# @param [Integer, String] key
|
235
|
-
# @param [Hash] scope_parameters
|
236
|
-
# @param [Hash] conditions
|
237
|
-
# @return [Syncano::Response]
|
238
|
-
def self.perform_find(client, key_name, key, scope_parameters, conditions)
|
239
|
-
check_class_method_existance!(:find)
|
240
|
-
make_request(client, nil, :find, conditions.merge(scope_parameters.merge(key_name.to_sym => key)))
|
241
|
-
end
|
242
|
-
|
243
|
-
# Executes proper create request
|
244
|
-
# @param [Syncano::Clients::Base] client
|
245
|
-
# @param [Jimson::BatchClient] batch_client
|
246
|
-
# @param [Hash] attributes
|
247
|
-
# @return [Syncano::Response]
|
248
|
-
def self.perform_create(client, batch_client, attributes)
|
249
|
-
check_class_method_existance!(:create)
|
250
|
-
make_request(client, batch_client, :create, attributes_to_sync(attributes))
|
251
|
-
end
|
252
|
-
|
253
|
-
# Executes proper update request
|
254
|
-
# @param [Jimson::BatchClient] batch_client
|
255
|
-
# @param [Hash] attributes
|
256
|
-
# @return [Syncano::Response]
|
257
|
-
def perform_update(batch_client, attributes)
|
258
|
-
check_instance_method_existance!(:update)
|
259
|
-
self.class.make_request(client, batch_client, :update, scope_parameters.merge(self.class.attributes_to_sync(attributes).merge(self.class.primary_key_name => primary_key)))
|
260
|
-
end
|
261
|
-
|
262
|
-
# Executes proper save request
|
263
|
-
# @param [Jimson::BatchClient] batch_client
|
264
|
-
# @return [Syncano::Response]
|
265
|
-
def perform_save(batch_client)
|
266
|
-
check_instance_method_existance!(:save)
|
180
|
+
class_attribute :resource_definition, :create_writable_attributes, :update_writable_attributes
|
181
|
+
attr_accessor :connection, :association_paths, :member_path, :scope_parameters, :destroyed
|
267
182
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
183
|
+
def initialize!(attributes = {}, from_database = false)
|
184
|
+
attributes = HashWithIndifferentAccess.new(attributes)
|
185
|
+
|
186
|
+
initialize_routing(attributes)
|
187
|
+
initialize_associations(attributes)
|
188
|
+
|
189
|
+
self.attributes.clear
|
190
|
+
self.attributes = attributes.except!(:links)
|
191
|
+
|
192
|
+
if from_database && self.class.attributes.keys.include?('custom_attributes')
|
193
|
+
self.custom_attributes = attributes.select{ |k, v| !self.attributes.keys.include?(k) }
|
194
|
+
end
|
195
|
+
|
196
|
+
apply_defaults
|
197
|
+
|
198
|
+
mark_as_saved! if !new_record? && from_database
|
199
|
+
|
200
|
+
self
|
201
|
+
end
|
202
|
+
|
203
|
+
def initialize_associations(attributes)
|
204
|
+
self.association_paths = HashWithIndifferentAccess.new
|
205
|
+
|
206
|
+
if attributes[:links].present?
|
207
|
+
attributes[:links].keys.each do |key|
|
208
|
+
association_paths[key] = attributes[:links][key]
|
209
|
+
end
|
272
210
|
end
|
273
211
|
end
|
274
212
|
|
275
|
-
|
276
|
-
|
277
|
-
# @return [Syncano::Response]
|
278
|
-
def perform_destroy(batch_client)
|
279
|
-
check_instance_method_existance!(:destroy)
|
280
|
-
self.class.make_request(client, batch_client, :destroy, scope_parameters.merge({ self.class.primary_key_name => primary_key }))
|
213
|
+
def initialize_routing(attributes)
|
214
|
+
self.member_path = attributes[:links].try(:[], :self)
|
281
215
|
end
|
282
216
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
syncano_model_name || to_s.split('::').last.downcase
|
217
|
+
def self.map_member_name_to_resource_class(name)
|
218
|
+
name = 'code_box' if name == 'codebox'
|
219
|
+
"::Syncano::Resources::#{name.camelize}".constantize
|
287
220
|
end
|
288
221
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
222
|
+
def self.map_collection_name_to_resource_class(name)
|
223
|
+
name = case name
|
224
|
+
when 'codeboxes'
|
225
|
+
'code_boxes'
|
226
|
+
when 'traces'
|
227
|
+
case self.name
|
228
|
+
when 'Syncano::Resources::CodeBox'
|
229
|
+
'code_box_traces'
|
230
|
+
end
|
231
|
+
else
|
232
|
+
name
|
233
|
+
end
|
294
234
|
|
295
|
-
|
296
|
-
mapping.keys.include?(method_name.to_sym) ? mapping[method_name.to_sym] : method_name
|
235
|
+
map_member_name_to_resource_class(name.singularize)
|
297
236
|
end
|
298
237
|
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
# @param [String] response_key
|
305
|
-
# @return [Syncano::Response]
|
306
|
-
def self.make_request(client, batch_client, method_name, attributes = {}, response_key = nil)
|
307
|
-
if batch_client.nil?
|
308
|
-
client.make_request(api_resource, api_method(method_name), attributes, response_key)
|
309
|
-
else
|
310
|
-
client.make_batch_request(batch_client, api_resource, api_method(method_name), attributes)
|
238
|
+
def apply_forced_defaults!
|
239
|
+
self.class.attributes.each do |attr_name, attr_definition|
|
240
|
+
if read_attribute(attr_name).blank? && attr_definition[:force_default]
|
241
|
+
write_attribute(attr_name, attr_definition[:default].is_a?(Proc) ? attr_definition[:default].call : attr_definition[:default])
|
242
|
+
end
|
311
243
|
end
|
312
244
|
end
|
313
245
|
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
246
|
+
def mark_as_saved!
|
247
|
+
raise(Syncano::Error.new('primary key is blank')) if new_record?
|
248
|
+
|
249
|
+
@previously_changed = changes
|
250
|
+
@changed_attributes.clear
|
251
|
+
self
|
319
252
|
end
|
320
253
|
|
321
|
-
|
322
|
-
|
323
|
-
def scope_parameters
|
324
|
-
self.class.map_to_scope_parameters(attributes)
|
254
|
+
def mark_as_destroyed!
|
255
|
+
self.destroyed = true
|
325
256
|
end
|
326
257
|
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
258
|
+
def has_many_association(name)
|
259
|
+
# TODO Implement QueryBuilders without scope parameters and adding objects to the association
|
260
|
+
raise(Syncano::Error.new('record not saved')) if new_record?
|
261
|
+
|
262
|
+
resource_class = self.class.map_collection_name_to_resource_class(name)
|
263
|
+
scope_parameters = resource_class.extract_scope_parameters(association_paths[name])
|
264
|
+
|
265
|
+
::Syncano::QueryBuilder.new(connection, resource_class, scope_parameters)
|
331
266
|
end
|
332
267
|
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
268
|
+
def belongs_to_association(name)
|
269
|
+
resource_class = self.class.map_member_name_to_resource_class(name)
|
270
|
+
scope_parameters = resource_class.extract_scope_parameters(association_paths[name])
|
271
|
+
pk = resource_class.extract_primary_key(association_paths[name])
|
272
|
+
|
273
|
+
::Syncano::QueryBuilder.new(connection, resource_class, scope_parameters).find(pk)
|
337
274
|
end
|
338
275
|
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
276
|
+
def custom_method(method_name, config)
|
277
|
+
connection.request self.class.custom_method_http_method(method_name),
|
278
|
+
self.class.custom_method_path(method_name, primary_key, scope_parameters),
|
279
|
+
config
|
280
|
+
end
|
281
|
+
|
282
|
+
def self.custom_method_http_method(method_name)
|
283
|
+
custom_method_definition(method_name)[:http_methods].first.to_sym
|
284
|
+
end
|
285
|
+
|
286
|
+
def self.collection_path_schema
|
287
|
+
resource_definition[:collection][:path].dup
|
288
|
+
end
|
289
|
+
|
290
|
+
def self.member_path_schema
|
291
|
+
resource_definition[:member][:path].dup
|
292
|
+
end
|
293
|
+
|
294
|
+
def self.custom_method_path_schema(method_name)
|
295
|
+
custom_method_definition(method_name)[:path].dup
|
296
|
+
end
|
297
|
+
|
298
|
+
def self.custom_method_definition(method_name)
|
299
|
+
resource_definition[:custom_methods].find do |method_definition|
|
300
|
+
method_definition[:name] == method_name
|
301
|
+
end or raise "No such method #{method_name}"
|
302
|
+
end
|
303
|
+
|
304
|
+
def self.scope_parameters_names
|
305
|
+
collection_path_schema.scan(PARAMETER_REGEXP).collect{ |matches| matches.first.to_sym }
|
344
306
|
end
|
345
307
|
|
346
|
-
|
347
|
-
|
348
|
-
# @return [Hash]
|
349
|
-
def self.attributes_to_sync(attributes = {})
|
350
|
-
attributes
|
308
|
+
def self.has_collection_actions?
|
309
|
+
resource_definition[:collection].present?
|
351
310
|
end
|
352
311
|
|
353
|
-
|
354
|
-
|
355
|
-
def attributes_to_sync
|
356
|
-
self.class.attributes_to_sync(attributes)
|
312
|
+
def self.has_member_actions?
|
313
|
+
resource_definition[:member].present?
|
357
314
|
end
|
358
315
|
|
359
|
-
|
360
|
-
|
361
|
-
raise NoMethodError.new("undefined method `#{method_name}' for #{to_s}") unless crud_class_methods.include?(method_name.to_sym)
|
316
|
+
def self.check_resource_method_existance!(method_name)
|
317
|
+
raise(NoMethodError.new) unless send("#{method_name}_implemented?")
|
362
318
|
end
|
363
319
|
|
364
|
-
|
365
|
-
|
366
|
-
|
320
|
+
def self.primary_key_name
|
321
|
+
resource_definition[:member][:path].scan(PARAMETER_REGEXP).last.first if has_member_actions?
|
322
|
+
end
|
323
|
+
|
324
|
+
def self.custom_method_path(name, pk, scope_parameters)
|
325
|
+
path = custom_method_path_schema(name)
|
326
|
+
|
327
|
+
scope_parameters_names.each do |scope_parameter_name|
|
328
|
+
path.sub!("{#{scope_parameter_name}}", scope_parameters[scope_parameter_name])
|
329
|
+
end
|
330
|
+
|
331
|
+
path.sub!("{#{primary_key_name}}", pk.to_s)
|
332
|
+
|
333
|
+
path
|
367
334
|
end
|
368
335
|
|
369
|
-
|
370
|
-
|
371
|
-
|
336
|
+
def self.collection_path(scope_parameters = {})
|
337
|
+
path = collection_path_schema
|
338
|
+
|
339
|
+
scope_parameters_names.each do |scope_parameter_name|
|
340
|
+
path.sub!("{#{scope_parameter_name}}", scope_parameters[scope_parameter_name])
|
341
|
+
end
|
342
|
+
|
343
|
+
path
|
372
344
|
end
|
373
345
|
|
374
|
-
|
375
|
-
|
376
|
-
|
346
|
+
def self.member_path(pk, scope_parameters = {})
|
347
|
+
path = member_path_schema
|
348
|
+
|
349
|
+
scope_parameters_names.each do |scope_parameter_name|
|
350
|
+
path.sub!("{#{scope_parameter_name}}", scope_parameters[scope_parameter_name])
|
351
|
+
end
|
352
|
+
|
353
|
+
path.sub!("{#{primary_key_name}}", pk.to_s)
|
354
|
+
|
355
|
+
path
|
356
|
+
end
|
357
|
+
|
358
|
+
def collection_path
|
359
|
+
self.class.collection_path(scope_parameters)
|
360
|
+
end
|
361
|
+
|
362
|
+
def member_path
|
363
|
+
self.class.member_path(primary_key, scope_parameters)
|
364
|
+
end
|
365
|
+
|
366
|
+
def check_resource_method_existance!(method_name)
|
367
|
+
self.class.check_resource_method_existance!(method_name)
|
368
|
+
end
|
369
|
+
|
370
|
+
{
|
371
|
+
index: { type: :collection, method: :get },
|
372
|
+
create: { type: :collection, method: :post },
|
373
|
+
show: { type: :member, method: :get },
|
374
|
+
update: { type: :member, method: :put },
|
375
|
+
destroy: { type: :member, method: :delete }
|
376
|
+
}.each do |name, parameters|
|
377
|
+
|
378
|
+
define_singleton_method(name.to_s + '_implemented?') do
|
379
|
+
send("has_#{parameters[:type]}_actions?") and resource_definition[parameters[:type]][:http_methods].include?(parameters[:method].to_s)
|
380
|
+
end
|
377
381
|
end
|
378
382
|
end
|
379
383
|
end
|
380
|
-
end
|
384
|
+
end
|