evil-seed 0.5.0 → 0.7.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 +27 -9
- data/CHANGELOG.md +142 -0
- data/Gemfile +7 -1
- data/README.md +40 -2
- data/evil-seed.gemspec +1 -1
- data/lib/evil_seed/configuration/root.rb +119 -12
- data/lib/evil_seed/configuration.rb +7 -3
- data/lib/evil_seed/dumper.rb +2 -1
- data/lib/evil_seed/record_dumper.rb +21 -10
- data/lib/evil_seed/relation_dumper.rb +92 -29
- data/lib/evil_seed/root_dumper.rb +7 -3
- data/lib/evil_seed/version.rb +1 -1
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 757a2cee2b774b29a9d248270642db41faa431fdfd1b738aed0b8ea275fa358a
|
4
|
+
data.tar.gz: 8dc1887a03f1169aa332f9ef345ea666d5582e578ec6c8c97c6041b6f281dff4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aea3be4c8d73fc5ec6e04457d6322306d12a70d44a4792eb1dc855162b28b3b325617815a8a5898733cdc31067e8c51fa3714909c123fe890d54e21123121f7f
|
7
|
+
data.tar.gz: 98ed722479590c6615d87dfa3a0262d7012f008f0c9be2c4ddb3066a01ef2959febaf3ecea60c25e362417e9eba986f7ada50fad9ce06355fb8b5dfbc75e664c
|
@@ -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
|
data/.github/workflows/test.yml
CHANGED
@@ -19,30 +19,48 @@ jobs:
|
|
19
19
|
- ruby: "head"
|
20
20
|
activerecord: "head"
|
21
21
|
database: sqlite
|
22
|
+
- ruby: "3.4"
|
23
|
+
activerecord: "8.0"
|
24
|
+
database: postgresql
|
25
|
+
- ruby: "3.4"
|
26
|
+
activerecord: "8.0"
|
27
|
+
database: mysql
|
28
|
+
- ruby: "3.4"
|
29
|
+
activerecord: "8.0"
|
30
|
+
database: sqlite
|
31
|
+
- ruby: "3.3"
|
32
|
+
activerecord: "7.2"
|
33
|
+
database: postgresql
|
34
|
+
- ruby: "3.3"
|
35
|
+
activerecord: "7.2"
|
36
|
+
database: mysql
|
37
|
+
- ruby: "3.3"
|
38
|
+
activerecord: "7.2"
|
39
|
+
database: sqlite
|
22
40
|
- ruby: "3.2"
|
23
|
-
activerecord: "7.
|
41
|
+
activerecord: "7.1"
|
24
42
|
database: postgresql
|
25
43
|
- ruby: "3.2"
|
26
|
-
activerecord: "7.
|
44
|
+
activerecord: "7.1"
|
27
45
|
database: mysql
|
28
46
|
- ruby: "3.2"
|
29
|
-
activerecord: "7.
|
47
|
+
activerecord: "7.1"
|
30
48
|
database: sqlite
|
31
49
|
- ruby: "3.1"
|
32
|
-
activerecord: "
|
50
|
+
activerecord: "7.0"
|
33
51
|
database: sqlite
|
34
52
|
- ruby: "3.0"
|
35
|
-
activerecord: "6.
|
53
|
+
activerecord: "6.1"
|
36
54
|
database: sqlite
|
37
55
|
- ruby: "2.7"
|
38
|
-
activerecord: "
|
56
|
+
activerecord: "6.0"
|
39
57
|
database: sqlite
|
40
58
|
|
41
59
|
runs-on: ubuntu-latest
|
42
60
|
|
43
61
|
services:
|
44
62
|
postgres:
|
45
|
-
image: postgres:
|
63
|
+
image: ${{ (matrix.database == 'postgresql') && 'postgres:17' || '' }}
|
46
64
|
env:
|
47
65
|
POSTGRES_USER: postgres
|
48
66
|
POSTGRES_PASSWORD: postgres
|
@@ -54,7 +72,7 @@ jobs:
|
|
54
72
|
--health-timeout 5s
|
55
73
|
--health-retries 5
|
56
74
|
mysql:
|
57
|
-
image: mysql:
|
75
|
+
image: ${{ (matrix.database == 'mysql') && 'mysql:9' || '' }}
|
58
76
|
env:
|
59
77
|
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
60
78
|
MYSQL_DATABASE: evil_seed_test
|
@@ -70,7 +88,7 @@ jobs:
|
|
70
88
|
POSTGRES_PASSWORD: postgres
|
71
89
|
|
72
90
|
steps:
|
73
|
-
- uses: actions/checkout@
|
91
|
+
- uses: actions/checkout@v4
|
74
92
|
- uses: ruby/setup-ruby@v1
|
75
93
|
with:
|
76
94
|
ruby-version: ${{ matrix.ruby }}
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,142 @@
|
|
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.7.0] - 2025-02-07
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- Options to exclude all `has_many` and `has_one` or optional `belongs_to` associations by default. [@Envek]
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
root.exclude_has_relations
|
18
|
+
root.exclude_optional_belongs_to
|
19
|
+
```
|
20
|
+
|
21
|
+
Excluded associations can be re-included by `include` with matching pattern.
|
22
|
+
|
23
|
+
- Exclusion and inclusion patterns can be specified as hashes and/or arrays. [@Envek]
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
config.root('Forum', featured: true) do |forum|
|
27
|
+
forum.include(parent: {questions: %i[answers votes]})
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
Which is equivalent to:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
config.root('Forum', featured: true) do |forum|
|
35
|
+
forum.include(/\Aforum(\.parent(\.questions(\.answers))?)?)?\z/)
|
36
|
+
forum.include(/\Aforum(\.parent(\.questions(\.votes))?)?)?\z/)
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
- Association limits also can be specified as hashes and/or arrays. [@Envek]
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
config.root('Forum', featured: true) do |forum|
|
44
|
+
forum.limit_associations_size(15, questions: %i[answers votes])
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
Which is equivalent to:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
config.root('Forum', featured: true) do |forum|
|
52
|
+
forum.limit_associations_size(15, 'forum.questions.answers')
|
53
|
+
forum.limit_associations_size(15, 'forum.questions.votes')
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
|
58
|
+
- Print reason of association exclusion or inclusion in verbose mode. [@Envek]
|
59
|
+
|
60
|
+
- Allow to apply custom scoping to included associations. [@Envek]
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
config.root('Forum', featured: true) do |forum|
|
64
|
+
forum.include('questions.answers') do
|
65
|
+
order(created_at: :desc)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
### Fixed
|
71
|
+
|
72
|
+
- Error when dumping single-column tables. [@lsantobuono]
|
73
|
+
- Bug with null foreign key to back to auxiliary `has_one` association with not matching names. E.g. user has many profiles and has one default profile, profile belongs to user.
|
74
|
+
- Ignored columns handling.
|
75
|
+
- Dump relations for records that were dumped earlier with relations excluded.
|
76
|
+
|
77
|
+
## [0.6.0] - 2024-06-18
|
78
|
+
|
79
|
+
### Added
|
80
|
+
|
81
|
+
- Association inclusion option. [@gazay] ([#13](https://github.com/evilmartians/evil-seed/pull/13))
|
82
|
+
- Option to limit association depth. [@gazay] ([#13](https://github.com/evilmartians/evil-seed/pull/13))
|
83
|
+
- Option to ignore `default_scope` in models. [@gazay] ([#13](https://github.com/evilmartians/evil-seed/pull/13))
|
84
|
+
- Option to disable nullifying of foreign keys. [@gazay] ([#13](https://github.com/evilmartians/evil-seed/pull/13))
|
85
|
+
|
86
|
+
## [0.5.0] - 2023-02-16
|
87
|
+
|
88
|
+
### Added
|
89
|
+
|
90
|
+
- Option to ignore columns from a given model. [@nhocki] ([#17](https://github.com/evilmartians/evil-seed/pull/17))
|
91
|
+
|
92
|
+
## [0.4.0] - 2022-12-07
|
93
|
+
|
94
|
+
### Fixed
|
95
|
+
|
96
|
+
- Ignore generated database columns. [@cmer] ([#16](https://github.com/evilmartians/evil-seed/pull/16))
|
97
|
+
|
98
|
+
## [0.3.0] - 2022-03-14
|
99
|
+
|
100
|
+
### Added
|
101
|
+
|
102
|
+
- Passing attribute value to anonymizer block (to partially modify it). [@Envek]
|
103
|
+
|
104
|
+
## [0.2.0] - 2022-03-10
|
105
|
+
|
106
|
+
### Fixed
|
107
|
+
|
108
|
+
- Ignore virtual ActiveRecord attributes. [@Envek]
|
109
|
+
|
110
|
+
### Removed
|
111
|
+
|
112
|
+
- Support for ActiveRecord 4.2
|
113
|
+
|
114
|
+
## [0.1.3] - 2021-09-02
|
115
|
+
|
116
|
+
### Fixed
|
117
|
+
|
118
|
+
- Compatibility with Ruby 3.0 and ActiveRecord 6.x.
|
119
|
+
|
120
|
+
## [0.1.2] - 2018-03-27
|
121
|
+
|
122
|
+
### Fixed
|
123
|
+
|
124
|
+
- Bug with unwanted pseudo columns in dump when dumping HABTM join table without one side.
|
125
|
+
|
126
|
+
## [0.1.1] - 2017-05-15
|
127
|
+
|
128
|
+
### Fixed
|
129
|
+
|
130
|
+
- ActiveRecord 4.2 support by backporting of `ActiveRecord::Relation#in_batches`
|
131
|
+
- Dumping of the whole model without constraints
|
132
|
+
|
133
|
+
## [0.1.0] - 2017-05-09
|
134
|
+
|
135
|
+
Initial release. [@palkan], [@Envek]
|
136
|
+
|
137
|
+
[@Envek]: https://github.com/Envek "Andrey Novikov"
|
138
|
+
[@palkan]: https://github.com/palkan "Vladimir Dementyev"
|
139
|
+
[@cmer]: https://github.com/cmer "Carl Mercier"
|
140
|
+
[@nhocki]: https://github.com/nhocki "Nicolás Hock-Isaza"
|
141
|
+
[@gazay]: https://github.com/gazay "Alex Gaziev"
|
142
|
+
[@lsantobuono]: https://github.com/lsantobuono ""
|
data/Gemfile
CHANGED
@@ -5,7 +5,7 @@ source 'https://rubygems.org'
|
|
5
5
|
# Specify your gem's dependencies in evil-seed.gemspec
|
6
6
|
gemspec
|
7
7
|
|
8
|
-
activerecord_version = ENV.fetch("ACTIVERECORD_VERSION", "~>
|
8
|
+
activerecord_version = ENV.fetch("ACTIVERECORD_VERSION", "~> 8.0")
|
9
9
|
case activerecord_version.upcase
|
10
10
|
when "HEAD"
|
11
11
|
git "https://github.com/rails/rails.git" do
|
@@ -15,4 +15,10 @@ when "HEAD"
|
|
15
15
|
else
|
16
16
|
activerecord_version = "~> #{activerecord_version}.0" if activerecord_version.match?(/^\d+\.\d+$/)
|
17
17
|
gem "activerecord", activerecord_version
|
18
|
+
if Gem::Version.new("7.2") > Gem::Version.new(activerecord_version.scan(/\d+\.\d+/).first)
|
19
|
+
gem "sqlite3", "~> 1.4"
|
20
|
+
gem "concurrent-ruby", "< 1.3.5"
|
21
|
+
end
|
18
22
|
end
|
23
|
+
|
24
|
+
gem "debug"
|
data/README.md
CHANGED
@@ -48,18 +48,42 @@ EvilSeed.configure do |config|
|
|
48
48
|
# First, you should specify +root models+ and their +constraints+ to limit the number of dumped records:
|
49
49
|
# This is like Forum.where(featured: true).all
|
50
50
|
config.root('Forum', featured: true) do |root|
|
51
|
+
# You can limit number of records to be dumped
|
52
|
+
root.limit(100)
|
53
|
+
# Specify order for records to be selected for dump
|
54
|
+
root.order(created_at: :desc)
|
55
|
+
|
51
56
|
# It's possible to remove some associations from dumping with pattern of association path to exclude
|
52
57
|
#
|
53
58
|
# Association path is a dot-delimited string of association chain starting from model itself:
|
54
59
|
# example: "forum.users.questions"
|
55
|
-
root.exclude(/\btracking_pixels\b/, 'forum.popular_questions')
|
60
|
+
root.exclude(/\btracking_pixels\b/, 'forum.popular_questions', /\Aforum\.parent\b/)
|
61
|
+
|
62
|
+
# Include back only certain association chains
|
63
|
+
root.include(parent: {questions: %i[answers votes]})
|
64
|
+
# which is the same as
|
65
|
+
root.include(/\Aforum(\.parent(\.questions(\.(answers|votes))?)?)?\z/)
|
66
|
+
|
67
|
+
# You can also specify custom scoping for associations
|
68
|
+
root.include(questions: { answers: :reactions }) do
|
69
|
+
order(created_at: :desc) # Any ActiveRecord query method is allowed
|
70
|
+
end
|
56
71
|
|
57
72
|
# It's possible to limit the number of included into dump has_many and has_one records for every association
|
58
73
|
# Note that belongs_to records for all not excluded associations are always dumped to keep referential integrity.
|
59
74
|
root.limit_associations_size(100)
|
60
75
|
|
61
76
|
# Or for certain association only
|
62
|
-
root.limit_associations_size(
|
77
|
+
root.limit_associations_size(5, 'forum.questions')
|
78
|
+
root.limit_associations_size(15, 'forum.questions.answers')
|
79
|
+
# or
|
80
|
+
root.limit_associations_size(5, :questions)
|
81
|
+
root.limit_associations_size(15, questions: :answers)
|
82
|
+
|
83
|
+
# Limit the depth of associations to be dumped from the root level
|
84
|
+
# All traverses through has_many, belongs_to, etc are counted
|
85
|
+
# So forum.subforums.subforums.questions.answers will be 5 levels deep
|
86
|
+
root.limit_deep(10)
|
63
87
|
end
|
64
88
|
|
65
89
|
# Everything you can pass to +where+ method will work as constraints:
|
@@ -95,6 +119,20 @@ EvilSeed.configure do |config|
|
|
95
119
|
# This will remove the columns even if the model is not a root node and is
|
96
120
|
# dumped via an association.
|
97
121
|
config.ignore_columns("Profile", :name)
|
122
|
+
|
123
|
+
# Disable foreign key nullification for records that are not included in the dump
|
124
|
+
# By default, EvilSeed will nullify foreign keys for records that are not included in the dump
|
125
|
+
config.dont_nullify = true
|
126
|
+
|
127
|
+
# Unscope relations to include soft-deleted records etc
|
128
|
+
# This is useful when you want to include all records, including those that are hidden by default
|
129
|
+
# By default, EvilSeed will abide default scope of models
|
130
|
+
config.unscoped = true
|
131
|
+
|
132
|
+
# Verbose mode will print out the progress of the dump to the console along with writing the file
|
133
|
+
# By default, verbose mode is off
|
134
|
+
config.verbose = true
|
135
|
+
config.verbose_sql = true
|
98
136
|
end
|
99
137
|
```
|
100
138
|
|
data/evil-seed.gemspec
CHANGED
@@ -29,7 +29,7 @@ Gem::Specification.new do |spec|
|
|
29
29
|
|
30
30
|
spec.add_dependency 'activerecord', '>= 5.0'
|
31
31
|
|
32
|
-
spec.add_development_dependency 'rake', '~>
|
32
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
33
33
|
spec.add_development_dependency 'minitest', '~> 5.0'
|
34
34
|
spec.add_development_dependency 'pg', '>= 0.20'
|
35
35
|
spec.add_development_dependency 'mysql2'
|
@@ -4,38 +4,145 @@ module EvilSeed
|
|
4
4
|
class Configuration
|
5
5
|
# Configuration for dumping some root model and its associations
|
6
6
|
class Root
|
7
|
-
attr_reader :model, :constraints
|
8
|
-
attr_reader :total_limit, :association_limits
|
9
|
-
attr_reader :exclusions
|
7
|
+
attr_reader :model, :constraints, :limit, :order
|
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
|
21
24
|
# @param association_patterns Array<String, Regex> Patterns to exclude associated models from dump
|
22
25
|
def exclude(*association_patterns)
|
23
|
-
|
26
|
+
association_patterns.each do |pattern|
|
27
|
+
case pattern
|
28
|
+
when String, Regexp
|
29
|
+
@exclusions << pattern
|
30
|
+
else
|
31
|
+
path_prefix = model.constantize.model_name.singular
|
32
|
+
@exclusions += compile_patterns(pattern, prefix: path_prefix).map { |p| Regexp.new(/\A#{p}\z/) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Include some excluded associations back to the dump
|
38
|
+
# @param association_patterns Array<String, Regex> Patterns to exclude associated models from dump
|
39
|
+
def include(*association_patterns, &block)
|
40
|
+
association_patterns.each do |pattern|
|
41
|
+
case pattern
|
42
|
+
when String, Regexp
|
43
|
+
@inclusions[pattern] = block
|
44
|
+
else
|
45
|
+
path_prefix = model.constantize.model_name.singular
|
46
|
+
compile_patterns(pattern, prefix: path_prefix).map do |p|
|
47
|
+
@inclusions[Regexp.new(/\A#{p}\z/)] = block
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def exclude_has_relations
|
54
|
+
@excluded_has_relations = :exclude_has_relations
|
55
|
+
end
|
56
|
+
|
57
|
+
def exclude_optional_belongs_to
|
58
|
+
@excluded_optional_belongs_to = :exclude_optional_belongs_to
|
59
|
+
end
|
60
|
+
|
61
|
+
def limit(limit = nil)
|
62
|
+
return @limit if limit.nil?
|
63
|
+
|
64
|
+
@limit = limit
|
65
|
+
end
|
66
|
+
|
67
|
+
def order(order = nil)
|
68
|
+
return @order if order.nil?
|
69
|
+
|
70
|
+
@order = order
|
24
71
|
end
|
25
72
|
|
26
73
|
# Limit number of records in all (if pattern is not provided) or given associations to include into dump
|
27
74
|
# @param limit [Integer] Maximum number of records in associations to include into dump
|
28
|
-
# @param association_pattern
|
29
|
-
def limit_associations_size(limit,
|
30
|
-
if
|
31
|
-
|
32
|
-
|
33
|
-
|
75
|
+
# @param association_pattern Array<String, Regex, Hash> Pattern to limit number of records for certain associated models
|
76
|
+
def limit_associations_size(limit, *association_patterns)
|
77
|
+
return @total_limit = limit if association_patterns.empty?
|
78
|
+
|
79
|
+
association_patterns.each do |pattern|
|
80
|
+
case pattern
|
81
|
+
when String, Regexp
|
82
|
+
@association_limits[pattern] = limit
|
83
|
+
else
|
84
|
+
path_prefix = model.constantize.model_name.singular
|
85
|
+
compile_patterns(pattern, prefix: path_prefix, partial: false).map do |p|
|
86
|
+
@association_limits[Regexp.new(/\A#{p}\z/)] = limit
|
87
|
+
end
|
88
|
+
end
|
34
89
|
end
|
35
90
|
end
|
36
91
|
|
92
|
+
# Limit deepenes of associations to include into dump
|
93
|
+
# @param limit [Integer] Maximum level to recursively dive into associations
|
94
|
+
def limit_deep(limit)
|
95
|
+
@deep_limit = limit
|
96
|
+
end
|
97
|
+
|
98
|
+
def do_not_nullify(nullify_flag)
|
99
|
+
@dont_nullify = nullify_flag
|
100
|
+
end
|
101
|
+
|
37
102
|
def excluded?(association_path)
|
38
|
-
exclusions.
|
103
|
+
exclusions.find { |exclusion| association_path.match(exclusion) } #.match(association_path) }
|
104
|
+
end
|
105
|
+
|
106
|
+
def included?(association_path)
|
107
|
+
inclusions.find { |inclusion, _block| association_path.match(inclusion) }
|
108
|
+
end
|
109
|
+
|
110
|
+
def excluded_has_relations?
|
111
|
+
@excluded_has_relations
|
112
|
+
end
|
113
|
+
|
114
|
+
def excluded_optional_belongs_to?
|
115
|
+
@excluded_optional_belongs_to
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def compile_patterns(pattern, prefix: "", partial: true)
|
121
|
+
wrap = -> (p) { partial ? "(?:\\.#{p})?" : "\\.#{p}" }
|
122
|
+
case pattern
|
123
|
+
when String, Symbol
|
124
|
+
["#{prefix}#{wrap.(pattern.to_s)}"]
|
125
|
+
when Regexp
|
126
|
+
["#{prefix}#{wrap.("(?:#{pattern.source})")}"]
|
127
|
+
when Array
|
128
|
+
pattern.map { |p| compile_patterns(p, prefix: prefix, partial: partial) }.flatten
|
129
|
+
when Hash
|
130
|
+
pattern.map do |k, v|
|
131
|
+
next nil unless v
|
132
|
+
subpatterns = compile_patterns(v, partial: partial)
|
133
|
+
next "#{prefix}#{wrap.(k)}" if subpatterns.empty?
|
134
|
+
|
135
|
+
subpatterns.map do |p|
|
136
|
+
"#{prefix}#{wrap.("#{k}#{p}")}"
|
137
|
+
end
|
138
|
+
end.compact.flatten
|
139
|
+
when false, nil
|
140
|
+
nil
|
141
|
+
when true
|
142
|
+
[prefix]
|
143
|
+
else
|
144
|
+
raise ArgumentError, "Unknown pattern type: #{pattern.class} for #{pattern.inspect}"
|
145
|
+
end
|
39
146
|
end
|
40
147
|
end
|
41
148
|
end
|
@@ -7,10 +7,14 @@ 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
|
14
18
|
@ignored_columns = Hash.new { |h, k| h[k] = [] }
|
15
19
|
end
|
16
20
|
|
@@ -19,7 +23,7 @@ module EvilSeed
|
|
19
23
|
end
|
20
24
|
|
21
25
|
def root(model, *constraints)
|
22
|
-
new_root = Root.new(model, *constraints)
|
26
|
+
new_root = Root.new(model, dont_nullify, *constraints)
|
23
27
|
yield new_root if block_given?
|
24
28
|
roots << new_root
|
25
29
|
end
|
@@ -35,7 +39,7 @@ module EvilSeed
|
|
35
39
|
end
|
36
40
|
|
37
41
|
def ignore_columns(model_class, *columns)
|
38
|
-
@ignored_columns[model_class] += columns
|
42
|
+
@ignored_columns[model_class.to_s] += columns.map(&:to_s)
|
39
43
|
end
|
40
44
|
|
41
45
|
# Customizer objects for every model
|
data/lib/evil_seed/dumper.rb
CHANGED
@@ -7,7 +7,7 @@ module EvilSeed
|
|
7
7
|
# This class initiates dump creation for every root model of configuration
|
8
8
|
# and then concatenates dumps from all roots into one single IO.
|
9
9
|
class Dumper
|
10
|
-
attr_reader :configuration, :loaded_map
|
10
|
+
attr_reader :configuration, :loaded_map, :to_load_map
|
11
11
|
|
12
12
|
# @param configuration [Configuration]
|
13
13
|
def initialize(configuration)
|
@@ -18,6 +18,7 @@ module EvilSeed
|
|
18
18
|
# @param output [IO] Stream to write SQL dump into
|
19
19
|
def call(output)
|
20
20
|
@loaded_map = Hash.new { |h, k| h[k] = Set.new } # stores primary keys of already dumped records for every table
|
21
|
+
@to_load_map = Hash.new { |h, k| h[k] = Set.new } # stores primary keys of records we're going to dump to avoid cycles
|
21
22
|
@output = output
|
22
23
|
configuration.roots.each do |root|
|
23
24
|
table_outputs = RootDumper.new(root, self).call
|
@@ -9,7 +9,7 @@ module EvilSeed
|
|
9
9
|
|
10
10
|
attr_reader :model_class, :configuration, :relation_dumper
|
11
11
|
|
12
|
-
delegate :loaded_map, to: :relation_dumper
|
12
|
+
delegate :loaded_map, :to_load_map, to: :relation_dumper
|
13
13
|
|
14
14
|
def initialize(model_class, configuration, relation_dumper)
|
15
15
|
@model_class = model_class
|
@@ -38,8 +38,10 @@ module EvilSeed
|
|
38
38
|
|
39
39
|
def loaded!(attributes)
|
40
40
|
id = model_class.primary_key && attributes[model_class.primary_key] || attributes
|
41
|
-
|
42
|
-
|
41
|
+
!loaded_map[model_class.table_name].include?(id).tap do
|
42
|
+
loaded_map[model_class.table_name] << id
|
43
|
+
to_load_map[model_class.table_name].delete(id)
|
44
|
+
end
|
43
45
|
end
|
44
46
|
|
45
47
|
def transform_and_anonymize(attributes)
|
@@ -51,9 +53,10 @@ module EvilSeed
|
|
51
53
|
end
|
52
54
|
|
53
55
|
def insertable_column_names
|
54
|
-
|
55
|
-
|
56
|
-
|
56
|
+
@insertable_column_names ||=
|
57
|
+
model_class.columns_hash.reject do |_k, v|
|
58
|
+
v.respond_to?(:virtual?) ? v.virtual? : false
|
59
|
+
end.keys - configuration.ignored_columns_for(model_class.to_s)
|
57
60
|
end
|
58
61
|
|
59
62
|
def insert_statement
|
@@ -65,26 +68,34 @@ module EvilSeed
|
|
65
68
|
|
66
69
|
def write!(attributes)
|
67
70
|
# Remove non-insertable columns from attributes
|
68
|
-
attributes = attributes.slice(*insertable_column_names)
|
71
|
+
attributes = prepare(attributes.slice(*insertable_column_names))
|
72
|
+
|
73
|
+
if configuration.verbose_sql
|
74
|
+
puts("-- #{relation_dumper.association_path}\n")
|
75
|
+
puts(@tuples_written.zero? ? insert_statement : ",\n")
|
76
|
+
puts(" (#{attributes.join(', ')})")
|
77
|
+
end
|
69
78
|
|
70
79
|
@output.write("-- #{relation_dumper.association_path}\n") && @header_written = true unless @header_written
|
71
80
|
@output.write(@tuples_written.zero? ? insert_statement : ",\n")
|
72
|
-
@output.write(" (#{
|
81
|
+
@output.write(" (#{attributes.join(', ')})")
|
73
82
|
@tuples_written += 1
|
74
83
|
@output.write(";\n") && @tuples_written = 0 if @tuples_written == MAX_TUPLES_PER_INSERT_STMT
|
75
84
|
end
|
76
85
|
|
77
86
|
def finalize!
|
78
|
-
return
|
87
|
+
return true if @finalized
|
88
|
+
return false unless @header_written && @tuples_written > 0
|
79
89
|
@output.write(";\n\n")
|
80
90
|
@tuples_written = 0
|
91
|
+
@finalized = true
|
81
92
|
end
|
82
93
|
|
83
94
|
def prepare(attributes)
|
84
95
|
attributes.map do |key, value|
|
85
96
|
type = model_class.attribute_types[key]
|
86
97
|
model_class.connection.quote(type.serialize(value))
|
87
|
-
end
|
98
|
+
end.flatten.compact
|
88
99
|
end
|
89
100
|
end
|
90
101
|
end
|
@@ -24,37 +24,51 @@ module EvilSeed
|
|
24
24
|
MAX_IDENTIFIERS_IN_IN_STMT = 1_000
|
25
25
|
|
26
26
|
attr_reader :relation, :root_dumper, :model_class, :association_path, :search_key, :identifiers, :nullify_columns,
|
27
|
-
:belongs_to_reflections, :has_many_reflections, :foreign_keys, :loaded_ids, :
|
28
|
-
:record_dumper, :inverse_reflection, :table_names, :options
|
27
|
+
:belongs_to_reflections, :has_many_reflections, :foreign_keys, :loaded_ids, :local_load_map,
|
28
|
+
:records, :record_dumper, :inverse_reflection, :table_names, :options,
|
29
|
+
:current_deep, :verbose, :custom_scope
|
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_load_map, to: :root_dumper
|
31
32
|
|
32
33
|
def initialize(relation, root_dumper, association_path, **options)
|
34
|
+
puts("- #{association_path}") if root_dumper.configuration.verbose
|
35
|
+
|
33
36
|
@relation = relation
|
34
37
|
@root_dumper = root_dumper
|
38
|
+
@verbose = configuration.verbose
|
35
39
|
@identifiers = options[:identifiers]
|
36
|
-
@
|
40
|
+
@local_load_map = Hash.new { |h, k| h[k] = [] }
|
37
41
|
@foreign_keys = Hash.new { |h, k| h[k] = [] }
|
38
42
|
@loaded_ids = []
|
39
43
|
@model_class = relation.klass
|
40
44
|
@search_key = options[:search_key] || model_class.primary_key
|
41
45
|
@association_path = association_path
|
42
46
|
@inverse_reflection = options[:inverse_of]
|
47
|
+
@records = []
|
43
48
|
@record_dumper = configuration.record_dumper_class.new(model_class, configuration, self)
|
44
49
|
@nullify_columns = []
|
45
50
|
@table_names = {}
|
46
51
|
@belongs_to_reflections = setup_belongs_to_reflections
|
47
52
|
@has_many_reflections = setup_has_many_reflections
|
48
53
|
@options = options
|
54
|
+
@current_deep = association_path.split('.').size
|
55
|
+
@dont_nullify = dont_nullify
|
56
|
+
@custom_scope = options[:custom_scope]
|
49
57
|
end
|
50
58
|
|
51
59
|
# Generate dump and write it into +io+
|
52
60
|
# @return [Array<IO>] List of dump IOs for separate tables in order of dependencies (belongs_to are first)
|
53
61
|
def call
|
54
62
|
dump!
|
55
|
-
|
56
|
-
|
57
|
-
|
63
|
+
if deep_limit and current_deep > deep_limit
|
64
|
+
[dump_records!].flatten.compact
|
65
|
+
else
|
66
|
+
[
|
67
|
+
dump_belongs_to_associations!,
|
68
|
+
dump_records!,
|
69
|
+
dump_has_many_associations!,
|
70
|
+
].flatten.compact
|
71
|
+
end
|
58
72
|
end
|
59
73
|
|
60
74
|
private
|
@@ -63,17 +77,31 @@ module EvilSeed
|
|
63
77
|
original_ignored_columns = model_class.ignored_columns
|
64
78
|
model_class.ignored_columns += Array(configuration.ignored_columns_for(model_class.sti_name))
|
65
79
|
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
|
66
|
-
if
|
80
|
+
if custom_scope
|
81
|
+
puts(" # #{search_key} (with scope)") if verbose
|
82
|
+
attrs = fetch_attributes(relation)
|
83
|
+
puts(" -- dumped #{attrs.size}") if verbose
|
84
|
+
attrs.each do |attributes|
|
85
|
+
next unless check_limits!
|
86
|
+
dump_record!(attributes)
|
87
|
+
end
|
88
|
+
elsif identifiers.present?
|
89
|
+
puts(" # #{search_key} => #{identifiers}") if verbose
|
67
90
|
# 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
|
68
91
|
identifiers.in_groups_of(MAX_IDENTIFIERS_IN_IN_STMT).each do |ids|
|
69
|
-
fetch_attributes(relation.where(search_key => ids.compact))
|
92
|
+
attrs = fetch_attributes(relation.where(search_key => ids.compact))
|
93
|
+
puts(" -- dumped #{attrs.size}") if verbose
|
94
|
+
attrs.each do |attributes|
|
70
95
|
next unless check_limits!
|
71
96
|
dump_record!(attributes)
|
72
97
|
end
|
73
98
|
end
|
74
99
|
else
|
100
|
+
puts(" # #{relation.count}") if verbose
|
75
101
|
relation.in_batches do |relation|
|
76
|
-
fetch_attributes(relation)
|
102
|
+
attrs = fetch_attributes(relation)
|
103
|
+
puts(" -- dumped #{attrs.size}") if verbose
|
104
|
+
attrs.each do |attributes|
|
77
105
|
next unless check_limits!
|
78
106
|
dump_record!(attributes)
|
79
107
|
end
|
@@ -84,43 +112,54 @@ module EvilSeed
|
|
84
112
|
end
|
85
113
|
|
86
114
|
def dump_record!(attributes)
|
87
|
-
|
88
|
-
|
115
|
+
unless dont_nullify
|
116
|
+
nullify_columns.each do |nullify_column|
|
117
|
+
attributes[nullify_column] = nil
|
118
|
+
end
|
89
119
|
end
|
90
|
-
|
120
|
+
records << attributes
|
91
121
|
foreign_keys.each do |reflection_name, fk_column|
|
92
122
|
foreign_key = attributes[fk_column]
|
93
|
-
next if foreign_key.nil? || loaded_map[table_names[reflection_name]].include?(foreign_key)
|
94
|
-
|
123
|
+
next if foreign_key.nil? || loaded_map[table_names[reflection_name]].include?(foreign_key) || to_load_map[table_names[reflection_name]].include?(foreign_key)
|
124
|
+
local_load_map[reflection_name] << foreign_key
|
125
|
+
to_load_map[table_names[reflection_name]] << foreign_key
|
95
126
|
end
|
96
127
|
loaded_ids << attributes[model_class.primary_key]
|
97
128
|
end
|
98
129
|
|
130
|
+
def dump_records!
|
131
|
+
records.each do |attributes|
|
132
|
+
record_dumper.call(attributes)
|
133
|
+
end
|
134
|
+
record_dumper.result
|
135
|
+
end
|
136
|
+
|
99
137
|
def dump_belongs_to_associations!
|
100
138
|
belongs_to_reflections.map do |reflection|
|
101
|
-
next if
|
139
|
+
next if local_load_map[reflection.name].empty?
|
102
140
|
RelationDumper.new(
|
103
141
|
build_relation(reflection),
|
104
142
|
root_dumper,
|
105
143
|
"#{association_path}.#{reflection.name}",
|
106
144
|
search_key: reflection.association_primary_key,
|
107
|
-
identifiers:
|
145
|
+
identifiers: local_load_map[reflection.name],
|
108
146
|
limitable: false,
|
109
147
|
).call
|
110
148
|
end
|
111
149
|
end
|
112
150
|
|
113
151
|
def dump_has_many_associations!
|
114
|
-
has_many_reflections.map do |reflection|
|
152
|
+
has_many_reflections.map do |reflection, custom_scope|
|
115
153
|
next if loaded_ids.empty? || total_limit.try(:zero?)
|
116
154
|
RelationDumper.new(
|
117
|
-
build_relation(reflection),
|
155
|
+
build_relation(reflection, custom_scope),
|
118
156
|
root_dumper,
|
119
157
|
"#{association_path}.#{reflection.name}",
|
120
158
|
search_key: reflection.foreign_key,
|
121
|
-
identifiers: loaded_ids,
|
159
|
+
identifiers: loaded_ids - local_load_map[reflection.name],
|
122
160
|
inverse_of: reflection.inverse_of.try(:name),
|
123
161
|
limitable: true,
|
162
|
+
custom_scope: custom_scope,
|
124
163
|
).call
|
125
164
|
end
|
126
165
|
end
|
@@ -130,7 +169,7 @@ module EvilSeed
|
|
130
169
|
# @return [Array<Hash{String => String, Integer, Float, Boolean, nil}>]
|
131
170
|
def fetch_attributes(relation)
|
132
171
|
relation.pluck(*model_class.column_names).map do |row|
|
133
|
-
Hash[model_class.column_names.zip(row)]
|
172
|
+
Hash[model_class.column_names.zip(Array(row))]
|
134
173
|
end
|
135
174
|
end
|
136
175
|
|
@@ -139,9 +178,14 @@ module EvilSeed
|
|
139
178
|
root_dumper.check_limits!(association_path)
|
140
179
|
end
|
141
180
|
|
142
|
-
def build_relation(reflection)
|
143
|
-
|
181
|
+
def build_relation(reflection, custom_scope = nil)
|
182
|
+
if configuration.unscoped
|
183
|
+
relation = reflection.klass.unscoped
|
184
|
+
else
|
185
|
+
relation = reflection.klass.all
|
186
|
+
end
|
144
187
|
relation = relation.instance_eval(&reflection.scope) if reflection.scope
|
188
|
+
relation = relation.instance_eval(&custom_scope) if custom_scope
|
145
189
|
relation = relation.where(reflection.type => model_class.to_s) if reflection.options[:as] # polymorphic
|
146
190
|
relation
|
147
191
|
end
|
@@ -149,24 +193,43 @@ module EvilSeed
|
|
149
193
|
def setup_belongs_to_reflections
|
150
194
|
model_class.reflect_on_all_associations(:belongs_to).reject do |reflection|
|
151
195
|
next false if reflection.options[:polymorphic] # TODO: Add support for polymorphic belongs_to
|
152
|
-
|
153
|
-
|
154
|
-
|
196
|
+
included = root.included?("#{association_path}.#{reflection.name}")
|
197
|
+
excluded = reflection.options[:optional] && root.excluded_optional_belongs_to?
|
198
|
+
excluded ||= root.excluded?("#{association_path}.#{reflection.name}")
|
199
|
+
inverse = reflection.name == inverse_reflection
|
200
|
+
puts " -- belongs_to #{reflection.name} #{"excluded by #{excluded}" if excluded} #{"re-included by #{included}" if included}" if verbose
|
201
|
+
if excluded and not included
|
202
|
+
if model_class.column_names.include?(reflection.foreign_key)
|
203
|
+
puts(" -- excluded #{reflection.foreign_key}") if verbose
|
204
|
+
nullify_columns << reflection.foreign_key
|
205
|
+
end
|
155
206
|
else
|
156
207
|
foreign_keys[reflection.name] = reflection.foreign_key
|
157
208
|
table_names[reflection.name] = reflection.table_name
|
158
209
|
end
|
159
|
-
excluded
|
210
|
+
excluded and not included or inverse
|
160
211
|
end
|
161
212
|
end
|
162
213
|
|
163
214
|
# This method returns only direct has_one and has_many reflections. For HABTM it returns intermediate has_many
|
164
215
|
def setup_has_many_reflections
|
216
|
+
puts(" -- reflections #{model_class._reflections.keys}") if verbose
|
165
217
|
model_class._reflections.select do |_reflection_name, reflection|
|
218
|
+
next false unless %i[has_one has_many].include?(reflection.macro)
|
219
|
+
|
166
220
|
next false if model_class.primary_key.nil?
|
221
|
+
|
167
222
|
next false if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
|
168
|
-
|
169
|
-
|
223
|
+
|
224
|
+
included = root.included?("#{association_path}.#{reflection.name}")
|
225
|
+
excluded = :inverse if reflection.name == inverse_reflection
|
226
|
+
excluded ||= root.excluded_has_relations?
|
227
|
+
excluded ||= root.excluded?("#{association_path}.#{reflection.name}")
|
228
|
+
puts " -- #{reflection.macro} #{reflection.name} #{"excluded by #{excluded}" if excluded} #{"re-included by #{included}" if included}" if verbose
|
229
|
+
!(excluded and not included)
|
230
|
+
end.map do |_reflection_name, reflection|
|
231
|
+
[reflection, root.included?("#{association_path}.#{reflection.name}")&.last]
|
232
|
+
end
|
170
233
|
end
|
171
234
|
end
|
172
235
|
end
|
@@ -5,15 +5,16 @@ 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
|
-
delegate :loaded_map, :configuration, to: :dumper
|
10
|
+
delegate :loaded_map, :to_load_map, :configuration, to: :dumper
|
11
11
|
|
12
12
|
def initialize(root, dumper)
|
13
13
|
@root = root
|
14
14
|
@dumper = dumper
|
15
|
-
@to_load_map = {}
|
16
15
|
@total_limit = root.total_limit
|
16
|
+
@deep_limit = root.deep_limit
|
17
|
+
@dont_nullify = root.dont_nullify
|
17
18
|
@association_limits = root.association_limits.dup
|
18
19
|
|
19
20
|
@model_class = root.model.constantize
|
@@ -24,7 +25,10 @@ module EvilSeed
|
|
24
25
|
def call
|
25
26
|
association_path = model_class.model_name.singular
|
26
27
|
relation = model_class.all
|
28
|
+
relation = relation.unscoped if configuration.unscoped
|
27
29
|
relation = relation.where(*root.constraints) if root.constraints.any? # without arguments returns not a relation
|
30
|
+
relation = relation.limit(root.limit) if root.limit
|
31
|
+
relation = relation.order(root.order) if root.order
|
28
32
|
RelationDumper.new(relation, self, association_path).call
|
29
33
|
end
|
30
34
|
|
data/lib/evil_seed/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: evil-seed
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrey Novikov
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2025-02-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -31,14 +31,14 @@ dependencies:
|
|
31
31
|
requirements:
|
32
32
|
- - "~>"
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: '
|
34
|
+
version: '13.0'
|
35
35
|
type: :development
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
39
|
- - "~>"
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version: '
|
41
|
+
version: '13.0'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: minitest
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -146,9 +146,11 @@ executables: []
|
|
146
146
|
extensions: []
|
147
147
|
extra_rdoc_files: []
|
148
148
|
files:
|
149
|
+
- ".github/workflows/release.yml"
|
149
150
|
- ".github/workflows/test.yml"
|
150
151
|
- ".gitignore"
|
151
152
|
- ".rubocop.yml"
|
153
|
+
- CHANGELOG.md
|
152
154
|
- Gemfile
|
153
155
|
- LICENSE.txt
|
154
156
|
- README.md
|
@@ -188,7 +190,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
188
190
|
- !ruby/object:Gem::Version
|
189
191
|
version: '0'
|
190
192
|
requirements: []
|
191
|
-
rubygems_version: 3.
|
193
|
+
rubygems_version: 3.5.22
|
192
194
|
signing_key:
|
193
195
|
specification_version: 4
|
194
196
|
summary: Create partial and anonymized production database dumps for use in development
|