Merge pull request #12 from coryschires/master
Merged in the counter caching code. Added docs for previous pull request concerning recording unique impressions
This commit is contained in:
commit
85a714ff21
|
@ -1,5 +1,4 @@
|
||||||
Gemfile.lock
|
Gemfile.lock
|
||||||
/test_app/db/migrate/20*
|
|
||||||
/test_app/db/schema.rb
|
/test_app/db/schema.rb
|
||||||
/pkg
|
/pkg
|
||||||
*~
|
*~
|
||||||
|
|
42
README.md
42
README.md
|
@ -5,15 +5,9 @@ impressionist
|
||||||
|
|
||||||
A lightweight plugin that logs impressions per action or manually per model
|
A lightweight plugin that logs impressions per action or manually per model
|
||||||
|
|
||||||
|
I would not call this a stable plugin yet, although I have been running it in prod with no problems. Use at your own risk ;-)
|
||||||
------------------------------------------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
NOTE: If you are upgrading from a version prior to 0.4.0, you will need to run this migration after the upgrade:
|
|
||||||
|
|
||||||
https://github.com/charlotte-ruby/impressionist/blob/master/upgrade_migrations/version_0_4_0.rb
|
|
||||||
|
|
||||||
If you don't run this migration you will receive this error: Unknown attribute : referrer
|
|
||||||
|
|
||||||
|
|
||||||
What does this thing do?
|
What does this thing do?
|
||||||
------------------------
|
------------------------
|
||||||
Logs an impression... and I use that term loosely. It can log page impressions (technically action impressions), but it is not limited to that.
|
Logs an impression... and I use that term loosely. It can log page impressions (technically action impressions), but it is not limited to that.
|
||||||
|
@ -28,7 +22,7 @@ http://www.user-agents.org/allagents.xml
|
||||||
|
|
||||||
Which versions of Rails and Ruby is this compatible with?
|
Which versions of Rails and Ruby is this compatible with?
|
||||||
---------------------------------------------------------
|
---------------------------------------------------------
|
||||||
Rails 3.0.x and Ruby 1.9.2 (also tested on REE 1.8.7) - Sorry, but you need to upgrade if you are using Rails 2. You know you want to anyways.. all the cool kids are doing it ;-)
|
Rails 3.0.4 and Ruby 1.9.2 (also tested on REE 1.8.7) - Sorry, but you need to upgrade if you are using Rails 2. You know you want to anyways.. all the cool kids are doing it ;-)
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
@ -112,6 +106,38 @@ Usage
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
Adding a counter cache
|
||||||
|
----------------------
|
||||||
|
Impressionist makes it easy to add a `counter_cache` column to your model. The most basic configuration looks like:
|
||||||
|
|
||||||
|
is_impressionable :counter_cache => true
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
is_impressionable :counter_cache => { :column_name => :my_column }
|
||||||
|
|
||||||
|
If you'd like to include only unique impressions in your count:
|
||||||
|
|
||||||
|
is_impressionable :counter_cache => { :column_name => :my_column, :unique => true }
|
||||||
|
|
||||||
|
|
||||||
|
What if I only want to record unique impressions?
|
||||||
|
-------------------------------------------------
|
||||||
|
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:
|
||||||
|
|
||||||
|
# only record impression if the request has a unique combination of type, id, and session
|
||||||
|
impressionist :unique => [:impressionable_type, :impressionable_id, :session_hash]
|
||||||
|
|
||||||
|
# only record impression if the request has a unique combination of controller, action, and session
|
||||||
|
impressionist :unique => [:controller_name, :action_name, :session_hash]
|
||||||
|
|
||||||
|
# only record impression if session is unique
|
||||||
|
impressionist :unique => [:session_hash]
|
||||||
|
|
||||||
|
Or you can use the `impressionist` method directly:
|
||||||
|
|
||||||
|
impressionist(impressionable, "some message", :unique => [:session_hash])
|
||||||
|
|
||||||
|
|
||||||
Development Roadmap
|
Development Roadmap
|
||||||
-------------------
|
-------------------
|
||||||
|
|
|
@ -1,3 +1,16 @@
|
||||||
class Impression < ActiveRecord::Base
|
class Impression < ActiveRecord::Base
|
||||||
belongs_to :impressionable, :polymorphic=>true
|
belongs_to :impressionable, :polymorphic=>true
|
||||||
|
|
||||||
|
after_save :update_impressions_counter_cache
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def update_impressions_counter_cache
|
||||||
|
impressionable_class = self.impressionable_type.constantize
|
||||||
|
|
||||||
|
if impressionable_class.counter_cache_options
|
||||||
|
resouce = impressionable_class.find(self.impressionable_id)
|
||||||
|
resouce.try(:update_counter_cache)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
|
@ -1,8 +1,31 @@
|
||||||
module Impressionist
|
module Impressionist
|
||||||
module Impressionable
|
module Impressionable
|
||||||
def is_impressionable
|
|
||||||
|
def self.included(base)
|
||||||
|
base.extend ClassMethods
|
||||||
|
base.send(:include, InstanceMethods)
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
attr_accessor :cache_options
|
||||||
|
@cache_options = nil
|
||||||
|
|
||||||
|
def is_impressionable(options={})
|
||||||
has_many :impressions, :as=>:impressionable
|
has_many :impressions, :as=>:impressionable
|
||||||
include InstanceMethods
|
@cache_options = options[:counter_cache]
|
||||||
|
end
|
||||||
|
|
||||||
|
def counter_cache_options
|
||||||
|
if @cache_options
|
||||||
|
options = { :column_name => :impressions_count, :unique => false }
|
||||||
|
options.merge!(@cache_options) if @cache_options.is_a?(Hash)
|
||||||
|
options
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def counter_caching?
|
||||||
|
counter_cache_options.present?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module InstanceMethods
|
module InstanceMethods
|
||||||
|
@ -19,6 +42,13 @@ module Impressionist
|
||||||
imps.all.size
|
imps.all.size
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_counter_cache
|
||||||
|
cache_options = self.class.counter_cache_options
|
||||||
|
column_name = cache_options[:column_name].to_sym
|
||||||
|
count = cache_options[:unique] ? impressionist_count(:filter => :ip_address) : impressionist_count
|
||||||
|
update_attribute(column_name, count)
|
||||||
|
end
|
||||||
|
|
||||||
# OLD METHODS - DEPRECATE IN V0.5
|
# OLD METHODS - DEPRECATE IN V0.5
|
||||||
def impression_count(start_date=nil,end_date=Time.now)
|
def impression_count(start_date=nil,end_date=Time.now)
|
||||||
impressionist_count({:start_date=>start_date, :end_date=>end_date, :filter=>:all})
|
impressionist_count({:start_date=>start_date, :end_date=>end_date, :filter=>:all})
|
||||||
|
@ -36,5 +66,6 @@ module Impressionist
|
||||||
impressionist_count({:start_date=>start_date, :end_date=>end_date, :filter=> :session_hash})
|
impressionist_count({:start_date=>start_date, :end_date=>end_date, :filter=> :session_hash})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,7 @@ require "rails"
|
||||||
module Impressionist
|
module Impressionist
|
||||||
class Engine < Rails::Engine
|
class Engine < Rails::Engine
|
||||||
initializer 'impressionist.extend_ar' do |app|
|
initializer 'impressionist.extend_ar' do |app|
|
||||||
ActiveRecord::Base.extend Impressionist::Impressionable
|
ActiveRecord::Base.send(:include, Impressionist::Impressionable)
|
||||||
end
|
end
|
||||||
|
|
||||||
initializer 'impressionist.controller' do
|
initializer 'impressionist.controller' do
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
PATH
|
PATH
|
||||||
remote: /home/mio/prog/projects/impressionist
|
remote: /Users/coryschires/Desktop/impressionist
|
||||||
specs:
|
specs:
|
||||||
impressionist (0.4.0)
|
impressionist (0.4.0)
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ GEM
|
||||||
autotest-standalone (4.5.8)
|
autotest-standalone (4.5.8)
|
||||||
bcrypt-ruby (3.0.1)
|
bcrypt-ruby (3.0.1)
|
||||||
builder (3.0.0)
|
builder (3.0.0)
|
||||||
capybara (1.1.1)
|
capybara (1.1.2)
|
||||||
mime-types (>= 1.16)
|
mime-types (>= 1.16)
|
||||||
nokogiri (>= 1.3.3)
|
nokogiri (>= 1.3.3)
|
||||||
rack (>= 1.0.0)
|
rack (>= 1.0.0)
|
||||||
|
@ -54,10 +54,10 @@ GEM
|
||||||
xpath (~> 0.1.4)
|
xpath (~> 0.1.4)
|
||||||
childprocess (0.2.2)
|
childprocess (0.2.2)
|
||||||
ffi (~> 1.0.6)
|
ffi (~> 1.0.6)
|
||||||
cucumber (1.1.1)
|
cucumber (1.1.3)
|
||||||
builder (>= 2.1.2)
|
builder (>= 2.1.2)
|
||||||
diff-lcs (>= 1.1.2)
|
diff-lcs (>= 1.1.2)
|
||||||
gherkin (~> 2.6.0)
|
gherkin (~> 2.6.7)
|
||||||
json (>= 1.4.6)
|
json (>= 1.4.6)
|
||||||
term-ansicolor (>= 1.0.6)
|
term-ansicolor (>= 1.0.6)
|
||||||
cucumber-rails (1.2.0)
|
cucumber-rails (1.2.0)
|
||||||
|
@ -65,12 +65,12 @@ GEM
|
||||||
cucumber (>= 1.1.1)
|
cucumber (>= 1.1.1)
|
||||||
nokogiri (>= 1.5.0)
|
nokogiri (>= 1.5.0)
|
||||||
daemons (1.0.10)
|
daemons (1.0.10)
|
||||||
database_cleaner (0.6.7)
|
database_cleaner (0.7.0)
|
||||||
diff-lcs (1.1.3)
|
diff-lcs (1.1.3)
|
||||||
erubis (2.7.0)
|
erubis (2.7.0)
|
||||||
ffi (1.0.9)
|
ffi (1.0.11)
|
||||||
gem_plugin (0.2.3)
|
gem_plugin (0.2.3)
|
||||||
gherkin (2.6.2)
|
gherkin (2.6.8)
|
||||||
json (>= 1.4.6)
|
json (>= 1.4.6)
|
||||||
hike (1.2.1)
|
hike (1.2.1)
|
||||||
i18n (0.6.0)
|
i18n (0.6.0)
|
||||||
|
@ -129,10 +129,10 @@ GEM
|
||||||
activesupport (~> 3.0)
|
activesupport (~> 3.0)
|
||||||
railties (~> 3.0)
|
railties (~> 3.0)
|
||||||
rspec (~> 2.7.0)
|
rspec (~> 2.7.0)
|
||||||
rubyzip (0.9.4)
|
rubyzip (0.9.5)
|
||||||
selenium-webdriver (2.10.0)
|
selenium-webdriver (2.13.0)
|
||||||
childprocess (>= 0.2.1)
|
childprocess (>= 0.2.1)
|
||||||
ffi (= 1.0.9)
|
ffi (~> 1.0.9)
|
||||||
json_pure
|
json_pure
|
||||||
rubyzip
|
rubyzip
|
||||||
spork (0.8.5)
|
spork (0.8.5)
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
# We don't really care about this model. It's just being used to test the uniqueness controller
|
||||||
|
# specs. Nevertheless, we need a model because the counter caching functionality expects it.
|
||||||
|
#
|
||||||
|
class Dummy < ActiveRecord::Base
|
||||||
|
self.abstract_class = true # doesn't need to be backed by an actual table
|
||||||
|
is_impressionable
|
||||||
|
end
|
|
@ -0,0 +1,3 @@
|
||||||
|
class Widget < ActiveRecord::Base
|
||||||
|
is_impressionable :counter_cache => true
|
||||||
|
end
|
|
@ -0,0 +1,15 @@
|
||||||
|
class CreateWidgets < ActiveRecord::Migration
|
||||||
|
def self.up
|
||||||
|
create_table :widgets do |t|
|
||||||
|
t.string :name
|
||||||
|
t.integer :impressions_count
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
drop_table :widgets
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
class CreateImpressionsTable < ActiveRecord::Migration
|
||||||
|
def self.up
|
||||||
|
create_table :impressions, :force => true do |t|
|
||||||
|
t.string :impressionable_type
|
||||||
|
t.integer :impressionable_id
|
||||||
|
t.integer :user_id
|
||||||
|
t.string :controller_name
|
||||||
|
t.string :action_name
|
||||||
|
t.string :view_name
|
||||||
|
t.string :request_hash
|
||||||
|
t.string :session_hash
|
||||||
|
t.string :ip_address
|
||||||
|
t.string :message
|
||||||
|
t.string :referrer
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
add_index :impressions, [:impressionable_type, :impressionable_id, :request_hash], :name => "poly_request_index", :unique => false
|
||||||
|
add_index :impressions, [:impressionable_type, :impressionable_id, :ip_address], :name => "poly_ip_index", :unique => false
|
||||||
|
add_index :impressions, [:impressionable_type, :impressionable_id, :session_hash], :name => "poly_session_index", :unique => false
|
||||||
|
add_index :impressions, [:controller_name,:action_name,:request_hash], :name => "controlleraction_request_index", :unique => false
|
||||||
|
add_index :impressions, [:controller_name,:action_name,:ip_address], :name => "controlleraction_ip_index", :unique => false
|
||||||
|
add_index :impressions, [:controller_name,:action_name,:session_hash], :name => "controlleraction_session_index", :unique => false
|
||||||
|
add_index :impressions, :user_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
remove_index :impressions, :name => :poly_request_index
|
||||||
|
remove_index :impressions, :name => :poly_ip_index
|
||||||
|
remove_index :impressions, :name => :poly_session_index
|
||||||
|
remove_index :impressions, :name => :controlleraction_request_index
|
||||||
|
remove_index :impressions, :name => :controlleraction_ip_index
|
||||||
|
remove_index :impressions, :name => :controlleraction_session_index
|
||||||
|
remove_index :impressions, :user_id
|
||||||
|
|
||||||
|
drop_table :impressions
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,7 +1,7 @@
|
||||||
require 'spec_helper.rb'
|
require 'spec_helper.rb'
|
||||||
|
|
||||||
describe ArticlesController do
|
describe ArticlesController do
|
||||||
fixtures :articles,:impressions,:posts
|
fixtures :articles,:impressions,:posts,:widgets
|
||||||
render_views
|
render_views
|
||||||
|
|
||||||
it "should make the impressionable_hash available" do
|
it "should make the impressionable_hash available" do
|
||||||
|
@ -69,6 +69,12 @@ describe PostsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe WidgetsController do
|
describe WidgetsController do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
@widget = Widget.find(1)
|
||||||
|
Widget.stub(:find).and_return(@widget)
|
||||||
|
end
|
||||||
|
|
||||||
it "should log impression at the per action level" do
|
it "should log impression at the per action level" do
|
||||||
get "show", :id=> 1
|
get "show", :id=> 1
|
||||||
Impression.all.size.should eq 12
|
Impression.all.size.should eq 12
|
||||||
|
@ -117,4 +123,3 @@ describe WidgetsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -44,7 +44,7 @@ describe DummyController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should recognize unique controller" do
|
it "should recognize unique controller" do
|
||||||
controller.stub!(:controller_name).and_return("test_controller")
|
controller.stub!(:controller_name).and_return("post")
|
||||||
controller.impressionist_subapp_filter(nil, [:controller_name])
|
controller.impressionist_subapp_filter(nil, [:controller_name])
|
||||||
controller.impressionist_subapp_filter(nil, [:controller_name])
|
controller.impressionist_subapp_filter(nil, [:controller_name])
|
||||||
Impression.should have(@impression_count + 1).records
|
Impression.should have(@impression_count + 1).records
|
||||||
|
@ -73,7 +73,7 @@ describe DummyController do
|
||||||
|
|
||||||
# extra redundant test for important controller and action combination.
|
# extra redundant test for important controller and action combination.
|
||||||
it "should recognize different controller and action" do
|
it "should recognize different controller and action" do
|
||||||
controller.stub!(:controller_name).and_return("test_controller")
|
controller.stub!(:controller_name).and_return("post")
|
||||||
controller.stub!(:action_name).and_return("test_action")
|
controller.stub!(:action_name).and_return("test_action")
|
||||||
controller.impressionist_subapp_filter(nil, [:controller_name, :action_name])
|
controller.impressionist_subapp_filter(nil, [:controller_name, :action_name])
|
||||||
controller.impressionist_subapp_filter(nil, [:controller_name, :action_name])
|
controller.impressionist_subapp_filter(nil, [:controller_name, :action_name])
|
||||||
|
@ -82,7 +82,7 @@ describe DummyController do
|
||||||
controller.impressionist_subapp_filter(nil, [:controller_name, :action_name])
|
controller.impressionist_subapp_filter(nil, [:controller_name, :action_name])
|
||||||
controller.impressionist_subapp_filter(nil, [:controller_name, :action_name])
|
controller.impressionist_subapp_filter(nil, [:controller_name, :action_name])
|
||||||
Impression.should have(@impression_count + 2).records
|
Impression.should have(@impression_count + 2).records
|
||||||
controller.stub!(:controller_name).and_return("another_controller")
|
controller.stub!(:controller_name).and_return("article")
|
||||||
controller.impressionist_subapp_filter(nil, [:controller_name, :action_name])
|
controller.impressionist_subapp_filter(nil, [:controller_name, :action_name])
|
||||||
controller.impressionist_subapp_filter(nil, [:controller_name, :action_name])
|
controller.impressionist_subapp_filter(nil, [:controller_name, :action_name])
|
||||||
Impression.should have(@impression_count + 3).records
|
Impression.should have(@impression_count + 3).records
|
||||||
|
@ -100,11 +100,11 @@ describe DummyController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should recognize different controller" do
|
it "should recognize different controller" do
|
||||||
controller.stub!(:controller_name).and_return("test_controller")
|
controller.stub!(:controller_name).and_return("post")
|
||||||
controller.impressionist_subapp_filter(nil, [:controller_name])
|
controller.impressionist_subapp_filter(nil, [:controller_name])
|
||||||
controller.impressionist_subapp_filter(nil, [:controller_name])
|
controller.impressionist_subapp_filter(nil, [:controller_name])
|
||||||
Impression.should have(@impression_count + 1).records
|
Impression.should have(@impression_count + 1).records
|
||||||
controller.stub!(:controller_name).and_return("another_controller")
|
controller.stub!(:controller_name).and_return("article")
|
||||||
controller.impressionist_subapp_filter(nil, [:controller_name])
|
controller.impressionist_subapp_filter(nil, [:controller_name])
|
||||||
controller.impressionist_subapp_filter(nil, [:controller_name])
|
controller.impressionist_subapp_filter(nil, [:controller_name])
|
||||||
Impression.should have(@impression_count + 2).records
|
Impression.should have(@impression_count + 2).records
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
one:
|
||||||
|
id: 1
|
||||||
|
name: A Widget
|
||||||
|
impressions_count: 0
|
|
@ -0,0 +1,30 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Impression do
|
||||||
|
fixtures :widgets
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
@widget = Widget.find(1)
|
||||||
|
Impression.destroy_all
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "self#counter_caching?" do
|
||||||
|
it "should know when counter caching is enabled" do
|
||||||
|
Widget.should be_counter_caching
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should know when counter caching is disabled" do
|
||||||
|
Article.should_not be_counter_caching
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#update_counter_cache" do
|
||||||
|
it "should update the counter cache column to reflect the correct number of impressions" do
|
||||||
|
lambda {
|
||||||
|
Impression.create(:impressionable_type => @widget.class.name, :impressionable_id => @widget.id)
|
||||||
|
@widget.reload
|
||||||
|
}.should change(@widget, :impressions_count).from(0).to(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -49,6 +49,8 @@ describe Impression do
|
||||||
@article.impressionist_count(:filter=>:session_hash).should eq 7
|
@article.impressionist_count(:filter=>:session_hash).should eq 7
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#OLD COUNT METHODS. DEPRECATE SOON
|
#OLD COUNT METHODS. DEPRECATE SOON
|
||||||
it "should return the impression count with no date range specified" do
|
it "should return the impression count with no date range specified" do
|
||||||
@article.impression_count.should eq 11
|
@article.impression_count.should eq 11
|
||||||
|
|
Loading…
Reference in New Issue