active-record-transactioner 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +7 -1
  3. data/Gemfile.lock +100 -12
  4. data/README.md +86 -0
  5. data/VERSION +1 -1
  6. data/active-record-transactioner.gemspec +68 -9
  7. data/lib/active-record-transactioner.rb +143 -106
  8. data/shippable.yml +16 -0
  9. data/spec/active-record-transactioner_spec.rb +21 -21
  10. data/spec/dummy/README.rdoc +28 -0
  11. data/spec/dummy/Rakefile +6 -0
  12. data/spec/dummy/app/assets/images/.keep +0 -0
  13. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  14. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  15. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  16. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  17. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  18. data/spec/dummy/app/mailers/.keep +0 -0
  19. data/spec/dummy/app/models/concerns/.keep +0 -0
  20. data/spec/dummy/app/models/user.rb +2 -0
  21. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  22. data/spec/dummy/bin/bundle +3 -0
  23. data/spec/dummy/bin/rails +4 -0
  24. data/spec/dummy/bin/rake +4 -0
  25. data/spec/dummy/config.ru +4 -0
  26. data/spec/dummy/config/application.rb +28 -0
  27. data/spec/dummy/config/boot.rb +5 -0
  28. data/spec/dummy/config/database.example.yml +7 -0
  29. data/spec/dummy/config/database.shippable.yml +7 -0
  30. data/spec/dummy/config/database.yml +7 -0
  31. data/spec/dummy/config/environment.rb +5 -0
  32. data/spec/dummy/config/environments/development.rb +32 -0
  33. data/spec/dummy/config/environments/production.rb +80 -0
  34. data/spec/dummy/config/environments/test.rb +39 -0
  35. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  36. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  37. data/spec/dummy/config/initializers/inflections.rb +16 -0
  38. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  39. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  40. data/spec/dummy/config/initializers/session_store.rb +3 -0
  41. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  42. data/spec/dummy/config/locales/en.yml +23 -0
  43. data/spec/dummy/config/routes.rb +56 -0
  44. data/spec/dummy/db/migrate/20141203180942_create_users.rb +13 -0
  45. data/spec/dummy/db/schema.rb +27 -0
  46. data/spec/dummy/lib/assets/.keep +0 -0
  47. data/spec/dummy/log/.keep +0 -0
  48. data/spec/dummy/public/404.html +58 -0
  49. data/spec/dummy/public/422.html +58 -0
  50. data/spec/dummy/public/500.html +57 -0
  51. data/spec/dummy/public/favicon.ico +0 -0
  52. data/spec/models/user_spec.rb +11 -0
  53. data/spec/models/user_threadded_spec.rb +11 -0
  54. data/spec/spec_helper.rb +17 -2
  55. data/spec/support/basic_user_operations.rb +44 -0
  56. data/spec/test_classes/active-record-transactioner-test-class.rb +12 -14
  57. metadata +124 -22
  58. data/README.rdoc +0 -36
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6ff5f7c2e010ea51c857be4b595aff6a11fc6a29
4
- data.tar.gz: c1b7216f9189778553b7455faf98d33760658683
3
+ metadata.gz: d0d85c7182312c5329e6c8731c57aa2d737f9952
4
+ data.tar.gz: 42316fbb652cfef2b9b35b0c572244dde822b404
5
5
  SHA512:
6
- metadata.gz: 8a4511fa3b9124dd3173097b6601c23af067afc4ac1964a251e3e80045aa282b0bd8431159d4385211dd979c12b5b2691166e36a169f4e4208fb68cb66a2015d
7
- data.tar.gz: a2e7df57feb26de445f1bb4a416d67265ce16aac5cbb23a2ca9528d768fc10251f67d8dd261e90dfad5cd7be21653ff9af2a225ada422209277f93db0b02fa22
6
+ metadata.gz: 90e200215749d32941aace7319d6c05d0cd305bf237afa40740d441078542fc83a6e647385449e2e4b9908f389471c59b27c90e4034b0d9612157816452b81ce
7
+ data.tar.gz: 2ee07d9fa63ebb9bb7ec9f4d620eef2ea98166fe25464497c42ecfebb533cdbb3e392fcd0bcdd4eeb4eb586452bf707797f86b4ad5b9059392ec9943d832da21
data/Gemfile CHANGED
@@ -6,9 +6,15 @@ source "http://rubygems.org"
6
6
  # Add dependencies to develop your gem here.
