activerecord5_delay_touching 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2ade7a8b8641da8a585a1428d7912234df0c15d4
4
+ data.tar.gz: 52b4ea7dd787480d6ca58d8bbfd19d4ff550a9eb
5
+ SHA512:
6
+ metadata.gz: 56a9977a19d7db5a7a2f152ca0e0c5099c55129f612ba05e1b864968d76f7fc1f48d91d386abba9e1256cec2f413d60c3f7221ab326ed3daaf00e1e84a30c887
7
+ data.tar.gz: e8058a3c8423fbb51153e9e0f70d6b7ad774d95f691112eac906e7c3adb8ec79a43b97e4f680f16f9ce5447ad2874ed07b2ae03908272a01f2c8d85b45610c17
@@ -0,0 +1,26 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .idea
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ *.bundle
20
+ *.so
21
+ *.o
22
+ *.a
23
+ mkmf.log
24
+ .ruby-version
25
+ coverage/
26
+ results.xml
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --tty
3
+ --format documentation
4
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in activerecord5_delay_touching.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 GoDaddy
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,110 @@
1
+ # Activerecord::DelayTouching
2
+
3
+ > **Note:** this version requires ActiveRecord 4.2 or higher. To use ActiveRecord 3.2 through 4.1, use the branch https://github.com/Kenneth-KT/activerecord5_delay_touching/tree/pre-activerecord-4.2.
4
+
5
+ Batch up your ActiveRecord "touch" operations for better performance.
6
+
7
+ When you want to invalidate a cache in Rails, you use `touch: true`. But when
8
+ you modify a bunch of records that all `belong_to` the same owning record, that record
9
+ will be touched N times. It's incredibly slow.
10
+
11
+ With this gem, all `touch` operations are consolidated into as few database
12
+ round-trips as possible. Instead of N touches you get 1 touch.
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ gem 'activerecord5_delay_touching'
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ Or install it yourself:
25
+
26
+ $ gem install activerecord5_delay_touching
27
+
28
+ ## Usage
29
+
30
+ The setup:
31
+
32
+ class Person < ActiveRecord::Base
33
+ has_many :pets
34
+ accepts_nested_attributes_for :pets
35
+ end
36
+
37
+ class Pet < ActiveRecord::Base
38
+ belongs_to :person, touch: true
39
+ end
40
+
41
+ Without `delay_touching`, this simple `update` in the controller calls
42
+ `@person.touch` N times, where N is the number of pets that were updated
43
+ via nested attributes. That's N-1 unnecessary round-trips to the database:
44
+
45
+ class PeopleController < ApplicationController
46
+ def update
47
+ ...
48
+ #
49
+ @person.update(person_params)
50
+ ...
51
+ end
52
+ end
53
+
54
+ # SQL (0.1ms) UPDATE "people" SET "updated_at" = '2014-07-09 19:48:07.137158' WHERE "people"."id" = 1
55
+ # SQL (0.1ms) UPDATE "people" SET "updated_at" = '2014-07-09 19:48:07.138457' WHERE "people"."id" = 1
56
+ # SQL (0.1ms) UPDATE "people" SET "updated_at" = '2014-07-09 19:48:07.140088' WHERE "people"."id" = 1
57
+
58
+ With `delay_touching`, @person is touched only once:
59
+
60
+ ActiveRecord::Base.delay_touching do
61
+ @person.update(person_params)
62
+ end
63
+
64
+ # SQL (0.1ms) UPDATE "people" SET "updated_at" = '2014-07-09 19:48:07.140088' WHERE "people"."id" = 1
65
+
66
+ ## Consolidates Touches Per Table
67
+
68
+ In the following example, a person gives his pet to another person. ActiveRecord
69
+ automatically touches the old person and the new person. With `delay_touching`,
70
+ this will only make a *single* round-trip to the database, setting `updated_at`
71
+ for all Person records in a single SQL UPDATE statement. Not a big deal when there are
72
+ only two touches, but when you're updating records en masse and have a cascade
73
+ of hundreds touches, it really is a big deal.
74
+
75
+ class Pet < ActiveRecord::Base
76
+ belongs_to :person, touch: true
77
+
78
+ def give(to_person)
79
+ ActiveRecord::Base.delay_touching do
80
+ self.person = to_person
81
+ save! # touches old person and new person in a single SQL UPDATE.
82
+ end
83
+ end
84
+ end
85
+
86
+ ## Cascading Touches
87
+
88
+ When `delay_touch` runs through and touches everything, it captures additional
89
+ `touch` calls that might be called as side-effects. (E.g., in `after_touch`
90
+ handlers.) Then it makes a second pass, batching up those touches as well.
91
+
92
+ It keeps doing this until there are no more touches, or until the sun swallows
93
+ up the earth. Whichever comes first.
94
+
95
+ ## Gotchas
96
+
97
+ Things to note:
98
+
99
+ * `after_touch` callbacks are still fired for every instance, but not until the block is exited.
100
+ And they won't happen in the same order as they would if you weren't batching up your touches.
101
+ * If you call person1.touch and then person2.touch, and they are two separate instances
102
+ with the same id, only person1's `after_touch` handler will be called.
103
+
104
+ ## Contributing
105
+
106
+ 1. Fork it ( https://github.com/Kenneth-KT/activerecord5_delay_touching/fork )
107
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
108
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
109
+ 4. Push to the branch (`git push origin my-new-feature`)
110
+ 5. Create a new Pull Request
@@ -0,0 +1,16 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core"
3
+ require "rspec/core/rake_task"
4
+
5
+ Rake::Task["spec"].clear
6
+ RSpec::Core::RakeTask.new(:spec) do |t|
7
+ t.fail_on_error = false
8
+ t.rspec_opts = %w[-f JUnit -o results.xml]
9
+ end
10
+
11
+ desc "Run RSpec with code coverage"
12
+ task :coverage do
13
+ ENV['COVERAGE'] = 'true'
14
+ Rake::Task["spec"].execute
15
+ end
16
+ task :default => :spec
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'activerecord/delay_touching/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "activerecord5_delay_touching"
8
+ spec.version = Activerecord::DelayTouching::VERSION
9
+ spec.authors = ["GoDaddy P&C Commerce", "Brian Morearty"]
10
+ spec.email = ["nemo-engg@godaddy.com", "brian@morearty.org"]
11
+ spec.summary = %q{Batch up your ActiveRecord "touch" operations for better performance.}
12
+ spec.description = %q{Batch up your ActiveRecord "touch" operations for better performance. ActiveRecord::Base.delay_touching do ... end. When "end" is reached, all accumulated "touch" calls will be consolidated into as few database round trips as possible.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activerecord", ">= 4.2", "< 5.3"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.6"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "sqlite3"
26
+ spec.add_development_dependency "timecop"
27
+ spec.add_development_dependency "rspec-rails", "~> 3.0"
28
+ spec.add_development_dependency "simplecov"
29
+ spec.add_development_dependency "simplecov-rcov"
30
+ spec.add_development_dependency "yarjuf"
31
+ end
@@ -0,0 +1,123 @@
1
+ require "activerecord/delay_touching/version"
2
+ require "activerecord/delay_touching/state"
3
+
4
+ module ActiveRecord
5
+ module DelayTouching
6
+ extend ActiveSupport::Concern
7
+
8
+ # Override ActiveRecord::Base#touch.
9
+ if ActiveRecord::VERSION::MAJOR >= 5
10
+ def touch(*names, time: nil)
11
+ names = self.class.send(:timestamp_attributes_for_update_in_model) if names.empty?
12
+ DelayTouching.handle_touch(self, names) || super
13
+ end
14
+ else
15
+ def touch(*names)
16
+ DelayTouching.handle_touch(self, names) || super
17
+ end
18
+ end
19
+
20
+ # These get added as class methods to ActiveRecord::Base.
21
+ module ClassMethods
22
+ # Lets you batch up your `touch` calls for the duration of a block.
23
+ #
24
+ # ==== Examples
25
+ #
26
+ # # Touches Person.first once, not twice, when the block exits.
27
+ # ActiveRecord::Base.delay_touching do
28
+ # Person.first.touch
29
+ # Person.first.touch
30
+ # end
31
+ #
32
+ def delay_touching(&block)
33
+ DelayTouching.call &block
34
+ end
35
+
36
+ # Are we currently executing in a delay_touching block?
37
+ def delay_touching?
38
+ DelayTouching.state.nesting > 0
39
+ end
40
+ end
41
+
42
+ def self.state
43
+ Thread.current[:delay_touching_state] ||= State.new
44
+ end
45
+
46
+ class << self
47
+ delegate :add_record, to: :state
48
+ end
49
+
50
+ def self.handle_touch(record, names)
51
+ if record.class.delay_touching? && !record.try(:no_touching?)
52
+ add_record(record, *names)
53
+ true
54
+ end
55
+ end
56
+
57
+ # Start delaying all touches. When done, apply them. (Unless nested.)
58
+ def self.call
59
+ state.nesting += 1
60
+ begin
61
+ yield
62
+ ensure
63
+ apply if state.nesting == 1
64
+ end
65
+ ensure
66
+ # Decrement nesting even if `apply` raised an error.
67
+ state.nesting -= 1
68
+ end
69
+
70
+ # Apply the touches that were delayed.
71
+ def self.apply
72
+ begin
73
+ ActiveRecord::Base.transaction do
74
+ state.records_by_attrs_and_class.each do |attr, classes_and_records|
75
+ classes_and_records.each do |klass, records|
76
+ touch_records attr, klass, records
77
+ end
78
+ end
79
+ end
80
+ end while state.more_records?
81
+ ensure
82
+ state.clear_records
83
+ end
84
+
85
+ # Touch the specified records--non-empty set of instances of the same class.
86
+ def self.touch_records(attr, klass, records)
87
+ attributes = records.first.send(:timestamp_attributes_for_update_in_model)
88
+ attributes << attr if attr
89
+
90
+ if attributes.present?
91
+ current_time = records.first.send(:current_time_from_proper_timezone)
92
+ changes = {}
93
+
94
+ attributes.each do |column|
95
+ column = column.to_s
96
+ changes[column] = current_time
97
+ records.each do |record|
98
+ # Don't bother if destroyed or not-saved
99
+ next unless record.persisted?
100
+ record.send(:write_attribute, column, current_time)
101
+ clear_attribute_changes(record, changes.keys)
102
+ end
103
+ end
104
+
105
+ klass.unscoped.where(klass.primary_key => records).update_all(changes)
106
+ end
107
+ state.updated attr, records
108
+ records.each { |record| record.run_callbacks(:touch) }
109
+ end
110
+
111
+ if ActiveRecord::VERSION::MAJOR >= 5
112
+ def self.clear_attribute_changes(record, attr_names)
113
+ record.clear_attribute_changes(attr_names)
114
+ end
115
+ else
116
+ def self.clear_attribute_changes(record, attr_names)
117
+ record.instance_variable_get('@changed_attributes').except!(*attr_names)
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ ActiveRecord::Base.include ActiveRecord::DelayTouching
@@ -0,0 +1,58 @@
1
+ require "activerecord/delay_touching/version"
2
+
3
+ module ActiveRecord
4
+ module DelayTouching
5
+
6
+ # Tracking of the touch state. This class has no class-level data, so you can
7
+ # store per-thread instances in thread-local variables.
8
+ class State
9
+ attr_accessor :nesting
10
+
11
+ def initialize
12
+ @records = Hash.new { Set.new }
13
+ @already_updated_records = Hash.new { Set.new }
14
+ @nesting = 0
15
+ end
16
+
17
+ def updated(attr, records)
18
+ @records[attr].subtract records
19
+ @records.delete attr if @records[attr].empty?
20
+ @already_updated_records[attr] += records
21
+ end
22
+
23
+ # Return the records grouped by the attributes that were touched, and by class:
24
+ # [
25
+ # [
26
+ # nil, { Person => [ person1, person2 ], Pet => [ pet1 ] }
27
+ # ],
28
+ # [
29
+ # :neutered_at, { Pet => [ pet1 ] }
30
+ # ],
31
+ # ]
32
+ def records_by_attrs_and_class
33
+ @records.map { |attrs, records| [attrs, records.group_by(&:class)] }
34
+ end
35
+
36
+ # There are more records as long as there is at least one record that is persisted
37
+ def more_records?
38
+ @records.each do |_, set|
39
+ set.each { |record| return true if record.persisted? } # will shortcut on first persisted record found
40
+ end
41
+
42
+ false # no persisted records found, so no more records to process
43
+ end
44
+
45
+ def add_record(record, *columns)
46
+ columns << nil if columns.empty? #if no arguments are passed, we will use nil to infer default column
47
+ columns.each do |column|
48
+ @records[column] += [ record ] unless @already_updated_records[column].include?(record)
49
+ end
50
+ end
51
+
52
+ def clear_records
53
+ @records.clear
54
+ @already_updated_records.clear
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,5 @@
1
+ module Activerecord
2
+ module DelayTouching
3
+ VERSION = "1.1.1"
4
+ end
5
+ end
@@ -0,0 +1 @@
1
+ require "activerecord/delay_touching"
@@ -0,0 +1,220 @@
1
+ require 'spec_helper'
2
+
3
+ describe Activerecord::DelayTouching do
4
+ let(:person) { Person.create name: "Rosey" }
5
+ let(:pet1) { Pet.create(name: "Bones") }
6
+ let(:pet2) { Pet.create(name: "Ema") }
7
+
8
+ it 'has a version number' do
9
+ expect(Activerecord::DelayTouching::VERSION).not_to be nil
10
+ end
11
+
12
+ it 'touch returns true' do
13
+ ActiveRecord::Base.delay_touching do
14
+ expect(person.touch).to eq(true)
15
+ end
16
+ end
17
+
18
+ it 'consolidates touches on a single record' do
19
+ expect_updates ["people"] do
20
+ ActiveRecord::Base.delay_touching do
21
+ person.touch
22
+ person.touch
23
+ end
24
+ end
25
+ end
26
+
27
+ it 'sets updated_at on the in-memory instance when it eventually touches the record' do
28
+ original_time = new_time = nil
29
+
30
+ Timecop.freeze(2014, 7, 4, 12, 0, 0) do
31
+ original_time = Time.current
32
+ person.touch
33
+ end
34
+
35
+ Timecop.freeze(2014, 7, 10, 12, 0, 0) do
36
+ new_time = Time.current
37
+ ActiveRecord::Base.delay_touching do
38
+ person.touch
39
+ expect(person.updated_at).to eq(original_time)
40
+ expect(person.changed?).to be_falsey
41
+ end
42
+ end
43
+
44
+ expect(person.updated_at).to eq(new_time)
45
+ expect(person.changed?).to be_falsey
46
+ end
47
+
48
+ it 'does not mark the instance as changed when touch is called' do
49
+ ActiveRecord::Base.delay_touching do
50
+ person.touch
51
+ expect(person).not_to be_changed
52
+ end
53
+ end
54
+
55
+ it 'consolidates touches for all instances in a single table' do
56
+ expect_updates ["pets"] do
57
+ ActiveRecord::Base.delay_touching do
58
+ pet1.touch
59
+ pet2.touch
60
+ end
61
+ end
62
+ end
63
+
64
+ it 'does nothing if no_touching is on' do
65
+ if ActiveRecord::Base.respond_to?(:no_touching)
66
+ expect_updates [] do
67
+ ActiveRecord::Base.no_touching do
68
+ ActiveRecord::Base.delay_touching do
69
+ person.touch
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ it 'only applies touches for which no_touching is off' do
77
+ if Person.respond_to?(:no_touching)
78
+ expect_updates ["pets"] do
79
+ Person.no_touching do
80
+ ActiveRecord::Base.delay_touching do
81
+ person.touch
82
+ pet1.touch
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ it 'does not apply nested touches if no_touching was turned on inside delay_touching' do
90
+ if ActiveRecord::Base.respond_to?(:no_touching)
91
+ expect_updates [ "people" ] do
92
+ ActiveRecord::Base.delay_touching do
93
+ person.touch
94
+ ActiveRecord::Base.no_touching do
95
+ pet1.touch
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ it 'can update nonstandard columns' do
103
+ expect_updates [ "pets" => [ "updated_at", "neutered_at" ] ] do
104
+ ActiveRecord::Base.delay_touching do
105
+ pet1.touch :neutered_at
106
+ end
107
+ end
108
+ end
109
+
110
+ it 'splits up nonstandard column touches and standard column touches' do
111
+ expect_updates [ { "pets" => [ "updated_at", "neutered_at" ] }, { "pets" => [ "updated_at" ] } ] do
112
+ ActiveRecord::Base.delay_touching do
113
+ pet1.touch :neutered_at
114
+ pet2.touch
115
+ end
116
+ end
117
+ end
118
+
119
+ it 'can update multiple nonstandard columns of a single record in different calls to touch' do
120
+ expect_updates [ { "pets" => [ "updated_at", "neutered_at" ] }, { "pets" => [ "updated_at", "fed_at" ] } ] do
121
+ ActiveRecord::Base.delay_touching do
122
+ pet1.touch :neutered_at
123
+ pet1.touch :fed_at
124
+ end
125
+ end
126
+ end
127
+
128
+ context 'touch: true' do
129
+ before do
130
+ person.pets << pet1
131
+ person.pets << pet2
132
+ end
133
+
134
+ it 'consolidates touch: true touches' do
135
+ expect_updates [ "pets", "people" ] do
136
+ ActiveRecord::Base.delay_touching do
137
+ pet1.touch
138
+ pet2.touch
139
+ end
140
+ end
141
+ end
142
+
143
+ it 'does not touch the owning record via touch: true if it was already touched explicitly' do
144
+ expect_updates [ "pets", "people" ] do
145
+ ActiveRecord::Base.delay_touching do
146
+ person.touch
147
+ pet1.touch
148
+ pet2.touch
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ context 'dependent deletes' do
155
+
156
+ let(:post) { Post.create! }
157
+ let(:user) { User.create! }
158
+ let(:comment) { Comment.create! }
159
+
160
+ before do
161
+ post.comments << comment
162
+ user.comments << comment
163
+ end
164
+
165
+ it 'does not attempt to touch deleted records' do
166
+ expect do
167
+ ActiveRecord::Base.delay_touching do
168
+ post.destroy
169
+ end
170
+ end.not_to raise_error
171
+ expect(post.destroyed?).to eq true
172
+ end
173
+
174
+ end
175
+
176
+ context 'persistence fails and rolls back transaction' do
177
+
178
+ it 'does not infinitely loop' do
179
+ updates = 0
180
+ allow(ActiveRecord::Base.connection).to receive(:update).and_wrap_original do |m, *args|
181
+ updates = updates + 1
182
+ raise StandardError, 'Too many updates - likely infinite loop detected' if updates > 1
183
+
184
+ m.call(*args)
185
+ end
186
+
187
+ ActiveRecord::Base.delay_touching do
188
+ ActiveRecord::Base.transaction do
189
+ # write and touch any new record
190
+ record = Post.create!
191
+ record.touch
192
+ raise ActiveRecord::Rollback
193
+ end
194
+ end
195
+
196
+ end
197
+
198
+ end
199
+
200
+ def expect_updates(tables)
201
+ expected_sql = tables.map do |entry|
202
+ if entry.kind_of?(Hash)
203
+ entry.map do |table, columns|
204
+ Regexp.new(%Q{UPDATE "#{table}" SET #{columns.map { |column| %Q{"#{column}" =.+} }.join(", ") } })
205
+ end
206
+ else
207
+ Regexp.new(%Q{UPDATE "#{entry}" SET "updated_at" = })
208
+ end
209
+ end.flatten
210
+ expect(ActiveRecord::Base.connection).to receive(:update).exactly(expected_sql.length).times do |stmt, _, _|
211
+ index = expected_sql.index { |sql| stmt.to_sql =~ sql}
212
+ expect(index).to be, "An unexpected touch occurred: #{stmt.to_sql}"
213
+ expected_sql.delete_at(index)
214
+ end
215
+
216
+ yield
217
+
218
+ expect(expected_sql).to be_empty, "Some of the expected updates were not executed."
219
+ end
220
+ end
@@ -0,0 +1,3 @@
1
+ @exclude_list = [
2
+ #'spec/**/*.rb'
3
+ ]
@@ -0,0 +1,25 @@
1
+ require 'yarjuf'
2
+
3
+ if ENV["COVERAGE"]
4
+ require_relative 'rcov_exclude_list.rb'
5
+ exlist = Dir.glob(@exclude_list)
6
+ require 'simplecov'
7
+ require 'simplecov-rcov'
8
+ SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter
9
+ SimpleCov.start do
10
+ exlist.each do |p|
11
+ add_filter p
12
+ end
13
+ end
14
+ end
15
+
16
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
17
+ require 'active_record'
18
+ require 'activerecord/delay_touching'
19
+ require 'timecop'
20
+
21
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
22
+
23
+ load File.dirname(__FILE__) + '/support/schema.rb'
24
+ require File.dirname(__FILE__) + '/support/models.rb'
25
+
@@ -0,0 +1,20 @@
1
+ class Person < ActiveRecord::Base
2
+ has_many :pets, inverse_of: :person
3
+ end
4
+
5
+ class Pet < ActiveRecord::Base
6
+ belongs_to :person, touch: true, inverse_of: :pets
7
+ end
8
+
9
+ class Post < ActiveRecord::Base
10
+ has_many :comments, dependent: :destroy
11
+ end
12
+
13
+ class User < ActiveRecord::Base
14
+ has_many :comments, dependent: :destroy
15
+ end
16
+
17
+ class Comment < ActiveRecord::Base
18
+ belongs_to :post, touch: true
19
+ belongs_to :user, touch: true
20
+ end
@@ -0,0 +1,33 @@
1
+ ActiveRecord::Schema.define do
2
+ self.verbose = false
3
+
4
+ create_table :people, :force => true do |t|
5
+ t.string :name
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ create_table :pets, :force => true do |t|
11
+ t.string :name
12
+ t.integer :person_id
13
+ t.datetime :neutered_at
14
+ t.datetime :fed_at
15
+
16
+ t.timestamps
17
+ end
18
+
19
+ create_table :posts, force: true do |t|
20
+ t.timestamps null: false
21
+ end
22
+
23
+ create_table :users, force: true do |t|
24
+ t.timestamps null: false
25
+ end
26
+
27
+ create_table :comments, force: true do |t|
28
+ t.integer :post_id
29
+ t.integer :user_id
30
+ t.timestamps null: false
31
+ end
32
+
33
+ end
metadata ADDED
@@ -0,0 +1,201 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord5_delay_touching
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.1
5
+ platform: ruby
6
+ authors:
7
+ - GoDaddy P&C Commerce
8
+ - Brian Morearty
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2018-11-28 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '4.2'
21
+ - - "<"
22
+ - !ruby/object:Gem::Version
23
+ version: '5.3'
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ version: '4.2'
31
+ - - "<"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.3'
34
+ - !ruby/object:Gem::Dependency
35
+ name: bundler
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.6'
41
+ type: :development
42
+ prerelease: false
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.6'
48
+ - !ruby/object:Gem::Dependency
49
+ name: rake
50
+ requirement: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: sqlite3
64
+ requirement: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ type: :development
70
+ prerelease: false
71
+ version_requirements: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ - !ruby/object:Gem::Dependency
77
+ name: timecop
78
+ requirement: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ type: :development
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ - !ruby/object:Gem::Dependency
91
+ name: rspec-rails
92
+ requirement: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ type: :development
98
+ prerelease: false
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.0'
104
+ - !ruby/object:Gem::Dependency
105
+ name: simplecov
106
+ requirement: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ type: :development
112
+ prerelease: false
113
+ version_requirements: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ - !ruby/object:Gem::Dependency
119
+ name: simplecov-rcov
120
+ requirement: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ type: :development
126
+ prerelease: false
127
+ version_requirements: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ - !ruby/object:Gem::Dependency
133
+ name: yarjuf
134
+ requirement: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ type: :development
140
+ prerelease: false
141
+ version_requirements: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ description: Batch up your ActiveRecord "touch" operations for better performance.
147
+ ActiveRecord::Base.delay_touching do ... end. When "end" is reached, all accumulated
148
+ "touch" calls will be consolidated into as few database round trips as possible.
149
+ email:
150
+ - nemo-engg@godaddy.com
151
+ - brian@morearty.org
152
+ executables: []
153
+ extensions: []
154
+ extra_rdoc_files: []
155
+ files:
156
+ - ".gitignore"
157
+ - ".rspec"
158
+ - Gemfile
159
+ - LICENSE.txt
160
+ - README.md
161
+ - Rakefile
162
+ - activerecord5_delay_touching.gemspec
163
+ - lib/activerecord/delay_touching.rb
164
+ - lib/activerecord/delay_touching/state.rb
165
+ - lib/activerecord/delay_touching/version.rb
166
+ - lib/activerecord5_delay_touching.rb
167
+ - spec/activerecord5/delay_touching_spec.rb
168
+ - spec/rcov_exclude_list.rb
169
+ - spec/spec_helper.rb
170
+ - spec/support/models.rb
171
+ - spec/support/schema.rb
172
+ homepage: ''
173
+ licenses:
174
+ - MIT
175
+ metadata: {}
176
+ post_install_message:
177
+ rdoc_options: []
178
+ require_paths:
179
+ - lib
180
+ required_ruby_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: '0'
185
+ required_rubygems_version: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - ">="
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ requirements: []
191
+ rubyforge_project:
192
+ rubygems_version: 2.6.14
193
+ signing_key:
194
+ specification_version: 4
195
+ summary: Batch up your ActiveRecord "touch" operations for better performance.
196
+ test_files:
197
+ - spec/activerecord5/delay_touching_spec.rb
198
+ - spec/rcov_exclude_list.rb
199
+ - spec/spec_helper.rb
200
+ - spec/support/models.rb
201
+ - spec/support/schema.rb