1require 'textacular/searchable' |
|
3class Invalidation < ActiveRecord::Base |
|
4 extend Searchable(:id, :summary, :details, :petition_id) |
|
5 include Browseable |
|
7 belongs_to :petition |
|
8 has_many :signatures |
|
10 facet :all, -> { by_most_recent } |
|
11 facet :completed, -> { completed.by_most_recent } |
|
12 facet :cancelled, -> { cancelled.by_most_recent } |
|
13 facet :pending, -> { pending.by_most_recent } |
|
14 facet :enqueued, -> { enqueued.by_most_recent } |
|
15 facet :running, -> { running.by_longest_running } |
|
18 petition_id name postcode ip_address
|
|
19 email domain constituency_id location_code
|
|
20 created_before created_after
|
|
21 ]
|
|
23 validates :summary, presence: true, length: { maximum: 255 } |
|
24 validates :details, length: { maximum: 10000 } |
|
25 validates :petition_id, numericality: { only_integer: true, allow_blank: true, greater_than_or_equal_to: 100000 } |
|
26 validates :name, length: { maximum: 255, allow_blank: true } |
|
27 validates :postcode, length: { maximum: 255, allow_blank: true } |
|
28 validates :ip_address, length: { maximum: 20 }, format: { with: /\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/ }, allow_blank: true |
|
29 validates :email, length: { maximum: 255, allow_blank: true } |
|
30 validates :domain, length: { maximum: 255, allow_blank: true } |
|
31 validates :constituency_id, length: { maximum: 30, allow_blank: true } |
|
32 validates :location_code, length: { maximum: 30, allow_blank: true } |
|
|
34 validate do |
35 if applied_conditions.empty? |
|
36 errors.add :petition_id, "Please select some conditions, otherwise all signatures will be invalidated" |
|
37 end |
|
39 if petition_id? |
|
40 errors.add :petition_id, "Petition doesn't exist" unless Petition.exists?(petition_id) |
|
41 end |
|
43 if domain? |
|
44 errors.add :domain, "Please enter a domain and not an email address" if domain =~ /@/ |
|
45 end |
|
47 if constituency_id? |
|
48 errors.add :constituency_id, "Constituency doesn't exist" unless Constituency.exists?(external_id: constituency_id) |
|
49 end |
|
51 if location_code? |
|
52 errors.add :location_code, "Location doesn't exist" unless Location.exists?(code: location_code) |
|
53 end |
|
55 if created_before? && created_after? |
|
56 errors.add :created_after, "Starting date is after the finishing date" unless created_before > created_after |
|
57 end |
|
58 end |
|
60 before_destroy do |
|
61 !started?
|
|
62 end |
|
64 class << self |
|
65 def by_most_recent |
|
66 reorder(created_at: :desc) |
|
67 end |
|
69 def by_longest_running |
|
70 reorder(started_at: :asc) |
|
71 end |
|
73 def cancelled |
|
74 where(arel_table[:cancelled_at].not_eq(nil)) |
|
75 end |
|
77 def completed |
|
78 where(arel_table[:completed_at].not_eq(nil)) |
|
79 end |
|
81 def enqueued |
|
82 where(arel_table[:enqueued_at].not_eq(nil).and(arel_table[:started_at].eq(nil))) |
|
83 end |
|
85 def not_completed |
|
86 where(arel_table[:completed_at].eq(nil)) |
|
87 end |
|
89 def pending |
|
90 where(enqueued_at: nil, started_at: nil, cancelled_at: nil, completed_at: nil) |
|
91 end |
|
93 def running |
|
94 started.not_completed
|
|
95 end |
|
97 def started |
|
98 where(arel_table[:started_at].not_eq(nil)) |
|
99 end |
|
100 end |
|
|
102 def cancel!(now = Time.current) |
103 return false if cancelled? || completed? |
|
105 update(cancelled_at: now) |
|
106 end |
|
|
108 def cancelled? |
109 cancelled_at?
|
|
110 end |
|
|
112 def completed? |
113 completed_at?
|
|
114 end |
|
|
116 def count! |
117 return false unless pending? |
|
119 update(matching_count: matching_signatures.count, counted_at: Time.current) |
|
120 end |
|
|
122 def start! |
123 return false unless pending? |
|
125 InvalidateSignaturesJob.perform_later(self) |
|
126 update(enqueued_at: Time.current) |
|
127 end |
|
|
129 def started? |
130 started_at?
|
|
131 end |
|
|
133 def enqueued? |
134 enqueued_at?
|
|
135 end |
|
|
137 def pending? |
138 !(enqueued? || started? || cancelled? || completed?)
|
|
139 end |
|
|
141 def running? |
142 started? && !(completed? || cancelled?)
|
|
143 end |
|
|
145 def percent_completed |
146 if started? || completed? |
|
147 matching_count.zero? ? 100 : calculate_percent_complete |
|
148 else |
|
149 matching_count.zero? ? 0 : calculate_percent_complete |
|
150 end |
|
151 end |
|
|
153 def matching_signatures |
154 scope = Signature.for_invalidating |
|
155 scope = petition_scope(scope) if petition_id? |
|
156 scope = name_scope(scope) if name? |
|
157 scope = postcode_scope(scope) if postcode? |
|
158 scope = ip_address_scope(scope) if ip_address? |
|
159 scope = email_scope(scope) if email? |
|
160 scope = domain_scope(scope) if domain? |
|
161 scope = constituency_id_scope(scope) if constituency_id? |
|
162 scope = location_code_scope(scope) if location_code? |
|
163 scope = date_range_scope(scope) if date_range? |
|
165 scope
|
|
166 end |
|
|
168 def invalidate! |
169 return if cancelled? || completed? |
|
171 update(
|
|
172 started_at: Time.current, |
|
173 matching_count: matching_signatures.count, |
|
174 counted_at: Time.current |
|
175 )
|
|
177 Appsignal.without_instrumentation do |
|
178 matching_signatures.find_in_batches(batch_size: 100) do |signatures| |
|
179 signatures.each do |signature| |
|
180 signature.invalidate!(Time.current, self) |
|
181 increment!(:invalidated_count) |
|
182 end |
|
184 reload and return if cancelled? |
|
185 end |
|
186 end |
|
188 update(completed_at: Time.current) |
|
189 end |
|
191 private
|
|
|
193 def petition_scope(scope) |
194 scope.where(petition_id: petition_id) |
|
195 end |
|
|
197 def name_scope(scope) |
198 if name =~ /%/ |
|
199 scope.where("lower(name) LIKE ?", name.strip.downcase) |
|
200 else |
|
201 scope.where("lower(name) = ?", name.strip.downcase) |
|
202 end |
|
203 end |
|
|
205 def postcode_scope(scope) |
206 scope.where(postcode: postcode) |
|
207 end |
|
|
209 def ip_address_scope(scope) |
210 scope.where(ip_address: ip_address) |
|
211 end |
|
|
213 def email_scope(scope) |
214 if email =~ /%/ |
|
215 scope.where("email LIKE ?", email) |
|
216 else |
|
217 scope.where("email = ?", email) |
|
218 end |
|
219 end |
|
|
221 def domain_scope(scope) |
222 if domain =~ /%/ |
|
223 scope.where("SUBSTRING(email FROM POSITION('@' IN email) + 1) LIKE ?", domain) |
|
224 else |
|
225 scope.where("SUBSTRING(email FROM POSITION('@' IN email) + 1) = ?", domain) |
|
226 end |
|
227 end |
|
|
229 def constituency_id_scope(scope) |
230 scope.where(constituency_id: constituency_id) |
|
231 end |
|
|
233 def location_code_scope(scope) |
234 scope.where(location_code: location_code) |
|
235 end |
|
|
237 def date_range? |
238 created_before? || created_after?
|
|
239 end |
|
|
241 def date_range_scope(scope) |
242 if created_before? |
|
243 scope = scope.where(table[:created_at].lt(created_before)) |
|
244 end |
|
246 if created_after? |
|
247 scope = scope.where(table[:created_at].gt(created_after)) |
|
248 end |
|
250 scope
|
|
251 end |
|
|
253 def table |
254 Signature.arel_table |
|
255 end |
|
|
257 def calculate_percent_complete |
258 [[0, ((invalidated_count.to_f / matching_count.to_f) * 100).floor].max, 100].min |
|
259 end |
|
|
261 def applied_conditions |
262 CONDITIONS.select{ |c| read_attribute(c).present? } |
|
263 end |
|
264end |