multirow_counter 0.0.1
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.
- data/lib/generators/multirow_counter_generator.rb +22 -0
- data/lib/generators/templates/multirow_counter_migration.rb +29 -0
- data/lib/multirow_counter/counter_model_creator.rb +9 -0
- data/lib/multirow_counter/extension.rb +29 -0
- data/lib/multirow_counter/railtie.rb +12 -0
- data/lib/multirow_counter.rb +4 -0
- data/test/multirow_counter_test.rb +45 -0
- metadata +80 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rails/generators/active_record/migration'
|
2
|
+
|
3
|
+
class MultirowCounterGenerator < Rails::Generators::Base
|
4
|
+
include Rails::Generators::Migration
|
5
|
+
extend ActiveRecord::Generators::Migration
|
6
|
+
|
7
|
+
source_root File.join(File.dirname(__FILE__), 'templates')
|
8
|
+
argument :model_name, :type => :string
|
9
|
+
argument :counter_name, :type => :string
|
10
|
+
argument :number_of_counter_rows
|
11
|
+
|
12
|
+
def create_migration
|
13
|
+
migration_template 'multirow_counter_migration.rb', "db/migrate/add_#{counter_name}_counter_to_#{model_name}.rb"
|
14
|
+
end
|
15
|
+
|
16
|
+
def inject_code
|
17
|
+
inject_into_file "app/models/#{model_name}.rb", :after => '/class <%= model_name.classify %>' do
|
18
|
+
"multirow_counter :#{counter_name}, :rows => #{number_of_counter_rows}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Add<%= counter_name.classify %>CounterTo<%= model_name.classify %> < ActiveRecord::Migration
|
2
|
+
|
3
|
+
def self.up
|
4
|
+
create_table :<%= [model_name, counter_name].join('_').tableize %> do |t|
|
5
|
+
t.integer :id
|
6
|
+
t.integer :<%= model_name %>_id
|
7
|
+
t.integer :counter_id
|
8
|
+
t.integer :value
|
9
|
+
end
|
10
|
+
|
11
|
+
add_index :<%= [model_name, counter_name].join('_').tableize %>, :<%= model_name %>_id
|
12
|
+
|
13
|
+
# You may want to consider moving this into a background task if it takes too long
|
14
|
+
<%= model_name.classify %>.find_each do |<%= model_name %>|
|
15
|
+
1.upto(<%= number_of_counter_rows %>) do |num|
|
16
|
+
MultirowCounter::<%= model_name.classify %><%= counter_name.classify %>.create! do |row|
|
17
|
+
row.<%= model_name %>_id = <%= model_name %>.id
|
18
|
+
row.counter_id = num
|
19
|
+
row.value = 0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.down
|
26
|
+
drop_table :<%= [model_name, counter_name].join('_').tableize %>
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module MultirowCounter
|
2
|
+
module Extension
|
3
|
+
def multirow_counter(counter_name, options)
|
4
|
+
num_rows = options[:rows] || raise(ArgumentError, "You need to specify how many rows will be used eg. :rows => 3")
|
5
|
+
class_name = self.name
|
6
|
+
|
7
|
+
creator = CounterModelCreator.new(counter_name.to_s, class_name)
|
8
|
+
const = creator.create
|
9
|
+
|
10
|
+
# define getter method
|
11
|
+
getter = lambda do
|
12
|
+
counter_relation = const.where(class_name.foreign_key => id)
|
13
|
+
counter_relation.sum(:value)
|
14
|
+
end
|
15
|
+
|
16
|
+
define_method(counter_name, &getter)
|
17
|
+
define_method("multirow_counter_#{counter_name}", &getter)
|
18
|
+
|
19
|
+
# define increment method
|
20
|
+
define_method("increment_#{counter_name}") do
|
21
|
+
counter_relation = const.where(class_name.foreign_key => id)
|
22
|
+
randomly_selected_counter_row = rand(num_rows) + 1
|
23
|
+
|
24
|
+
counter_relation.where(:counter_id => randomly_selected_counter_row).limit(1).update_all("value = value+1")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe MultirowCounter do
|
4
|
+
before do
|
5
|
+
reset_tables
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should respond to #version" do
|
9
|
+
assert @shop.respond_to?(:version)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should begin at 0" do
|
13
|
+
assert_equal 0, @shop.version
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should allow incrementing" do
|
17
|
+
@shop.increment_version
|
18
|
+
assert_equal 1, @shop.version
|
19
|
+
end
|
20
|
+
|
21
|
+
it "supports many increments" do
|
22
|
+
10.times { @shop.increment_version }
|
23
|
+
assert_equal 10, @shop.version
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should not send all increments to the same row" do
|
27
|
+
10.times { @shop.increment_version }
|
28
|
+
|
29
|
+
shop_versions = MultirowCounter::ShopVersion.where(:shop_id => @shop.id)
|
30
|
+
refute shop_versions.any? { |v| v.value == 10 }
|
31
|
+
end
|
32
|
+
|
33
|
+
it "requires the number of rows to be specified" do
|
34
|
+
class Foo < ActiveRecord::Base
|
35
|
+
lambda {
|
36
|
+
multirow_counter :the_count, :random => 'oops'
|
37
|
+
}.must_raise ArgumentError
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it "defines a second obscure getter" do
|
42
|
+
@shop.multirow_counter_version.must_equal @shop.version
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: multirow_counter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jesse Storimer
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-16 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord
|
16
|
+
requirement: &2158458500 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2158458500
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: mysql2
|
27
|
+
requirement: &2158458060 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2158458060
|
36
|
+
description: Typically SQL is not a great place to store a counter that is incremented
|
37
|
+
often. For instance if you're counting the number of visits to a page by incrementing
|
38
|
+
a SQL column and that page gets popular then there's a good chance that the SQL
|
39
|
+
counter will become a benchmark. This is because doing an UPDATE on the row in question
|
40
|
+
locks the row during the course of the UPDATE. So for many concurrent UPDATES there
|
41
|
+
will be lots of lock contention. This gem helps with that.
|
42
|
+
email:
|
43
|
+
- jesse@shopify.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- lib/generators/multirow_counter_generator.rb
|
49
|
+
- lib/generators/templates/multirow_counter_migration.rb
|
50
|
+
- lib/multirow_counter/counter_model_creator.rb
|
51
|
+
- lib/multirow_counter/extension.rb
|
52
|
+
- lib/multirow_counter/railtie.rb
|
53
|
+
- lib/multirow_counter.rb
|
54
|
+
- test/multirow_counter_test.rb
|
55
|
+
homepage: http://github.com/Shopify/multirow-counter
|
56
|
+
licenses: []
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ! '>='
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
requirements: []
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.8.11
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: Encapsulates a multi-row counter for SQL.
|
79
|
+
test_files:
|
80
|
+
- test/multirow_counter_test.rb
|