ar_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,20 @@
1
+ .DS_Store
2
+
3
+ *.gem
4
+ *.rbc
5
+ .bundle
6
+ .config
7
+ coverage
8
+ InstalledFiles
9
+ lib/bundler/man
10
+ pkg
11
+ rdoc
12
+ spec/reports
13
+ test/tmp
14
+ test/version_tmp
15
+ tmp
16
+
17
+ # YARD artifacts
18
+ .yardoc
19
+ _yardoc
20
+ doc/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in model-counter.gemspec
4
+ gemspec
@@ -0,0 +1,52 @@
1
+ ar_counter
2
+ ==========
3
+
4
+ Behave similar to ActiveRecord counter cache but it will put the counter on separate table.
5
+
6
+ note:
7
+ Only works with MySQL database
8
+
9
+
10
+ == Example
11
+
12
+ You need to create a stats table for the model that you want to count.
13
+
14
+ === Model :
15
+
16
+ class Driver < ActiveRecord::Base
17
+ has_many :cars
18
+ has_stats_for :cars
19
+ end
20
+
21
+ class Car < ActiveRecord::Base
22
+ belongs_to :driver
23
+ stats_to :driver
24
+ end
25
+
26
+ === Migration :
27
+
28
+ create_table :drivers, :force => true do |t|
29
+ t.string :name
30
+ end
31
+
32
+ create_table :cars, :force => true do |t|
33
+ t.string :brand
34
+ t.integer :driver_id
35
+ end
36
+
37
+ create_table :driver_stats, :force => true do |t|
38
+ t.integer :driver_id
39
+ t.integer :cars_count, :default => 0
40
+ end
41
+
42
+ # unique index must be created for MySQL ON DUPLICATE KEY UPDATE to work
43
+ add_index :driver_stats, :driver_id, :unique => true
44
+
45
+ === Create stats records for existing model
46
+
47
+ rake rebuild_counter_cached MODEL=Driver
48
+
49
+ == How to use
50
+
51
+ @driver.cars_count
52
+
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "ar_counter/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "ar_counter"
7
+ s.version = ARCounter::VERSION
8
+ s.authors = ["Rafeequl"]
9
+ s.email = ["rafeequl@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{A simple model to model stats/counter}
12
+ s.description = %q{A simple model to model stats/counter. It behaves similar to ActiveRecord counter cache but it will put the counter in separate table.}
13
+
14
+ s.rubyforge_project = "ar_counter"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ # s.add_development_dependency "rspec"
23
+ # s.add_runtime_dependency "rest-client"
24
+ end
@@ -0,0 +1,78 @@
1
+ require "ar_counter/version"
2
+ require "active_support"
3
+
4
+ module ARCounter
5
+ extend ActiveSupport::Concern
6
+
7
+ included do |target|
8
+ target.class_eval {
9
+ @@counter_cached ||= []
10
+ }
11
+ end
12
+
13
+ module ClassMethods
14
+ def counts_for(*args)
15
+ after_create :increase_counter_stats
16
+ after_destroy :decrease_counter_stats
17
+ class_eval(<<CODE)
18
+ @@counter_cached = args
19
+ CODE
20
+ end
21
+
22
+ def has_counter_for(*args)
23
+ args.each do |a|
24
+ class_eval(<<CODE)
25
+ define_method "#{a.to_s}_count" do
26
+ count_target(a)
27
+ end
28
+ CODE
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+ def increase_counter_stats
35
+ counter_sql @@counter_cached, 1
36
+ end
37
+
38
+ def decrease_counter_stats
39
+ counter_sql @@counter_cached, -1
40
+ end
41
+
42
+ def counter_sql(counter_cached, increment_by)
43
+ counter_cached.each do |klas|
44
+ stat_table = "#{klas}_stats"
45
+ owner_id_column = "#{klas}_id"
46
+ klass_id = self.send("#{klas}_id")
47
+ sql_for_increment(stat_table, stat_column, owner_id_column, klass_id, increment_by)
48
+ end
49
+ end
50
+
51
+ # SQL
52
+ def sql_for_subtract(stat_table, stat_column, owner_id_column, klass_id)
53
+ sql = "UPDATE #{stat_table} SET #{stat_column}=#{stat_column}-1 WHERE #{owner_id_column}=#{klass_id}"
54
+ ActiveRecord::Base.connection.execute(sql) if stat_table && owner_id_column && klass_id
55
+ end
56
+
57
+ def sql_for_add(stat_table, stat_column, owner_id_column, klass_id)
58
+ sql = "INSERT INTO #{stat_table} (#{owner_id_column},#{stat_column}) VALUES (#{klass_id},1) \
59
+ ON DUPLICATE KEY UPDATE #{stat_column}=#{stat_column}+1"
60
+ ActiveRecord::Base.connection.execute(sql) if stat_table && owner_id_column && klass_id
61
+ end
62
+
63
+ def sql_for_increment(stat_table, stat_column, owner_id_column, klass_id, increment_by)
64
+ sql = "INSERT INTO #{stat_table} (#{owner_id_column},#{stat_column}) VALUES (#{klass_id},1) \
65
+ ON DUPLICATE KEY UPDATE #{stat_column}=#{stat_column}+#{increment_by}"
66
+ ActiveRecord::Base.connection.execute(sql) if stat_table && owner_id_column && klass_id
67
+ end
68
+
69
+ def stat_column
70
+ "#{self.class.to_s.pluralize.downcase}_count"
71
+ end
72
+
73
+ def count_target(target)
74
+ sql = "SELECT #{target.to_s}_count from #{self.class.to_s.downcase}_stats where #{self.class.to_s.downcase}_id=#{self.id}"
75
+ query = ActiveRecord::Base.connection.execute(sql)
76
+ query.first ? query.first[0].to_i : 0
77
+ end
78
+ end
@@ -0,0 +1,3 @@
1
+ module ARCounter
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,68 @@
1
+ require_relative './test_helper.rb'
2
+
3
+ require_relative "../lib/ar_counter"
4
+
5
+ class Driver < ActiveRecord::Base
6
+ include ARCounter
7
+ has_many :cars
8
+ has_counter_for :cars
9
+ end
10
+
11
+ class Car < ActiveRecord::Base
12
+ include ARCounter
13
+ belongs_to :driver
14
+ counts_for :driver
15
+ end
16
+
17
+
18
+ class ModelCounterTest < Test::Unit::TestCase
19
+
20
+ def test_new_user
21
+ @driver = Driver.new(:name => rand(1000))
22
+ @driver.save
23
+
24
+ assert_equal 0, @driver.cars_count
25
+ end
26
+
27
+ def test_new_user_have_one_car
28
+ @driver = Driver.new(:name => rand(1000))
29
+ @driver.save
30
+
31
+ @car = Car.new(:brand => "BMW", :driver_id => @driver.id)
32
+ @car.save
33
+
34
+ assert_equal 1, @driver.cars_count
35
+ end
36
+
37
+ def test_increase_cars_count_when_driver_add_more_car
38
+ @driver = Driver.new(:name => rand(1000))
39
+ @driver.save
40
+
41
+ 2.times do |i|
42
+ car = Car.new(:brand => "Motor #{i}", :driver_id => @driver.id)
43
+ car.save
44
+ end
45
+
46
+ current_car_total = @driver.cars_count
47
+
48
+ new_car = Car.new(:brand => 'BMW', :driver_id => @driver.id)
49
+ new_car.save
50
+
51
+ assert_equal current_car_total+1, @driver.cars_count
52
+ end
53
+
54
+ def test_decrease_cars_count_when_driver_deleted_a_car
55
+ @driver = Driver.new(:name => rand(1000))
56
+ @driver.save!
57
+
58
+ 2.times do |i|
59
+ car = @driver.cars.build(:brand => "Motor #{i}", :driver_id => @driver.id)
60
+ car.save
61
+ end
62
+
63
+ current_car_total = @driver.cars_count
64
+ car = Car.last.destroy
65
+ assert_equal current_car_total-1, @driver.cars_count
66
+ end
67
+
68
+ end
@@ -0,0 +1,17 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+ create_table :drivers, :force => true do |t|
3
+ t.string :name
4
+ end
5
+
6
+ create_table :cars, :force => true do |t|
7
+ t.string :brand
8
+ t.integer :driver_id
9
+ end
10
+
11
+ create_table :driver_stats, :force => true do |t|
12
+ t.integer :driver_id
13
+ t.integer :cars_count, :default => 0
14
+ end
15
+
16
+ add_index(:driver_stats, :driver_id, :unique => true)
17
+ end
@@ -0,0 +1,35 @@
1
+ $:.unshift File.expand_path('../../lib', __FILE__)
2
+
3
+ require 'test/unit'
4
+ require 'active_record'
5
+
6
+ begin
7
+ require 'mysql2'
8
+ 'mysql2'
9
+ rescue
10
+ MissingSourceFile
11
+ end
12
+
13
+ db_config = {
14
+ adapter: "mysql2",
15
+ database: "model_counter",
16
+ username: "root",
17
+ password: "",
18
+ host: "localhost"
19
+ }
20
+
21
+ ActiveRecord::Base.establish_connection(db_config.merge(:database => "mysql"))
22
+ # drop db if exist
23
+ ActiveRecord::Base.connection.drop_database(db_config[:database]) rescue nil
24
+ # create db
25
+ ActiveRecord::Base.connection.create_database(db_config[:database])
26
+ ActiveRecord::Base.establish_connection(db_config)
27
+ load(File.dirname(__FILE__) + "/schema.rb")
28
+
29
+ unless Kernel.respond_to?(:require_relative)
30
+ module Kernel
31
+ def require_relative(path)
32
+ require File.join(File.dirname(caller[0]), path.to_str)
33
+ end
34
+ end
35
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ar_counter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Rafeequl
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-23 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: A simple model to model stats/counter. It behaves similar to ActiveRecord
15
+ counter cache but it will put the counter in separate table.
16
+ email:
17
+ - rafeequl@gmail.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - .gitignore
23
+ - Gemfile
24
+ - README.md
25
+ - Rakefile
26
+ - ar_counter.gemspec
27
+ - lib/ar_counter.rb
28
+ - lib/ar_counter/version.rb
29
+ - test/model_counter_test.rb
30
+ - test/schema.rb
31
+ - test/test_helper.rb
32
+ homepage: ''
33
+ licenses: []
34
+ post_install_message:
35
+ rdoc_options: []
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubyforge_project: ar_counter
52
+ rubygems_version: 1.8.21
53
+ signing_key:
54
+ specification_version: 3
55
+ summary: A simple model to model stats/counter
56
+ test_files:
57
+ - test/model_counter_test.rb
58
+ - test/schema.rb
59
+ - test/test_helper.rb
60
+ has_rdoc: