multirow_counter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,9 @@
1
+ class CounterModelCreator < Struct.new(:counter_name, :class_name)
2
+ def create
3
+ counter_class = Class.new(ActiveRecord::Base)
4
+ const_name = [class_name, counter_name.classify].join
5
+ MultirowCounter.const_set(const_name, counter_class)
6
+ end
7
+ end
8
+
9
+
@@ -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,12 @@
1
+ module MultirowCounter
2
+ class Railtie < Rails::Railtie
3
+ initializer 'multirow-counter extension' do
4
+ ActiveRecord::Base.extend MultirowCounter::Extension
5
+ end
6
+
7
+ generators do
8
+ require "generators/multirow_counter_generator"
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,4 @@
1
+ require 'multirow_counter/extension'
2
+ require 'multirow_counter/counter_model_creator'
3
+ require 'multirow_counter/railtie' if defined?(Rails)
4
+
@@ -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