visitors 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,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --profile
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.2@visitors
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in visitors.gemspec
4
+ gemspec
@@ -0,0 +1,5 @@
1
+ guard 'rspec', :version => 2 do
2
+ watch(%r{^spec/(.*)_spec.rb})
3
+ watch(%r{^lib/(.*)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch(%r{^spec/spec_helper.rb}) { "spec" }
5
+ end
@@ -0,0 +1,12 @@
1
+ # Visitors
2
+
3
+ bundle install
4
+ bundle console
5
+
6
+ Visitors::Store.new
7
+ Visitors::Day.new
8
+
9
+ visitors store
10
+ visitors web
11
+
12
+ ### Come Back Later
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'visitors'
5
+ require 'visitors/cli'
6
+ rescue LoadError
7
+ require File.expand_path('../../lib/visitors', __FILE__)
8
+ require File.expand_path('../../lib/visitors/cli', __FILE__)
9
+ end
10
+
11
+ Visitors::CLI.start
@@ -0,0 +1,5 @@
1
+ development:
2
+ database: postgres://localhost/visitors_development
3
+ redis_namespace: visitors_development
4
+ redis_config:
5
+ host: localhost
@@ -0,0 +1,26 @@
1
+ $:.push File.expand_path('..', __FILE__)
2
+ require 'visitors/helpers'
3
+
4
+ module Visitors
5
+ extend self
6
+
7
+ autoload :Config, 'visitors/config'
8
+ autoload :Resource, 'visitors/resource'
9
+
10
+ autoload :Archiver, 'visitors/archiver'
11
+ autoload :Day, 'visitors/models'
12
+ autoload :Month, 'visitors/models'
13
+ autoload :Year, 'visitors/models'
14
+
15
+ def config
16
+ @config ||= Config.load
17
+ end
18
+
19
+ def find(id)
20
+ Resource.find(id)
21
+ end
22
+
23
+ def increment(id, field)
24
+ Resource.new(id).increment(field)
25
+ end
26
+ end
@@ -0,0 +1,25 @@
1
+ class Visitors::Archiver
2
+ Day = Visitors::Day
3
+ Month = Visitors::Month
4
+ Year = Visitors::Year
5
+
6
+ def archive_yesterday
7
+ Day.all.map do |day|
8
+ execute()
9
+ end
10
+ end
11
+
12
+ def save_today
13
+ Day.destroy
14
+
15
+ Visitors.store.slice.map do |id, stats|
16
+ Day.create(stats.merge(:resource_id => id))
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def execute(*args)
23
+ Day.repository.adapter.select(*args)
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ require 'thor'
2
+
3
+ class Visitors::CLI < Thor
4
+ desc 'store', 'start the redis in-memory store'
5
+ def store
6
+ say "You're running in development, do it yourself!", :blue
7
+ end
8
+
9
+ desc 'web', 'start the web console (NOT IMPLEMENTED)'
10
+ def web
11
+ say 'Not implemented yet', :red
12
+ end
13
+ end
@@ -0,0 +1,32 @@
1
+ class Visitors::Config
2
+ class MissingEnvironmentError < StandardError; end
3
+
4
+ def self.load
5
+ new.tap { |instance| instance.send(:define_methods_from_yaml) }
6
+ end
7
+
8
+ def all
9
+ return yaml[env] if yaml[env]
10
+ raise MissingEnvironmentError, "#{env.inspect} environment not configured"
11
+ end
12
+
13
+ def inspect
14
+ all.inspect
15
+ end
16
+
17
+ private
18
+
19
+ def yaml
20
+ @yaml ||= YAML.load_file(File.expand_path('../../../config.yml', __FILE__))
21
+ end
22
+
23
+ def env
24
+ ENV['VISITORS_ENV'] || 'development'
25
+ end
26
+
27
+ def define_methods_from_yaml
28
+ all.each do |key, value|
29
+ self.class.send(:define_method, key) { value }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,55 @@
1
+ require 'redis'
2
+ require 'redis-namespace'
3
+
4
+ module Helpers
5
+ class UnsupportedField < StandardError; end
6
+
7
+ FIELDS = [:show, :search, :email, :website]
8
+ SET_NAME = 'resource_ids'
9
+
10
+ def redis
11
+ @redis ||= Redis::Namespace.new(Visitors.config.redis_namespace,
12
+ :redis => redis_connection)
13
+ end
14
+
15
+ def fields
16
+ FIELDS
17
+ end
18
+
19
+ def assert_valid_field!(name)
20
+ unless fields.include?(name)
21
+ error_message = "Invalid field #{name.inspect}. Valid fields are #{fields.inspect}"
22
+ raise UnsupportedField, error_message
23
+ end
24
+ end
25
+
26
+ def stats_for(key)
27
+ redis.hgetall(key)
28
+ end
29
+
30
+ def count
31
+ redis.scard(SET_NAME)
32
+ end
33
+
34
+ def known_ids
35
+ redis.smembers(SET_NAME)
36
+ end
37
+
38
+ def parse_date(date)
39
+ date = date.to_s
40
+ case date
41
+ when /\d{4}/
42
+ date
43
+ when /\d{1,2}/
44
+ Time.now.strftime("#{'%02d' % date}%y")
45
+ else
46
+ Time.now.strftime('%m%y')
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def redis_connection
53
+ @redis_connection ||= Redis.new(Visitors.config.redis_config)
54
+ end
55
+ end
@@ -0,0 +1,25 @@
1
+ require 'dm-core'
2
+ require 'dm-migrations'
3
+
4
+ DataMapper.setup(:default, Visitors.config.database)
5
+
6
+ MODEL_NAMES = %w[Day Month Year]
7
+
8
+ MODEL_NAMES.each do |class_name|
9
+ Visitors.class_eval <<-RUBY, __FILE__, __LINE__ + 1
10
+ class Visitors::#{class_name}
11
+ include DataMapper::Resource
12
+
13
+ property :id, Serial
14
+ property :archived, Time
15
+ property :resource_id, Integer, :required => true
16
+
17
+ Visitors::Resource.fields.each do |field|
18
+ property field, Integer, :default => 0, :required => true
19
+ end
20
+ end
21
+ RUBY
22
+ end
23
+
24
+ DataMapper.finalize
25
+ DataMapper.auto_migrate!
@@ -0,0 +1,41 @@
1
+ class Visitors::Resource
2
+ include Helpers
3
+ extend Helpers
4
+
5
+ MONTHS = (1..12).to_a
6
+
7
+ class << self
8
+ def find(resource_id)
9
+ Hash[*MONTHS.map do |month|
10
+ [month, stats_for(new(resource_id, month).to_redis_key)]
11
+ end.flatten]
12
+ end
13
+
14
+ def increment(resource_id, field)
15
+ new(resource_id).increment(field)
16
+ end
17
+
18
+ alias :incr :increment
19
+ end
20
+
21
+ attr_reader :id, :date
22
+
23
+ def initialize(id, date = nil)
24
+ raise ArgumentError, 'Invalid id' unless id
25
+ @id = id
26
+ @date = parse_date(date)
27
+ end
28
+
29
+ def to_redis_key
30
+ [id, date].join(':')
31
+ end
32
+
33
+ alias :key :to_redis_key
34
+
35
+ def increment(field)
36
+ assert_valid_field!(field.to_sym)
37
+
38
+ redis.sadd(SET_NAME, id)
39
+ redis.hincrby(key, field, 1)
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ module Visitors
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,3 @@
1
+ require 'rspec'
2
+ $:.push File.expand_path('../../lib', __FILE__)
3
+ require 'visitors'
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe Visitors::Config do
4
+ describe '#all' do
5
+ it 'uses the config.yml file to return a hash of configuration information' do
6
+ File.stub!(:expand_path => '/path/to/config.yml')
7
+ YAML.should_receive(:load_file).with('/path/to/config.yml').and_return('development' => {})
8
+ Visitors::Config.load.all
9
+ end
10
+
11
+ it 'raises when an environment is not defined' do
12
+ expect {
13
+ YAML.stub!(:load_file => {})
14
+ Visitors::Config.load.all
15
+ }.to raise_error(Visitors::Config::MissingEnvironmentError)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe Visitors::Day do
4
+ end
5
+
6
+ describe Visitors::Month do
7
+ end
8
+
9
+ describe Visitors::Year do
10
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Visitors::Resource do
4
+ def resource(id = nil, date = nil)
5
+ @resource ||= Visitors::Resource.new(id, date)
6
+ end
7
+
8
+ describe '#key' do
9
+ context 'with an integer id' do
10
+ context 'with no date' do
11
+ it 'returns the resource id and the current date' do
12
+ Time.stub!(:now => double('time', :strftime => '0311'))
13
+ resource(1).key.should == '1:0311'
14
+ end
15
+ end
16
+
17
+ context 'with a timestamp of the form mmyy' do
18
+ it 'uses the id and the supplied timestamp' do
19
+ resource(1, '0311').key.should == '1:0311'
20
+ end
21
+ end
22
+
23
+ context 'with a nil date' do
24
+ it 'ignores the date and returns the id with the current date' do
25
+ Time.stub!(:now => double('time', :strftime => '0311'))
26
+ resource(1, nil).key.should == '1:0311'
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ context 'with no id' do
33
+ it 'returns an empty key' do
34
+ expect { resource(nil).key }.to raise_error(ArgumentError)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe Visitors do
4
+ describe '.assert_valid_field!' do
5
+ it 'protects against using unknown fields' do
6
+ error_message = %{Invalid field "invalid_field". Valid fields are []}
7
+
8
+ expect {
9
+ Visitors.stub!(:fields => %w[])
10
+ Visitors.assert_valid_field!('invalid_field')
11
+ }.to raise_error(Visitors::UnsupportedField, error_message)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'visitors/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'visitors'
7
+ s.version = Visitors::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ['James Conroy-Finn']
10
+ s.email = ['james@logi.cl']
11
+ s.homepage = 'http://github.com/jcf/visitors'
12
+ s.summary = %q{All-in-one fast logging system for your Rails application}
13
+ s.description = %q{Redis-backed activity storage to track visits to different parts of your Ruby web app in real-time.}
14
+
15
+ s.rubyforge_project = 'visitors'
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ['lib']
21
+
22
+ {
23
+ 'redis' => '2.1.1',
24
+ 'redis-namespace' => '0.10.0',
25
+ 'dm-core' => '1.0.2',
26
+ 'dm-migrations' => '1.0.2',
27
+ 'dm-postgres-adapter' => '1.0.2',
28
+ 'sinatra' => '1.1.3',
29
+ 'thor' => '0.14.6'
30
+ }.each { |gem, version| s.add_dependency gem, "~> #{version}" }
31
+
32
+ {
33
+ 'rspec' => '2.5.0',
34
+ 'guard-rspec' => '0.1.9',
35
+ 'rb-fsevent' => '0.3.10'
36
+ }.each { |gem, version| s.add_development_dependency gem, "~> #{version}" }
37
+ end
metadata ADDED
@@ -0,0 +1,192 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: visitors
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - James Conroy-Finn
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-02-28 00:00:00 +00:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: redis
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ~>
23
+ - !ruby/object:Gem::Version
24
+ version: 2.1.1
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: redis-namespace
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ~>
34
+ - !ruby/object:Gem::Version
35
+ version: 0.10.0
36
+ type: :runtime
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: dm-core
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: 1.0.2
47
+ type: :runtime
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: dm-migrations
51
+ prerelease: false
52
+ requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ version: 1.0.2
58
+ type: :runtime
59
+ version_requirements: *id004
60
+ - !ruby/object:Gem::Dependency
61
+ name: dm-postgres-adapter
62
+ prerelease: false
63
+ requirement: &id005 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 1.0.2
69
+ type: :runtime
70
+ version_requirements: *id005
71
+ - !ruby/object:Gem::Dependency
72
+ name: sinatra
73
+ prerelease: false
74
+ requirement: &id006 !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ~>
78
+ - !ruby/object:Gem::Version
79
+ version: 1.1.3
80
+ type: :runtime
81
+ version_requirements: *id006
82
+ - !ruby/object:Gem::Dependency
83
+ name: thor
84
+ prerelease: false
85
+ requirement: &id007 !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ~>
89
+ - !ruby/object:Gem::Version
90
+ version: 0.14.6
91
+ type: :runtime
92
+ version_requirements: *id007
93
+ - !ruby/object:Gem::Dependency
94
+ name: rspec
95
+ prerelease: false
96
+ requirement: &id008 !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: 2.5.0
102
+ type: :development
103
+ version_requirements: *id008
104
+ - !ruby/object:Gem::Dependency
105
+ name: guard-rspec
106
+ prerelease: false
107
+ requirement: &id009 !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ~>
111
+ - !ruby/object:Gem::Version
112
+ version: 0.1.9
113
+ type: :development
114
+ version_requirements: *id009
115
+ - !ruby/object:Gem::Dependency
116
+ name: rb-fsevent
117
+ prerelease: false
118
+ requirement: &id010 !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ~>
122
+ - !ruby/object:Gem::Version
123
+ version: 0.3.10
124
+ type: :development
125
+ version_requirements: *id010
126
+ description: Redis-backed activity storage to track visits to different parts of your Ruby web app in real-time.
127
+ email:
128
+ - james@logi.cl
129
+ executables:
130
+ - visitors
131
+ extensions: []
132
+
133
+ extra_rdoc_files: []
134
+
135
+ files:
136
+ - .gitignore
137
+ - .rspec
138
+ - .rvmrc
139
+ - Gemfile
140
+ - Guardfile
141
+ - README.md
142
+ - Rakefile
143
+ - bin/visitors
144
+ - config.yml
145
+ - lib/visitors.rb
146
+ - lib/visitors/archiver.rb
147
+ - lib/visitors/cli.rb
148
+ - lib/visitors/config.rb
149
+ - lib/visitors/helpers.rb
150
+ - lib/visitors/models.rb
151
+ - lib/visitors/resource.rb
152
+ - lib/visitors/version.rb
153
+ - spec/spec_helper.rb
154
+ - spec/visitors/config_spec.rb
155
+ - spec/visitors/models_spec.rb
156
+ - spec/visitors/resource_spec.rb
157
+ - spec/visitors_spec.rb
158
+ - visitors.gemspec
159
+ has_rdoc: true
160
+ homepage: http://github.com/jcf/visitors
161
+ licenses: []
162
+
163
+ post_install_message:
164
+ rdoc_options: []
165
+
166
+ require_paths:
167
+ - lib
168
+ required_ruby_version: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: "0"
174
+ required_rubygems_version: !ruby/object:Gem::Requirement
175
+ none: false
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: "0"
180
+ requirements: []
181
+
182
+ rubyforge_project: visitors
183
+ rubygems_version: 1.5.2
184
+ signing_key:
185
+ specification_version: 3
186
+ summary: All-in-one fast logging system for your Rails application
187
+ test_files:
188
+ - spec/spec_helper.rb
189
+ - spec/visitors/config_spec.rb
190
+ - spec/visitors/models_spec.rb
191
+ - spec/visitors/resource_spec.rb
192
+ - spec/visitors_spec.rb