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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: abe63339fd87ddbe4b2456fd22a333580d6865c5981b4b9c9a3660d11943a8e3
4
- data.tar.gz: be20c90cef3dc401fb3ab55eaec17c68d8484cf475aace68d50aec7bb626092e
3
+ metadata.gz: 6a8f36ac50a10800cbce6e6b9d086e346b1e7362e422080d532a87bb981d8135
4
+ data.tar.gz: 6df1ea6cf1cb05f405938d83e09436927e21b7828424f33c79a105e4c11da127
5
5
  SHA512:
6
- metadata.gz: c7f91c8cf381d8eb5be0990261d9344fe870648b5f0879290ae8defbfdbc79a51d61b0db227c21de4d8be2d734e655070882dfe2e44acf9983c099b0366e4654
7
- data.tar.gz: e3000098bc7effd1432ebfabffc511e764bdf7e3e562544c50d66e390e3936a486c297b4c3fb88cf062f7512608ebfbc631a52623b0a1371f869e63d615a70f2
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 2020-01-24 14:47:44 +1300 using RuboCop version 0.79.0.
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: 7
20
+ # Offense count: 10
14
21
  # Configuration parameters: CountComments, ExcludedMethods.
15
22
  # ExcludedMethods: refine
16
23
  Metrics/BlockLength:
17
- Max: 153
24
+ Max: 175
18
25
 
19
- # Offense count: 4
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: 7
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: 42
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.5.5
1
+ 2.7.1
data/.travis.yml CHANGED
@@ -5,10 +5,10 @@ cache: bundler
5
5
  rvm:
6
6
  - 2.5.1
7
7
  - 2.6.5
8
- before_install: gem install bundler -v 2.1.0
8
+ before_install: gem install bundler -v 2.1.4
9
9
  services:
10
10
  - mongodb
11
11
 
12
- script:
12
+ script:
13
13
  - bundle exec rake spec
14
14
  - bundle exec rubocop -P
data/Gemfile.lock CHANGED
@@ -1,64 +1,67 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- consyncful (0.3.2)
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.0.2.1)
13
- activesupport (= 6.0.2.1)
14
- activesupport (6.0.2.1)
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 (>= 0.7, < 2)
17
- minitest (~> 5.1)
18
- tzinfo (~> 1.1)
19
- zeitwerk (~> 2.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.7.1)
24
- concurrent-ruby (1.1.6)
25
- contentful (2.15.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.12.2)
33
+ ffi (1.15.0)
33
34
  ffi-compiler (1.0.1)
34
35
  ffi (>= 1.0.0)
35
36
  rake
36
- http (4.3.0)
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.2.0)
44
- http-parser (1.2.1)
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.2)
49
+ i18n (1.8.10)
47
50
  concurrent-ruby (~> 1.0)
48
51
  jaro_winkler (1.5.4)
49
- minitest (5.14.0)
50
- mongo (2.11.3)
51
- bson (>= 4.4.2, < 5.0.0)
52
- mongoid (7.0.5)
53
- activemodel (>= 5.1, < 6.1)
54
- mongo (>= 2.5.1, < 3.0.0)
55
- multi_json (1.14.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.3)
62
+ public_suffix (4.0.6)
60
63
  rainbow (3.0.0)
61
- rake (10.5.0)
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
- thread_safe (0.3.6)
84
- tzinfo (1.2.6)
85
- thread_safe (~> 0.1)
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.6)
91
+ unf_ext (0.0.7.7)
89
92
  unicode-display_width (1.6.1)
