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
data/Rakefile
CHANGED
@@ -1,7 +1,50 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'rake/testtask'
|
3
4
|
|
4
|
-
RSpec::Core::RakeTask.new
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
task default: [:ci]
|
5
7
|
|
6
|
-
|
7
|
-
task
|
8
|
+
desc 'Run specs in isolation'
|
9
|
+
task :"spec:isolation" do
|
10
|
+
FileList['spec/**/*_spec.rb'].each do |spec|
|
11
|
+
sh 'rspec', spec
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Run CI tasks'
|
16
|
+
task ci: [:spec, :lint, :"spec:isolation"]
|
17
|
+
|
18
|
+
Rake::TestTask.new(:lint) do |test|
|
19
|
+
test.description = 'Run adapter lint tests against memory adapter'
|
20
|
+
test.test_files = FileList.new('spec/test/*_test.rb')
|
21
|
+
test.libs << 'test'
|
22
|
+
test.verbose = true
|
23
|
+
end
|
24
|
+
|
25
|
+
begin
|
26
|
+
require 'rubocop/rake_task'
|
27
|
+
|
28
|
+
Rake::Task[:default].enhance [:rubocop]
|
29
|
+
|
30
|
+
RuboCop::RakeTask.new do |task|
|
31
|
+
task.options << '--display-cop-names' << '--lint'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
desc 'Run mutant against a specific subject'
|
36
|
+
task :mutant do
|
37
|
+
subject = ARGV.last
|
38
|
+
if subject == 'mutant'
|
39
|
+
abort "usage: rake mutant SUBJECT\nexample: rake mutant Syncano::API"
|
40
|
+
else
|
41
|
+
opts = {
|
42
|
+
'include' => 'lib',
|
43
|
+
'require' => 'syncano',
|
44
|
+
'use' => 'rspec',
|
45
|
+
'ignore-subject' => "#{subject}#respond_to_missing?"
|
46
|
+
}.to_a.map { |k, v| "--#{k} #{v}" }.join(' ')
|
47
|
+
|
48
|
+
exec("bundle exec mutant #{opts} #{subject}")
|
49
|
+
end
|
50
|
+
end
|
data/circle.yml
ADDED
data/lib/active_attr/dirty.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'active_support'
|
1
|
+
require 'active_support/concern'
|
2
2
|
require 'active_model/dirty'
|
3
3
|
require 'active_attr'
|
4
4
|
|
@@ -14,7 +14,7 @@ module ActiveAttr
|
|
14
14
|
# Overwritten attribute! method
|
15
15
|
# @param [Symbol] name
|
16
16
|
# @param [Hash] options
|
17
|
-
def attribute!(name, options={})
|
17
|
+
def attribute!(name, options = {})
|
18
18
|
super(name, options)
|
19
19
|
define_method("#{name}=") do |value|
|
20
20
|
send("#{name}_will_change!") unless value == read_attribute(name)
|
@@ -22,19 +22,5 @@ module ActiveAttr
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
25
|
-
|
26
|
-
# Overwritten constructor
|
27
|
-
# @param [Hash] attributes
|
28
|
-
# @param [Hash] options
|
29
|
-
def initialize(attributes = nil, options = {})
|
30
|
-
super(attributes, options)
|
31
|
-
(@changed_attributes || {}).clear unless new_record?
|
32
|
-
end
|
33
|
-
|
34
|
-
# Overwritten save method
|
35
|
-
def save
|
36
|
-
@previously_changed = changes
|
37
|
-
@changed_attributes.clear
|
38
|
-
end
|
39
25
|
end
|
40
|
-
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
|
data/lib/syncano.rb
CHANGED
@@ -1,107 +1,68 @@
|
|
1
|
+
$: << Dir.pwd
|
2
|
+
|
3
|
+
require 'faraday'
|
4
|
+
require 'active_attr/model'
|
5
|
+
require 'active_attr/dirty'
|
6
|
+
require 'active_attr/typecasting_override'
|
7
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
8
|
+
require 'active_support/core_ext/class/attribute.rb'
|
9
|
+
require 'active_support/inflector'
|
1
10
|
require 'syncano/version'
|
11
|
+
require 'syncano/api'
|
12
|
+
require 'syncano/connection'
|
13
|
+
require 'syncano/schema'
|
14
|
+
require 'syncano/scope'
|
15
|
+
require 'syncano/resources'
|
16
|
+
require 'syncano/resources/base'
|
17
|
+
require 'syncano/resources/collection'
|
18
|
+
require 'syncano/resources/space'
|
19
|
+
require 'syncano/query_builder'
|
20
|
+
require 'syncano/model/base'
|
2
21
|
|
3
|
-
|
4
|
-
class
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
auth_data = self.auth_data(options)
|
10
|
-
client = Syncano::Clients::Rest.new(auth_data[:instance_name], auth_data[:api_key], auth_data[:auth_key])
|
11
|
-
client.login(options[:username], options[:password]) if client.auth_key.nil? && options[:username].present?
|
12
|
-
client
|
13
|
-
end
|
22
|
+
module Syncano
|
23
|
+
class << self
|
24
|
+
def connect(options = {})
|
25
|
+
connection = Connection.new(
|
26
|
+
options.reverse_merge(api_key: ENV['SYNCANO_API_KEY']))
|
27
|
+
connection.authenticate! unless connection.authenticated?
|
14
28
|
|
15
|
-
|
16
|
-
|
17
|
-
# @return [Syncano::Clients::Rest] Syncano client.
|
18
|
-
def self.sync_client(options = {})
|
19
|
-
auth_data = self.auth_data(options)
|
20
|
-
client = Syncano::Clients::Sync.instance(auth_data[:instance_name], auth_data[:api_key], auth_data[:auth_key])
|
21
|
-
client.login(options[:username], options[:password]) if client.auth_key.nil? && options[:username].present?
|
22
|
-
client.reconnect
|
23
|
-
client
|
29
|
+
API.new connection
|
30
|
+
end
|
24
31
|
end
|
25
32
|
|
26
|
-
|
27
|
-
|
28
|
-
# Prepares hash with auth data from options or constants in initializer
|
29
|
-
# @param [Hash] options with keys: instance_name, api_key which can be also provided as constants in the initializer
|
30
|
-
# @return [Hash]
|
31
|
-
def self.auth_data(options = {})
|
32
|
-
instance_name = options[:instance_name] || ::SYNCANO_INSTANCE_NAME
|
33
|
-
raise 'Syncano instance name cannot be blank!' if instance_name.nil?
|
33
|
+
class Error < StandardError; end
|
34
34
|
|
35
|
-
|
36
|
-
raise 'Syncano api key cannot be blank!' if api_key.nil?
|
35
|
+
class RuntimeError < StandardError; end
|
37
36
|
|
38
|
-
|
39
|
-
|
40
|
-
end
|
37
|
+
class HTTPError < StandardError
|
38
|
+
attr_accessor :body, :original_response
|
41
39
|
|
42
|
-
|
43
|
-
|
44
|
-
|
40
|
+
def initialize(body, original_response)
|
41
|
+
self.body = body
|
42
|
+
self.original_response = original_response
|
43
|
+
end
|
45
44
|
|
46
|
-
|
47
|
-
|
45
|
+
def inspect
|
46
|
+
"<#{self.class.name} #{body} #{original_response}>"
|
47
|
+
end
|
48
48
|
|
49
|
-
|
50
|
-
|
49
|
+
alias :to_s :inspect
|
50
|
+
end
|
51
51
|
|
52
|
-
|
53
|
-
|
52
|
+
class ClientError < HTTPError; end
|
53
|
+
class ServerError < HTTPError; end
|
54
54
|
|
55
|
-
|
56
|
-
|
57
|
-
require 'active_support/core_ext/class/attribute.rb'
|
58
|
-
require 'active_support/core_ext/object/blank.rb'
|
59
|
-
require 'active_support/json/decoding.rb'
|
60
|
-
require 'active_support/json/encoding.rb'
|
61
|
-
require 'active_support/time_with_zone.rb'
|
62
|
-
require 'active_support/concern'
|
63
|
-
require 'active_support/inflector/inflections'
|
55
|
+
class UnsupportedStatusError < StandardError
|
56
|
+
attr_accessor :original_response
|
64
57
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
require 'active_model/dirty'
|
58
|
+
def initialize(original_response)
|
59
|
+
self.original_response = original_response
|
60
|
+
end
|
69
61
|
|
70
|
-
|
71
|
-
|
72
|
-
|
62
|
+
def inspect
|
63
|
+
"The server returned unsupported status code #{original_response.status}"
|
64
|
+
end
|
73
65
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
require 'syncano/clients/rest'
|
78
|
-
require 'syncano/clients/sync'
|
79
|
-
require 'syncano/sync_connection'
|
80
|
-
require 'syncano/query_builder'
|
81
|
-
require 'syncano/batch_queue'
|
82
|
-
require 'syncano/batch_queue_element'
|
83
|
-
require 'syncano/response'
|
84
|
-
require 'syncano/resources/base'
|
85
|
-
require 'syncano/resources/admin'
|
86
|
-
require 'syncano/resources/api_key'
|
87
|
-
require 'syncano/resources/data_object'
|
88
|
-
require 'syncano/resources/collection'
|
89
|
-
require 'syncano/resources/folder'
|
90
|
-
require 'syncano/resources/project'
|
91
|
-
require 'syncano/resources/role'
|
92
|
-
require 'syncano/resources/subscription'
|
93
|
-
require 'syncano/resources/user'
|
94
|
-
require 'syncano/packets/base'
|
95
|
-
require 'syncano/packets/auth'
|
96
|
-
require 'syncano/packets/call'
|
97
|
-
require 'syncano/packets/call_response'
|
98
|
-
require 'syncano/packets/error'
|
99
|
-
require 'syncano/packets/message'
|
100
|
-
require 'syncano/packets/notification'
|
101
|
-
require 'syncano/packets/ping'
|
102
|
-
require 'syncano/resources/notifications/base'
|
103
|
-
require 'syncano/resources/notifications/create'
|
104
|
-
require 'syncano/resources/notifications/update'
|
105
|
-
require 'syncano/resources/notifications/destroy'
|
106
|
-
require 'syncano/resources/notifications/message'
|
107
|
-
require 'syncano/active_record/base'
|
66
|
+
alias :to_s :inspect
|
67
|
+
end
|
68
|
+
end
|
data/lib/syncano/api.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Syncano
|
4
|
+
class Connection
|
5
|
+
API_VERSION = 'v1'
|
6
|
+
AUTH_PATH = 'account/auth/'
|
7
|
+
METHODS = Set.new [:get, :post, :put, :delete, :head, :patch, :options]
|
8
|
+
|
9
|
+
def self.api_root
|
10
|
+
ENV['API_ROOT']
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(options = {})
|
14
|
+
self.api_key = options[:api_key]
|
15
|
+
self.email = options[:email]
|
16
|
+
self.password = options[:password]
|
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
|
22
|
+
end
|
23
|
+
|
24
|
+
def authenticated?
|
25
|
+
!api_key.nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
def authenticate(email, password)
|
29
|
+
self.email = email
|
30
|
+
self.password = password
|
31
|
+
authenticate!
|
32
|
+
end
|
33
|
+
|
34
|
+
def authenticate!
|
35
|
+
response = conn.post(AUTH_PATH, email: email, password: password)
|
36
|
+
body = parse_response(response)
|
37
|
+
|
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
|
44
|
+
end
|
45
|
+
|
46
|
+
def request(method, path, params = {})
|
47
|
+
raise %{Unsupported method "#{method}"} unless METHODS.include? method
|
48
|
+
conn.headers['X-API-KEY'] = api_key
|
49
|
+
conn.headers['User-Agent'] = "Syncano Ruby Gem #{Syncano::VERSION}"
|
50
|
+
response = conn.send(method, path, params)
|
51
|
+
|
52
|
+
case response
|
53
|
+
when Status.no_content
|
54
|
+
when Status.successful
|
55
|
+
parse_response response
|
56
|
+
when Status.client_error # TODO figure out if we want to raise an exception on not found or not
|
57
|
+
raise ClientError.new(response.body, response)
|
58
|
+
when Status.server_error
|
59
|
+
raise ServerError.new(response.body, response)
|
60
|
+
else
|
61
|
+
raise UnsupportedStatusError.new(response)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def parse_response(response)
|
68
|
+
JSON.parse(response.body)
|
69
|
+
end
|
70
|
+
|
71
|
+
class Status
|
72
|
+
class << self
|
73
|
+
def successful
|
74
|
+
->(response) { (200...300).include? response.status }
|
75
|
+
end
|
76
|
+
|
77
|
+
def client_error
|
78
|
+
->(response) { (400...500).include? response.status }
|
79
|
+
end
|
80
|
+
|
81
|
+
def no_content
|
82
|
+
->(response) { response.status == 204 }
|
83
|
+
end
|
84
|
+
|
85
|
+
def server_error
|
86
|
+
->(response) { response.status >= 500 }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
attr_accessor :api_key
|
92
|
+
attr_accessor :api_root
|
93
|
+
attr_accessor :email
|
94
|
+
attr_accessor :password
|
95
|
+
attr_accessor :conn
|
96
|
+
end
|
97
|
+
end
|
@@ -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
|