synced 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d8fd447ac4edfd7650c0cbc27b6dd390e01709b9
4
- data.tar.gz: e0bc89a27dacb6abd1039c0e0c86eb233d85266e
3
+ metadata.gz: 9269d91cf4e1c50718fb11cc8c3a6860ede34a3a
4
+ data.tar.gz: 07a7b737a0d403e3e78011a2dd9b49385e65bfa9
5
5
  SHA512:
6
- metadata.gz: b037b0f4e35a473b0c0d0ea4e9cc95161f362620af9ad3a5b6aabad5590080c3b6133fcfab6fc2568144e1e1b0b0d34ae7bc4fb58ed8fd79e52768a1838d6e48
7
- data.tar.gz: cc2c8a5fcdb35be25a7dd4ebba407f6b7be476066bd95a6b60a37d6cbb44a4b78dff786f5396638cfd17271d616e09c84f9bf8ae5b057b47f397581335492e45
6
+ metadata.gz: e787d2b0ccca79dd8cd4dbe21814bbef16664e88290a82185253a88e4e4088cac2c30b8c925b8e01e2048570d552b0696151f06e1d9dde0ddcf8b5edca767613
7
+ data.tar.gz: 5236ccc2bebfbd74810f21d29630075322426a6c5ffc9e60fed447b912637b05875a96f74536240c9212764217c600a907633bf87355364159400b25b522cec1
@@ -3,39 +3,41 @@ require 'hashie'
3
3
  # Provide a serialized attribute for models. This attribute is `synced_data_key`
4
4
  # which by default is `:synced_data`. This is a friendlier alternative to
5
5
  # `serialize` with respect to dirty attributes.
6
- module Synced::HasSyncedData
7
- extend ActiveSupport::Concern
8
- class SyncedData < Hashie::Mash; end
6
+ module Synced
7
+ module HasSyncedData
8
+ extend ActiveSupport::Concern
9
+ class SyncedData < Hashie::Mash; end
9
10
 
10
- included do
11
- if synced_data_key
12
- define_method "#{synced_data_key}=" do |object|
13
- write_attribute synced_data_key, dump(object)
14
- end
11
+ included do
12
+ if synced_data_key
13
+ define_method "#{synced_data_key}=" do |object|
14
+ write_attribute synced_data_key, dump(object)
15
+ end
15
16
 
16
- define_method synced_data_key do
17
- instance_variable_get("@#{synced_data_key}") ||
18
- instance_variable_set("@#{synced_data_key}",
19
- SyncedData.new(loaded_synced_data))
17
+ define_method synced_data_key do
18
+ instance_variable_get("@#{synced_data_key}") ||
19
+ instance_variable_set("@#{synced_data_key}",
20
+ SyncedData.new(loaded_synced_data))
21
+ end
20
22
  end
21
23
  end
22
- end
23
24
 
24
- private
25
+ private
25
26
 
26
- def loaded_synced_data
27
- if data = read_attribute(synced_data_key)
28
- load data
29
- else
30
- {}
27
+ def loaded_synced_data
28
+ if data = read_attribute(synced_data_key)
29
+ load data
30
+ else
31
+ {}
32
+ end
31
33
  end
32
- end
33
34
 
34
- def dump(object)
35
- JSON.dump object
36
- end
35
+ def dump(object)
36
+ JSON.dump object
37
+ end
37
38
 
38
- def load(source)
39
- JSON.load source
39
+ def load(source)
40
+ JSON.load source
41
+ end
40
42
  end
41
43
  end
@@ -1,75 +1,77 @@
1
1
  require "synced/synchronizer"
2
2
 