90
- zeitwerk (2.2.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 (~> 10.0)
102
+ rake (~> 13.0)
100
103
  rspec (~> 3.0)
101
104
  rubocop (= 0.79.0)
102
105
 
103
106
  BUNDLED WITH
104
- 2.1.0
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
- too slow, and makes testing applications painful. Consyncful uses Contentful's syncronisation API
7
- to keep a local copy of the entire content in a Mongo database up to date.
8
-
9
- Once the content is availble locally, finding and interact with contentful data is as easy as
10
- using [Mongoid](https://docs.mongodb.com/mongoid/current/tutorials/mongoid-documents/) ODM.
11
-
12
- This gem doesn't provide any intergration with the management api or any way to update contentful models from the local store. It is strictly read only.
13
-
14
- ## Why do I have to use MongoDB?
15
-
16
- 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. :)
17
- 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.
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 mongoid, generate a mongoid.yml by running:
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
- Consyncful uses [contentful.rb](https://github.com/contentful/contentful.rb) so client options are as documented there.
37
- ```ruby
38
- Consyncful.configure do |config|
39
- config.locale = 'en-NZ'
40
- config.contentful_client_options = {
41
- api_url: 'cdn.contentful.com',
42
- space: 'space_id',
43
- access_token: 'ACCESS TOKEN',
44
- environment: 'master', # optional
45
- logger: Logger.new(STDOUT) # optional for debugging
46
- }
47
- end
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 rails app
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 dynamicly 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:
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 mongoid associations, Consyncful provides the following helpers to set up the correct relationships:
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
- ### Syncronizing contentful data
93
+ ### Synchronizing contentful data
85
94
 
86
- To run a syncronization process run:
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 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.
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 syncronise from scratch run:
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 rails models
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 mongoid [queries](https://docs.mongodb.com/mongoid/current/tutorials/mongoid-queries/).
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 woule expect:
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 contentful. If you want them in the order they appare in contentful, use the `.in_order` helper:
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
- instance.other_things.in_order # ordered the same as in contentful
143
+ Consyncful::Base.where(title: 'a title') # [ #<ModelName>, #<OtherModelName> ]
127
144
  ```
128
145
 
129
- #### Finding entrys from different content types
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
- 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:
150
+ Callbacks can be registered using:
132
151
 
133
152
  ```ruby
134
- Consyncful::Base.where(title: 'a title') # [ #<ModelName>, #<OtherModelName> ]
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
- ## Limitations
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
- ### Locales
179
+ ### Why do I have to use MongoDB?
140
180
 
141
- Current Consyncful only uses one globally configured locale to map the data to the database.
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', '~> 10.0'
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
- class << self
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-US'
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
- def self.client
40
- @client ||= begin
41
- options = Consyncful.configuration.contentful_client_options
42
- options.reverse_merge!(DEFAULT_CLIENT_OPTIONS)
43
- Contentful::Client.new(options)
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
@@ -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 { |a| _base[foreign_key].index(a.id) }
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
- if @item.type == 'Entry'
15
- @item.content_type.id
16
- elsif @item.type == 'Asset'
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(locale)
27
+ def mapped_fields(default_locale)
26
28
  fields = generic_fields
27
29
 
28
- @item.fields_with_locales.each do |field, value_with_locales|
29
- value = value_with_locales[locale.to_sym]
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
- fields[:created_at] = @item.created_at
45
- fields[:updated_at] = @item.updated_at
46
- fields[:revision] = @item.revision
47
- fields[:contentful_type] = type
48
- fields[:synced_at] = Time.current
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 raw_file(locale)
53
- file_json = @item.raw.fetch('fields', {}).fetch('file', nil)
54
- file_json[locale] unless file_json.nil?
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 assign_field(hash, field, value)
93
+ def mapped_field_entry_for(field, value)
70
94
  if single_reference?(value)
71
- hash[ActiveSupport::Inflector.foreign_key(field).to_sym] = value.id
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
- hash[ids_field_name.to_sym] = value.map(&:id)
98
+ [ids_field_name.to_sym, value.map(&:id)]
75
99
  else
76
- hash[field] = value
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
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Adds Consyncful task to Rails
3
4
  class Consyncful::Railtie < Rails::Railtie
4
5
  rake_tasks do
5
6
  load 'consyncful/tasks/consyncful.rake'
@@ -3,6 +3,8 @@
3
3
  require 'rainbow'
4
4
 
5
5
  module Consyncful
6
+ ##
7
+ # Responsible for recording changes during a sync for outputting in logs
6
8
  class Stats
7
9
  def initialize
8
10
  @stats = {
@@ -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
- DEFAULT_LOCALE = 'en-NZ'
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
- self.next_url = sync.next_sync_url
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
- if item.deletion?
76
- delete_model(item.id, stats)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Consyncful
4
- VERSION = '0.3.2'
4
+ VERSION = '0.6.1'
5
5
  end
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.3.2
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: 2020-02-26 00:00:00.000000000 Z
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: '10.0'
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: '10.0'
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
- rubyforge_project:
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