1require 'textacular/searchable' |
|
2require_dependency 'archived' |
|
4module Archived |
|
|
5 class Petition < ActiveRecord::Base |
6 STOPPED_STATE = 'stopped' |
|
7 CLOSED_STATE = 'closed' |
|
8 HIDDEN_STATE = 'hidden' |
|
9 REJECTED_STATE = 'rejected' |
|
10 STATES = [STOPPED_STATE, CLOSED_STATE, HIDDEN_STATE, REJECTED_STATE] |
|
11 PUBLISHED_STATES = [CLOSED_STATE] |
|
12 VISIBLE_STATES = [CLOSED_STATE, REJECTED_STATE] |
|
13 MODERATED_STATES = [CLOSED_STATE, HIDDEN_STATE, REJECTED_STATE] |
|
14 DEBATABLE_STATES = [CLOSED_STATE] |
|
16 belongs_to :parliament, inverse_of: :petitions, required: true |
|
17 belongs_to :locked_by, class_name: 'AdminUser' |
|
19 has_one :creator, -> { creator }, class_name: "Signature" |
|
20 has_one :debate_outcome, dependent: :destroy |
|
21 has_one :government_response, dependent: :destroy |
|
22 has_one :note, dependent: :destroy |
|
23 has_one :rejection, dependent: :destroy |
|
25 has_many :emails, :dependent => :destroy |
|
26 has_many :signatures |
|
27 has_many :sponsors, -> { sponsors }, class_name: "Signature" |
|
29 validates :action, presence: true, length: { maximum: 150 } |
|
30 validates :background, length: { maximum: 300 }, allow_blank: true |
|
31 validates :additional_details, length: { maximum: 1000 }, allow_blank: true |
|
32 validates :state, presence: true, inclusion: STATES |
|
33 validates :closed_at, presence: true, if: :closed? |
|
35 before_save :update_debate_state, if: :scheduled_debate_date_changed? |
|
37 extend Searchable(:action, :background, :additional_details) |
|
38 include Browseable, Taggable |
|
40 filter :parliament |
|
42 facet :all, -> { visible.by_most_signatures } |
|
43 facet :awaiting_response, -> { awaiting_response.by_waiting_for_response_longest } |
|
44 facet :awaiting_debate_date, -> { awaiting_debate_date.by_waiting_for_debate_longest } |
|
45 facet :with_debate_outcome, -> { with_debate_outcome.by_most_recent_debate_outcome } |
|
46 facet :with_debated_outcome, -> { with_debated_outcome.by_most_recent_debate_outcome } |
|
47 facet :published, -> { published.by_most_signatures } |
|
48 facet :stopped, -> { stopped.by_most_signatures } |
|
49 facet :closed, -> { closed.by_most_signatures } |
|
50 facet :rejected, -> { rejected.by_most_signatures } |
|
51 facet :hidden, -> { hidden.by_most_recent } |
|
52 facet :with_response, -> { with_response.by_most_signatures } |
|
53 facet :debated, -> { debated.by_most_recent_debate_outcome } |
|
54 facet :not_debated, -> { not_debated.by_most_recent_debate_outcome } |
|
55 facet :by_most_signatures, -> { by_most_signatures } |
|
56 facet :by_created_at, -> { by_created_at } |
|
57 facet :in_debate_queue, -> { in_debate_queue.by_waiting_for_debate_longest } |
|
59 default_scope { preload(:parliament) } |
|
61 delegate :threshold_for_response, :threshold_for_debate, to: :parliament |
|
62 delegate :show_on_a_map?, to: :parliament |
|
64 with_options allow_nil: true, prefix: true do |
|
65 delegate :name, :email, to: :creator |
|
66 delegate :code, :details, to: :rejection |
|
67 delegate :summary, :details, :created_at, :updated_at, to: :government_response |
|
68 delegate :date, :transcript_url, :video_url, :overview, to: :debate_outcome, prefix: :debate |
|
69 delegate :debate_pack_url, to: :debate_outcome, prefix: false |
|
70 end |
|
72 alias_attribute :open_at, :opened_at |
|
74 class << self |
|
75 def for_state(state) |
|
76 where(state: state) |
|
77 end |
|
79 def by_created_at |
|
80 reorder(created_at: :asc) |
|
81 end |
|
83 def by_most_recent_debate_outcome |
|
84 reorder(debate_outcome_at: :desc, created_at: :desc) |
|
85 end |
|
87 def by_waiting_for_debate_longest |
|
88 reorder(debate_threshold_reached_at: :asc, created_at: :desc) |
|
89 end |
|
91 def by_most_recent |
|
92 reorder(created_at: :desc) |
|
93 end |
|
95 def by_most_signatures |
|
96 reorder(signature_count: :desc) |
|
97 end |
|
99 def by_waiting_for_response_longest |
|
100 reorder(response_threshold_reached_at: :asc, created_at: :desc) |
|
101 end |
|
103 def awaiting_debate_date |
|
104 debate_threshold_reached.not_scheduled
|
|
105 end |
|
107 def awaiting_response |
|
108 response_threshold_reached.not_responded
|
|
109 end |
|
111 def not_responded |
|
112 where(government_response_at: nil) |
|
113 end |
|
115 def with_response |
|
116 where.not(government_response_at: nil).preload(:government_response) |
|
117 end |
|
119 def response_threshold_reached |
|
120 where.not(response_threshold_reached_at: nil) |
|
121 end |
|
123 def published |
|
124 where(state: PUBLISHED_STATES) |
|
125 end |
|
127 def moderated |
|
128 where(state: MODERATED_STATES) |
|
129 end |
|
131 def stopped |
|
132 where(state: STOPPED_STATE) |
|
133 end |
|
135 def closed |
|
136 where(state: CLOSED_STATE) |
|
137 end |
|
139 def rejected |
|
140 where(state: REJECTED_STATE) |
|
141 end |
|
143 def hidden |
|
144 where(state: HIDDEN_STATE) |
|
145 end |
|
147 def debateable |
|
148 where(state: DEBATABLE_STATES) |
|
149 end |
|
151 def debated |
|
152 where(debate_state: 'debated').preload(:debate_outcome) |
|
153 end |
|
155 def not_debated |
|
156 where(debate_state: 'not_debated') |
|
157 end |
|
159 def debate_threshold_reached |
|
160 where.not(debate_threshold_reached_at: nil) |
|
161 end |
|
163 def debate_scheduled |
|
164 where.not(scheduled_debate_date: nil) |
|
165 end |
|
167 def not_scheduled |
|
168 where(scheduled_debate_date: nil) |
|
169 end |
|
171 def with_debate_outcome |
|
172 where.not(debate_outcome_at: nil) |
|
173 end |
|
175 def with_debated_outcome |
|
176 debated.where.not(debate_outcome_at: nil) |
|
177 end |
|
179 def visible |
|
180 where(state: VISIBLE_STATES) |
|
181 end |
|
183 def in_need_of_marking_as_debated(date = Date.current) |
|
184 where(scheduled_debate_state.and(debate_date_in_the_past(date)))
|
|
185 end |
|
187 def mark_petitions_as_debated!(date = Date.current) |
|
188 in_need_of_marking_as_debated(date).update_all(debate_state: 'debated') |
|
189 end |
|
191 def in_debate_queue |
|
192 where(threshold_for_debate_reached.or(scheduled_for_debate))
|
|
193 end |
|
195 private
|
|
197 def debate_date_in_the_past(date) |
|
198 arel_table[:scheduled_debate_date].lt(date) |
|
199 end |
|
201 def scheduled_debate_state |
|
202 arel_table[:debate_state].eq('scheduled') |
|
203 end |
|
205 def threshold_for_debate_reached |
|
206 arel_table[:debate_threshold_reached_at].not_eq(nil) |
|
207 end |
|
209 def scheduled_for_debate |
|
210 arel_table[:scheduled_debate_date].not_eq(nil) |
|
211 end |
|
212 end |
|
214 def moderated? |
|
215 state.in?(MODERATED_STATES) |
|
216 end |
|
218 def can_have_debate_added? |
|
219 state.in?(DEBATABLE_STATES) |
|
220 end |
|
222 def stopped? |
|
223 state == STOPPED_STATE |
|
224 end |
|
226 def closed? |
|
227 state == CLOSED_STATE |
|
228 end |
|
230 def rejected? |
|
231 state == REJECTED_STATE |
|
232 end |
|
234 def hidden? |
|
235 state == HIDDEN_STATE |
|
236 end |
|
238 def published? |
|
239 state.in?(PUBLISHED_STATES) |
|
240 end |
|
242 def duration |
|
243 if parliament.petition_duration? |
|
244 parliament.petition_duration
|
|
245 elsif opened_at? |
|
246 calculate_petition_duration
|
|
247 else |
|
248 0 |
|
249 end |
|
250 end |
|
252 def closed_early_due_to_election? |
|
253 closed_at == parliament.dissolution_at
|
|
254 end |
|
256 def government_response? |
|
257 government_response_at && government_response
|
|
258 end |
|
260 def threshold_for_debate_reached? |
|
261 signature_count >= parliament.threshold_for_debate
|
|
262 end |
|
264 def threshold_for_response_reached? |
|
265 signature_count >= parliament.threshold_for_response
|
|
266 end |
|
268 def signatures_by_constituency |
|
269 if defined?(@_signatures_by_constituency) |
|
270 @_signatures_by_constituency |
|
271 else |
|
272 if signatures_by_constituency? |
|
273 @_signatures_by_constituency = calculate_signatures_by_constituency(super) |
|
274 else |
|
275 []
|
|
276 end |
|
277 end |
|
278 end |
|
280 def signatures_by_country |
|
281 if defined?(@_signatures_by_country) |
|
282 @_signatures_by_country |
|
283 else |
|
284 if signatures_by_country? |
|
285 @_signatures_by_country = calculate_signatures_by_country(super) |
|
286 else |
|
287 []
|
|
288 end |
|
289 end |
|
290 end |
|
292 def get_email_requested_at_for(name) |
|
293 self["email_requested_for_#{name}_at"] |
|
294 end |
|
296 def set_email_requested_at_for(name, to: Time.current) |
|
297 update_column("email_requested_for_#{name}_at", to) |
|
298 end |
|
300 def signatures_to_email_for(name) |
|
|
301 if timestamp = get_email_requested_at_for(name) |
302 signatures.need_emailing_for(name, since: timestamp) |
|
303 else |
|
304 raise ArgumentError, "The #{name} email has not been requested for petition #{id}" |
|
305 end |
|
306 end |
|
308 def update_debate_state |
|
309 self.debate_state = evaluate_debate_state |
|
310 end |
|
312 def update_lock!(user, now = Time.current) |
|
313 if locked_by == user |
|
314 update!(locked_at: now) |
|
315 end |
|
316 end |
|
318 def checkout!(user, now = Time.current) |
|
319 with_lock do |
|
320 if locked_by.present? && locked_by != user |
|
321 false |
|
322 else |
|
323 update!(locked_by: user, locked_at: now) |
|
324 end |
|
325 end |
|
326 end |
|
328 def force_checkout!(user, now = Time.current) |
|
329 update!(locked_by: user, locked_at: now) |
|
330 end |
|
332 def release!(user) |
|
333 with_lock do |
|
334 if locked_by.present? && locked_by == user |
|
335 update!(locked_by: nil, locked_at: nil) |
|
336 end |
|
337 end |
|
338 end |
|
340 private
|
|
342 def evaluate_debate_state |
|
343 if scheduled_debate_date? |
|
344 scheduled_debate_date > Date.current ? 'scheduled' : 'debated' |
|
345 else |
|
346 'awaiting' |
|
347 end |
|
348 end |
|
350 def calculate_petition_duration |
|
351 if opened_at + 3.months == closed_at |
|
352 3 |
|
353 elsif opened_at + 6.months == closed_at |
|
354 6 |
|
355 elsif opened_at + 9.months == closed_at |
|
356 9 |
|
357 elsif opened_at + 12.months == closed_at |
|
358 12 |
|
359 else |
|
360 Rational(closed_at - opened_at, 86400 * 30).to_f |
|
361 end |
|
362 end |
|
364 def constituencies(external_ids) |
|
365 Constituency.where(external_id: external_ids).order(:name) |
|
366 end |
|
368 def calculate_signatures_by_constituency(hash) |
|
369 constituencies(hash.keys).map do |constituency| |
|
370 {
|
|
371 name: constituency.name, |
|
372 ons_code: constituency.ons_code, |
|
373 mp: constituency.mp_name, |
|
374 signature_count: hash[constituency.external_id] |
|
375 }
|
|
376 end |
|
377 end |
|
379 def locations(codes) |
|
380 Location.where(code: codes).order(:name) |
|
381 end |
|
383 def calculate_signatures_by_country(hash) |
|
384 locations(hash.keys).map do |location| |
|
385 {
|
|
386 name: location.name, |
|
387 code: location.code, |
|
388 signature_count: hash[location.code] |
|
389 }
|
|
390 end |
|
391 end |
|
392 end |
|
393end |