3
- module Synced::Model
4
- # Enables synced for ActiveRecord model.
5
- #
6
- # @param options [Hash] Configuration options for synced. They are inherited
7
- # by subclasses, but can be overwritten in the subclass.
8
- # @option options [Symbol] id_key: attribute name under which
9
- # remote object's ID is stored, default is :synced_id.
10
- # @option options [Symbol] synced_all_at_key: attribute name under which
11
- # last synchronization time is stored, default is :synced_all_at. It's only
12
- # used when only_updated option is enabled.
13
- # @option options [Boolean] only_updated: If true requests to API will take
14
- # advantage of updated_since param and fetch only created/changed/deleted
15
- # remote objects
16
- # @option options [Symbol] data_key: attribute name under which remote
17
- # object's data is stored.
18
- # @option options [Array] local_attributes: Array of attributes in the remote
19
- # object which will be mapped to local object attributes.
20
- def synced(options = {})
21
- class_attribute :synced_id_key, :synced_all_at_key, :synced_data_key,
22
- :synced_local_attributes, :synced_associations, :synced_only_updated
23
- self.synced_id_key = options.fetch(:id_key, :synced_id)
24
- self.synced_all_at_key = options.fetch(:synced_all_at_key,
25
- :synced_all_at)
26
- self.synced_data_key = options.fetch(:data_key, :synced_data)
27
- self.synced_local_attributes = options.fetch(:local_attributes, [])
28
- self.synced_associations = options.fetch(:associations, [])
29
- self.synced_only_updated = options.fetch(:only_updated, false)
30
- include Synced::HasSyncedData
31
- end
3
+ module Synced
4
+ module Model
5
+ # Enables synced for ActiveRecord model.
6
+ #
7
+ # @param options [Hash] Configuration options for synced. They are inherited
8
+ # by subclasses, but can be overwritten in the subclass.
9
+ # @option options [Symbol] id_key: attribute name under which
10
+ # remote object's ID is stored, default is :synced_id.
11
+ # @option options [Symbol] synced_all_at_key: attribute name under which
12
+ # last synchronization time is stored, default is :synced_all_at. It's only
13
+ # used when only_updated option is enabled.
14
+ # @option options [Boolean] only_updated: If true requests to API will take
15
+ # advantage of updated_since param and fetch only created/changed/deleted
16
+ # remote objects
17
+ # @option options [Symbol] data_key: attribute name under which remote
18
+ # object's data is stored.
19
+ # @option options [Array] local_attributes: Array of attributes in the remote
20
+ # object which will be mapped to local object attributes.
21
+ def synced(options = {})
22
+ class_attribute :synced_id_key, :synced_all_at_key, :synced_data_key,
23
+ :synced_local_attributes, :synced_associations, :synced_only_updated
24
+ self.synced_id_key = options.fetch(:id_key, :synced_id)
25
+ self.synced_all_at_key = options.fetch(:synced_all_at_key,
26
+ :synced_all_at)
27
+ self.synced_data_key = options.fetch(:data_key, :synced_data)
28
+ self.synced_local_attributes = options.fetch(:local_attributes, [])
29
+ self.synced_associations = options.fetch(:associations, [])
30
+ self.synced_only_updated = options.fetch(:only_updated, false)
31
+ include Synced::HasSyncedData
32
+ end
32
33
 
