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.
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/README.md +52 -0
- data/Rakefile +1 -0
- data/ar_counter.gemspec +24 -0
- data/lib/ar_counter.rb +78 -0
- data/lib/ar_counter/version.rb +3 -0
- data/test/model_counter_test.rb +68 -0
- data/test/schema.rb +17 -0
- data/test/test_helper.rb +35 -0
- metadata +60 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -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
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/ar_counter.gemspec
ADDED
@@ -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
|
data/lib/ar_counter.rb
ADDED
@@ -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,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
|
data/test/schema.rb
ADDED
@@ -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
|
data/test/test_helper.rb
ADDED
@@ -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:
|