active_record_data_loader 1.0.1 → 1.0.2

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
- SHA256:
3
- metadata.gz: ddff4ba719de66b138820d4e5c257df064e85259e595156f5f25c54622e9c60a
4
- data.tar.gz: e82e4ae901cd557a197c4b5c66069bbb01e2cf64973c4b885a35661f1ecb05ca
2
+ SHA1:
3
+ metadata.gz: a76332e648af9f10d665eaab3fb442b18d708914
4
+ data.tar.gz: 012eb43c82d6bca82f97ece0d719faa07ea79c6c
5
5
  SHA512:
6
- metadata.gz: 6a6d299daed5d81cd92f65206307ceab39e4147afa8956822890941426e4765b197d149d97992d19bc7506697a76cd9e6056623549eeb632cc884dc03f02cea5
7
- data.tar.gz: a68aca773f47b9b06f833ad0a8e6eaf6bb29b76d9d966597c17f40855430c799703d75cc8ef34fa209e66cd0cc50b220e00b51b086b1cb136c92607a9f0c873c
6
+ metadata.gz: 2f41b6aedb4c4cd4f02319cd3d13c50715a0e91bbfd0710e04caa2145054d26b20a4e7a2f69b295b28650a0ad8e111f322fbf008bda28f0db7fa6fa25428de24
7
+ data.tar.gz: 18d9fb81d1e5d273aab3d21bc2d12f5e28624636f6286deeaf0ccadfdbf20afe629583c96dde78ee32acaaa0b931a363fe32c2c8a77435eca32540875a8d3ee7
@@ -19,5 +19,6 @@ notifications:
19
19
  before_install: "gem update --system && gem install bundler"
20
20
  before_script:
21
21
  - psql -c 'create database test;' -U postgres
22
+ - mysql -e 'CREATE DATABASE IF NOT EXISTS test;'
22
23
  - cp config/database.yml.travis config/database.yml
23
24
  script: "bundle exec rake"
@@ -1,5 +1,13 @@
1
1
  # Change log
2
2
 