33
- # Performs synchronization of given remote objects to local database.
34
- #
35
- # @param remote [Array] - Remote objects to be synchronized with local db. If
36
- # it's nil then synchronizer will make request on it's own.
37
- # @param model_class [Class] - ActiveRecord model class to which remote objects
38
- # will be synchronized.
39
- # @param scope [ActiveRecord::Base] - Within this object scope local objects
40
- # will be synchronized. By default it's model_class.
41
- # @param remove [Boolean] - If it's true all local objects within
42
- # current scope which are not present in the remote array will be destroyed.
43
- # If only_updated is enabled, ids of objects to be deleted will be taken
44
- # from the meta part. By default if cancel_at column is present, all
45
- # missing local objects will be canceled with cancel_all,
46
- # if it's missing, all will be destroyed with destroy_all.
47
- # You can also force method to remove local objects by passing it
48
- # to remove: :mark_as_missing.
49
- # @example Synchronizing amenities
50
- #
51
- # Amenity.synchronize(remote: [remote_amenity1, remote_amenity2])
52
- #
53
- # @example Synchronizing rentals within given website. This will
54
- # create/remove/update rentals only within website.
55
- # It requires relation website.rentals to exist.
56
- #
57
- # Rental.synchronize(remote: remote_rentals, scope: website)
58
- #
59
- def synchronize(remote: nil, model_class: self, scope: nil, remove: false,
60
- include: nil)
61
- options = {
62
- scope: scope,
63
- id_key: synced_id_key,
64
- synced_all_at_key: synced_all_at_key,
65
- data_key: synced_data_key,
66
- remove: remove,
67
- local_attributes: synced_local_attributes,
68
- associations: synced_associations,
69
- only_updated: synced_only_updated,
70
- include: include
71
- }
72
- synchronizer = Synced::Synchronizer.new(remote, model_class, options)
73
- synchronizer.perform
34
+ # Performs synchronization of given remote objects to local database.
35
+ #
36
+ # @param remote [Array] - Remote objects to be synchronized with local db. If
37
+ # it's nil then synchronizer will make request on it's own.
38
+ # @param model_class [Class] - ActiveRecord model class to which remote objects
39
+ # will be synchronized.
40
+ # @param scope [ActiveRecord::Base] - Within this object scope local objects
41
+ # will be synchronized. By default it's model_class.
42
+ # @param remove [Boolean] - If it's true all local objects within
43
+ # current scope which are not present in the remote array will be destroyed.
44
+ # If only_updated is enabled, ids of objects to be deleted will be taken
45
+ # from the meta part. By default if cancel_at column is present, all
46
+ # missing local objects will be canceled with cancel_all,
47
+ # if it's missing, all will be destroyed with destroy_all.
48
+ # You can also force method to remove local objects by passing it
49
+ # to remove: :mark_as_missing.
50
+ # @example Synchronizing amenities
51
+ #
52
+ # Amenity.synchronize(remote: [remote_amenity1, remote_amenity2])
53
+ #
54
+ # @example Synchronizing rentals within given website. This will
55
+ # create/remove/update rentals only within website.
56
+ # It requires relation website.rentals to exist.
57
+ #
58
+ # Rental.synchronize(remote: remote_rentals, scope: website)
59
+ #
60
+ def synchronize(remote: nil, model_class: self, scope: nil, remove: false,
61
+ include: nil)
62
+ options = {
63
+ scope: scope,
64
+ id_key: synced_id_key,
65
+ synced_all_at_key: synced_all_at_key,
66
+ data_key: synced_data_key,
67
+ remove: remove,
68
+ local_attributes: synced_local_attributes,
69
+ associations: synced_associations,
70
+ only_updated: synced_only_updated,
71
+ include: include
72
+ }
73
+ synchronizer = Synced::Synchronizer.new(remote, model_class, options)
74
+ synchronizer.perform
75
+ end
74
76
  end
75
77
  end
@@ -1,4 +1,3 @@
1
- require "synced/has_synced_data"
2
1
  require "synced/model"
3
2
 
4
3
  module Synced
@@ -9,6 +8,10 @@ module Synced
9
8
  g.test_framework :rspec
10
9
  end
11
10
 
11
+ config.to_prepare do
12
+ require "synced/has_synced_data"
13
+ end
14
+
12
15
  ActiveSupport.on_load :active_record do
13
16
  extend Synced::Model
14
17
  end
@@ -1,194 +1,196 @@
1
1
  # Synchronizer class which performs actual synchronization between
2
2
  # local database and given array of remote objects
