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.
@@ -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