evil-seed 0.4.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +86 -0
- data/.github/workflows/test.yml +79 -0
- data/CHANGELOG.md +74 -0
- data/Gemfile +13 -0
- data/README.md +31 -1
- data/evil-seed.gemspec +0 -1
- data/lib/evil_seed/configuration/root.rb +27 -4
- data/lib/evil_seed/configuration.rb +15 -2
- data/lib/evil_seed/record_dumper.rb +9 -3
- data/lib/evil_seed/relation_dumper.rb +50 -14
- data/lib/evil_seed/root_dumper.rb +4 -1
- data/lib/evil_seed/version.rb +1 -1
- metadata +9 -29
- data/.travis.yml +0 -82
- data/Appraisals +0 -48
- data/gemfiles/.bundle/config +0 -2
- data/gemfiles/activerecord_5_0.gemfile +0 -10
- data/gemfiles/activerecord_5_1.gemfile +0 -10
- data/gemfiles/activerecord_5_2.gemfile +0 -10
- data/gemfiles/activerecord_6_0.gemfile +0 -10
- data/gemfiles/activerecord_6_1.gemfile +0 -10
- data/gemfiles/activerecord_master.gemfile +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dfdedee7cac6da3803d6071ccbda5eaf19ee07bc07c67775b202160c304dd323
|
4
|
+
data.tar.gz: e04bc0672ccb683928651843c749ad05378af846c6d4b51d7e7c3d14eb43c179
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2973540b6fd16936419e9e76e389df4dd854313e3674ff7c73f7056c18c3e8979ca302d0c395a60e3a17abb7f9bc2b886ef4939711ad43c3df72cebc06cf944
|
7
|
+
data.tar.gz: cb2772174b471dc7977fcaa00bc79dc0bf45429f836a7e490487d37e625f4d8328583d1d103b13d2a8899e56736793acb24e35fd362c196aab1a60489f023159
|
@@ -0,0 +1,86 @@
|
|
1
|
+
name: Release gem
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
tags:
|
6
|
+
- v*
|
7
|
+
|
8
|
+
jobs:
|
9
|
+
release:
|
10
|
+
runs-on: ubuntu-latest
|
11
|
+
permissions:
|
12
|
+
contents: write
|
13
|
+
id-token: write
|
14
|
+
packages: write
|
15
|
+
steps:
|
16
|
+
- uses: actions/checkout@v4
|
17
|
+
with:
|
18
|
+
fetch-depth: 0 # Fetch current tag as annotated. See https://github.com/actions/checkout/issues/290
|
19
|
+
- uses: ruby/setup-ruby@v1
|
20
|
+
with:
|
21
|
+
ruby-version: "3.3"
|
22
|
+
- name: "Extract data from tag: version, message, body"
|
23
|
+
id: tag
|
24
|
+
run: |
|
25
|
+
git fetch --tags --force # Really fetch annotated tag. See https://github.com/actions/checkout/issues/290#issuecomment-680260080
|
26
|
+
echo ::set-output name=version::${GITHUB_REF#refs/tags/v}
|
27
|
+
echo ::set-output name=subject::$(git for-each-ref $GITHUB_REF --format='%(contents:subject)')
|
28
|
+
BODY="$(git for-each-ref $GITHUB_REF --format='%(contents:body)')"
|
29
|
+
# Extract changelog entries between this and previous version headers
|
30
|
+
escaped_version=$(echo ${GITHUB_REF#refs/tags/v} | sed -e 's/[]\/$*.^[]/\\&/g')
|
31
|
+
changelog=$(awk "BEGIN{inrelease=0} /## \[${escaped_version}\]/{inrelease=1;next} /## \[[0-9]+\.[0-9]+\.[0-9]+.*?\]/{inrelease=0;exit} {if (inrelease) print}" CHANGELOG.md)
|
32
|
+
# Multiline body for release. See https://github.community/t/set-output-truncates-multiline-strings/16852/5
|
33
|
+
BODY="${BODY}"$'\n'"${changelog}"
|
34
|
+
BODY="${BODY//'%'/'%25'}"
|
35
|
+
BODY="${BODY//$'\n'/'%0A'}"
|
36
|
+
BODY="${BODY//$'\r'/'%0D'}"
|
37
|
+
echo "::set-output name=body::$BODY"
|
38
|
+
# Add pre-release option if tag name has any suffix after vMAJOR.MINOR.PATCH
|
39
|
+
if [[ ${GITHUB_REF#refs/tags/} =~ ^v[0-9]+\.[0-9]+\.[0-9]+.+ ]]; then
|
40
|
+
echo ::set-output name=prerelease::true
|
41
|
+
fi
|
42
|
+
- name: Build gem
|
43
|
+
run: gem build
|
44
|
+
- name: Calculate checksums
|
45
|
+
run: sha256sum evil-seed-${{ steps.tag.outputs.version }}.gem > SHA256SUM
|
46
|
+
- name: Check version
|
47
|
+
run: ls -l evil-seed-${{ steps.tag.outputs.version }}.gem
|
48
|
+
- name: Create Release
|
49
|
+
id: create_release
|
50
|
+
uses: actions/create-release@v1
|
51
|
+
env:
|
52
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
53
|
+
with:
|
54
|
+
tag_name: ${{ github.ref }}
|
55
|
+
release_name: ${{ steps.tag.outputs.subject }}
|
56
|
+
body: ${{ steps.tag.outputs.body }}
|
57
|
+
draft: false
|
58
|
+
prerelease: ${{ steps.tag.outputs.prerelease }}
|
59
|
+
- name: Upload built gem as release asset
|
60
|
+
uses: actions/upload-release-asset@v1
|
61
|
+
env:
|
62
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
63
|
+
with:
|
64
|
+
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
65
|
+
asset_path: evil-seed-${{ steps.tag.outputs.version }}.gem
|
66
|
+
asset_name: evil-seed-${{ steps.tag.outputs.version }}.gem
|
67
|
+
asset_content_type: application/x-tar
|
68
|
+
- name: Upload checksums as release asset
|
69
|
+
uses: actions/upload-release-asset@v1
|
70
|
+
env:
|
71
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
72
|
+
with:
|
73
|
+
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
74
|
+
asset_path: SHA256SUM
|
75
|
+
asset_name: SHA256SUM
|
76
|
+
asset_content_type: text/plain
|
77
|
+
- name: Publish to GitHub packages
|
78
|
+
env:
|
79
|
+
GEM_HOST_API_KEY: Bearer ${{ secrets.GITHUB_TOKEN }}
|
80
|
+
run: |
|
81
|
+
gem push evil-seed-${{ steps.tag.outputs.version }}.gem --host https://rubygems.pkg.github.com/${{ github.repository_owner }}
|
82
|
+
- name: Configure RubyGems Credentials
|
83
|
+
uses: rubygems/configure-rubygems-credentials@main
|
84
|
+
- name: Publish to RubyGems
|
85
|
+
run: |
|
86
|
+
gem push evil-seed-${{ steps.tag.outputs.version }}.gem
|
@@ -0,0 +1,79 @@
|
|
1
|
+
name: Tests
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- '**'
|
7
|
+
tags-ignore:
|
8
|
+
- 'v*'
|
9
|
+
pull_request:
|
10
|
+
|
11
|
+
jobs:
|
12
|
+
test:
|
13
|
+
name: Ruby ${{ matrix.ruby }}, ActiveRecord ${{ matrix.activerecord }}, ${{ matrix.database }}
|
14
|
+
continue-on-error: ${{ matrix.ruby == 'head' }}
|
15
|
+
strategy:
|
16
|
+
fail-fast: false
|
17
|
+
matrix:
|
18
|
+
include:
|
19
|
+
- ruby: "head"
|
20
|
+
activerecord: "head"
|
21
|
+
database: sqlite
|
22
|
+
- ruby: "3.3"
|
23
|
+
activerecord: "7.1"
|
24
|
+
database: postgresql
|
25
|
+
- ruby: "3.3"
|
26
|
+
activerecord: "7.1"
|
27
|
+
database: mysql
|
28
|
+
- ruby: "3.3"
|
29
|
+
activerecord: "7.1"
|
30
|
+
database: sqlite
|
31
|
+
- ruby: "3.2"
|
32
|
+
activerecord: "7.0"
|
33
|
+
database: sqlite
|
34
|
+
- ruby: "3.1"
|
35
|
+
activerecord: "6.1"
|
36
|
+
database: sqlite
|
37
|
+
- ruby: "3.0"
|
38
|
+
activerecord: "6.0"
|
39
|
+
database: sqlite
|
40
|
+
|
41
|
+
runs-on: ubuntu-latest
|
42
|
+
|
43
|
+
services:
|
44
|
+
postgres:
|
45
|
+
image: postgres:16
|
46
|
+
env:
|
47
|
+
POSTGRES_USER: postgres
|
48
|
+
POSTGRES_PASSWORD: postgres
|
49
|
+
ports:
|
50
|
+
- 5432:5432
|
51
|
+
options: >-
|
52
|
+
--health-cmd pg_isready
|
53
|
+
--health-interval 10s
|
54
|
+
--health-timeout 5s
|
55
|
+
--health-retries 5
|
56
|
+
mysql:
|
57
|
+
image: mysql:8.4
|
58
|
+
env:
|
59
|
+
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
60
|
+
MYSQL_DATABASE: evil_seed_test
|
61
|
+
ports:
|
62
|
+
- 3306:3306
|
63
|
+
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
64
|
+
|
65
|
+
env:
|
66
|
+
CI: true
|
67
|
+
ACTIVERECORD_VERSION: "${{ matrix.activerecord }}"
|
68
|
+
DB: "${{ matrix.database }}"
|
69
|
+
POSTGRES_USER: postgres
|
70
|
+
POSTGRES_PASSWORD: postgres
|
71
|
+
|
72
|
+
steps:
|
73
|
+
- uses: actions/checkout@v4
|
74
|
+
- uses: ruby/setup-ruby@v1
|
75
|
+
with:
|
76
|
+
ruby-version: ${{ matrix.ruby }}
|
77
|
+
bundler-cache: true
|
78
|
+
- name: Run tests
|
79
|
+
run: bundle exec rake
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [Unreleased]
|
9
|
+
|
10
|
+
## [0.6.0] - 2024-06-18
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- Association inclusion option. [@gazay] ([#13](https://github.com/evilmartians/evil-seed/pull/13))
|
15
|
+
- Option to limit association depth. [@gazay] ([#13](https://github.com/evilmartians/evil-seed/pull/13))
|
16
|
+
- Option to ignore `default_scope` in models. [@gazay] ([#13](https://github.com/evilmartians/evil-seed/pull/13))
|
17
|
+
- Option to disable nullifying of foreign keys. [@gazay] ([#13](https://github.com/evilmartians/evil-seed/pull/13))
|
18
|
+
|
19
|
+
## [0.5.0] - 2023-02-16
|
20
|
+
|
21
|
+
### Added
|
22
|
+
|
23
|
+
- Option to ignore columns from a given model. [@nhocki] ([#17](https://github.com/evilmartians/evil-seed/pull/17))
|
24
|
+
|
25
|
+
## [0.4.0] - 2022-12-07
|
26
|
+
|
27
|
+
### Fixed
|
28
|
+
|
29
|
+
- Ignore generated database columns. [@cmer] ([#16](https://github.com/evilmartians/evil-seed/pull/16))
|
30
|
+
|
31
|
+
## [0.3.0] - 2022-03-14
|
32
|
+
|
33
|
+
### Added
|
34
|
+
|
35
|
+
- Passing attribute value to anonymizer block (to partially modify it). [@Envek]
|
36
|
+
|
37
|
+
## [0.2.0] - 2022-03-10
|
38
|
+
|
39
|
+
### Fixed
|
40
|
+
|
41
|
+
- Ignore virtual ActiveRecord attributes. [@Envek]
|
42
|
+
|
43
|
+
### Removed
|
44
|
+
|
45
|
+
- Support for ActiveRecord 4.2
|
46
|
+
|
47
|
+
## [0.1.3] - 2021-09-02
|
48
|
+
|
49
|
+
### Fixed
|
50
|
+
|
51
|
+
- Compatibility with Ruby 3.0 and ActiveRecord 6.x.
|
52
|
+
|
53
|
+
## [0.1.2] - 2018-03-27
|
54
|
+
|
55
|
+
### Fixed
|
56
|
+
|
57
|
+
- Bug with unwanted pseudo columns in dump when dumping HABTM join table without one side.
|
58
|
+
|
59
|
+
## [0.1.1] - 2017-05-15
|
60
|
+
|
61
|
+
### Fixed
|
62
|
+
|
63
|
+
- ActiveRecord 4.2 support by backporting of `ActiveRecord::Relation#in_batches`
|
64
|
+
- Dumping of the whole model without constraints
|
65
|
+
|
66
|
+
## [0.1.0] - 2017-05-09
|
67
|
+
|
68
|
+
Initial release. [@palkan], [@Envek]
|
69
|
+
|
70
|
+
[@Envek]: https://github.com/Envek "Andrey Novikov"
|
71
|
+
[@palkan]: https://github.com/palkan "Vladimir Dementyev"
|
72
|
+
[@cmer]: https://github.com/cmer "Carl Mercier"
|
73
|
+
[@nhocki]: https://github.com/nhocki "Nicolás Hock-Isaza"
|
74
|
+
[@gazay]: https://github.com/gazay "Alex Gaziev"
|
data/Gemfile
CHANGED
@@ -4,3 +4,16 @@ source 'https://rubygems.org'
|
|
4
4
|
|
5
5
|
# Specify your gem's dependencies in evil-seed.gemspec
|
6
6
|
gemspec
|
7
|
+
|
8
|
+
activerecord_version = ENV.fetch("ACTIVERECORD_VERSION", "~> 7.1")
|
9
|
+
case activerecord_version.upcase
|
10
|
+
when "HEAD"
|
11
|
+
git "https://github.com/rails/rails.git" do
|
12
|
+
gem "activerecord"
|
13
|
+
gem "rails"
|
14
|
+
end
|
15
|
+
else
|
16
|
+
activerecord_version = "~> #{activerecord_version}.0" if activerecord_version.match?(/^\d+\.\d+$/)
|
17
|
+
gem "activerecord", activerecord_version
|
18
|
+
gem "sqlite3", "~> 1.4"
|
19
|
+
end
|
data/README.md
CHANGED
@@ -52,7 +52,10 @@ EvilSeed.configure do |config|
|
|
52
52
|
#
|
53
53
|
# Association path is a dot-delimited string of association chain starting from model itself:
|
54
54
|
# example: "forum.users.questions"
|
55
|
-
root.exclude(/\btracking_pixels\b/, 'forum.popular_questions')
|
55
|
+
root.exclude(/\btracking_pixels\b/, 'forum.popular_questions', /\Aforum\.parent\b/)
|
56
|
+
|
57
|
+
# Include back only certain associations
|
58
|
+
root.include(/\Aforum(\.parent(\.questions(\.answers)?)?)?\z/)
|
56
59
|
|
57
60
|
# It's possible to limit the number of included into dump has_many and has_one records for every association
|
58
61
|
# Note that belongs_to records for all not excluded associations are always dumped to keep referential integrity.
|
@@ -60,6 +63,11 @@ EvilSeed.configure do |config|
|
|
60
63
|
|
61
64
|
# Or for certain association only
|
62
65
|
root.limit_associations_size(10, 'forum.questions')
|
66
|
+
|
67
|
+
# Limit the depth of associations to be dumped from the root level
|
68
|
+
# All traverses through has_many, belongs_to, etc are counted
|
69
|
+
# So forum.subforums.subforums.questions.answers will be 5 levels deep
|
70
|
+
root.limit_deep(10)
|
63
71
|
end
|
64
72
|
|
65
73
|
# Everything you can pass to +where+ method will work as constraints:
|
@@ -88,6 +96,28 @@ EvilSeed.configure do |config|
|
|
88
96
|
email { Faker::Internet.email }
|
89
97
|
login { |login| "#{login}-test" }
|
90
98
|
end
|
99
|
+
|
100
|
+
# You can ignore columns for any model. This is specially useful when working
|
101
|
+
# with encrypted columns.
|
102
|
+
#
|
103
|
+
# This will remove the columns even if the model is not a root node and is
|
104
|
+
# dumped via an association.
|
105
|
+
config.ignore_columns("Profile", :name)
|
106
|
+
|
107
|
+
# Disable foreign key nullification for records that are not included in the dump
|
108
|
+
# By default, EvilSeed will nullify foreign keys for records that are not included in the dump
|
109
|
+
config.dont_nullify = true
|
110
|
+
|
111
|
+
# Unscope relations to include soft-deleted records etc
|
112
|
+
# This is useful when you want to include all records, including those that are hidden by default
|
113
|
+
# By default, EvilSeed will abide default scope of models
|
114
|
+
config.unscoped = true
|
115
|
+
|
116
|
+
# Verbose mode will print out the progress of the dump to the console along with writing the file
|
117
|
+
# By default, verbose mode is off
|
118
|
+
config.verbose = true
|
119
|
+
config.verbose_sql = true
|
120
|
+
end
|
91
121
|
```
|
92
122
|
|
93
123
|
### Creating dump
|
data/evil-seed.gemspec
CHANGED
@@ -5,16 +5,19 @@ module EvilSeed
|
|
5
5
|
# Configuration for dumping some root model and its associations
|
6
6
|
class Root
|
7
7
|
attr_reader :model, :constraints
|
8
|
-
attr_reader :total_limit, :association_limits
|
9
|
-
attr_reader :exclusions
|
8
|
+
attr_reader :total_limit, :association_limits, :deep_limit, :dont_nullify
|
9
|
+
attr_reader :exclusions, :inclusions
|
10
10
|
|
11
11
|
# @param model [String] Name of the model class to dump
|
12
12
|
# @param constraints [String, Hash] Everything you can feed into +where+ to limit number of records
|
13
|
-
def initialize(model, *constraints)
|
13
|
+
def initialize(model, dont_nullify, *constraints)
|
14
14
|
@model = model
|
15
15
|
@constraints = constraints
|
16
16
|
@exclusions = []
|
17
|
+
@inclusions = []
|
17
18
|
@association_limits = {}
|
19
|
+
@deep_limit = nil
|
20
|
+
@dont_nullify = dont_nullify
|
18
21
|
end
|
19
22
|
|
20
23
|
# Exclude some of associations from the dump
|
@@ -23,6 +26,12 @@ module EvilSeed
|
|
23
26
|
@exclusions += association_patterns
|
24
27
|
end
|
25
28
|
|
29
|
+
# Include some excluded associations back to the dump
|
30
|
+
# @param association_patterns Array<String, Regex> Patterns to exclude associated models from dump
|
31
|
+
def include(*association_patterns)
|
32
|
+
@inclusions += association_patterns
|
33
|
+
end
|
34
|
+
|
26
35
|
# Limit number of records in all (if pattern is not provided) or given associations to include into dump
|
27
36
|
# @param limit [Integer] Maximum number of records in associations to include into dump
|
28
37
|
# @param association_pattern [String, Regex] Pattern to limit number of records for certain associated models
|
@@ -34,8 +43,22 @@ module EvilSeed
|
|
34
43
|
end
|
35
44
|
end
|
36
45
|
|
46
|
+
# Limit deepenes of associations to include into dump
|
47
|
+
# @param limit [Integer] Maximum level to recursively dive into associations
|
48
|
+
def limit_deep(limit)
|
49
|
+
@deep_limit = limit
|
50
|
+
end
|
51
|
+
|
52
|
+
def do_not_nullify(nullify_flag)
|
53
|
+
@dont_nullify = nullify_flag
|
54
|
+
end
|
55
|
+
|
37
56
|
def excluded?(association_path)
|
38
|
-
exclusions.any? { |exclusion|
|
57
|
+
exclusions.any? { |exclusion| association_path.match(exclusion) } #.match(association_path) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def included?(association_path)
|
61
|
+
inclusions.any? { |inclusion| association_path.match(inclusion) } #.match(association_path) }
|
39
62
|
end
|
40
63
|
end
|
41
64
|
end
|
@@ -7,10 +7,15 @@ require_relative 'anonymizer'
|
|
7
7
|
module EvilSeed
|
8
8
|
# This module holds configuration for creating dump: which models and their constraints
|
9
9
|
class Configuration
|
10
|
-
attr_accessor :record_dumper_class
|
10
|
+
attr_accessor :record_dumper_class, :verbose, :verbose_sql, :unscoped, :dont_nullify
|
11
11
|
|
12
12
|
def initialize
|
13
13
|
@record_dumper_class = RecordDumper
|
14
|
+
@verbose = false
|
15
|
+
@verbose_sql = false
|
16
|
+
@unscoped = false
|
17
|
+
@dont_nullify = false
|
18
|
+
@ignored_columns = Hash.new { |h, k| h[k] = [] }
|
14
19
|
end
|
15
20
|
|
16
21
|
def roots
|
@@ -18,7 +23,7 @@ module EvilSeed
|
|
18
23
|
end
|
19
24
|
|
20
25
|
def root(model, *constraints)
|
21
|
-
new_root = Root.new(model, *constraints)
|
26
|
+
new_root = Root.new(model, dont_nullify, *constraints)
|
22
27
|
yield new_root if block_given?
|
23
28
|
roots << new_root
|
24
29
|
end
|
@@ -33,10 +38,18 @@ module EvilSeed
|
|
33
38
|
customizers[model_class.to_s] << Anonymizer.new(model_class, &block)
|
34
39
|
end
|
35
40
|
|
41
|
+
def ignore_columns(model_class, *columns)
|
42
|
+
@ignored_columns[model_class] += columns
|
43
|
+
end
|
44
|
+
|
36
45
|
# Customizer objects for every model
|
37
46
|
# @return [Hash{String => Array<#call>}]
|
38
47
|
def customizers
|
39
48
|
@customizers ||= Hash.new { |h, k| h[k] = [] }
|
40
49
|
end
|
50
|
+
|
51
|
+
def ignored_columns_for(model_class)
|
52
|
+
@ignored_columns[model_class]
|
53
|
+
end
|
41
54
|
end
|
42
55
|
end
|
@@ -65,11 +65,17 @@ module EvilSeed
|
|
65
65
|
|
66
66
|
def write!(attributes)
|
67
67
|
# Remove non-insertable columns from attributes
|
68
|
-
attributes = attributes.slice(*insertable_column_names)
|
68
|
+
attributes = prepare(attributes.slice(*insertable_column_names))
|
69
|
+
|
70
|
+
if configuration.verbose_sql
|
71
|
+
puts("-- #{relation_dumper.association_path}\n")
|
72
|
+
puts(@tuples_written.zero? ? insert_statement : ",\n")
|
73
|
+
puts(" (#{attributes.join(', ')})")
|
74
|
+
end
|
69
75
|
|
70
76
|
@output.write("-- #{relation_dumper.association_path}\n") && @header_written = true unless @header_written
|
71
77
|
@output.write(@tuples_written.zero? ? insert_statement : ",\n")
|
72
|
-
@output.write(" (#{
|
78
|
+
@output.write(" (#{attributes.join(', ')})")
|
73
79
|
@tuples_written += 1
|
74
80
|
@output.write(";\n") && @tuples_written = 0 if @tuples_written == MAX_TUPLES_PER_INSERT_STMT
|
75
81
|
end
|
@@ -84,7 +90,7 @@ module EvilSeed
|
|
84
90
|
attributes.map do |key, value|
|
85
91
|
type = model_class.attribute_types[key]
|
86
92
|
model_class.connection.quote(type.serialize(value))
|
87
|
-
end
|
93
|
+
end.flatten.compact
|
88
94
|
end
|
89
95
|
end
|
90
96
|
end
|
@@ -25,13 +25,15 @@ module EvilSeed
|
|
25
25
|
|
26
26
|
attr_reader :relation, :root_dumper, :model_class, :association_path, :search_key, :identifiers, :nullify_columns,
|
27
27
|
:belongs_to_reflections, :has_many_reflections, :foreign_keys, :loaded_ids, :to_load_map,
|
28
|
-
:record_dumper, :inverse_reflection, :table_names, :options
|
28
|
+
:record_dumper, :inverse_reflection, :table_names, :options,
|
29
|
+
:current_deep, :verbose
|
29
30
|
|
30
|
-
delegate :root, :configuration, :total_limit, :loaded_map, to: :root_dumper
|
31
|
+
delegate :root, :configuration, :dont_nullify, :total_limit, :deep_limit, :loaded_map, to: :root_dumper
|
31
32
|
|
32
33
|
def initialize(relation, root_dumper, association_path, **options)
|
33
34
|
@relation = relation
|
34
35
|
@root_dumper = root_dumper
|
36
|
+
@verbose = configuration.verbose
|
35
37
|
@identifiers = options[:identifiers]
|
36
38
|
@to_load_map = Hash.new { |h, k| h[k] = [] }
|
37
39
|
@foreign_keys = Hash.new { |h, k| h[k] = [] }
|
@@ -46,41 +48,62 @@ module EvilSeed
|
|
46
48
|
@belongs_to_reflections = setup_belongs_to_reflections
|
47
49
|
@has_many_reflections = setup_has_many_reflections
|
48
50
|
@options = options
|
51
|
+
@current_deep = association_path.split('.').size
|
52
|
+
@dont_nullify = dont_nullify
|
53
|
+
|
54
|
+
puts("- #{association_path}") if verbose
|
49
55
|
end
|
50
56
|
|
51
57
|
# Generate dump and write it into +io+
|
52
58
|
# @return [Array<IO>] List of dump IOs for separate tables in order of dependencies (belongs_to are first)
|
53
59
|
def call
|
54
60
|
dump!
|
55
|
-
|
56
|
-
|
57
|
-
|
61
|
+
if deep_limit and current_deep > deep_limit
|
62
|
+
[record_dumper.result].flatten.compact
|
63
|
+
else
|
64
|
+
belongs_to_dumps = dump_belongs_to_associations!
|
65
|
+
has_many_dumps = dump_has_many_associations!
|
66
|
+
[belongs_to_dumps, record_dumper.result, has_many_dumps].flatten.compact
|
67
|
+
end
|
58
68
|
end
|
59
69
|
|
60
70
|
private
|
61
71
|
|
62
72
|
def dump!
|
73
|
+
original_ignored_columns = model_class.ignored_columns
|
74
|
+
model_class.ignored_columns += Array(configuration.ignored_columns_for(model_class.sti_name))
|
75
|
+
model_class.send(:reload_schema_from_cache) if ActiveRecord.version < Gem::Version.new("6.1.0.rc1") # See https://github.com/rails/rails/pull/37581
|
63
76
|
if identifiers.present?
|
77
|
+
puts(" # #{search_key} => #{identifiers}") if verbose
|
64
78
|
# Don't use AR::Base#find_each as we will get error on Oracle if we will have more than 1000 ids in IN statement
|
65
79
|
identifiers.in_groups_of(MAX_IDENTIFIERS_IN_IN_STMT).each do |ids|
|
66
|
-
fetch_attributes(relation.where(search_key => ids.compact))
|
80
|
+
attrs = fetch_attributes(relation.where(search_key => ids.compact))
|
81
|
+
puts(" -- dumped #{attrs.size}") if verbose
|
82
|
+
attrs.each do |attributes|
|
67
83
|
next unless check_limits!
|
68
84
|
dump_record!(attributes)
|
69
85
|
end
|
70
86
|
end
|
71
87
|
else
|
88
|
+
puts(" # #{relation.count}") if verbose
|
72
89
|
relation.in_batches do |relation|
|
73
|
-
fetch_attributes(relation)
|
90
|
+
attrs = fetch_attributes(relation)
|
91
|
+
puts(" -- dumped #{attrs.size}") if verbose
|
92
|
+
attrs.each do |attributes|
|
74
93
|
next unless check_limits!
|
75
94
|
dump_record!(attributes)
|
76
95
|
end
|
77
96
|
end
|
78
97
|
end
|
98
|
+
ensure
|
99
|
+
model_class.ignored_columns = original_ignored_columns
|
79
100
|
end
|
80
101
|
|
81
102
|
def dump_record!(attributes)
|
82
|
-
|
83
|
-
|
103
|
+
unless dont_nullify
|
104
|
+
nullify_columns.each do |nullify_column|
|
105
|
+
attributes[nullify_column] = nil
|
106
|
+
end
|
84
107
|
end
|
85
108
|
return unless record_dumper.call(attributes)
|
86
109
|
foreign_keys.each do |reflection_name, fk_column|
|
@@ -135,7 +158,11 @@ module EvilSeed
|
|
135
158
|
end
|
136
159
|
|
137
160
|
def build_relation(reflection)
|
138
|
-
|
161
|
+
if configuration.unscoped
|
162
|
+
relation = reflection.klass.unscoped
|
163
|
+
else
|
164
|
+
relation = reflection.klass.all
|
165
|
+
end
|
139
166
|
relation = relation.instance_eval(&reflection.scope) if reflection.scope
|
140
167
|
relation = relation.where(reflection.type => model_class.to_s) if reflection.options[:as] # polymorphic
|
141
168
|
relation
|
@@ -144,23 +171,32 @@ module EvilSeed
|
|
144
171
|
def setup_belongs_to_reflections
|
145
172
|
model_class.reflect_on_all_associations(:belongs_to).reject do |reflection|
|
146
173
|
next false if reflection.options[:polymorphic] # TODO: Add support for polymorphic belongs_to
|
174
|
+
included = root.included?("#{association_path}.#{reflection.name}")
|
147
175
|
excluded = root.excluded?("#{association_path}.#{reflection.name}") || reflection.name == inverse_reflection
|
148
|
-
if excluded
|
149
|
-
|
176
|
+
if excluded and not included
|
177
|
+
if model_class.column_names.include?(reflection.foreign_key)
|
178
|
+
puts(" -- excluded #{reflection.foreign_key}") if verbose
|
179
|
+
nullify_columns << reflection.foreign_key
|
180
|
+
end
|
150
181
|
else
|
151
182
|
foreign_keys[reflection.name] = reflection.foreign_key
|
152
183
|
table_names[reflection.name] = reflection.table_name
|
153
184
|
end
|
154
|
-
excluded
|
185
|
+
excluded and not included
|
155
186
|
end
|
156
187
|
end
|
157
188
|
|
158
189
|
# This method returns only direct has_one and has_many reflections. For HABTM it returns intermediate has_many
|
159
190
|
def setup_has_many_reflections
|
191
|
+
puts(" -- reflections #{model_class._reflections.keys}") if verbose
|
160
192
|
model_class._reflections.select do |_reflection_name, reflection|
|
161
193
|
next false if model_class.primary_key.nil?
|
194
|
+
|
162
195
|
next false if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
|
163
|
-
|
196
|
+
|
197
|
+
included = root.included?("#{association_path}.#{reflection.name}")
|
198
|
+
excluded = root.excluded?("#{association_path}.#{reflection.name}") || reflection.name == inverse_reflection
|
199
|
+
%i[has_one has_many].include?(reflection.macro) && !(excluded and not included)
|
164
200
|
end.map(&:second)
|
165
201
|
end
|
166
202
|
end
|
@@ -5,7 +5,7 @@ require_relative 'relation_dumper'
|
|
5
5
|
module EvilSeed
|
6
6
|
# This module collects dumps generation for root and all it's dependencies
|
7
7
|
class RootDumper
|
8
|
-
attr_reader :root, :dumper, :model_class, :total_limit, :association_limits
|
8
|
+
attr_reader :root, :dumper, :model_class, :total_limit, :deep_limit, :dont_nullify, :association_limits
|
9
9
|
|
10
10
|
delegate :loaded_map, :configuration, to: :dumper
|
11
11
|
|
@@ -14,6 +14,8 @@ module EvilSeed
|
|
14
14
|
@dumper = dumper
|
15
15
|
@to_load_map = {}
|
16
16
|
@total_limit = root.total_limit
|
17
|
+
@deep_limit = root.deep_limit
|
18
|
+
@dont_nullify = root.dont_nullify
|
17
19
|
@association_limits = root.association_limits.dup
|
18
20
|
|
19
21
|
@model_class = root.model.constantize
|
@@ -24,6 +26,7 @@ module EvilSeed
|
|
24
26
|
def call
|
25
27
|
association_path = model_class.model_name.singular
|
26
28
|
relation = model_class.all
|
29
|
+
relation = relation.unscoped if configuration.unscoped
|
27
30
|
relation = relation.where(*root.constraints) if root.constraints.any? # without arguments returns not a relation
|
28
31
|
RelationDumper.new(relation, self, association_path).call
|
29
32
|
end
|
data/lib/evil_seed/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: evil-seed
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrey Novikov
|
8
8
|
- Vladimir Dementyev
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2024-06-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -137,20 +137,6 @@ dependencies:
|
|
137
137
|
- - ">="
|
138
138
|
- !ruby/object:Gem::Version
|
139
139
|
version: '0'
|
140
|
-
- !ruby/object:Gem::Dependency
|
141
|
-
name: appraisal
|
142
|
-
requirement: !ruby/object:Gem::Requirement
|
143
|
-
requirements:
|
144
|
-
- - ">="
|
145
|
-
- !ruby/object:Gem::Version
|
146
|
-
version: '0'
|
147
|
-
type: :development
|
148
|
-
prerelease: false
|
149
|
-
version_requirements: !ruby/object:Gem::Requirement
|
150
|
-
requirements:
|
151
|
-
- - ">="
|
152
|
-
- !ruby/object:Gem::Version
|
153
|
-
version: '0'
|
154
140
|
description: " This gem allows you to easily dump and transform subset of your
|
155
141
|
ActiveRecord models and their relations.\n"
|
156
142
|
email:
|
@@ -160,10 +146,11 @@ executables: []
|
|
160
146
|
extensions: []
|
161
147
|
extra_rdoc_files: []
|
162
148
|
files:
|
149
|
+
- ".github/workflows/release.yml"
|
150
|
+
- ".github/workflows/test.yml"
|
163
151
|
- ".gitignore"
|
164
152
|
- ".rubocop.yml"
|
165
|
-
-
|
166
|
-
- Appraisals
|
153
|
+
- CHANGELOG.md
|
167
154
|
- Gemfile
|
168
155
|
- LICENSE.txt
|
169
156
|
- README.md
|
@@ -171,13 +158,6 @@ files:
|
|
171
158
|
- bin/console
|
172
159
|
- bin/setup
|
173
160
|
- evil-seed.gemspec
|
174
|
-
- gemfiles/.bundle/config
|
175
|
-
- gemfiles/activerecord_5_0.gemfile
|
176
|
-
- gemfiles/activerecord_5_1.gemfile
|
177
|
-
- gemfiles/activerecord_5_2.gemfile
|
178
|
-
- gemfiles/activerecord_6_0.gemfile
|
179
|
-
- gemfiles/activerecord_6_1.gemfile
|
180
|
-
- gemfiles/activerecord_master.gemfile
|
181
161
|
- lib/evil-seed.rb
|
182
162
|
- lib/evil/seed.rb
|
183
163
|
- lib/evil_seed.rb
|
@@ -195,7 +175,7 @@ homepage: https://github.com/palkan/evil-seed
|
|
195
175
|
licenses:
|
196
176
|
- MIT
|
197
177
|
metadata: {}
|
198
|
-
post_install_message:
|
178
|
+
post_install_message:
|
199
179
|
rdoc_options: []
|
200
180
|
require_paths:
|
201
181
|
- lib
|
@@ -210,8 +190,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
210
190
|
- !ruby/object:Gem::Version
|
211
191
|
version: '0'
|
212
192
|
requirements: []
|
213
|
-
rubygems_version: 3.
|
214
|
-
signing_key:
|
193
|
+
rubygems_version: 3.5.11
|
194
|
+
signing_key:
|
215
195
|
specification_version: 4
|
216
196
|
summary: Create partial and anonymized production database dumps for use in development
|
217
197
|
test_files: []
|
data/.travis.yml
DELETED
@@ -1,82 +0,0 @@
|
|
1
|
-
dist: xenial
|
2
|
-
language: ruby
|
3
|
-
cache: bundler
|
4
|
-
sudo: false
|
5
|
-
|
6
|
-
addons:
|
7
|
-
postgresql: "10"
|
8
|
-
apt:
|
9
|
-
sources:
|
10
|
-
- travis-ci/sqlite3
|
11
|
-
packages:
|
12
|
-
- sqlite3
|
13
|
-
|
14
|
-
services:
|
15
|
-
- mysql
|
16
|
-
- postgresql
|
17
|
-
|
18
|
-
before_install: gem install bundler -v '~> 1.17'
|
19
|
-
|
20
|
-
matrix:
|
21
|
-
include:
|
22
|
-
- rvm: 3.0.2
|
23
|
-
gemfile: gemfiles/activerecord_master.gemfile
|
24
|
-
env: "DB=sqlite"
|
25
|
-
- rvm: 3.0.2
|
26
|
-
gemfile: gemfiles/activerecord_master.gemfile
|
27
|
-
env: "DB=postgresql"
|
28
|
-
- rvm: 3.0.2
|
29
|
-
gemfile: gemfiles/activerecord_master.gemfile
|
30
|
-
env: "DB=mysql"
|
31
|
-
- rvm: 2.7.4
|
32
|
-
gemfile: gemfiles/activerecord_6_1.gemfile
|
33
|
-
env: "DB=sqlite"
|
34
|
-
- rvm: 2.7.4
|
35
|
-
gemfile: gemfiles/activerecord_6_1.gemfile
|
36
|
-
env: "DB=postgresql"
|
37
|
-
- rvm: 2.7.4
|
38
|
-
gemfile: gemfiles/activerecord_6_1.gemfile
|
39
|
-
env: "DB=mysql"
|
40
|
-
- rvm: 2.7.4
|
41
|
-
gemfile: gemfiles/activerecord_6_0.gemfile
|
42
|
-
env: "DB=sqlite"
|
43
|
-
- rvm: 2.7.4
|
44
|
-
gemfile: gemfiles/activerecord_6_0.gemfile
|
45
|
-
env: "DB=postgresql"
|
46
|
-
- rvm: 2.7.4
|
47
|
-
gemfile: gemfiles/activerecord_6_0.gemfile
|
48
|
-
env: "DB=mysql"
|
49
|
-
- rvm: 2.6.8
|
50
|
-
gemfile: gemfiles/activerecord_5_2.gemfile
|
51
|
-
env: "DB=sqlite"
|
52
|
-
- rvm: 2.6.8
|
53
|
-
gemfile: gemfiles/activerecord_5_2.gemfile
|
54
|
-
env: "DB=postgresql"
|
55
|
-
- rvm: 2.6.8
|
56
|
-
gemfile: gemfiles/activerecord_5_2.gemfile
|
57
|
-
env: "DB=mysql"
|
58
|
-
- rvm: 2.5.9
|
59
|
-
gemfile: gemfiles/activerecord_5_1.gemfile
|
60
|
-
env: "DB=sqlite"
|
61
|
-
- rvm: 2.5.9
|
62
|
-
gemfile: gemfiles/activerecord_5_1.gemfile
|
63
|
-
env: "DB=postgresql"
|
64
|
-
- rvm: 2.5.9
|
65
|
-
gemfile: gemfiles/activerecord_5_1.gemfile
|
66
|
-
env: "DB=mysql"
|
67
|
-
- rvm: 2.4.10
|
68
|
-
gemfile: gemfiles/activerecord_5_0.gemfile
|
69
|
-
env: "DB=sqlite"
|
70
|
-
- rvm: 2.4.10
|
71
|
-
gemfile: gemfiles/activerecord_5_0.gemfile
|
72
|
-
env: "DB=postgresql"
|
73
|
-
- rvm: 2.4.10
|
74
|
-
gemfile: gemfiles/activerecord_5_0.gemfile
|
75
|
-
env: "DB=mysql"
|
76
|
-
- rvm: 2.3.8
|
77
|
-
gemfile: gemfiles/activerecord_4_2.gemfile
|
78
|
-
env: "DB=postgresql"
|
79
|
-
- rvm: 2.3.8
|
80
|
-
gemfile: gemfiles/activerecord_4_2.gemfile
|
81
|
-
env: "DB=sqlite"
|
82
|
-
# Please note that gem can't be tested against MySQL on ActiveRecord 4.2 (Dump and restore test doesn't work)!
|
data/Appraisals
DELETED
@@ -1,48 +0,0 @@
|
|
1
|
-
appraise 'activerecord-5-0' do
|
2
|
-
gem 'activerecord', '~> 5.0.0'
|
3
|
-
gem "pg", ">= 0.20", "< 2.0"
|
4
|
-
gem 'mysql2', '~> 0.4.4'
|
5
|
-
gem 'sqlite3', '~> 1.3.6'
|
6
|
-
end
|
7
|
-
|
8
|
-
appraise 'activerecord-5-1' do
|
9
|
-
gem 'activerecord', '~> 5.1.0'
|
10
|
-
gem "pg", ">= 0.20", "< 2.0"
|
11
|
-
gem 'mysql2', '~> 0.4.4'
|
12
|
-
gem 'sqlite3', '~> 1.3'
|
13
|
-
end
|
14
|
-
|
15
|
-
appraise 'activerecord-5-2' do
|
16
|
-
gem 'activerecord', '~> 5.2.0'
|
17
|
-
gem "pg", ">= 0.20", "< 2.0"
|
18
|
-
gem 'mysql2', '~> 0.4.4'
|
19
|
-
gem 'sqlite3', '~> 1.3'
|
20
|
-
end
|
21
|
-
|
22
|
-
appraise 'activerecord-6-0' do
|
23
|
-
gem 'activerecord', '~> 6.0.0'
|
24
|
-
gem "pg", ">= 0.20", "< 2.0"
|
25
|
-
gem 'mysql2', '~> 0.4.4'
|
26
|
-
gem 'sqlite3', '~> 1.4'
|
27
|
-
end
|
28
|
-
|
29
|
-
appraise 'activerecord-6-0' do
|
30
|
-
gem 'activerecord', '~> 6.0.0'
|
31
|
-
gem "pg", ">= 0.20", "< 2.0"
|
32
|
-
gem 'mysql2', '~> 0.4.4'
|
33
|
-
gem 'sqlite3', '~> 1.4'
|
34
|
-
end
|
35
|
-
|
36
|
-
appraise 'activerecord-6-1' do
|
37
|
-
gem 'activerecord', '~> 6.1.0'
|
38
|
-
gem "pg", "~> 1.1"
|
39
|
-
gem 'mysql2', '~> 0.5'
|
40
|
-
gem 'sqlite3', '~> 1.4'
|
41
|
-
end
|
42
|
-
|
43
|
-
appraise 'activerecord-master' do
|
44
|
-
gem 'activerecord', git: 'https://github.com/rails/rails.git'
|
45
|
-
gem "pg", "~> 1.1"
|
46
|
-
gem 'mysql2', '~> 0.5'
|
47
|
-
gem 'sqlite3', '~> 1.4'
|
48
|
-
end
|
data/gemfiles/.bundle/config
DELETED