1require 'active_support/core_ext/digest/uuid' |
|
2require 'postcode_sanitizer' |
|
3require 'ipaddr' |
|
|
5class Signature < ActiveRecord::Base |
6 include PerishableTokenGenerator |
|
7 include GeoipLookup |
|
9 has_perishable_token
|
|
10 has_perishable_token called: 'signed_token' |
|
11 has_perishable_token called: 'unsubscribe_token' |
|
13 ISO8601_TIMESTAMP = /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z\z/ |
|
15 PENDING_STATE = 'pending' |
|
16 FRAUDULENT_STATE = 'fraudulent' |
|
17 VALIDATED_STATE = 'validated' |
|
18 INVALIDATED_STATE = 'invalidated' |
|
20 STATES = [ |
|
21 PENDING_STATE, FRAUDULENT_STATE, |
|
22 VALIDATED_STATE, INVALIDATED_STATE |
|
23 ]
|
|
25 TIMESTAMPS = { |
|
26 'government_response' => :government_response_email_at, |
|
27 'debate_scheduled' => :debate_scheduled_email_at, |
|
28 'debate_outcome' => :debate_outcome_email_at, |
|
29 'petition_email' => :petition_email_at |
|
30 }
|
|
32 belongs_to :petition |
|
33 belongs_to :invalidation |
|
35 validates :state, inclusion: { in: STATES } |
|
36 validates :name, presence: true, length: { maximum: 255 } |
|
37 validates :email, presence: true, email: { allow_blank: true }, on: :create |
|
38 validates :location_code, presence: true |
|
39 validates :postcode, presence: true, postcode: true, if: :united_kingdom? |
|
40 validates :postcode, length: { maximum: 255 }, allow_blank: true |
|
41 validates :uk_citizenship, acceptance: true, unless: :persisted?, allow_nil: false |
|
42 validates :constituency_id, length: { maximum: 255 } |
|
44 attr_readonly :sponsor, :creator |
|
46 before_create if: :email? do |
|
47 self.uuid = generate_uuid |
|
48 self.canonical_email = Domain.normalize(email) |
|
50 if find_duplicate |
|
51 raise ActiveRecord::RecordNotUnique, "Signature is not unique: #{name}, #{email}, #{postcode}" |
|
52 end |
|
54 if find_similar |
|
55 raise ActiveRecord::RecordNotUnique, "Signature is not unique: #{name}, #{email}, #{postcode}" |
|
56 end |
|
57 end |
|
59 before_destroy do |
|
60 !creator?
|
|
61 end |
|
63 after_destroy do |
|
64 if validated? |
|
65 now = Time.current |
|
66 ConstituencyPetitionJournal.invalidate_signature_for(self, now) |
|
67 CountryPetitionJournal.invalidate_signature_for(self, now) |
|
68 petition.decrement_signature_count!(now)
|
|
69 end |
|
70 end |
|
72 class << self |
|
73 def batch(id = 0, limit: 1000) |
|
74 where(arel_table[:id].gt(id)).order(id: :asc).limit(limit) |
|
75 end |
|
77 def by_most_recent |
|
78 order(created_at: :desc) |
|
79 end |
|
81 def column_name_for(timestamp) |
|
82 TIMESTAMPS.fetch(timestamp) |
|
83 rescue KeyError => e |
|
84 raise ArgumentError, "Unknown petition email timestamp: #{timestamp.inspect}" |
|
85 end |
|
87 def destroy!(signature_ids) |
|
88 signatures = find(signature_ids)
|
|
90 transaction do |
|
91 signatures.each do |signature| |
|
92 signature.destroy!
|
|
93 end |
|
94 end |
|
95 end |
|
97 def duplicate(id, email) |
|
98 where(arel_table[:id].not_eq(id).and(arel_table[:email].eq(email))) |
|
99 end |
|
101 def duplicate_emails |
|
102 unscoped.from(validated.select(:uuid).group(:uuid).having(arel_table[Arel.star].count.gt(1))).count |
|
103 end |
|
105 def pending_rate |
|
106 (Rational(pending.count, total.count) * 100).to_d(2) |
|
107 end |
|
109 def similar(id, email) |
|
110 where(canonical_email: email).where.not(id: id) |
|
111 end |
|
113 def for_domain(domain) |
|
114 where("SUBSTRING(email FROM POSITION('@' IN email) + 1) = ?", domain[1..-1]) |
|
115 end |
|
117 def for_email(email) |
|
118 where("(REGEXP_REPLACE(LEFT(email, POSITION('@' IN email) - 1), '\\.|\\+.+', '', 'g') || SUBSTRING(email FROM POSITION('@' IN email)) = ?)", normalize_email(email)) |
|
119 end |
|
121 def for_invalidating |
|
122 where(state: [PENDING_STATE, VALIDATED_STATE]) |
|
123 end |
|
125 def for_ip(ip) |
|
126 where("inet(ip_address) <<= inet(?)", ip) |
|
127 end |
|
129 def for_name(name) |
|
130 where(arel_table[:name].lower.eq(name.downcase)) |
|
131 end |
|
133 def for_petition(id) |
|
134 where(petition_id: id) |
|
135 end |
|
137 def for_postcode(postcode) |
|
138 where(postcode: PostcodeSanitizer.call(postcode)) |
|
139 end |
|
141 def for_sector(postcode) |
|
142 where("LEFT(postcode, -3) = ?", PostcodeSanitizer.call(postcode)[0..-4]) |
|
143 end |
|
145 def for_timestamp(timestamp, since:) |
|
146 column = arel_table[column_name_for(timestamp)]
|
|
147 where(column.eq(nil).or(column.lt(since))) |
|
148 end |
|
150 def fraudulent |
|
151 where(state: FRAUDULENT_STATE) |
|
152 end |
|
154 def fraudulent_domains |
|
155 where(state: FRAUDULENT_STATE). |
|
156 select("SUBSTRING(email FROM POSITION('@' IN email) + 1) AS domain"). |
|
157 group("SUBSTRING(email FROM POSITION('@' IN email) + 1)"). |
|
158 order("COUNT(*) DESC"). |
|
159 count(:all) |
|
160 end |
|
162 def invalidate!(signature_ids, now = Time.current, invalidation_id = nil) |
|
163 signatures = find(signature_ids)
|
|
165 transaction do |
|
166 signatures.each do |signature| |
|
167 signature.invalidate!(now, invalidation_id)
|
|
168 end |
|
169 end |
|
170 end |
|
172 def invalidated |
|
173 where(state: INVALIDATED_STATE) |
|
174 end |
|
176 def missing_constituency_id(since: nil) |
|
177 if since |
|
178 uk.validated(since: since).where(constituency_id: nil) |
|
179 else |
|
180 uk.validated.where(constituency_id: nil) |
|
181 end |
|
182 end |
|
184 def need_emailing_for(timestamp, since:) |
|
185 validated.subscribed.for_timestamp(timestamp, since: since) |
|
186 end |
|
188 def pending |
|
189 where(state: PENDING_STATE) |
|
190 end |
|
192 def total |
|
193 where(state: [PENDING_STATE, VALIDATED_STATE]) |
|
194 end |
|
196 def petition_ids_signed_since(timestamp) |
|
197 validated(since: timestamp).distinct.pluck(:petition_id) |
|
198 end |
|
|
200 def search(query, options = {}) |
201 query = query.to_s
|
|
202 state = options[:state] |
|
203 window = options[:window] |
|
204 page = [options[:page].to_i, 1].max |
|
205 scope = preload(:petition).by_most_recent |
|
207 if state.in?(STATES) |
|
208 scope = scope.where(state: state) |
|
209 end |
|
211 if window.present? |
|
212 if window =~ ISO8601_TIMESTAMP |
|
213 starts_at = window.in_time_zone.at_beginning_of_hour
|
|
214 ends_at = starts_at.advance(hours: 1) |
|
215 scope = scope.where(created_at: starts_at..ends_at) |
|
216 elsif window =~ /\A\d+\z/ |
|
217 starts_at = window.to_i.seconds.ago
|
|
218 ends_at = Time.current |
|
219 scope = scope.where(created_at: starts_at..ends_at) |
|
220 end |
|
221 end |
|
223 if ip_search?(query) |
|
224 scope = scope.for_ip(query)
|
|
225 elsif domain_search?(query) |
|
226 scope = scope.for_domain(query)
|
|
227 elsif email_search?(query) |
|
228 scope = scope.for_email(query)
|
|
229 elsif petition_search?(query) |
|
230 scope = scope.for_petition(query)
|
|
231 elsif postcode_search?(query) |
|
232 scope = scope.for_postcode(query)
|
|
233 elsif sector_search?(query) |
|
234 scope = scope.for_sector(query)
|
|
235 else |
|
236 scope = scope.for_name(query)
|
|
237 end |
|
239 scope.paginate(page: page, per_page: 50) |
|
240 end |
|
242 def creator |
|
243 where(arel_table[:creator].eq(true)) |
|
244 end |
|
246 def sponsors |
|
247 where(arel_table[:sponsor].eq(true)) |
|
248 end |
|
250 def subscribed |
|
251 where(notify_by_email: true) |
|
252 end |
|
254 def fraudulent_domains(since: 1.hour.ago, limit: 20) |
|
255 select("SUBSTRING(email FROM POSITION('@' IN email) + 1) AS domain"). |
|
256 where(arel_table[:created_at].gt(since)). |
|
257 where(state: FRAUDULENT_STATE). |
|
258 group("SUBSTRING(email FROM POSITION('@' IN email) + 1)"). |
|
259 order("COUNT(*) DESC"). |
|
260 limit(limit).
|
|
261 count(:all) |
|
262 end |
|
264 def fraudulent_ips(since: 1.hour.ago, limit: 20) |
|
265 select(:ip_address). |
|
266 where(arel_table[:created_at].gt(since)). |
|
267 where(state: FRAUDULENT_STATE). |
|
268 group(:ip_address). |
|
269 order("COUNT(*) DESC"). |
|
270 limit(limit).
|
|
271 count(:all) |
|
272 end |
|
274 def trending_domains(since: 1.hour.ago, limit: 20) |
|
275 select("SUBSTRING(email FROM POSITION('@' IN email) + 1) AS domain"). |
|
276 where(arel_table[:validated_at].gt(since)). |
|
277 where(arel_table[:invalidated_at].eq(nil)). |
|
278 group("SUBSTRING(email FROM POSITION('@' IN email) + 1)"). |
|
279 order("COUNT(*) DESC"). |
|
280 limit(limit).
|
|
281 count(:all) |
|
282 end |
|
284 def trending_ips(since: 1.hour.ago, limit: 20) |
|
285 select(:ip_address). |
|
286 where(arel_table[:validated_at].gt(since)). |
|
287 where(arel_table[:invalidated_at].eq(nil)). |
|
288 group(:ip_address). |
|
289 order("COUNT(*) DESC"). |
|
290 limit(limit).
|
|
291 count(:all) |
|
292 end |
|
|
294 def trending_domains_by_petition(window, threshold = 5) |
295 trending_domains = Hash.new { |h, k| h[k] = {} } |
|
296 domain = "SUBSTRING(email FROM POSITION('@' IN email) + 1)" |
|
298 where(validated_at: window) |
|
299 .group(:petition_id, domain) |
|
300 .having(count_star.gteq(threshold))
|
|
301 .order(:petition_id, count_star.desc) |
|
302 .pluck(:petition_id, domain, count_star.to_sql) |
|
303 .each_with_object(trending_domains) do |(petition_id, domain, count), hash| |
|
304 hash[petition_id][domain] = count
|
|
305 end |
|
306 end |
|
|
308 def trending_ips_by_petition(window, threshold = 5, ignored_domains = []) |
309 trending_ips = Hash.new { |h, k| h[k] = {} } |
|
310 domain_not_in = "SUBSTRING(email FROM POSITION('@' IN email) + 1) NOT IN (?)" |
|
312 scope = where(validated_at: window) |
|
314 unless ignored_domains.empty? |
|
315 scope = scope.where(domain_not_in, ignored_domains)
|
|
316 end |
|
318 scope
|
|
319 .group(:petition_id, :ip_address) |
|
320 .having(count_star.gteq(threshold))
|
|
321 .order(:petition_id, count_star.desc) |
|
322 .pluck(:petition_id, :ip_address, count_star.to_sql) |
|
323 .each_with_object(trending_ips) do |(petition_id, ip_address, count), hash| |
|
324 hash[petition_id][ip_address] = count
|
|
325 end |
|
326 end |
|
328 def uk |
|
329 where(location_code: "GB") |
|
330 end |
|
332 def unarchived |
|
333 where(archived_at: nil) |
|
334 end |
|
336 def subscribe!(signature_ids) |
|
337 signatures = find(signature_ids)
|
|
339 transaction do |
|
340 signatures.each do |signature| |
|
341 signature.update!(notify_by_email: true) |
|
342 end |
|
343 end |
|
344 end |
|
346 def unsubscribe!(signature_ids) |
|
347 signatures = find(signature_ids)
|
|
349 transaction do |
|
350 signatures.each do |signature| |
|
351 if signature.creator? |
|
352 raise RuntimeError, "Can't unsubscribe the creator signature" |
|
353 elsif signature.pending? |
|
354 raise RuntimeError, "Can't unsubscribe a pending signature" |
|
355 else |
|
356 signature.update!(notify_by_email: false) |
|
357 end |
|
358 end |
|
359 end |
|
360 end |
|
362 def validate!(signature_ids, now = Time.current, force: false, request: nil) |
|
363 signatures = find(signature_ids)
|
|
365 transaction do |
|
366 signatures.each do |signature| |
|
367 signature.validate!(now, force: force, request: request) |
|
368 end |
|
369 end |
|
370 end |
|
372 def validated(since: nil, upto: nil) |
|
373 scope = where(state: VALIDATED_STATE) |
|
374 scope = scope.where(validated_at.gt(since)) if since |
|
375 scope = scope.where(validated_at.lteq(upto)) if upto |
|
376 scope
|
|
377 end |
|
379 def validated_count(timestamp, upto) |
|
380 validated(since: timestamp, upto: upto).pluck(count_star.to_sql).first |
|
381 end |
|
383 def validated_count_by_location_code(timestamp, upto) |
|
384 validated(since: timestamp, upto: upto).group(:location_code).pluck(:location_code, count_star.to_sql) |
|
385 end |
|
387 def validated_count_by_constituency_id(timestamp, upto) |
|
388 validated(since: timestamp, upto: upto).group(:constituency_id).pluck(:constituency_id, count_star.to_sql) |
|
389 end |
|
391 def validated?(id) |
|
392 where(id: id).where(validated_at.not_eq(nil)).exists? |
|
393 end |
|
395 private
|
|
|
397 def ip_search?(query) |
398 IPAddr.new(query) |
|
399 rescue IPAddr::InvalidAddressError => e |
|
400 false |
|
401 end |
|
403 def domain_search?(query) |
|
404 query.starts_with?('@') |
|
405 end |
|
407 def email_search?(query) |
|
408 query.include?('@') |
|
409 end |
|
411 def petition_search?(query) |
|
412 query =~ /\A\d+\z/ |
|
413 end |
|
415 def postcode_search?(query) |
|
416 PostcodeSanitizer.call(query) =~ PostcodeValidator::PATTERN |
|
417 end |
|
419 def sector_search?(query) |
|
420 PostcodeSanitizer.call(query) =~ /\A[A-Z]{1,2}[0-9][0-9A-Z]?XXX\z/ |
|
421 end |
|
423 def validated_at |
|
424 arel_table[:validated_at] |
|
425 end |
|
427 def count_star |
|
428 arel_table[Arel.star].count |
|
429 end |
|
431 def max_validated_at |
|
432 arel_table[:validated_at].maximum.to_sql |
|
433 end |
|
435 def normalize_email(email) |
|
436 "#{normalize_user(email)}@#{normalize_domain(email)}" |
|
437 end |
|
439 def normalize_user(email) |
|
440 email.split("@").first.split("+").first.tr(".", "").downcase |
|
441 end |
|
443 def normalize_domain(email) |
|
444 email.split("@").last.downcase |
|
445 end |
|
446 end |
|
448 attr_accessor :uk_citizenship |
|
|
450 def find_duplicate |
451 return nil unless petition |
|
453 signatures = petition.signatures.duplicate(id, email)
|
|
454 return signatures.first if signatures.many? |
|
|
456 if signature = signatures.first |
457 if sanitized_name == signature.sanitized_name |
|
458 signature
|
|
459 elsif postcode != signature.postcode |
|
460 signature
|
|
461 end |
|
462 end |
|
463 end |
|
465 def find_duplicate! |
|
466 find_duplicate || find_similar || (raise ActiveRecord::RecordNotFound, "Signature not found: #{name}, #{email}, #{postcode}") |
|
467 end |
|
|
469 def find_similar |
470 return nil unless petition |
|
472 signatures = petition.signatures.similar(id, canonical_email)
|
|
473 return signatures.first if signatures.many? |
|
|
475 if signature = signatures.first |
476 if sanitized_name == signature.sanitized_name |
|
477 signature
|
|
478 elsif postcode != signature.postcode |
|
479 signature
|
|
480 end |
|
481 end |
|
482 end |
|
484 def name=(value) |
|
485 super(value.to_s.strip) |
|
486 end |
|
488 def email=(value) |
|
489 super(value.to_s.strip.downcase) |
|
490 end |
|
492 def postcode=(value) |
|
493 super(PostcodeSanitizer.call(value)) |
|
494 end |
|
496 def sanitized_name |
|
497 name.to_s.parameterize
|
|
498 end |
|
500 def pending? |
|
501 state == PENDING_STATE |
|
502 end |
|
504 def fraudulent? |
|
505 state == FRAUDULENT_STATE |
|
506 end |
|
508 def validated? |
|
509 state == VALIDATED_STATE |
|
510 end |
|
512 def invalidated? |
|
513 state == INVALIDATED_STATE |
|
514 end |
|
516 def subscribed? |
|
517 validated? && !unsubscribed?
|
|
518 end |
|
520 def unsubscribed? |
|
521 notify_by_email == false |
|
522 end |
|
524 def fraudulent!(now = Time.current) |
|
525 retry_lock do |
|
526 if pending? |
|
527 update_columns(state: FRAUDULENT_STATE, updated_at: now) |
|
528 end |
|
529 end |
|
530 end |
|
|
532 def validate!(now = Time.current, force: false, request: nil) |
533 update_signature_counts = false |
|
534 new_constituency_id = nil |
|
536 unless constituency_id? |
|
537 if united_kingdom? && postcode? |
|
538 new_constituency_id = constituency.try(:external_id) |
|
539 end |
|
540 end |
|
|
542 retry_lock do |
543 if force || pending? |
|
544 update_signature_counts = true |
|
545 petition.validate_creator!(now) unless creator? |
|
547 attributes = {
|
|
548 number: petition.signature_count + 1, |
|
549 state: VALIDATED_STATE, |
|
550 validated_at: now, |
|
551 invalidation_id: nil, |
|
552 invalidated_at: nil, |
|
553 updated_at: now |
|
554 }
|
|
556 if request |
|
557 attributes[:validated_ip] = request.remote_ip |
|
558 end |
|
560 if new_constituency_id |
|
561 attributes[:constituency_id] = new_constituency_id |
|
562 end |
|
564 unless signed_token? |
|
565 attributes[:signed_token] = Authlogic::Random.friendly_token |
|
566 end |
|
568 update_columns(attributes)
|
|
569 end |
|
570 end |
|
572 if update_signature_counts |
|
573 @just_validated = true |
|
574 end |
|
576 if inline_updates? && update_signature_counts |
|
577 last_signed_at = petition.last_signed_at
|
|
578 petition.increment_signature_count!(now)
|
|
580 ConstituencyPetitionJournal.increment_signature_counts_for(petition, last_signed_at) |
|
581 CountryPetitionJournal.increment_signature_counts_for(petition, last_signed_at) |
|
582 end |
|
583 end |
|
585 def just_validated? |
|
586 defined?(@just_validated) ? @just_validated : false |
|
587 end |
|
589 def validated_before?(timestamp) |
|
590 validated? && validated_at < timestamp
|
|
591 end |
|
593 def reload(*) |
|
594 super.tap { @just_validated = false } |
|
595 end |
|
|
597 def invalidate!(now = Time.current, invalidation_id = nil) |
598 update_signature_counts = false |
|
600 retry_lock do |
|
601 if validated? |
|
602 update_signature_counts = true |
|
603 end |
|
605 update_columns(
|
|
606 state: INVALIDATED_STATE, |
|
607 notify_by_email: false, |
|
608 invalidation_id: invalidation_id, |
|
609 invalidated_at: now, |
|
610 updated_at: now |
|
611 )
|
|
612 end |
|
614 if update_signature_counts |
|
615 ConstituencyPetitionJournal.invalidate_signature_for(self, now) |
|
616 CountryPetitionJournal.invalidate_signature_for(self, now) |
|
617 petition.decrement_signature_count!(now)
|
|
618 end |
|
619 end |
|
621 def mark_seen_signed_confirmation_page! |
|
622 update seen_signed_confirmation_page: true |
|
623 end |
|
|
625 def save(*args) |
626 super |
|
627 rescue ActiveRecord::RecordNotUnique => e |
|
628 if creator? |
|
629 errors.add(:name, :already_signed, name: name, email: email) and return false |
|
630 else |
|
631 raise e
|
|
632 end |
|
633 end |
|
|
635 def unsubscribe!(token) |
636 if unsubscribed? |
|
637 errors.add(:base, "Already Unsubscribed") |
|
638 elsif unsubscribe_token != token |
|
639 errors.add(:base, "Invalid Unsubscribe Token") |
|
640 else |
|
641 update(notify_by_email: false) |
|
642 end |
|
643 end |
|
645 def already_unsubscribed? |
|
646 errors[:base].include?("Already Unsubscribed") |
|
647 end |
|
649 def invalid_unsubscribe_token? |
|
650 errors[:base].include?("Invalid Unsubscribe Token") |
|
651 end |
|
653 def constituency |
|
654 if constituency_id? |
|
655 @constituency ||= Constituency.find_by_external_id(constituency_id) |
|
656 elsif united_kingdom? |
|
657 @constituency ||= Constituency.find_by_postcode(postcode) |
|
658 end |
|
659 end |
|
661 def signed_token |
|
662 super || generate_and_save_signed_token |
|
663 end |
|
665 def get_email_sent_at_for(timestamp) |
|
666 self[column_name_for(timestamp)] |
|
667 end |
|
669 def set_email_sent_at_for(timestamp, to: Time.current) |
|
670 update_column(column_name_for(timestamp), to)
|
|
671 end |
|
673 def account |
|
674 Mail::Address.new(email).local |
|
675 rescue Mail::Field::ParseError |
|
676 nil |
|
677 end |
|
679 def domain |
|
680 Mail::Address.new(email).domain |
|
681 rescue Mail::Field::ParseError |
|
682 nil |
|
683 end |
|
685 def rate(window = 5.minutes) |
|
686 period = Range.new(created_at - window, created_at) |
|
687 petition.signatures.where(ip_address: ip_address, created_at: period).count |
|
688 end |
|
690 def update_uuid |
|
691 update_column(:uuid, generate_uuid) |
|
692 end |
|
694 def update_canonical_email |
|
695 update_column(:canonical_email, Domain.normalize(email)) |
|
696 end |
|
698 def number |
|
699 super || petition.signature_count + 1 |
|
700 end |
|
702 def email_threshold_reached? |
|
703 email_count >= 5 |
|
704 end |
|
706 def united_kingdom? |
|
707 location_code == 'GB' |
|
708 end |
|
709 alias_method :uk?, :united_kingdom? |
|
711 def update_all(updates) |
|
712 self.class.unscoped.where(id: id).update_all(updates) |
|
713 end |
|
715 def location |
|
716 if postcode? |
|
717 "#{formatted_postcode}, #{location_code}" |
|
718 else |
|
719 location_code
|
|
720 end |
|
721 end |
|
723 def form_duration |
|
724 form_requested_at? ? created_at - form_requested_at : 0 |
|
725 end |
|
727 def form_token_reused? |
|
728 self.class.where(form_token: form_token).count > 1 |
|
729 end |
|
731 private
|
|
733 def formatted_postcode |
|
734 if united_kingdom? |
|
735 postcode.gsub(/\A([A-Z0-9]+?)([A-Z0-9]{3})\z/, "\\1 \\2") |
|
736 else |
|
737 postcode
|
|
738 end |
|
739 end |
|
|
741 def inline_updates? |
742 ENV["INLINE_UPDATES"] == "true" |
|
743 end |
|
745 def generate_uuid |
|
746 Digest::UUID.uuid_v5(Digest::UUID::URL_NAMESPACE, "mailto:#{email}") |
|
747 end |
|
749 def generate_and_save_signed_token |
|
750 token = Authlogic::Random.friendly_token |
|
752 retry_lock do |
|
753 if signed_token? |
|
754 token = read_attribute(:signed_token) |
|
755 else |
|
756 update_column(:signed_token, token) |
|
757 end |
|
758 end |
|
760 token
|
|
761 end |
|
|
763 def column_name_for(timestamp) |
764 self.class.column_name_for(timestamp) |
|
765 end |
|
|
767 def retry_lock |
768 retried = false |
|
770 begin |
|
771 with_lock { yield } |
|
772 rescue PG::InFailedSqlTransaction => e |
|
773 if retried |
|
774 raise e
|
|
775 else |
|
776 retried = true |
|
777 self.class.connection.clear_cache! |
|
778 retry |
|
779 end |
|
780 end |
|
781 end |
|
782end |