3
- class Synced::Synchronizer
4
- attr_reader :id_key
5
-
6
- # Initializes a new Synchronizer
7
- #
8
- # @param remote_objects [Array|NilClass] Array of objects to be synchronized
9
- # with local database. Objects need to respond to at least :id message.
10
- # If it's nil, then synchronizer will fetch the remote objects on it's own.
11
- # @param model_class [Class] ActiveRecord model class from which local objects
12
- # will be created.
13
- # @param options [Hash]
14
- # @option options [Symbol] scope: Within this object scope local objects
15
- # will be synchronized. By default it's model_class.
16
- # @option options [Symbol] id_key: attribute name under which
17
- # remote object's ID is stored, default is :synced_id.
18
- # @option options [Symbol] synced_all_at_key: attribute name under which
19
- # remote object's sync time is stored, default is :synced_all_at
20
- # @option options [Symbol] data_key: attribute name under which remote
21
- # object's data is stored.
22
- # @option options [Array] local_attributes: Array of attributes in the remote
23
- # object which will be mapped to local object attributes.
24
- # @option options [Boolean] remove: If it's true all local objects within
25
- # current scope which are not present in the remote array will be destroyed.
26
- # If only_updated is enabled, ids of objects to be deleted will be taken
27
- # from the meta part. By default if cancel_at column is present, all
28
- # missing local objects will be canceled with cancel_all,
29
- # if it's missing, all will be destroyed with destroy_all.
30
- # You can also force method to remove local objects by passing it
31
- # to remove: :mark_as_missing.
32
- # @option options [Boolean] only_updated: If true requests to API will take
33
- # advantage of updated_since param and fetch only created/changed/deleted
34
- # remote objects
35
- def initialize(remote_objects, model_class, options = {})
36
- @model_class = model_class
37
- @scope = options[:scope]
38
- @id_key = options[:id_key]
39
- @synced_all_at_key = options[:synced_all_at_key]
40
- @data_key = options[:data_key]
41
- @remove = options[:remove]
42
- @only_updated = options[:only_updated]
43
- @include = options[:include]
44
- @local_attributes = Array(options[:local_attributes])
45
- @associations = Array(options[:associations])
46
- @remote_objects = Array(remote_objects) if remote_objects
47
- @request_performed = false
48
- end
3
+ module Synced
4
+ class Synchronizer
5
+ attr_reader :id_key
6
+
7
+ # Initializes a new Synchronizer
8
+ #
9
+ # @param remote_objects [Array|NilClass] Array of objects to be synchronized
10
+ # with local database. Objects need to respond to at least :id message.
11
+ # If it's nil, then synchronizer will fetch the remote objects on it's own.
12
+ # @param model_class [Class] ActiveRecord model class from which local objects
13
+ # will be created.
14
+ # @param options [Hash]
15
+ # @option options [Symbol] scope: Within this object scope local objects
16
+ # will be synchronized. By default it's model_class.
17
+ # @option options [Symbol] id_key: attribute name under which
18
+ # remote object's ID is stored, default is :synced_id.
19
+ # @option options [Symbol] synced_all_at_key: attribute name under which
20
+ # remote object's sync time is stored, default is :synced_all_at
21
+ # @option options [Symbol] data_key: attribute name under which remote
22
+ # object's data is stored.
23
+ # @option options [Array] local_attributes: Array of attributes in the remote
24
+ # object which will be mapped to local object attributes.
25
+ # @option options [Boolean] remove: If it's true all local objects within
26
+ # current scope which are not present in the remote array will be destroyed.
27
+ # If only_updated is enabled, ids of objects to be deleted will be taken
28
+ # from the meta part. By default if cancel_at column is present, all
29
+ # missing local objects will be canceled with cancel_all,
30
+ # if it's missing, all will be destroyed with destroy_all.
31
+ # You can also force method to remove local objects by passing it
32
+ # to remove: :mark_as_missing.
33
+ # @option options [Boolean] only_updated: If true requests to API will take
34
+ # advantage of updated_since param and fetch only created/changed/deleted
35
+ # remote objects
36
+ def initialize(remote_objects, model_class, options = {})
37
+ @model_class = model_class
38
+ @scope = options[:scope]
39
+ @id_key = options[:id_key]
40
+ @synced_all_at_key = options[:synced_all_at_key]
41
+ @data_key = options[:data_key]
42
+ @remove = options[:remove]
43
+ @only_updated = options[:only_updated]
44
+ @include = options[:include]
45
+ @local_attributes = Array(options[:local_attributes])
46
+ @associations = Array(options[:associations])
47
+ @remote_objects = Array(remote_objects) if remote_objects
48
+ @request_performed = false
49
+ end
49
50
 
50
- def perform
51
- relation_scope.transaction do
52
- remove_relation.send(remove_strategy) if @remove
53
-
54
- remote_objects.map do |remote|
55
- local_object = local_object_by_remote_id(remote.id) || relation_scope.new
56
- local_object.attributes = default_attributes_mapping(remote)
57
- local_object.attributes = local_attributes_mapping(remote)
58
- local_object.save! if local_object.changed?
59
- local_object.tap do |local_object|
60
- @associations.each do |association|
61
- klass = association.to_s.classify.constantize
62
- klass.synchronize(remote: remote[association], scope: local_object,
63
- remove: @remove)
51
+ def perform
52
+ relation_scope.transaction do
53
+ remove_relation.send(remove_strategy) if @remove
54
+
55
+ remote_objects.map do |remote|
56
+ local_object = local_object_by_remote_id(remote.id) || relation_scope.new
57
+ local_object.attributes = default_attributes_mapping(remote)
58
+ local_object.attributes = local_attributes_mapping(remote)
59
+ local_object.save! if local_object.changed?
60
+ local_object.tap do |local_object|
61
+ @associations.each do |association|
62
+ klass = association.to_s.classify.constantize
63
+ klass.synchronize(remote: remote[association], scope: local_object,
64
+ remove: @remove)
65
+ end
66
+ end
67
+ end.tap do |local_objects|
68
+ if updated_since_enabled? && @request_performed
69
+ relation_scope.update_all(@synced_all_at_key => Time.now)
64
70
  end
