diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 11f4a3f3b6f26bd50a7e5c43ff667482ce1b6ab5..168646bbe416f751f5559818949a06d52e64f82e 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -63,32 +63,23 @@ def required_minimum_access_level_for_private_project(feature)
 
   validate :repository_children_level
 
-  default_value_for :builds_access_level, value: ENABLED, allows_nil: false
-  default_value_for :issues_access_level, value: ENABLED, allows_nil: false
-  default_value_for :forking_access_level, value: ENABLED, allows_nil: false
-  default_value_for :merge_requests_access_level, value: ENABLED, allows_nil: false
-  default_value_for :snippets_access_level, value: ENABLED, allows_nil: false
-  default_value_for :wiki_access_level, value: ENABLED, allows_nil: false
-  default_value_for :repository_access_level, value: ENABLED, allows_nil: false
-  default_value_for :analytics_access_level, value: ENABLED, allows_nil: false
-  default_value_for :metrics_dashboard_access_level, value: PRIVATE, allows_nil: false
-  default_value_for :operations_access_level, value: ENABLED, allows_nil: false
-  default_value_for :security_and_compliance_access_level, value: PRIVATE, allows_nil: false
-  default_value_for :monitor_access_level, value: ENABLED, allows_nil: false
-  default_value_for :infrastructure_access_level, value: ENABLED, allows_nil: false
-  default_value_for :feature_flags_access_level, value: ENABLED, allows_nil: false
-  default_value_for :environments_access_level, value: ENABLED, allows_nil: false
-  default_value_for :releases_access_level, value: ENABLED, allows_nil: false
-
-  default_value_for(:pages_access_level, allows_nil: false) do |feature|
-    if ::Gitlab::Pages.access_control_is_forced?
-      PRIVATE
-    else
-      feature.project&.public? ? ENABLED : PRIVATE
-    end
-  end
-
-  default_value_for(:package_registry_access_level) do |feature|
+  attribute :builds_access_level, default: ENABLED
+  attribute :issues_access_level, default: ENABLED
+  attribute :forking_access_level, default: ENABLED
+  attribute :merge_requests_access_level, default: ENABLED
+  attribute :snippets_access_level, default: ENABLED
+  attribute :wiki_access_level, default: ENABLED
+  attribute :repository_access_level, default: ENABLED
+  attribute :analytics_access_level, default: ENABLED
+  attribute :metrics_dashboard_access_level, default: PRIVATE
+  attribute :operations_access_level, default: ENABLED
+  attribute :security_and_compliance_access_level, default: PRIVATE
+  attribute :monitor_access_level, default: ENABLED
+  attribute :infrastructure_access_level, default: ENABLED
+  attribute :feature_flags_access_level, default: ENABLED
+  attribute :environments_access_level, default: ENABLED
+
+  attribute :package_registry_access_level, default: -> do
     if ::Gitlab.config.packages.enabled
       ENABLED
     else
@@ -96,7 +87,7 @@ def required_minimum_access_level_for_private_project(feature)
     end
   end
 
-  default_value_for(:container_registry_access_level) do |feature|
+  attribute :container_registry_access_level, default: -> do
     if gitlab_config_features.container_registry
       ENABLED
     else
@@ -104,6 +95,9 @@ def required_minimum_access_level_for_private_project(feature)
     end
   end
 
