-
Notifications
You must be signed in to change notification settings - Fork 312
/
impressionist_controller.rb
166 lines (139 loc) · 5.15 KB
/
impressionist_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
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