contentful-migrations 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0c1bda0be1633e800fda617dde80e5c119ac55a447d41a40d2657b276d63d1c4
4
+ data.tar.gz: 433a6db64e7e1ad24b718ef1e4609e10ee4bbf20d9e61428f8a742fad837de70
5
+ SHA512:
6
+ metadata.gz: 4d22786b8b8bb1f6abab61cd4551adff371491f32881a0b3aadb133646139b18abed22ba10f7a0e53a3e24335e95c89d55a8c55b8c81fa7c45c9f474c9c6416a
7
+ data.tar.gz: 400571dac6d75bd1da0a1708a36de9a3f45da0e6678f3c1fa8e1e35ec483b77715793f96d2689f2128466dd4c1bb2d2df666235d7a4952db79ec0bda2d21a4c1
@@ -0,0 +1,9 @@
1
+ require 'contentful/management'
2
+ require 'contentful_migrations/utils'
3
+ require 'contentful_migrations/version'
4
+ require 'contentful_migrations/migration_content_type'
5
+ require 'contentful_migrations/migration_proxy'
6
+ require 'contentful_migrations/migration'
7
+ require 'contentful_migrations/migrator'
8
+
9
+ load 'tasks/contentful_migrations.rake' if defined?(Rails)
@@ -0,0 +1,42 @@
1
+ module ContentfulMigrations
2
+ class Migration
3
+ attr_reader :name, :version, :contentful_client, :contentful_space
4
+
5
+ def initialize(name = self.class.name, version = nil, client = nil, space = nil)
6
+ @name = name
7
+ @version = version
8
+ @contentful_client = client
9
+ @contentful_space = space
10
+ end
11
+
12
+ def migrate(direction, client, space)
13
+ @contentful_client = client
14
+ @contentful_space = space
15
+ send(direction)
16
+ self
17
+ end
18
+
19
+ def with_space
20
+ yield(contentful_space)
21
+ end
22
+
23
+ def with_editor_interfaces
24
+ yield(contentful_client.editor_interfaces)
25
+ end
26
+
27
+ def record_migration(migration_content_type)
28
+ entry = migration_content_type.entries.create(version: version)
29
+ entry.save
30
+ entry.publish
31
+ entry
32
+ end
33
+
34
+ def erase_migration(migration_content_type)
35
+ entry = migration_content_type.entries.all.find { |m| m.version.to_i == version.to_i }
36
+ return unless entry
37
+ entry.unpublish
38
+ entry.destroy
39
+ entry
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,45 @@
1
+ module ContentfulMigrations
2
+ class MigrationContentType
3
+ DEFAULT_MIGRATION_CONTENT_TYPE = 'migrations'.freeze
4
+
5
+ attr_reader :access_token, :space_id, :client, :space,
6
+ :migration_content_type_name, :logger
7
+
8
+ def initialize(client:,
9
+ space:,
10
+ logger:,
11
+ migration_content_type_name: DEFAULT_MIGRATION_CONTENT_TYPE)
12
+ @client = client
13
+ @space = space
14
+ @logger = logger
15
+ @migration_content_type_name = migration_content_type_name
16
+ end
17
+
18
+ def resolve
19
+ @migration_content_type ||= find_or_create_migration_content_type
20
+ end
21
+
22
+ private
23
+
24
+ def find_or_create_migration_content_type
25
+ content_type = space.content_types.find(migration_content_type_name)
26
+ if content_type.nil? || content_type.is_a?(Contentful::Management::Error)
27
+ build_migration_content_type
28
+ else
29
+ content_type
30
+ end
31
+ end
32
+
33
+ def build_migration_content_type
34
+ content_type = space.content_types.create(
35
+ name: migration_content_type_name,
36
+ id: migration_content_type_name,
37
+ description: 'Migration Table for interal use only, do not delete'
38
+ )
39
+ content_type.fields.create(id: 'version', name: 'version', type: 'Integer')
40
+ content_type.save
41
+ content_type.publish
42
+ content_type
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,35 @@
1
+ require 'forwardable'
2
+ require 'contentful_migrations/utils'
3
+
4
+ module ContentfulMigrations
5
+ # MigrationProxy is used to defer loading of the actual migration classes
6
+ # until they are needed. This implementation is borrowed from activerecord's
7
+ # migration library.
8
+
9
+ MigrationProxy = Struct.new(:name, :version, :filename, :scope) do
10
+ extend Forwardable
11
+ include Utils
12
+
13
+ def initialize(name, version, filename, scope)
14
+ super
15
+ @migration = nil
16
+ end
17
+
18
+ def basename
19
+ File.basename(filename)
20
+ end
21
+
22
+ delegate %i[migrate record_migration erase_migration] => :migration
23
+
24
+ private
25
+
26
+ def migration
27
+ @migration ||= load_migration
28
+ end
29
+
30
+ def load_migration
31
+ require(File.expand_path(filename))
32
+ constantize(name).new(name, version)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,127 @@
1
+ module ContentfulMigrations
2
+ class Migrator
3
+ include Utils
4
+
5
+ class InvalidMigrationPath < StandardError #:nodoc:
6
+ def initialize(migrations_path)
7
+ super("#{migrations_path} is not a valid directory.")
8
+ end
9
+ end
10
+
11
+ DEFAULT_MIGRATION_PATH = 'db/contentful_migrations'.freeze
12
+
13
+ def self.migrate(args = {})
14
+ new(parse_options(args)).migrate
15
+ end
16
+
17
+ def self.rollback(args = {})
18
+ new(parse_options(args)).rollback
19
+ end
20
+
21
+ def self.pending(args = {})
22
+ new(parse_options(args)).pending
23
+ end
24
+
25
+ attr_reader :migrations_path, :access_token, :space_id, :client, :space,
26
+ :migration_content_type_name, :logger
27
+
28
+ def initialize(migrations_path:,
29
+ access_token:,
30
+ space_id:,
31
+ migration_content_type_name:,
32
+ logger:)
33
+ @migrations_path = migrations_path
34
+ @access_token = access_token
35
+ @logger = logger
36
+ @space_id = space_id
37
+ @migration_content_type_name = migration_content_type_name
38
+ @client = Contentful::Management::Client.new(access_token)
39
+ @space = @client.spaces.find(space_id)
40
+ validate_options
41
+ end
42
+
43
+ def migrate
44
+ runnable = migrations(migrations_path).reject { |m| ran?(m) }
45
+ if runnable.empty?
46
+ logger.info('No migrations to run, everything up to date!')
47
+ end
48
+
49
+ runnable.each do |migration|
50
+ logger.info("running migration #{migration.version} #{migration.name} ")
51
+ migration.migrate(:up, client, space)
52
+ migration.record_migration(migration_content_type)
53
+ end
54
+ self
55
+ end
56
+
57
+ def rollback
58
+ already_migrated = migrations(migrations_path).select { |m| ran?(m) }
59
+ migration = already_migrated.pop
60
+ logger.info("Rolling back migration #{migration.version} #{migration.name} ")
61
+ migration.migrate(:down, client, space)
62
+ migration.erase_migration(migration_content_type)
63
+ end
64
+
65
+ def pending
66
+ runnable = migrations(migrations_path).reject { |m| ran?(m) }
67
+
68
+ runnable.each do |migration|
69
+ logger.info("Pending #{migration.version} #{migration.name} ")
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def self.parse_options(args)
76
+ {
77
+ migrations_path: ENV.fetch('MIGRATION_PATH', DEFAULT_MIGRATION_PATH),
78
+ access_token: ENV['CONTENTFUL_MANAGEMENT_ACCESS_TOKEN'],
79
+ space_id: ENV['CONTENTFUL_SPACE_ID'],
80
+ migration_content_type_name: MigrationContentType::DEFAULT_MIGRATION_CONTENT_TYPE,
81
+ logger: Logger.new(STDOUT)
82
+ }.merge(args)
83
+ end
84
+
85
+ def validate_options
86
+ raise InvalidMigrationPath, migrations_path unless File.directory?(migrations_path)
87
+ end
88
+
89
+ def ran?(migration)
90
+ migrated.include?(migration.version.to_i)
91
+ end
92
+
93
+ def migrated
94
+ @migrated ||= load_migrated
95
+ end
96
+
97
+ def load_migrated
98
+ migration_content_type.entries.all.map { |m| m.version.to_i }
99
+ end
100
+
101
+ def migrations(paths)
102
+ paths = Array(paths)
103
+ migrations = migration_files(paths).map do |file|
104
+ version, name, scope = parse_migration_filename(file)
105
+ ContentfulMigrations::MigrationProxy.new(camelize(name), version.to_i, file, scope)
106
+ end
107
+
108
+ migrations.sort_by(&:version)
109
+ end
110
+
111
+ def migration_files(paths)
112
+ Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }]
113
+ end
114
+
115
+ def migration_content_type
116
+ @migration_content_type ||= MigrationContentType.new(
117
+ space: space, client: client, logger: logger
118
+ ).resolve
119
+ end
120
+
121
+ MIGRATION_FILENAME_REGEX = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/
122
+
123
+ def parse_migration_filename(filename)
124
+ File.basename(filename).scan(MIGRATION_FILENAME_REGEX).first
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,51 @@
1
+ module ContentfulMigrations
2
+ module Utils
3
+ # This method was taken from ActiveSupport::Inflector to avoid having
4
+ # a dependency on ActiveSupport in this project.
5
+ # http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-constantize
6
+ def camelize(term, uppercase_first_letter = true)
7
+ string = term.to_s
8
+ string = if uppercase_first_letter
9
+ string.sub(/^[a-z\d]*/, &:capitalize)
10
+ else
11
+ string.sub(/^((?=\b|[A-Z_])|\w)/, &:downcase)
12
+ end
13
+ string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{Regexp.last_match(1)}#{Regexp.last_match(2).capitalize}" }
14
+ string.gsub!('/'.freeze, '::'.freeze)
15
+ string
16
+ end
17
+ # This method was taken from ActiveSupport::Inflector to avoid having
18
+ # a dependency on ActiveSupport in this project.
19
+ # http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-constantize
20
+
21
+ def constantize(camel_cased_word)
22
+ names = camel_cased_word.split('::'.freeze)
23
+
24
+ # Trigger a built-in NameError exception including the ill-formed constant in the message.
25
+ Object.const_get(camel_cased_word) if names.empty?
26
+
27
+ # Remove the first blank element in case of '::ClassName' notation.
28
+ names.shift if names.size > 1 && names.first.empty?
29
+
30
+ names.inject(Object) do |constant, name|
31
+ if constant == Object
32
+ constant.const_get(name)
33
+ else
34
+ candidate = constant.const_get(name)
35
+ next candidate if constant.const_defined?(name, false)
36
+ next candidate unless Object.const_defined?(name)
37
+
38
+ # Go down the ancestors to check if it is owned directly. The check
39
+ # stops when we reach Object or the end of ancestors tree.
40
+ constant = constant.ancestors.each_with_object(constant) do |ancestor, const|
41
+ break const if ancestor == Object
42
+ break ancestor if ancestor.const_defined?(name, false)
43
+ end
44
+
45
+ # owner is in Object, so raise
46
+ constant.const_get(name, false)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,3 @@
1
+ module ContentfulMigrations
2
+ VERSION = '0.1.0'.freeze
3
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Create a contentful migration
3
+
4
+ Example:
5
+ rails generate contentful_migrations my_data
6
+
7
+ This will create:
8
+ db/contentful_migrations/xxxxx_my_data.rb
@@ -0,0 +1,31 @@
1
+ require 'rails/generators'
2
+
3
+ class ContentfulMigrationGenerator < Rails::Generators::NamedBase
4
+ # source_root File.expand_path("../templates", __FILE__)
5
+
6
+ def copy_initializer_file
7
+ name = file_name.to_s
8
+ migration_file = "db/contentful_migrations/#{next_migration_number}_#{name.underscore}.rb"
9
+ create_file migration_file, <<-FILE.strip_heredoc
10
+ class #{name.camelize} < ContentfulMigrations::Migration
11
+
12
+ def up
13
+ with_space do |space|
14
+ # TODO: use contentful-management.rb here
15
+ end
16
+ end
17
+
18
+ def down
19
+ with_space do |space|
20
+ # TODO: use contentful-management.rb here
21
+ end
22
+ end
23
+
24
+ end
25
+ FILE
26
+ end
27
+
28
+ def next_migration_number
29
+ Time.now.utc.strftime('%Y%m%d%H%M%S')
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ require 'contentful_migrations'
2
+ namespace :contentful_migrations do
3
+ desc 'Migrate the contentful space, runs all pending migrations'
4
+ task :migrate, [:contentful_space] do |_t, _args|
5
+ ContentfulMigrations::Migrator.migrate
6
+ end
7
+
8
+ desc 'Rollback previous contentful migration'
9
+ task :rollback, [:contentful_space] do |_t, _args|
10
+ ContentfulMigrations::Migrator.rollback
11
+ end
12
+
13
+ desc 'List any pending contentful migrations'
14
+ task :pending, [:contentful_space] do |_t, _args|
15
+ ContentfulMigrations::Migrator.pending
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: contentful-migrations
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kevin English
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-02-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: contentful-management
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.10.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.10.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.16'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.16'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 12.3.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 12.3.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.6'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: byebug
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 10.0.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 10.0.0
83
+ description: |-
84
+ Migration library system for Contentful API dependent on
85
+ contentful-management gem and plagarized from activerecord.
86
+ email:
87
+ - me@kenglish.co
88
+ executables: []
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - lib/contentful_migrations.rb
93
+ - lib/contentful_migrations/migration.rb
94
+ - lib/contentful_migrations/migration_content_type.rb
95
+ - lib/contentful_migrations/migration_proxy.rb
96
+ - lib/contentful_migrations/migrator.rb
97
+ - lib/contentful_migrations/utils.rb
98
+ - lib/contentful_migrations/version.rb
99
+ - lib/generators/contentful_migration/USAGE
100
+ - lib/generators/contentful_migration/contentful_migration_generator.rb
101
+ - lib/tasks/contentful_migrations.rake
102
+ homepage: https://github.com/monkseal/contentful-migrations.rb
103
+ licenses:
104
+ - MIT
105
+ metadata:
106
+ allowed_push_host: https://rubygems.org
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubyforge_project:
123
+ rubygems_version: 2.7.3
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: Contentful Migrations in Ruby
127
+ test_files: []