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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9a046b3aea1b09b90d510fa29ffd79df71e678101e081c72a4f1d419869eecca
4
- data.tar.gz: 6d49dc0c48eb9c143842839697399afbdcf706c03d9164e86e15efad0a75499d
3
+ metadata.gz: dfdedee7cac6da3803d6071ccbda5eaf19ee07bc07c67775b202160c304dd323
4
+ data.tar.gz: e04bc0672ccb683928651843c749ad05378af846c6d4b51d7e7c3d14eb43c179
5
5
  SHA512:
6
- metadata.gz: 0b9f060b074653b5d6bfc942ff60d90d378fd114b7efaf5100037ab75c8f49bf1fa9b7301c19df0649fc41c3545862811183bda4095efb5bbd90339332e11d35
7
- data.tar.gz: 42369aa53c56fd5200ef78269d6379a7d454a27a1e37941e3503c4498e1e13fee7b1f85b4dd80b2679526c97d289f757babacaa81424109427d9a7a4372bf52a
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
@@ -37,5 +37,4 @@ Gem::Specification.new do |spec|
37
37
  spec.add_development_dependency 'rubocop'
38
38
  spec.add_development_dependency 'bundler'
39
39
  spec.add_development_dependency 'pry'
40
- spec.add_development_dependency 'appraisal'
41
40
  end
@@ -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| exclusion.match(association_path) }
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(" (#{prepare(attributes).join(', ')})")
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
- belongs_to_dumps = dump_belongs_to_associations!
56
- has_many_dumps = dump_has_many_associations!
57
- [belongs_to_dumps, record_dumper.result, has_many_dumps].flatten.compact
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)).each do |attributes|
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).each do |attributes|
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
- nullify_columns.each do |nullify_column|
83
- attributes[nullify_column] = nil
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
- relation = reflection.klass.all
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
- nullify_columns << reflection.foreign_key if model_class.column_names.include?(reflection.foreign_key)
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
- %i[has_one has_many].include?(reflection.macro) && !root.excluded?("#{association_path}.#{reflection.name}")
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EvilSeed
4
- VERSION = '0.4.0'
4
+ VERSION = '0.6.0'
5
5
  end
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.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: 2022-12-07 00:00:00.000000000 Z
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
- - ".travis.yml"
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.1.6
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
@@ -1,2 +0,0 @@
1
- ---
2
- BUNDLE_RETRY: "1"
@@ -1,10 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "~> 5.0.0"
6
- gem "pg", ">= 0.20", "< 2.0"
7
- gem "mysql2", "~> 0.4.4"
8
- gem "sqlite3", "~> 1.3.6"
9
-
10
- gemspec path: "../"
@@ -1,10 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "~> 5.1.0"
6
- gem "pg", ">= 0.20", "< 2.0"
7
- gem "mysql2", "~> 0.4.4"
8
- gem "sqlite3", "~> 1.3"
9
-
10
- gemspec path: "../"
@@ -1,10 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "~> 5.2.0"
6
- gem "pg", ">= 0.20", "< 2.0"
7
- gem "mysql2", "~> 0.4.4"
8
- gem "sqlite3", "~> 1.3"
9
-
10
- gemspec path: "../"
@@ -1,10 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "~> 6.0.0"
6
- gem "pg", ">= 0.20", "< 2.0"
7
- gem "mysql2", "~> 0.4.4"
8
- gem "sqlite3", "~> 1.4"
9
-
10
- gemspec path: "../"
@@ -1,10 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "~> 6.1.0"
6
- gem "pg", "~> 1.1"
7
- gem "mysql2", "~> 0.5"
8
- gem "sqlite3", "~> 1.4"
9
-
10
- gemspec path: "../"
@@ -1,10 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", git: "https://github.com/rails/rails.git"
6
- gem "pg", "~> 1.1"
7
- gem "mysql2", "~> 0.5"
8
- gem "sqlite3", "~> 1.4"
9
-
10
- gemspec path: "../"