1require 'textacular/searchable'
 
2
 
3class Invalidation < ActiveRecord::Base
 
4  extend Searchable(:id, :summary, :details, :petition_id)
 
5  include Browseable
 
6
 
7  belongs_to :petition
 
8  has_many :signatures
 
 9
 
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 }
 
 
17  CONDITIONS = %i[
18
 
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 }
 
  • Block cyclomatic complexity is 13. It should be 4 or less. » roodi
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
 
  • Complexity 2 » saikuro
102  def cancel!(now = Time.current)
 
103    return false if cancelled? || completed?
 
 
105    update(cancelled_at: now)
 
106  end
 
  • Complexity 1 » saikuro
108  def cancelled?
 
109    cancelled_at?
 
110  end
 
  • Complexity 1 » saikuro
112  def completed?
 
113    completed_at?
 
114  end
 
  • Complexity 2 » saikuro
116  def count!
 
117    return false unless pending?
 
 
119    update(matching_count: matching_signatures.count, counted_at: Time.current)
 
120  end
 
  • Complexity 2 » saikuro
122  def start!
 
123    return false unless pending?
 
 
125    InvalidateSignaturesJob.perform_later(self)
 
126    update(enqueued_at: Time.current)
 
127  end
 
  • Complexity 1 » saikuro
129  def started?
 
130    started_at?
 
131  end
 
  • Complexity 1 » saikuro
133  def enqueued?
 
134    enqueued_at?
 
135  end
 
  • Complexity 1 » saikuro
137  def pending?
 
138    !(enqueued? || started? || cancelled? || completed?)
 
139  end
 
  • Complexity 1 » saikuro
141  def running?
 
142    started? && !(completed? || cancelled?)
 
143  end
 
  • DuplicateMethodCall - calls 'matching_count.zero?' 2 times » reek
  • Complexity 4 » saikuro
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
 
  • TooManyStatements - has approx 11 statements » reek
  • Complexity 10 » saikuro
  • Method name "matching_signatures" cyclomatic complexity is 10. It should be 8 or less. » roodi
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
 
  • DuplicateMethodCall - calls 'Time.current' 4 times » reek
  • NestedIterators - contains iterators nested 2 deep » reek
  • TooManyStatements - has approx 9 statements » reek
  • Complexity 6 » saikuro
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
 
  • Complexity 1 » saikuro
193  def petition_scope(scope)
 
194    scope.where(petition_id: petition_id)
 
195  end
 
  • DuplicateMethodCall - calls 'name.strip' 2 times » reek
  • DuplicateMethodCall - calls 'name.strip.downcase' 2 times » reek
  • Complexity 2 » saikuro
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
 
  • Complexity 1 » saikuro
205  def postcode_scope(scope)
 
206    scope.where(postcode: postcode)
 
207  end
 
  • Complexity 1 » saikuro
209  def ip_address_scope(scope)
 
210    scope.where(ip_address: ip_address)
 
211  end
 
  • Complexity 2 » saikuro
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
 
  • Complexity 2 » saikuro
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
 
  • Complexity 1 » saikuro
229  def constituency_id_scope(scope)
 
230    scope.where(constituency_id: constituency_id)
 
231  end
 
  • Complexity 1 » saikuro
233  def location_code_scope(scope)
 
234    scope.where(location_code: location_code)
 
235  end
 
  • Complexity 1 » saikuro
237  def date_range?
 
238    created_before? || created_after?
 
239  end
 
  • DuplicateMethodCall - calls 'table[:created_at]' 2 times » reek
  • Complexity 3 » saikuro
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
 
  • UtilityFunction - doesn't depend on instance state (maybe move it to another class?) » reek
  • Complexity 1 » saikuro
253  def table
 
254    Signature.arel_table
 
255  end
 
  • Complexity 1 » saikuro
257  def calculate_percent_complete
 
258    [[0, ((invalidated_count.to_f / matching_count.to_f) * 100).floor].max, 100].min
 
259  end
 
  • UncommunicativeVariableName - has the variable name 'c' » reek
  • Complexity 2 » saikuro
261  def applied_conditions
 
262    CONDITIONS.select{ |c| read_attribute(c).present? }
 
263  end
 
264end