acts_as_scrubbable 1.0.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 2a4b66ce45eefc1baa779af4eecf03005b196a6b
4
- data.tar.gz: 5f4d1febcb268c6891a02d1ddf5d48adf1dc8b22
2
+ SHA256:
3
+ metadata.gz: bb73960ded68e0d1a181ea4bf86300ae30dccb3e29330e63ddc8cae835662750
4
+ data.tar.gz: 534cdd721d48284f9d714093f307108f0c12ac13ef9636bd28b08745f984fcc9
5
5
  SHA512:
6
- metadata.gz: bcbae2c5dddaa9a6ec84a40c9bbf91ee283a422ac441af330c23eb2f216806cbde92ef47eb50435f99fd74416e62e4944e68491aefd6621ed5a15a0bcabef462
7
- data.tar.gz: 67a3505a44a30b0946c0e6dd2b8235433bc7c7459571aba934325d9157f097c0991ea7c94f8824a14ff5aa8e7218cbcea27c9bca307884b843bc54f9de86a430
6
+ metadata.gz: 8685741230ee6f515b9b5738dd5b32772b66f513f991f19fe95d0db2dfcb6414f3424d1a28218e4f2b5a285bb6a0b739ce6446e2bfd569023caf1dc432ec9924
7
+ data.tar.gz: 4883b89adccb21c37572a3e4aa98fba58b19765d356748875921ab877c6bcc4a201b9c798f38777d8ac99cd46c0b6c4d27aac2595657d03c6199fdcc012fe69b
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - 2.4.3
5
+
6
+ script:
7
+ - bundle exec rspec
data/Gemfile CHANGED
@@ -1,5 +1,3 @@
1
- ruby '2.3.1'
2
-
3
1
  source 'https://rubygems.org'
4
2
 
5
3
  gemspec
@@ -13,9 +13,9 @@ Gem::Specification.new do |s|
13
13
  s.license = "MIT"
14
14
  s.required_ruby_version = '~> 2.0'
15
15
 
16
- s.add_runtime_dependency 'activesupport' , '>= 4.1', '< 5.1'
17
- s.add_runtime_dependency 'activerecord' , '>= 4.1', '< 5.1'
18
- s.add_runtime_dependency 'railties' , '>= 4.1', '< 5.1'
16
+ s.add_runtime_dependency 'activesupport' , '>= 4.1', '< 6'
17
+ s.add_runtime_dependency 'activerecord' , '>= 4.1', '< 6'
18
+ s.add_runtime_dependency 'railties' , '>= 4.1', '< 6'
19
19
  s.add_runtime_dependency 'faker' , '>= 1.4'
20
20
  s.add_runtime_dependency 'highline' , '>= 1.7'
21
21
  s.add_runtime_dependency 'term-ansicolor' , '>= 1.3'
@@ -37,7 +37,7 @@ module ActsAsScrubbable
37
37
  :middle_name => -> { Faker::Name.name },
38
38
  :name => -> { Faker::Name.name },
39
39
  :email => -> { Faker::Internet.email },
40
- :name_title => -> { Faker::Name.title },
40
+ :name_title => -> { defined? Faker::Job ? Faker::Job.title : Faker::Name.title },
41
41
  :company_name => -> { Faker::Company.name },
42
42
  :street_address => -> { Faker::Address.street_address },
43
43
  :secondary_address => -> { Faker::Address.secondary_address },
