hit_counter 0.1.0 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +23 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
  5. data/.github/ISSUE_TEMPLATE/task.md +21 -0
  6. data/.github/PULL_REQUEST_TEMPLATE.md +31 -0
  7. data/.github/dependabot.yml +11 -0
  8. data/.github/workflows/test.yml +35 -0
  9. data/.gitignore +8 -0
  10. data/.simplecov +15 -0
  11. data/CODE_OF_CONDUCT.md +10 -0
  12. data/CONTRIBUTING.md +34 -0
  13. data/Gemfile +3 -13
  14. data/LICENSE.txt +17 -11
  15. data/README.md +121 -0
  16. data/Rakefile +6 -44
  17. data/SECURITY.md +16 -0
  18. data/certs/ivanoblomov.pem +25 -0
  19. data/config/initializers/mime_types.rb +3 -0
  20. data/config/mongoid.yml +6 -0
  21. data/hit_counter.gemspec +30 -85
  22. data/lib/hit_counter.rb +82 -72
  23. data/lib/version.rb +6 -0
  24. data/public/images/digits/celtic/0.png +0 -0
  25. data/public/images/digits/celtic/1.png +0 -0
  26. data/public/images/digits/celtic/2.png +0 -0
  27. data/public/images/digits/celtic/3.png +0 -0
  28. data/public/images/digits/celtic/4.png +0 -0
  29. data/public/images/digits/celtic/5.png +0 -0
  30. data/public/images/digits/celtic/6.png +0 -0
  31. data/public/images/digits/celtic/7.png +0 -0
  32. data/public/images/digits/celtic/8.png +0 -0
  33. data/public/images/digits/celtic/9.png +0 -0
  34. data/public/images/digits/odometer/0.png +0 -0
  35. data/public/images/digits/odometer/1.png +0 -0
  36. data/public/images/digits/odometer/2.png +0 -0
  37. data/public/images/digits/odometer/3.png +0 -0
  38. data/public/images/digits/odometer/4.png +0 -0
  39. data/public/images/digits/odometer/5.png +0 -0
  40. data/public/images/digits/odometer/6.png +0 -0
  41. data/public/images/digits/odometer/7.png +0 -0
  42. data/public/images/digits/odometer/8.png +0 -0
  43. data/public/images/digits/odometer/9.png +0 -0
  44. data/public/images/digits/scout/0.png +0 -0
  45. data/public/images/digits/scout/1.png +0 -0
  46. data/public/images/digits/scout/2.png +0 -0
  47. data/public/images/digits/scout/3.png +0 -0
  48. data/public/images/digits/scout/4.png +0 -0
  49. data/public/images/digits/scout/5.png +0 -0
  50. data/public/images/digits/scout/6.png +0 -0
  51. data/public/images/digits/scout/7.png +0 -0
  52. data/public/images/digits/scout/8.png +0 -0
  53. data/public/images/digits/scout/9.png +0 -0
  54. data/spec/lib/hit_counter_spec.rb +104 -70
  55. data/spec/spec_helper.rb +10 -3
  56. data.tar.gz.sig +0 -0
  57. metadata +262 -147
  58. metadata.gz.sig +0 -0
  59. data/README.rdoc +0 -82
  60. data/VERSION +0 -1
  61. data/public/images/digits/celtic/0.gif +0 -0
  62. data/public/images/digits/celtic/1.gif +0 -0
  63. data/public/images/digits/celtic/2.gif +0 -0
  64. data/public/images/digits/celtic/3.gif +0 -0
  65. data/public/images/digits/celtic/4.gif +0 -0
  66. data/public/images/digits/celtic/5.gif +0 -0
  67. data/public/images/digits/celtic/6.gif +0 -0
  68. data/public/images/digits/celtic/7.gif +0 -0
  69. data/public/images/digits/celtic/8.gif +0 -0
  70. data/public/images/digits/celtic/9.gif +0 -0
  71. data/public/images/digits/odometer/0.gif +0 -0
  72. data/public/images/digits/odometer/1.gif +0 -0
  73. data/public/images/digits/odometer/2.gif +0 -0
  74. data/public/images/digits/odometer/3.gif +0 -0
  75. data/public/images/digits/odometer/4.gif +0 -0
  76. data/public/images/digits/odometer/5.gif +0 -0
  77. data/public/images/digits/odometer/6.gif +0 -0
  78. data/public/images/digits/odometer/7.gif +0 -0
  79. data/public/images/digits/odometer/8.gif +0 -0
  80. data/public/images/digits/odometer/9.gif +0 -0
  81. data/public/images/digits/scout/0.gif +0 -0
  82. data/public/images/digits/scout/1.gif +0 -0
  83. data/public/images/digits/scout/2.gif +0 -0
  84. data/public/images/digits/scout/3.gif +0 -0
  85. data/public/images/digits/scout/4.gif +0 -0
  86. data/public/images/digits/scout/5.gif +0 -0
  87. data/public/images/digits/scout/6.gif +0 -0
  88. data/public/images/digits/scout/7.gif +0 -0
  89. data/public/images/digits/scout/8.gif +0 -0
  90. data/public/images/digits/scout/9.gif +0 -0
