impressionist 0.4.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -10,9 +10,9 @@ I would not call this a stable plugin yet, although I have been running it in pr
10
10
 
11
11
  What does this thing do?
12
12
  ------------------------
13
- Logs an impression... and I use that term loosely. It can log page impressions (technically action impressions), but it is not limited to that.
14
- You can log impressions multiple times per request. And you can also attach it to a model. The goal of this project is to provide customizable
15
- stats that are immediately accessible in your application as opposed to using G Analytics and pulling data using their API. You can attach custom
13
+ Logs an impression... and I use that term loosely. It can log page impressions (technically action impressions), but it is not limited to that.
14
+ You can log impressions multiple times per request. And you can also attach it to a model. The goal of this project is to provide customizable
15
+ stats that are immediately accessible in your application as opposed to using G Analytics and pulling data using their API. You can attach custom
16
16
  messages to impressions. No reporting yet.. this thingy just creates the data.
17
17
 
18
18
  What about bots?
@@ -95,17 +95,49 @@ Usage
95
95
  6. Get the unique impression count from a model filtered by IP address. This in turn will give you impressions with unique request_hash, since rows with the same request_hash will have the same IP address.
96
96
 
97
97
  @widget.impressionist_count(:filter=>:ip_address)
98
-
98
+
99
99
  7. Get the unique impression count from a model filtered by session hash. Same as #6 regarding request hash. This may be more desirable than filtering by IP address depending on your situation, since filtering by IP may ignore visitors that use the same IP. The downside to this filtering is that a user could clear session data in their browser and skew the results.
100
100
 
101
101
  @widget.impressionist_count(:filter=>:session_hash)
102
-
102
+
103
103
  8. Get total impression count. This may return more than 1 impression per http request, depending on how you are logging impressions
104
104
 
105
105
  @widget.impressionist_count(:filter=>:all)
106
106
 
107
107
  Logging impressions for authenticated users happens automatically. If you have a current_user helper or use @current_user in your before_filter to set your authenticated user, current_user.id will be written to the user_id field in the impressions table.
108
108
 
109
+ Adding a counter cache
110
+ ----------------------
111
+ Impressionist makes it easy to add a `counter_cache` column to your model. The most basic configuration looks like:
112
+
113
+ is_impressionable :counter_cache => true
114
+
115
+ This will automatically increment the `impressions_count` column in the included model. Note: You'll need to add that column to your model. If you'd like specific a different column name, you can:
116
+
117
+ is_impressionable :counter_cache => { :column_name => :my_column }
118
+
119
+ If you'd like to include only unique impressions in your count:
120
+
121
+ is_impressionable :counter_cache => { :column_name => :my_column, :unique => true }
122
+
123
+
124
+ What if I only want to record unique impressions?
125
+ -------------------------------------------------
126
+ Maybe you only care about unique impressions and would like to eliminate unnecessary database calls. You can specify conditions for recording impressions in your controller:
127
+
128
+ # only record impression if the request has a unique combination of type, id, and session
129
+ impressionist :unique => [:impressionable_type, :impressionable_id, :session_hash]
130
+
131
+ # only record impression if the request has a unique combination of controller, action, and session
132
+ impressionist :unique => [:controller_name, :action_name, :session_hash]
133
+
134
+ # only record impression if session is unique
135
+ impressionist :unique => [:session_hash]
136
+
137
+ Or you can use the `impressionist` method directly:
138
+
139
+ impressionist(impressionable, "some message", :unique => [:session_hash])
140
+
109
141
 
110
142
  Development Roadmap
111
143
  -------------------
@@ -115,14 +147,14 @@ Development Roadmap
115
147
  * AB testing integration
116
148
 
117
149
  Contributing to impressionist
118
- -----------------------------
150
+ -----------------------------
119
151
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
120
152
  * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
121
153
  * Fork the project
122
154
  * Start a feature/bugfix branch
123
155
  * Commit and push until you are happy with your contribution
124
156
  * Make sure to add rpsec tests for it. Patches or features without tests will be ignored. Also, try to write better tests than I do ;-)
125
- * If adding engine controller or view functionality, use HAML and Inherited Resources.
157
+ * If adding engine controller or view functionality, use HAML and Inherited Resources.
126
158
  * All testing is done inside a small Rails app (test_app). You will find specs within this app.