7
7
  # Include everything needed to run rake, tests, features, etc.
8
8
  group :development do
9
- gem "rspec", "~> 2.8.0"
9
+ gem "rails", "~> 4.0.10"
10
+ gem "rspec-rails", "~> 3.1.0"
10
11
  gem "rdoc", "~> 3.12"
11
12
  gem "bundler", ">= 1.0.0"
12
13
  gem "jeweler", ">= 1.8.4"
13
14
  gem "builder"
15
+ gem "activerecord"
16
+ gem "mysql2"
17
+ gem "pry"
14
18
  end
19
+
20
+ gem "codeclimate-test-reporter", group: :test, require: nil
@@ -1,11 +1,41 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
+ actionmailer (4.0.12)
5
+ actionpack (= 4.0.12)
6
+ mail (~> 2.5, >= 2.5.4)
7
+ actionpack (4.0.12)
8
+ activesupport (= 4.0.12)
9
+ builder (~> 3.1.0)
10
+ erubis (~> 2.7.0)
11
+ rack (~> 1.5.2)
12
+ rack-test (~> 0.6.2)
13
+ activemodel (4.0.12)
14
+ activesupport (= 4.0.12)
15
+ builder (~> 3.1.0)
16
+ activerecord (4.0.12)
17
+ activemodel (= 4.0.12)
18
+ activerecord-deprecated_finders (~> 1.0.2)
19
+ activesupport (= 4.0.12)
20
+ arel (~> 4.0.0)
21
+ activerecord-deprecated_finders (1.0.3)
22
+ activesupport (4.0.12)
23
+ i18n (~> 0.6, >= 0.6.9)
24
+ minitest (~> 4.2)
25
+ multi_json (~> 1.3)
26
+ thread_safe (~> 0.1)
27
+ tzinfo (~> 0.3.37)
4
28
  addressable (2.3.6)
5
- builder (3.2.2)
29
+ arel (4.0.2)
30
+ builder (3.1.4)
31
+ codeclimate-test-reporter (0.4.2)
32
+ simplecov (>= 0.7.1, < 1.0.0)
33
+ coderay (1.1.0)
6
34
  descendants_tracker (0.0.4)
7
35
  thread_safe (~> 0.3, >= 0.3.1)
8
- diff-lcs (1.1.3)
36
+ diff-lcs (1.2.5)
37
+ docile (1.1.5)
38
+ erubis (2.7.0)
9
39
  faraday (0.9.0)
10
40
  multipart-post (>= 1.2, < 3)
11
41
  git (1.2.6)
@@ -19,6 +49,8 @@ GEM
19
49
  oauth2
20
50
  hashie (2.1.1)
21
51
  highline (1.6.21)
52
+ hike (1.2.3)
53
+ i18n (0.6.11)
22
54
  jeweler (2.0.1)
23
55
  builder
24
56
  bundler (>= 1.0)
@@ -31,10 +63,16 @@ GEM
31
63
  json (1.8.1)
32
64
  jwt (0.1.13)
33
65
  multi_json (>= 1.5)
66
+ mail (2.6.3)
67
+ mime-types (>= 1.16, < 3)
68
+ method_source (0.8.2)
69
+ mime-types (2.4.3)
34
70
  mini_portile (0.6.0)
71
+ minitest (4.7.5)
35
72
  multi_json (1.10.0)
36
73
  multi_xml (0.5.5)
37
74
  multipart-post (2.0.0)
75
+ mysql2 (0.3.16)
38
76
  nokogiri (1.6.2.1)