data/lib/hit_counter.rb CHANGED
@@ -1,137 +1,147 @@
1
- # Copyright (c) 2011 Roderick Monje
2
- #
3
- # This program is free software: you can redistribute it and/or modify
4
- # it under the terms of the GNU General Public License as published by
5
- # the Free Software Foundation, either version 3 of the License, or
6
- # (at your option) any later version.
7
- #
8
- # This program is distributed in the hope that it will be useful,
9
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
- # GNU General Public License for more details.
12
- #
13
- # You should have received a copy of the GNU General Public License
14
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
1
+ # Copyright the HitCounter contributors.
2
+ # SPDX-License-Identifier: MIT
3
+ # frozen_string_literal: true
4
+
15
5
  require 'rubygems'
16
6
  require 'bundler/setup'
17
7
  Bundler.require :default
18
8
 
9
+ # Ruby version of that old 90s chestnut, <BLINK>the web-site hit counter</BLINK>
10
+ #
11
+ # @example
12
+ # hc = HitCounter.get 'cnn.com'
13
+ # hc.increment
19
14
  class HitCounter
20
15
  include Mongoid::Document
21
16
  include Mongoid::Timestamps
22
17
 
23
- # Mongo Config ===================================================================================
18
+ # Mongo Config ===============================================================
19
+ field :hits, type: Integer, default: 0
24
20
  field :url
25
- field :hits, :type => Integer, :default => 0
26
21
 
27
22
  # Validates the <code>HitCounter</code>'s URL.
28
23
  #
29
- # If the URI library can parse the value and the scheme is valid, then we assume the url is valid.
24
+ # If the URI library can parse the value and the scheme is valid, then we
25
+ # assume the url is valid.
30
26
  class UrlValidator < ActiveModel::EachValidator
31
- def validate_each record, attribute, value
32
- begin
33
- uri = Addressable::URI.parse value
34
-
35
- if !['http', 'https'].include? uri.scheme
36
- raise Addressable::URI::InvalidURIError
37
- end
38
- rescue Addressable::URI::InvalidURIError
39
- record.errors[attribute] << 'Invalid URL'
40
- end
27
+ def validate_each(record, attribute, value)
28
+ uri = Addressable::URI.parse value
29
+ raise Addressable::URI::InvalidURIError unless %w[http https].include?(
30
+ uri.scheme
31
+ )
32
+ rescue Addressable::URI::InvalidURIError
33
+ record.errors.add attribute, 'Invalid URL'
41
34
  end
42
35
  end
43
36
 
44
- validates :url, :presence => true, :url => true, :uniqueness => true
37
+ validates :hits, numericality: { only_integer: true }
38
+ validates :url, presence: true, url: true, uniqueness: true
45
39
 
46
- # Class methods ==================================================================================
40
+ # Class methods ==============================================================
47
41
 
48
- # Returns a <code>HitCounter</code> matching the specified URL. The HitCounter is created if no
49
- # matching one is found. In the latter case, the hits argument specifies the starting count.
42
+ # Returns a <code>HitCounter</code> matching the specified URL. The HitCounter
43
+ # is created if no matching one is found. In the latter case, the hits
44
+ # argument specifies the starting count.
50
45
  #
51
- # * *Args*
52
- # - +url+ -> the URL for the site being counted
53
- # - +hits+ -> the number of hits to start with
54
- # * *Returns*
55
- # - the site's HitCounter
56
- def self.get url, hits = 0
57
- args = {:url => HitCounter.normalize_url(url)}
58
- args[:hits] = hits unless HitCounter.exists? :conditions => args
59
- self.find_or_create_by args
46
+ # @param url [String] the URL for the site being counted
47
+ # @param hits [Integer] the number of hits to start with
48
+ # @return [HitCounter] the site's HitCounter
49
+ # @example
50
+ # hc = HitCounter.get 'cnn.com'
51
+ # hc = HitCounter.get 'cnn.com', 100
52
+ def self.get(url, hits = 0)
53
+ args = { url: normalize_url(url) }
54
+ args[:hits] = hits unless where(conditions: args).exists?
55
+ find_or_create_by args
60
56
  end