127
159
 
128
160
  Copyright (c) 2011 John McAliley. See LICENSE.txt for further details.
data/Rakefile CHANGED
@@ -70,14 +70,14 @@ namespace :version do
70
70
  Rake::Task['git:release'].invoke
71
71
  else
72
72
  puts "Commit your changed files first"
73
- end
73
+ end
74
74
  end
75
-
75
+
76
76
  desc "create a new version, create tag and push to github"
77
77
  task :major_release do
78
78
  if Jeweler::Commands::ReleaseToGit.new.clean_staging_area?
79
79
  Rake::Task['version:bump:major'].invoke
80
- Rake::Task['gemspec:release'].invoke
80
+ Rake::Task['gemspec:release'].invoke
81
81
  Rake::Task['git:release'].invoke
82
82
  else
83
83
  puts "Commit your changed files first"
@@ -87,9 +87,9 @@ end
87
87
 
88
88
  namespace :impressionist do
89
89
  require File.dirname(__FILE__) + "/lib/impressionist/bots"
90
-
90
+
91
91
  desc "output the list of bots from http://www.user-agents.org/"
92
- task :bots do
92
+ task :bots do
93
93
  p Impressionist::Bots.consume
94
94
  end
95
95
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.0
1
+ 1.0.0
@@ -3,27 +3,23 @@ require 'digest/sha2'
3
3
  module ImpressionistController
4
4
  module ClassMethods
5
5
  def impressionist(opts={})
6
- before_filter { |c| c.impressionist_subapp_filter opts[:actions] }
6
+ before_filter { |c| c.impressionist_subapp_filter(opts[:actions], opts[:unique])}
7
7
  end
8
8
  end
9
-
9
+
10
10
  module InstanceMethods
11
11
  def self.included(base)
12
12
  base.before_filter :impressionist_app_filter
13
13
  end
14
14
 
15
- def impressionist(obj,message=nil)
15
+ def impressionist(obj,message=nil,opts={})
16
16
  unless bypass
17
17
  if obj.respond_to?("impressionable?")
18
- obj.impressions.create(:message=> message,
19
- :request_hash=> @impressionist_hash,
20
- :session_hash=> request.session_options[:id],
21
- :ip_address=> request.remote_ip,
22
- :user_id=> user_id,
23
- :controller_name=>controller_name,
24
- :action_name=> action_name,
25
- :referrer=>request.referer)
18
+ if unique_instance?(obj, opts[:unique])
19
+ obj.impressions.create(associative_create_statement({:message => message}))
20
+ end
26
21
  else
22
+ # we could create an impression anyway. for classes, too. why not?
27
23
  raise "#{obj.class.to_s} is not impressionable!"
28
24
  end
29
25
  end
@@ -33,31 +29,73 @@ module ImpressionistController
33
29
  @impressionist_hash = Digest::SHA2.hexdigest(Time.now.to_f.to_s+rand(10000).to_s)
34
30
  end
35
31
 
36
- def impressionist_subapp_filter(actions=nil)
32
+ def impressionist_subapp_filter(actions=nil,unique_opts=nil)
37
33
  unless bypass
38
34
  actions.collect!{|a|a.to_s} unless actions.blank?
39
- if actions.blank? or actions.include?(action_name)
40
- Impression.create(:controller_name=> controller_name,
41
- :action_name=> action_name,
42
- :user_id=> user_id,
43
- :request_hash=> @impressionist_hash,
44
- :session_hash=> request.session_options[:id],
45
- :ip_address=> request.remote_ip,
46
- :impressionable_type=> controller_name.singularize.camelize,
47
- :impressionable_id=> params[:id],
48
- :referrer=>request.referer)
35
+ if (actions.blank? || actions.include?(action_name)) && unique?(unique_opts)
36
+ Impression.create(direct_create_statement)
49
37
  end
50
38
  end
51
39
  end
52
40
 
53
41
  private
42
+
54
43
  def bypass
55
44
  Impressionist::Bots::WILD_CARDS.each do |wild_card|
56
45
  return true if request.user_agent and request.user_agent.downcase.include? wild_card
57
46
  end
58
47
  Impressionist::Bots::LIST.include? request.user_agent
59
48
  end