3
+ ## [v1.0.2] - 2019-07-05
4
+
5
+ [Diff](https://github.com/abeiderman/active_record_data_loader/compare/v1.0.1...v1.0.2)
6
+
7
+ ### Enhancements:
8
+ * Add support for MySQL enums
9
+ * Accept a connection factory lambda as part of the configuration
10
+
3
11
  ## [v1.0.1] - 2019-06-16
4
12
 
5
13
  [Diff](https://github.com/abeiderman/active_record_data_loader/compare/v1.0.0...v1.0.1)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- active_record_data_loader (1.0.1)
4
+ active_record_data_loader (1.0.2)
5
5
  activerecord (>= 4.0)
6
6
 
7
7
  GEM
@@ -40,6 +40,7 @@ GEM
40
40
  json (2.2.0)
41
41
  method_source (0.9.2)
42
42
  minitest (5.11.3)
43
+ mysql2 (0.5.2)
43
44
  parallel (1.17.0)
44
45
  parser (2.6.3.0)
45
46
  ast (~> 2.4.0)
@@ -96,6 +97,7 @@ DEPENDENCIES
96
97
  appraisal
97
98
  bundler (>= 1.16)
98
99
  coveralls
100
+ mysql2
99
101
  pg
100
102
  pry
101
103
  rake (~> 12.0)
@@ -37,6 +37,7 @@ Gem::Specification.new do |spec|
37
37
  spec.add_development_dependency "appraisal"
38
38
  spec.add_development_dependency "bundler", ">= 1.16"
39
39
  spec.add_development_dependency "coveralls"
40
+ spec.add_development_dependency "mysql2"
40
41
  spec.add_development_dependency "pg"
41
42
  spec.add_development_dependency "pry"
42
43
  spec.add_development_dependency "rake", "~> 12.0"
@@ -8,3 +8,10 @@ postgres:
8
8
  sqlite3:
9
9
  adapter: "sqlite3"
10
10
  database: ":memory:"
11
+
12
+ mysql:
13
+ adapter: "mysql2"
14
+ host: "127.0.0.1"
15
+ database: "test"
16
+ username: "test"
17
+ password: "test"
@@ -5,3 +5,8 @@ postgres:
5
5
  sqlite3:
6
6
  adapter: "sqlite3"
7
7
  database: ":memory:"
8
+
9
+ mysql:
10
+ adapter: "mysql2"
11
+ database: "test"
12
+ username: "travis"
@@ -24,7 +24,7 @@ require "active_record_data_loader/loader"
24
24
  module ActiveRecordDataLoader
25
25
  def self.define(config = ActiveRecordDataLoader.configuration, &block)
26
26
  LoaderProxy.new(
27
- configuration,
27
+ config,
28
28
  ActiveRecordDataLoader::Dsl::Definition.new(config).tap { |l| l.instance_eval(&block) }
29
29
  )
30
30
  end
@@ -46,25 +46,28 @@ module ActiveRecordDataLoader
46
46
  def load_data
47
47
  ActiveRecordDataLoader::ActiveRecord::PerRowValueCache.clear
48
48
 
49
- definition.models.map do |m|
50
- generator = ActiveRecordDataLoader::ActiveRecord::ModelDataGenerator.new(
51
- model: m.klass,
52
- column_settings: m.columns,
53
- polymorphic_settings: m.polymorphic_associations,
54
- belongs_to_settings: m.belongs_to_associations
55
- )
56
-
57
- ActiveRecordDataLoader::Loader.load_data(
58
- data_generator: generator,
59
- batch_size: m.batch_size,
60
- total_rows: m.row_count,
61
- configuration: configuration
62
- )
63
- end
49
+ definition.models.map { |m| load_model(m) }
64
50
  end
65
51
 
66
52
  private
67
53
 
68
54
  attr_reader :definition, :configuration
55
+
56
+ def load_model(model)
57
+ generator = ActiveRecordDataLoader::ActiveRecord::ModelDataGenerator.new(
58
+ model: model.klass,
59
+ column_settings: model.columns,
60
+ polymorphic_settings: model.polymorphic_associations,
61
+ belongs_to_settings: model.belongs_to_associations,
62
+ connection_factory: configuration.connection_factory
63
+ )
64
+
65
+ ActiveRecordDataLoader::Loader.load_data(
66
+ data_generator: generator,
67
+ batch_size: model.batch_size,
68
+ total_rows: model.row_count,
69
+ configuration: configuration
70
+ )
71
+ end
69
72
  end
70
73
  end
@@ -12,13 +12,14 @@ module ActiveRecordDataLoader
12
12
  datetime: DatetimeValueGenerator,
13
13
  }.freeze
14
14
 
15
- def config_for(model_class:, ar_column:)
15
+ def config_for(model_class:, ar_column:, connection_factory:)
16
16
  raise_error_if_not_supported(model_class, ar_column)
17
17
 
18
18
  {
19
- ar_column.name.to_sym => VALUE_GENERATORS[ar_column.type].generator_for(
19
+ ar_column.name.to_sym => VALUE_GENERATORS[column_type(ar_column)].generator_for(
20
20
  model_class: model_class,
21
- ar_column: ar_column
21
+ ar_column: ar_column,
22
+ connection_factory: connection_factory
22
23
  ),
23
24
  }
24
25
  end
@@ -26,7 +27,7 @@ module ActiveRecordDataLoader
26
27
  def supported?(model_class:, ar_column:)
27
28
  return false if model_class.reflect_on_association(ar_column.name)
28
29
 
29
- VALUE_GENERATORS.keys.include?(ar_column.type)
30
+ VALUE_GENERATORS.keys.include?(column_type(ar_column))
30
31
  end
31
32
 
32
33
  private
@@ -38,6 +39,14 @@ module ActiveRecordDataLoader
38
39
  Column '#{ar_column.name}' of type '#{ar_column.type}' in model '#{model_class.name}' not supported"
39
40
  ERROR
40
41
  end
42
+
43
+ def column_type(ar_column)
44
+ if ar_column.type == :string && ar_column.sql_type.to_s.downcase.start_with?("enum")
45
+ :enum
46
+ else
47
+ ar_column.type
48
+ end
49
+ end
41
50
  end
42
51
  end
43
52
  end
@@ -4,7 +4,7 @@ module ActiveRecordDataLoader
4
4
  module ActiveRecord
5
5
  class DatetimeValueGenerator
6
6
  class << self
7
- def generator_for(model_class:, ar_column:)
7
+ def generator_for(model_class:, ar_column:, connection_factory: nil)
8
8
  ->(row) { timestamp(model_class, row) }
9
9
  end
10
10
 
@@ -4,14 +4,26 @@ module ActiveRecordDataLoader
4
4
  module ActiveRecord
5
5
  class EnumValueGenerator
6
6
  class << self
7
- def generator_for(model_class:, ar_column:)
8
- values = enum_values_for(model_class, ar_column.sql_type)
7
+ def generator_for(model_class:, ar_column:, connection_factory:)
8
+ values = enum_values_for(model_class, ar_column.sql_type, connection_factory)
9
9
  -> { values.sample }
10
10
  end
11
11
 
12
12
  private
13
13
 
14
- def enum_values_for(model_class, enum_type)
14
+ def enum_values_for(model_class, enum_type, connection_factory)
15
+ connection = connection_factory.call
16
+
17
+ if connection.adapter_name.downcase.to_sym == :postgresql
18
+ postgres_enum_values_for(model_class, enum_type)
19
+ elsif connection.adapter_name.downcase.to_s.start_with?("mysql")
20
+ mysql_enum_values_for(model_class, enum_type)
21
+ else
22
+ []
23
+ end
24
+ end
25
+
26
+ def postgres_enum_values_for(model_class, enum_type)
15
27
  model_class
16
28
  .connection
17
29
  .execute("SELECT unnest(enum_range(NULL::#{enum_type}))::text")
@@ -19,6 +31,16 @@ module ActiveRecordDataLoader
19
31
  .flatten
20
32
  .compact
21
33
  end
34
+
35
+ def mysql_enum_values_for(_model_class, enum_type)
36
+ enum_type
37
+ .to_s
38
+ .downcase
39
+ .gsub(/\Aenum\(|\)\Z/, "")
40
+ .split(",")
41
+ .map(&:strip)
42
+ .map { |s| s.gsub(/\A'|'\Z/, "") }
43
+ end
22
44
  end
23
45
  end
24
46
  end
@@ -4,7 +4,7 @@ module ActiveRecordDataLoader
4
4
  module ActiveRecord
5
5
  class IntegerValueGenerator
6
6
  class << self
7
- def generator_for(model_class:, ar_column:)
7
+ def generator_for(model_class:, ar_column:, connection_factory: nil)
8
8
  range_limit = [(256**number_of_bytes(ar_column)) / 2 - 1, 1_000_000_000].min
9
9
 
10
10
  -> { rand(0..range_limit) }
@@ -5,12 +5,19 @@ module ActiveRecordDataLoader
5
5
  class ModelDataGenerator
6
6
  attr_reader :table
7
7
 
8
- def initialize(model:, column_settings:, polymorphic_settings: [], belongs_to_settings: [])
8
+ def initialize(
9
+ model:,
10
+ column_settings:,
11
+ polymorphic_settings: [],
12
+ belongs_to_settings: [],
13
+ connection_factory:
14
+ )
9
15
  @model_class = model
10
16
  @table = model.table_name
11
17
  @column_settings = column_settings
12
18
  @polymorphic_settings = polymorphic_settings
13
19
  @belongs_to_settings = belongs_to_settings.map { |s| [s.name, s.query] }.to_h
20
+ @connection_factory = connection_factory
14
21
  end
15
22
 
16
23
  def column_list
@@ -50,7 +57,13 @@ module ActiveRecordDataLoader
50
57
  .columns_hash
51
58
  .reject { |name| name == @model_class.primary_key }
52
59
  .select { |_, c| ColumnConfiguration.supported?(model_class: @model_class, ar_column: c) }
53
- .map { |_, c| ColumnConfiguration.config_for(model_class: @model_class, ar_column: c) }
60
+ .map do |_, c|
61
+ ColumnConfiguration.config_for(
62
+ model_class: @model_class,
63
+ ar_column: c,
64
+ connection_factory: @connection_factory
65
+ )
66
+ end
54
67
  .reduce({}, :merge)
55
68
  end
56
69
 
@@ -12,7 +12,7 @@ module ActiveRecordDataLoader
12
12
  }.freeze
13
13
 
14
14
  class << self
15
- def generator_for(model_class:, ar_column:)
15
+ def generator_for(model_class:, ar_column:, connection_factory: nil)
16
16
  scenario = GENERATORS.keys.find { |m| send(m, model_class, ar_column) }
17
17
  generator = GENERATORS.fetch(scenario, -> { SecureRandom.uuid })
18
18
 
@@ -2,18 +2,20 @@
2
2
 
3
3
  module ActiveRecordDataLoader
4
4
  class Configuration
5
- attr_accessor :default_batch_size, :default_row_count, :logger, :statement_timeout
5
+ attr_accessor :default_batch_size, :default_row_count, :logger, :statement_timeout, :connection_factory
6
6
 
7
7
  def initialize(
8
8
  default_batch_size: 100_000,
9
9
  default_row_count: 1,
10
10
  logger: nil,
11
- statement_timeout: "2min"
11
+ statement_timeout: "2min",
12
+ connection_factory: -> { ::ActiveRecord::Base.connection }
12
13
  )
13
14
  @default_batch_size = default_batch_size
14
15
  @default_row_count = default_row_count
15
16
  @logger = logger || default_logger
16
17
  @statement_timeout = statement_timeout
18
+ @connection_factory = connection_factory
17
19
  end
18
20
 
19
21
  private
@@ -14,14 +14,15 @@ module ActiveRecordDataLoader
14
14
  new(
15
15
  logger: configuration.logger,
16
16
  statement_timeout: configuration.statement_timeout,
17
- strategy: strategy_class.new(data_generator)
17
+ strategy: strategy_class(configuration.connection_factory).new(data_generator),
18
+ connection_factory: configuration.connection_factory
18
19
  ).load_data(batch_size, total_rows)
19
20
  end
20
21
 
21
22
  private
22
23
 
23
- def strategy_class
24
- if ::ActiveRecord::Base.connection.raw_connection.respond_to?(:copy_data)
24
+ def strategy_class(connection_factory)
25
+ if connection_factory.call.raw_connection.respond_to?(:copy_data)
25
26
  ActiveRecordDataLoader::CopyStrategy
26
27
  else
27
28
  ActiveRecordDataLoader::BulkInsertStrategy
@@ -29,10 +30,11 @@ module ActiveRecordDataLoader
29
30
  end
30
31
  end
31
32
 
32
- def initialize(logger:, statement_timeout:, strategy:)
33
+ def initialize(logger:, statement_timeout:, strategy:, connection_factory:)
33
34
  @logger = logger
34
35
  @strategy = strategy
35
36
  @statement_timeout = statement_timeout
37
+ @connection_factory = connection_factory
36
38
  end
37
39
 
38
40
  def load_data(batch_size, total_rows)
@@ -55,7 +57,7 @@ module ActiveRecordDataLoader
55
57
 
56
58
  private
57
59
 
58
- attr_reader :strategy, :statement_timeout, :logger
60
+ attr_reader :strategy, :statement_timeout, :logger, :connection_factory
59
61
 
60
62
  def load_in_batches(batch_size, total_rows, batch_count)
61
63
  with_connection do |connection|
@@ -71,22 +73,26 @@ module ActiveRecordDataLoader
71
73
  end
72
74
 
73
75
  def with_connection
74
- if ::ActiveRecord::Base.connection.adapter_name.downcase.to_sym == :postgresql
76
+ if connection.adapter_name.downcase.to_sym == :postgresql
75
77
  original_timeout = retrieve_statement_timeout
76
78
  update_statement_timeout(statement_timeout)
77
- yield ::ActiveRecord::Base.connection
79
+ yield connection
78
80
  update_statement_timeout(original_timeout)
79
81
  else
80
- yield ::ActiveRecord::Base.connection
82
+ yield connection
81
83
  end
82
84
  end
83
85
 
84
86
  def retrieve_statement_timeout
85
- ::ActiveRecord::Base.connection.execute("SHOW statement_timeout").first["statement_timeout"]
87
+ connection.execute("SHOW statement_timeout").first["statement_timeout"]
86
88
  end
87
89
 
88
90
  def update_statement_timeout(timeout)
89
- ::ActiveRecord::Base.connection.execute("SET statement_timeout = \"#{timeout}\"")
91
+ connection.execute("SET statement_timeout = \"#{timeout}\"")
92
+ end
93
+
94
+ def connection
95
+ connection_factory.call
90
96
  end
91
97
  end
92
98
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordDataLoader
4
- VERSION = "1.0.1"
4
+ VERSION = "1.0.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_data_loader
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alejandro Beiderman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-17 00:00:00.000000000 Z
11
+ date: 2019-07-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: mysql2
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: pg
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -251,7 +265,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
251
265
  - !ruby/object:Gem::Version
252
266
  version: '0'
253
267
  requirements: []
254
- rubygems_version: 3.0.3
268
+ rubyforge_project:
269
+ rubygems_version: 2.6.14
255
270
  signing_key:
256
271
  specification_version: 4
257
272
  summary: A utility to bulk load test data for performance testing.