65
- end
66
- end.tap do |local_objects|
67
- if updated_since_enabled? && @request_performed
68
- relation_scope.update_all(@synced_all_at_key => Time.now)
69
71
  end
70
72
  end
71
73
  end
72
- end
73
-
74
- private
75
74
 
76
- def local_attributes_mapping(remote)
77
- Hash[@local_attributes.map { |k| [k, remote[k]] }]
78
- end
75
+ private
79
76
 
80
- def default_attributes_mapping(remote)
81
- {}.tap do |attributes|
82
- attributes[@id_key] = remote.id
83
- attributes[@data_key] = remote if @data_key
77
+ def local_attributes_mapping(remote)
78
+ Hash[@local_attributes.map { |k| [k, remote[k]] }]
84
79
  end
85
- end
86
80
 
87
- # Returns relation within which local objects are created/edited and removed
88
- # If no scope is provided, the relation_scope will be class on which
89
- # .synchronize method is called.
90
- # If scope is provided, like: account, then relation_scope will be a relation
91
- # account.rentals (given we run .synchronize on Rental class)
92
- #
93
- # @return [ActiveRecord::Relation|Class]
94
- def relation_scope
95
- @scope ? @scope.send(resource_name) : @model_class
96
- end
81
+ def default_attributes_mapping(remote)
82
+ {}.tap do |attributes|
83
+ attributes[@id_key] = remote.id
84
+ attributes[@data_key] = remote if @data_key
85
+ end
86
+ end
97
87
 
98
- # Returns api client from the closest possible source.
99
- #
100
- # @raise [BookingSync::API::Unauthorized] - On unauthorized user
101
- # @return [BookingSync::API::Client] BookingSync API client
102
- def api
103
- closest = [@scope, @scope.class, @model_class].detect do |o|
104
- o.respond_to?(:api)
105
- end
106
- closest && closest.api || raise(MissingAPIClient.new(@scope, @model_class))
107
- end
88
+ # Returns relation within which local objects are created/edited and removed
89
+ # If no scope is provided, the relation_scope will be class on which
90
+ # .synchronize method is called.
91
+ # If scope is provided, like: account, then relation_scope will be a relation
92
+ # account.rentals (given we run .synchronize on Rental class)
93
+ #
94
+ # @return [ActiveRecord::Relation|Class]
95
+ def relation_scope
96
+ @scope ? @scope.send(resource_name) : @model_class
97
+ end
108
98
 
109
- def local_object_by_remote_id(remote_id)
110
- local_objects.find { |l| l.attributes[id_key.to_s] == remote_id }
111
- end
99
+ # Returns api client from the closest possible source.
100
+ #
101
+ # @raise [BookingSync::API::Unauthorized] - On unauthorized user
102
+ # @return [BookingSync::API::Client] BookingSync API client
103
+ def api
104
+ closest = [@scope, @scope.class, @model_class].detect do |o|
105
+ o.respond_to?(:api)
106
+ end
107
+ closest && closest.api || raise(MissingAPIClient.new(@scope, @model_class))
108
+ end
112
109
 
113
- def local_objects
114
- @local_objects ||= relation_scope.where(id_key => remote_objects_ids).to_a
115
- end
110
+ def local_object_by_remote_id(remote_id)
111
+ local_objects.find { |l| l.attributes[id_key.to_s] == remote_id }
112
+ end
116
113
 
117
- def remote_objects_ids
118
- @remote_objects_ids ||= remote_objects.map(&:id)
119
- end
114
+ def local_objects
115
+ @local_objects ||= relation_scope.where(id_key => remote_objects_ids).to_a
116
+ end
120
117
 