60
-
49
+
50
+ def unique_instance?(impressionable, unique_opts)
51
+ return unique_opts.blank? || impressionable.impressions.where(unique_query(unique_opts)).size == 0
52
+ end
53
+
54
+ def unique?(unique_opts)
55
+ return unique_opts.blank? || Impression.where(unique_query(unique_opts)).size == 0
56
+ end
57
+
58
+ # creates the query to check for uniqueness
59
+ def unique_query(unique_opts)
60
+ full_statement = direct_create_statement
61
+ # reduce the full statement to the params we need for the specified unique options
62
+ unique_opts.reduce({}) do |query, param|
63
+ query[param] = full_statement[param]
64
+ query
65
+ end
66
+ end
67
+
68
+ # creates a statment hash that contains default values for creating an impression via an AR relation.
69
+ def associative_create_statement(query_params={})
70
+ query_params.reverse_merge!(
71
+ :controller_name => controller_name,
72
+ :action_name => action_name,
73
+ :user_id => user_id,
74
+ :request_hash => @impressionist_hash,
75
+ :session_hash => session_hash,
76
+ :ip_address => request.remote_ip,
77
+ :referrer => request.referer
78
+ )
79
+ end
80
+
81
+ # creates a statment hash that contains default values for creating an impression.
82
+ def direct_create_statement(query_params={})
83
+ query_params.reverse_merge!(
84
+ :impressionable_type => controller_name.singularize.camelize,
85
+ :impressionable_id=> params[:id]
86
+ )
87
+ associative_create_statement(query_params)
88
+ end
89
+
90
+ def session_hash
91
+ # # careful: request.session_options[:id] encoding in rspec test was ASCII-8BIT
92
+ # # that broke the database query for uniqueness. not sure if this is a testing only issue.
93
+ # str = request.session_options[:id]
94
+ # logger.debug "Encoding: #{str.encoding.inspect}"
95
+ # # request.session_options[:id].encode("ISO-8859-1")
96
+ request.session_options[:id]
97
+ end
98
+
61
99
  #use both @current_user and current_user helper
62
100
  def user_id
63
101
  user_id = @current_user ? @current_user.id : nil rescue nil
@@ -1,3 +1,16 @@
1
1
  class Impression < ActiveRecord::Base
2
2
  belongs_to :impressionable, :polymorphic=>true
3
+
4
+ after_save :update_impressions_counter_cache
5
+
6
+ private
7
+
8
+ def update_impressions_counter_cache
9
+ impressionable_class = self.impressionable_type.constantize
10
+
11
+ if impressionable_class.counter_cache_options
12
+ resouce = impressionable_class.find(self.impressionable_id)
13
+ resouce.try(:update_counter_cache)
14
+ end
15
+ end
3
16
  end
@@ -1,15 +1,38 @@
1
1
  module Impressionist
2
2
  module Impressionable
