webflow_sync 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 14705897f82379cd4aa7b11ceda57374b6066d26b96344318ec4089048c377bb
4
+ data.tar.gz: b17539c21bc34e339ddd12843831fcf61294876d6bc136d6ac7abbced15826fb
5
+ SHA512:
6
+ metadata.gz: fb9e2b651a04fd77506a3c869c5b33716131fa765ad413ac6180ca5c1805e141bb6b3b7310c4a8dbdc3f6e71782e7a1bf1a62bdbb78b8c224d8ef3c0669c3e74
7
+ data.tar.gz: e6bcaf360ecf06bbaf08f78664715dceea5f10a3f529912f9d445aaa39fbe769b4c0fc4c11c583d5e6977e5e3885dc90c17b3a1c8f17c404b585140a3de57dc0
@@ -0,0 +1,20 @@
1
+ Copyright 2021 Viktor
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,82 @@
1
+ ![build](https://github.com/vfonic/webflow_sync/workflows/build/badge.svg)
2
+
3
+ # WebflowSync
4
+
5
+ Keep your Ruby on Rails records in sync with WebFlow.*
6
+
7
+ *Currently only one way Rails => WebFlow synchronization works
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'webflow_sync'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ ```bash
20
+ $ bundle
21
+ ```
22
+
23
+ Then run the install generator:
24
+
25
+ ```bash
26
+ bundle exec rails generate webflow_sync:install
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ ### Add WebflowSync to models
32
+
33
+ For each model that you want to sync to WebFlow, you need to run the collection generator:
34
+
35
+ ```bash
36
+ bundle exec rails generate webflow_sync:collection Article
37
+ ```
38
+
39
+ Please note that this _does not_ create a WebFlow collection as that's not possible to do through WebFlow API.
40
+
41
+ ### Create WebFlow collections
42
+
43
+ As mentioned above, you need to create the WebFlow collection yourself.
44
+
45
+ Make sure that the collection `slug` matches the Rails model collection name (the output of `model_class.model_name.collection`).
46
+
47
+ For example, for `Article` model:
48
+
49
+ ```ruby
50
+ > Article.model_name.collection
51
+ # => "articles"
52
+ ```
53
+
54
+ Your WebFlow collection `slug` should be `"articles"`.
55
+
56
+ ### Run the initial sync
57
+
58
+ After setting up which models you want to sync to WebFlow, you can run the initial sync for each of the models:
59
+
60
+ ```ruby
61
+ WebflowSync::InitialSyncJob.perform_later('articles')
62
+ ```
63
+
64
+ You can also run this from a Rake task:
65
+
66
+ ```ruby
67
+ bundle exec rails "webflow_sync:initial_sync[articles]"
68
+ ```
69
+
70
+ *Quotes are needed in order for this to work in all shells.
71
+
72
+ ### Important note
73
+
74
+ This gem silently "fails" (does nothing) when `webflow_site_id` or `webflow_item_id` is `nil`! This is not always desired behavior so be aware of that.
75
+
76
+ ## Contributing
77
+
78
+ PRs welcome!
79
+
80
+ ## License
81
+
82
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
6
+ load 'rails/tasks/engine.rake'
7
+
8
+ load 'rails/tasks/statistics.rake'
9
+
10
+ require 'bundler/gem_tasks'
11
+
12
+ require 'stylecheck/rake_tasks' unless Rails.env.production?
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebflowSync
4
+ class CreateItemJob < ApplicationJob
5
+ def perform(collection_slug, id)
6
+ model_class = collection_slug.classify.constantize
7
+ record = model_class.find_by(id: id)
8
+ return if record.blank?
9
+ return if record.webflow_site_id.blank?
10
+
11
+ WebflowSync::Api.new(record.webflow_site_id).create_item(record, collection_slug)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebflowSync
4
+ class DestroyItemJob < ApplicationJob
5
+ def perform(collection_slug:, webflow_site_id:, webflow_item_id:)
6
+ return if webflow_site_id.blank?
7
+ return if webflow_item_id.blank?
8
+
9
+ WebflowSync::Api.new(webflow_site_id).delete_item(collection_slug, webflow_item_id)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebflowSync
4
+ class InitialSyncJob < ApplicationJob
5
+ def perform(collection_slug)
6
+ model_class = collection_slug.classify.constantize
7
+ model_class.where(webflow_item_id: nil).find_each do |record|
8
+ next if record.webflow_site_id.blank?
9
+
10
+ WebflowSync::Api.new(record.webflow_site_id).create_item(record, collection_slug)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebflowSync
4
+ class UpdateItemJob < ApplicationJob
5
+ def perform(collection_slug, id)
6
+ model_class = collection_slug.classify.constantize
7
+ record = model_class.find_by(id: id)
8
+ return if record.blank?
9
+ return if record.webflow_site_id.blank?
10
+ return if record.webflow_item_id.blank?
11
+
12
+ WebflowSync::Api.new(record.webflow_site_id).update_item(record, collection_slug)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Synchronizes any changes to public records to Webflow
4
+ module WebflowSync
5
+ module Callbacks
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ attr_accessor :skip_webflow_sync
10
+
11
+ after_create :create_webflow_item
12
+ after_update :update_webflow_item
13
+ after_destroy :destroy_webflow_item
14
+
15
+ def create_webflow_item
16
+ return if self.skip_webflow_sync || WebflowSync.configuration.skip_webflow_sync
17
+
18
+ WebflowSync::CreateItemJob.perform_later(self.model_name.collection, self.id)
19
+ end
20
+
21
+ def update_webflow_item
22
+ return if self.skip_webflow_sync || WebflowSync.configuration.skip_webflow_sync
23
+
24
+ WebflowSync::UpdateItemJob.perform_later(self.model_name.collection, self.id)
25
+ end
26
+
27
+ def destroy_webflow_item
28
+ return if self.skip_webflow_sync || WebflowSync.configuration.skip_webflow_sync
29
+
30
+ WebflowSync::DestroyItemJob.perform_later(collection_slug: self.model_name.collection,
31
+ webflow_site_id: self.webflow_site_id,
32
+ webflow_item_id: self.webflow_item_id)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Synchronizes any changes to public records to Webflow
4
+ module WebflowSync
5
+ module ItemSync
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include WebflowSync::Callbacks
10
+
11
+ # override this method to get more granular, per site customization
12
+ # for example, you could have Site model in your codebase:
13
+ #
14
+ # class Site < ApplicationRecord
15
+ # has_many :articles
16
+ # end
17
+ #
18
+ # class Article < ApplicationRecord
19
+ # belongs_to :site
20
+ #
21
+ # def webflow_site_id
22
+ # self.site.webflow_site_id
23
+ # end
24
+ # end
25
+ #
26
+ def webflow_site_id
27
+ WebflowSync.configuration.webflow_site_id
28
+ end
29
+
30
+ # You can customize this to your liking:
31
+ # def as_webflow_json
32
+ # {
33
+ # title: self.title.capitalize,
34
+ # slug: self.title.parameterize,
35
+ # published_at: self.created_at,
36
+ # image: self.image_url
37
+ # }
38
+ # end
39
+ def as_webflow_json
40
+ self.as_json
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebflowSync
4
+ class Api
5
+ attr_reader :site_id
6
+
7
+ def initialize(site_id)
8
+ @site_id = site_id
9
+ end
10
+
11
+ def create_item(record, collection_slug)
12
+ collection = find_webflow_collection(collection_slug)
13
+ response = client.create_item(collection['_id'], record.as_webflow_json, live: true)
14
+
15
+ record.update!(webflow_item_id: response['_id'])
16
+ end
17
+
18
+ def update_item(record, collection_slug)
19
+ collection = find_webflow_collection(collection_slug)
20
+ client.update_item(
21
+ { '_cid' => collection['_id'], '_id' => record.webflow_item_id },
22
+ record.as_webflow_json, live: true
23
+ )
24
+ end
25
+
26
+ def delete_item(collection_slug, webflow_item_id)
27
+ collection = find_webflow_collection(collection_slug)
28
+ client.delete_item({ '_cid' => collection['_id'], '_id' => webflow_item_id })
29
+ end
30
+
31
+ private
32
+
33
+ def client
34
+ @client ||= ::Webflow::Client.new
35
+ end
36
+
37
+ def collections
38
+ @collections ||= client.collections(site_id)
39
+ end
40
+
41
+ def find_webflow_collection(collection_slug)
42
+ result = collections.find { |collection| collection['slug'] == collection_slug }
43
+ raise "Cannot find collection #{collection_slug} for Webflow site #{site_id}" unless result
44
+
45
+ result
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/active_record'
4
+
5
+ module WebflowSync
6
+ module Generators
7
+ class CollectionGenerator < Rails::Generators::NamedBase
8
+ desc 'Registers ActiveRecord model to sync to WebFlow collection'
9
+
10
+ source_root File.expand_path('templates', __dir__)
11
+
12
+ include Rails::Generators::Migration
13
+ def add_migration
14
+ migration_template 'migration.rb.erb', "#{migration_path}/add_webflow_item_id_to_#{table_name}.rb",
15
+ migration_version: migration_version
16
+ end
17
+
18
+ def include_item_sync_in_model_file
19
+ module_snippet = <<~END_OF_INCLUDE.indent(2)
20
+
21
+ include WebflowSync::ItemSync
22
+ END_OF_INCLUDE
23
+
24
+ insert_into_file "app/models/#{name.underscore}.rb", module_snippet, after: / < ApplicationRecord$/
25
+ end
26
+
27
+ def self.next_migration_number(dirname)
28
+ ActiveRecord::Generators::Base.next_migration_number(dirname)
29
+ end
30
+
31
+ private
32
+
33
+ def migration_path
34
+ ActiveRecord::Migrator.migrations_paths.first
35
+ end
36
+
37
+ def migration_version
38
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebflowSync
4
+ module Generators
5
+ class InstallGenerator < ::Rails::Generators::Base
6
+ source_root File.expand_path('templates', __dir__)
7
+
8
+ def add_config_initializer
9
+ template 'webflow_sync.rb', 'config/initializers/webflow_sync.rb'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ class AddWebflowItemIdTo<%= table_name.camelize %> < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ add_column :<%= table_name %>, :webflow_item_id, :string
4
+ add_index :<%= table_name %>, :webflow_item_id, unique: true
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ WebflowSync.configure do |config|
4
+ # config.skip_webflow_sync = Rails.env.test? # default
5
+ config.webflow_site_id = ENV.fetch('WEBFLOW_SITE_ID')
6
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :webflow_sync do
4
+ desc 'Perform initial sync from Rails to WebFlow'
5
+ task :initial_sync, %i[collection_slug] => :environment do |_task, args|
6
+ WebflowSync::InitialSyncJob.perform_later(args[:collection_slug])
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'webflow_sync/version'
4
+ require 'webflow_sync/configuration'
5
+ require 'webflow_sync/engine'
6
+ require 'webflow'
7
+
8
+ module WebflowSync
9
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebflowSync
4
+ class << self
5
+ attr_reader :configuration
6
+
7
+ def configure
8
+ self.configuration ||= Configuration.new
9
+ yield(configuration)
10
+ end
11
+
12
+ private
13
+
14
+ attr_writer :configuration
15
+ end
16
+
17
+ class Configuration
18
+ attr_accessor :skip_webflow_sync, :webflow_site_id
19
+
20
+ def initialize
21
+ @skip_webflow_sync = Rails.env.test?
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebflowSync
4
+ class Engine < ::Rails::Engine
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebflowSync
4
+ class Railtie < ::Rails::Railtie
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebflowSync
4
+ VERSION = '0.1.0'
5
+ end
metadata ADDED
@@ -0,0 +1,177 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: webflow_sync
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Viktor
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-01-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: webflow-ruby
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: factory_bot_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'
55
+ - !ruby/object:Gem::Dependency
56
+ name: guard-rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec-rails
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: vcr
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: webmock
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description:
126
+ email:
127
+ - viktor.fonic@gmail.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - MIT-LICENSE
133
+ - README.md
134
+ - Rakefile
135
+ - app/jobs/webflow_sync/create_item_job.rb
136
+ - app/jobs/webflow_sync/destroy_item_job.rb
137
+ - app/jobs/webflow_sync/initial_sync_job.rb
138
+ - app/jobs/webflow_sync/update_item_job.rb
139
+ - app/models/concerns/webflow_sync/callbacks.rb
140
+ - app/models/concerns/webflow_sync/item_sync.rb
141
+ - app/services/webflow_sync/api.rb
142
+ - lib/generators/webflow_sync/collection_generator.rb
143
+ - lib/generators/webflow_sync/install_generator.rb
144
+ - lib/generators/webflow_sync/templates/migration.rb.erb
145
+ - lib/generators/webflow_sync/templates/webflow_sync.rb
146
+ - lib/tasks/webflow_sync_tasks.rake
147
+ - lib/webflow_sync.rb
148
+ - lib/webflow_sync/configuration.rb
149
+ - lib/webflow_sync/engine.rb
150
+ - lib/webflow_sync/railtie.rb
151
+ - lib/webflow_sync/version.rb
152
+ homepage: https://github.com/vfonic/webflow_sync
153
+ licenses:
154
+ - MIT
155
+ metadata:
156
+ homepage_uri: https://github.com/vfonic/webflow_sync
157
+ source_code_uri: https://github.com/vfonic/webflow_sync
158
+ post_install_message:
159
+ rdoc_options: []
160
+ require_paths:
161
+ - lib
162
+ required_ruby_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '2.6'
167
+ required_rubygems_version: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - ">="
170
+ - !ruby/object:Gem::Version
171
+ version: '0'
172
+ requirements: []
173
+ rubygems_version: 3.2.3
174
+ signing_key:
175
+ specification_version: 4
176
+ summary: Keep Rails models in sync with WebFlow.
177
+ test_files: []