contentful-migrations 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []