diff --git a/app/components/layout/common_html_attributes_component.rb b/app/components/layout/common_html_attributes_component.rb index 63f35e0f6..c79881ba6 100644 --- a/app/components/layout/common_html_attributes_component.rb +++ b/app/components/layout/common_html_attributes_component.rb @@ -4,18 +4,18 @@ class Layout::CommonHtmlAttributesComponent < ApplicationComponent private def attributes - sanitize([dir, lang, html_class].compact.join(" ")) + tag.attributes(dir: dir, lang: lang, class: html_class) end def dir - 'dir="rtl"' if rtl? + "rtl" if rtl? end def lang - "lang=\"#{I18n.locale}\"" + I18n.locale end def html_class - "class=\"tenant-#{Tenant.current_schema}\"" if Rails.application.config.multitenancy + "tenant-#{Tenant.current_schema}" if Rails.application.config.multitenancy end end diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index d22836066..7a27f7d3f 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -164,7 +164,7 @@ class ProposalsController < ApplicationController .sort_by_confidence_score .limit(Setting["featured_proposals_number"]) if @featured_proposals.present? - @resources = @resources.where.not(id: @featured_proposals) + @resources = @resources.excluding(@featured_proposals) end end end diff --git a/app/helpers/shifts_helper.rb b/app/helpers/shifts_helper.rb index 01552a730..34a64e2a2 100644 --- a/app/helpers/shifts_helper.rb +++ b/app/helpers/shifts_helper.rb @@ -24,12 +24,12 @@ module ShiftsHelper end def start_date(polls) - start_date = polls.map(&:starts_at).min.to_date + start_date = polls.minimum(:starts_at).to_date [start_date, Date.current].max end def end_date(polls) - polls.map(&:ends_at).max.to_date + polls.maximum(:ends_at).to_date end def officer_select_options(officers) diff --git a/app/lib/acts_as_paranoid_aliases.rb b/app/lib/acts_as_paranoid_aliases.rb index 76afbbdf0..f8a44f766 100644 --- a/app/lib/acts_as_paranoid_aliases.rb +++ b/app/lib/acts_as_paranoid_aliases.rb @@ -43,7 +43,7 @@ module ActsAsParanoidAliases end def without_confirmed_hide - where(confirmed_hide_at: nil) + excluding(with_confirmed_hide) end def with_hidden diff --git a/app/models/banner.rb b/app/models/banner.rb index ea3a9b513..8d0513a21 100644 --- a/app/models/banner.rb +++ b/app/models/banner.rb @@ -20,7 +20,7 @@ class Banner < ApplicationRecord has_many :web_sections, through: :sections scope :with_active, -> { where(post_started_at: ..Date.current, post_ended_at: Date.current..) } - scope :with_inactive, -> { where.not(id: with_active) } + scope :with_inactive, -> { excluding(with_active) } scope :in_section, ->(section_name) do joins(:web_sections, :sections).where("web_sections.name ilike ?", section_name) end diff --git a/app/models/budget.rb b/app/models/budget.rb index 319f02d4b..894519282 100644 --- a/app/models/budget.rb +++ b/app/models/budget.rb @@ -47,7 +47,7 @@ class Budget < ApplicationRecord accepts_nested_attributes_for :phases scope :published, -> { where(published: true) } - scope :drafting, -> { where.not(id: published) } + scope :drafting, -> { excluding(published) } scope :informing, -> { where(phase: "informing") } scope :accepting, -> { where(phase: "accepting") } scope :reviewing, -> { where(phase: "reviewing") } diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index 53b8c196c..b6b2ae6be 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -73,7 +73,7 @@ class Budget scope :valuation_open, -> { where(valuation_finished: false) } scope :with_admin, -> { where.not(administrator_id: nil) } - scope :without_admin, -> { where(administrator_id: nil) } + scope :without_admin, -> { excluding(with_admin) } scope :without_valuator_group, -> { where(valuator_group_assignments_count: 0) } scope :without_valuator, -> { without_valuator_group.where(valuator_assignments_count: 0) } scope :under_valuation, -> { valuation_open.valuating.with_admin } @@ -87,7 +87,7 @@ class Budget scope :valuation_finished_feasible, -> { where(valuation_finished: true, feasibility: "feasible") } scope :feasible, -> { where(feasibility: "feasible") } scope :unfeasible, -> { where(feasibility: "unfeasible") } - scope :not_unfeasible, -> { where.not(feasibility: "unfeasible") } + scope :not_unfeasible, -> { excluding(unfeasible) } scope :undecided, -> { where(feasibility: "undecided") } scope :with_supports, -> { where(cached_votes_up: 1..) } diff --git a/app/models/concerns/globalizable.rb b/app/models/concerns/globalizable.rb index 5578c37db..0ab046c1b 100644 --- a/app/models/concerns/globalizable.rb +++ b/app/models/concerns/globalizable.rb @@ -102,17 +102,11 @@ module Globalizable translations_foreign_key = reflect_on_association(:translations).foreign_key fallbacks = Globalize.fallbacks(Globalize.locale) - fallbacks_with_order = fallbacks.map.with_index do |locale, order| - "('#{locale}', #{order})" - end.join(", ") - translations_ids = translation_class .select("DISTINCT ON (#{translations_foreign_key}) id") .where(locale: fallbacks) - .joins("LEFT JOIN (VALUES #{fallbacks_with_order}) " \ - "AS locales(name, ordering) " \ - "ON locale = locales.name") - .order(translations_foreign_key, "locales.ordering") + .order(translations_foreign_key) + .in_order_of(:locale, fallbacks) with_translations(fallbacks).where("#{translations_table_name}.id": translations_ids) end diff --git a/app/models/concerns/verification.rb b/app/models/concerns/verification.rb index 298c44b4d..e72ba0ab8 100644 --- a/app/models/concerns/verification.rb +++ b/app/models/concerns/verification.rb @@ -3,7 +3,7 @@ module Verification included do scope :residence_verified, -> { where.not(residence_verified_at: nil) } - scope :residence_unverified, -> { where(residence_verified_at: nil) } + scope :residence_unverified, -> { excluding(residence_verified) } scope :residence_and_phone_verified, -> { residence_verified.where.not(confirmed_phone: nil) } scope :residence_or_phone_unverified, -> { residence_unverified.or(where(confirmed_phone: nil)) } scope :phone_not_fully_confirmed, -> { where(unconfirmed_phone: nil).or(where(confirmed_phone: nil)) } diff --git a/app/models/dashboard/administrator_task.rb b/app/models/dashboard/administrator_task.rb index e0091d0a4..d0682c99d 100644 --- a/app/models/dashboard/administrator_task.rb +++ b/app/models/dashboard/administrator_task.rb @@ -6,8 +6,8 @@ class Dashboard::AdministratorTask < ApplicationRecord default_scope { order(created_at: :asc) } - scope :pending, -> { where(executed_at: nil) } scope :done, -> { where.not(executed_at: nil) } + scope :pending, -> { excluding(done) } def title "#{source.proposal.title} #{source.action.title}" diff --git a/app/models/legislation/process.rb b/app/models/legislation/process.rb index a57f53564..f59ddf377 100644 --- a/app/models/legislation/process.rb +++ b/app/models/legislation/process.rb @@ -54,7 +54,35 @@ class Legislation::Process < ApplicationRecord validates :"#{phase_name}_end_date", presence: true, if: enabled_attribute end - validate :valid_date_ranges + validates :end_date, + comparison: { + greater_than_or_equal_to: :start_date, + message: :invalid_date_range + }, + allow_blank: true, + if: -> { start_date } + validates :debate_end_date, + comparison: { + greater_than_or_equal_to: :debate_start_date, + message: :invalid_date_range + }, + allow_blank: true, + if: -> { debate_start_date } + validates :draft_end_date, + comparison: { + greater_than_or_equal_to: :draft_start_date, + message: :invalid_date_range + }, + allow_blank: true, + if: -> { draft_start_date } + validates :allegations_end_date, + comparison: { + greater_than_or_equal_to: :allegations_start_date, + message: :invalid_date_range + }, + allow_blank: true, + if: -> { allegations_start_date } + validates :background_color, format: { allow_blank: true, with: ->(*) { CSS_HEX_COLOR }} validates :font_color, format: { allow_blank: true, with: ->(*) { CSS_HEX_COLOR }} @@ -140,22 +168,4 @@ class Legislation::Process < ApplicationRecord def self.search(terms) pg_search(terms) end - - private - - def valid_date_ranges - if end_date && start_date && end_date < start_date - errors.add(:end_date, :invalid_date_range) - end - if debate_end_date && debate_start_date && debate_end_date < debate_start_date - errors.add(:debate_end_date, :invalid_date_range) - end - if draft_end_date && draft_start_date && draft_end_date < draft_start_date - errors.add(:draft_end_date, :invalid_date_range) - end - if allegations_end_date && allegations_start_date && - allegations_end_date < allegations_start_date - errors.add(:allegations_end_date, :invalid_date_range) - end - end end diff --git a/app/models/poll.rb b/app/models/poll.rb index 18c78a77d..8b6ec8624 100644 --- a/app/models/poll.rb +++ b/app/models/poll.rb @@ -33,10 +33,33 @@ class Poll < ApplicationRecord belongs_to :budget validates_translation :name, presence: true - validate :date_range - validate :start_date_is_not_past_date, on: :create + + validates :starts_at, presence: true + validates :ends_at, presence: true + + validates :starts_at, + comparison: { + less_than_or_equal_to: :ends_at, + message: ->(*) { I18n.t("errors.messages.invalid_date_range") } + }, + allow_blank: true, + if: -> { ends_at } + validates :starts_at, + comparison: { + greater_than_or_equal_to: ->(*) { Time.current }, + message: ->(*) { I18n.t("errors.messages.past_date") } + }, + allow_blank: true, + on: :create + validates :ends_at, + comparison: { + greater_than_or_equal_to: ->(*) { Time.current }, + message: ->(*) { I18n.t("errors.messages.past_date") } + }, + on: :update, + if: -> { will_save_change_to_ends_at? } + validate :start_date_change, on: :update - validate :end_date_is_not_past_date, on: :update validate :end_date_change, on: :update validate :only_one_active, unless: :public? @@ -80,7 +103,7 @@ class Poll < ApplicationRecord where("? < ends_at and ? >= starts_at", poll.starts_at.beginning_of_day, poll.ends_at.end_of_day) - .where.not(id: poll.id) + .excluding(poll) .where(related: poll.related) end @@ -162,18 +185,6 @@ class Poll < ApplicationRecord Poll::Voter.where(poll: self, user: user, origin: "web").exists? end - def date_range - if starts_at.blank? || ends_at.blank? || starts_at > ends_at - errors.add(:starts_at, I18n.t("errors.messages.invalid_date_range")) - end - end - - def start_date_is_not_past_date - if starts_at.present? && starts_at < Time.current - errors.add(:starts_at, I18n.t("errors.messages.past_date")) - end - end - def start_date_change if will_save_change_to_starts_at? if starts_at_in_database < Time.current @@ -184,12 +195,6 @@ class Poll < ApplicationRecord end end - def end_date_is_not_past_date - if will_save_change_to_ends_at? && ends_at < Time.current - errors.add(:ends_at, I18n.t("errors.messages.past_date")) - end - end - def end_date_change if will_save_change_to_ends_at? && ends_at_in_database < Time.current errors.add(:ends_at, I18n.t("errors.messages.cannot_change_date.poll_ended")) diff --git a/app/models/poll/question/option.rb b/app/models/poll/question/option.rb index c191c5923..299e764bb 100644 --- a/app/models/poll/question/option.rb +++ b/app/models/poll/question/option.rb @@ -20,7 +20,7 @@ class Poll::Question::Option < ApplicationRecord validates_translation :title, presence: true validates :given_order, presence: true, uniqueness: { scope: :question_id } - scope :with_content, -> { where.not(id: without_content) } + scope :with_content, -> { excluding(without_content) } scope :without_content, -> do where(description: "") .where.missing(:images) diff --git a/app/models/proposal.rb b/app/models/proposal.rb index 634077fe0..6cdcc5979 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -79,14 +79,14 @@ class Proposal < ApplicationRecord scope :not_archived, -> { where(created_at: Setting.archived_proposals_date_limit..) } scope :last_week, -> { where(created_at: 7.days.ago..) } scope :retired, -> { where.not(retired_at: nil) } - scope :not_retired, -> { where(retired_at: nil) } + scope :not_retired, -> { excluding(retired) } scope :successful, -> { where(cached_votes_up: Proposal.votes_needed_for_success..) } scope :unsuccessful, -> { where(cached_votes_up: ...Proposal.votes_needed_for_success) } scope :public_for_api, -> { all } scope :selected, -> { where(selected: true) } scope :not_selected, -> { where(selected: false) } scope :published, -> { where.not(published_at: nil) } - scope :draft, -> { where(published_at: nil) } + scope :draft, -> { excluding(published) } scope :not_supported_by_user, ->(user) { where.not(id: user.find_voted_items(votable_type: "Proposal")) } scope :created_by, ->(author) { where(author: author) } @@ -108,15 +108,11 @@ class Proposal < ApplicationRecord tagged_with(user.interests, any: true) .where.not(author_id: user.id) .unsuccessful - .not_followed_by_user(user) + .excluding(followed_by_user(user)) .not_archived .not_supported_by_user(user) end - def self.not_followed_by_user(user) - where.not(id: followed_by_user(user).ids) - end - def to_param "#{id}-#{title}".parameterize end diff --git a/app/models/proposal_notification.rb b/app/models/proposal_notification.rb index 5579fd728..7cad3c705 100644 --- a/app/models/proposal_notification.rb +++ b/app/models/proposal_notification.rb @@ -16,7 +16,7 @@ class ProposalNotification < ApplicationRecord scope :sort_by_moderated, -> { reorder(moderated: :desc) } scope :moderated, -> { where(moderated: true) } - scope :not_moderated, -> { where(moderated: false) } + scope :not_moderated, -> { excluding(moderated) } scope :pending_review, -> { moderated.where(ignored_at: nil) } scope :ignored, -> { moderated.where.not(ignored_at: nil) } diff --git a/app/models/user.rb b/app/models/user.rb index 07c05dd05..8ba6d0f4c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -104,8 +104,8 @@ class User < ApplicationRecord where(document_type: document_type, document_number: document_number) end scope :email_digest, -> { where(email_digest: true) } - scope :active, -> { where(erased_at: nil) } scope :erased, -> { where.not(erased_at: nil) } + scope :active, -> { excluding(erased) } scope :public_for_api, -> { all } scope :by_authors, ->(author_ids) { where(id: author_ids) } scope :by_comments, ->(commentables) do diff --git a/db/dev_seeds/flags.rb b/db/dev_seeds/flags.rb index 582bd088f..dc5db86ab 100644 --- a/db/dev_seeds/flags.rb +++ b/db/dev_seeds/flags.rb @@ -1,19 +1,19 @@ section "Flagging Debates & Comments" do 40.times do debate = Debate.sample - flagger = User.where.not(id: debate.author_id).sample + flagger = User.excluding(debate.author).sample Flag.flag(flagger, debate) end 40.times do comment = Comment.sample - flagger = User.where.not(id: comment.user_id).sample + flagger = User.excluding(comment.user).sample Flag.flag(flagger, comment) end 40.times do proposal = Proposal.sample - flagger = User.where.not(id: proposal.author_id).sample + flagger = User.excluding(proposal.author).sample Flag.flag(flagger, proposal) end end diff --git a/db/dev_seeds/votes.rb b/db/dev_seeds/votes.rb index eb7711136..a91b6d315 100644 --- a/db/dev_seeds/votes.rb +++ b/db/dev_seeds/votes.rb @@ -1,5 +1,5 @@ section "Voting Debates, Proposals & Comments" do - not_org_users = User.where.not(id: User.organizations) + not_org_users = User.excluding(User.organizations) 100.times do voter = not_org_users.level_two_or_three_verified.sample vote = [true, false].sample diff --git a/spec/factory_bot/strategy/insert.rb b/spec/factory_bot/strategy/insert.rb index 5c7468a25..9de345d1c 100644 --- a/spec/factory_bot/strategy/insert.rb +++ b/spec/factory_bot/strategy/insert.rb @@ -11,11 +11,7 @@ module FactoryBot build_class = evaluation.instance_variable_get(:@attribute_assigner) .instance_variable_get(:@build_class) - timestamps = { created_at: Time.current, updated_at: Time.current }.select do |attribute, _| - build_class.has_attribute?(attribute) - end - - build_class.insert!(timestamps.merge(@strategy.result(evaluation))) + build_class.insert!(@strategy.result(evaluation)) end end diff --git a/spec/models/poll/poll_spec.rb b/spec/models/poll/poll_spec.rb index 992c7daa7..d88a1277a 100644 --- a/spec/models/poll/poll_spec.rb +++ b/spec/models/poll/poll_spec.rb @@ -24,12 +24,14 @@ describe Poll do poll.starts_at = nil expect(poll).not_to be_valid - expect(poll.errors[:starts_at]).to eq ["Invalid date range"] + expect(poll.errors[:starts_at]).to eq ["can't be blank"] end it "is not valid without an end date" do poll.ends_at = nil + expect(poll).not_to be_valid + expect(poll.errors[:ends_at]).to eq ["can't be blank"] end it "is not valid without a proper start/end date range" do