evil-seed 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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