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
         |