39
77
  mini_portile (= 0.6.0)
40
78
  oauth2 (0.9.3)
@@ -43,26 +81,76 @@ GEM
43
81
  multi_json (~> 1.3)
44
82
  multi_xml (~> 0.5)
45
83
  rack (~> 1.2)
84
+ pry (0.10.1)
85
+ coderay (~> 1.1.0)
86
+ method_source (~> 0.8.1)
87
+ slop (~> 3.4)
46
88
  rack (1.5.2)
89
+ rack-test (0.6.2)
90
+ rack (>= 1.0)
91
+ rails (4.0.12)
92
+ actionmailer (= 4.0.12)
93
+ actionpack (= 4.0.12)
94
+ activerecord (= 4.0.12)
95
+ activesupport (= 4.0.12)
96
+ bundler (>= 1.3.0, < 2.0)
97
+ railties (= 4.0.12)
98
+ sprockets-rails (~> 2.0)
99
+ railties (4.0.12)
100
+ actionpack (= 4.0.12)
101
+ activesupport (= 4.0.12)
102
+ rake (>= 0.8.7)
103
+ thor (>= 0.18.1, < 2.0)
47
104
  rake (10.3.2)
48
105
  rdoc (3.12.2)
49
106
  json (~> 1.4)
50
- rspec (2.8.0)
51
- rspec-core (~> 2.8.0)
52
- rspec-expectations (~> 2.8.0)
53
- rspec-mocks (~> 2.8.0)
54
- rspec-core (2.8.0)
55
- rspec-expectations (2.8.0)
56
- diff-lcs (~> 1.1.2)
57
- rspec-mocks (2.8.0)
58
- thread_safe (0.3.3)
107
+ rspec-core (3.1.7)
108
+ rspec-support (~> 3.1.0)
109
+ rspec-expectations (3.1.2)
110
+ diff-lcs (>= 1.2.0, < 2.0)
111
+ rspec-support (~> 3.1.0)
112
+ rspec-mocks (3.1.3)
113
+ rspec-support (~> 3.1.0)
114
+ rspec-rails (3.1.0)
115
+ actionpack (>= 3.0)
116
+ activesupport (>= 3.0)
117
+ railties (>= 3.0)
118
+ rspec-core (~> 3.1.0)
119
+ rspec-expectations (~> 3.1.0)
120
+ rspec-mocks (~> 3.1.0)
121
+ rspec-support (~> 3.1.0)
122
+ rspec-support (3.1.2)
123
+ simplecov (0.9.1)
124
+ docile (~> 1.1.0)
125
+ multi_json (~> 1.0)
126
+ simplecov-html (~> 0.8.0)
127
+ simplecov-html (0.8.0)
128
+ slop (3.6.0)
129
+ sprockets (2.12.3)
130
+ hike (~> 1.2)
131
+ multi_json (~> 1.0)
132
+ rack (~> 1.0)
133
+ tilt (~> 1.1, != 1.3.0)
134
+ sprockets-rails (2.2.2)
135
+ actionpack (>= 3.0)
136
+ activesupport (>= 3.0)
137
+ sprockets (>= 2.8, < 4.0)
138
+ thor (0.19.1)
139
+ thread_safe (0.3.4)
140
+ tilt (1.4.1)
141
+ tzinfo (0.3.42)
59
142
 
60
143
  PLATFORMS
61
144
  ruby
62
145
 
63
146
  DEPENDENCIES
147
+ activerecord
64
148
  builder
65
149
  bundler (>= 1.0.0)
150
+ codeclimate-test-reporter
66
151
  jeweler (>= 1.8.4)
152
+ mysql2
153
+ pry
154
+ rails (~> 4.0.10)
67
155
  rdoc (~> 3.12)