+  after_initialize :set_pages_access_level, if: :new_record?
+  after_initialize :set_default_values, unless: :new_record?
+
   # "enabled" here means "not disabled". It includes private features!
   scope :with_feature_enabled, ->(feature) {
     feature_access_level_attribute = arel_table[access_level_attribute(feature)]
@@ -170,6 +164,23 @@ def package_registry_access_level=(value)
 
   private
 
+  def set_pages_access_level
+    self.pages_access_level ||= if ::Gitlab::Pages.access_control_is_forced?
+                                  PRIVATE
+                                else
+                                  self.project&.public? ? ENABLED : PRIVATE
+                                end
+  end
+
+  def set_default_values
+    self.class.column_names.each do |column_name|
+      next unless has_attribute?(column_name)
+      next unless read_attribute(column_name).nil?
+
+      write_attribute(column_name, self.class.column_defaults[column_name])
+    end
+  end
+
   # Validates builds and merge requests access level
   # which cannot be higher than repository access level
   def repository_children_level
diff --git a/ee/app/models/ee/project_feature.rb b/ee/app/models/ee/project_feature.rb
index 15f110ccb03a2cdb079037ba2915a56ceab9aedf..963b2ff69c3085454da3ccdf5ce21c2218b06ffb 100644
--- a/ee/app/models/ee/project_feature.rb
+++ b/ee/app/models/ee/project_feature.rb
@@ -30,7 +30,7 @@ module ProjectFeature
         end
       end
 
-      default_value_for :requirements_access_level, value: Featurable::ENABLED, allows_nil: false
+      attribute :requirements_access_level, default: Featurable::ENABLED
 
       private
 
diff --git a/ee/spec/models/project_feature_spec.rb b/ee/spec/models/project_feature_spec.rb
index 43545fec41f93f49531fb9b137c1e8d9aa3a8b45..446e0438432f59f3ff524aae44316cff29183fdb 100644
--- a/ee/spec/models/project_feature_spec.rb
+++ b/ee/spec/models/project_feature_spec.rb
@@ -6,6 +6,12 @@
   let(:project) { create(:project, :public) }
   let(:user) { create(:user) }
 
+  describe 'default values' do
+    subject { Project.new.project_feature }
+
+    specify { expect(subject.requirements_access_level).to eq(Featurable::ENABLED) }
+  end
+
   describe '#feature_available?' do
     let(:features) { %w(issues wiki builds merge_requests snippets repository pages) }
 
diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb
index fb6aaffdf223de070bfbc303c4952f1bd9b6c2d1..fe0b46c31172cb0e4f8eaf882750818cee54d018 100644
--- a/spec/models/project_feature_spec.rb
+++ b/spec/models/project_feature_spec.rb
@@ -2,7 +2,7 @@
 
 require 'spec_helper'
 
-RSpec.describe ProjectFeature do
+RSpec.describe ProjectFeature, feature_category: :projects do
   using RSpec::Parameterized::TableSyntax
 
   let_it_be_with_reload(:project) { create(:project) }
@@ -10,6 +10,28 @@
 
   it { is_expected.to belong_to(:project) }
 
+  describe 'default values' do
+    subject { Project.new.project_feature }
+
+    specify { expect(subject.builds_access_level).to eq(ProjectFeature::ENABLED) }
+    specify { expect(subject.issues_access_level).to eq(ProjectFeature::ENABLED) }
+    specify { expect(subject.forking_access_level).to eq(ProjectFeature::ENABLED) }
+    specify { expect(subject.merge_requests_access_level).to eq(ProjectFeature::ENABLED) }
+    specify { expect(subject.snippets_access_level).to eq(ProjectFeature::ENABLED) }
+    specify { expect(subject.wiki_access_level).to eq(ProjectFeature::ENABLED) }
+    specify { expect(subject.repository_access_level).to eq(ProjectFeature::ENABLED) }
+    specify { expect(subject.metrics_dashboard_access_level).to eq(ProjectFeature::PRIVATE) }
+    specify { expect(subject.operations_access_level).to eq(ProjectFeature::ENABLED) }
+    specify { expect(subject.security_and_compliance_access_level).to eq(ProjectFeature::PRIVATE) }
+    specify { expect(subject.monitor_access_level).to eq(ProjectFeature::ENABLED) }
+    specify { expect(subject.infrastructure_access_level).to eq(ProjectFeature::ENABLED) }
+    specify { expect(subject.feature_flags_access_level).to eq(ProjectFeature::ENABLED) }
+    specify { expect(subject.environments_access_level).to eq(ProjectFeature::ENABLED) }
+    specify { expect(subject.releases_access_level).to eq(ProjectFeature::ENABLED) }
+    specify { expect(subject.package_registry_access_level).to eq(ProjectFeature::ENABLED) }
+    specify { expect(subject.container_registry_access_level).to eq(ProjectFeature::ENABLED) }
+  end
+
   describe 'PRIVATE_FEATURES_MIN_ACCESS_LEVEL_FOR_PRIVATE_PROJECT' do
     it 'has higher level than that of PRIVATE_FEATURES_MIN_ACCESS_LEVEL' do
       described_class::PRIVATE_FEATURES_MIN_ACCESS_LEVEL_FOR_PRIVATE_PROJECT.each do |feature, level|