@@ -0,0 +1,84 @@
1
+ require "parallel"
2
+
3
+ module ActsAsScrubbable
4
+ class ParallelTableScrubber
5
+ def initialize(ar_class)
6
+ @ar_class = ar_class
7
+ end
8
+
9
+ def scrub(num_batches:)
10
+ # Removing any find or initialize callbacks from model
11
+ ar_class.reset_callbacks(:initialize)
12
+ ar_class.reset_callbacks(:find)
13
+
14
+ queries = parallel_queries(ar_class: ar_class, num_batches: num_batches)
15
+ scrubbed_count = Parallel.map(queries) { |query|
16
+ scrubbed_count = 0
17
+ ActiveRecord::Base.connection_pool.with_connection do
18
+ relation = ar_class
19
+ relation = relation.send(:scrubbable_scope) if ar_class.respond_to?(:scrubbable_scope)
20
+ relation.where(query).find_in_batches(batch_size: 1000) do |batch|
21
+ ActiveRecord::Base.transaction do
22
+ batch.each do |obj|
23
+ obj.scrub!
24
+ scrubbed_count += 1
25
+ end
26
+ end
27
+ end
28
+ end
29
+ scrubbed_count
30
+ }.reduce(:+)
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :ar_class
36
+
37
+ # create even ID ranges for the table
38
+ def parallel_queries(ar_class:, num_batches:)
39
+ raise "Model is missing id column" if ar_class.columns.none? { |column| column.name == "id" }
40
+
41
+ if ar_class.respond_to?(:scrubbable_scope)
42
+ num_records = ar_class.send(:scrubbable_scope).count
43
+ else
44
+ num_records = ar_class.count
45
+ end
46
+ return [] if num_records == 0 # no records to import
47
+
48
+ record_window_size, modulus = num_records.divmod(num_batches)
49
+ if record_window_size < 1
50
+ record_window_size = 1
51
+ modulus = 0
52
+ end
53
+
54
+ start_id = next_id(ar_class: ar_class, offset: 0)
55
+ queries = num_batches.times.each_with_object([]) do |_, queries|
56
+ next unless start_id
57
+
58
+ end_id = next_id(ar_class: ar_class, id: start_id, offset: record_window_size-1)
59
+ if modulus > 0
60
+ end_id = next_id(ar_class: ar_class, id: end_id)
61
+ modulus -= 1
62
+ end
63
+ queries << {id: start_id..end_id} if end_id
64
+ start_id = next_id(ar_class: ar_class, id: end_id || start_id)
65
+ end
66
+
67
+ # just in case new records are added since we started, extend the end ID
68
+ queries[-1] = ["#{ar_class.quoted_table_name}.id >= ?", queries[-1][:id].begin] if queries.any?
69
+
70
+ queries
71
+ end
72
+
73
+ def next_id(ar_class:, id: nil, offset: 1)
74
+ if ar_class.respond_to?(:scrubbable_scope)
75
+ collection = ar_class.send(:scrubbable_scope)
76
+ else
77
+ collection = ar_class.all
78
+ end
79
+ collection = collection.reorder(:id)
80
+ collection = collection.where("#{ar_class.quoted_table_name}.id >= :id", id: id) if id
81
+ collection.offset(offset).limit(1).pluck(:id).first
82
+ end
83
+ end
84
+ end
@@ -3,64 +3,55 @@ require 'rake'
3
3
 
4
4
  namespace :scrub do
5
5
 
6
- desc "scrub all"
6
+ desc "scrub all scrubbable tables"
7
7
  task all: :environment do
8
-
9
8
  require 'highline/import'
10
9
  require 'term/ansicolor'
11
10
  require 'logger'
12
11
  require 'parallel'
13
12
 
14
-
15
13
  include Term::ANSIColor
16
14
 
17
- @logger = Logger.new($stdout)
18
- @logger.formatter = proc do |severity, datetime, progname, msg|
15
+ logger = Logger.new($stdout)
16
+ logger.formatter = proc do |severity, datetime, progname, msg|
19
17
  "#{datetime}: [#{severity}] - #{msg}\n"
20
18
  end
21
19
 
22
20
  db_host = ActiveRecord::Base.connection_config[:host]
23
21
  db_name = ActiveRecord::Base.connection_config[:database]
24
22
 