121
- def remote_objects
122
- @remote_objects ||= fetch_remote_objects
123
- end
118
+ def remote_objects_ids
119
+ @remote_objects_ids ||= remote_objects.map(&:id)
120
+ end
124
121
 
125
- def deleted_remote_objects_ids
126
- remote_objects unless @request_performed
127
- api.last_response.meta[:deleted_ids]
128
- end
122
+ def remote_objects
123
+ @remote_objects ||= fetch_remote_objects
124
+ end
129
125
 
130
- def fetch_remote_objects
131
- api.paginate(resource_name, api_request_options).tap do
132
- @request_performed = true
126
+ def deleted_remote_objects_ids
127
+ remote_objects unless @request_performed
128
+ api.last_response.meta[:deleted_ids]
133
129
  end
134
- end
135
130
 
136
- def api_request_options
137
- {}.tap do |options|
138
- options[:include] = @associations if @associations.present?
139
- if @include.present?
140
- options[:include] ||= []
141
- options[:include] += @include
131
+ def fetch_remote_objects
132
+ api.paginate(resource_name, api_request_options).tap do
133
+ @request_performed = true
142
134
  end
143
- options[:updated_since] = minimum_updated_at if updated_since_enabled?
144
- options[:auto_paginate] = true
145
135
  end
146
- end
147
-
148
- def minimum_updated_at
149
- relation_scope.minimum(@synced_all_at_key)
150
- end
151
136
 
152
- def updated_since_enabled?
153
- @only_updated && @synced_all_at_key
154
- end
137
+ def api_request_options
138
+ {}.tap do |options|
139
+ options[:include] = @associations if @associations.present?
140
+ if @include.present?
141
+ options[:include] ||= []
142
+ options[:include] += @include
143
+ end
144
+ options[:updated_since] = minimum_updated_at if updated_since_enabled?
145
+ options[:auto_paginate] = true
146
+ end
147
+ end
155
148
 
156
- def resource_name
157
- @model_class.to_s.tableize
158
- end
149
+ def minimum_updated_at
150
+ relation_scope.minimum(@synced_all_at_key)
151
+ end
159
152
 
160
- def remove_strategy
161
- @remove == true ? default_remove_strategy : @remove
162
- end
153
+ def updated_since_enabled?
154
+ @only_updated && @synced_all_at_key
155
+ end
163
156
 
164
- def default_remove_strategy
165
- if @model_class.column_names.include?("canceled_at")
166
- :cancel_all
167
- else
168
- :destroy_all
157
+ def resource_name
158
+ @model_class.to_s.tableize
169
159
  end
170
- end
171
160
 
172
- def remove_relation
173
- if @only_updated
174
- relation_scope.where(id_key => deleted_remote_objects_ids)
175
- else
176
- relation_scope.where.not(id_key => remote_objects_ids)
161
+ def remove_strategy
162
+ @remove == true ? default_remove_strategy : @remove
177
163
  end
178
- end
179
164
 
180
- class MissingAPIClient < StandardError
181
- def initialize(scope, model_class)
182
- @scope = scope
183
- @model_class = model_class
165
+ def default_remove_strategy
166
+ if @model_class.column_names.include?("canceled_at")
167
+ :cancel_all
168
+ else
169
+ :destroy_all
170
+ end
184
171
  end
185
172
 
186
- def message
187
- if @scope
188
- %Q{Missing BookingSync API client in #{@scope} object or
189
- #{@scope.class} class}
173
+ def remove_relation
174
+ if @only_updated
175
+ relation_scope.where(id_key => deleted_remote_objects_ids)
190
176
  else
191
- %Q{Missing BookingSync API client in #{@model_class} class}
177
+ relation_scope.where.not(id_key => remote_objects_ids)
178
+ end
179
+ end
180
+
181
+ class MissingAPIClient < StandardError
182
+ def initialize(scope, model_class)
183
+ @scope = scope
184
+ @model_class = model_class
185
+ end
186
+
187
+ def message
188
+ if @scope
189
+ %Q{Missing BookingSync API client in #{@scope} object or
190
+ #{@scope.class} class}
191
+ else
192
+ %Q{Missing BookingSync API client in #{@model_class} class}
193
+ end
192
194
  end
193
195
  end
194
196
  end
@@ -1,3 +1,3 @@
1
1
  module Synced
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: synced
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastien Grosjean