webflow_sync 1.1.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e85c49b258ebbde8c6d5984893be9f59acf389505f0af30951d98fccc4b2f0d
4
- data.tar.gz: '09b216274b17cfd61ff9aa1c2a2cc984b12655302daa4d25e3bb0e37a29b1180'
3
+ metadata.gz: 071c32fa2d8e2e8cd05717a410d44b382b52487f00111113dbbecdb4de1c5280
4
+ data.tar.gz: 15deffb7ed2f04269423ec82498160afb8a928e26b9021a085b6e433a1a52bb7
5
5
  SHA512:
6
- metadata.gz: 3bea1e564a550a72cf7164e3cce4fb7710478e3683c8a1067b1dfc790b87679a59f4a03985fdf610628deeb4334d0d5bd540391afb85d4a41cbb85b67f323525
7
- data.tar.gz: bc6bba426b31b48d20caa8ebbfa88e3ec3237b51faa8ac520e715c73fdedf18c1807eff59932e512200d47ed0fb7e921c0ca10b2cd5531cba5cc0096487f6323
6
+ metadata.gz: b8aeed4c68a939a8a377a5c1e6eee0154fa19778c0b5f1ac725ad36df80d8bcd8f4505bd07c48c6d44dcc3c71b26166a833e512fa7af801da8fc7fcd7f9ff6ad
7
+ data.tar.gz: 307d91073258ab1f08c1d47d47d6126f315ee2be9ed9781dc6ba05e299f846a483843798d2c80cff902767995677e99632e0bb1fb5efb0d38ff63e3883e41975
data/README.md CHANGED
@@ -5,7 +5,9 @@
5
5
 
6
6
  Keep your Ruby on Rails records in sync with WebFlow.*
7
7
 
8
- *Currently only one way Rails => WebFlow synchronization works
8
+ *Currently only one way Rails => WebFlow synchronization.
9
+
10
+ For the latest changes, check the [CHANGELOG.md](CHANGELOG.md).
9
11
 
10
12
  ## Installation
11
13
 
@@ -36,6 +38,35 @@ Run API token Rails generator and follow instructions:
36
38
  ```bash
37
39
  bundle exec rails generate webflow_sync:api_token_flow
38
40
  ```
41
+ ### Configuration options
42
+
43
+ In `config/initializers/webflow_sync.rb` you can specify configuration options:
44
+
45
+ 1. `api_token`
46
+ 2. `webflow_site_id`
47
+ 3. `skip_webflow_sync` - skip synchronization for different environments.
48
+ 4. `publish_on_sync` - republish all domains after create, update and destroy actions.
49
+ 5. `sync_webflow_slug` - save slug generated on WebFlow to the Rails model, `webflow_slug` column.
50
+
51
+ This can be useful if you want to link to WebFlow item directly from your Rails app:
52
+
53
+ ```rb
54
+ link_to('View on site', "https://#{webflow_domain}/articles/#{record.webflow_slug}", target: :blank)
55
+ ```
56
+
57
+ To save slug generated on WebFlow in Rails model, `webflow_slug` column:
58
+
59
+ 1. add `webflow_slug` column on the model table, then
60
+ 2. set the `sync_webflow_slug` option to `true`.
61
+
62
+ Example:
63
+
64
+ ```rb
65
+ WebflowSync.configure do |config|
66
+ config.skip_webflow_sync = ActiveModel::Type::Boolean.new.cast(ENV.fetch('SKIP_WEBFLOW_SYNC'))
67
+ config.sync_webflow_slug = ActiveModel::Type::Boolean.new.cast(ENV.fetch('SYNC_WEBFLOW_SLUG'))
68
+ end
69
+ ```
39
70
 
40
71
  ### Add WebflowSync to models
41
72
 
@@ -51,17 +82,27 @@ Please note that this _does not_ create a WebFlow collection as that's not possi
51
82
 
52
83
  As mentioned above, you need to create the WebFlow collection yourself.
