consyncful 0.3.2 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +13 -11
- data/.ruby-version +1 -1
- data/.travis.yml +2 -2
- data/Gemfile.lock +35 -32
- data/README.md +86 -46
- data/consyncful.gemspec +4 -3
- data/lib/consyncful.rb +21 -16
- data/lib/consyncful/base.rb +9 -3
- data/lib/consyncful/item_mapper.rb +49 -25
- data/lib/consyncful/persisted_item.rb +76 -0
- data/lib/consyncful/railtie.rb +1 -0
- data/lib/consyncful/stats.rb +2 -0
- data/lib/consyncful/sync.rb +40 -60
- data/lib/consyncful/version.rb +1 -1
- metadata +22 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6a8f36ac50a10800cbce6e6b9d086e346b1e7362e422080d532a87bb981d8135
|
4
|
+
data.tar.gz: 6df1ea6cf1cb05f405938d83e09436927e21b7828424f33c79a105e4c11da127
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bdebe27350c747d4d3d02e290c5971d2b295815741e887ad658a09248f0d7d0a798a4060d5500c99d2f18e4b7f920d98e32bc0f0d49bbdea488ded106bda53d7
|
7
|
+
data.tar.gz: 034ec0cbec2265225f48bbd52d6412fe238ee026b78827bfacec54e8a31c42f9786495709c7dcea1e4ecf261761897f99a8417e4bea09149296c6cc51c9f789b
|
data/.rubocop_todo.yml
CHANGED
@@ -1,22 +1,29 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config`
|
3
|
-
# on
|
3
|
+
# on 2021-03-01 10:54:42 +1300 using RuboCop version 0.79.0.
|
4
4
|
# The point is for the user to remove these configuration records
|
5
5
|
# one by one as the offenses are removed from the code base.
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
7
7
|
# versions of RuboCop, may require this file to be generated again.
|
8
8
|
|
9
|
+
# Offense count: 1
|
10
|
+
# Cop supports --auto-correct.
|
11
|
+
# Configuration parameters: AllowInHeredoc.
|
12
|
+
Layout/TrailingWhitespace:
|
13
|
+
Exclude:
|
14
|
+
- 'lib/consyncful/item_mapper.rb'
|
15
|
+
|
9
16
|
# Offense count: 1
|
10
17
|
Metrics/AbcSize:
|
11
18
|
Max: 19
|
12
19
|
|
13
|
-
# Offense count:
|
20
|
+
# Offense count: 10
|
14
21
|
# Configuration parameters: CountComments, ExcludedMethods.
|
15
22
|
# ExcludedMethods: refine
|
16
23
|
Metrics/BlockLength:
|
17
|
-
Max:
|
24
|
+
Max: 175
|
18
25
|
|
19
|
-
# Offense count:
|
26
|
+
# Offense count: 3
|
20
27
|
# Cop supports --auto-correct.
|
21
28
|
# Configuration parameters: AutoCorrect, EnforcedStyle.
|
22
29
|
# SupportedStyles: nested, compact
|
@@ -24,21 +31,16 @@ Style/ClassAndModuleChildren:
|
|
24
31
|
Exclude:
|
25
32
|
- 'lib/consyncful/railtie.rb'
|
26
33
|
- 'spec/consyncful/base_spec.rb'
|
27
|
-
- 'spec/consyncful/sync_spec.rb'
|
28
34
|
|
29
|
-
# Offense count:
|
35
|
+
# Offense count: 3
|
30
36
|
Style/Documentation:
|
31
37
|
Exclude:
|
32
38
|
- 'spec/**/*'
|
33
39
|
- 'test/**/*'
|
34
40
|
- 'lib/consyncful.rb'
|
35
|
-
- 'lib/consyncful/base.rb'
|
36
|
-
- 'lib/consyncful/item_mapper.rb'
|
37
41
|
- 'lib/consyncful/railtie.rb'
|
38
|
-
- 'lib/consyncful/stats.rb'
|
39
|
-
- 'lib/consyncful/sync.rb'
|
40
42
|
|
41
|
-
# Offense count:
|
43
|
+
# Offense count: 56
|
42
44
|
# Cop supports --auto-correct.
|
43
45
|
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
44
46
|
# URISchemes: http, https
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.7.1
|
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,64 +1,67 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
consyncful (0.
|
4
|
+
consyncful (0.6.1)
|
5
5
|
contentful (>= 2.11.1, < 3.0.0)
|
6
|
+
hooks (>= 0.4.1)
|
6
7
|
mongoid (>= 7.0.2, < 8.0.0)
|
7
8
|
rainbow
|
8
9
|
|
9
10
|
GEM
|
10
11
|
remote: https://rubygems.org/
|
11
12
|
specs:
|
12
|
-
activemodel (6.
|
13
|
-
activesupport (= 6.
|
14
|
-
activesupport (6.
|
13
|
+
activemodel (6.1.3.1)
|
14
|
+
activesupport (= 6.1.3.1)
|
15
|
+
activesupport (6.1.3.1)
|
15
16
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
16
|
-
i18n (>=
|
17
|
-
minitest (
|
18
|
-
tzinfo (~>
|
19
|
-
zeitwerk (~> 2.
|
17
|
+
i18n (>= 1.6, < 2)
|
18
|
+
minitest (>= 5.1)
|
19
|
+
tzinfo (~> 2.0)
|
20
|
+
zeitwerk (~> 2.3)
|
20
21
|
addressable (2.7.0)
|
21
22
|
public_suffix (>= 2.0.2, < 5.0)
|
22
23
|
ast (2.4.0)
|
23
|
-
bson (4.
|
24
|
-
concurrent-ruby (1.1.
|
25
|
-
contentful (2.
|
24
|
+
bson (4.12.0)
|
25
|
+
concurrent-ruby (1.1.8)
|
26
|
+
contentful (2.16.0)
|
26
27
|
http (> 0.8, < 5.0)
|
27
28
|
multi_json (~> 1)
|
28
29
|
database_cleaner (1.8.3)
|
29
30
|
diff-lcs (1.3)
|
30
31
|
domain_name (0.5.20190701)
|
31
32
|
unf (>= 0.0.5, < 1.0.0)
|
32
|
-
ffi (1.
|
33
|
+
ffi (1.15.0)
|
33
34
|
ffi-compiler (1.0.1)
|
34
35
|
ffi (>= 1.0.0)
|
35
36
|
rake
|
36
|
-
|
37
|
+
hooks (0.4.1)
|
38
|
+
uber (~> 0.0.14)
|
39
|
+
http (4.4.1)
|
37
40
|
addressable (~> 2.3)
|
38
41
|
http-cookie (~> 1.0)
|
39
42
|
http-form_data (~> 2.2)
|
40
43
|
http-parser (~> 1.2.0)
|
41
44
|
http-cookie (1.0.3)
|
42
45
|
domain_name (~> 0.5)
|
43
|
-
http-form_data (2.
|
44
|
-
http-parser (1.2.
|
46
|
+
http-form_data (2.3.0)
|
47
|
+
http-parser (1.2.3)
|
45
48
|
ffi-compiler (>= 1.0, < 2.0)
|
46
|
-
i18n (1.8.
|
49
|
+
i18n (1.8.10)
|
47
50
|
concurrent-ruby (~> 1.0)
|
48
51
|
jaro_winkler (1.5.4)
|
49
|
-
minitest (5.14.
|
50
|
-
mongo (2.
|
51
|
-
bson (>= 4.
|
52
|
-
mongoid (7.
|
53
|
-
activemodel (>= 5.1, < 6.
|
54
|
-
mongo (>= 2.5
|
55
|
-
multi_json (1.
|
52
|
+
minitest (5.14.4)
|
53
|
+
mongo (2.14.0)
|
54
|
+
bson (>= 4.8.2, < 5.0.0)
|
55
|
+
mongoid (7.2.2)
|
56
|
+
activemodel (>= 5.1, < 6.2)
|
57
|
+
mongo (>= 2.10.5, < 3.0.0)
|
58
|
+
multi_json (1.15.0)
|
56
59
|
parallel (1.19.1)
|
57
60
|
parser (2.7.0.2)
|
58
61
|
ast (~> 2.4.0)
|
59
|
-
public_suffix (4.0.
|
62
|
+
public_suffix (4.0.6)
|
60
63
|
rainbow (3.0.0)
|
61
|
-
rake (
|
64
|
+
rake (13.0.1)
|
62
65
|
rspec (3.9.0)
|
63
66
|
rspec-core (~> 3.9.0)
|
64
67
|
rspec-expectations (~> 3.9.0)
|
@@ -80,14 +83,14 @@ GEM
|
|
80
83
|
ruby-progressbar (~> 1.7)
|
81
84
|
unicode-display_width (>= 1.4.0, < 1.7)
|
82
85
|
ruby-progressbar (1.10.1)
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
+
tzinfo (2.0.4)
|
87
|
+
concurrent-ruby (~> 1.0)
|
88
|
+
uber (0.0.15)
|
86
89
|
unf (0.1.4)
|
87
90
|
unf_ext
|
88
|
-
unf_ext (0.0.7.
|
91
|
+
unf_ext (0.0.7.7)
|
89
92
|
unicode-display_width (1.6.1)
|
90
|
-
zeitwerk (2.
|
93
|
+
zeitwerk (2.4.2)
|
91
94
|
|
92
95
|
PLATFORMS
|
93
96
|
ruby
|
@@ -96,9 +99,9 @@ DEPENDENCIES
|
|
96
99
|
bundler (~> 2)
|
97
100
|
consyncful!
|
98
101
|
database_cleaner
|
99
|
-
rake (~>
|
102
|
+
rake (~> 13.0)
|
100
103
|
rspec (~> 3.0)
|
101
104
|
rubocop (= 0.79.0)
|
102
105
|
|
103
106
|
BUNDLED WITH
|
104
|
-
2.1.
|
107
|
+
2.1.4
|
data/README.md
CHANGED
@@ -2,19 +2,27 @@
|
|
2
2
|
|
3
3
|
Contentful -> local database synchronisation for Rails
|
4
4
|
|
5
|
-
Requesting complicated models from the Contentful Delivery API in Rails applications is often
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
5
|
+
Requesting complicated models from the Contentful Delivery API in Rails applications is often too slow, and makes testing applications painful. Consyncful uses Contentful's synchronisation API to keep a local, up-to-date copy of the entire content in a Mongo database.
|
6
|
+
|
7
|
+
Once the content is available locally, finding and interact with contentful data is as easy as using [Mongoid](https://docs.mongodb.com/mongoid/current/tutorials/mongoid-documents/) ODM.
|
8
|
+
|
9
|
+
This gem doesn't provide any integration with the management API, or any way to update Contentful models from the local store. It is strictly read only.
|
10
|
+
|
11
|
+
- [Installation](#installation)
|
12
|
+
- [Usage](#usage)
|
13
|
+
- [Creating contentful models in your Rails app](#creating-contentful-models-in-your-rails-app)
|
14
|
+
- [Synchronizing contentful data](#synchronizing-contentful-data)
|
15
|
+
- [Finding and interacting with models](#finding-and-interacting-with-models)
|
16
|
+
- [Querying](#querying)
|
17
|
+
- [References](#references)
|
18
|
+
- [Finding entries from different content types](#finding-entries-from-different-content-types)
|
19
|
+
- [Sync callbacks](#sync-callbacks)
|
20
|
+
- [Using Locales for specific fields](#using-locales-for-specific-fields)
|
21
|
+
- [Configuring what Mongo database Consyncful uses](#configuring-what-mongo-database-consyncful-uses)
|
22
|
+
- [Why do I have to use MongoDB?](#why-do-i-have-to-use-mongodb)
|
23
|
+
- [Development](#development)
|
24
|
+
- [Contributing](#contributing)
|
25
|
+
- [License](#license)
|
18
26
|
|
19
27
|
## Installation
|
20
28
|
|
@@ -28,28 +36,29 @@ And then execute:
|
|
28
36
|
|
29
37
|
$ bundle
|
30
38
|
|
31
|
-
If you don't already use
|
39
|
+
If you don't already use Mongoid, generate a mongoid.yml by running:
|
32
40
|
|
33
41
|
$ rake g mongoid:config
|
34
42
|
|
35
43
|
Add an initializer:
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
|
45
|
+
Consyncful uses [contentful.rb](https://github.com/contentful/contentful.rb); client options are as documented there.
|
46
|
+
```rb
|
47
|
+
Consyncful.configure do |config|
|
48
|
+
config.locale = 'en-NZ'
|
49
|
+
config.contentful_client_options = {
|
50
|
+
api_url: 'cdn.contentful.com',
|
51
|
+
space: 'space_id',
|
52
|
+
access_token: 'ACCESS TOKEN',
|
53
|
+
environment: 'master', # optional
|
54
|
+
logger: Logger.new(STDOUT) # optional for debugging
|
55
|
+
}
|
56
|
+
end
|
48
57
|
```
|
49
58
|
|
50
59
|
## Usage
|
51
60
|
|
52
|
-
### Creating contentful models in your
|
61
|
+
### Creating contentful models in your Rails app
|
53
62
|
|
54
63
|
Create models by inheriting from `Consyncful::Base`
|
55
64
|
|
@@ -59,9 +68,9 @@ class ModelName < Consyncful::Base
|
|
59
68
|
end
|
60
69
|
```
|
61
70
|
|
62
|
-
Model fields will be
|
71
|
+
Model fields will be dynamically assigned, but Mongoid dynamic fields are not accessible if the entry has an empty field. If you want the accessor methods to be reliably available for fields it is recommended to define the fields in the model:
|
63
72
|
|
64
|
-
```ruby
|
73
|
+
```ruby
|
65
74
|
class ModelName < Consyncful::Base
|
66
75
|
contentful_model_name 'contentfulTypeName'
|
67
76
|
|
@@ -70,9 +79,9 @@ class ModelName < Consyncful::Base
|
|
70
79
|
end
|
71
80
|
```
|
72
81
|
|
73
|
-
Contentful reference fields are a bit special compared with standard
|
82
|
+
Contentful reference fields are a bit special compared with standard Mongoid associations. Consyncful provides the following helpers to set up the correct relationships:
|
74
83
|
|
75
|
-
```ruby
|
84
|
+
```ruby
|
76
85
|
class ModelWithReferences < Consyncful::Base
|
77
86
|
contentful_model_name 'contentfulTypeName'
|
78
87
|
|
@@ -81,26 +90,26 @@ class ModelWithReferences < Consyncful::Base
|
|
81
90
|
end
|
82
91
|
```
|
83
92
|
|
84
|
-
###
|
93
|
+
### Synchronizing contentful data
|
85
94
|
|
86
|
-
To run a
|
95
|
+
To run a synchronization process run:
|
87
96
|
|
88
97
|
$ rake consyncful:sync
|
89
98
|
|
90
|
-
The first time you run this it will download all the
|
99
|
+
The first time you run this it will download all the Contentful content. It will then check every 15 seconds for changes to the content and update/delete records in the database when changes are made in Contentful.
|
91
100
|
|
92
|
-
If you want to
|
101
|
+
If you want to synchronise from scratch, run:
|
93
102
|
|
94
103
|
$ rake consyncful:refresh
|
95
104
|
|
96
105
|
It is recommended to refresh your data if you change model names.
|
97
106
|
|
98
|
-
Now you've synced your data, it is all available via your
|
107
|
+
Now you've synced your data, it is all available via your Rails models.
|
99
108
|
|
100
109
|
### Finding and interacting with models
|
101
110
|
|
102
111
|
#### Querying
|
103
|
-
Models are available using standard
|
112
|
+
Models are available using standard Mongoid [queries](https://docs.mongodb.com/mongoid/current/tutorials/mongoid-queries/).
|
104
113
|
|
105
114
|
```ruby
|
106
115
|
instance = ModelName.find_by(instance: 'foo')
|
@@ -109,7 +118,7 @@ instance.is_awesome # true
|
|
109
118
|
```
|
110
119
|
|
111
120
|
#### References
|
112
|
-
References work like you
|
121
|
+
References work like you would expect:
|
113
122
|
|
114
123
|
```ruby
|
115
124
|
|
@@ -120,25 +129,56 @@ instance.other_things # all the referenced things, polymorphic, so might be diff
|
|
120
129
|
```
|
121
130
|
|
122
131
|
**Except**:
|
123
|
-
`references_many` associations return objects in a different order from how they are ordered in
|
132
|
+
`references_many` associations return objects in a different order from how they are ordered in Contentful. If you want them in the order they appear in Contentful, use the `.in_order` helper:
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
instance.other_things.in_order # ordered the same as in Contentful
|
136
|
+
```
|
137
|
+
|
138
|
+
#### Finding entries from different content types
|
139
|
+
|
140
|
+
Because all Contentful models are stored as polymorphic subtypes of `Consyncful::Base`, you can query all entries without knowing what type you are looking for:
|
124
141
|
|
125
142
|
```ruby
|
126
|
-
|
143
|
+
Consyncful::Base.where(title: 'a title') # [ #<ModelName>, #<OtherModelName> ]
|
127
144
|
```
|
128
145
|
|
129
|
-
|
146
|
+
### Sync callbacks
|
147
|
+
|
148
|
+
You may want to attach some application logic to happen before or after a sync run, for example to update caches.
|
130
149
|
|
131
|
-
|
150
|
+
Callbacks can be registered using:
|
132
151
|
|
133
152
|
```ruby
|
134
|
-
|
153
|
+
Consyncful::Sync.before_run do
|
154
|
+
# do something before the run
|
155
|
+
end
|
156
|
+
```
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
Consyncful::Sync.after_run do |updated_ids|
|
160
|
+
# invalidate cache for updated_ids, or something
|
161
|
+
end
|
135
162
|
```
|
136
163
|
|
137
|
-
|
164
|
+
### Using Locales for specific fields
|
165
|
+
|
166
|
+
If fields have multiple locales then the default locale will be mapped to the field name. Additional locales will have a suffix (lower snake case) on the field name. e.g title (default), title_mi_nz (New Zealand Maori mi-NZ)
|
167
|
+
|
168
|
+
### Configuring what Mongo database Consyncful uses
|
169
|
+
|
170
|
+
You can also configure what Mongoid client Consyncful uses and the name of the collection the entries are stored under. This is useful if you want to have your consyncful data hosted in a different mongo database than your application-specific mongo database.
|
171
|
+
|
172
|
+
```rb
|
173
|
+
Consyncful.configure do |config|
|
174
|
+
config.mongo_client = :consyncful # defaults to :default (referencing the clients in mongoid.yml)
|
175
|
+
config.mongo_collection = 'contentful_models' # this is the default
|
176
|
+
end
|
177
|
+
```
|
138
178
|
|
139
|
-
###
|
179
|
+
### Why do I have to use MongoDB?
|
140
180
|
|
141
|
-
|
181
|
+
Consyncful currently only supports Mongoid ODM because models have dynamic schemas. And that's all we've had a chance to work out so far. The same pattern might be able to be extended to work with ActiveRecord, but having to migrate the local database as well as your contentful content type's seems tedious.
|
142
182
|
|
143
183
|
## Development
|
144
184
|
|
data/consyncful.gemspec
CHANGED
@@ -7,8 +7,8 @@ require 'consyncful/version'
|
|
7
7
|
Gem::Specification.new do |spec|
|
8
8
|
spec.name = 'consyncful'
|
9
9
|
spec.version = Consyncful::VERSION
|
10
|
-
spec.authors = ['Andy Anastasiadis-Gray', 'Montgomery Anderson']
|
11
|
-
spec.email = ['andy@boost.co.nz', 'montgomery@boost.co.nz']
|
10
|
+
spec.authors = ['Andy Anastasiadis-Gray', 'Montgomery Anderson', 'Greg Rogan']
|
11
|
+
spec.email = ['andy@boost.co.nz', 'montgomery@boost.co.nz', 'greg@boost.co.nz']
|
12
12
|
|
13
13
|
spec.summary = 'Contentful to local database synchronisation for Rails'
|
14
14
|
spec.homepage = 'https://github.com/boost/consyncful'
|
@@ -32,11 +32,12 @@ Gem::Specification.new do |spec|
|
|
32
32
|
|
33
33
|
spec.add_development_dependency 'bundler', '~> 2'
|
34
34
|
spec.add_development_dependency 'database_cleaner'
|
35
|
-
spec.add_development_dependency 'rake', '~>
|
35
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
36
36
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
37
37
|
spec.add_development_dependency 'rubocop', '0.79.0'
|
38
38
|
|
39
39
|
spec.add_dependency 'contentful', ['>=2.11.1', '<3.0.0']
|
40
|
+
spec.add_dependency 'hooks', '>=0.4.1'
|
40
41
|
spec.add_dependency 'mongoid', ['>=7.0.2', '<8.0.0']
|
41
42
|
spec.add_dependency 'rainbow'
|
42
43
|
end
|
data/lib/consyncful.rb
CHANGED
@@ -11,23 +11,18 @@ require 'consyncful/sync'
|
|
11
11
|
require 'consyncful/railtie' if defined?(Rails)
|
12
12
|
|
13
13
|
module Consyncful
|
14
|
-
|
15
|
-
attr_accessor :configuration
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.configure
|
19
|
-
self.configuration ||= Configuration.new
|
20
|
-
yield(configuration)
|
21
|
-
end
|
22
|
-
|
14
|
+
# Handles Rails configurations for Consynful
|
23
15
|
class Configuration
|
24
|
-
attr_accessor :contentful_client_options, :locale
|
16
|
+
attr_accessor :contentful_client_options, :locale,
|
17
|
+
:mongo_client, :mongo_collection
|
25
18
|
|
26
19
|
def initialize
|
27
20
|
@contentful_client_options = {
|
28
21
|
api_url: 'cdn.contentful.com'
|
29
22
|
}
|
30
|
-
@locale = 'en-
|
23
|
+
@locale = 'en-NZ'
|
24
|
+
@mongo_client = :default
|
25
|
+
@mongo_collection = 'contentful_models'
|
31
26
|
end
|
32
27
|
end
|
33
28
|
|
@@ -36,11 +31,21 @@ module Consyncful
|
|
36
31
|
api_url: 'cdn.contentful.com'
|
37
32
|
}.freeze
|
38
33
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
34
|
+
class << self
|
35
|
+
def configuration
|
36
|
+
@configuration ||= Configuration.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def configure
|
40
|
+
yield configuration
|
41
|
+
end
|
42
|
+
|
43
|
+
def client
|
44
|
+
@client ||= begin
|
45
|
+
options = Consyncful.configuration.contentful_client_options
|
46
|
+
options.reverse_merge!(DEFAULT_CLIENT_OPTIONS)
|
47
|
+
Contentful::Client.new(options)
|
48
|
+
end
|
44
49
|
end
|
45
50
|
end
|
46
51
|
end
|
data/lib/consyncful/base.rb
CHANGED
@@ -1,14 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Consyncful
|
4
|
+
##
|
5
|
+
# Provides common functionality of Mongoid models created from contentful
|
6
|
+
# entries
|
4
7
|
class Base
|
5
8
|
include Mongoid::Document
|
6
9
|
include Mongoid::Attributes::Dynamic
|
7
10
|
|
8
|
-
store_in collection: 'contentful_models'
|
9
|
-
|
10
11
|
cattr_accessor :model_map
|
11
12
|
|
13
|
+
store_in collection: -> { Consyncful.configuration.mongo_collection },
|
14
|
+
client: -> { Consyncful.configuration.mongo_client }
|
15
|
+
|
12
16
|
def self.contentful_model_name(name)
|
13
17
|
self.model_map ||= {}
|
14
18
|
|
@@ -19,7 +23,9 @@ module Consyncful
|
|
19
23
|
def self.references_many(name)
|
20
24
|
has_and_belongs_to_many name.to_sym, class_name: 'Consyncful::Base', inverse_of: nil do
|
21
25
|
def in_order
|
22
|
-
_target.to_a.sort_by
|
26
|
+
_target.to_a.sort_by do |reference|
|
27
|
+
_base[foreign_key].index(reference.id)
|
28
|
+
end
|
23
29
|
end
|
24
30
|
end
|
25
31
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Consyncful
|
4
|
+
##
|
5
|
+
# Responsible for mapping an update received from Contentful's syncronisation API
|
6
|
+
# into useful fields for Consyncful::PersistedItem to store in the database.
|
4
7
|
class ItemMapper
|
5
8
|
def initialize(item)
|
6
9
|
@item = item
|
@@ -11,10 +14,9 @@ module Consyncful
|
|
11
14
|
end
|
12
15
|
|
13
16
|
def type
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
'asset'
|
17
|
+
case @item.type
|
18
|
+
when 'Entry' then @item.content_type.id
|
19
|
+
when 'Asset' then 'asset'
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
@@ -22,17 +24,11 @@ module Consyncful
|
|
22
24
|
@item.id
|
23
25
|
end
|
24
26
|
|
25
|
-
def mapped_fields(
|
27
|
+
def mapped_fields(default_locale)
|
26
28
|
fields = generic_fields
|
27
29
|
|
28
|
-
|
29
|
-
|
30
|
-
next if value.is_a? Contentful::File # it is special
|
31
|
-
|
32
|
-
assign_field(fields, field, value)
|
33
|
-
end
|
34
|
-
|
35
|
-
fields[:file] = raw_file(locale) if type == 'asset'
|
30
|
+
fields.merge!(localized_fields(default_locale))
|
31
|
+
fields.merge!(localized_asset_fields(default_locale)) if type == 'asset'
|
36
32
|
|
37
33
|
fields
|
38
34
|
end
|
@@ -40,18 +36,46 @@ module Consyncful
|
|
40
36
|
private
|
41
37
|
|
42
38
|
def generic_fields
|
39
|
+
{ created_at: @item.created_at,
|
40
|
+
updated_at: @item.updated_at,
|
41
|
+
revision: @item.revision,
|
42
|
+
contentful_type: type,
|
43
|
+
synced_at: Time.current }
|
44
|
+
end
|
45
|
+
|
46
|
+
def localized_fields(default_locale)
|
43
47
|
fields = {}
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
48
|
+
|
49
|
+
@item.fields_with_locales.each do |field, value_with_locales|
|
50
|
+
value_with_locales.each do |locale_code, value|
|
51
|
+
next if value.is_a? Contentful::File # assets are handeled below
|
52
|
+
|
53
|
+
field_name = localized_field_name(field, locale_code, default_locale)
|
54
|
+
field_name, value = mapped_field_entry_for(field_name, value)
|
55
|
+
fields[field_name] = value
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
49
59
|
fields
|
50
60
|
end
|
51
61
|
|
52
|
-
def
|
53
|
-
|
54
|
-
|
62
|
+
def localized_asset_fields(default_locale)
|
63
|
+
fields = {}
|
64
|
+
files_by_locale = @item.raw.dig('fields', 'file') || {}
|
65
|
+
|
66
|
+
files_by_locale.each do |locale_code, details|
|
67
|
+
field_name = localized_field_name('file', locale_code, default_locale)
|
68
|
+
fields[field_name.to_sym] = details
|
69
|
+
end
|
70
|
+
|
71
|
+
fields
|
72
|
+
end
|
73
|
+
|
74
|
+
# Suffixes the field with the locale unless it's the default locale.
|
75
|
+
def localized_field_name(field, locale_code, default_locale)
|
76
|
+
return field if locale_code.to_s == default_locale.to_s
|
77
|
+
|
78
|
+
"#{field}_#{locale_code.to_s.underscore}".to_sym
|
55
79
|
end
|
56
80
|
|
57
81
|
def reference_value?(value)
|
@@ -66,14 +90,14 @@ module Consyncful
|
|
66
90
|
value.is_a?(Array) && single_reference?(value.first)
|
67
91
|
end
|
68
92
|
|
69
|
-
def
|
93
|
+
def mapped_field_entry_for(field, value)
|
70
94
|
if single_reference?(value)
|
71
|
-
|
95
|
+
[ActiveSupport::Inflector.foreign_key(field).to_sym, value.id]
|
72
96
|
elsif many_reference?(value)
|
73
97
|
ids_field_name = field.to_s.singularize + '_ids' # fk field name
|
74
|
-
|
98
|
+
[ids_field_name.to_sym, value.map(&:id)]
|
75
99
|
else
|
76
|
-
|
100
|
+
[field, value]
|
77
101
|
end
|
78
102
|
end
|
79
103
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Consyncful
|
4
|
+
##
|
5
|
+
# Takes a mapped item from Contentful and creates/updates/deletes
|
6
|
+
# the relevant model in the local database.
|
7
|
+
class PersistedItem
|
8
|
+
DEFAULT_LOCALE = 'en-NZ'
|
9
|
+
|
10
|
+
def initialize(item, sync_id, stats)
|
11
|
+
@item = item
|
12
|
+
@sync_id = sync_id
|
13
|
+
@stats = stats
|
14
|
+
end
|
15
|
+
|
16
|
+
def persist
|
17
|
+
puts Rainbow("syncing: #{@item.id}").yellow
|
18
|
+
if @item.deletion?
|
19
|
+
delete_model(@item.id, @stats)
|
20
|
+
else
|
21
|
+
create_or_update_model(@item, @sync_id, @stats)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def delete_model(id, stats)
|
28
|
+
Base.find_by(id: id).destroy
|
29
|
+
stats.record_deleted
|
30
|
+
rescue Mongoid::Errors::DocumentNotFound
|
31
|
+
puts Rainbow("Deleted record not found: #{id}").yellow
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_or_update_model(item, sync_id, stats)
|
36
|
+
return if item.type.nil?
|
37
|
+
|
38
|
+
instance = find_or_initialize_item(item)
|
39
|
+
update_stats(instance, stats)
|
40
|
+
|
41
|
+
reset_fields(instance)
|
42
|
+
|
43
|
+
item.mapped_fields(DEFAULT_LOCALE).each do |field, value|
|
44
|
+
instance[field] = value
|
45
|
+
end
|
46
|
+
|
47
|
+
instance[:sync_id] = sync_id
|
48
|
+
|
49
|
+
instance.save
|
50
|
+
end
|
51
|
+
|
52
|
+
def find_or_initialize_item(item)
|
53
|
+
model_class(item.type).find_or_initialize_by(id: item.id)
|
54
|
+
end
|
55
|
+
|
56
|
+
def update_stats(instance, stats)
|
57
|
+
if instance.persisted?
|
58
|
+
stats.record_updated
|
59
|
+
else
|
60
|
+
stats.record_added
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def model_class(type)
|
65
|
+
Base.model_map[type] || Base
|
66
|
+
end
|
67
|
+
|
68
|
+
def reset_fields(instance)
|
69
|
+
instance.attributes.each do |field_name, _value|
|
70
|
+
next if field_name.in? %w[_id _type]
|
71
|
+
|
72
|
+
instance[field_name] = nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/consyncful/railtie.rb
CHANGED
data/lib/consyncful/stats.rb
CHANGED
data/lib/consyncful/sync.rb
CHANGED
@@ -2,13 +2,27 @@
|
|
2
2
|
|
3
3
|
require 'rainbow'
|
4
4
|
require 'consyncful/item_mapper'
|
5
|
+
require 'consyncful/persisted_item'
|
5
6
|
require 'consyncful/stats'
|
7
|
+
require 'hooks'
|
6
8
|
|
7
9
|
module Consyncful
|
10
|
+
##
|
11
|
+
# A mongoid model that stores the state of a syncronisation feed. Stores the
|
12
|
+
# next URL provided by Contentfuls Sync API.
|
13
|
+
#
|
14
|
+
# Sync's are affectivly singletons,
|
15
|
+
# there should only ever be one in the database
|
16
|
+
#
|
17
|
+
# Is also the entrypoint of a Syncronization run
|
8
18
|
class Sync
|
9
19
|
include Mongoid::Document
|
20
|
+
include Hooks
|
10
21
|
|
11
|
-
|
22
|
+
store_in client: -> { Consyncful.configuration.mongo_client }
|
23
|
+
|
24
|
+
define_hook :before_run
|
25
|
+
define_hook :after_run
|
12
26
|
|
13
27
|
field :next_url
|
14
28
|
field :last_run_at, type: DateTime
|
@@ -17,31 +31,41 @@ module Consyncful
|
|
17
31
|
last || new
|
18
32
|
end
|
19
33
|
|
34
|
+
##
|
35
|
+
# Delete the previous sync chains from database and create a fresh one.
|
36
|
+
# Used to completely resync all items from Contentful.
|
20
37
|
def self.fresh
|
21
38
|
destroy_all
|
22
39
|
latest
|
23
40
|
end
|
24
41
|
|
42
|
+
##
|
43
|
+
# Makes sure that the database contains only records that have been provided
|
44
|
+
# during this chain of syncronisation.
|
25
45
|
def drop_stale
|
26
46
|
stale = Base.where(:sync_id.ne => id, :sync_id.exists => true)
|
27
47
|
puts Rainbow("Dropping #{stale.count} records that haven't been touched in this sync").red
|
28
48
|
stale.destroy
|
29
49
|
end
|
30
50
|
|
51
|
+
##
|
52
|
+
# Entry point to a syncronization run. Is responsible for updating Sync state
|
31
53
|
def run
|
54
|
+
run_hook :before_run
|
55
|
+
|
32
56
|
stats = Consyncful::Stats.new
|
33
57
|
load_all_models
|
34
58
|
|
35
59
|
sync = start_sync
|
36
60
|
|
37
|
-
sync_items(sync, stats)
|
61
|
+
changed_ids = sync_items(sync, stats)
|
38
62
|
|
39
63
|
drop_stale
|
40
64
|
|
41
|
-
|
42
|
-
self.last_run_at = Time.current
|
43
|
-
save
|
65
|
+
update_run(sync.next_sync_url)
|
44
66
|
stats.print_stats
|
67
|
+
|
68
|
+
run_hook :after_run, changed_ids
|
45
69
|
end
|
46
70
|
|
47
71
|
private
|
@@ -52,6 +76,12 @@ module Consyncful
|
|
52
76
|
Rails.application.eager_load!
|
53
77
|
end
|
54
78
|
|
79
|
+
def update_run(next_url)
|
80
|
+
self.next_url = next_url
|
81
|
+
self.last_run_at = Time.current
|
82
|
+
save
|
83
|
+
end
|
84
|
+
|
55
85
|
def start_sync
|
56
86
|
if next_url.present?
|
57
87
|
puts Rainbow("Starting update, last update: #{last_run_at} (#{(Time.current - last_run_at).round(3)}s ago)").blue
|
@@ -63,69 +93,19 @@ module Consyncful
|
|
63
93
|
end
|
64
94
|
|
65
95
|
def sync_items(sync, stats)
|
96
|
+
ids = []
|
66
97
|
sync.each_page do |page|
|
67
98
|
page.items.each do |item|
|
68
|
-
sync_item(ItemMapper.new(item), stats)
|
99
|
+
ids << sync_item(ItemMapper.new(item), stats)
|
69
100
|
end
|
70
101
|
end
|
102
|
+
ids
|
71
103
|
end
|
72
104
|
|
73
105
|
def sync_item(item, stats)
|
74
106
|
puts Rainbow("syncing: #{item.id}").yellow
|
75
|
-
|
76
|
-
|
77
|
-
else
|
78
|
-
create_or_update_model(item, stats)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def delete_model(id, stats)
|
83
|
-
Base.find_by(id: id).destroy
|
84
|
-
stats.record_deleted
|
85
|
-
rescue Mongoid::Errors::DocumentNotFound
|
86
|
-
puts Rainbow("Deleted record not found: #{id}").yellow
|
87
|
-
nil
|
88
|
-
end
|
89
|
-
|
90
|
-
def create_or_update_model(item, stats)
|
91
|
-
return if item.type.nil?
|
92
|
-
|
93
|
-
instance = find_or_initialize_item(item)
|
94
|
-
update_stats(instance, stats)
|
95
|
-
|
96
|
-
reset_fields(instance)
|
97
|
-
|
98
|
-
item.mapped_fields(DEFAULT_LOCALE).each do |field, value|
|
99
|
-
instance[field] = value
|
100
|
-
end
|
101
|
-
|
102
|
-
instance[:sync_id] = id
|
103
|
-
|
104
|
-
instance.save
|
105
|
-
end
|
106
|
-
|
107
|
-
def find_or_initialize_item(item)
|
108
|
-
model_class(item.type).find_or_initialize_by(id: item.id)
|
109
|
-
end
|
110
|
-
|
111
|
-
def update_stats(instance, stats)
|
112
|
-
if instance.persisted?
|
113
|
-
stats.record_updated
|
114
|
-
else
|
115
|
-
stats.record_added
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
def model_class(type)
|
120
|
-
Base.model_map[type] || Base
|
121
|
-
end
|
122
|
-
|
123
|
-
def reset_fields(instance)
|
124
|
-
instance.attributes.each do |field_name, _value|
|
125
|
-
next if field_name.in? %w[_id _type]
|
126
|
-
|
127
|
-
instance[field_name] = nil
|
128
|
-
end
|
107
|
+
PersistedItem.new(item, id, stats).persist
|
108
|
+
item.id
|
129
109
|
end
|
130
110
|
end
|
131
111
|
end
|
data/lib/consyncful/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: consyncful
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Anastasiadis-Gray
|
8
8
|
- Montgomery Anderson
|
9
|
+
- Greg Rogan
|
9
10
|
autorequire:
|
10
11
|
bindir: exe
|
11
12
|
cert_chain: []
|
12
|
-
date:
|
13
|
+
date: 2021-04-27 00:00:00.000000000 Z
|
13
14
|
dependencies:
|
14
15
|
- !ruby/object:Gem::Dependency
|
15
16
|
name: bundler
|
@@ -45,14 +46,14 @@ dependencies:
|
|
45
46
|
requirements:
|
46
47
|
- - "~>"
|
47
48
|
- !ruby/object:Gem::Version
|
48
|
-
version: '
|
49
|
+
version: '13.0'
|
49
50
|
type: :development
|
50
51
|
prerelease: false
|
51
52
|
version_requirements: !ruby/object:Gem::Requirement
|
52
53
|
requirements:
|
53
54
|
- - "~>"
|
54
55
|
- !ruby/object:Gem::Version
|
55
|
-
version: '
|
56
|
+
version: '13.0'
|
56
57
|
- !ruby/object:Gem::Dependency
|
57
58
|
name: rspec
|
58
59
|
requirement: !ruby/object:Gem::Requirement
|
@@ -101,6 +102,20 @@ dependencies:
|
|
101
102
|
- - "<"
|
102
103
|
- !ruby/object:Gem::Version
|
103
104
|
version: 3.0.0
|
105
|
+
- !ruby/object:Gem::Dependency
|
106
|
+
name: hooks
|
107
|
+
requirement: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: 0.4.1
|
112
|
+
type: :runtime
|
113
|
+
prerelease: false
|
114
|
+
version_requirements: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: 0.4.1
|
104
119
|
- !ruby/object:Gem::Dependency
|
105
120
|
name: mongoid
|
106
121
|
requirement: !ruby/object:Gem::Requirement
|
@@ -139,6 +154,7 @@ description:
|
|
139
154
|
email:
|
140
155
|
- andy@boost.co.nz
|
141
156
|
- montgomery@boost.co.nz
|
157
|
+
- greg@boost.co.nz
|
142
158
|
executables: []
|
143
159
|
extensions: []
|
144
160
|
extra_rdoc_files: []
|
@@ -162,6 +178,7 @@ files:
|
|
162
178
|
- lib/consyncful.rb
|
163
179
|
- lib/consyncful/base.rb
|
164
180
|
- lib/consyncful/item_mapper.rb
|
181
|
+
- lib/consyncful/persisted_item.rb
|
165
182
|
- lib/consyncful/railtie.rb
|
166
183
|
- lib/consyncful/stats.rb
|
167
184
|
- lib/consyncful/sync.rb
|
@@ -186,8 +203,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
186
203
|
- !ruby/object:Gem::Version
|
187
204
|
version: '0'
|
188
205
|
requirements: []
|
189
|
-
|
190
|
-
rubygems_version: 2.7.6.2
|
206
|
+
rubygems_version: 3.1.2
|
191
207
|
signing_key:
|
192
208
|
specification_version: 4
|
193
209
|
summary: Contentful to local database synchronisation for Rails
|