25
- @logger.warn "Please verify the information below to continue".red
26
- @logger.warn "Host: ".red + " #{db_host}".white
27
- @logger.warn "Database: ".red + "#{db_name}".white
23
+ logger.warn "Please verify the information below to continue".red
24
+ logger.warn "Host: ".red + " #{db_host}".white
25
+ logger.warn "Database: ".red + "#{db_name}".white
28
26
 
29
27
  unless ENV["SKIP_CONFIRM"] == "true"
30
-
31
28
  answer = ask("Type '#{db_host}' to continue. \n".red + '-> '.white)
32
29
  unless answer == db_host
33
- @logger.error "exiting ...".red
30
+ logger.error "exiting ...".red
34
31
  exit
35
32
  end
36
33
  end
37
34
 
38
- @logger.warn "Scrubbing classes".red
35
+ logger.warn "Scrubbing classes".red
39
36
 
40
37
  Rails.application.eager_load! # make sure all the classes are loaded
41
38
 
42
- @total_scrubbed = 0
43
-
44
39
  ar_classes = ActiveRecord::Base.descendants.select{|d| d.scrubbable? }.sort_by{|d| d.to_s }
45
40
 
46
-
47
- # if the ENV variable is set
48
-
49
- unless ENV["SCRUB_CLASSES"].blank?
41
+ if ENV["SCRUB_CLASSES"].present?
50
42
  class_list = ENV["SCRUB_CLASSES"].split(",")
51
43
  class_list = class_list.map {|_class_str| _class_str.constantize }
52
44
  ar_classes = ar_classes & class_list
53
45
  end
54
46
 
55
- @logger.info "Srubbable Classes: #{ar_classes.join(', ')}".white
47
+ logger.info "Scrubbable Classes: #{ar_classes.join(', ')}".white
56
48
 
57
49
  Parallel.each(ar_classes) do |ar_class|
58
-
59
50
  # Removing any find or initialize callbacks from model
60
51
  ar_class.reset_callbacks(:initialize)
61
52
  ar_class.reset_callbacks(:find)
62
53
 
63
- @logger.info "Scrubbing #{ar_class} ...".green
54
+ logger.info "Scrubbing #{ar_class} ...".green
64
55
 
65
56
  scrubbed_count = 0
66
57
 
@@ -81,16 +72,59 @@ namespace :scrub do
81
72
  end
82
73
  end
83
74
 
84
- @logger.info "#{scrubbed_count} #{ar_class} objects scrubbed".blue
75
+ logger.info "#{scrubbed_count} #{ar_class} objects scrubbed".blue
85
76
  end
86
77
  ActiveRecord::Base.connection.verify!
87
78
 
88
79
  if ENV["SKIP_AFTERHOOK"].blank?
89
- @logger.info "Running after hook".red
80
+ logger.info "Running after hook".red
90
81
  ActsAsScrubbable.execute_after_hook
91
82
  end
92
83
 
93
- @logger.info "Scrub Complete!".white
84
+ logger.info "Scrub Complete!".white
85
+ end
86
+
87
+ desc "Scrub one table"
88
+ task :model, [:ar_class] => :environment do |_, args|
89
+ require 'highline/import'
90
+ require 'term/ansicolor'
91
+ require 'logger'
92
+ require 'acts_as_scrubbable/parallel_table_scrubber'
93
+
94
+ include Term::ANSIColor
95
+
96
+ logger = Logger.new($stdout)
97
+ logger.formatter = proc do |severity, datetime, progname, msg|
98
+ "#{datetime}: [#{severity}] - #{msg}\n"
99
+ end
100
+
101
+ db_host = ActiveRecord::Base.connection_config[:host]
102
+ db_name = ActiveRecord::Base.connection_config[:database]
103
+
104
+ logger.warn "Please verify the information below to continue".red
105
+ logger.warn "Host: ".red + " #{db_host}".white
106
+ logger.warn "Database: ".red + "#{db_name}".white
107
+
108
+ unless ENV["SKIP_CONFIRM"] == "true"
109
+ answer = ask("Type '#{db_host}' to continue. \n".red + '-> '.white)
110
+ unless answer == db_host
111
+ logger.error "exiting ...".red
112
+ exit
113
+ end
114
+ end
115
+
116
+ Rails.application.eager_load! # make sure all the classes are loaded
117
+
118
+ ar_class = args[:ar_class].constantize
119
+ logger.info "Scrubbing #{ar_class} ...".green
120
+
121
+ num_batches = Integer(ENV.fetch("SCRUB_BATCHES", "256"))
122
+ scrubbed_count = ActsAsScrubbable::ParallelTableScrubber.new(ar_class).scrub(num_batches: num_batches)
123
+
124
+ logger.info "#{scrubbed_count} #{ar_class} objects scrubbed".blue
125
+ ActiveRecord::Base.connection.verify!
126
+
127
+ logger.info "Scrub Complete!".white
94
128
  end
