mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 15:25:35 +08:00
DEV: Set limits on custom fields
This patch sets some limits on custom fields: - an entity can’t have more than 100 custom fields defined on it - a custom field can’t hold a value greater than 10,000,000 characters The current implementation of custom fields is relatively complex and does an upsert in SQL at some point, thus preventing to simply add an `ActiveRecord` validation on the custom field model without having to rewrite a part of the existing logic. That’s one of the reasons this patch is implementing validations in the `HasCustomField` module adding them to the model including the module.
This commit is contained in:
parent
c08a52e502
commit
5257c80064
|
@ -60,12 +60,19 @@ module HasCustomFields
|
|||
end
|
||||
end
|
||||
|
||||
included do
|
||||
has_many :_custom_fields, dependent: :destroy, class_name: "#{name}CustomField"
|
||||
after_save :save_custom_fields
|
||||
CUSTOM_FIELDS_MAX_ITEMS = 100
|
||||
CUSTOM_FIELDS_MAX_VALUE_LENGTH = 10_000_000
|
||||
|
||||
included do
|
||||
attr_reader :preloaded_custom_fields
|
||||
|
||||
has_many :_custom_fields, dependent: :destroy, class_name: "#{name}CustomField"
|
||||
|
||||
validate :custom_fields_max_items, unless: :custom_fields_clean?
|
||||
validate :custom_fields_value_length, unless: :custom_fields_clean?
|
||||
|
||||
after_save :save_custom_fields
|
||||
|
||||
def custom_fields_fk
|
||||
@custom_fields_fk ||= "#{_custom_fields.reflect_on_all_associations(:belongs_to)[0].name}_id"
|
||||
end
|
||||
|
@ -133,6 +140,28 @@ module HasCustomFields
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def custom_fields_max_items
|
||||
if custom_fields.size > CUSTOM_FIELDS_MAX_ITEMS
|
||||
errors.add(
|
||||
:base,
|
||||
I18n.t("custom_fields.validations.max_items", max_items_number: CUSTOM_FIELDS_MAX_ITEMS),
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def custom_fields_value_length
|
||||
return if custom_fields.values.all? { _1.to_s.size <= CUSTOM_FIELDS_MAX_VALUE_LENGTH }
|
||||
errors.add(
|
||||
:base,
|
||||
I18n.t(
|
||||
"custom_fields.validations.max_value_length",
|
||||
max_value_length: CUSTOM_FIELDS_MAX_VALUE_LENGTH,
|
||||
),
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def reload(options = nil)
|
||||
|
|
|
@ -245,6 +245,10 @@ en:
|
|||
errors:
|
||||
<<: *errors
|
||||
|
||||
custom_fields:
|
||||
validations:
|
||||
max_items: "Maximum number of custom fields for this entity has been reached (%{max_items_number})"
|
||||
max_value_length: "Maximum length for a custom field value has been reached (%{max_value_length})"
|
||||
invite:
|
||||
expired: "Your invite token has expired. Please <a href='%{base_url}/about'>contact staff</a>."
|
||||
not_found: "Your invite token is invalid. Please <a href='%{base_url}/about'>contact staff</a>."
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
RSpec.describe Category do
|
||||
fab!(:user) { Fabricate(:user) }
|
||||
|
||||
it_behaves_like "it has custom fields"
|
||||
|
||||
it { is_expected.to validate_presence_of :user_id }
|
||||
it { is_expected.to validate_presence_of :name }
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@ RSpec.describe Group do
|
|||
let(:user) { Fabricate(:user) }
|
||||
let(:group) { Fabricate(:group) }
|
||||
|
||||
it_behaves_like "it has custom fields"
|
||||
|
||||
describe "Validations" do
|
||||
it { is_expected.to allow_value("#{"a" * 996}.com").for(:automatic_membership_email_domains) }
|
||||
it do
|
||||
|
|
|
@ -7,6 +7,8 @@ RSpec.describe Post do
|
|||
|
||||
before { Oneboxer.stubs :onebox }
|
||||
|
||||
it_behaves_like "it has custom fields"
|
||||
|
||||
it { is_expected.to have_many(:reviewables).dependent(:destroy) }
|
||||
|
||||
describe "#hidden_reasons" do
|
||||
|
|
|
@ -16,6 +16,8 @@ RSpec.describe Topic do
|
|||
Fabricate(:user, trust_level: SiteSetting.min_trust_level_to_allow_invite)
|
||||
end
|
||||
|
||||
it_behaves_like "it has custom fields"
|
||||
|
||||
describe "Validations" do
|
||||
let(:topic) { Fabricate.build(:topic) }
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe User do
|
||||
subject(:user) { Fabricate(:user, last_seen_at: 1.day.ago) }
|
||||
|
||||
fab!(:group) { Fabricate(:group) }
|
||||
|
||||
subject(:user) { Fabricate(:user, last_seen_at: 1.day.ago) }
|
||||
it_behaves_like "it has custom fields"
|
||||
|
||||
def user_error_message(*keys)
|
||||
I18n.t(:"activerecord.errors.models.user.attributes.#{keys.join(".")}")
|
||||
|
|
27
spec/support/shared_examples_for_custom_fields.rb
Normal file
27
spec/support/shared_examples_for_custom_fields.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples_for "it has custom fields" do
|
||||
let(:record) { described_class.new }
|
||||
|
||||
describe "Max number of custom fields" do
|
||||
let(:custom_fields) { (1..101).to_a.product(["value"]).to_h }
|
||||
|
||||
before { record.custom_fields = custom_fields }
|
||||
|
||||
it "can't have more than 100 custom fields" do
|
||||
expect(record).to be_invalid
|
||||
expect(record.errors[:base]).to include(/Maximum number.*\(100\)/)
|
||||
end
|
||||
end
|
||||
|
||||
describe "Max length of a custom field" do
|
||||
let(:bad_value) { "a" * 10_000_001 }
|
||||
|
||||
before { record.custom_fields[:my_custom_field] = bad_value }
|
||||
|
||||
it "can't have more than 10,000,000 characters" do
|
||||
expect(record).to be_invalid
|
||||
expect(record.errors[:base]).to include(/Maximum length.*\(10000000\)/)
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user