53
84
 
54
- Make sure that the collection `slug` matches the Rails model collection name (the output of `model_class.model_name.collection`).
85
+ Make sure that the collection `slug` matches the Rails model collection name (the output of `model_class.model_name.to_s.underscore.dasherize.pluralize`).
55
86
 
56
87
  For example, for `Article` model:
57
88
 
58
89
  ```ruby
59
- > Article.model_name.collection
90
+ > Article.model_name.to_s.underscore.dasherize.pluralize
60
91
  # => "articles"
61
92
  ```
62
93
 
63
94
  Your WebFlow collection `slug` should be `"articles"`.
64
95
 
96
+ For Rails models named with multiple words, make a collection that have a space between words in the name. WebFlow will set the `slug` by replacing space for "-".
97
+
98
+ For example, for `FeaturedArticle` model:
99
+
100
+ ```ruby
101
+ > FeaturedArticle.model_name.to_s.underscore.dasherize.pluralize
102
+ # => "featured-articles"
103
+ ```
104
+ Your WebFlow collection `slug` in this case should be `"featured-articles"`.
105
+
65
106
  ### Set `webflow_site_id`
66
107
 
67
108
  There are couple of ways how you can set the `webflow_site_id` to be used.
@@ -128,13 +169,43 @@ Or if you already use `#as_json` for some other use-case and cannot modify it, y
128
169
  # app/models/article.rb
129
170
  class Article < ApplicationRecord
130
171
  include WebflowSync::ItemSync
131
-
172
+
132
173
  def as_webflow_json
133
174
  self.as_json
134
175
  end
135
176
  end