61
57
 
62
- # Instance methods: Overrides ====================================================================
58
+ # Instance methods: Overrides ================================================
59
+
60
+ # Sets the number of hits.
61
+ #
62
+ # @param value [Integer] the initial number of hits
63
+ # @return [Integer] the number of hits
64
+ # @example
65
+ # hc.hits = 100
66
+ def hits=(value)
67
+ self[:hits] = value.to_i
68
+ end
63
69
 
64
70
  # Sets the URL to be tracked. The http prefix is optional.
65
71
  #
66
- # * *Args*
67
- # - +value+ -> the URL for the site being counted
68
- # * *Returns*
69
- # - the value
70
- def url= value
71
- self[:url] = HitCounter.normalize_url value
72
+ # @param value [String] the URL for the site being counted
73
+ # @return [String] the normalized URL
74
+ # @example
75
+ # hc.url = 'cnn.com'
76
+ def url=(value)
77
+ self[:url] = HitCounter.send(:normalize_url, value)
72
78
  end
73
79
 
74
- # Instance methods ===============================================================================
80
+ # Instance methods ===========================================================
75
81
 
76
82
  # Returns the hit count as a PNG image, using the specified style:
77
83
  # 1 odometer
78
84
  # 2 scout
79
85
  # 3 Celtic
80
86
  #
81
- # * *Args*
82
- # - +style_number+ -> the image style
83
- # * *Returns*
84
- # - the current hit count as a PNG image
85
- def image style_number
86
- image = HitCounter.cat_image self.hits.to_s, HitCounter.normalize_style_number(style_number)
87
+ # @param style_number [String] the image style
88
+ # @return [Magick::Image] the current hit count as a PNG image
89
+ # @example
90
+ # hc.image = 1
91
+ def image(style_number)
92
+ image = HitCounter.send(:cat_image, hits.to_s, HitCounter.send(:normalize_style_number, style_number))
87
93
  image.format = 'png'
88
94
  image
89
95
  end
90
96
 
91
97
  # Increments the hit count and saves the HitCounter.
92
98
  #
93
- # * *Returns*
94
- # - true if the save was successful
99
+ # @return [true] if the save was successful
100
+ # @example
101
+ # hc.increment
95
102
  def increment
96
103
  self.hits += 1
97
- self.save
104
+ save
98
105
  end
99
106
 
100
- private
101
-
102
- STYLES = ['odometer', 'scout', 'celtic']
107
+ STYLES = %w[odometer scout celtic].freeze
103
108
 
104
- def self.cat_image number, style_index, images = Magick::ImageList.new
109
+ private_class_method def self.cat_image(number, style_index, images = Magick::ImageList.new)
105
110
  return images.append(false) if number.blank?
106
- HitCounter.cat_image number[1..-1], style_index, images << Magick::Image.read("#{Rails.root}/public/images/digits/#{STYLES[style_index]}/#{number[0..0]}.gif").first
111
+
112
+ cat_image(number[1..-1], style_index, images <<
113
+ Magick::Image.read("#{Rails.root}/public/images/digits/"\
114
+ "#{STYLES[style_index]}/#{number[0..0]}.png").first)
107
115
  end
108
116
 
109
- def self.normalize_style_number value
117
+ private_class_method def self.normalize_style_number(value)
110
118
  value = value.to_i
111
- value -= 1 if value > 0
119
+ value -= 1 if value.positive?
112
120
 
113
- if value >= 0 && value < STYLES.size
114
- return value
115
- else
116
- value % 3
117
- end
121
+ return value if value >= 0 && value < STYLES.size
122
+
123
+ value % 3
118
124
  end
119
125
 