3
- def is_impressionable
4
- has_many :impressions, :as=>:impressionable
5
- include InstanceMethods
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ base.send(:include, InstanceMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ attr_accessor :cache_options
11
+ @cache_options = nil
12
+
13
+ def is_impressionable(options={})
14
+ has_many :impressions, :as=>:impressionable
15
+ @cache_options = options[:counter_cache]
16
+ end
17
+
18
+ def counter_cache_options
19
+ if @cache_options
20
+ options = { :column_name => :impressions_count, :unique => false }
21
+ options.merge!(@cache_options) if @cache_options.is_a?(Hash)
22
+ options
23
+ end
24
+ end
25
+
26
+ def counter_caching?
27
+ counter_cache_options.present?
28
+ end
6
29
  end
7
-
30
+
8
31
  module InstanceMethods
9
32
  def impressionable?
10
33
  true
11
34
  end
12
-
35
+
13
36
  def impressionist_count(options={})
14
37
  options.reverse_merge!(:filter=>:request_hash, :start_date=>nil, :end_date=>Time.now)
15
38
  imps = options[:start_date].blank? ? impressions : impressions.where("created_at>=? and created_at<=?",options[:start_date],options[:end_date])
@@ -18,7 +41,14 @@ module Impressionist
18
41
  end
19
42
  imps.all.size
20
43
  end
21
-
44
+
45
+ def update_counter_cache
46
+ cache_options = self.class.counter_cache_options
47
+ column_name = cache_options[:column_name].to_sym
48
+ count = cache_options[:unique] ? impressionist_count(:filter => :ip_address) : impressionist_count
49
+ update_attribute(column_name, count)
50
+ end
51
+
22
52
  # OLD METHODS - DEPRECATE IN V0.5
23
53
  def impression_count(start_date=nil,end_date=Time.now)
24
54
  impressionist_count({:start_date=>start_date, :end_date=>end_date, :filter=>:all})
@@ -27,14 +57,15 @@ module Impressionist
27
57
  def unique_impression_count(start_date=nil,end_date=Time.now)
28
58
  impressionist_count({:start_date=>start_date, :end_date=>end_date, :filter=> :request_hash})
29
59
  end
30
-
60
+
31
61
  def unique_impression_count_ip(start_date=nil,end_date=Time.now)
32
62
  impressionist_count({:start_date=>start_date, :end_date=>end_date, :filter=> :ip_address})
33
63
  end
34
-
64
+
35
65
  def unique_impression_count_session(start_date=nil,end_date=Time.now)
36
66
  impressionist_count({:start_date=>start_date, :end_date=>end_date, :filter=> :session_hash})
37
67
  end
38
68
  end
69
+
39
70
  end
40
71
  end
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{impressionist}
8
- s.version = "0.4.0"
8
+ s.version = "1.0.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["cowboycoded"]
@@ -39,7 +39,7 @@ Gem::Specification.new do |s|
39
39
  "upgrade_migrations/version_0_3_0.rb",
40
40
  "upgrade_migrations/version_0_4_0.rb"
41
41
  ]
