diff --git a/app/models/appearance.rb b/app/models/appearance.rb index c79326e84275ae1c5bc325076ca7db2c5567dc97..f9c48482be74537531f7c5ff72646e2ccb4497a6 100644 --- a/app/models/appearance.rb +++ b/app/models/appearance.rb @@ -10,5 +10,5 @@ class Appearance < ActiveRecord::Base mount_uploader :logo, AttachmentUploader mount_uploader :header_logo, AttachmentUploader - has_many :uploads, as: :model, dependent: :destroy + has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 668caef0d2cff5d1ade60b86a6db91e2fb82965a..b0d7f7ef5f525d41ad74a733c0a2106d7afce922 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -13,13 +13,13 @@ class ApplicationSetting < ActiveRecord::Base [\r\n] # any number of newline characters }x - serialize :restricted_visibility_levels # rubocop:disable Cop/ActiverecordSerialize - serialize :import_sources # rubocop:disable Cop/ActiverecordSerialize - serialize :disabled_oauth_sign_in_sources, Array # rubocop:disable Cop/ActiverecordSerialize - serialize :domain_whitelist, Array # rubocop:disable Cop/ActiverecordSerialize - serialize :domain_blacklist, Array # rubocop:disable Cop/ActiverecordSerialize - serialize :repository_storages # rubocop:disable Cop/ActiverecordSerialize - serialize :sidekiq_throttling_queues, Array # rubocop:disable Cop/ActiverecordSerialize + serialize :restricted_visibility_levels # rubocop:disable Cop/ActiveRecordSerialize + serialize :import_sources # rubocop:disable Cop/ActiveRecordSerialize + serialize :disabled_oauth_sign_in_sources, Array # rubocop:disable Cop/ActiveRecordSerialize + serialize :domain_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize + serialize :domain_blacklist, Array # rubocop:disable Cop/ActiveRecordSerialize + serialize :repository_storages # rubocop:disable Cop/ActiveRecordSerialize + serialize :sidekiq_throttling_queues, Array # rubocop:disable Cop/ActiveRecordSerialize cache_markdown_field :sign_in_text cache_markdown_field :help_page_text diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb index 46d412fbd72a2ce3e86ef1b1596651b5774afe53..112a8778b4ecb2c39321c460e3684af005e1aaf9 100644 --- a/app/models/audit_event.rb +++ b/app/models/audit_event.rb @@ -1,5 +1,5 @@ class AuditEvent < ActiveRecord::Base - serialize :details, Hash # rubocop:disable Cop/ActiverecordSerialize + serialize :details, Hash # rubocop:disable Cop/ActiveRecordSerialize belongs_to :user, foreign_key: :author_id diff --git a/app/models/board.rb b/app/models/board.rb index 18081a32157f46f264b68ff92fb039a9ed246ee8..97d0f55092537a4a70264c7e03a0b3dab535f044 100644 --- a/app/models/board.rb +++ b/app/models/board.rb @@ -1,7 +1,7 @@ class Board < ActiveRecord::Base belongs_to :project - has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all + has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent validates :project, presence: true diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 2e7a80d308b360aff626821b288e6f5126f25683..5fb4c7e3cb3cff78a2739be856daa79eab67443b 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -19,8 +19,8 @@ module Ci ) end - serialize :options # rubocop:disable Cop/ActiverecordSerialize - serialize :yaml_variables, Gitlab::Serializer::Ci::Variables # rubocop:disable Cop/ActiverecordSerialize + serialize :options # rubocop:disable Cop/ActiveRecordSerialize + serialize :yaml_variables, Gitlab::Serializer::Ci::Variables # rubocop:disable Cop/ActiveRecordSerialize delegate :name, to: :project, prefix: true diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index c5847dee7f7e79ada66fe67026bdaa4a8ccfd7c5..b646b32fc64476fc43d79c2b2b0c9ec2fa50e0aa 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -14,7 +14,7 @@ module Ci has_many :stages has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id has_many :builds, foreign_key: :commit_id - has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id + has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id # rubocop:disable Cop/ActiveRecordDependent # Merge requests for which the current pipeline is running against # the merge request's latest commit. diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index d12f96f3d0bbf85f9d34635fb29f6916d7d912fb..f5790f9744f61922a90a266707077c8ee79bfaf5 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -8,7 +8,7 @@ module Ci FORM_EDITABLE = %i[description tag_list active run_untagged locked].freeze has_many :builds - has_many :runner_projects, dependent: :destroy + has_many :runner_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :projects, through: :runner_projects has_one :last_build, ->() { order('id DESC') }, class_name: 'Ci::Build' diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb index 564334ad1ada8363f288fc8ada3d83d44f371232..c58ce5c3717a60056da48879ba155815f91e33ae 100644 --- a/app/models/ci/trigger_request.rb +++ b/app/models/ci/trigger_request.rb @@ -6,7 +6,7 @@ module Ci belongs_to :pipeline, foreign_key: :commit_id has_many :builds - serialize :variables # rubocop:disable Cop/ActiverecordSerialize + serialize :variables # rubocop:disable Cop/ActiveRecordSerialize def user_variables return [] unless variables diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb index a7fd0a15f0fa0a6673fc5b86b3333d1c644d61e1..f4f9b037957634318fb820e85558cf403830137c 100644 --- a/app/models/concerns/awardable.rb +++ b/app/models/concerns/awardable.rb @@ -2,7 +2,7 @@ module Awardable extend ActiveSupport::Concern included do - has_many :award_emoji, -> { includes(:user).order(:id) }, as: :awardable, dependent: :destroy + has_many :award_emoji, -> { includes(:user).order(:id) }, as: :awardable, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent if self < Participable # By default we always load award_emoji user association diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 41c8b52527310b3fd7d6756ed5fae347cf3de177..23cb85600da6c10a6b1cf946a2099d17925e297e 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -30,7 +30,7 @@ module Issuable belongs_to :updated_by, class_name: "User" belongs_to :last_edited_by, class_name: 'User' belongs_to :milestone - has_many :notes, as: :noteable, inverse_of: :noteable, dependent: :destroy do + has_many :notes, as: :noteable, inverse_of: :noteable, dependent: :destroy do # rubocop:disable Cop/ActiveRecordDependent def authors_loaded? # We check first if we're loaded to not load unnecessarily. loaded? && to_a.all? { |note| note.association(:author).loaded? } @@ -42,9 +42,9 @@ module Issuable end end - has_many :label_links, as: :target, dependent: :destroy + has_many :label_links, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :labels, through: :label_links - has_many :todos, as: :target, dependent: :destroy + has_many :todos, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_one :metrics diff --git a/app/models/concerns/protected_ref.rb b/app/models/concerns/protected_ref.rb index 47e71c58557771dbad6de1ebdf43e9d3ea3c82df..fc6b840f7a8fac5dd4b533186e2ce4690e854cd8 100644 --- a/app/models/concerns/protected_ref.rb +++ b/app/models/concerns/protected_ref.rb @@ -17,7 +17,7 @@ module ProtectedRef class_methods do def protected_ref_access_levels(*types) types.each do |type| - has_many :"#{type}_access_levels", dependent: :destroy + has_many :"#{type}_access_levels", dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent validates :"#{type}_access_levels", length: { is: 1, message: "are restricted to a single instance per #{self.model_name.human}." } diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb index ee108f010a69052cee07c6fd41cf81ac48caffbb..f5048d17d806069f910403ddbf44c3725f20223d 100644 --- a/app/models/concerns/routable.rb +++ b/app/models/concerns/routable.rb @@ -4,8 +4,8 @@ module Routable extend ActiveSupport::Concern included do - has_one :route, as: :source, autosave: true, dependent: :destroy - has_many :redirect_routes, as: :source, autosave: true, dependent: :destroy + has_one :route, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :redirect_routes, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent validates_associated :route validates :route, presence: true diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb index 647a6cad3d7b614a4b92b855ba43268815d2482b..bd75f25a210ec73f85d5c4f083a191559f710395 100644 --- a/app/models/concerns/spammable.rb +++ b/app/models/concerns/spammable.rb @@ -8,7 +8,7 @@ module Spammable end included do - has_one :user_agent_detail, as: :subject, dependent: :destroy + has_one :user_agent_detail, as: :subject, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent attr_accessor :spam attr_accessor :spam_log diff --git a/app/models/concerns/subscribable.rb b/app/models/concerns/subscribable.rb index f60a0f8f438eacd3e3b9b53173bc809d5cdfd324..274b38a7708cfc98f3fd152d93f41998c693e7a8 100644 --- a/app/models/concerns/subscribable.rb +++ b/app/models/concerns/subscribable.rb @@ -9,7 +9,7 @@ module Subscribable extend ActiveSupport::Concern included do - has_many :subscriptions, dependent: :destroy, as: :subscribable + has_many :subscriptions, dependent: :destroy, as: :subscribable # rubocop:disable Cop/ActiveRecordDependent end def subscribed?(user, project = nil) diff --git a/app/models/concerns/time_trackable.rb b/app/models/concerns/time_trackable.rb index 9cf834407841da3c1ae492135831dcfd815ac9c3..b517ddaebd781bdd10b8f0cc886d54fea76e1e99 100644 --- a/app/models/concerns/time_trackable.rb +++ b/app/models/concerns/time_trackable.rb @@ -18,7 +18,7 @@ module TimeTrackable validates :time_estimate, numericality: { message: 'has an invalid format' }, allow_nil: false validate :check_negative_time_spent - has_many :timelogs, dependent: :destroy + has_many :timelogs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent end def spend_time(options) diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb index 053f2a11aa02699556aba609562bfa12ce0207f0..51768dd96bc897066c905a9021a1e6bf70b26a07 100644 --- a/app/models/deploy_key.rb +++ b/app/models/deploy_key.rb @@ -1,5 +1,5 @@ class DeployKey < Key - has_many :deploy_keys_projects, dependent: :destroy + has_many :deploy_keys_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :projects, through: :deploy_keys_projects scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) } diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index 20ef137850095dc56786623c3cadc41adaf2bd73..e9a60e6ce098c24216d2e39450991ab7ea9ebb67 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -6,9 +6,9 @@ class DiffNote < Note NOTEABLE_TYPES = %w(MergeRequest Commit).freeze - serialize :original_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize - serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize - serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize + serialize :original_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize + serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize + serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize validates :original_position, presence: true validates :position, presence: true diff --git a/app/models/environment.rb b/app/models/environment.rb index eb24ff00ce39e179206b8346ff5fcb534d51109b..e9ebf0637f39671a9fc9aa4f6814df369c9d86a4 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -6,7 +6,7 @@ class Environment < ActiveRecord::Base belongs_to :project, required: true, validate: true - has_many :deployments, dependent: :destroy + has_many :deployments, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_one :last_deployment, -> { order('deployments.id DESC') }, class_name: 'Deployment' before_validation :nullify_external_url diff --git a/app/models/event.rb b/app/models/event.rb index 29bc141c5cdf7a0d4569e0a5f7849dc9c6343c14..8d93a228494e5f2fd8d5c8f8929d5286b09371cc 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -50,7 +50,7 @@ class Event < ActiveRecord::Base belongs_to :target, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations # For Hash only - serialize :data # rubocop:disable Cop/ActiverecordSerialize + serialize :data # rubocop:disable Cop/ActiveRecordSerialize # Callbacks after_create :reset_project_activity diff --git a/app/models/group.rb b/app/models/group.rb index a6fdb30f84cee80d91f2df9bf2b833f0ad8635ee..b93fce6100d1fc7530adab14a850dcb8fb201465 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -8,7 +8,7 @@ class Group < Namespace include Referable include SelectForProjectAuthorization - has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source + has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent alias_method :members, :group_members has_many :users, through: :group_members has_many :owners, @@ -16,11 +16,11 @@ class Group < Namespace through: :group_members, source: :user - has_many :requesters, -> { where.not(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' + has_many :requesters, -> { where.not(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent - has_many :project_group_links, dependent: :destroy + has_many :project_group_links, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :shared_projects, through: :project_group_links, source: :project - has_many :notification_settings, dependent: :destroy, as: :source + has_many :notification_settings, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent has_many :labels, class_name: 'GroupLabel' validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } @@ -31,7 +31,7 @@ class Group < Namespace validates :two_factor_grace_period, presence: true, numericality: { greater_than_or_equal_to: 0 } mount_uploader :avatar, AvatarUploader - has_many :uploads, as: :model, dependent: :destroy + has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent after_create :post_create_hook after_destroy :post_destroy_hook diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 7503f3739c31c0ef1a2a5bdedc963129b11fbbd4..7a9f899795956a62e00923087defc3a99623c110 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -12,7 +12,7 @@ class WebHook < ActiveRecord::Base default_value_for :repository_update_events, false default_value_for :enable_ssl_verification, true - has_many :web_hook_logs, dependent: :destroy + has_many :web_hook_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent scope :push_hooks, -> { where(push_events: true) } scope :tag_push_hooks, -> { where(tag_push_events: true) } diff --git a/app/models/hooks/web_hook_log.rb b/app/models/hooks/web_hook_log.rb index d73cfcf630de986dbdb704c04543a983b8848740..e72c125fb694b08780c01fca3d180d241762d626 100644 --- a/app/models/hooks/web_hook_log.rb +++ b/app/models/hooks/web_hook_log.rb @@ -1,9 +1,9 @@ class WebHookLog < ActiveRecord::Base belongs_to :web_hook - serialize :request_headers, Hash # rubocop:disable Cop/ActiverecordSerialize - serialize :request_data, Hash # rubocop:disable Cop/ActiverecordSerialize - serialize :response_headers, Hash # rubocop:disable Cop/ActiverecordSerialize + serialize :request_headers, Hash # rubocop:disable Cop/ActiveRecordSerialize + serialize :request_data, Hash # rubocop:disable Cop/ActiveRecordSerialize + serialize :response_headers, Hash # rubocop:disable Cop/ActiveRecordSerialize validates :web_hook, presence: true diff --git a/app/models/issue.rb b/app/models/issue.rb index a97e88f76f6de693390a21cd6e56b9e977f1f6b7..01f985823e14d0d3c4b301a6606c2d04c72f21a2 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -23,9 +23,14 @@ class Issue < ActiveRecord::Base belongs_to :project belongs_to :moved_to, class_name: 'Issue' - has_many :events, as: :target, dependent: :destroy + has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent - has_many :merge_requests_closing_issues, class_name: 'MergeRequestsClosingIssues', dependent: :delete_all + has_many :merge_requests_closing_issues, + class_name: 'MergeRequestsClosingIssues', + dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent + + has_many :issue_assignees + has_many :assignees, class_name: "User", through: :issue_assignees has_many :issue_assignees has_many :assignees, class_name: "User", through: :issue_assignees diff --git a/app/models/label.rb b/app/models/label.rb index ed6a8411da9dd2de3f57bd14a2eee9e3b7822daf..674bb3f2720fadac5aeb52aa27b4790fcf675bb7 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -15,9 +15,9 @@ class Label < ActiveRecord::Base default_value_for :color, DEFAULT_COLOR - has_many :lists, dependent: :destroy + has_many :lists, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :priorities, class_name: 'LabelPriority' - has_many :label_links, dependent: :destroy + has_many :label_links, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :issues, through: :label_links, source: :target, source_type: 'Issue' has_many :merge_requests, through: :label_links, source: :target, source_type: 'MergeRequest' diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb index 2d5909ab25eac5abd871e261e4b5ef447d2f9240..c36be956ff02064e9ae614dcc30d0619927c56bb 100644 --- a/app/models/legacy_diff_note.rb +++ b/app/models/legacy_diff_note.rb @@ -7,7 +7,7 @@ class LegacyDiffNote < Note include NoteOnDiff - serialize :st_diff # rubocop:disable Cop/ActiverecordSerialize + serialize :st_diff # rubocop:disable Cop/ActiveRecordSerialize validates :line_code, presence: true, line_code: true diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb index 7712d5783e049d0d2121b6559603669a2b9c9da9..b7cf96abe8328aebe2bbf8c84ff6daa2fdd10b30 100644 --- a/app/models/lfs_object.rb +++ b/app/models/lfs_object.rb @@ -1,5 +1,5 @@ class LfsObject < ActiveRecord::Base - has_many :lfs_objects_projects, dependent: :destroy + has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :projects, through: :lfs_objects_projects validates :oid, presence: true, uniqueness: true diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 808212c780cbc9b784c6278a67a47c6ede721e14..2752181ed7958e8863c0e2753b4b4e42302f08ba 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -12,19 +12,21 @@ class MergeRequest < ActiveRecord::Base belongs_to :source_project, class_name: "Project" belongs_to :merge_user, class_name: "User" - has_many :merge_request_diffs, dependent: :destroy + has_many :merge_request_diffs has_one :merge_request_diff, -> { order('merge_request_diffs.id DESC') } belongs_to :head_pipeline, foreign_key: "head_pipeline_id", class_name: "Ci::Pipeline" - has_many :events, as: :target, dependent: :destroy + has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent - has_many :merge_requests_closing_issues, class_name: 'MergeRequestsClosingIssues', dependent: :delete_all + has_many :merge_requests_closing_issues, + class_name: 'MergeRequestsClosingIssues', + dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent belongs_to :assignee, class_name: "User" - serialize :merge_params, Hash # rubocop:disable Cop/ActiverecordSerialize + serialize :merge_params, Hash # rubocop:disable Cop/ActiveRecordSerialize after_create :ensure_merge_request_diff, unless: :importing? after_update :reload_diff_if_branch_changed diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index f1ee4d3f7a91d7781005e0c6b997d5b00e546e21..0cbf58a23255520c72d1c71d6d3f8ecd0e2814b4 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -12,8 +12,8 @@ class MergeRequestDiff < ActiveRecord::Base belongs_to :merge_request has_many :merge_request_diff_files, -> { order(:merge_request_diff_id, :relative_order) } - serialize :st_commits # rubocop:disable Cop/ActiverecordSerialize - serialize :st_diffs # rubocop:disable Cop/ActiverecordSerialize + serialize :st_commits # rubocop:disable Cop/ActiveRecordSerialize + serialize :st_diffs # rubocop:disable Cop/ActiveRecordSerialize state_machine :state, initial: :empty do state :collected diff --git a/app/models/milestone.rb b/app/models/milestone.rb index d2e2749f70dfce1696b5f9b01e7f752cf1632c49..c0ccbf8e27e476b90669c57adab92b737be4e9f9 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -21,7 +21,7 @@ class Milestone < ActiveRecord::Base has_many :issues has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues has_many :merge_requests - has_many :events, as: :target, dependent: :destroy + has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent scope :active, -> { with_state(:active) } scope :closed, -> { with_state(:closed) } diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 672eab94c07ff54ffa4bf57c93dac8c6c82473df..15713fc5f6de72b8b0d01b6a79b757e0232a85c1 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -15,13 +15,13 @@ class Namespace < ActiveRecord::Base cache_markdown_field :description, pipeline: :description - has_many :projects, dependent: :destroy + has_many :projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :project_statistics belongs_to :owner, class_name: "User" belongs_to :parent, class_name: "Namespace" has_many :children, class_name: "Namespace", foreign_key: :parent_id - has_one :chat_team, dependent: :destroy + has_one :chat_team, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent validates :owner, presence: true, unless: ->(n) { n.type == "Group" } validates :name, diff --git a/app/models/note.rb b/app/models/note.rb index dfd435bcdf6c6883d31325e2d3b0ae4e22b541bc..3d39047d32f7758a9fd583c802565a00731970eb 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -46,8 +46,8 @@ class Note < ActiveRecord::Base belongs_to :updated_by, class_name: "User" belongs_to :last_edited_by, class_name: 'User' - has_many :todos, dependent: :destroy - has_many :events, as: :target, dependent: :destroy + has_many :todos, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_one :system_note_metadata delegate :gfm_reference, :local_reference, to: :noteable diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb index 6e13f9b2089d8de0d540030ee58ddc830f911ac4..654be927ed868ea3c9ccf82946a21e49329b3849 100644 --- a/app/models/personal_access_token.rb +++ b/app/models/personal_access_token.rb @@ -3,7 +3,7 @@ class PersonalAccessToken < ActiveRecord::Base include TokenAuthenticatable add_authentication_token_field :token - serialize :scopes, Array # rubocop:disable Cop/ActiverecordSerialize + serialize :scopes, Array # rubocop:disable Cop/ActiveRecordSerialize belongs_to :user diff --git a/app/models/project.rb b/app/models/project.rb index 3a5a01db518a91df8b1012c2ba3b1556ce0779ee..5c673fdb7bad603e3bed90004f9b7f6ee9637cae 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -59,6 +59,7 @@ class Project < ActiveRecord::Base update_column(:last_repository_updated_at, self.created_at) end + before_destroy :remove_private_deploy_keys after_destroy :remove_pages # update visibility_level of forks @@ -80,96 +81,108 @@ class Project < ActiveRecord::Base belongs_to :namespace has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event' - has_many :boards, before_add: :validate_board_limit, dependent: :destroy + has_many :boards, before_add: :validate_board_limit # Project services - has_one :campfire_service, dependent: :destroy - has_one :drone_ci_service, dependent: :destroy - has_one :emails_on_push_service, dependent: :destroy - has_one :pipelines_email_service, dependent: :destroy - has_one :irker_service, dependent: :destroy - has_one :pivotaltracker_service, dependent: :destroy - has_one :hipchat_service, dependent: :destroy - has_one :flowdock_service, dependent: :destroy - has_one :assembla_service, dependent: :destroy - has_one :asana_service, dependent: :destroy - has_one :gemnasium_service, dependent: :destroy - has_one :mattermost_slash_commands_service, dependent: :destroy - has_one :mattermost_service, dependent: :destroy - has_one :slack_slash_commands_service, dependent: :destroy - has_one :slack_service, dependent: :destroy - has_one :buildkite_service, dependent: :destroy - has_one :bamboo_service, dependent: :destroy - has_one :teamcity_service, dependent: :destroy - has_one :pushover_service, dependent: :destroy - has_one :jira_service, dependent: :destroy - has_one :redmine_service, dependent: :destroy - has_one :custom_issue_tracker_service, dependent: :destroy - has_one :bugzilla_service, dependent: :destroy - has_one :gitlab_issue_tracker_service, dependent: :destroy, inverse_of: :project - has_one :external_wiki_service, dependent: :destroy - has_one :kubernetes_service, dependent: :destroy, inverse_of: :project - has_one :prometheus_service, dependent: :destroy, inverse_of: :project - has_one :mock_ci_service, dependent: :destroy - has_one :mock_deployment_service, dependent: :destroy - has_one :mock_monitoring_service, dependent: :destroy - has_one :microsoft_teams_service, dependent: :destroy - - has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" + has_one :campfire_service + has_one :drone_ci_service + has_one :emails_on_push_service + has_one :pipelines_email_service + has_one :irker_service + has_one :pivotaltracker_service + has_one :hipchat_service + has_one :flowdock_service + has_one :assembla_service + has_one :asana_service + has_one :gemnasium_service + has_one :mattermost_slash_commands_service + has_one :mattermost_service + has_one :slack_slash_commands_service + has_one :slack_service + has_one :buildkite_service + has_one :bamboo_service + has_one :teamcity_service + has_one :pushover_service + has_one :jira_service + has_one :redmine_service + has_one :custom_issue_tracker_service + has_one :bugzilla_service + has_one :gitlab_issue_tracker_service, inverse_of: :project + has_one :external_wiki_service + has_one :kubernetes_service, inverse_of: :project + has_one :prometheus_service, inverse_of: :project + has_one :mock_ci_service + has_one :mock_deployment_service + has_one :mock_monitoring_service + has_one :microsoft_teams_service + + has_one :forked_project_link, foreign_key: "forked_to_project_id" has_one :forked_from_project, through: :forked_project_link has_many :forked_project_links, foreign_key: "forked_from_project_id" has_many :forks, through: :forked_project_links, source: :forked_to_project # Merge Requests for target project should be removed with it - has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id' - has_many :issues, dependent: :destroy - has_many :labels, dependent: :destroy, class_name: 'ProjectLabel' - has_many :services, dependent: :destroy - has_many :events, dependent: :destroy - has_many :milestones, dependent: :destroy - has_many :notes, dependent: :destroy - has_many :snippets, dependent: :destroy, class_name: 'ProjectSnippet' - has_many :hooks, dependent: :destroy, class_name: 'ProjectHook' - has_many :protected_branches, dependent: :destroy - has_many :protected_tags, dependent: :destroy + has_many :merge_requests, foreign_key: 'target_project_id' + has_many :issues + has_many :labels, class_name: 'ProjectLabel' + has_many :services + has_many :events + has_many :milestones + has_many :notes + has_many :snippets, class_name: 'ProjectSnippet' + has_many :hooks, class_name: 'ProjectHook' + has_many :protected_branches + has_many :protected_tags has_many :project_authorizations has_many :authorized_users, through: :project_authorizations, source: :user, class_name: 'User' - has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source + has_many :project_members, -> { where(requested_at: nil) }, + as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent + alias_method :members, :project_members has_many :users, through: :project_members - has_many :requesters, -> { where.not(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'ProjectMember' + has_many :requesters, -> { where.not(requested_at: nil) }, + as: :source, class_name: 'ProjectMember', dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent - has_many :deploy_keys_projects, dependent: :destroy + has_many :deploy_keys_projects has_many :deploy_keys, through: :deploy_keys_projects - has_many :users_star_projects, dependent: :destroy + has_many :users_star_projects has_many :starrers, through: :users_star_projects, source: :user - has_many :releases, dependent: :destroy - has_many :lfs_objects_projects, dependent: :destroy + has_many :releases + has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :lfs_objects, through: :lfs_objects_projects - has_many :project_group_links, dependent: :destroy + has_many :project_group_links has_many :invited_groups, through: :project_group_links, source: :group - has_many :pages_domains, dependent: :destroy - has_many :todos, dependent: :destroy - has_many :notification_settings, dependent: :destroy, as: :source - - has_one :import_data, dependent: :delete, class_name: 'ProjectImportData' - has_one :project_feature, dependent: :destroy - has_one :statistics, class_name: 'ProjectStatistics', dependent: :delete - has_many :container_repositories, dependent: :destroy - - has_many :commit_statuses, dependent: :destroy - has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline' - has_many :builds, class_name: 'Ci::Build' # the builds are created from the commit_statuses - has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject' + has_many :pages_domains + has_many :todos + has_many :notification_settings, as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent + + has_one :import_data, class_name: 'ProjectImportData' + has_one :project_feature + has_one :statistics, class_name: 'ProjectStatistics' + + # Container repositories need to remove data from the container registry, + # which is not managed by the DB. Hence we're still using dependent: :destroy + # here. + has_many :container_repositories, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + + has_many :commit_statuses + has_many :pipelines, class_name: 'Ci::Pipeline' + + # Ci::Build objects store data on the file system such as artifact files and + # build traces. Currently there's no efficient way of removing this data in + # bulk that doesn't involve loading the rows into memory. As a result we're + # still using `dependent: :destroy` here. + has_many :builds, class_name: 'Ci::Build', dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :runner_projects, class_name: 'Ci::RunnerProject' has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner' has_many :variables, class_name: 'Ci::Variable' - has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger' - has_many :environments, dependent: :destroy - has_many :deployments, dependent: :destroy - has_many :pipeline_schedules, dependent: :destroy, class_name: 'Ci::PipelineSchedule' + has_many :triggers, class_name: 'Ci::Trigger' + has_many :environments + has_many :deployments + has_many :pipeline_schedules, class_name: 'Ci::PipelineSchedule' has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner' @@ -224,7 +237,7 @@ class Project < ActiveRecord::Base before_save :ensure_runners_token mount_uploader :avatar, AvatarUploader - has_many :uploads, as: :model, dependent: :destroy + has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent # Scopes scope :pending_delete, -> { where(pending_delete: true) } @@ -1240,7 +1253,13 @@ class Project < ActiveRecord::Base File.join(pages_path, 'public') end + def remove_private_deploy_keys + deploy_keys.where(public: false).delete_all + end + def remove_pages + ::Projects::UpdatePagesConfigurationService.new(self).execute + # 1. We rename pages to temporary directory # 2. We wait 5 minutes, due to NFS caching # 3. We asynchronously remove pages with force diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index e3cafd4d1c62c8f8cdcdd6e64dd177cd889e7b5e..377304743248be98f43a7a3742e49e07f135bc99 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -10,7 +10,7 @@ class ProjectImportData < ActiveRecord::Base insecure_mode: true, algorithm: 'aes-256-cbc' - serialize :data, JSON # rubocop:disable Cop/ActiverecordSerialize + serialize :data, JSON # rubocop:disable Cop/ActiveRecordSerialize validates :project, presence: true diff --git a/app/models/project_services/slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb index 4592cb747a0bbf5df6d257aaee28476ddaa01344..eb4da68bb7e0be5a880a2088199d57bb00be4947 100644 --- a/app/models/project_services/slash_commands_service.rb +++ b/app/models/project_services/slash_commands_service.rb @@ -5,7 +5,7 @@ class SlashCommandsService < Service prop_accessor :token - has_many :chat_names, foreign_key: :service_id, dependent: :destroy + has_many :chat_names, foreign_key: :service_id, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent def valid_token?(token) self.respond_to?(:token) && diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb index edde7bedbabef62ff7b26502b16efc0b286ee2ac..298569cb7a6b99981d1f77ec10911342143ed4db 100644 --- a/app/models/sent_notification.rb +++ b/app/models/sent_notification.rb @@ -1,5 +1,5 @@ class SentNotification < ActiveRecord::Base - serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize + serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize belongs_to :project belongs_to :noteable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations diff --git a/app/models/service.rb b/app/models/service.rb index 6a0b0a5c522f975c8c29037402640792422301ee..60fa81b18c4b06d1d02761a8436ca352bda49630 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -2,7 +2,7 @@ # and implement a set of methods class Service < ActiveRecord::Base include Sortable - serialize :properties, JSON # rubocop:disable Cop/ActiverecordSerialize + serialize :properties, JSON # rubocop:disable Cop/ActiveRecordSerialize default_value_for :active, false default_value_for :push_events, true diff --git a/app/models/snippet.rb b/app/models/snippet.rb index b3aa7bb986e51e4c2a033b8d57b67ca7a61d262d..09d5ff4661804665475c2eb36148cf57a7202f15 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -30,7 +30,7 @@ class Snippet < ActiveRecord::Base belongs_to :author, class_name: 'User' belongs_to :project - has_many :notes, as: :noteable, dependent: :destroy + has_many :notes, as: :noteable, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent delegate :name, :email, to: :author, prefix: true, allow_nil: true diff --git a/app/models/user.rb b/app/models/user.rb index 0febae84873bf0e73fc32a2c6a7066c198df8f3b..4411a06d429b45b9451616313646c6f2d5718765 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -41,7 +41,7 @@ class User < ActiveRecord::Base otp_secret_encryption_key: Gitlab::Application.secrets.otp_key_base devise :two_factor_backupable, otp_number_of_backup_codes: 10 - serialize :otp_backup_codes, JSON # rubocop:disable Cop/ActiverecordSerialize + serialize :otp_backup_codes, JSON # rubocop:disable Cop/ActiveRecordSerialize devise :lockable, :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :confirmable, :registerable @@ -67,24 +67,24 @@ class User < ActiveRecord::Base # # Namespace for personal projects - has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id, autosave: true + has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id, autosave: true # rubocop:disable Cop/ActiveRecordDependent # Profile has_many :keys, -> do type = Key.arel_table[:type] where(type.not_eq('DeployKey').or(type.eq(nil))) - end, dependent: :destroy - has_many :deploy_keys, -> { where(type: 'DeployKey') }, dependent: :destroy + end, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :deploy_keys, -> { where(type: 'DeployKey') }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent - has_many :emails, dependent: :destroy - has_many :personal_access_tokens, dependent: :destroy - has_many :identities, dependent: :destroy, autosave: true - has_many :u2f_registrations, dependent: :destroy - has_many :chat_names, dependent: :destroy + has_many :emails, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :personal_access_tokens, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :identities, dependent: :destroy, autosave: true # rubocop:disable Cop/ActiveRecordDependent + has_many :u2f_registrations, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :chat_names, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent # Groups - has_many :members, dependent: :destroy - has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, source: 'GroupMember' + has_many :members, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, source: 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent has_many :groups, through: :group_members has_many :owned_groups, -> { where members: { access_level: Gitlab::Access::OWNER } }, through: :group_members, source: :group has_many :masters_groups, -> { where members: { access_level: Gitlab::Access::MASTER } }, through: :group_members, source: :group @@ -92,35 +92,35 @@ class User < ActiveRecord::Base # Projects has_many :groups_projects, through: :groups, source: :projects has_many :personal_projects, through: :namespace, source: :projects - has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy + has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :projects, through: :project_members has_many :created_projects, foreign_key: :creator_id, class_name: 'Project' - has_many :users_star_projects, dependent: :destroy + has_many :users_star_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :starred_projects, through: :users_star_projects, source: :project has_many :project_authorizations has_many :authorized_projects, through: :project_authorizations, source: :project - has_many :snippets, dependent: :destroy, foreign_key: :author_id - has_many :notes, dependent: :destroy, foreign_key: :author_id - has_many :issues, dependent: :destroy, foreign_key: :author_id - has_many :merge_requests, dependent: :destroy, foreign_key: :author_id - has_many :events, dependent: :destroy, foreign_key: :author_id - has_many :subscriptions, dependent: :destroy + has_many :snippets, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent + has_many :notes, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent + has_many :issues, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent + has_many :merge_requests, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent + has_many :events, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent + has_many :subscriptions, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id, class_name: "Event" - has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy - has_one :abuse_report, dependent: :destroy, foreign_key: :user_id - has_many :reported_abuse_reports, dependent: :destroy, foreign_key: :reporter_id, class_name: "AbuseReport" - has_many :spam_logs, dependent: :destroy - has_many :builds, dependent: :nullify, class_name: 'Ci::Build' - has_many :pipelines, dependent: :nullify, class_name: 'Ci::Pipeline' - has_many :todos, dependent: :destroy - has_many :notification_settings, dependent: :destroy - has_many :award_emoji, dependent: :destroy - has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :owner_id + has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_one :abuse_report, dependent: :destroy, foreign_key: :user_id # rubocop:disable Cop/ActiveRecordDependent + has_many :reported_abuse_reports, dependent: :destroy, foreign_key: :reporter_id, class_name: "AbuseReport" # rubocop:disable Cop/ActiveRecordDependent + has_many :spam_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :builds, dependent: :nullify, class_name: 'Ci::Build' # rubocop:disable Cop/ActiveRecordDependent + has_many :pipelines, dependent: :nullify, class_name: 'Ci::Pipeline' # rubocop:disable Cop/ActiveRecordDependent + has_many :todos, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :notification_settings, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :award_emoji, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :owner_id # rubocop:disable Cop/ActiveRecordDependent has_many :issue_assignees has_many :assigned_issues, class_name: "Issue", through: :issue_assignees, source: :issue - has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" + has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent # # Validations @@ -211,7 +211,7 @@ class User < ActiveRecord::Base end mount_uploader :avatar, AvatarUploader - has_many :uploads, as: :model, dependent: :destroy + has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent # Scopes scope :admins, -> { where(admin: true) } diff --git a/changelogs/unreleased/foreign-keys-for-project-model.yml b/changelogs/unreleased/foreign-keys-for-project-model.yml new file mode 100644 index 0000000000000000000000000000000000000000..3648b1c3735f64d170f260b8108892ed5e239f7a --- /dev/null +++ b/changelogs/unreleased/foreign-keys-for-project-model.yml @@ -0,0 +1,4 @@ +--- +title: Speed up project removals by adding foreign keys with cascading deletes to various tables +merge_request: +author: diff --git a/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb b/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb new file mode 100644 index 0000000000000000000000000000000000000000..3eaafac321df570bc31ece6634023080d43cb12e --- /dev/null +++ b/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb @@ -0,0 +1,187 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class ProjectForeignKeysWithCascadingDeletes < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + CONCURRENCY = 4 + + disable_ddl_transaction! + + # The tables/columns for which to remove orphans and add foreign keys. Order + # matters as some tables/columns should be processed before others. + TABLES = [ + [:boards, :projects, :project_id], + [:lists, :labels, :label_id], + [:lists, :boards, :board_id], + [:services, :projects, :project_id], + [:forked_project_links, :projects, :forked_to_project_id], + [:merge_requests, :projects, :target_project_id], + [:labels, :projects, :project_id], + [:issues, :projects, :project_id], + [:events, :projects, :project_id], + [:milestones, :projects, :project_id], + [:notes, :projects, :project_id], + [:snippets, :projects, :project_id], + [:web_hooks, :projects, :project_id], + [:protected_branch_merge_access_levels, :protected_branches, :protected_branch_id], + [:protected_branch_push_access_levels, :protected_branches, :protected_branch_id], + [:protected_branches, :projects, :project_id], + [:protected_tags, :projects, :project_id], + [:deploy_keys_projects, :projects, :project_id], + [:users_star_projects, :projects, :project_id], + [:releases, :projects, :project_id], + [:project_group_links, :projects, :project_id], + [:pages_domains, :projects, :project_id], + [:todos, :projects, :project_id], + [:project_import_data, :projects, :project_id], + [:project_features, :projects, :project_id], + [:ci_builds, :projects, :project_id], + [:ci_pipelines, :projects, :project_id], + [:ci_runner_projects, :projects, :project_id], + [:ci_triggers, :projects, :project_id], + [:environments, :projects, :project_id], + [:deployments, :projects, :project_id] + ] + + def up + # These existing foreign keys don't have an "ON DELETE CASCADE" clause. + remove_foreign_key_without_error(:boards, :project_id) + remove_foreign_key_without_error(:lists, :label_id) + remove_foreign_key_without_error(:lists, :board_id) + remove_foreign_key_without_error(:protected_branch_merge_access_levels, + :protected_branch_id) + + remove_foreign_key_without_error(:protected_branch_push_access_levels, + :protected_branch_id) + + remove_orphaned_rows + add_foreign_keys + + # These columns are not indexed yet, meaning a cascading delete would take + # forever. + add_concurrent_index(:project_group_links, :project_id) + add_concurrent_index(:pages_domains, :project_id) + end + + def down + TABLES.each do |(source, _, column)| + remove_foreign_key_without_error(source, column) + end + + add_concurrent_foreign_key(:boards, :projects, column: :project_id) + add_concurrent_foreign_key(:lists, :labels, column: :label_id) + add_concurrent_foreign_key(:lists, :boards, column: :board_id) + + add_concurrent_foreign_key(:protected_branch_merge_access_levels, + :protected_branches, + column: :protected_branch_id) + + add_concurrent_foreign_key(:protected_branch_push_access_levels, + :protected_branches, + column: :protected_branch_id) + + remove_index_without_error(:project_group_links, :project_id) + remove_index_without_error(:pages_domains, :project_id) + end + + def add_foreign_keys + TABLES.each do |(source, target, column)| + add_concurrent_foreign_key(source, target, column: column) + end + end + + # Removes orphans from various tables concurrently. + def remove_orphaned_rows + Gitlab::Database.with_connection_pool(CONCURRENCY) do |pool| + queues = queues_for_rows(TABLES) + + threads = queues.map do |queue| + Thread.new do + pool.with_connection do |connection| + Thread.current[:foreign_key_connection] = connection + + # Disables statement timeouts for the current connection. This is + # necessary as removing of orphaned data might otherwise exceed the + # statement timeout. + disable_statement_timeout + + remove_orphans(*queue.pop) until queue.empty? + + steal_from_queues(queues - [queue]) + end + end + end + + threads.each(&:join) + end + end + + def steal_from_queues(queues) + loop do + stolen = false + + queues.each do |queue| + # Stealing is racy so it's possible a pop might be called on an + # already-empty queue. + begin + remove_orphans(*queue.pop(true)) + stolen = true + rescue ThreadError + end + end + + break unless stolen + end + end + + def remove_orphans(source, target, column) + quoted_source = quote_table_name(source) + quoted_target = quote_table_name(target) + quoted_column = quote_column_name(column) + + execute <<-EOF.strip_heredoc + DELETE FROM #{quoted_source} + WHERE NOT EXISTS ( + SELECT true + FROM #{quoted_target} + WHERE #{quoted_target}.id = #{quoted_source}.#{quoted_column} + ) + AND #{quoted_source}.#{quoted_column} IS NOT NULL + EOF + end + + def remove_foreign_key_without_error(table, column) + remove_foreign_key(table, column: column) + rescue ArgumentError + end + + def remove_index_without_error(table, column) + remove_concurrent_index(table, column) + rescue ArgumentError + end + + def connection + # Rails memoizes connection objects, but this causes them to be shared + # amongst threads; we don't want that. + Thread.current[:foreign_key_connection] || ActiveRecord::Base.connection + end + + def queues_for_rows(rows) + queues = Array.new(CONCURRENCY) { Queue.new } + slice_size = rows.length / CONCURRENCY + + # Divide all the tuples as evenly as possible amongst the queues. + rows.each_slice(slice_size).each_with_index do |slice, index| + bucket = index % CONCURRENCY + + slice.each do |row| + queues[bucket] << row + end + end + + queues + end +end diff --git a/db/migrate/20170622130029_correct_protected_branches_foreign_keys.rb b/db/migrate/20170622130029_correct_protected_branches_foreign_keys.rb new file mode 100644 index 0000000000000000000000000000000000000000..46497775527279169a99037cf53b2e281c44721b --- /dev/null +++ b/db/migrate/20170622130029_correct_protected_branches_foreign_keys.rb @@ -0,0 +1,40 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class CorrectProtectedBranchesForeignKeys < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + def up + remove_foreign_key_without_error(:protected_branch_push_access_levels, + column: :protected_branch_id) + + execute <<-EOF + DELETE FROM protected_branch_push_access_levels + WHERE NOT EXISTS ( + SELECT true + FROM protected_branches + WHERE protected_branch_push_access_levels.protected_branch_id = protected_branches.id + ) + AND protected_branch_id IS NOT NULL + EOF + + add_concurrent_foreign_key(:protected_branch_push_access_levels, + :protected_branches, + column: :protected_branch_id) + end + + def down + # Previously there was a foreign key without a CASCADING DELETE, so we'll + # just leave the foreign key in place. + end + + def remove_foreign_key_without_error(*args) + remove_foreign_key(*args) + rescue ArgumentError + end +end diff --git a/db/migrate/20170622132212_add_foreign_key_for_merge_request_diffs.rb b/db/migrate/20170622132212_add_foreign_key_for_merge_request_diffs.rb new file mode 100644 index 0000000000000000000000000000000000000000..9f524fac8a7b0c424141471afcf9ae2aa63e3007 --- /dev/null +++ b/db/migrate/20170622132212_add_foreign_key_for_merge_request_diffs.rb @@ -0,0 +1,30 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddForeignKeyForMergeRequestDiffs < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + def up + execute <<-EOF + DELETE FROM merge_request_diffs + WHERE NOT EXISTS ( + SELECT true + FROM merge_requests + WHERE merge_requests.id = merge_request_diffs.merge_request_id + ) + EOF + + add_concurrent_foreign_key(:merge_request_diffs, + :merge_requests, + column: :merge_request_id) + end + + def down + remove_foreign_key(:merge_request_diffs, column: :merge_request_id) + end +end diff --git a/db/schema.rb b/db/schema.rb index 40f30a10a016a15e1fcecd38761af0756cba73b3..f12fdf903c562aaf5402892859652ab50224c243 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -971,6 +971,7 @@ ActiveRecord::Schema.define(version: 20170703102400) do end add_index "pages_domains", ["domain"], name: "index_pages_domains_on_domain", unique: true, using: :btree + add_index "pages_domains", ["project_id"], name: "index_pages_domains_on_project_id", using: :btree create_table "personal_access_tokens", force: :cascade do |t| t.integer "user_id", null: false @@ -1020,6 +1021,7 @@ ActiveRecord::Schema.define(version: 20170703102400) do end add_index "project_group_links", ["group_id"], name: "index_project_group_links_on_group_id", using: :btree + add_index "project_group_links", ["project_id"], name: "index_project_group_links_on_project_id", using: :btree create_table "project_import_data", force: :cascade do |t| t.integer "project_id" @@ -1528,48 +1530,75 @@ ActiveRecord::Schema.define(version: 20170703102400) do add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree add_index "web_hooks", ["type"], name: "index_web_hooks_on_type", using: :btree - add_foreign_key "boards", "projects" + add_foreign_key "boards", "projects", name: "fk_f15266b5f9", on_delete: :cascade add_foreign_key "chat_teams", "namespaces", on_delete: :cascade add_foreign_key "ci_builds", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_a2141b1522", on_delete: :nullify add_foreign_key "ci_builds", "ci_stages", column: "stage_id", name: "fk_3a9eaa254d", on_delete: :cascade + add_foreign_key "ci_builds", "projects", name: "fk_befce0568a", on_delete: :cascade add_foreign_key "ci_pipeline_schedules", "projects", name: "fk_8ead60fcc4", on_delete: :cascade add_foreign_key "ci_pipeline_schedules", "users", column: "owner_id", name: "fk_9ea99f58d2", on_delete: :nullify add_foreign_key "ci_pipelines", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_3d34ab2e06", on_delete: :nullify add_foreign_key "ci_pipelines", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_262d4c2d19", on_delete: :nullify + add_foreign_key "ci_pipelines", "projects", name: "fk_86635dbd80", on_delete: :cascade + add_foreign_key "ci_runner_projects", "projects", name: "fk_4478a6f1e4", on_delete: :cascade add_foreign_key "ci_stages", "ci_pipelines", column: "pipeline_id", name: "fk_fb57e6cc56", on_delete: :cascade add_foreign_key "ci_stages", "projects", name: "fk_2360681d1d", on_delete: :cascade add_foreign_key "ci_trigger_requests", "ci_triggers", column: "trigger_id", name: "fk_b8ec8b7245", on_delete: :cascade + add_foreign_key "ci_triggers", "projects", name: "fk_e3e63f966e", on_delete: :cascade add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade add_foreign_key "ci_variables", "projects", name: "fk_ada5eb64b3", on_delete: :cascade add_foreign_key "container_repositories", "projects" + add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade + add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade + add_foreign_key "environments", "projects", name: "fk_d1c8c1da6a", on_delete: :cascade + add_foreign_key "events", "projects", name: "fk_0434b48643", on_delete: :cascade + add_foreign_key "forked_project_links", "projects", column: "forked_to_project_id", name: "fk_434510edb0", on_delete: :cascade add_foreign_key "issue_assignees", "issues", name: "fk_b7d881734a", on_delete: :cascade add_foreign_key "issue_assignees", "users", name: "fk_5e0c8d9154", on_delete: :cascade add_foreign_key "issue_metrics", "issues", on_delete: :cascade + add_foreign_key "issues", "projects", name: "fk_899c8f3231", on_delete: :cascade add_foreign_key "label_priorities", "labels", on_delete: :cascade add_foreign_key "label_priorities", "projects", on_delete: :cascade add_foreign_key "labels", "namespaces", column: "group_id", on_delete: :cascade - add_foreign_key "lists", "boards" - add_foreign_key "lists", "labels" + add_foreign_key "labels", "projects", name: "fk_7de4989a69", on_delete: :cascade + add_foreign_key "lists", "boards", name: "fk_0d3f677137", on_delete: :cascade + add_foreign_key "lists", "labels", name: "fk_7a5553d60f", on_delete: :cascade add_foreign_key "merge_request_diff_files", "merge_request_diffs", on_delete: :cascade + add_foreign_key "merge_request_diffs", "merge_requests", name: "fk_8483f3258f", on_delete: :cascade add_foreign_key "merge_request_metrics", "ci_pipelines", column: "pipeline_id", on_delete: :cascade add_foreign_key "merge_request_metrics", "merge_requests", on_delete: :cascade + add_foreign_key "merge_requests", "projects", column: "target_project_id", name: "fk_a6963e8447", on_delete: :cascade add_foreign_key "merge_requests_closing_issues", "issues", on_delete: :cascade add_foreign_key "merge_requests_closing_issues", "merge_requests", on_delete: :cascade + add_foreign_key "milestones", "projects", name: "fk_9bd0a0c791", on_delete: :cascade + add_foreign_key "notes", "projects", name: "fk_99e097b079", on_delete: :cascade add_foreign_key "oauth_openid_requests", "oauth_access_grants", column: "access_grant_id", name: "fk_oauth_openid_requests_oauth_access_grants_access_grant_id" + add_foreign_key "pages_domains", "projects", name: "fk_ea2f6dfc6f", on_delete: :cascade add_foreign_key "personal_access_tokens", "users" add_foreign_key "project_authorizations", "projects", on_delete: :cascade add_foreign_key "project_authorizations", "users", on_delete: :cascade + add_foreign_key "project_features", "projects", name: "fk_18513d9b92", on_delete: :cascade + add_foreign_key "project_group_links", "projects", name: "fk_daa8cee94c", on_delete: :cascade + add_foreign_key "project_import_data", "projects", name: "fk_ffb9ee3a10", on_delete: :cascade add_foreign_key "project_statistics", "projects", on_delete: :cascade - add_foreign_key "protected_branch_merge_access_levels", "protected_branches" - add_foreign_key "protected_branch_push_access_levels", "protected_branches" + add_foreign_key "protected_branch_merge_access_levels", "protected_branches", name: "fk_8a3072ccb3", on_delete: :cascade + add_foreign_key "protected_branch_push_access_levels", "protected_branches", name: "fk_9ffc86a3d9", on_delete: :cascade + add_foreign_key "protected_branches", "projects", name: "fk_7a9c6d93e7", on_delete: :cascade add_foreign_key "protected_tag_create_access_levels", "namespaces", column: "group_id" add_foreign_key "protected_tag_create_access_levels", "protected_tags" add_foreign_key "protected_tag_create_access_levels", "users" + add_foreign_key "protected_tags", "projects", name: "fk_8e4af87648", on_delete: :cascade + add_foreign_key "releases", "projects", name: "fk_47fe2a0596", on_delete: :cascade + add_foreign_key "services", "projects", name: "fk_71cce407f9", on_delete: :cascade + add_foreign_key "snippets", "projects", name: "fk_be41fd4bb7", on_delete: :cascade add_foreign_key "subscriptions", "projects", on_delete: :cascade add_foreign_key "system_note_metadata", "notes", name: "fk_d83a918cb1", on_delete: :cascade add_foreign_key "timelogs", "issues", name: "fk_timelogs_issues_issue_id", on_delete: :cascade add_foreign_key "timelogs", "merge_requests", name: "fk_timelogs_merge_requests_merge_request_id", on_delete: :cascade + add_foreign_key "todos", "projects", name: "fk_45054f9c45", on_delete: :cascade add_foreign_key "trending_projects", "projects", on_delete: :cascade add_foreign_key "u2f_registrations", "users" + add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade add_foreign_key "web_hook_logs", "web_hooks", on_delete: :cascade + add_foreign_key "web_hooks", "projects", name: "fk_0c8ca6d9d1", on_delete: :cascade end diff --git a/rubocop/cop/active_record_dependent.rb b/rubocop/cop/active_record_dependent.rb new file mode 100644 index 0000000000000000000000000000000000000000..8d15f15088554950a062959b7062cce45f305de0 --- /dev/null +++ b/rubocop/cop/active_record_dependent.rb @@ -0,0 +1,26 @@ +require_relative '../model_helpers' + +module RuboCop + module Cop + # Cop that prevents the use of `dependent: ...` in ActiveRecord models. + class ActiveRecordDependent < RuboCop::Cop::Cop + include ModelHelpers + + MSG = 'Do not use `dependent: to remove associated data, ' \ + 'use foreign keys with cascading deletes instead'.freeze + + METHOD_NAMES = [:has_many, :has_one, :belongs_to].freeze + + def on_send(node) + return unless in_model?(node) + return unless METHOD_NAMES.include?(node.children[1]) + + node.children.last.each_node(:pair) do |pair| + key_name = pair.children[0].children[0] + + add_offense(pair, :expression) if key_name == :dependent + end + end + end + end +end diff --git a/rubocop/cop/activerecord_serialize.rb b/rubocop/cop/active_record_serialize.rb similarity index 89% rename from rubocop/cop/activerecord_serialize.rb rename to rubocop/cop/active_record_serialize.rb index 9bdcc3b4c34de2b88d44f5ecc6e07eacec7de653..204caf37f8b84af2928c15d09fae990d8914f763 100644 --- a/rubocop/cop/activerecord_serialize.rb +++ b/rubocop/cop/active_record_serialize.rb @@ -3,7 +3,7 @@ require_relative '../model_helpers' module RuboCop module Cop # Cop that prevents the use of `serialize` in ActiveRecord models. - class ActiverecordSerialize < RuboCop::Cop::Cop + class ActiveRecordSerialize < RuboCop::Cop::Cop include ModelHelpers MSG = 'Do not store serialized data in the database, use separate columns and/or tables instead'.freeze diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb index 69b4b29507cc8c392f7dd155861f43205dd0c73c..1e70314598a2f780c0e782a19218f6e058335fa8 100644 --- a/rubocop/rubocop.rb +++ b/rubocop/rubocop.rb @@ -1,9 +1,10 @@ require_relative 'cop/custom_error_class' require_relative 'cop/gem_fetcher' -require_relative 'cop/activerecord_serialize' +require_relative 'cop/active_record_serialize' require_relative 'cop/redirect_with_status' require_relative 'cop/polymorphic_associations' require_relative 'cop/project_path_helper' +require_relative 'cop/active_record_dependent' require_relative 'cop/migration/add_column' require_relative 'cop/migration/add_column_with_default_to_large_table' require_relative 'cop/migration/add_concurrent_foreign_key' diff --git a/spec/factories/ci/runner_projects.rb b/spec/factories/ci/runner_projects.rb index 6712dd5d82ef805261e9966526ee9f0b3d882643..33a17cf7ed5747505ce9c96dfbadc7e65741d86e 100644 --- a/spec/factories/ci/runner_projects.rb +++ b/spec/factories/ci/runner_projects.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :ci_runner_project, class: Ci::RunnerProject do - runner_id 1 - project_id 1 + runner factory: :ci_runner + project factory: :empty_project end end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index ac9303370abc0bd7dabaee30f5845a4895959ee8..505039c9d88fd825cda1c74ba721241434313130 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -155,7 +155,7 @@ describe Issuable do end describe "#sort" do - let(:project) { build_stubbed(:empty_project) } + let(:project) { create(:empty_project) } context "by milestone due date" do # Correct order is: diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb index 5c13cf584f9a58c18506f9ecafe8c2b58fd11b6d..38fbdd2536a46cbcdab1ca82454acb6a5bb5af51 100644 --- a/spec/models/forked_project_link_spec.rb +++ b/spec/models/forked_project_link_spec.rb @@ -42,7 +42,7 @@ describe ForkedProjectLink, "add link on fork" do describe '#forked?' do let(:project_to) { create(:project, forked_project_link: forked_project_link) } - let(:forked_project_link) { build(:forked_project_link) } + let(:forked_project_link) { create(:forked_project_link) } before do forked_project_link.forked_from_project = project_from @@ -59,9 +59,9 @@ describe ForkedProjectLink, "add link on fork" do end it "project_to.destroy destroys fork_link" do - expect(forked_project_link).to receive(:destroy) - project_to.destroy + + expect(ForkedProjectLink.exists?(id: forked_project_link.id)).to eq(false) end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 587d4b83cb4694b5e7da4dc9061fee45f63709a7..d91f1f1a11cb465c7a34753dbeae4935e82865ac 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -10,7 +10,7 @@ describe MergeRequest, models: true do it { is_expected.to belong_to(:source_project).class_name('Project') } it { is_expected.to belong_to(:merge_user).class_name("User") } it { is_expected.to belong_to(:assignee) } - it { is_expected.to have_many(:merge_request_diffs).dependent(:destroy) } + it { is_expected.to have_many(:merge_request_diffs) } end describe 'modules' do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index f50b4aea41120ff3a6fb63452e68a363fb4e5f20..75fa2d2eb4658d5b791b4c02dd049dd2f1a8f371 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -7,50 +7,50 @@ describe Project, models: true do it { is_expected.to belong_to(:creator).class_name('User') } it { is_expected.to have_many(:users) } it { is_expected.to have_many(:services) } - it { is_expected.to have_many(:events).dependent(:destroy) } - it { is_expected.to have_many(:merge_requests).dependent(:destroy) } - it { is_expected.to have_many(:issues).dependent(:destroy) } - it { is_expected.to have_many(:milestones).dependent(:destroy) } - it { is_expected.to have_many(:project_members).dependent(:destroy) } + it { is_expected.to have_many(:events) } + it { is_expected.to have_many(:merge_requests) } + it { is_expected.to have_many(:issues) } + it { is_expected.to have_many(:milestones) } + it { is_expected.to have_many(:project_members).dependent(:delete_all) } it { is_expected.to have_many(:users).through(:project_members) } - it { is_expected.to have_many(:requesters).dependent(:destroy) } - it { is_expected.to have_many(:notes).dependent(:destroy) } - it { is_expected.to have_many(:snippets).class_name('ProjectSnippet').dependent(:destroy) } - it { is_expected.to have_many(:deploy_keys_projects).dependent(:destroy) } + it { is_expected.to have_many(:requesters).dependent(:delete_all) } + it { is_expected.to have_many(:notes) } + it { is_expected.to have_many(:snippets).class_name('ProjectSnippet') } + it { is_expected.to have_many(:deploy_keys_projects) } it { is_expected.to have_many(:deploy_keys) } - it { is_expected.to have_many(:hooks).dependent(:destroy) } - it { is_expected.to have_many(:protected_branches).dependent(:destroy) } - it { is_expected.to have_one(:forked_project_link).dependent(:destroy) } - it { is_expected.to have_one(:slack_service).dependent(:destroy) } - it { is_expected.to have_one(:microsoft_teams_service).dependent(:destroy) } - it { is_expected.to have_one(:mattermost_service).dependent(:destroy) } - it { is_expected.to have_one(:pushover_service).dependent(:destroy) } - it { is_expected.to have_one(:asana_service).dependent(:destroy) } - it { is_expected.to have_many(:boards).dependent(:destroy) } - it { is_expected.to have_one(:campfire_service).dependent(:destroy) } - it { is_expected.to have_one(:drone_ci_service).dependent(:destroy) } - it { is_expected.to have_one(:emails_on_push_service).dependent(:destroy) } - it { is_expected.to have_one(:pipelines_email_service).dependent(:destroy) } - it { is_expected.to have_one(:irker_service).dependent(:destroy) } - it { is_expected.to have_one(:pivotaltracker_service).dependent(:destroy) } - it { is_expected.to have_one(:hipchat_service).dependent(:destroy) } - it { is_expected.to have_one(:flowdock_service).dependent(:destroy) } - it { is_expected.to have_one(:assembla_service).dependent(:destroy) } - it { is_expected.to have_one(:slack_slash_commands_service).dependent(:destroy) } - it { is_expected.to have_one(:mattermost_slash_commands_service).dependent(:destroy) } - it { is_expected.to have_one(:gemnasium_service).dependent(:destroy) } - it { is_expected.to have_one(:buildkite_service).dependent(:destroy) } - it { is_expected.to have_one(:bamboo_service).dependent(:destroy) } - it { is_expected.to have_one(:teamcity_service).dependent(:destroy) } - it { is_expected.to have_one(:jira_service).dependent(:destroy) } - it { is_expected.to have_one(:redmine_service).dependent(:destroy) } - it { is_expected.to have_one(:custom_issue_tracker_service).dependent(:destroy) } - it { is_expected.to have_one(:bugzilla_service).dependent(:destroy) } - it { is_expected.to have_one(:gitlab_issue_tracker_service).dependent(:destroy) } - it { is_expected.to have_one(:external_wiki_service).dependent(:destroy) } - it { is_expected.to have_one(:project_feature).dependent(:destroy) } - it { is_expected.to have_one(:statistics).class_name('ProjectStatistics').dependent(:delete) } - it { is_expected.to have_one(:import_data).class_name('ProjectImportData').dependent(:delete) } + it { is_expected.to have_many(:hooks) } + it { is_expected.to have_many(:protected_branches) } + it { is_expected.to have_one(:forked_project_link) } + it { is_expected.to have_one(:slack_service) } + it { is_expected.to have_one(:microsoft_teams_service) } + it { is_expected.to have_one(:mattermost_service) } + it { is_expected.to have_one(:pushover_service) } + it { is_expected.to have_one(:asana_service) } + it { is_expected.to have_many(:boards) } + it { is_expected.to have_one(:campfire_service) } + it { is_expected.to have_one(:drone_ci_service) } + it { is_expected.to have_one(:emails_on_push_service) } + it { is_expected.to have_one(:pipelines_email_service) } + it { is_expected.to have_one(:irker_service) } + it { is_expected.to have_one(:pivotaltracker_service) } + it { is_expected.to have_one(:hipchat_service) } + it { is_expected.to have_one(:flowdock_service) } + it { is_expected.to have_one(:assembla_service) } + it { is_expected.to have_one(:slack_slash_commands_service) } + it { is_expected.to have_one(:mattermost_slash_commands_service) } + it { is_expected.to have_one(:gemnasium_service) } + it { is_expected.to have_one(:buildkite_service) } + it { is_expected.to have_one(:bamboo_service) } + it { is_expected.to have_one(:teamcity_service) } + it { is_expected.to have_one(:jira_service) } + it { is_expected.to have_one(:redmine_service) } + it { is_expected.to have_one(:custom_issue_tracker_service) } + it { is_expected.to have_one(:bugzilla_service) } + it { is_expected.to have_one(:gitlab_issue_tracker_service) } + it { is_expected.to have_one(:external_wiki_service) } + it { is_expected.to have_one(:project_feature) } + it { is_expected.to have_one(:statistics).class_name('ProjectStatistics') } + it { is_expected.to have_one(:import_data).class_name('ProjectImportData') } it { is_expected.to have_one(:last_event).class_name('Event') } it { is_expected.to have_one(:forked_from_project).through(:forked_project_link) } it { is_expected.to have_many(:commit_statuses) } @@ -62,18 +62,18 @@ describe Project, models: true do it { is_expected.to have_many(:variables) } it { is_expected.to have_many(:triggers) } it { is_expected.to have_many(:pages_domains) } - it { is_expected.to have_many(:labels).class_name('ProjectLabel').dependent(:destroy) } - it { is_expected.to have_many(:users_star_projects).dependent(:destroy) } - it { is_expected.to have_many(:environments).dependent(:destroy) } - it { is_expected.to have_many(:deployments).dependent(:destroy) } - it { is_expected.to have_many(:todos).dependent(:destroy) } - it { is_expected.to have_many(:releases).dependent(:destroy) } - it { is_expected.to have_many(:lfs_objects_projects).dependent(:destroy) } - it { is_expected.to have_many(:project_group_links).dependent(:destroy) } - it { is_expected.to have_many(:notification_settings).dependent(:destroy) } + it { is_expected.to have_many(:labels).class_name('ProjectLabel') } + it { is_expected.to have_many(:users_star_projects) } + it { is_expected.to have_many(:environments) } + it { is_expected.to have_many(:deployments) } + it { is_expected.to have_many(:todos) } + it { is_expected.to have_many(:releases) } + it { is_expected.to have_many(:lfs_objects_projects) } + it { is_expected.to have_many(:project_group_links) } + it { is_expected.to have_many(:notification_settings).dependent(:delete_all) } it { is_expected.to have_many(:forks).through(:forked_project_links) } it { is_expected.to have_many(:uploads).dependent(:destroy) } - it { is_expected.to have_many(:pipeline_schedules).dependent(:destroy) } + it { is_expected.to have_many(:pipeline_schedules) } context 'after initialized' do it "has a project_feature" do @@ -2199,4 +2199,21 @@ describe Project, models: true do end end end + + describe '#remove_private_deploy_keys' do + it 'removes the private deploy keys of a project' do + project = create(:empty_project) + + private_key = create(:deploy_key, public: false) + public_key = create(:deploy_key, public: true) + + create(:deploy_keys_project, deploy_key: private_key, project: project) + create(:deploy_keys_project, deploy_key: public_key, project: project) + + project.remove_private_deploy_keys + + expect(project.deploy_keys.where(public: false).any?).to eq(false) + expect(project.deploy_keys.where(public: true).any?).to eq(true) + end + end end diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb index 518e97d17a1c461e3305763d577e1d07b6675302..f05d5c7fce5a4141b0e98915c4ad37d29d073159 100644 --- a/spec/presenters/ci/build_presenter_spec.rb +++ b/spec/presenters/ci/build_presenter_spec.rb @@ -85,7 +85,7 @@ describe Ci::BuildPresenter do describe 'quack like a Ci::Build permission-wise' do context 'user is not allowed' do - let(:project) { build_stubbed(:empty_project, public_builds: false) } + let(:project) { create(:empty_project, public_builds: false) } it 'returns false' do expect(presenter.can?(nil, :read_build)).to be_falsy @@ -93,7 +93,7 @@ describe Ci::BuildPresenter do end context 'user is allowed' do - let(:project) { build_stubbed(:empty_project, :public) } + let(:project) { create(:empty_project, :public) } it 'returns true' do expect(presenter.can?(nil, :read_build)).to be_truthy diff --git a/spec/rubocop/cop/active_record_dependent_spec.rb b/spec/rubocop/cop/active_record_dependent_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..599a032bfc5e35787693815f81cc4e3c7f2f9307 --- /dev/null +++ b/spec/rubocop/cop/active_record_dependent_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' +require 'rubocop' +require 'rubocop/rspec/support' +require_relative '../../../rubocop/cop/active_record_dependent' + +describe RuboCop::Cop::ActiveRecordDependent do + include CopHelper + + subject(:cop) { described_class.new } + + context 'inside the app/models directory' do + it 'registers an offense when dependent: is used' do + allow(cop).to receive(:in_model?).and_return(true) + + inspect_source(cop, 'belongs_to :foo, dependent: :destroy') + + aggregate_failures do + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([1]) + end + end + end + + context 'outside the app/models directory' do + it 'does nothing' do + allow(cop).to receive(:in_model?).and_return(false) + + inspect_source(cop, 'belongs_to :foo, dependent: :destroy') + + expect(cop.offenses).to be_empty + end + end +end diff --git a/spec/rubocop/cop/activerecord_serialize_spec.rb b/spec/rubocop/cop/active_record_serialize_spec.rb similarity index 86% rename from spec/rubocop/cop/activerecord_serialize_spec.rb rename to spec/rubocop/cop/active_record_serialize_spec.rb index 5bd7e5fa9265243514f0b8a6a2c242af8c8d3559..b94b25cecd015ea6616607a042658080b352d53e 100644 --- a/spec/rubocop/cop/activerecord_serialize_spec.rb +++ b/spec/rubocop/cop/active_record_serialize_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' require 'rubocop' require 'rubocop/rspec/support' -require_relative '../../../rubocop/cop/activerecord_serialize' +require_relative '../../../rubocop/cop/active_record_serialize' -describe RuboCop::Cop::ActiverecordSerialize do +describe RuboCop::Cop::ActiveRecordSerialize do include CopHelper subject(:cop) { described_class.new } diff --git a/spec/workers/expire_build_instance_artifacts_worker_spec.rb b/spec/workers/expire_build_instance_artifacts_worker_spec.rb index 1d8da68883b1b8f39c6149c485d098cf207044f2..bed5c5e2ecb27731bccc215a972318604841915b 100644 --- a/spec/workers/expire_build_instance_artifacts_worker_spec.rb +++ b/spec/workers/expire_build_instance_artifacts_worker_spec.rb @@ -30,20 +30,6 @@ describe ExpireBuildInstanceArtifactsWorker do expect(build.reload.artifacts_file_identifier).to be_nil end end - - context 'when associated project was removed' do - let(:build) do - create(:ci_build, :artifacts, artifacts_expiry) do |build| - build.project.pending_delete = true - end - end - - it 'does not remove artifacts' do - expect do - build.reload.artifacts_file - end.not_to raise_error - end - end end context 'with not yet expired artifacts' do