167 lines
5.2 KiB
Ruby
167 lines
5.2 KiB
Ruby
require 'digest/sha2'
|
|
|
|
|
|
module ImpressionistController
|
|
module ClassMethods
|
|
def impressionist(opts={})
|
|
if Rails::VERSION::MAJOR >= 5
|
|
before_action { |c| c.impressionist_subapp_filter(opts) }
|
|
else
|
|
before_filter { |c| c.impressionist_subapp_filter(opts) }
|
|
end
|
|
end
|
|
end
|
|
|
|
module InstanceMethods
|
|
def self.included(base)
|
|
if Rails::VERSION::MAJOR >= 5
|
|
base.before_action :impressionist_app_filter
|
|
else
|
|
base.before_filter :impressionist_app_filter
|
|
end
|
|
end
|
|
|
|
def impressionist(obj,message=nil,opts={})
|
|
if should_count_impression?(opts)
|
|
if obj.respond_to?("impressionable?")
|
|
if unique_instance?(obj, opts[:unique])
|
|
obj.impressions.create(associative_create_statement({:message => message}))
|
|
end
|
|
else
|
|
# we could create an impression anyway. for classes, too. why not?
|
|
raise "#{obj.class.to_s} is not impressionable!"
|
|
end
|
|
end
|
|
end
|
|
|
|
def impressionist_app_filter
|
|
@impressionist_hash = Digest::SHA2.hexdigest(Time.now.to_f.to_s+rand(10000).to_s)
|
|
end
|
|
|
|
def impressionist_subapp_filter(opts = {})
|
|
if should_count_impression?(opts)
|
|
actions = opts[:actions]
|
|
actions.collect!{|a|a.to_s} unless actions.blank?
|
|
if (actions.blank? || actions.include?(action_name)) && unique?(opts[:unique])
|
|
Impression.create(direct_create_statement)
|
|
end
|
|
end
|
|
end
|
|
|
|
protected
|
|
|
|
# creates a statment hash that contains default values for creating an impression via an AR relation.
|
|
def associative_create_statement(query_params={})
|
|
# support older versions of rails:
|
|
# see https://github.com/rails/rails/pull/34039
|
|
if Rails::VERSION::MAJOR < 6
|
|
filter = ActionDispatch::Http::ParameterFilter.new(Rails.application.config.filter_parameters)
|
|
else
|
|
filter = ActiveSupport::ParameterFilter.new(Rails.application.config.filter_parameters)
|
|
end
|
|
|
|
query_params.reverse_merge!(
|
|
:controller_name => controller_name,
|
|
:action_name => action_name,
|
|
:user_id => user_id,
|
|
:request_hash => @impressionist_hash,
|
|
:session_hash => session_hash,
|
|
:ip_address => request.remote_ip,
|
|
:referrer => request.referer,
|
|
:params => filter.filter(params_hash)
|
|
)
|
|
end
|
|
|
|
private
|
|
|
|
def bypass
|
|
Impressionist::Bots.bot?(request.user_agent)
|
|
end
|
|
|
|
def should_count_impression?(opts)
|
|
!bypass && condition_true?(opts[:if]) && condition_false?(opts[:unless])
|
|
end
|
|
|
|
def condition_true?(condition)
|
|
condition.present? ? conditional?(condition) : true
|
|
end
|
|
|
|
def condition_false?(condition)
|
|
condition.present? ? !conditional?(condition) : true
|
|
end
|
|
|
|
def conditional?(condition)
|
|
condition.is_a?(Symbol) ? self.send(condition) : condition.call
|
|
end
|
|
|
|
def unique_instance?(impressionable, unique_opts)
|
|
return unique_opts.blank? || !impressionable.impressions.where(unique_query(unique_opts, impressionable)).exists?
|
|
end
|
|
|
|
def unique?(unique_opts)
|
|
return unique_opts.blank? || check_impression?(unique_opts)
|
|
end
|
|
|
|
def check_impression?(unique_opts)
|
|
impressions = Impression.where(unique_query(unique_opts - [:params]))
|
|
check_unique_impression?(impressions, unique_opts)
|
|
end
|
|
|
|
def check_unique_impression?(impressions, unique_opts)
|
|
impressions_present = impressions.exists?
|
|
impressions_present && unique_opts_has_params?(unique_opts) ? check_unique_with_params?(impressions) : !impressions_present
|
|
end
|
|
|
|
def unique_opts_has_params?(unique_opts)
|
|
unique_opts.include?(:params)
|
|
end
|
|
|
|
def check_unique_with_params?(impressions)
|
|
request_param = params_hash
|
|
impressions.detect{|impression| impression.params == request_param }.nil?
|
|
end
|
|
|
|
# creates the query to check for uniqueness
|
|
def unique_query(unique_opts,impressionable=nil)
|
|
full_statement = direct_create_statement({},impressionable)
|
|
# reduce the full statement to the params we need for the specified unique options
|
|
unique_opts.reduce({}) do |query, param|
|
|
query[param] = full_statement[param]
|
|
query
|
|
end
|
|
end
|
|
|
|
# creates a statment hash that contains default values for creating an impression.
|
|
def direct_create_statement(query_params={},impressionable=nil)
|
|
query_params.reverse_merge!(
|
|
:impressionable_type => controller_name.singularize.camelize,
|
|
:impressionable_id => impressionable.present? ? impressionable.id : params[:id]
|
|
)
|
|
associative_create_statement(query_params)
|
|
end
|
|
|
|
def session_hash
|
|
id = session.id || request.session_options[:id]
|
|
|
|
if id.respond_to?(:cookie_value)
|
|
id.cookie_value
|
|
elsif id.is_a?(Rack::Session::SessionId)
|
|
id.public_id
|
|
else
|
|
id.to_s
|
|
end
|
|
end
|
|
|
|
def params_hash
|
|
request.params.except(:controller, :action, :id)
|
|
end
|
|
|
|
#use both @current_user and current_user helper
|
|
def user_id
|
|
user_id = @current_user ? @current_user.id : nil rescue nil
|
|
user_id = current_user ? current_user.id : nil rescue nil if user_id.blank?
|
|
user_id
|
|
end
|
|
end
|
|
end
|