42
- s.homepage = %q{http://github.com/cowboycoded/impressionist}
42
+ s.homepage = %q{https://github.com/charlotte-ruby/impressionist}
43
43
  s.licenses = ["MIT"]
44
44
  s.require_paths = ["lib"]
45
45
  s.rubygems_version = %q{1.3.7}
@@ -8,30 +8,30 @@ class CreateImpressionsTable < ActiveRecord::Migration
8
8
  t.string :action_name
9
9
  t.string :view_name
10
10
  t.string :request_hash
11
- t.string :session_hash
12
11
  t.string :ip_address
13
- t.string :message
14
- t.string :referrer
12
+ t.text :session_hash
13
+ t.text :message
14
+ t.text :referrer
15
15
  t.timestamps
16
16
  end
17
17
  add_index :impressions, [:impressionable_type, :impressionable_id, :request_hash], :name => "poly_request_index", :unique => false
18
18
  add_index :impressions, [:impressionable_type, :impressionable_id, :ip_address], :name => "poly_ip_index", :unique => false
19
- add_index :impressions, [:impressionable_type, :impressionable_id, :session_hash], :name => "poly_session_index", :unique => false
19
+ add_index :impressions, [:impressionable_type, :impressionable_id, :session_hash], :name => "poly_session_index", :unique => false
20
20
  add_index :impressions, [:controller_name,:action_name,:request_hash], :name => "controlleraction_request_index", :unique => false
21
21
  add_index :impressions, [:controller_name,:action_name,:ip_address], :name => "controlleraction_ip_index", :unique => false
22
- add_index :impressions, [:controller_name,:action_name,:session_hash], :name => "controlleraction_session_index", :unique => false
22
+ add_index :impressions, [:controller_name,:action_name,:session_hash], :name => "controlleraction_session_index", :unique => false
23
23
  add_index :impressions, :user_id
24
24
  end
25
25
 
26
26
  def self.down
27
27
  remove_index :impressions, :name => :poly_request_index
28
28
  remove_index :impressions, :name => :poly_ip_index
29
- remove_index :impressions, :name => :poly_session_index
29
+ remove_index :impressions, :name => :poly_session_index
30
30
  remove_index :impressions, :name => :controlleraction_request_index
31
31
  remove_index :impressions, :name => :controlleraction_ip_index
32
- remove_index :impressions, :name => :controlleraction_session_index
32
+ remove_index :impressions, :name => :controlleraction_session_index
33
33
  remove_index :impressions, :user_id
34
-
34
+
35
35
  drop_table :impressions
36
36
  end
37
37
  end
@@ -2,11 +2,11 @@ require "impressionist"
2
2
  require "rails"
3
3
 
4
4
  module Impressionist
5
- class Engine < Rails::Engine
5
+ class Engine < Rails::Engine
6
6
  initializer 'impressionist.extend_ar' do |app|
7
- ActiveRecord::Base.extend Impressionist::Impressionable
7
+ ActiveRecord::Base.send(:include, Impressionist::Impressionable)
8
8
  end
9
-
9
+
10
10
  initializer 'impressionist.controller' do
11
11
  ActiveSupport.on_load(:action_controller) do
12
12
  include ImpressionistController::InstanceMethods
metadata CHANGED
@@ -1,12 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: impressionist
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 4
8
- - 0
9
- version: 0.4.0
4
+ prerelease:
5
+ version: 1.0.0
10
6
  platform: ruby
11
7
  authors:
12
8
  - cowboycoded
@@ -14,64 +10,51 @@ autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
12
 
17
- date: 2011-06-03 00:00:00 -04:00
18
- default_executable:
13
+ date: 2011-06-03 00:00:00 Z
19
14
  dependencies:
20
15
  - !ruby/object:Gem::Dependency
21
16
  name: shoulda
17
+ prerelease: false
22
18
  requirement: &id001 !ruby/object:Gem::Requirement
23
19
  none: false
24
20
  requirements:
25
21
  - - ">="
26
22
  - !ruby/object:Gem::Version
27
- segments:
28
- - 0
29
23
  version: "0"
30
24
  type: :development
31
- prerelease: false
32
25
  version_requirements: *id001
33
26
  - !ruby/object:Gem::Dependency
34
27
  name: bundler
28
+ prerelease: false
35
29
  requirement: &id002 !ruby/object:Gem::Requirement
36
30
  none: false
37
31
  requirements:
38
32
  - - ~>
39
33
  - !ruby/object:Gem::Version
40
- segments:
41
- - 1
42
- - 0
43
- - 0
44
34
  version: 1.0.0
45
35
  type: :development
46
- prerelease: false
47
36
  version_requirements: *id002
48
37
  - !ruby/object:Gem::Dependency
49
38
  name: jeweler
39
+ prerelease: false
50
40
  requirement: &id003 !ruby/object:Gem::Requirement
51
41
  none: false
52
42
  requirements:
53
43
  - - ~>
54
44
  - !ruby/object:Gem::Version
55
- segments:
56
- - 1
57
- - 5
58
- - 1
59
45
  version: 1.5.1
60
46
  type: :development
61
- prerelease: false
62
47
  version_requirements: *id003
63
48
  - !ruby/object:Gem::Dependency
64
49
  name: rcov
50
+ prerelease: false
65
51
  requirement: &id004 !ruby/object:Gem::Requirement
66
52
  none: false
67
53
  requirements:
68
54
  - - ">="
69
55
  - !ruby/object:Gem::Version
70
- segments:
71
- - 0
72
56
  version: "0"
73
57
  type: :development
74
- prerelease: false
75
58
  version_requirements: *id004
76
59
  description: Log impressions from controller actions or from a model
77
60
  email: john.mcaliley@gmail.com
@@ -104,8 +87,7 @@ files:
104
87
  - logo.png
105
88
  - upgrade_migrations/version_0_3_0.rb
106
89
  - upgrade_migrations/version_0_4_0.rb
107
- has_rdoc: true
108
- homepage: http://github.com/cowboycoded/impressionist
90
+ homepage: https://github.com/charlotte-ruby/impressionist
109
91
  licenses:
110
92
  - MIT
111
93
  post_install_message:
@@ -118,22 +100,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
118
100
  requirements:
119
101
  - - ">="
120
102
  - !ruby/object:Gem::Version
121
- hash: -566691724227540930
122
- segments:
123
- - 0
124
103
  version: "0"
125
104
  required_rubygems_version: !ruby/object:Gem::Requirement
126
105
  none: false
127
106
  requirements:
128
107
  - - ">="
129
108
  - !ruby/object:Gem::Version
130
- segments:
131
- - 0
132
109
  version: "0"
133
110
  requirements: []
134
111
 
135
112
  rubyforge_project:
136
- rubygems_version: 1.3.7
113
+ rubygems_version: 1.8.11
137
114
  signing_key:
138
115
  specification_version: 3
139
116
  summary: Easy way to log impressions