EventCounter
EventCounter is a database based event counter with throttling per time intervals.
Usage
Let's define counters in model
class Article < ActiveRecord::Base
has_counter :views, interval: 5.minutes # default interval is :day
# :year, :month, :week and :day symbols are supported
has_counter :views_by_week, interval: :week
end
Let's count...
article = Article.create!
article.up!(:views)
# => #<EventCounter id: 1, name: "views", value: 1, countable_id: 1,
# countable_type: "Article", created_at: "2014-10-16 23:20:00">
# creates counter (if it doesn't exist) with value 1 and on Time.now() rounded
# to 5 minutes, e.x.:
# at once
article.up!(:views, 3)
# => #<EventCounter id: 1, name: "views", value: 4, countable_id: 1,
# countable_type: "Article", created_at: "2014-10-16 23:20:00">
# Updates counter (if the other exists in that interval) with value 3 and
# on Time.now() rounded to 5 minutes
# later
article.up!(:views, 5)
# => #<EventCounter id: 2, name: "views", value: 5, countable_id: 1,
# countable_type: "Article", created_at: "2014-10-16 23:25:00">
article.down!(:views, 2)
# => #<EventCounter id: 2, name: "views", value: 3, countable_id: 1,
# countable_type: "Article", created_at: "2014-10-16 23:25:00">
# anytime or in a background job
article.up!(:views, 7, on_time: 10.minutes.ago.in_time_zone)
# => #<EventCounter id: 3, name: "views", value: 7, countable_id: 1,
# countable_type: "Article", created_at: "2014-10-16 23:15:00">
# we have not got? let's fix it
article.up!(:views, 9, on_time: 10.minutes.ago.in_time_zone, force: true)
# => #<EventCounter id: 3, name: "views", value: 9, countable_id: 1,
# countable_type: "Article", created_at: "2014-10-16 23:15:00">
Let's get some statistics for our charts...
article.data_for(:views)
# => [
# [Thu, 16 Oct 2014 23:15:00 MSK +04:00, 9],
# [Thu, 16 Oct 2014 23:20:00 MSK +04:00, 4],
# [Thu, 16 Oct 2014 23:25:00 MSK +04:00, 3]
# ]
article.data_for(:views, interval: 10.minutes)
# => [
# [Thu, 16 Oct 2014 23:10:00 MSK +04:00, 9],
# [Thu, 16 Oct 2014 23:20:00 MSK +04:00, 7]
# ]
# with range
range_start = Time.zone.local(2014, 10, 16, 23, 0)
range_end = Time.zone.local(2014, 10, 16, 23, 10)
range = range_start..range_end
article.data_for(:views, interval: 10.minutes, range: range)
#=> [
# [Thu, 16 Oct 2014 23:00:00 MSK +04:00, 0]
# [Thu, 16 Oct 2014 23:10:00 MSK +04:00, 9]
# ]
# for different time zone (although we have no data for that time)
range_start = Time.zone.local(2014, 10, 16, 23, 0).in_time_zone('UTC')
range_end = Time.zone.local(2014, 10, 16, 23, 10).in_time_zone('UTC')
range = range_start..range_end
article.data_for(:views, interval: 10.minutes, range: range, tz: 'UTC')
#=> [
# [Thu, 16 Oct 2014 23:00:00 UTC +00:00, 0]
# [Thu, 16 Oct 2014 23:10:00 UTC +00:00, 0]
# ]
article.data_for(:views, interval: :day)
# => [Thu, 16 Oct 2014 00:00:00 MSK +04:00, 16]
article.data_for(:views, interval: :day, raw: true)
#=> [{"created_at" => "2014-10-16 00:00:00", "value" => "16"}]
# The raw result will make difference in performance on a big data.
# The returned time is in the requested time zone. By default, a normalization
# looks as `Time.zone.parse(i['created_at']), i['value'].to_i ]`
# class wide
range_start = Time.zone.local(2014, 10, 15)
range_end = Time.zone.local(2014, 10, 16)
range = range_start..range_end
Article.data_for(:views, interval: :day, range: range)
# => [
# [Thu, 15 Oct 2014 00:00:00 MSK +04:00, 0]
# [Thu, 16 Oct 2014 00:00:00 MSK +04:00, 16]
# ]
Limitations
- It works ONLY with PostgreSQL at the moment.
- Ruby 2+
- ActiveRecord 3+
- ActiveSupport 3+
- It's polymorphic association.
- It uses ActiveSupport::TimeWithZone to return user friendly statistics. So, you have to operate with dates with time zones.
- Use it in production with caution because it's early release.
Installation
Add gem to Gemfile
gem 'event-counter'
Create migration rails g migration create_event_counters
with the
following code:
class CreateEventCounters < ActiveRecord::Migration
def change
create_table :event_counters, force: true do |t|
t.string :name, null: false
t.integer :value, default: 0, null: false
t.references :countable, polymorphic: true, null: false
t.datetime :created_at
end
add_index :event_counters, :created_at
add_index :event_counters,
[:countable_type, :name, :countable_id], name: 'index_complex'
end
end
License
MIT