dumped_railers 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.rspec +3 -0
- data/.travis.yml +12 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +165 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/dumped_railers.gemspec +37 -0
- data/gemfiles/Gemfile.rails_5.2.4 +6 -0
- data/gemfiles/Gemfile.rails_6.0.3.4 +6 -0
- data/gemfiles/Gemfile.rails_6.1.0 +6 -0
- data/lib/dumped_railers.rb +48 -0
- data/lib/dumped_railers/dump.rb +23 -0
- data/lib/dumped_railers/file_helper.rb +41 -0
- data/lib/dumped_railers/fixture_builder/model.rb +34 -0
- data/lib/dumped_railers/fixture_builder/record.rb +55 -0
- data/lib/dumped_railers/import.rb +30 -0
- data/lib/dumped_railers/preprocessor/strip_ignorables.rb +13 -0
- data/lib/dumped_railers/record_builder/dependency_tracker.rb +82 -0
- data/lib/dumped_railers/record_builder/fixture_row.rb +114 -0
- data/lib/dumped_railers/record_builder/fixture_set.rb +46 -0
- data/lib/dumped_railers/record_builder/fixture_table.rb +74 -0
- data/lib/dumped_railers/version.rb +3 -0
- metadata +201 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e71ec228651de3ad8d735b37d3d091b1ac453df4e021a0545476a456eff5f7cb
|
4
|
+
data.tar.gz: da03643cbd834f41f59878ea1d7bf1411f2b739fc26a87e660e7b1f8d30c9fcd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f53cd73227a90f14269c0dad160e3105bb3307c28274f45b7e2a96b44a22c7e9765cca115e23cc3a62dd0678afd17b0e4135f2d9c5035eb03a93a9e0e34b8fbb
|
7
|
+
data.tar.gz: f0d1cfad86c548302527028ccfa20d5ef10a16d24206ece6bdd8318f14768fa23aa7df06f68115ec27869fda3fa1297f3855e407d6b8cb67d3b4fd523bcb439a
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Change Log
|
2
|
+
|
3
|
+
## [0.1.0]
|
4
|
+
### Added
|
5
|
+
- Implement method that dumps specified model data in fixture (YAML) format
|
6
|
+
- Implement import method that transfer fixture into database
|
7
|
+
- Add test helpers to make it easy to implement database related tests
|
8
|
+
- Add config options to be able to ignore specific columns
|
9
|
+
- Accept preprocessors to make filtering behavior pluggable and customizable
|
10
|
+
- Add Readme
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2020 Koji Onishi
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
# DumpedRailers <img src='https://user-images.githubusercontent.com/23026542/101830310-aaf10000-3b77-11eb-9e0a-d14e45b27760.png' width=40>
|
2
|
+
[![Build Status](https://travis-ci.com/fursich/dumped_railers.svg?branch=main)](https://travis-ci.com/fursich/dumped_railers) [![Gem Version](https://badge.fury.io/rb/dumped_railers.svg)](https://badge.fury.io/rb/dumped_railers)
|
3
|
+
|
4
|
+
Helping you take a snapshot of ActiveRecord models in Rails-compatible fixture format, and re-importing them wherever necessary without destroying current data you have.
|
5
|
+
|
6
|
+
With Rails, you can import any fixture data using `rails db:fixtures:load` - however, this let's you remove all the existing data in your database before importing the fixtures. This is good for clean seeding, typically when running your tests, but there are other senarios where you want to merely ADD data, without damaging the current data you are woking with.
|
7
|
+
|
8
|
+
This is a bit trickey puzzle, though. To add the imported records, you cannot dump and re-import the primary key, as they are already taken by the original records. But usually your records involve reference to associated records, where the associations are guaranteed by the very primary keys.
|
9
|
+
In other words, the fixures have to be stored and re-imported, so as to maintain their original inter-dependencies, but their primary keys have to be re-assigned (headache)
|
10
|
+
|
11
|
+
DumpedRailers can add (not replace) the fixture without removing the current records, while restoring **all associations** among the original records.
|
12
|
+
Additionally, it can ignore, mask, or tweak any attributes when dumping the records into fixture files, which is convenient to export sensitive data (typically in your production environment) into, for instance, your staging environment.
|
13
|
+
|
14
|
+
This feature can particularily help you in the following senarios:
|
15
|
+
- you want to copy a group of records without breaking the other data sets you are working on
|
16
|
+
- you want to transfer some production data into dev environment, to reproduce some errors you encontered.
|
17
|
+
- you work for a multi-tenancy service, where you want to duplicate interdependent set of records from tenant A to tenant B.
|
18
|
+
- you simply want to populate interdependent record sets, which is not easy to re-build with FactoryBot
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
Add this line to your application's Gemfile:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
gem 'dumped_railers'
|
26
|
+
```
|
27
|
+
|
28
|
+
And then execute:
|
29
|
+
|
30
|
+
$ bundle install
|
31
|
+
|
32
|
+
Or install it yourself as:
|
33
|
+
|
34
|
+
$ gem install dumped_railers
|
35
|
+
|
36
|
+
## Usage
|
37
|
+
|
38
|
+
### Getting Started
|
39
|
+
|
40
|
+
* if you want to dump (let's say) User, Item, and Tag models, just run the following.
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
DumpedRailers.dump!(User, Item, Tag, base_dir: 'tmp/fixtures/')
|
44
|
+
```
|
45
|
+
this will generate three fixture files under tmp/fixtures/ folder.
|
46
|
+
|
47
|
+
* if you want to import the records you just saved, run:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
DumpedRailers.import!('tmp/fixtures')
|
51
|
+
```
|
52
|
+
|
53
|
+
* you can also specify individual model(s) for selective import.
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
DumpedRailers.import!('tmp/fixtures/users.yml', 'tmp/fixtures/items.yml')
|
57
|
+
```
|
58
|
+
|
59
|
+
NOTE: you at least have to provide all the dependent records, so that DumpedRailers can resolve dependencies among the fixtures provided.
|
60
|
+
|
61
|
+
### Ignored Columns
|
62
|
+
|
63
|
+
* By default, DumpedRailers ignore three columns - `id`, `created_at`, `updated_at`. You can always update/change this settings as follows.
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
DumpedRailers.configure do |config|
|
67
|
+
config.ignorable_columns += [:published_on] # published_on will be ignored on top of default settings.
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
* of course you can totally replace the settings with your own.
|
72
|
+
```ruby
|
73
|
+
DumpedRailers.configure do |config|
|
74
|
+
config.ignorable_columns = %i[uuid created_on updated_on] # uuid and created_on will be ignored instead of id, created_at, updated_at
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
### Masking, filtering
|
79
|
+
|
80
|
+
* you can pass `preprocessors` to DumpedRailers before it starts dump. All the attributes are filtered through preprocessors in order of registration.
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
DumpedRailers.dump!(User, Item, base_dir: 'tmp/', preprocessors: [MaskingPreprocessor.new])
|
84
|
+
```
|
85
|
+
|
86
|
+
* "Preprocessors" can be lambda, or module, or any objects that can repond to #call(atrs, model).
|
87
|
+
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
class MaskingPreprocessor
|
91
|
+
def call(attrs, model)
|
92
|
+
attrs.map { |col, value|
|
93
|
+
col.match?(/password/) ? [col, '<MASKED>'] : [col, value]
|
94
|
+
}.to_h
|
95
|
+
end
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
* a lambda object can be accepted as well
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
masking_preprocessor = -> (attrs, model) { attrs.transform_values(&:upcase) }
|
103
|
+
```
|
104
|
+
|
105
|
+
NOTE: The proprocessors must return attributes in the same format `{ attributes_name: value }` so that preprocessors and dump handlers can preprocessors in nested manner.
|
106
|
+
|
107
|
+
### pseudo multi-tenancy (such as ActsAsTenant)
|
108
|
+
|
109
|
+
* Such library builds multi-tenancy environment on one single database, using default_scope to switch over database access rights between tenants. You can incorporate data from Tenant A to Tenant B as follows. let's say we use [ActsAsTenant](https://github.com/ErwinM/acts_as_tenant)
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
# make sure to delete old fixtures in the folder
|
113
|
+
File.delete(Dir.glob('tmp/fixtures/{**,*}/*.yml'))
|
114
|
+
|
115
|
+
# let DumpedRailers ignore tenant column, as it will be overwritten by ActsAsTenant
|
116
|
+
DumpedRailers.configure do |config|
|
117
|
+
config.ignorable_columns += [:account_id]
|
118
|
+
end
|
119
|
+
|
120
|
+
# dump from tenant_a
|
121
|
+
ActsAsTenant.with_tenant(tenant_a) do
|
122
|
+
DumpedRailers.dump!(Item, Tag, base_dir: 'tmp/fixtures/')
|
123
|
+
end
|
124
|
+
|
125
|
+
# import into tenant_b
|
126
|
+
ActsAsTenant.with_tenant(tenant_b) do
|
127
|
+
DumpedRailers.import!('tmp/fixtures')
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
## Troubleshooting
|
132
|
+
|
133
|
+
When DumpedRailers fail to resolve dependencies, please check the following.
|
134
|
+
|
135
|
+
* DumpedRailers uses ActiveRecord reflection methods to sort out model dependencies. You might have to check wheather your relation is defined on the model (especially, `belongs_to` cannot be omitted).
|
136
|
+
|
137
|
+
* Dependencies cannot be resolved when cyclic dependencies are detected. For instance, the case like below cannot be resolved.
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
class Chicken < ActiveRecord::Base
|
141
|
+
belongs_to :egg, optional: true
|
142
|
+
end
|
143
|
+
|
144
|
+
class Egg < ActiveRecord::Base
|
145
|
+
belongs_to :chicken, optional: true
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
* When exception raised, checking your log might give you a good hint (desperately staring at the backtrace won't give much information)
|
150
|
+
consider displaying `tail -f logs/development.log` while executing your script.
|
151
|
+
|
152
|
+
## Development
|
153
|
+
|
154
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
155
|
+
|
156
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
157
|
+
|
158
|
+
## Contributing
|
159
|
+
|
160
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[fursich]/dumped_railers.
|
161
|
+
|
162
|
+
|
163
|
+
## License
|
164
|
+
|
165
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "dumped_railers"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative 'lib/dumped_railers/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = 'dumped_railers'
|
5
|
+
spec.version = DumpedRailers::VERSION
|
6
|
+
spec.authors = ['Koji Onishi']
|
7
|
+
spec.email = ['fursich0@gmail.com']
|
8
|
+
|
9
|
+
spec.summary = %q{A flexible fixture importer/exporter, that can transport ActiveRecord data in fixture format}
|
10
|
+
spec.description = %q{DumpedRailers helps you take a snapshot of ActiveRecord models in Rails-compatible fixture format, and re-import them wherever necessary without destroying current data you have.}
|
11
|
+
spec.homepage = 'https://github.com/fursich/dumped_railers'
|
12
|
+
spec.license = 'MIT'
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
|
14
|
+
|
15
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
16
|
+
spec.metadata['source_code_uri'] = 'https://github.com/fursich/dumped_railers'
|
17
|
+
spec.metadata['changelog_uri'] = 'https://github.com/fursich/dumped_railers/CHANGELOG.md'
|
18
|
+
|
19
|
+
# Specify which files should be added to the gem when it is released.
|
20
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
21
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
22
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
23
|
+
end
|
24
|
+
spec.bindir = 'exe'
|
25
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
|
+
spec.require_paths = ['lib']
|
27
|
+
|
28
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
29
|
+
spec.add_development_dependency 'rake', '~> 12.3.3'
|
30
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
31
|
+
spec.add_development_dependency 'sqlite3'
|
32
|
+
spec.add_development_dependency 'activerecord', '~> 5.2'
|
33
|
+
spec.add_development_dependency 'database_cleaner-active_record', '~> 1.8'
|
34
|
+
spec.add_development_dependency 'pry'
|
35
|
+
spec.add_development_dependency 'pry-byebug'
|
36
|
+
spec.add_development_dependency 'pry-doc'
|
37
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dumped_railers/version'
|
4
|
+
require 'dumped_railers/file_helper.rb'
|
5
|
+
require 'dumped_railers/dump'
|
6
|
+
require 'dumped_railers/import'
|
7
|
+
|
8
|
+
module DumpedRailers
|
9
|
+
class << self
|
10
|
+
|
11
|
+
def dump!(*models, base_dir: './', preprocessors: nil)
|
12
|
+
preprocessors = [Preprocessor::StripIgnorables.new, *preprocessors].compact.uniq
|
13
|
+
|
14
|
+
fixture_handler = Dump.new(*models, preprocessors: preprocessors)
|
15
|
+
fixture_handler.build_fixtures!
|
16
|
+
fixture_handler.persist_all!(base_dir)
|
17
|
+
end
|
18
|
+
|
19
|
+
def import!(*paths)
|
20
|
+
# make sure class-baseed caches starts with clean state
|
21
|
+
DumpedRailers::RecordBuilder::FixtureRow::RecordStore.clear!
|
22
|
+
DumpedRailers::RecordBuilder::DependencyTracker.clear!
|
23
|
+
|
24
|
+
fixture_handler = Import.new(*paths)
|
25
|
+
fixture_handler.import_all!
|
26
|
+
end
|
27
|
+
|
28
|
+
class Configuration < ::OpenStruct; end
|
29
|
+
|
30
|
+
def config
|
31
|
+
@_config ||= Configuration.new
|
32
|
+
end
|
33
|
+
|
34
|
+
def configure
|
35
|
+
yield config
|
36
|
+
end
|
37
|
+
|
38
|
+
# FIXME: make it minimum
|
39
|
+
IGNORABLE_COLUMNS = %w[id created_at updated_at]
|
40
|
+
def configure_defaults!
|
41
|
+
configure do |config|
|
42
|
+
config.ignorable_columns = IGNORABLE_COLUMNS
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
configure_defaults!
|
48
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'fixture_builder/model'
|
4
|
+
require_relative 'preprocessor/strip_ignorables'
|
5
|
+
|
6
|
+
module DumpedRailers
|
7
|
+
class Dump
|
8
|
+
def initialize(*models, preprocessors: [])
|
9
|
+
@fixture_tables = models.map { |model|
|
10
|
+
FixtureBuilder::Model.new(model, preprocessors: preprocessors)
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def build_fixtures!
|
15
|
+
@fixtures = @fixture_tables.map(&:build!).to_h
|
16
|
+
end
|
17
|
+
|
18
|
+
def persist_all!(base_dir)
|
19
|
+
FileUtils.mkdir_p(base_dir)
|
20
|
+
FileHelper.write(*@fixtures, base_dir: base_dir)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
|
5
|
+
module DumpedRailers
|
6
|
+
module FileHelper
|
7
|
+
class << self
|
8
|
+
def read_fixtures(*paths)
|
9
|
+
yaml_files = paths.flat_map { |path|
|
10
|
+
if File.file?(path)
|
11
|
+
path
|
12
|
+
else
|
13
|
+
[*Dir["#{path}/{**,*}/*.yml"], "#{path}.yml"].select { |f|
|
14
|
+
::File.file?(f)
|
15
|
+
}
|
16
|
+
end
|
17
|
+
}.uniq.compact
|
18
|
+
|
19
|
+
yaml_files.map { |file|
|
20
|
+
raw_data = ::File.read(file)
|
21
|
+
YAML.load(raw_data)
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def write(*fixtures, base_dir:)
|
26
|
+
fixtures.each do |table_name, fixture|
|
27
|
+
pathname =
|
28
|
+
if defined?(Rails)
|
29
|
+
Rails.root.join("#{base_dir}/#{table_name}.yml")
|
30
|
+
else
|
31
|
+
Pathname.new("#{base_dir}/#{table_name}.yml")
|
32
|
+
end
|
33
|
+
|
34
|
+
pathname.open('w') do |f|
|
35
|
+
f.write fixture.to_yaml
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'record'
|
4
|
+
|
5
|
+
module DumpedRailers
|
6
|
+
module FixtureBuilder
|
7
|
+
class Model
|
8
|
+
def initialize(model, preprocessors:)
|
9
|
+
@model = model
|
10
|
+
@fixture_records = model.order(:id).map { |record|
|
11
|
+
Record.new(record, model, preprocessors: preprocessors)
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def build!
|
16
|
+
fixture_body = @fixture_records.map(&:build!).to_h
|
17
|
+
fixture = fixture_body.reverse_merge build_fixture_header_for(@model)
|
18
|
+
|
19
|
+
[@model.table_name, fixture]
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def build_fixture_header_for(model)
|
25
|
+
{ '_fixture' =>
|
26
|
+
{
|
27
|
+
'model_class' => model.name,
|
28
|
+
'fixture_generated_by' => 'DumpedRailers',
|
29
|
+
}
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DumpedRailers
|
4
|
+
module FixtureBuilder
|
5
|
+
class Record
|
6
|
+
def initialize(record, model, preprocessors:)
|
7
|
+
@record = record
|
8
|
+
@model = model
|
9
|
+
@preprocessors = preprocessors
|
10
|
+
end
|
11
|
+
|
12
|
+
def build!
|
13
|
+
id = @record.id
|
14
|
+
attributes =
|
15
|
+
@preprocessors.inject(@record.attributes) { |attrs, preprocessor|
|
16
|
+
preprocessor.call(attrs, @model)
|
17
|
+
}
|
18
|
+
|
19
|
+
# convert "belong_to association" foreign keys into record-unique labels
|
20
|
+
@model.reflect_on_all_associations.select(&:belongs_to?).each do |rel|
|
21
|
+
# skip ignorables
|
22
|
+
next unless attributes.has_key? rel.foreign_key.to_s
|
23
|
+
|
24
|
+
if rel.polymorphic?
|
25
|
+
model_name = attributes[rel.foreign_type.to_s]
|
26
|
+
|
27
|
+
attributes[rel.name.to_s] = record_label_for(
|
28
|
+
model_name,
|
29
|
+
attributes.delete(rel.foreign_key.to_s),
|
30
|
+
attributes.delete(rel.foreign_type.to_s)
|
31
|
+
)
|
32
|
+
else
|
33
|
+
attributes[rel.name.to_s] = record_label_for(
|
34
|
+
rel.name,
|
35
|
+
attributes.delete(rel.foreign_key.to_s)
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
[record_label_for(@model.name, id), attributes]
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def record_label_for(model_name, id, type=nil)
|
46
|
+
return nil unless id
|
47
|
+
|
48
|
+
identifier = "#{model_name.to_s.underscore}_#{id}"
|
49
|
+
type_specifier = "(#{type})" if type
|
50
|
+
|
51
|
+
"__#{identifier}#{type_specifier}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'record_builder/fixture_set'
|
4
|
+
|
5
|
+
module DumpedRailers
|
6
|
+
class Import
|
7
|
+
attr_reader :fixture_set
|
8
|
+
|
9
|
+
def initialize(*paths)
|
10
|
+
@raw_fixtures = FileHelper.read_fixtures(*paths)
|
11
|
+
@fixture_set = RecordBuilder::FixtureSet.new(@raw_fixtures)
|
12
|
+
end
|
13
|
+
|
14
|
+
def import_all!
|
15
|
+
fixture_set.sort_by_table_dependencies!
|
16
|
+
@record_sets = fixture_set.build_record_sets!
|
17
|
+
|
18
|
+
ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
|
19
|
+
# models have to be persisted one-by-one so that dependent models are able to
|
20
|
+
# resolve "belongs_to" (parent) association
|
21
|
+
@record_sets.each do |_model, records|
|
22
|
+
# FIXME: faster implementation wanted, parhaps with activerocord-import
|
23
|
+
# (objects needs to be reloaded somehow when using buik insert)
|
24
|
+
records.each(&:save!)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DumpedRailers
|
4
|
+
module Preprocessor
|
5
|
+
class StripIgnorables
|
6
|
+
def call(attributes, _model)
|
7
|
+
attributes.reject { |column_name, _v|
|
8
|
+
DumpedRailers.config.ignorable_columns.map(&:to_s).include?(column_name)
|
9
|
+
}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DumpedRailers
|
4
|
+
module RecordBuilder
|
5
|
+
class DependencyTracker
|
6
|
+
class << self
|
7
|
+
def for(model)
|
8
|
+
trackers[model] ||= new
|
9
|
+
end
|
10
|
+
|
11
|
+
def clear!
|
12
|
+
@trackers = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def trackers
|
18
|
+
@trackers ||= {}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def on(record)
|
23
|
+
dependencies[record] ||= RecordDependency.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def list_all_record_labels_with(attr)
|
27
|
+
list_all_dependencies_with(attr)
|
28
|
+
.map { |dependent| dependent.record_label }
|
29
|
+
.compact
|
30
|
+
end
|
31
|
+
|
32
|
+
def list_all_model_names_with(attr)
|
33
|
+
list_all_dependencies_with(attr)
|
34
|
+
.map { |dependent| dependent.model_name }
|
35
|
+
.compact
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def dependencies
|
41
|
+
@dependencies ||= {}
|
42
|
+
end
|
43
|
+
|
44
|
+
def list_all_dependencies_with(attr)
|
45
|
+
dependencies
|
46
|
+
.values
|
47
|
+
.map { |record_dependency|
|
48
|
+
record_dependency.with(attr)
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
class RecordDependency
|
53
|
+
def with(attr)
|
54
|
+
record_dependency[attr.to_sym] ||= DependentObject.new
|
55
|
+
end
|
56
|
+
|
57
|
+
def each_dependent_record_label(&block)
|
58
|
+
return enum_for(:each_dependent_record_label) unless block_given?
|
59
|
+
|
60
|
+
record_dependency.each { |attr, dependent_object|
|
61
|
+
block.call(attr, dependent_object.record_label)
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def dependent_record_labels
|
66
|
+
record_dependency.values.map(&:record_label).compact
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def record_dependency
|
72
|
+
@record_dependency ||= {}
|
73
|
+
end
|
74
|
+
|
75
|
+
class DependentObject
|
76
|
+
attr_accessor :record_label, :model_name
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DumpedRailers
|
4
|
+
module RecordBuilder
|
5
|
+
class FixtureRow
|
6
|
+
attr_reader :label, :attrs
|
7
|
+
|
8
|
+
def initialize(label, attrs)
|
9
|
+
@label = label
|
10
|
+
@attrs = attrs
|
11
|
+
end
|
12
|
+
|
13
|
+
def analyze_dependencies!(dependency_tracker)
|
14
|
+
raise RuntimeError, 'Can\'t execute the dependency analysis twice. This has been done already' if @dependency
|
15
|
+
|
16
|
+
@dependency = dependency_tracker
|
17
|
+
|
18
|
+
attrs.each do |attr, value|
|
19
|
+
ref, model_name = parse_reference_from(value)
|
20
|
+
next unless ref
|
21
|
+
|
22
|
+
attrs[attr] = ref.to_sym
|
23
|
+
@dependency.with(attr).record_label = ref.to_sym
|
24
|
+
@dependency.with(attr).model_name = model_name&.to_sym
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def instantiate_as!(model)
|
29
|
+
raise RuntimeError, 'Could not find the dependency tracker. Run #analyze_dependencies to instaitiate the records' unless @dependency
|
30
|
+
|
31
|
+
@model = model
|
32
|
+
resolve_reference!
|
33
|
+
object = model.new(attrs)
|
34
|
+
RecordStore.register(label, object: object)
|
35
|
+
|
36
|
+
object
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def parse_reference_from(val)
|
42
|
+
# NOTE: make sure its object is a string (can be a json object)
|
43
|
+
return unless val.is_a? String
|
44
|
+
return unless val.start_with? '__'
|
45
|
+
|
46
|
+
# format convention
|
47
|
+
# for non-polymorphic association: __[identifier]
|
48
|
+
# for polymorphic association: __[identifier]([model_name])
|
49
|
+
ref, _, model_name = val.scan(/\A(__[^(\s]+)(\(([^)]+)\))?\z/).first
|
50
|
+
|
51
|
+
[ref, model_name]
|
52
|
+
end
|
53
|
+
|
54
|
+
def resolve_reference!
|
55
|
+
raise RuntimeError, <<~"ERROR_MESSAGE" unless resolvable?
|
56
|
+
cannot resolve dependencies. (some fixtures might be missing)
|
57
|
+
model: #{@model}
|
58
|
+
record: #{label}
|
59
|
+
ERROR_MESSAGE
|
60
|
+
|
61
|
+
@dependency.each_dependent_record_label { |attr, record_label|
|
62
|
+
attrs[attr] = RecordStore.retrieve!(record_label)
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def resolvable?
|
67
|
+
@dependency.dependent_record_labels.all? { |label|
|
68
|
+
RecordStore.registered?(label)
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
class RecordStore
|
73
|
+
class << self
|
74
|
+
def register(label, object:)
|
75
|
+
set_object(label, object)
|
76
|
+
end
|
77
|
+
|
78
|
+
def registered?(label)
|
79
|
+
!object_for(label).nil?
|
80
|
+
end
|
81
|
+
|
82
|
+
def retrieve!(label)
|
83
|
+
raise RuntimeError, "couldn't resolve dependent record: #{label}" unless registered?(label)
|
84
|
+
|
85
|
+
object_for(label)
|
86
|
+
end
|
87
|
+
|
88
|
+
def clear!
|
89
|
+
@repository = {}
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def repository
|
95
|
+
@repository ||= {}
|
96
|
+
end
|
97
|
+
|
98
|
+
def object_for(label)
|
99
|
+
repository[label.to_sym]
|
100
|
+
end
|
101
|
+
|
102
|
+
def set_object(label, object)
|
103
|
+
repository[label.to_sym] = object
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def initialize
|
108
|
+
# use this class as (some sort of) Singleton
|
109
|
+
raise NotImplementedError
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'fixture_table'
|
4
|
+
require 'tsort'
|
5
|
+
|
6
|
+
module DumpedRailers
|
7
|
+
module RecordBuilder
|
8
|
+
class FixtureSet
|
9
|
+
include TSort
|
10
|
+
attr_reader :fixture_tables, :record_sets
|
11
|
+
|
12
|
+
def initialize(raw_fixtures)
|
13
|
+
@fixture_tables = raw_fixtures.map { |raw_records| build_fixture_table(raw_records) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def sort_by_table_dependencies!
|
17
|
+
@fixture_tables.each(&:analyze_metadata_dependencies!)
|
18
|
+
# dependency are sorted in topological order using Active Record reflection
|
19
|
+
@fixture_tables = tsort
|
20
|
+
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def build_record_sets!
|
25
|
+
@record_sets = @fixture_tables.map { |table|
|
26
|
+
[table.model, table.build_records!]
|
27
|
+
}.to_h
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def build_fixture_table(raw_records)
|
33
|
+
FixtureTable.new(raw_records)
|
34
|
+
end
|
35
|
+
|
36
|
+
def tsort_each_node(&block)
|
37
|
+
@fixture_tables.each { |table| block.call(table) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def tsort_each_child(node, &block)
|
41
|
+
dependent_nodes = @fixture_tables.select { |table| node.dependencies.include? table.model_name }
|
42
|
+
dependent_nodes.each &block
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'fixture_row'
|
4
|
+
require_relative 'dependency_tracker'
|
5
|
+
|
6
|
+
module DumpedRailers
|
7
|
+
module RecordBuilder
|
8
|
+
class FixtureTable
|
9
|
+
attr_reader :model, :model_name, :rows, :objects, :dependencies
|
10
|
+
|
11
|
+
def initialize(raw_records)
|
12
|
+
config = raw_records.delete('_fixture')
|
13
|
+
|
14
|
+
@model = identify_model!(config)
|
15
|
+
@model_name = model.name.to_sym
|
16
|
+
@dependency_tracker = DependencyTracker.for(model)
|
17
|
+
|
18
|
+
@rows = raw_records.map { |label, attrs|
|
19
|
+
build_fixture_row(label, attrs)
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def analyze_metadata_dependencies!
|
24
|
+
raise RuntimeError, "Dependency Analysis has already been done with the fixture for #{model_name}" if @dependencies
|
25
|
+
|
26
|
+
rows.map { |row| row.analyze_dependencies!(@dependency_tracker.on(row)) }
|
27
|
+
|
28
|
+
@dependencies = model.reflect_on_all_associations.select(&:belongs_to?).flat_map { |rel|
|
29
|
+
if rel.polymorphic?
|
30
|
+
@dependency_tracker.list_all_model_names_with(rel.name)
|
31
|
+
else
|
32
|
+
rel.class_name.to_sym
|
33
|
+
end
|
34
|
+
}.uniq
|
35
|
+
end
|
36
|
+
|
37
|
+
def build_records!
|
38
|
+
raise RuntimeError, "The records in this fixture for #{model_name} have been built already" if @instantiated
|
39
|
+
|
40
|
+
@objects = rows.map { |row| row.instantiate_as!(model) }
|
41
|
+
@instantiated = true
|
42
|
+
|
43
|
+
objects
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def build_fixture_row(label, attrs)
|
49
|
+
FixtureRow.new(
|
50
|
+
label.to_sym,
|
51
|
+
attrs.symbolize_keys,
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
def identify_model!(config)
|
56
|
+
model_name = config&.dig('model_class')
|
57
|
+
raise RuntimeError, <<~"ERROR_MESSAGE" unless model_name
|
58
|
+
couldn't find `_fixture: model_class` label in the fixture.
|
59
|
+
(possibly not an auto-generated one?)
|
60
|
+
ERROR_MESSAGE
|
61
|
+
|
62
|
+
model = model_name.safe_constantize
|
63
|
+
return model if model && model < ActiveRecord::Base
|
64
|
+
|
65
|
+
raise RuntimeError, <<~"ERROR_MESSAGE"
|
66
|
+
couldn't find a model named #{model_name} specified with `_fixture: model_class` label in the fixture.
|
67
|
+
you might want to check whether:
|
68
|
+
- this task runs in the same application that the fixtures were generated in
|
69
|
+
- relevant tables have not been altered or dropped since the fixtures were generated
|
70
|
+
ERROR_MESSAGE
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
metadata
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dumped_railers
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Koji Onishi
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-12-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 12.3.3
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 12.3.3
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '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: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sqlite3
|
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: activerecord
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '5.2'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '5.2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: database_cleaner-active_record
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.8'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.8'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry
|
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: pry-byebug
|
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
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: pry-doc
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description: DumpedRailers helps you take a snapshot of ActiveRecord models in Rails-compatible
|
140
|
+
fixture format, and re-import them wherever necessary without destroying current
|
141
|
+
data you have.
|
142
|
+
email:
|
143
|
+
- fursich0@gmail.com
|
144
|
+
executables: []
|
145
|
+
extensions: []
|
146
|
+
extra_rdoc_files: []
|
147
|
+
files:
|
148
|
+
- ".gitignore"
|
149
|
+
- ".rspec"
|
150
|
+
- ".travis.yml"
|
151
|
+
- CHANGELOG.md
|
152
|
+
- Gemfile
|
153
|
+
- LICENSE.txt
|
154
|
+
- README.md
|
155
|
+
- Rakefile
|
156
|
+
- bin/console
|
157
|
+
- bin/setup
|
158
|
+
- dumped_railers.gemspec
|
159
|
+
- gemfiles/Gemfile.rails_5.2.4
|
160
|
+
- gemfiles/Gemfile.rails_6.0.3.4
|
161
|
+
- gemfiles/Gemfile.rails_6.1.0
|
162
|
+
- lib/dumped_railers.rb
|
163
|
+
- lib/dumped_railers/dump.rb
|
164
|
+
- lib/dumped_railers/file_helper.rb
|
165
|
+
- lib/dumped_railers/fixture_builder/model.rb
|
166
|
+
- lib/dumped_railers/fixture_builder/record.rb
|
167
|
+
- lib/dumped_railers/import.rb
|
168
|
+
- lib/dumped_railers/preprocessor/strip_ignorables.rb
|
169
|
+
- lib/dumped_railers/record_builder/dependency_tracker.rb
|
170
|
+
- lib/dumped_railers/record_builder/fixture_row.rb
|
171
|
+
- lib/dumped_railers/record_builder/fixture_set.rb
|
172
|
+
- lib/dumped_railers/record_builder/fixture_table.rb
|
173
|
+
- lib/dumped_railers/version.rb
|
174
|
+
homepage: https://github.com/fursich/dumped_railers
|
175
|
+
licenses:
|
176
|
+
- MIT
|
177
|
+
metadata:
|
178
|
+
homepage_uri: https://github.com/fursich/dumped_railers
|
179
|
+
source_code_uri: https://github.com/fursich/dumped_railers
|
180
|
+
changelog_uri: https://github.com/fursich/dumped_railers/CHANGELOG.md
|
181
|
+
post_install_message:
|
182
|
+
rdoc_options: []
|
183
|
+
require_paths:
|
184
|
+
- lib
|
185
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
186
|
+
requirements:
|
187
|
+
- - ">="
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: 2.3.0
|
190
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
requirements: []
|
196
|
+
rubygems_version: 3.0.3
|
197
|
+
signing_key:
|
198
|
+
specification_version: 4
|
199
|
+
summary: A flexible fixture importer/exporter, that can transport ActiveRecord data
|
200
|
+
in fixture format
|
201
|
+
test_files: []
|