ar_counter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: