seed_migrator 0.4.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
+ SHA1:
3
+ metadata.gz: e48f4a0ebe69dcc507716f264265ecd3053667b5
4
+ data.tar.gz: b238670732ed6f978f8c34312e1cba4e1a46e1a6
5
+ SHA512:
6
+ metadata.gz: b0e613afc70bf2398c980e6451374efab38705e3a50af1ddb656dc3179d27e2840c153a11c54f67399dd99c69a500ba3fac42b159cc27daf205333d833db4e77
7
+ data.tar.gz: fa14f2abd15e7498f618ba773df1fbbef788eae2387a8b273ee6e8096e14452bbe8a2c554e9a56f596ec127afb19a532d247b25ac87905d45bf4b7d88d87769c
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2014 HornsAndHooves
2
+ Copyright (c) 2013 TMX Credit
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,146 @@
1
+ # SeedMigrator
2
+
3
+ [![Build Status](https://secure.travis-ci.org/HornsAndHooves/seed_migrator.png)](http://travis-ci.org/HornsAndHooves/seed_migrator)
4
+ [![Code Climate](https://codeclimate.com/github/HornsAndHooves/seed_migrator.png)](https://codeclimate.com/github/HornsAndHooves/seed_migrator)
5
+
6
+ The problem of seed data can get annoying once your Rails app is in production.
7
+ Ordinarily, you would place your seeds data in `seeds.rb`. Unfortunately, once
8
+ your application goes live, you will likely not be in a position to reload your
9
+ seeds. This leaves you with Rails migrations as the likely choice, but
10
+ that now makes development harder, because things seed out of order (migrations
11
+ run before seeds). This gem solves these problems.
12
+
13
+ ## Installation
14
+
15
+ In your Gemfile:
16
+
17
+ ```ruby
18
+ gem 'seed_migrator'
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ Data updates are defined similar to migrations. Each file contains a class
24
+ definition which should ideally extend `SeedMigrator::Updater` but doesn't have
25
+ to as long as it implements `apply_update` and `revert_update`. They need to
26
+ follow the default Rails naming convention; a file called
27
+ `update_order_types.rb` should contain the class `UpdateOrderTypes`. It is
28
+ highly recommended that each file have a prefix that defines its order. The
29
+ format is pretty flexible, but the prefix must start with a number and not have
30
+ any underscores. So `01_update_order_types.rb` is fine, so is
31
+ `1A5_update_order_types.rb`. In each of these cases, the name of the class is
32
+ still `UpdateOrderTypes`. If you extend `SeedMigrator::Updater` you only need to
33
+ override `revert_update` if you need your migration to be reversible, otherwise
34
+ it's not necessary.
35
+
36
+ Here's an example data update class definition:
37
+
38
+ ```ruby
39
+ class UpdateOrderTypes < SeedMigrators::Updater
40
+ def perform_update
41
+ OrderType.create :type_code => 'very_shiny'
42
+ end
43
+
44
+ # Overriden in case we need to roll back this migration.
45
+ def undo_update
46
+ OrderType.where(:type_code => 'very_shiny').first.delete
47
+ end
48
+ end
49
+ ```
50
+
51
+ ### Migrations
52
+
53
+ Assuming we have created the update file above in `db/data_updates` in a
54
+ file named `01_update_order_types.rb`
55
+
56
+ `root_updates_path` and optionally `should_run?(update_name)` must be defined
57
+ in every migration where we intend to do data updates. Realistically, our app
58
+ should extend `SeedMigrator` and then include the new module in each migration
59
+ where needed.
60
+
61
+ ```ruby
62
+ module CoreDataUpdate
63
+ include SeedMigrator
64
+
65
+ def root_updates_path
66
+ Rails.root.join('db','data_updates')
67
+ end
68
+
69
+ def should_run?(update_name)
70
+ Rails.env == 'production'
71
+ end
72
+ end
73
+ ```
74
+
75
+ Now, define a migration that will perform our data update.
76
+
77
+ ```ruby
78
+ class CreateVeryShinyOrderType < ActiveRecord::Migration
79
+ include CoreDataUpdate
80
+
81
+ def up
82
+ apply_update :01_update_order_types
83
+ end
84
+
85
+ def down
86
+ revert_update :01_update_order_types
87
+ end
88
+ end
89
+ ```
90
+
91
+ Note that the update prefix is optional. The following will also work.
92
+
93
+ ```ruby
94
+ class CreateVeryShinyOrderType < ActiveRecord::Migration
95
+ include CoreDataUpdate
96
+
97
+ def up
98
+ apply_update :update_order_types
99
+ end
100
+
101
+ def down
102
+ revert_update :update_order_types
103
+ end
104
+ end
105
+ ```
106
+
107
+ Old style migrations, i.e. `def self.up` are not supported.
108
+
109
+ ### Seeds
110
+
111
+ At the bottom of your `seeds.rb`, include the following:
112
+
113
+ ```ruby
114
+ include SeedMigrator::Seeds
115
+ apply_updates Rails.root.join('db','data_updates')
116
+ ```
117
+
118
+ ### Generators
119
+
120
+ Two generators are added for your convenience:
121
+
122
+ * install: Updates the seeds file as specified above and creates a custom
123
+ data update module for engines. See
124
+ {file:lib/generators/seed_migrator/create/USAGE} for examples
125
+ * create: Creates new data\_update and migration files as specified above. See
126
+ {file:lib/generators/seed_migrator/install/USAGE} for examples
127
+
128
+ NOTE: If you're using this from a Rails engine with Rails 3.2 and you're using
129
+ RSpec to test, invoking the generator will likely put the generated files in
130
+ `spec/dummy/db` instead of `db`. Just something to be aware of.
131
+
132
+ ## Testing
133
+
134
+ Install all the gem dependencies.
135
+
136
+ bundle install
137
+
138
+ Run tests
139
+
140
+ rake spec
141
+
142
+ ## License
143
+
144
+ Copyright (c) 2014 HornsAndHooves.
145
+ Copyright (c) 2013 TMX Credit.
146
+ Released under the MIT License. See LICENSE file for details.
@@ -0,0 +1,9 @@
1
+ Description:
2
+ Creates files necessary for a data update
3
+
4
+ Example:
5
+ rails generate seed_migrator:create foo
6
+
7
+ This will:
8
+ * Create db/migrate/<timestamp>_foo.rb
9
+ * Create db/data_updates/<timestamp>_foo_data_update.rb
@@ -0,0 +1,17 @@
1
+ require 'rails/generators/active_record'
2
+ require 'generators/seed_migrator/helper'
3
+
4
+ # Generator to create a data update + associated migration
5
+ class SeedMigrator::CreateGenerator < Rails::Generators::NamedBase
6
+ include Generators::SeedMigrator::Helper
7
+ include Rails::Generators::Migration
8
+ extend ActiveRecord::Generators::Migration
9
+
10
+ source_root File.expand_path('../templates', __FILE__)
11
+
12
+ # Creates the data update file and the migration file.
13
+ def create_helper_file
14
+ migration_template "data_update.rb", "db/data_updates/#{file_name}_data_update.rb"
15
+ migration_template "data_update_migration.rb", "db/migrate/#{file_name}.rb"
16
+ end
17
+ end
@@ -0,0 +1,10 @@
1
+ class <%= data_update_class_name %> < SeedMigrator::Updater
2
+ def perform_update
3
+ # <%= class_name %>.create :type_code => 'very_shiny'
4
+ end
5
+
6
+ # Overridden in case we need to roll back this migration.
7
+ def undo_update
8
+ # <%= class_name %>.where(:type_code => 'very_shiny').first.delete
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ include <%= application_class_name %>DataUpdate
3
+
4
+ def up
5
+ apply_update '<%= data_update_file_name %>'
6
+ end
7
+
8
+ def down
9
+ revert_update '<%= data_update_file_name %>'
10
+ end
11
+ end
@@ -0,0 +1,48 @@
1
+ module Generators # :nodoc:
2
+ module SeedMigrator # :nodoc:
3
+ # Helper methods for generators
4
+ module Helper
5
+ # Is this a Rails Application or Engine?
6
+ # @return [Boolean]
7
+ def application?
8
+ Rails.application.is_a?(Rails::Application)
9
+ end
10
+
11
+ # Name of the application or engine useful for files and directories
12
+ # @return [String]
13
+ def application_name
14
+ if defined?(Rails) && Rails.application
15
+ application_class_name.underscore
16
+ else
17
+ "application"
18
+ end
19
+ end
20
+
21
+ # Fully qualified name of the application or engine
22
+ # @example AppName::Engine or AppName::Application
23
+ # @return [String]
24
+ def full_application_class_name
25
+ Rails.application.class.name
26
+ end
27
+
28
+ # Regular name of the application or engine
29
+ # @example AppName
30
+ # @return [String]
31
+ def application_class_name
32
+ full_application_class_name.split('::').first
33
+ end
34
+
35
+ # Class name for use in data_update files
36
+ # @return [String]
37
+ def data_update_class_name
38
+ migration_class_name
39
+ end
40
+
41
+ # Name useful for the data update file
42
+ # @return [String]
43
+ def data_update_file_name
44
+ "#{migration_number}_#{file_name}_data_update"
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,11 @@
1
+ Description:
2
+ Creates the necessary files to integrate with SeedMigrator
3
+
4
+ Example:
5
+ rails generate seed_migrator:install
6
+
7
+ This will:
8
+ * Update your db/seeds.rb to run the data update
9
+ * Add an initializer:
10
+ (Application) config/initializers/<application_name>_data_update.rb
11
+ (Engine) lib/<application_name>/<application_name>_data_update.rb
@@ -0,0 +1,33 @@
1
+ require 'generators/seed_migrator/helper'
2
+
3
+ # Generator to install tmx data update in a new rails system.
4
+ class SeedMigrator::InstallGenerator < Rails::Generators::Base
5
+ include Generators::SeedMigrator::Helper
6
+
7
+ source_root File.expand_path('../templates', __FILE__)
8
+
9
+ # Create the initializer file with default options.
10
+ def create_initializer
11
+ log :initializer, "Adding custom data update module"
12
+
13
+ if application?
14
+ template "data_update_module.rb", "config/initializers/#{application_name}_data_update.rb"
15
+ else
16
+ template "data_update_module.rb", "lib/#{application_name}/#{application_name}_data_update.rb"
17
+ end
18
+ end
19
+
20
+ # Update seeds.rb
21
+ def update_seeds
22
+ log :initializer, "Adding data update seeder to seeds.rb"
23
+
24
+ seed_code =<<SEED
25
+ include SeedMigrator::Seeds
26
+ apply_updates #{full_application_class_name}.root.join('db', 'data_updates')
27
+ SEED
28
+
29
+ in_root do
30
+ inject_into_file 'db/seeds.rb', "\n#{seed_code}\n", { :before => /\z/, :verbose => false }
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+ # Facilitates conducting data migrations.
2
+ module <%= application_class_name %>DataUpdate
3
+ include SeedMigrator
4
+
5
+ # Returns the path where data updates are
6
+ def root_updates_path
7
+ <%= full_application_class_name %>.root.join('db','data_updates')
8
+ end
9
+
10
+ # Updates will run in production, but not in test an dev.
11
+ def should_run?(_)
12
+ Rails.env.production?
13
+ end
14
+
15
+ end
@@ -0,0 +1,48 @@
1
+ module SeedMigrator
2
+
3
+ # Should be included in seeds.rb to enable running data updates.
4
+ module Seeds
5
+ include UpdateClassLoader
6
+
7
+ # Applies all the updates from the given file system path.
8
+ #
9
+ # Basically, requires all the files from the given directory with a .rb
10
+ # suffix, instantiates the class in the file, and calls 'perform_update'
11
+ # on it. This naturally requires that classes obey the default naming
12
+ # convention, i.e. a file named sample_update.rb should define a class
13
+ # named SampleUpdate.
14
+ #
15
+ # @param [String|Pathname|Symbol] updates_path
16
+ # @return [Hash] A hash or results; update => result
17
+ def apply_updates(updates_path)
18
+ update_files = get_update_files(updates_path)
19
+ results = {}
20
+ update_files.each { |file|
21
+ update = file.split('.').first
22
+ unless update.blank?
23
+ update_class = get_update_class(updates_path, update)
24
+ res = update_class.new.perform_update
25
+ results[update] = res
26
+ end
27
+ }
28
+ results
29
+ end
30
+
31
+ # Gets the list of what should be the update files from the given file
32
+ # system path, sorted in alphabetical order. Returns an empty array if the
33
+ # updates_path is not on the file system or is not a directory.
34
+ # @param [String] updates_path
35
+ # @return [Array]
36
+ def get_update_files(updates_path)
37
+ if File.exists?(updates_path) && File.directory?(updates_path)
38
+ Dir.entries(updates_path).select { |file|
39
+ file.ends_with? '.rb'
40
+ }.sort
41
+ else
42
+ []
43
+ end
44
+ end
45
+ private :get_update_files
46
+
47
+ end
48
+ end
@@ -0,0 +1,57 @@
1
+ module SeedMigrator
2
+ # Machinery to load the actual data update classes
3
+ module UpdateClassLoader
4
+
5
+ # Loads the class corresponding to the given update name from the given
6
+ # file system path.
7
+ #
8
+ # @param [String|Pathname|Symbol] root_path
9
+ # @param [String|Symbol] update_name
10
+ #
11
+ # @return [::Class]
12
+ def get_update_class(root_path, update_name)
13
+ update = strip_seq_prefix(update_name.to_s)
14
+ file_name = find_file(root_path, update)
15
+ if file_name
16
+ file_name = File.basename(file_name, '.rb')
17
+ if root_path.to_s.ends_with?('/')
18
+ require "#{root_path}#{file_name}"
19
+ else
20
+ require "#{root_path}/#{file_name}"
21
+ end
22
+ update.camelize.constantize
23
+ else
24
+ raise LoadError, "Unable to find file for update #{update_name} in #{root_path}"
25
+ end
26
+ end
27
+
28
+ # Determines what's the actual filename for the given update name
29
+ def find_file(root_path, update)
30
+ update_file = "#{update}.rb"
31
+ Dir.entries(root_path).find{|entry| strip_seq_prefix(entry) == update_file }
32
+ end
33
+ private :find_file
34
+
35
+ # Takes the given file name and strips out the sequence prefix if any. If
36
+ # the file name starts with a digit, all characters up to the first '_' are
37
+ # considered part of the prefix. For example, for '00234ab_foo_bar' the
38
+ # prefix would be '00234ab' and 'foo_bar' would be returned.
39
+ #
40
+ # @param [String] update_name
41
+ #
42
+ # @return [String]
43
+ def strip_seq_prefix(update_name)
44
+ index = update_name =~ /_/
45
+ if index
46
+ start = update_name[0,1]
47
+ # If the first letter of the prefix is a digit, we assume the prefix is
48
+ # for sequence ordering purposes.
49
+ if %w(0 1 2 3 4 5 6 7 8 9).include?(start)
50
+ update_name = update_name[index + 1, update_name.size - index]
51
+ end
52
+ end
53
+ update_name
54
+ end
55
+ private :strip_seq_prefix
56
+ end
57
+ end
@@ -0,0 +1,34 @@
1
+ require 'active_record'
2
+
3
+
4
+ module SeedMigrator
5
+
6
+ # Adds support for methods which are part of ActiveRecord::Migration interface.
7
+ # This is a convenience feature to make using data updates easier.
8
+ # The module should only implement methods relevant in the context of data
9
+ # updates.
10
+ module ActiveRecordMigrationCompatible
11
+ # @see ActiveRecord::ConnectionAdapters::DatabaseStatements#execute
12
+ def execute(*args, &block)
13
+ ::ActiveRecord::Base.connection.execute(*args, &block)
14
+ end
15
+ end
16
+
17
+ # The base class for all post-release data updates
18
+ #
19
+ # @abstract
20
+ class Updater
21
+ include ::SeedMigrator::ActiveRecordMigrationCompatible
22
+
23
+ # Performs the data update. The subclass must override this method.
24
+ def perform_update
25
+ raise "#{self.class} should override 'update_data'"
26
+ end
27
+
28
+ # Reverses the update. If the update is reversible, this class should
29
+ # be overriden in the subclass.
30
+ def undo_update
31
+ raise "#{self.class} does not support rolling back the update"
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,58 @@
1
+ require 'active_support/all' # Should be required first.
2
+ require 'seed_migrator/update_class_loader' #Should be required second.
3
+
4
+ require 'seed_migrator/updater'
5
+ require 'seed_migrator/seeds'
6
+
7
+ # Extends the migrations DSL to include the functionality to execute data updates.
8
+ #
9
+ # Note that each data update class is instantiated regardless of whether it is
10
+ # expected to run. This enables the developer to discover any data update that
11
+ # is incorrectly referenced in a migration, prior to deployment to production.
12
+ module SeedMigrator
13
+ include SeedMigrator::UpdateClassLoader
14
+
15
+ # Returns the root data updates path.
16
+ def root_updates_path
17
+ raise "Must override in subclass!"
18
+ end
19
+
20
+ # Return +true+ if the named update should run, +false+ otherwise. Subclasses
21
+ # are expected to override this as needed.
22
+ def should_run?(update_name)
23
+ true
24
+ end
25
+
26
+ # Applies the given update.
27
+ # @param [String|Symbol] update_name
28
+ def apply_update(update_name)
29
+ perform(update_name, :perform_update)
30
+ end
31
+
32
+ # Reverts the given update.
33
+ # @param [String|Symbol] update_name
34
+ def revert_update(update_name)
35
+ perform(update_name, :undo_update)
36
+ end
37
+
38
+ # Perform the update action.
39
+ # @param [String] update_name
40
+ # @param [Symbol] action Update action.
41
+ # Either :perform_update or :undo_update
42
+ def perform(update_name, action)
43
+ update = load_updater_class(update_name)
44
+
45
+ update.send(action) if should_run?(update_name)
46
+ end
47
+ private :perform
48
+
49
+ # Load the updater class and instantiate it. This enables the developer
50
+ # to discover when the data update has been incorrectly referenced in the
51
+ # migration, prior to deployment to production.
52
+ # @param [String] update_name
53
+ def load_updater_class(update_name)
54
+ get_update_class(root_updates_path, update_name).new
55
+ end
56
+ private :load_updater_class
57
+
58
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: seed_migrator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ platform: ruby
6
+ authors:
7
+ - Arthur Shagall
8
+ - Zach Belzer
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-09-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '4.0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '4.0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: activerecord
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '4.0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '4.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: i18n
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">"
47
+ - !ruby/object:Gem::Version
48
+ version: '0.1'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">"
54
+ - !ruby/object:Gem::Version
55
+ version: '0.1'
56
+ - !ruby/object:Gem::Dependency
57
+ name: jeweler
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: yard
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: rspec
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ description: Provides a clean way to handle updates to seed data post-launch.
99
+ email:
100
+ - arthur.shagall@gmail.com
101
+ - zbelzer@gmail.com
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files:
105
+ - LICENSE
106
+ - README.md
107
+ files:
108
+ - LICENSE
109
+ - README.md
110
+ - lib/generators/seed_migrator/create/USAGE
111
+ - lib/generators/seed_migrator/create/create_generator.rb
112
+ - lib/generators/seed_migrator/create/templates/data_update.rb
113
+ - lib/generators/seed_migrator/create/templates/data_update_migration.rb
114
+ - lib/generators/seed_migrator/helper.rb
115
+ - lib/generators/seed_migrator/install/USAGE
116
+ - lib/generators/seed_migrator/install/install_generator.rb
117
+ - lib/generators/seed_migrator/install/templates/data_update_module.rb
118
+ - lib/seed_migrator.rb
119
+ - lib/seed_migrator/seeds.rb
120
+ - lib/seed_migrator/update_class_loader.rb
121
+ - lib/seed_migrator/updater.rb
122
+ homepage: https://github.com/HornsAndHooves
123
+ licenses: []
124
+ metadata: {}
125
+ post_install_message:
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubyforge_project:
141
+ rubygems_version: 2.2.2
142
+ signing_key:
143
+ specification_version: 4
144
+ summary: Handle post-release data updates through migrations
145
+ test_files: []