120
- def self.normalize_url value
126
+ private_class_method def self.normalize_url(value)
121
127
  value !~ %r{^http://} ? "http://#{value}" : value
122
128
  end
123
129
  end
124
130
 
125
131
  if defined? Rails
132
+ # Overriding Rails to include install task
126
133
  class Railtie < Rails::Railtie
127
134
  rake_tasks do
128
135
  namespace :hit_counter do
129
136
  desc 'Install HitCounter into your app.'
130
137
  task :install do
131
138
  puts 'Installing required image files...'
132
- system "rsync -ruv #{Gem.searcher.find('hit_counter').full_gem_path}/public ."
139
+ system "rsync -ruv #{Gem.searcher.find('hit_counter').full_gem_path}"\
140
+ '/config .'
141
+ system "rsync -ruv #{Gem.searcher.find('hit_counter').full_gem_path}"\
142
+ '/public .'
133
143
  end
134
144
  end
135
145
  end
136
146
  end
137
- end
147
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Ruby version of that old 90s chestnut, <BLINK>the web-site hit counter</BLINK>
4
+ class HitCounter
5
+ VERSION = '0.1.4' unless defined?(HitCounter::VERSION)
6
+ end
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,109 +1,143 @@
1
- require File.dirname(__FILE__) + '/../spec_helper'
1
+ # Copyright the HitCounter contributors.
2
+ # SPDX-License-Identifier: MIT
3
+ # frozen_string_literal: true
2
4
 
5
+ require "#{File.dirname(__FILE__)}/../spec_helper"
6
+
7
+ JAVASCRIPT_HACK = %q(eval("console.log('All your base belong to us!')"))
8
+ # rubocop:disable Metrics/BlockLength
3
9
  describe HitCounter do
4
- before :all do
5
- HitCounter.delete_all
6
- @hit_counter = HitCounter.get 'www.google.com'
7
- end
10
+ before(:all) { described_class.delete_all }
11
+
12
+ subject { described_class.get 'www.google.com' }
8
13
 
9
- describe '#get' do
10
- context 'when existing URL' do
11
- describe 'document count' do
12
- specify { HitCounter.count.should == 1 }
14
+ describe '.get' do
15
+ context 'with existing URL' do
16
+ let!(:hit_counter2) { described_class.get subject.url, 10 }
17
+
18
+ describe '.count' do
19
+ it { expect(described_class.count).to eq 1 }
13
20
  end
14
21
 
15
22
  context 'with starting count 10' do
16
- describe 'hits' do
17
- specify { HitCounter.get(@hit_counter.url, 10).hits.should == 0 }
23
+ describe '#hits' do
24
+ it { expect(hit_counter2.hits).to eq 10 }
18
25
  end
19
26
  end
20
27
  end
28
+ context 'with new URL' do
29
+ before { described_class.get 'www.cnn.com' }
21
30
 
22
- context 'when new URL' do
23
- before { HitCounter.get 'www.cnn.com' }
24
-
25
- describe 'document count' do
26
- specify { HitCounter.count.should == 2 }
31
+ describe '.count' do
32
+ it { expect(described_class.count).to eq 2 }
27
33
  end
28
34
 
35
+ context 'with blank starting count' do
36
+ describe '#hits' do
37
+ it do
38
+ expect(described_class.get('www.nytimes.com', nil).hits).to eq 0
39
+ end
40
+ end
41
+ end
29
42
  context 'with starting count 10' do
30
- describe 'hits' do
31
- specify { HitCounter.get('www.nytimes.com', 10).hits.should == 10 }
43
+ describe '#hits' do
44
+ it { expect(described_class.get('online.wsj.com', 10).hits).to eq 10 }
32
45
  end
33
46
  end
34
47
  end
35
- end
48
+ context 'with JavaScript' do
49
+ before { described_class.delete_all }
36
50
 
37
- describe '#hits' do
38
- subject { @hit_counter.hits }
51
+ subject { described_class.get(JAVASCRIPT_HACK) }
39
52
 
40
- it { should == 0 }
53
+ it do
54
+ subject.save
55
+ expect(subject).to be_invalid
56
+ end
57
+ end
58
+ end
59
+ describe '#hits' do
60
+ it { expect(subject.hits).to eq 0 }
41
61
 
42
62
  context 'when incremented' do
43
- before { @hit_counter.increment }
63
+ before { subject.increment }
44
64
 
45
- specify { @hit_counter.hits.should == 1 }
65
+ it { expect(subject.hits).to eq 1 }
46
66
  end
47
67
  end
68
+ describe '#hits=' do
69
+ context 'with JavaScript' do
70
+ before { subject.hits = JAVASCRIPT_HACK }
48
71
 
49
- describe '#image' do
50
- subject { @hit_counter.image '1' }
51
-
52
- it { should be_a Magick::Image }
53
- pending 'should reflect hits'
72
+ it { expect(subject.hits).to eq 0 }
73
+ end
54
74
  end
55
-
56
- describe '#normalize_style_number' do
57
- context 'when undefined' do
58
- it 'should return 0' do
59
- HitCounter.normalize_style_number(nil).should == 0
60
- end
75
+ describe '#image' do
76
+ context 'with a valid :style_number' do
77
+ it { expect(subject.image('1').filename).to eq "./public/images/digits/odometer/#{subject.hits}.png" }
61
78
  end
62
-
63
- context 'when within range' do
64
- it 'should return 0 for 1' do
65
- HitCounter.normalize_style_number('1').should == 0
79
+ context 'with JavaScript' do
80
+ it {
81
+ expect(subject.image(JAVASCRIPT_HACK).filename).to eq "./public/images/digits/odometer/#{subject.hits}.png"
82
+ }
83
+ end
84
+ end
85
+ describe '#url=' do
86
+ context 'with JavaScript' do
87
+ before do
88
+ subject.url = JAVASCRIPT_HACK
89
+ subject.save
66
90
  end
67
91
 
68
- it 'should return 2 for 3' do
69
- HitCounter.normalize_style_number('3').should == 2
70
- end
92
+ it { expect(subject).to be_invalid }
71
93
  end
72
-
73
- context 'when outside range' do
74
- context 'and negative' do
75
- context 'should return 2 for' do
76
- it -1 do
77
- HitCounter.normalize_style_number('-1').should == 2
78
- end
79
-
80
- it -31 do
81
- HitCounter.normalize_style_number('-31').should == 2
94
+ end
95
+ describe '.normalize_style_number' do
96
+ context 'with nil' do
97
+ it { expect(described_class.send(:normalize_style_number, nil)).to be_zero }
98
+ end
99
+ context 'with empty string' do
100
+ it { expect(described_class.send(:normalize_style_number, '')).to be_zero }
101
+ end
102
+ context 'with JavaScript' do
103
+ it { expect(described_class.send(:normalize_style_number, JAVASCRIPT_HACK)).to be_zero }
104
+ end
105
+ context 'with number in range' do
106
+ {
107
+ '1' => 0,
108
+ '3' => 2
109
+ }.each do |input, output|
110
+ context "with '#{input}'" do
111
+ it do
112
+ expect(described_class.send(:normalize_style_number, input)).to eq output
82
113
  end
83
114
  end
84
115
  end
85
-
86
- context 'and positive' do
87
- context 'should return 0 for' do
88
- it 4 do
89
- HitCounter.normalize_style_number('4').should == 0
90
- end
91
-
92
- it 34 do
93
- HitCounter.normalize_style_number('34').should == 0
116
+ end
117
+ context 'with number outside range' do
118
+ {
119
+ '-31' => 2,
120
+ '-1' => 2,
121
+ '4' => 0,
122
+ '34' => 0
123
+ }.each do |input, output|
124
+ context "with '#{input}'" do
125
+ it do
126
+ expect(described_class.send(:normalize_style_number, input)).to eq output
94
127
  end
95
128
  end
96
129
  end
97
130
  end
98
131
  end
99
-
100
- describe '#normalize_url' do
101
- context 'with "cnn.com"' do
102
- specify { HitCounter.normalize_url('cnn.com').should == 'http://cnn.com' }
103
- end
104
-
105
- context 'with "http://www.nytimes.com"' do
106
- specify { HitCounter.normalize_url('http://www.nytimes.com').should == 'http://www.nytimes.com' }
132
+ describe '.normalize_url' do
133
+ {
134
+ 'cnn.com' => 'http://cnn.com',
135
+ 'http://www.nytimes.com' => 'http://www.nytimes.com'
136
+ }.each do |input, output|
137
+ context "with '#{input}'" do
138
+ it { expect(described_class.send(:normalize_url, input)).to eq output }
139
+ end
107
140
  end
108
141
  end
109
- end
142
+ end
143
+ # rubocop:enable Metrics/BlockLength
data/spec/spec_helper.rb CHANGED
@@ -1,14 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'simplecov'
1
4
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
5
  $LOAD_PATH.unshift(File.dirname(__FILE__))
6
+ require 'addressable/uri'
7
+ require 'mongoid'
8
+ require 'RMagick'
3
9
  require 'rspec'
10
+
4
11
  require 'hit_counter'
5
12
 
6
- Mongoid::Config.from_hash 'database' => 'hit_counter'
7
- Mongoid.logger = false
13
+ Mongoid.load! 'config/mongoid.yml', :test
14
+ Mongo::Logger.logger.level = ::Logger::WARN
8
15
 
9
16
  # Stub Rails root during tests.
10
17
  module Rails
11
18
  def self.root
12
19
  '.'
13
20
  end
14
- end
21
+ end
data.tar.gz.sig ADDED
Binary file