136
177
  ```
137
178
 
179
+ ### Sync a Rails model with the custom collection
180
+
181
+ If collection `slug` does not match the Rails model collection name, you can still sync with WebFlow collection, but need to specify collection slug for each `CreateItemJob` and `UpdateItemJob` call. If not specified, model name is used as a default slug name.
182
+ You would also need to replace included `WebflowSync::ItemSync` with custom callbacks that will call appropriate WebflowSync jobs.
183
+
184
+ For example:
185
+
186
+ ```ruby
187
+ WebflowSync::CreateItemJob.perform_later(model_name, id, collection_slug)
188
+ WebflowSync::UpdateItemJob.perform_later(model_name, id, collection_slug)
189
+ WebflowSync::DestroyItemJob.perform_later(collection_slug:, webflow_site_id:, webflow_item_id:)
190
+ ```
191
+
192
+ Where:
193
+
194
+ 1. `model_name` - Rails model that has `webflow_site_id` and `webflow_item_id` defined
195
+ 2. `collection_slug` - slug of the WebFlow collection (defaults to: `model_name.underscore.dasherize.pluralize `)
196
+
197
+
198
+ For example:
199
+
200
+ ```ruby
201
+ WebflowSync::CreateItemJob.perform_now('articles', 1, 'stories')
202
+ ```
203
+ Or, if you want to use the default 'articles' collection_slug:
204
+
205
+ ```ruby
206
+ WebflowSync::CreateItemJob.perform_now('articles', 1)
207
+ ```
208
+
138
209
  ### Run the initial sync
139
210
 
140
211
  After setting up which models you want to sync to WebFlow, you can run the initial sync for each of the models:
@@ -2,8 +2,16 @@
2
2
 
3
3
  module WebflowSync
4
4
  class CreateItemJob < ApplicationJob
5
- def perform(collection_slug, id)
6
- model_class = collection_slug.classify.constantize
5
+ # WebFlow collection slug should be in plural form.
6
+ # For collections that have spaces in their name, WebFlow sets slug by replacing space for "-".
7
+ # 'JobListing'.underscore.dasherize.pluralize => "job-listings"
8
+ # 'job_listing'.underscore.dasherize.pluralize => "job-listings"
9
+ # We can sync Rails model that has different class name than its WebFlow collection
10
+ # model_name => Rails model that has webflow_site_id and webflow_item_id columns
11
+ # collection_slug => slug of the WebFlow collection
12
+ # model_name = 'articles'; id = article.id, collection_slug = 'stories'
13
+ def perform(model_name, id, collection_slug = model_name.underscore.dasherize.pluralize)
14
+ model_class = model_name.underscore.classify.constantize
7
15
  record = model_class.find_by(id: id)
8
16
  return if record.blank?
9
17
  return if record.webflow_site_id.blank?
@@ -3,7 +3,7 @@
3
3
  module WebflowSync
4
4
  class InitialSyncJob < ApplicationJob
5
5
  def perform(collection_slug)
6
- model_class = collection_slug.classify.constantize
6
+ model_class = collection_slug.underscore.classify.constantize
7
7
  model_class.where(webflow_item_id: nil).find_each do |record|
8
8
  next if record.webflow_site_id.blank?
9
9
 
@@ -2,12 +2,16 @@
2
2
 
3
3
  module WebflowSync
4
4
  class UpdateItemJob < ApplicationJob
5
- def perform(collection_slug, id)
6
- model_class = collection_slug.classify.constantize
5
+ # WebFlow collection slug should be in plural form.
6
+ # For collections that have spaces in their name, WebFlow sets slug by replacing space for "-".
7
+ # 'JobListing'.underscore.dasherize.pluralize => 'job-listings'
8
+ # 'job_listing'.underscore.dasherize.pluralize => 'job-listings'
9
+ def perform(model_name, id, collection_slug = model_name.underscore.dasherize.pluralize)
10
+ model_class = model_name.underscore.classify.constantize
7
11
  record = model_class.find_by(id: id)
8
12
  return if record.blank?
9
13
  return if record.webflow_site_id.blank?
10
- return WebflowSync::CreateItemJob.perform_now(collection_slug, id) if record.webflow_item_id.blank?
14
+ return WebflowSync::CreateItemJob.perform_now(model_name, id, collection_slug) if record.webflow_item_id.blank?
11
15
 
12
16
  WebflowSync::Api.new(record.webflow_site_id).update_item(record, collection_slug)
13
17
  end
@@ -26,8 +26,10 @@ module WebflowSync
26
26
 
27
27
  def destroy_webflow_item
28
28
  return if self.skip_webflow_sync || WebflowSync.configuration.skip_webflow_sync
29
+ # Make sure slug is in the pulural form, and multiple words slug separated by dashes
30
+ collection_slug = self.model_name.collection.underscore.dasherize
29
31
 
30
- WebflowSync::DestroyItemJob.perform_later(collection_slug: self.model_name.collection,
32
+ WebflowSync::DestroyItemJob.perform_later(collection_slug: collection_slug,
31
33
  webflow_site_id: self.webflow_site_id,
32
34
  webflow_item_id: self.webflow_item_id)
33
35
  end
@@ -8,20 +8,45 @@ module WebflowSync
8
8
  @site_id = site_id
9
9
  end
10
10
 
11
+ def get_all_items(collection_slug:, page_limit: 100) # rubocop:disable Metrics/MethodLength
12
+ collection_id = find_webflow_collection(collection_slug)['_id']
13
+ max_items_per_page = page_limit # Webflow::Error: 'limit' must be less than or equal to 100
14
+ first_page_number = 1
15
+
16
+ result = make_request(:paginate_items, collection_id, page: first_page_number, per_page: max_items_per_page)
17
+ puts "Get all items from WebFlow for #{collection_slug} page: #{first_page_number}"
18
+
19
+ total_items = result['total']
20
+ total_pages = (total_items.to_f / max_items_per_page).ceil
21
+ items = result['items']
22
+
23
+ (2..total_pages).each do |page_number|
24
+ next_page_items = make_request(:paginate_items, collection_id,
25
+ page: page_number, per_page: max_items_per_page)['items']
26
+ puts "Get all items from WebFlow for #{collection_slug} page: #{page_number}"
27
+
28
+ items.concat next_page_items
29
+ end
30
+
31
+ items
32
+ end
33
+
11
34
  def get_item(collection_slug, webflow_item_id)
12
35
  collection = find_webflow_collection(collection_slug)
13
- client.item(collection['_id'], webflow_item_id)
36
+
37
+ make_request(:item, collection['_id'], webflow_item_id)
14
38
  end
15
39
 
16
40
  def create_item(record, collection_slug) # rubocop:disable Metrics/MethodLength
17
41
  collection = find_webflow_collection(collection_slug)
18
- response = client.create_item(
19
- collection['_id'],
20
- record.as_webflow_json.reverse_merge(_archived: false, _draft: false), live: true
21
- )
42
+ response = make_request(:create_item, collection['_id'],
43
+ record.as_webflow_json.reverse_merge(_archived: false, _draft: false), live: true)
44
+
45
+ if update_record_colums(record, response)
46
+ # When the item is created, sometimes changes are not visible throughout the WebFlow site immediately (probably due to some caching).
47
+ # To make this change immediately visible from the WebFlow site, we need to publish the site.
48
+ publish
22
49
 
23
- # use update_column to skip callbacks to prevent WebflowSync::ItemSync to kick off
24
- if record.update_column(:webflow_item_id, response['_id']) # rubocop:disable Rails/SkipsModelValidations
25
50
  puts "Created #{record.inspect} in #{collection_slug}"
26
51
  response
27
52
  else
@@ -32,21 +57,37 @@ module WebflowSync
32
57
 
33
58
  def update_item(record, collection_slug)
34
59
  collection = find_webflow_collection(collection_slug)
35
- response = client.update_item(
36
- { '_cid' => collection['_id'], '_id' => record.webflow_item_id },
37
- record.as_webflow_json.reverse_merge(_archived: false, _draft: false), live: true
38
- )
60
+ response = make_request(:update_item, { '_cid' => collection['_id'], '_id' => record.webflow_item_id },
61
+ record.as_webflow_json.reverse_merge(_archived: false, _draft: false), live: true)
62
+
63
+ # When the item is updated, sometimes changes are not visible throughout the WebFlow site immediately (probably due to some caching).
64
+ # To make this change immediately visible from the WebFlow site, we need to publish the site.
65
+ publish
66
+
39
67
  puts "Updated #{record.inspect} in #{collection_slug}"
40
68
  response
41
69
  end
42
70
 
43
71
  def delete_item(collection_slug, webflow_item_id)
44
72
  collection = find_webflow_collection(collection_slug)
45
- response = client.delete_item({ '_cid' => collection['_id'], '_id' => webflow_item_id })
73
+ response = make_request(:delete_item, { '_cid' => collection['_id'], '_id' => webflow_item_id })
74
+ # When the item is removed from WebFlow, it's still visible throughout the WebFlow site (probably due to some caching).
75
+ # To remove the item immediately from the WebFlow site, we need to publish the site.
76
+ publish
77
+
46
78
  puts "Deleted #{webflow_item_id} from #{collection_slug}"
47
79
  response
48
80
  end
49
81
 
82
+ def publish
83
+ return unless WebflowSync.configuration.publish_on_sync
84
+
85
+ response = make_request(:publish, site_id, domain_names: domain_names)
86
+
87
+ puts "Publish all domains for Webflow site with id: #{site_id}"
88
+ response
89
+ end
90
+
50
91
  private
51
92
 
52
93
  def client
@@ -57,11 +98,50 @@ module WebflowSync
57
98
  @collections ||= client.collections(site_id)
58
99
  end
59
100
 
101
+ def domain_names
102
+ @domain_names ||= find_domain_names
103
+ end
104
+
105
+ def find_domain_names
106
+ # client.domains request does not return the default domain
107
+ # We need to get default domain name if there are no custom domains set to avoid error:
108
+ # Webflow::Error: Domain not found for site
109
+ site = client.site(site_id)
110
+ default_domain_name = "#{site['shortName']}.webflow.io"
111
+ names = [default_domain_name]
112
+ client.domains(site_id).each { |domain| names << domain['name'] }
113
+
114
+ names
115
+ end
116
+
60
117
  def find_webflow_collection(collection_slug)
61
118
  response = collections.find { |collection| collection['slug'] == collection_slug }
62
119
  raise "Cannot find collection #{collection_slug} for Webflow site #{site_id}" unless response
63
120
 
64
121
  response
65
122
  end
123
+
124
+ def update_record_colums(record, response)
125
+ # use update_column to skip callbacks to prevent WebflowSync::ItemSync to kick off
126
+ if WebflowSync.configuration.sync_webflow_slug
127
+ record.update_columns(webflow_item_id: response['_id'], webflow_slug: response['slug']) # rubocop:disable Rails/SkipsModelValidations
128
+ else
129
+ record.update_column(:webflow_item_id, response['_id']) # rubocop:disable Rails/SkipsModelValidations
130
+ end
131
+ end
132
+
133
+ def make_request(method_name, *args, retries: 0, **kwargs)
134
+ if kwargs.present?
135
+ client.public_send(method_name, *args, **kwargs)
136
+ else
137
+ client.public_send(method_name, *args)
138
+ end
139
+ rescue Webflow::Error => e
140
+ raise if retries >= 8 || e.message.strip != 'Rate limit hit'
141
+
142
+ puts "Sleeping #{2**retries} seconds"
143
+ sleep 2**retries
144
+ make_request(method_name, *args, retries: retries + 1, **kwargs)
145
+ end
66
146
  end
67
147
  end
@@ -4,4 +4,6 @@ WebflowSync.configure do |config|
4
4
  # config.api_token = ENV.fetch('WEBFLOW_API_TOKEN')
5
5
  # config.skip_webflow_sync = Rails.env.test? # default
6
6
  config.webflow_site_id = ENV.fetch('WEBFLOW_SITE_ID')
7
+ config.sync_webflow_slug = ENV.fetch('SYNC_WEBFLOW_SLUG')
8
+ config.publish_on_sync = true
7
9
  end
@@ -6,10 +6,13 @@ module WebflowSync
6
6
 
7
7
  def configure
8
8
  self.configuration ||= Configuration.new
9
+
10
+ self.configuration.publish_on_sync = true
11
+ self.configuration.skip_webflow_sync = !Rails.env.production?
12
+
9
13
  yield(self.configuration)
10
14
 
11
15
  self.configuration.api_token ||= ENV.fetch('WEBFLOW_API_TOKEN')
12
- defined?(self.configuration.skip_webflow_sync) or self.configuration.skip_webflow_sync = !Rails.env.production?
13
16
  end
14
17
 
15
18
  private
@@ -18,7 +21,7 @@ module WebflowSync
18
21
  end
19
22
 
20
23
  class Configuration
21
- attr_accessor :skip_webflow_sync, :webflow_site_id
24
+ attr_accessor :skip_webflow_sync, :webflow_site_id, :sync_webflow_slug, :publish_on_sync
22
25
  attr_reader :api_token
23
26
 
24
27
  def api_token=(value)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WebflowSync
4
- VERSION = '1.1.0'
4
+ VERSION = '3.1.1'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: webflow_sync
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 3.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Viktor
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-25 00:00:00.000000000 Z
11
+ date: 2021-08-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dotenv-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
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: factory_bot_rails
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -173,7 +187,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
173
187
  - !ruby/object:Gem::Version
174
188
  version: '0'
175
189
  requirements: []
176
- rubygems_version: 3.2.11
190
+ rubygems_version: 3.0.9
177
191
  signing_key:
178
192
  specification_version: 4
179
193
  summary: Keep Rails models in sync with WebFlow.