syncano 3.1.1.beta5 → 3.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +164 -2
- data/lib/active_attr/dirty.rb +40 -0
- data/lib/generators/syncano/templates/initializers/syncano.rb +4 -1
- data/lib/syncano.rb +12 -1
- data/lib/syncano/active_record/association/base.rb +38 -0
- data/lib/syncano/active_record/association/belongs_to.rb +30 -0
- data/lib/syncano/active_record/association/has_many.rb +69 -0
- data/lib/syncano/active_record/association/has_one.rb +22 -0
- data/lib/syncano/active_record/associations.rb +112 -0
- data/lib/syncano/active_record/base.rb +318 -0
- data/lib/syncano/active_record/callbacks.rb +46 -0
- data/lib/syncano/active_record/scope_builder.rb +174 -0
- data/lib/syncano/resources/base.rb +4 -2
- data/lib/syncano/resources/data_object.rb +5 -0
- data/lib/syncano/resources/folder.rb +1 -1
- data/lib/syncano/version.rb +1 -1
- data/syncano.gemspec +2 -0
- metadata +42 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9c3a289c247f6654d64d2a9510acb205bce778a1
|
4
|
+
data.tar.gz: 23f7bc994f710dd3b4e25be3d2847358506b173d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ffc0eea9c73a2d85d04999f7d2cb7cd3cb3bd199655fa929b8360abbf76f587bacb66983f69ca8986d0879d236f417470a714fa7e00091c4861af8a55a66404f
|
7
|
+
data.tar.gz: 51d2312ec5ba3115266634e9e17742b6501c05232fa4cef96edb292ba95728ceb128f576502bdd1d80ab2e0f20536bf7e7a12f3ed69ba2a97a125793473c3663
|
data/README.md
CHANGED
@@ -10,7 +10,7 @@ Click here to learn more about [Syncano](http://www.syncano.com) or [create an a
|
|
10
10
|
|
11
11
|
Add this line to your application's Gemfile:
|
12
12
|
|
13
|
-
gem 'syncano', '~> 3.1.1
|
13
|
+
gem 'syncano', '~> 3.1.1'
|
14
14
|
|
15
15
|
And then execute:
|
16
16
|
|
@@ -18,7 +18,7 @@ And then execute:
|
|
18
18
|
|
19
19
|
Or install it yourself as:
|
20
20
|
|
21
|
-
$ gem install syncano -v 3.1.1
|
21
|
+
$ gem install syncano -v 3.1.1
|
22
22
|
|
23
23
|
At the end generate initializer with api key and api instance name:
|
24
24
|
|
@@ -337,6 +337,168 @@ This library does not implement any validations. All errors from the api will ca
|
|
337
337
|
It is thought that user will create his own validation mechanisms specific not only for restrictions imposed by the Syncano, but also for his own logic.
|
338
338
|
It can be compared to the exceptions after violating constraints in the MySQL database.
|
339
339
|
|
340
|
+
### Integration with Ruby on Rails
|
341
|
+
|
342
|
+
Syncano gem provides handy class for implementing model with ActiveRecord pattern. See example below:
|
343
|
+
|
344
|
+
```ruby
|
345
|
+
class Category < Syncano::ActiveRecord::Base
|
346
|
+
has_many :articles
|
347
|
+
|
348
|
+
attribute :name, type: String
|
349
|
+
validates :name, presence: true
|
350
|
+
end
|
351
|
+
|
352
|
+
class Article < Syncano::ActiveRecord::Base
|
353
|
+
belongs_to :category
|
354
|
+
|
355
|
+
attribute :title, type: String
|
356
|
+
attribute :text, type: String
|
357
|
+
attribute :promoted, type: Integer, filterable: :data1
|
358
|
+
validates :title, presence: true
|
359
|
+
validates :text, presence: true
|
360
|
+
|
361
|
+
scope :promoted, -> { where('promoted = ?', 1) }
|
362
|
+
|
363
|
+
before_save :sanitize_content
|
364
|
+
|
365
|
+
private
|
366
|
+
|
367
|
+
def sanitize_content
|
368
|
+
self.title = Sanitize.clean(title)
|
369
|
+
self.text = Sanitize.clean(text)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
```
|
373
|
+
|
374
|
+
#### Attributes
|
375
|
+
|
376
|
+
As you can see above every attribute has to be declared with a type. Every ActiveRecord class has also three standard attributes:
|
377
|
+
- :id, type: Integer
|
378
|
+
- :created_at, type: DateTime
|
379
|
+
- :updated_at, type: DateTime
|
380
|
+
|
381
|
+
There can be up to three filterable attributes (mapped to the Syncano data1, data2, data3 attributes), which can be used in where and order clauses. They always should have Integer type.
|
382
|
+
|
383
|
+
Attributes can be validated like in standard ActiveRecord.
|
384
|
+
|
385
|
+
#### Scopes and query building
|
386
|
+
|
387
|
+
You can sort and filter by filterable attributes:
|
388
|
+
|
389
|
+
```ruby
|
390
|
+
where('attribute1 > ? AND attribute2 <= ?', 0, 30).order('attribute3 ASC').where('attribute 2 > ?', 5)
|
391
|
+
```
|
392
|
+
|
393
|
+
As you can see methods can be chained.
|
394
|
+
|
395
|
+
#### Callbacks
|
396
|
+
|
397
|
+
There are available ten different callbacks fired in the following sequence:
|
398
|
+
|
399
|
+
1. before_validation
|
400
|
+
2. after_validation
|
401
|
+
3. before_save
|
402
|
+
4. before_create / before_update
|
403
|
+
5. after_create / after_save
|
404
|
+
6. after_save
|
405
|
+
|
406
|
+
1. before_destroy
|
407
|
+
2. after_destroy
|
408
|
+
|
409
|
+
#### Associations
|
410
|
+
|
411
|
+
There are three types of relations (belongs_to, has_one, has_many) which are based on Syncano parent - child mechanism.
|
412
|
+
|
413
|
+
##### belongs_to :category
|
414
|
+
|
415
|
+
Adds following methods:
|
416
|
+
|
417
|
+
```ruby
|
418
|
+
self.category
|
419
|
+
self.category = Category.first
|
420
|
+
self.category_id
|
421
|
+
self.category_id = Category.first.id
|
422
|
+
```
|
423
|
+
|
424
|
+
Remember to always declare belongs_to association! It creates proper attribute in model.
|
425
|
+
|
426
|
+
##### has_one :article
|
427
|
+
|
428
|
+
```ruby
|
429
|
+
self.article
|
430
|
+
self.article = Article.first # this method updates article object
|
431
|
+
self.build_article(article_attributes)
|
432
|
+
self.create_article(article_attributes)
|
433
|
+
```
|
434
|
+
|
435
|
+
##### has_many :articles
|
436
|
+
|
437
|
+
```ruby
|
438
|
+
self.articles
|
439
|
+
self.articles = Article.first(5) # this method updates each article object
|
440
|
+
self.articles << Article.first # this method updates article object
|
441
|
+
self.articles.build(article_attributes)
|
442
|
+
self.articles.create(article_attributes)
|
443
|
+
```
|
444
|
+
|
445
|
+
You can also call scope builder methods on has_many collection:
|
446
|
+
|
447
|
+
```ruby
|
448
|
+
self.articles.promoted.all
|
449
|
+
```
|
450
|
+
|
451
|
+
#### Other useful methods in examples
|
452
|
+
|
453
|
+
```ruby
|
454
|
+
Article.promoted.find(id)
|
455
|
+
Article.where('promoted = ?', 0).count
|
456
|
+
Article.since(min_id).before(max_id)
|
457
|
+
Article.since(Time.now - 10.days)
|
458
|
+
```
|
459
|
+
|
460
|
+
For all methods please see reference for Syncano::ActiveRecord::Base class.
|
461
|
+
|
462
|
+
#### Collection and folders
|
463
|
+
|
464
|
+
Classes which inherit from Syncano::ActiveRecord::Base needs a collection and a folders in Syncano.
|
465
|
+
|
466
|
+
Collection can be configured as constant in initializer:
|
467
|
+
|
468
|
+
```ruby
|
469
|
+
SYNCANO_ACTIVERECORD_COLLECTION = Syncano.client.project.first.collection.first
|
470
|
+
```
|
471
|
+
|
472
|
+
or it can be overwritten in selected model:
|
473
|
+
|
474
|
+
```ruby
|
475
|
+
class Article < Syncano::ActiveRecord::Base
|
476
|
+
|
477
|
+
private
|
478
|
+
|
479
|
+
def self.collection
|
480
|
+
Syncano.client.project.first.collection.first
|
481
|
+
end
|
482
|
+
end
|
483
|
+
```
|
484
|
+
|
485
|
+
Folders are used as classes - each model as his own folder (ie. Articles). Folder is automatically created and used without any additional configuration, but you can customize convention by overwriting folder_name or folder method:
|
486
|
+
|
487
|
+
```ruby
|
488
|
+
class Article < Syncano::ActiveRecord::Base
|
489
|
+
|
490
|
+
private
|
491
|
+
|
492
|
+
def self.folder_name
|
493
|
+
'Posts'
|
494
|
+
end
|
495
|
+
|
496
|
+
def self.folder
|
497
|
+
collection.folders.find_by_name(folder_name)
|
498
|
+
end
|
499
|
+
end
|
500
|
+
```
|
501
|
+
|
340
502
|
## Contributing
|
341
503
|
|
342
504
|
1. Fork it
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_model/dirty'
|
3
|
+
require 'active_attr'
|
4
|
+
|
5
|
+
# Overwritting ActiveAttr module
|
6
|
+
module ActiveAttr
|
7
|
+
# Overwritting ActiveAttr::Dirty module
|
8
|
+
module Dirty
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
include ActiveModel::Dirty
|
11
|
+
|
12
|
+
# Class methods for ActiveAttr::Dirty module
|
13
|
+
module ClassMethods
|
14
|
+
# Overwritten attribute! method
|
15
|
+
# @param [Symbol] name
|
16
|
+
# @param [Hash] options
|
17
|
+
def attribute!(name, options={})
|
18
|
+
super(name, options)
|
19
|
+
define_method("#{name}=") do |value|
|
20
|
+
send("#{name}_will_change!") unless value == read_attribute(name)
|
21
|
+
super(value)
|
22
|
+
end
|
23
|
+
end
|
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
|
+
end
|
40
|
+
end
|
@@ -1,4 +1,7 @@
|
|
1
1
|
# Syncano instance name
|
2
2
|
SYNCANO_INSTANCE_NAME = 'instance_name'
|
3
3
|
# Syncano api key
|
4
|
-
SYNCANO_API_KEY = 'api_key'
|
4
|
+
SYNCANO_API_KEY = 'api_key'
|
5
|
+
|
6
|
+
# Collection used for Syncano::ActiveRecord
|
7
|
+
# SYNCANO_ACTIVERECORD_COLLECTION = Syncano.client.projects.first.collections.first
|
data/lib/syncano.rb
CHANGED
@@ -59,6 +59,16 @@ require 'active_support/core_ext/object/blank.rb'
|
|
59
59
|
require 'active_support/json/decoding.rb'
|
60
60
|
require 'active_support/json/encoding.rb'
|
61
61
|
require 'active_support/time_with_zone.rb'
|
62
|
+
require 'active_support/concern'
|
63
|
+
require 'active_support/inflector/inflections'
|
64
|
+
|
65
|
+
# ActiveModel
|
66
|
+
require 'active_model/forbidden_attributes_protection'
|
67
|
+
require 'active_model/dirty'
|
68
|
+
|
69
|
+
# ActiveAttr
|
70
|
+
require 'active_attr/model'
|
71
|
+
require 'active_attr/dirty'
|
62
72
|
|
63
73
|
# Syncano
|
64
74
|
require 'syncano/errors'
|
@@ -92,4 +102,5 @@ require 'syncano/resources/notifications/base'
|
|
92
102
|
require 'syncano/resources/notifications/create'
|
93
103
|
require 'syncano/resources/notifications/update'
|
94
104
|
require 'syncano/resources/notifications/destroy'
|
95
|
-
require 'syncano/resources/notifications/message'
|
105
|
+
require 'syncano/resources/notifications/message'
|
106
|
+
require 'syncano/active_record/base'
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'syncano/active_record/scope_builder'
|
2
|
+
|
3
|
+
class Syncano
|
4
|
+
module ActiveRecord
|
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)
|
13
|
+
self.source_model = source_model
|
14
|
+
self.associated_model = name.to_s.classify.constantize
|
15
|
+
self.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/active_record/association/base'
|
2
|
+
|
3
|
+
class Syncano
|
4
|
+
module ActiveRecord
|
5
|
+
module Association
|
6
|
+
# Class for belongs to association
|
7
|
+
class BelongsTo < Syncano::ActiveRecord::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)
|
14
|
+
super
|
15
|
+
self.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,69 @@
|
|
1
|
+
require 'syncano/active_record/association/base'
|
2
|
+
|
3
|
+
class Syncano
|
4
|
+
module ActiveRecord
|
5
|
+
module Association
|
6
|
+
# Class for has many association
|
7
|
+
class HasMany < Syncano::ActiveRecord::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
|
28
|
+
associated_model.new(foreign_key => source.id)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Creates new associated object
|
32
|
+
# @return [Object]
|
33
|
+
def create
|
34
|
+
associated_model.create(foreign_key => source.id)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Adds object to the related collection by setting foreign key
|
38
|
+
# @param [Object] object
|
39
|
+
# @return [Object]
|
40
|
+
def <<(object)
|
41
|
+
object.send("#{foreign_key}=", source.id)
|
42
|
+
object.save unless object.new_record?
|
43
|
+
object
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
attr_accessor :source
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
attr_writer :associated_model, :foreign_key, :source_model
|
53
|
+
|
54
|
+
# Overwritten method_missing for handling scope methods
|
55
|
+
# @param [String] name
|
56
|
+
# @param [Array] args
|
57
|
+
def method_missing(name, *args)
|
58
|
+
scope_builder = Syncano::ActiveRecord::ScopeBuilder.new(associated_model).by_parent_id(source.id)
|
59
|
+
|
60
|
+
if scope_builder.respond_to?(name) || !source.scopes[name].nil?
|
61
|
+
scope_builder.send(name, *args)
|
62
|
+
else
|
63
|
+
super
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'syncano/active_record/association/base'
|
2
|
+
|
3
|
+
class Syncano
|
4
|
+
module ActiveRecord
|
5
|
+
module Association
|
6
|
+
# Class for has one association
|
7
|
+
class HasOne < Syncano::ActiveRecord::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,112 @@
|
|
1
|
+
require 'syncano/active_record/association/belongs_to'
|
2
|
+
require 'syncano/active_record/association/has_many'
|
3
|
+
require 'syncano/active_record/association/has_one'
|
4
|
+
|
5
|
+
class Syncano
|
6
|
+
module ActiveRecord
|
7
|
+
# Module with associations functionality for Syncano::ActiveRecord
|
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::ActiveRecord::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
|
+
# Setter for associations
|
28
|
+
def associations=(hash)
|
29
|
+
self._associations = hash
|
30
|
+
end
|
31
|
+
|
32
|
+
# Defines belongs_to association
|
33
|
+
# @param [Symbol] object_name
|
34
|
+
def belongs_to(object_name)
|
35
|
+
association = Syncano::ActiveRecord::Association::BelongsTo.new(self, object_name)
|
36
|
+
associations[object_name] = association
|
37
|
+
|
38
|
+
attribute association.foreign_key
|
39
|
+
validates association.foreign_key, numericality: { only_integer: true, allow_nil: true }
|
40
|
+
|
41
|
+
define_method(object_name) do
|
42
|
+
id = send(self.class.associations[object_name].foreign_key)
|
43
|
+
scope = scope_builder(self.class.associations[object_name].associated_model)
|
44
|
+
scope.find(id) if id.present?
|
45
|
+
end
|
46
|
+
|
47
|
+
define_method("#{object_name}=") do |object|
|
48
|
+
unless object.is_a?(self.class.associations[object_name].associated_model)
|
49
|
+
"Object should be an instance of #{self.class.associations[object_name].associated_model} class"
|
50
|
+
end
|
51
|
+
send("#{self.class.associations[object_name].foreign_key}=", object.try(:id))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Defines has_one association
|
56
|
+
# @param [Symbol] object_name
|
57
|
+
def has_one(object_name)
|
58
|
+
association = Syncano::ActiveRecord::Association::HasOne.new(self, object_name)
|
59
|
+
associations[object_name] = association
|
60
|
+
|
61
|
+
define_method(object_name) do
|
62
|
+
scope = scope_builder(self.class.associations[object_name].associated_model)
|
63
|
+
scope.by_parent_id(id).first if id
|
64
|
+
end
|
65
|
+
|
66
|
+
define_method("#{object_name}=") do |object|
|
67
|
+
object.send("#{self.class.associations[object_name].foreign_key}=", id)
|
68
|
+
object.save unless object.new_record?
|
69
|
+
object
|
70
|
+
end
|
71
|
+
|
72
|
+
define_method("build_#{object_name}") do |attributes = {}|
|
73
|
+
self.class.associations[object_name].associated_model.new(attributes)
|
74
|
+
end
|
75
|
+
|
76
|
+
define_method("create_#{object_name}") do |attributes = {}|
|
77
|
+
self.class.associations[object_name].associated_model.create(attributes)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Defines has_many association
|
82
|
+
# @param [Symbol] collection_name
|
83
|
+
def has_many(collection_name)
|
84
|
+
association = Syncano::ActiveRecord::Association::HasMany.new(self, collection_name)
|
85
|
+
associations[collection_name] = association
|
86
|
+
|
87
|
+
define_method(collection_name) do
|
88
|
+
self.class.associations[collection_name].scope_builder(self)
|
89
|
+
end
|
90
|
+
|
91
|
+
define_method("#{collection_name}=") do |collection|
|
92
|
+
association = self.class.associations[collection_name]
|
93
|
+
|
94
|
+
collection.each do |object|
|
95
|
+
"Object should be an instance of #{association.associated_class} class" unless object.is_a?(association.associated_class)
|
96
|
+
end
|
97
|
+
|
98
|
+
send(collection_name).all.each do |object|
|
99
|
+
object.send("#{association.foreign_key}=", nil)
|
100
|
+
object.save
|
101
|
+
end
|
102
|
+
|
103
|
+
collection.each do |object|
|
104
|
+
object.send("#{association.foreign_key}=", id)
|
105
|
+
object.save
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,318 @@
|
|
1
|
+
require 'syncano/active_record/scope_builder'
|
2
|
+
require 'syncano/active_record/associations'
|
3
|
+
require 'syncano/active_record/callbacks'
|
4
|
+
|
5
|
+
class Syncano
|
6
|
+
# Scope for modules and classes integrating ActiveRecord functionality
|
7
|
+
module ActiveRecord
|
8
|
+
# Class for integrating ActiveRecord functionality
|
9
|
+
class Base
|
10
|
+
include ActiveAttr::Model
|
11
|
+
include ActiveAttr::Dirty
|
12
|
+
include ActiveModel::ForbiddenAttributesProtection
|
13
|
+
include Syncano::ActiveRecord::Associations
|
14
|
+
include Syncano::ActiveRecord::Callbacks
|
15
|
+
|
16
|
+
attribute :id, type: Integer
|
17
|
+
attribute :created_at, type: DateTime
|
18
|
+
attribute :updated_at, type: DateTime
|
19
|
+
|
20
|
+
# Gets collection with all objects
|
21
|
+
# @return [Array]
|
22
|
+
def self.all
|
23
|
+
scope_builder.all
|
24
|
+
end
|
25
|
+
|
26
|
+
# Counts all objects
|
27
|
+
# @return [Integer]
|
28
|
+
def self.count
|
29
|
+
scope_builder.count
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns first object or collection of first x objects
|
33
|
+
# @param [Integer] amount
|
34
|
+
# @return [Object, Array]
|
35
|
+
def self.first(amount = nil)
|
36
|
+
scope_builder.first(amount)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns last object or collection of last x objects
|
40
|
+
# @param [Integer] amount
|
41
|
+
# @return [Object, Array]
|
42
|
+
def self.last(amount = nil)
|
43
|
+
scope_builder.last(amount)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns scope builder with condition passed as arguments
|
47
|
+
# @param [String] condition
|
48
|
+
# @param [Array] params
|
49
|
+
# @return [Syncano::ActiveRecord::ScopeBuilder]
|
50
|
+
def self.where(condition, *params)
|
51
|
+
scope_builder.where(condition, *params)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns scope builder with order passed as first argument
|
55
|
+
# @param [String] order
|
56
|
+
# @return [Syncano::ActiveRecord::ScopeBuilder]
|
57
|
+
def self.order(order)
|
58
|
+
scope_builder.order(order)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns one object found by id
|
62
|
+
# @param [Integer] id
|
63
|
+
# @return [Object]
|
64
|
+
def self.find(id)
|
65
|
+
scope_builder.find(id)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Creates new object with specified attributes
|
69
|
+
# @param [Hash] attributes
|
70
|
+
# @return [Object]
|
71
|
+
def self.create(attributes)
|
72
|
+
new_object = self.new(attributes)
|
73
|
+
new_object.save
|
74
|
+
new_object
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns scope builder with filtering by ids newer than provided
|
78
|
+
# @param [Integer] id
|
79
|
+
# @return [Syncano::ActiveRecord::ScopeBuilder]
|
80
|
+
def self.since(id)
|
81
|
+
scope_builder.since(id)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns scope builder with filtering by ids older than provided
|
85
|
+
# @param [Integer] id
|
86
|
+
# @return [Syncano::ActiveRecord::ScopeBuilder]
|
87
|
+
def self.before(id)
|
88
|
+
scope_builder.before(id)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns corresponding Syncano folder
|
92
|
+
# @return [Syncano::Resources::Folder]
|
93
|
+
def self.folder
|
94
|
+
begin
|
95
|
+
folder = collection.folders.find_by_name(folder_name)
|
96
|
+
rescue Syncano::ApiError => e
|
97
|
+
if e.message.starts_with?('DoesNotExist')
|
98
|
+
folder = collection.folders.create(name: folder_name)
|
99
|
+
else
|
100
|
+
raise e
|
101
|
+
end
|
102
|
+
end
|
103
|
+
folder
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns scope builder with limit parameter set to parameter
|
107
|
+
# @param [Integer] amount
|
108
|
+
# @return [Syncano::ActiveRecord::ScopeBuilder]
|
109
|
+
def self.limit(amount)
|
110
|
+
scope_builder.limit(amount)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Returns hash with filterable attributes
|
114
|
+
# @return [HashWithIndifferentAccess]
|
115
|
+
def self.filterable_attributes
|
116
|
+
self._filterable_attributes ||= HashWithIndifferentAccess.new
|
117
|
+
end
|
118
|
+
|
119
|
+
# Returns hash with scopes
|
120
|
+
# @return [HashWithIndifferentAccess]
|
121
|
+
def self.scopes
|
122
|
+
self._scopes ||= HashWithIndifferentAccess.new
|
123
|
+
end
|
124
|
+
|
125
|
+
# Maps syncano attributes to corresponding model attributes
|
126
|
+
# @param [Hash] attributes
|
127
|
+
# @return [HashWithIndifferentAccess]
|
128
|
+
def self.map_from_syncano_attributes(attributes = {})
|
129
|
+
mappings = HashWithIndifferentAccess.new(filterable_attributes.invert)
|
130
|
+
HashWithIndifferentAccess[attributes.map {|k, v| [mappings[k] || k, v] }]
|
131
|
+
end
|
132
|
+
|
133
|
+
# Maps model attributes to corresponding syncano attributes
|
134
|
+
# @param [Hash] attributes
|
135
|
+
# @return [HashWithIndifferentAccess]
|
136
|
+
def self.map_to_syncano_attributes(attributes = {})
|
137
|
+
mappings = filterable_attributes
|
138
|
+
HashWithIndifferentAccess[attributes.map {|k, v| [mappings[k] || k, v] }]
|
139
|
+
end
|
140
|
+
|
141
|
+
# Maps one model attribute to corresponding syncano attribute
|
142
|
+
# @param [Symbol, String] attribute
|
143
|
+
# @return [String]
|
144
|
+
def self.map_to_syncano_attribute(attribute)
|
145
|
+
mappings = filterable_attributes
|
146
|
+
mappings[attribute] || attribute
|
147
|
+
end
|
148
|
+
|
149
|
+
# Constructor for model
|
150
|
+
# @param [Hash] params
|
151
|
+
def initialize(params = {})
|
152
|
+
if params.is_a?(Syncano::Resources::DataObject)
|
153
|
+
super(self.class.map_from_syncano_attributes(params.attributes).merge(id: params.id))
|
154
|
+
else
|
155
|
+
params.delete(:id)
|
156
|
+
super(self.class.map_from_syncano_attributes(params))
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Overwritten equality operator
|
161
|
+
# @param [Object] object
|
162
|
+
# @return [TrueClass, FalseClass]
|
163
|
+
def ==(object)
|
164
|
+
self.class == object.class && self.id == object.id
|
165
|
+
end
|
166
|
+
|
167
|
+
# Updates object with specified attributes
|
168
|
+
# @param [Hash] attributes
|
169
|
+
# @return [TrueClass, FalseClass]
|
170
|
+
def update_attributes(attributes)
|
171
|
+
self.attributes = attributes
|
172
|
+
self.save
|
173
|
+
end
|
174
|
+
|
175
|
+
# Performs validations
|
176
|
+
# @return [TrueClass, FalseClass]
|
177
|
+
def valid?
|
178
|
+
process_callbacks(:before_validation)
|
179
|
+
process_callbacks(:after_validation) if result = super
|
180
|
+
result
|
181
|
+
end
|
182
|
+
|
183
|
+
# Saves object in Syncano
|
184
|
+
# @return [TrueClass, FalseClass]
|
185
|
+
def save
|
186
|
+
saved = false
|
187
|
+
|
188
|
+
if valid?
|
189
|
+
process_callbacks(:before_save)
|
190
|
+
process_callbacks(persisted? ? :before_update : :before_create)
|
191
|
+
|
192
|
+
data_object = persisted? ? self.class.folder.data_objects.find(id) : self.class.folder.data_objects.new
|
193
|
+
data_object.attributes = self.class.map_to_syncano_attributes(attributes.except(:id, :created_at, :updated_at))
|
194
|
+
data_object.save
|
195
|
+
|
196
|
+
if data_object.saved?
|
197
|
+
self.updated_at = data_object[:updated_at]
|
198
|
+
|
199
|
+
if persisted?
|
200
|
+
process_callbacks(:after_update)
|
201
|
+
else
|
202
|
+
self.id = data_object.id
|
203
|
+
self.created_at = data_object[:created_at]
|
204
|
+
process_callbacks(:after_create)
|
205
|
+
end
|
206
|
+
|
207
|
+
self.class.associations.values.select{ |association| association.belongs_to? }.each do |association|
|
208
|
+
change = changes[association.foreign_key]
|
209
|
+
|
210
|
+
if change.present?
|
211
|
+
if change.last.nil? || association.associated_model.find(change.last).present?
|
212
|
+
data_object.remove_parent(change.first) unless change.first.nil?
|
213
|
+
data_object.add_parent(change.last) unless change.last.nil?
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
super
|
219
|
+
|
220
|
+
process_callbacks(:after_save)
|
221
|
+
saved = true
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
saved
|
226
|
+
end
|
227
|
+
|
228
|
+
# Deletes object from Syncano
|
229
|
+
# @return [TrueClass, FalseClass]
|
230
|
+
def destroy
|
231
|
+
process_callbacks(:before_destroy)
|
232
|
+
data_object = self.class.folder.data_objects.find(id)
|
233
|
+
data_object.destroy
|
234
|
+
process_callbacks(:after_destroy) if data_object.destroyed
|
235
|
+
data_object.destroyed
|
236
|
+
end
|
237
|
+
|
238
|
+
# Checks if object has not been saved in Syncano yet
|
239
|
+
# @return [TrueClass, FalseClass]
|
240
|
+
def new_record?
|
241
|
+
!persisted?
|
242
|
+
end
|
243
|
+
|
244
|
+
# Checks if object has been already saved in Syncano
|
245
|
+
# @return [TrueClass, FalseClass]
|
246
|
+
def persisted?
|
247
|
+
id.present?
|
248
|
+
end
|
249
|
+
|
250
|
+
private
|
251
|
+
|
252
|
+
class_attribute :_filterable_attributes, :_scopes
|
253
|
+
|
254
|
+
# Returns Syncano collection for storing Syncano::ActiveRecord objects
|
255
|
+
# @return [Syncano::Resource::Collection]
|
256
|
+
def self.collection
|
257
|
+
SYNCANO_ACTIVERECORD_COLLECTION
|
258
|
+
end
|
259
|
+
|
260
|
+
# Returns Syncano collection for storing Syncano::ActiveRecord objects
|
261
|
+
# @return [Syncano::Resource::Collection]
|
262
|
+
def self.folder_name
|
263
|
+
name.pluralize
|
264
|
+
end
|
265
|
+
|
266
|
+
# Setter for filterable_attributes attribute
|
267
|
+
def self.filterable_attributes=(hash)
|
268
|
+
self._filterable_attributes = hash
|
269
|
+
end
|
270
|
+
|
271
|
+
# Setter for scopes attribute
|
272
|
+
def self.scopes=(hash)
|
273
|
+
self._scopes = hash
|
274
|
+
end
|
275
|
+
|
276
|
+
# Returns scope builder for current model
|
277
|
+
# @return [Syncano::ActiveRecord::ScopeBuilder]
|
278
|
+
def self.scope_builder
|
279
|
+
Syncano::ActiveRecord::ScopeBuilder.new(self)
|
280
|
+
end
|
281
|
+
|
282
|
+
# Defines model attribute
|
283
|
+
# @param [Symbol] name
|
284
|
+
# @param [Hash] options
|
285
|
+
def self.attribute(name, options = {})
|
286
|
+
if options[:filterable].present?
|
287
|
+
self.filterable_attributes = HashWithIndifferentAccess.new if filterable_attributes.nil?
|
288
|
+
filterable_attributes[name] = options.delete(:filterable)
|
289
|
+
end
|
290
|
+
super(name, options)
|
291
|
+
end
|
292
|
+
|
293
|
+
# Defines model scope
|
294
|
+
# @param [Symbol] name
|
295
|
+
# @param [Proc] procedure
|
296
|
+
def self.scope(name, procedure)
|
297
|
+
scopes[name] = procedure
|
298
|
+
end
|
299
|
+
|
300
|
+
# Overwritten method_missing for handling calling defined scopes
|
301
|
+
# @param [String] name
|
302
|
+
# @param [Array] args
|
303
|
+
def self.method_missing(name, *args)
|
304
|
+
if scopes[name].nil?
|
305
|
+
super
|
306
|
+
else
|
307
|
+
scope_builder.send(name.to_sym, *args)
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
# Returns scope builder for specified class
|
312
|
+
# @return [Syncano::ActiveRecord::ScopeBuilder]
|
313
|
+
def scope_builder(object_class)
|
314
|
+
Syncano::ActiveRecord::ScopeBuilder.new(object_class)
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Syncano
|
2
|
+
module ActiveRecord
|
3
|
+
# Module with callbacks functionality for Syncano::ActiveRecord
|
4
|
+
module Callbacks
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
# Defines chains for all types of callbacks
|
9
|
+
[:validation, :save, :create, :update, :destroy].each do |action|
|
10
|
+
[:before, :after].each do |type|
|
11
|
+
chain_name = "#{type}_#{action}_callbacks"
|
12
|
+
class_attribute chain_name
|
13
|
+
send("#{chain_name}=", [])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Class methods for Syncano::ActiveRecord::Callbacks module
|
19
|
+
module ClassMethods
|
20
|
+
private
|
21
|
+
|
22
|
+
[:validation, :save, :create, :update, :destroy].each do |action|
|
23
|
+
[:before, :after].each do |type|
|
24
|
+
define_method("prepend_#{type}_#{action}") do |argument|
|
25
|
+
send("#{type}_#{action}_callbacks").unshift(argument)
|
26
|
+
end
|
27
|
+
|
28
|
+
define_method("#{type}_#{action}") do |argument|
|
29
|
+
send("#{type}_#{action}_callbacks") << argument
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Processes callbacks with specified type
|
36
|
+
# @param [Symbol, String] type
|
37
|
+
def process_callbacks(type)
|
38
|
+
if respond_to?("#{type}_callbacks")
|
39
|
+
send("#{type}_callbacks").each do |callback_name|
|
40
|
+
send(callback_name)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
class Syncano
|
2
|
+
module ActiveRecord
|
3
|
+
# ScopeBuilder class allows for creating and chaining more complex queries
|
4
|
+
class ScopeBuilder
|
5
|
+
# Constructor for ScopeBuilder
|
6
|
+
# @param [Class] model
|
7
|
+
def initialize(model)
|
8
|
+
raise 'Model should be a class extending module Syncano::ActiveRecord::Base' unless model <= Syncano::ActiveRecord::Base
|
9
|
+
|
10
|
+
self.model = model
|
11
|
+
self.parameters = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns collection of objects
|
15
|
+
# @return [Array]
|
16
|
+
def all
|
17
|
+
folder.data_objects.all(parameters).collect do |data_object|
|
18
|
+
model.new(data_object)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns count of objects
|
23
|
+
# @return [Integer]
|
24
|
+
def count
|
25
|
+
folder.data_objects.all(parameters).count
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns one object found by id
|
29
|
+
# @param [Integer] id
|
30
|
+
# @return [Object]
|
31
|
+
def find(id)
|
32
|
+
parameters[:data_ids] = [id]
|
33
|
+
all.first
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns first object or collection of first x objects
|
37
|
+
# @param [Integer] amount
|
38
|
+
# @return [Object, Array]
|
39
|
+
def first(amount = nil)
|
40
|
+
objects = all.first(amount || 1)
|
41
|
+
amount.nil? ? objects.first : objects
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns last object or last x objects
|
45
|
+
# @param [Integer] amount
|
46
|
+
# @return [Object, Array]
|
47
|
+
def last(amount)
|
48
|
+
objects = all.last(amount || 1)
|
49
|
+
amount.nil? ? objects.first : objects
|
50
|
+
end
|
51
|
+
|
52
|
+
# Adds to the current scope builder condition to the scope builder
|
53
|
+
# @param [String] condition
|
54
|
+
# @param [Array] params
|
55
|
+
# @return [Syncano::ActiveRecord::ScopeBuilder]
|
56
|
+
def where(condition, *params)
|
57
|
+
raise 'Invalid params count in where clause!' unless condition.count('?') == params.count
|
58
|
+
|
59
|
+
params.each do |param|
|
60
|
+
condition.sub!('?', param.to_s)
|
61
|
+
end
|
62
|
+
|
63
|
+
conditions = condition.gsub(/\s+/, ' ').split(/and/i)
|
64
|
+
|
65
|
+
conditions.each do |condition|
|
66
|
+
attribute, operator, value = condition.split(' ')
|
67
|
+
|
68
|
+
raise 'Invalid attribute in where clause!' unless model.attributes.keys.include?(attribute)
|
69
|
+
raise 'Invalid operator in where clause!' unless self.class.where_mapping.keys.include?(operator)
|
70
|
+
raise 'Parameter in where clause is not an integer!' if !(value =~ /\A[-+]?[0-9]+\z/)
|
71
|
+
|
72
|
+
method_name = "#{model.filterable_attributes[attribute]}__#{self.class.where_mapping[operator]}"
|
73
|
+
parameters[method_name] = value
|
74
|
+
end
|
75
|
+
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
79
|
+
# Adds to the current scope builder condition for filtering by parent_id
|
80
|
+
# @param [Integer] parent_id
|
81
|
+
# @return [Syncano::ActiveRecord::ScopeBuilder]
|
82
|
+
def by_parent_id(parent_id)
|
83
|
+
parameters[:parent_ids] = parent_id
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
# Adds to the current scope builder order clause
|
88
|
+
# @param [String] order
|
89
|
+
# @return [Syncano::ActiveRecord::ScopeBuilder]
|
90
|
+
def order(order)
|
91
|
+
attribute, order_type = order.gsub(/\s+/, ' ').split(' ')
|
92
|
+
raise 'Invalid attribute in order clause' unless (model.attributes.keys + ['id', 'created_at']).include?(attribute)
|
93
|
+
|
94
|
+
attribute = model.map_to_syncano_attribute(attribute)
|
95
|
+
order_type = order_type.to_s.downcase == 'desc' ? 'DESC' : 'ASC'
|
96
|
+
|
97
|
+
self.parameters.merge!({ order_by: attribute, order: order_type })
|
98
|
+
|
99
|
+
self
|
100
|
+
end
|
101
|
+
|
102
|
+
# Adds to the current scope builder condition for filtering by ids newer than provided
|
103
|
+
# @param [Integer, String] id - id or datetime
|
104
|
+
# @return [Syncano::ActiveRecord::ScopeBuilder]
|
105
|
+
def since(id)
|
106
|
+
if !(id =~ /\A[-+]?[0-9]+\z/)
|
107
|
+
self.parameters[:since] = id
|
108
|
+
else
|
109
|
+
self.parameters[:since_time] = id.to_time
|
110
|
+
end
|
111
|
+
self
|
112
|
+
end
|
113
|
+
|
114
|
+
# Adds to the current scope builder condition for filtering by ids older than provided
|
115
|
+
# @param [Integer] id
|
116
|
+
# @return [Syncano::ActiveRecord::ScopeBuilder]
|
117
|
+
def before(id)
|
118
|
+
self.parameters[:max_id] = id
|
119
|
+
self
|
120
|
+
end
|
121
|
+
|
122
|
+
# Adds to the current scope builder limit clause
|
123
|
+
# @param [Integer] amount
|
124
|
+
# @return [Syncano::ActiveRecord::ScopeBuilder]
|
125
|
+
def limit(amount)
|
126
|
+
self.parameters[:limit] = amount
|
127
|
+
self
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
attr_accessor :parameters, :model, :scopes
|
133
|
+
|
134
|
+
# Returns folder for current model
|
135
|
+
# @return [Syncano::Resources::Folder]
|
136
|
+
def folder
|
137
|
+
model.folder
|
138
|
+
end
|
139
|
+
|
140
|
+
# Returns scopes for current model
|
141
|
+
# @return [HashWithIndifferentAccess]
|
142
|
+
def scopes
|
143
|
+
model.scopes
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns mapping for operators
|
147
|
+
# @return [Hash]
|
148
|
+
def self.where_mapping
|
149
|
+
{ '=' => 'eq', '!=' => 'neq', '<>' => 'neq', '>=' => 'gte', '>' => 'gt', '<=' => 'lte', '<' => 'lt' }
|
150
|
+
end
|
151
|
+
|
152
|
+
# Applies scope to the current scope builder
|
153
|
+
# @param [Symbol] name
|
154
|
+
# @param [Array] args
|
155
|
+
# @return [Syncano::ActiveRecord::ScopeBuilder]
|
156
|
+
def execute_scope(name, *args)
|
157
|
+
procedure = scopes[name]
|
158
|
+
instance_exec(*args, &procedure)
|
159
|
+
self
|
160
|
+
end
|
161
|
+
|
162
|
+
# Overwritten method_missing for handling calling defined scopes
|
163
|
+
# @param [String] name
|
164
|
+
# @param [Array] args
|
165
|
+
def method_missing(name, *args)
|
166
|
+
if scopes[name].nil?
|
167
|
+
super
|
168
|
+
else
|
169
|
+
execute_scope(name, *args)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -126,16 +126,18 @@ class Syncano
|
|
126
126
|
# @return [Syncano::Resources::Base]
|
127
127
|
def save
|
128
128
|
response = perform_save(nil)
|
129
|
+
response_data = ActiveSupport::HashWithIndifferentAccess.new(response.data)
|
129
130
|
|
130
131
|
if new_record?
|
131
|
-
response_data = ActiveSupport::HashWithIndifferentAccess.new(response.data)
|
132
132
|
created_object = self.class.new(client, self.class.map_to_scope_parameters(attributes).merge(response_data))
|
133
133
|
|
134
134
|
self.id = created_object.id
|
135
135
|
self.attributes.merge!(created_object.attributes)
|
136
|
-
|
136
|
+
else
|
137
|
+
self[:updated_at] = response_data[:updated_at]
|
137
138
|
end
|
138
139
|
|
140
|
+
mark_as_saved!
|
139
141
|
self
|
140
142
|
end
|
141
143
|
|
@@ -211,6 +211,11 @@ class Syncano
|
|
211
211
|
end
|
212
212
|
end
|
213
213
|
|
214
|
+
if attributes.keys.map(&:to_sym).include?(:folders) && !attributes.keys.map(&:to_sym).include?(:folder)
|
215
|
+
attributes[:folder] = attributes[:folders]
|
216
|
+
attributes.delete(:folders)
|
217
|
+
end
|
218
|
+
|
214
219
|
attributes.delete(:user)
|
215
220
|
attributes.delete(:created_at)
|
216
221
|
attributes.delete(:updated_at)
|
@@ -5,7 +5,7 @@ class Syncano
|
|
5
5
|
# Association has_many :data_objects
|
6
6
|
# @return [Syncano::QueryBuilder] query builder for resource Syncano::Resources::DataObject
|
7
7
|
def data_objects
|
8
|
-
::Syncano::QueryBuilder.new(client, ::Syncano::Resources::DataObject, scope_parameters.merge(
|
8
|
+
::Syncano::QueryBuilder.new(client, ::Syncano::Resources::DataObject, scope_parameters.merge(folders: @saved_attributes[:name]))
|
9
9
|
end
|
10
10
|
|
11
11
|
# Wrapper for api "get_one" method with folder_name as a key
|
data/lib/syncano/version.rb
CHANGED
data/syncano.gemspec
CHANGED
@@ -20,8 +20,10 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_dependency 'jimson-client'
|
22
22
|
spec.add_dependency 'activesupport'
|
23
|
+
spec.add_dependency 'activemodel'
|
23
24
|
spec.add_dependency 'multi_json', '~> 1.10'
|
24
25
|
spec.add_dependency 'eventmachine'
|
26
|
+
spec.add_dependency 'active_attr'
|
25
27
|
|
26
28
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
27
29
|
spec.add_development_dependency 'rake'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: syncano
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.1.1
|
4
|
+
version: 3.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Zadrożny
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-07-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jimson-client
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activemodel
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: multi_json
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,6 +80,20 @@ dependencies:
|
|
66
80
|
- - ">="
|
67
81
|
- !ruby/object:Gem::Version
|
68
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: active_attr
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
69
97
|
- !ruby/object:Gem::Dependency
|
70
98
|
name: bundler
|
71
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -163,9 +191,18 @@ files:
|
|
163
191
|
- LICENSE.txt
|
164
192
|
- README.md
|
165
193
|
- Rakefile
|
194
|
+
- lib/active_attr/dirty.rb
|
166
195
|
- lib/generators/syncano/install_generator.rb
|
167
196
|
- lib/generators/syncano/templates/initializers/syncano.rb
|
168
197
|
- lib/syncano.rb
|
198
|
+
- lib/syncano/active_record/association/base.rb
|
199
|
+
- lib/syncano/active_record/association/belongs_to.rb
|
200
|
+
- lib/syncano/active_record/association/has_many.rb
|
201
|
+
- lib/syncano/active_record/association/has_one.rb
|
202
|
+
- lib/syncano/active_record/associations.rb
|
203
|
+
- lib/syncano/active_record/base.rb
|
204
|
+
- lib/syncano/active_record/callbacks.rb
|
205
|
+
- lib/syncano/active_record/scope_builder.rb
|
169
206
|
- lib/syncano/batch_queue.rb
|
170
207
|
- lib/syncano/batch_queue_element.rb
|
171
208
|
- lib/syncano/clients/base.rb
|
@@ -227,9 +264,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
227
264
|
version: '0'
|
228
265
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
229
266
|
requirements:
|
230
|
-
- - "
|
267
|
+
- - ">="
|
231
268
|
- !ruby/object:Gem::Version
|
232
|
-
version:
|
269
|
+
version: '0'
|
233
270
|
requirements: []
|
234
271
|
rubyforge_project:
|
235
272
|
rubygems_version: 2.2.2
|
@@ -248,3 +285,4 @@ test_files:
|
|
248
285
|
- spec/spec_helper.rb
|
249
286
|
- spec/sync_resources_spec.rb
|
250
287
|
- spec/syncano_spec.rb
|
288
|
+
has_rdoc:
|