95
129
  end
96
130
 
@@ -1,3 +1,3 @@
1
1
  module ActsAsScrubbable
2
- VERSION = '1.0.0'
2
+ VERSION = '1.1.1'
3
3
  end
data/spec/db/schema.rb CHANGED
@@ -2,8 +2,23 @@ ActiveRecord::Schema.define(version: 20150421224501) do
2
2
 
3
3
  create_table "scrubbable_models", force: true do |t|
4
4
  t.string "first_name"
5
+ t.string "last_name"
6
+ t.string "middle_name"
7
+ t.string "name"
8
+ t.string "email"
9
+ t.string "title"
10
+ t.string "company_name"
5
11
  t.string "address1"
12
+ t.string "address2"
13
+ t.string "zip_code"
14
+ t.string "state"
15
+ t.string "state_short"
16
+ t.string "city"
6
17
  t.string "lat"
18
+ t.string "lon"
19
+ t.string "username"
20
+ t.boolean "active"
21
+ t.string "school"
7
22
  end
8
23
 
9
24
  end
@@ -5,9 +5,35 @@ RSpec.describe ActsAsScrubbable::Scrub do
5
5
  describe '.scrub' do
6
6
 
7
7
  # update_columns cannot be run on a new record
8
- subject{ ScrubbableModel.new }
8
+ subject { ScrubbableModel.new }
9
9
  before(:each) { subject.save }
10
10
 
11
+ it 'scrubs all columns' do
12
+ subject.attributes = {
13
+ first_name: "Ted",
14
+ last_name: "Lowe",
15
+ middle_name: "Cassidy",
16
+ name: "Miss Vincenzo Smitham",
17
+ email: "trentdibbert@wiza.com",
18
+ title: "Internal Consultant",
19
+ company_name: "Greenfelder, Collier and Lesch",
20
+ address1: "86780 Watsica Flats",
21
+ address2: "Apt. 913",
22
+ zip_code: "49227",
23
+ state: "Ohio",
24
+ state_short: "OH",
25
+ city: "Port Hildegard",
26
+ lat: -79.5855309778974,
27
+ lon: 13.517352691513906,
28
+ username: "oscar.hermann",
29
+ active: false,
30
+ school: "Eastern Lebsack",
31
+ }
32
+ expect {
33
+ subject.scrub!
34
+ }.not_to raise_error
35
+ end
36
+
11
37
  it 'changes the first_name attribute when scrub is run' do
12
38
  subject.first_name = "Ted"
13
39
  allow(Faker::Name).to receive(:first_name).and_return("John")
@@ -1,7 +1,7 @@
1
1
  require 'nulldb/rails'
2
2
  require 'nulldb_rspec'
3
3
 
4
- ActiveRecord::Base.configurations = {"test" => {adapter: :nulldb}}
4
+ ActiveRecord::Base.configurations.merge!("test" => {adapter: 'nulldb'})
5
5
 
6
6
  NullDB.configure do |c|
7
7
  c.project_root = './spec'