68
- rspec (~> 2.8.0)
156
+ rspec-rails (~> 3.1.0)
@@ -0,0 +1,86 @@
1
+ [![Build Status](https://api.shippable.com/projects/540e7b993479c5ea8f9ec1f2/badge?branchName=master)](https://app.shippable.com/projects/540e7b993479c5ea8f9ec1f2/builds/latest)
2
+ [![Code Climate](https://codeclimate.com/github/kaspernj/active-record-transactioner/badges/gpa.svg)](https://codeclimate.com/github/kaspernj/active-record-transactioner)
3
+ [![Test Coverage](https://codeclimate.com/github/kaspernj/active-record-transactioner/badges/coverage.svg)](https://codeclimate.com/github/kaspernj/active-record-transactioner)
4
+
5
+ # active-record-transactioner
6
+
7
+ Queue saving and destroying of many models into transactions through multiple threads for optimal database-performance in ActiveRecord.
8
+
9
+ ## Install
10
+
11
+ Add to your Gemfile and bundle:
12
+ ```ruby
13
+ gem 'active-record-transactioner'
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ ### Iterate a million times - will update each 1000 records in a single transaction with `save!`.
19
+ ```ruby
20
+ ActiveRecordTransactioner.new do |trans|
21
+ models.each do |model|
22
+ model.some_attribute = "some_value"
23
+ trans.save!(model)
24
+ end
25
+ end
26
+ ```
27
+
28
+ You can also do it a bit more complicated with some custom options.
29
+ ```ruby
30
+ ActiveRecordTransactioner.new(
31
+ call_args: ["Hello world!"],
32
+ call_method: :save!,
33
+ transaction_method: :transaction,
34
+ transaction_size: 1000,
35
+ threadded: false
36
+ ) do |trans|
37
+ models.each do |model|
38
+ model.some_attribute = "some_value"
39
+ trans.save!(model)
40
+ end
41
+ end
42
+ ```
43
+
44
+ ### Destroy
45
+ ```ruby
46
+ ActiveRecordTransactioner.new do |trans|
47
+ models.each do |model|
48
+ trans.destroy!(model)
49
+ end
50
+ end
51
+ ```
52
+
53
+ ### Threadded
54
+
55
+ The "threadded" and "max_running_threads" options will start new threads to actually do the saving of the models, while continuing to queue up new models for saving in the primary thread. This way the database can utilize multiple cores, and if you use a threadded VM like JRuby or Rubinius, you will utilize even more cores.
56
+
57
+ This can help greatly speed up the processing.
58
+
59
+ Be aware that the saving of only one type of model, will be limited to only one thread, so it will make sense to try and queue up as many type of models as possible. Like users, orders and so on.
60
+
61
+ ```ruby
62
+ ActiveRecordTransactioner.new(
63
+ threadded: true,
64
+ max_running_threads: 3
65
+ ) do |trans|
66
+ models.each do |model|
67
+ model.some_attribute = "some_value"
68
+ trans.save!(model)
69
+ end
70
+ end
71
+ ```
72
+
73
+ ## Contributing to active-record-transactioner
74
+
75
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
76
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
77
+ * Fork the project.
78
+ * Start a feature/bugfix branch.
79
+ * Commit and push until you are happy with your contribution.
80
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
81
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
82
+
83
+ ## Copyright
84
+
85
+ Copyright (c) 2013 Kasper Johansen. See LICENSE.txt for
86
+ further details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.5
1
+ 0.0.6
@@ -2,19 +2,21 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
+ # stub: active-record-transactioner 0.0.6 ruby lib
5
6
 
6
7
  Gem::Specification.new do |s|
7
8
  s.name = "active-record-transactioner"
8
- s.version = "0.0.5"
9
+ s.version = "0.0.6"
9
10
 
10
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
+ s.require_paths = ["lib"]
11
13
  s.authors = ["Kasper Johansen"]
12
- s.date = "2014-05-18"
14
+ s.date = "2014-12-05"
13
15
  s.description = "Queue up calls to specific models and execute them in transactions, after a certain number of models have been added."
14
16
  s.email = "kj@gfish.com"
15
17
  s.extra_rdoc_files = [
16
18
  "LICENSE.txt",
17
- "README.rdoc"
19
+ "README.md"
18
20
  ]
19
21
  s.files = [
20
22
  ".document",
@@ -22,43 +24,100 @@ Gem::Specification.new do |s|
22
24
  "Gemfile",
23
25
  "Gemfile.lock",
24
26
  "LICENSE.txt",
25
- "README.rdoc",
27
+ "README.md",
26
28
  "Rakefile",
27
29
  "VERSION",
28
30
  "active-record-transactioner.gemspec",
29
31
  "lib/active-record-transactioner.rb",
32
+ "shippable.yml",
30
33
  "spec/active-record-transactioner_spec.rb",
34
+ "spec/dummy/README.rdoc",
35
+ "spec/dummy/Rakefile",
36
+ "spec/dummy/app/assets/images/.keep",
37
+ "spec/dummy/app/assets/javascripts/application.js",
38
+ "spec/dummy/app/assets/stylesheets/application.css",
39
+ "spec/dummy/app/controllers/application_controller.rb",
40
+ "spec/dummy/app/controllers/concerns/.keep",
41
+ "spec/dummy/app/helpers/application_helper.rb",
42
+ "spec/dummy/app/mailers/.keep",
43
+ "spec/dummy/app/models/concerns/.keep",
44
+ "spec/dummy/app/models/user.rb",
45
+ "spec/dummy/app/views/layouts/application.html.erb",
46
+ "spec/dummy/bin/bundle",
47
+ "spec/dummy/bin/rails",
48
+ "spec/dummy/bin/rake",
49
+ "spec/dummy/config.ru",
50
+ "spec/dummy/config/application.rb",
51
+ "spec/dummy/config/boot.rb",
52
+ "spec/dummy/config/database.example.yml",
53
+ "spec/dummy/config/database.shippable.yml",
54
+ "spec/dummy/config/database.yml",
55
+ "spec/dummy/config/environment.rb",
56
+ "spec/dummy/config/environments/development.rb",
57
+ "spec/dummy/config/environments/production.rb",
58
+ "spec/dummy/config/environments/test.rb",
59
+ "spec/dummy/config/initializers/backtrace_silencers.rb",
60
+ "spec/dummy/config/initializers/filter_parameter_logging.rb",
61
+ "spec/dummy/config/initializers/inflections.rb",
62
+ "spec/dummy/config/initializers/mime_types.rb",
63
+ "spec/dummy/config/initializers/secret_token.rb",
64
+ "spec/dummy/config/initializers/session_store.rb",
65
+ "spec/dummy/config/initializers/wrap_parameters.rb",
66
+ "spec/dummy/config/locales/en.yml",
67
+ "spec/dummy/config/routes.rb",
68
+ "spec/dummy/db/migrate/20141203180942_create_users.rb",
69
+ "spec/dummy/db/schema.rb",
70
+ "spec/dummy/lib/assets/.keep",
71
+ "spec/dummy/log/.keep",
72
+ "spec/dummy/public/404.html",
73
+ "spec/dummy/public/422.html",
74
+ "spec/dummy/public/500.html",
75
+ "spec/dummy/public/favicon.ico",
76
+ "spec/models/user_spec.rb",
77
+ "spec/models/user_threadded_spec.rb",
31
78
  "spec/spec_helper.rb",
79
+ "spec/support/basic_user_operations.rb",
32
80
  "spec/test_classes/active-record-transactioner-test-class.rb"
33
81
  ]
34
82
  s.homepage = "http://github.com/kaspernj/active-record-transactioner"
35
83
  s.licenses = ["MIT"]
36
- s.require_paths = ["lib"]
37
- s.rubygems_version = "2.0.7"
84
+ s.rubygems_version = "2.4.0"
38
85
  s.summary = "Queue up calls to specific models and execute them in transactions, after a certain number of models have been added."
39
86
 
40
87
  if s.respond_to? :specification_version then
41
88
  s.specification_version = 4
42
89
 
43
90
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
44
- s.add_development_dependency(%q<rspec>, ["~> 2.8.0"])
91
+ s.add_development_dependency(%q<rails>, ["~> 4.0.10"])
92
+ s.add_development_dependency(%q<rspec-rails>, ["~> 3.1.0"])
45
93
  s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
46
94
  s.add_development_dependency(%q<bundler>, [">= 1.0.0"])
47
95
  s.add_development_dependency(%q<jeweler>, [">= 1.8.4"])
48
96
  s.add_development_dependency(%q<builder>, [">= 0"])
97
+ s.add_development_dependency(%q<activerecord>, [">= 0"])
98
+ s.add_development_dependency(%q<mysql2>, [">= 0"])
99
+ s.add_development_dependency(%q<pry>, [">= 0"])
49
100
  else
50
- s.add_dependency(%q<rspec>, ["~> 2.8.0"])
101
+ s.add_dependency(%q<rails>, ["~> 4.0.10"])
102
+ s.add_dependency(%q<rspec-rails>, ["~> 3.1.0"])
51
103
  s.add_dependency(%q<rdoc>, ["~> 3.12"])
52
104
  s.add_dependency(%q<bundler>, [">= 1.0.0"])
53
105
  s.add_dependency(%q<jeweler>, [">= 1.8.4"])
54
106
  s.add_dependency(%q<builder>, [">= 0"])
107
+ s.add_dependency(%q<activerecord>, [">= 0"])
108
+ s.add_dependency(%q<mysql2>, [">= 0"])
109
+ s.add_dependency(%q<pry>, [">= 0"])
55
110
  end
56
111
  else
57
- s.add_dependency(%q<rspec>, ["~> 2.8.0"])
112
+ s.add_dependency(%q<rails>, ["~> 4.0.10"])
113
+ s.add_dependency(%q<rspec-rails>, ["~> 3.1.0"])
58
114
  s.add_dependency(%q<rdoc>, ["~> 3.12"])
59
115
  s.add_dependency(%q<bundler>, [">= 1.0.0"])
60
116
  s.add_dependency(%q<jeweler>, [">= 1.8.4"])
61
117
  s.add_dependency(%q<builder>, [">= 0"])
118
+ s.add_dependency(%q<activerecord>, [">= 0"])
119
+ s.add_dependency(%q<mysql2>, [">= 0"])
120
+ s.add_dependency(%q<pry>, [">= 0"])
62
121
  end
63
122
  end
64
123
 
@@ -2,147 +2,184 @@ require "monitor"
2
2
 
3
3
  class ActiveRecordTransactioner
4
4
  DEFAULT_ARGS = {
5
- :call_args => [],
6
- :call_method => :save!,
7
- :transaction_method => :transaction,
8
- :transaction_size => 1000,
9
- :max_running_threads => 2,
10
- :debug => false
5
+ call_args: [],
6
+ call_method: :save!,
7
+ transaction_method: :transaction,
8
+ transaction_size: 1000,
9
+ threadded: false,
10
+ max_running_threads: 2,
11
+ debug: false
11
12
  }
12
-
13
+
13
14
  ALLOWED_ARGS = DEFAULT_ARGS.keys
14
-
15
+
15
16
  def initialize(args = {})
16
- args.each do |key, val|
17
- raise "Invalid key: '#{key}'." unless ALLOWED_ARGS.include?(key)
18
- end
19
-
17
+ args.each { |key, val| raise "Invalid key: '#{key}'." unless ALLOWED_ARGS.include?(key) }
18
+
20
19
  @args = DEFAULT_ARGS.merge(args)
21
- @models = {}
22
- @threads = []
23
- @count = 0
24
- @lock = Monitor.new
25
- @lock_threads = Monitor.new
26
- @lock_models = {}
27
- @debug = @args[:debug]
28
-
20
+ parse_and_set_args
21
+
29
22
  if block_given?
30
23
  begin
31
24
  yield self
32
25
  ensure
33
26
  flush
34
- join
27
+ join if threadded?
35
28
  end
36
29
  end
37
30
  end
38
-
39
- #Adds another model to the queue and calls 'flush' if it is over the limit.
40
- def queue(model)
31
+
32
+ # Adds another model to the queue and calls 'flush' if it is over the limit.
33
+ def save!(model)
34
+ raise ActiveRecord::RecordInvalid, model unless model.valid?
35
+ queue(model, type: :save!, validate: false)
36
+ end
37
+
38
+ def destroy!(model)
39
+ queue(model, type: :destroy!)
40
+ end
41
+
42
+ # Adds another model to the queue and calls 'flush' if it is over the limit.
43
+ def queue(model, args = {})
44
+ args[:type] ||= :save!
45
+
41
46
  @lock.synchronize do
42
47
  klass = model.class
43
-
44
- @lock_models[klass] = Mutex.new if !@lock_models.key?(klass)
45
- @models[klass] = [] if !@models.key?(klass)
46
- @models[klass] << model
48
+
49
+ validate = args.key?(:validate) ? args[:validate] : true
50
+
51
+ @lock_models[klass] ||= Monitor.new
52
+ @models[klass] ||= []
53
+ @models[klass] << {model: model, type: args[:type], validate: validate}
47
54
  @count += 1
48
55
  end
49
-
50
- flush if @count >= @args[:transaction_size]
56
+
57
+ flush if should_flush?
51
58
  end
52
-
53
- #Flushes the specified method on all the queued models in a thread for each type of model.
59
+
60
+ # Flushes the specified method on all the queued models in a thread for each type of model.
54
61
  def flush
55
- threads = []
56
- wait_for_threads
57
-
62
+ wait_for_threads if threadded?
63
+
58
64
  @lock.synchronize do
59
- @models.each do |klass, val|
60
- next if val.empty?
61
-
62
- models = val
65
+ @models.each do |klass, models|
66
+ next if models.empty?
67
+
63
68
  @models[klass] = []
64
69
  @count -= models.length
65
- thread = nil
66
-
67
- @lock_models[klass].synchronize do
68
- thread = Thread.new do
69
- begin
70
- @lock_models[klass].synchronize do
71
- debug "Opening new transaction by using '#{@args[:transaction_method]}'."
72
- klass.__send__(@args[:transaction_method]) do
73
- models.each do |model|
74
- # debug "Saving #{model.class.name}(#{model.id}) with method #{@args[:call_method]}"
75
- model.__send__(@args[:call_method], *@args[:call_args])
76
- end
77
- end
78
- end
79
- rescue => e
80
- puts e.inspect
81
- puts e.backtrace
82
-
83
- if e.is_a?(NoMethodError) and e.message.to_s.include?("`reverse' for nil:NilClass")
84
- puts "Warning: Known Rails reverse error when using transaction - retrying in 2 sec."
85
- sleep 2
86
- puts "Retrying"
87
- puts
88
- retry
89
- end
90
-
91
- raise e
92
- ensure
93
- debug "Removing thread #{Thread.current.__id__}"
94
- @threads.delete(Thread.current)
95
-
96
- @lock.synchronize do
97
- ActiveRecord::Base.connection.close if ActiveRecord::Base.connection
98
- end
99
- end
100
- end
101
- end
102
-
103
- @lock_threads.synchronize do
104
- threads << thread
105
- @threads << thread
70
+
71
+ if threadded?
72
+ work_threadded(klass, models)
73
+ else
74
+ work_models_through_transaction(klass, models)
106
75
  end
107
76
  end
108
77
  end
109
-
110
- return {
111
- :threads => threads
112
- }
113
78
  end
114
-
115
- #Waits for any remaining running threads.
79
+
80
+ # Waits for any remaining running threads.
116
81
  def join
117
- @lock_threads.synchronize do
118
- @threads.each do |thread|
119
- thread.join
120
- end
82
+ threads_to_join = @lock_threads.synchronize { @threads.clone }
83
+
84
+ debug "Threads to join: #{threads_to_join}" if @debug
85
+ threads_to_join.each do |thread|
86
+ thread.join
121
87
  end
122
88
  end
123
-
89
+
90
+ def threadded?
91
+ @args[:threadded]
92
+ end
93
+
124
94
  private
125
-
95
+
96
+ def parse_and_set_args
97
+ @models = {}
98
+ @threads = []
99
+ @count = 0
100
+ @lock = Monitor.new
101
+ @lock_threads = Monitor.new
102
+ @lock_models = {}
103
+ @max_running_threads = @args[:max_running_threads].to_i
104
+ @transaction_size = @args[:transaction_size].to_i
105
+ @debug = @args[:debug]
106
+ end
107
+
126
108
  def debug(str)
127
- puts "{ActiveRecordTransactioner}: #{str}" if @debug
109
+ print "{ActiveRecordTransactioner}: #{str}\n" if @debug
128
110
  end
129
-
111
+
130
112
  def wait_for_threads
131
113
  break_loop = false
132
114
  while !break_loop
133
- debug "Trying to lock..." if @debug
134
- @lock.synchronize do
135
- debug "Running threads: #{@threads.length} / #{@args[:max_running_threads]}"
136
- if @threads.length < @args[:max_running_threads]
137
- break_loop = true
138
- else
139
- debug "Waiting for threads #{@threads.length} / #{@args[:max_running_threads]}" if @debug
140
- end
115
+ debug "Running threads: #{@threads.length} / #{@max_running_threads}" if @debug
116
+ if allowed_to_start_new_thread?
117
+ break_loop = true
118
+ else
119
+ debug "Waiting for threads #{@threads.length} / #{@max_running_threads}" if @debug
141
120
  end
142
-
143
- sleep 0.2
121
+
122
+ sleep 0.2 unless break_loop
144
123
  end
145
-
124
+
146
125
  debug "Done waiting." if @debug
147
126
  end
148
- end
127
+
128
+ def work_models_through_transaction(klass, models)
129
+ debug "Synchronizing model: #{klass.name}"
130
+
131
+ @lock_models[klass].synchronize do
132
+ debug "Opening new transaction by using '#{@args[:transaction_method]}'." if @debug
133
+
134
+ klass.__send__(@args[:transaction_method]) do
135
+ debug "Going through models." if @debug
136
+ models.each do |work|
137
+ debug work if @debug
138
+
139
+ if work[:type] == :save!
140
+ validate = work.key?(:validate) ? work[:validate] : true
141
+ work[:model].save! validate: validate
142
+ elsif work[:type] == :destroy!
143
+ work[:model].destroy!
144
+ else
145
+ raise "Invalid type: '#{work[:type]}'."
146
+ end
147
+ end
148
+
149
+ debug "Done working with models." if @debug
150
+ end
151
+ end
152
+ end
153
+
154
+ def work_threadded(klass, models)
155
+ @lock_threads.synchronize do
156
+ @threads << Thread.new do
157
+ begin
158
+ ActiveRecord::Base.connection_pool.with_connection do
159
+ work_models_through_transaction(klass, models)
160
+ end
161
+ rescue => e
162
+ pute e.inspect
163
+ puts e.backtrace
164
+
165
+ raise e
166
+ ensure
167
+ debug "Removing thread #{Thread.current.__id__}" if @debug
168
+ @lock_threads.synchronize { @threads.delete(Thread.current) }
169
+
170
+ debug "Threads count after remove: #{@threads.length}" if @debug
171
+ end
172
+ end
173
+ end
174
+
175
+ debug "Threads-count after started to work: #{@threads.length}"
176
+ end
177
+
178
+ def should_flush?
179
+ @count >= @transaction_size
180
+ end
181
+
182
+ def allowed_to_start_new_thread?
183
+ @lock_threads.synchronize { return @threads.length < @max_running_threads }
184
+ end
185
+ end