1require 'ipaddr'
 
2
 
3require_dependency 'archived'
 
4
 
5module Archived
  • Class "Signature" has 308 lines. It should have 300 or less. » roodi
6  class Signature < ActiveRecord::Base
 
7    include GeoipLookup
 
8
 
 9    ISO8601_TIMESTAMP = /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z\z/
 
 
11    PENDING_STATE = 'pending'
 
12    FRAUDULENT_STATE = 'fraudulent'
 
13    VALIDATED_STATE = 'validated'
 
14    INVALIDATED_STATE = 'invalidated'
 
 
16    STATES = [
 
17      PENDING_STATE, FRAUDULENT_STATE,
 
18      VALIDATED_STATE, INVALIDATED_STATE
 
19    ]
 
 
21    TIMESTAMPS = {
 
22      'government_response' => :government_response_email_at,
 
23      'debate_scheduled'    => :debate_scheduled_email_at,
 
24      'debate_outcome'      => :debate_outcome_email_at,
 
25      'petition_email'      => :petition_email_at
 
26    }
 
 
28    belongs_to :petition
 
29    belongs_to :invalidation
 
30    belongs_to :constituency, primary_key: :external_id
 
 
32    validates :constituency_id, length: { maximum: 255 }
 
33    validates :email, presence: true
 
34    validates :location_code, presence: true
 
35    validates :name, presence: true, length: { maximum: 255 }
 
36    validates :state, presence: true, inclusion: { in: STATES }
 
 
38    attr_readonly :sponsor, :creator
 
 
40    before_destroy do
 
41      !creator?
 
42    end
 
 
44    class << self
 
45      def batch(id = 0, limit: 1000)
 
46        where(arel_table[:id].gteq(id)).order(id: :asc).limit(limit)
 
47      end
 
 
49      def by_most_recent
 
50        order(created_at: :desc)
 
51      end
 
 
53      def column_name_for(timestamp)
 
54        TIMESTAMPS.fetch(timestamp)
 
55      rescue
 
56        raise ArgumentError, "Unknown petition email timestamp: #{timestamp.inspect}"
 
57      end
 
 
59      def destroy!(signature_ids)
 
60        signatures = find(signature_ids)
 
 
62        transaction do
 
63          signatures.each do |signature|
 
64            signature.destroy!
 
65          end
 
66        end
 
67      end
 
 
69      def for_domain(domain)
 
70        where("SUBSTRING(email FROM POSITION('@' IN email) + 1) = ?", domain[1..-1])
 
71      end
 
 
73      def for_email(email)
 
74        where("(REGEXP_REPLACE(LEFT(email, POSITION('@' IN email) - 1), '\\.|\\+.+', '', 'g') || SUBSTRING(email FROM POSITION('@' IN email)) = ?)", normalize_email(email))
 
75      end
 
 
77      def for_ip(ip)
 
78        where("inet(ip_address) <<= inet(?)", ip)
 
79      end
 
 
81      def for_name(name)
 
82        where(arel_table[:name].lower.eq(name.downcase))
 
83      end
 
 
85      def for_petition(id)
 
86        where(petition_id: id)
 
87      end
 
 
89      def for_postcode(postcode)
 
90        where(postcode: PostcodeSanitizer.call(postcode))
 
91      end
 
 
93      def for_sector(postcode)
 
94        where("LEFT(postcode, -3) = ?", PostcodeSanitizer.call(postcode)[0..-4])
 
95      end
 
 
97      def for_timestamp(timestamp, since:)
 
98        column = arel_table[column_name_for(timestamp)]
 
 99        where(column.eq(nil).or(column.lt(since)))
 
100      end
 
 
102      def need_emailing_for(timestamp, since:)
 
103        validated.subscribed.for_timestamp(timestamp, since: since)
 
104      end
 
 
106      def subscribed
 
107        where(notify_by_email: true)
 
108      end
 
 
110      def validated
 
111        where(state: VALIDATED_STATE)
 
112      end
 
 
114      def creator
 
115        where(arel_table[:creator].eq(true))
 
116      end
 
 
118      def sponsors
 
119        where(arel_table[:sponsor].eq(true))
 
120      end
 
  • Method name "search" cyclomatic complexity is 11. It should be 8 or less. » roodi
  • Method "search" has 37 lines. It should have 20 or less. » roodi
122      def search(query, options = {})
 
123        query  = query.to_s
 
124        state  = options[:state]
 
125        window = options[:window]
 
126        page   = [options[:page].to_i, 1].max
 
127        scope  = preload(:petition).by_most_recent
 
 
129        if state.in?(STATES)
 
130          scope = scope.where(state: state)
 
131        end
 
 
133        if window && window =~ ISO8601_TIMESTAMP
 
134          starts_at = window.in_time_zone.at_beginning_of_hour
 
135          ends_at = starts_at.advance(hours: 1)
 
136          scope = scope.where(created_at: starts_at..ends_at)
 
137        elsif window =~ /\A\d+\z/
 
138          starts_at = window.to_i.seconds.ago
 
139          ends_at = Time.current
 
140          scope = scope.where(created_at: starts_at..ends_at)
 
141        end
 
 
143        if ip_search?(query)
 
144          scope = scope.for_ip(query)
 
145        elsif domain_search?(query)
 
146          scope = scope.for_domain(query)
 
147        elsif email_search?(query)
 
148          scope = scope.for_email(query)
 
149        elsif petition_search?(query)
 
150          scope = scope.for_petition(query)
 
151        elsif postcode_search?(query)
 
152          scope = scope.for_postcode(query)
 
153        elsif sector_search?(query)
 
154          scope = scope.for_sector(query)
 
155        else
 
156          scope = scope.for_name(query)
 
157        end
 
 
159        scope.paginate(page: page, per_page: 50)
 
160      end
 
 
162      def subscribe!(signature_ids)
 
163        signatures = find(signature_ids)
 
 
165        transaction do
 
166          signatures.each do |signature|
 
167            signature.update!(notify_by_email: true)
 
168          end
 
169        end
 
170      end
 
 
172      def unsubscribe!(signature_ids)
 
173        signatures = find(signature_ids)
 
 
175        transaction do
 
176          signatures.each do |signature|
 
177            if signature.creator?
 
178              raise RuntimeError, "Can't unsubscribe the creator signature"
 
179            elsif signature.pending?
 
180              raise RuntimeError, "Can't unsubscribe a pending signature"
 
181            else
 
182              signature.update!(notify_by_email: false)
 
183            end
 
184          end
 
185        end
 
186      end
 
 
188      private
 
 
190      def ip_search?(query)
 
191        IPAddr.new(query)
 
192      rescue IPAddr::InvalidAddressError => e
 
193        false
 
194      end
 
 
196      def domain_search?(query)
 
197        query.starts_with?('@')
 
198      end
 
 
200      def email_search?(query)
 
201        query.include?('@')
 
202      end
 
 
204      def petition_search?(query)
 
205        query =~ /\A\d+\z/
 
206      end
 
 
208      def postcode_search?(query)
 
209        PostcodeSanitizer.call(query) =~ PostcodeValidator::PATTERN
 
210      end
 
 
212      def sector_search?(query)
 
213        PostcodeSanitizer.call(query) =~ /\A[A-Z]{1,2}[0-9][0-9A-Z]?XXX\z/
 
214      end
 
 
216      def normalize_email(email)
 
217        "#{normalize_user(email)}@#{normalize_domain(email)}"
 
218      end
 
 
220      def normalize_user(email)
 
221        email.split("@").first.split("+").first.tr(".", "").downcase
 
222      end
 
 
224      def normalize_domain(email)
 
225        email.split("@").last.downcase
 
226      end
 
227    end
 
 
229    def get_email_sent_at_for(timestamp)
 
230      self[column_name_for(timestamp)]
 
231    end
 
 
233    def set_email_sent_at_for(timestamp, to: Time.current)
 
234      update_column(column_name_for(timestamp), to)
 
235    end
 
 
237    def pending?
 
238      state == PENDING_STATE
 
239    end
 
 
241    def fraudulent?
 
242      state == FRAUDULENT_STATE
 
243    end
 
 
245    def validated?
 
246      state == VALIDATED_STATE
 
247    end
 
 
249    def invalidated?
 
250      state == INVALIDATED_STATE
 
251    end
 
 
253    def subscribed?
 
254      validated? && !unsubscribed?
 
255    end
 
 
257    def unsubscribed?
 
258      notify_by_email == false
 
259    end
 
 
261    def unsubscribe!(token)
 
262      if unsubscribed?
 
263        errors.add(:base, "Already Unsubscribed")
 
264      elsif unsubscribe_token != token
 
265        errors.add(:base, "Invalid Unsubscribe Token")
 
266      else
 
267        update(notify_by_email: false)
 
268      end
 
269    end
 
 
271    def already_unsubscribed?
 
272      errors[:base].include?("Already Unsubscribed")
 
273    end
 
 
275    def invalid_unsubscribe_token?
 
276      errors[:base].include?("Invalid Unsubscribe Token")
 
277    end
 
 
279    def united_kingdom?
 
280      location_code == 'GB'
 
281    end
 
282    alias_method :uk?, :united_kingdom?
 
 
284    def location
 
285      if postcode?
 
286        "#{formatted_postcode}, #{location_code}"
 
287      else
 
288        location_code
 
289      end
 
290    end
 
 
292    def account
 
293      Mail::Address.new(email).local
 
294    rescue Mail::Field::ParseError
 
295      nil
 
296    end
 
 
298    def domain
 
299      Mail::Address.new(email).domain
 
300    rescue Mail::Field::ParseError
 
301      nil
 
302    end
 
 
304    private
 
 
306    def formatted_postcode
 
307      if united_kingdom?
 
308        postcode.gsub(/\A([A-Z0-9]+?)([A-Z0-9]{3})\z/, "\\1 \\2")
 
309      else
 
310        postcode
 
311      end
 
312    end
 
 
314    def column_name_for(timestamp)
 
315      self.class.column_name_for(timestamp)
 
316    end
 
317  end
 
318end