@@ -15,7 +15,24 @@ end
15
15
  class NonScrubbableModel < ActiveRecord::Base; end
16
16
 
17
17
  class ScrubbableModel < ActiveRecord::Base
18
- acts_as_scrubbable :first_name, :address1 => :street_address, :lat => :latitude
18
+ acts_as_scrubbable :first_name,
19
+ :last_name,
20
+ :middle_name,
21
+ :name,
22
+ :email,
23
+ :company_name,
24
+ :zip_code,
25
+ :state,
26
+ :city,
27
+ :username,
28
+ :school,
29
+ :title => :name_title,
30
+ :address1 => :street_address,
31
+ :address2 => :secondary_address,
32
+ :state_short => :state_abbr,
33
+ :lat => :latitude,
34
+ :lon => :longitude,
35
+ :active => :boolean
19
36
  attr_accessor :scrubbing_begun, :scrubbing_finished
20
37
  set_callback :scrub, :before do
21
38
  self.scrubbing_begun = true
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts_as_scrubbable
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samer Masry
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-20 00:00:00.000000000 Z
11
+ date: 2021-07-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: '4.1'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '5.1'
22
+ version: '6'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: '4.1'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '5.1'
32
+ version: '6'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: activerecord
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -39,7 +39,7 @@ dependencies:
39
39
  version: '4.1'
40
40
  - - "<"
41
41
  - !ruby/object:Gem::Version
42
- version: '5.1'
42
+ version: '6'
43
43
  type: :runtime
44
44
  prerelease: false
45
45
  version_requirements: !ruby/object:Gem::Requirement
@@ -49,7 +49,7 @@ dependencies:
49
49
  version: '4.1'
50
50
  - - "<"
51
51
  - !ruby/object:Gem::Version
52
- version: '5.1'
52
+ version: '6'
53
53
  - !ruby/object:Gem::Dependency
54
54
  name: railties
55
55
  requirement: !ruby/object:Gem::Requirement
@@ -59,7 +59,7 @@ dependencies:
59
59
  version: '4.1'
60
60
  - - "<"
61
61
  - !ruby/object:Gem::Version
62
- version: '5.1'
62
+ version: '6'
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
@@ -69,7 +69,7 @@ dependencies:
69
69
  version: '4.1'
70
70
  - - "<"
71
71
  - !ruby/object:Gem::Version
72
- version: '5.1'
72
+ version: '6'
73
73
  - !ruby/object:Gem::Dependency
74
74
  name: faker
75
75
  requirement: !ruby/object:Gem::Requirement
@@ -220,11 +220,13 @@ extra_rdoc_files: []
220
220
  files:
221
221
  - ".gitignore"
222
222
  - ".rspec"
223
+ - ".travis.yml"
223
224
  - Gemfile
224
225
  - Guardfile
225
226
  - README.md
226
227
  - acts_as_scrubbable.gemspec
227
228
  - lib/acts_as_scrubbable.rb
229
+ - lib/acts_as_scrubbable/parallel_table_scrubber.rb
228
230
  - lib/acts_as_scrubbable/scrub.rb
229
231
  - lib/acts_as_scrubbable/scrubbable.rb
230
232
  - lib/acts_as_scrubbable/tasks.rb
@@ -239,7 +241,7 @@ homepage: https://github.com/smasry/acts_as_scrubbable
239
241
  licenses:
240
242
  - MIT
241
243
  metadata: {}
242
- post_install_message:
244
+ post_install_message:
243
245
  rdoc_options: []
244
246
  require_paths:
245
247
  - lib
@@ -254,9 +256,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
254
256
  - !ruby/object:Gem::Version
255
257
  version: '0'
256
258
  requirements: []
257
- rubyforge_project:
258
- rubygems_version: 2.5.1
259
- signing_key:
259
+ rubygems_version: 3.1.4
260
+ signing_key:
260
261
  specification_version: 4
261
262
  summary: Scrubbing data made easy
262
263
  test_files: