## Code Context **Query:** contact management, contact inbox, custom attribute definition, custom filter, contact merge, contact search, CRM, note, label ### Entry Points - **AddRegexToCustomAttributeDefinition** (class) - db/migrate/20230905060223_add_regex_to_custom_attribute_definition.rb:1 - **CustomAttributeDefinition** (class) - app/models/custom_attribute_definition.rb:24 - **CustomAttributeDefinitionPolicy** (class) - app/policies/custom_attribute_definition_policy.rb:1 ### Related Symbols - app/models/application_record.rb: ApplicationRecord:1 - app/policies/application_policy.rb: ApplicationPolicy:1 - db/migrate/20230905060223_add_regex_to_custom_attribute_definition.rb: change:2 - app/models/custom_attribute_definition.rb: normalize_attribute_fields:54, sync_widget_pre_chat_custom_fields:59, update_widget_pre_chat_custom_fields:63, attribute_must_not_conflict:67 - app/policies/custom_attribute_definition_policy.rb: index?:2, show?:6, create?:10 ### Code #### AddRegexToCustomAttributeDefinition (db/migrate/20230905060223_add_regex_to_custom_attribute_definition.rb:1) ```ruby class AddRegexToCustomAttributeDefinition < ActiveRecord::Migration[7.0] def change add_column :custom_attribute_definitions, :regex_pattern, :string add_column :custom_attribute_definitions, :regex_cue, :string end end ``` #### CustomAttributeDefinition (app/models/custom_attribute_definition.rb:24) ```ruby class CustomAttributeDefinition < ApplicationRecord STANDARD_ATTRIBUTES = { :conversation => %w[status priority assignee_id inbox_id team_id display_id campaign_id labels browser_language country_code referer created_at last_activity_at], :contact => %w[name email phone_number identifier country_code city company_name created_at last_activity_at referer blocked], :company => %w[name domain description contacts_count created_at updated_at last_activity_at] }.freeze scope :with_attribute_model, ->(attribute_model) { attribute_model.presence && where(attribute_model: attribute_model) } validates :attribute_display_name, presence: true before_validation :normalize_attribute_fields validates :attribute_key, presence: true, uniqueness: { scope: [:account_id, :attribute_model] }, format: { with: /\A[\p{L}\p{N}_.\-]+\z/, message: I18n.t('errors.custom_attribute_definition.attribute_key_format') } validates :attribute_display_type, presence: true validates :attribute_model, presence: true validate :attribute_must_not_conflict, on: :create enum attribute_model: { conversation_attribute: 0, contact_attribute: 1, company_attribute: 2 } enum attribute_display_type: { text: 0, number: 1, currency: 2, percent: 3, link: 4, date: 5, list: 6, checkbox: 7 } belongs_to :account after_update :update_widget_pre_chat_custom_fields, unless: :company_attribute? after_destroy :sync_widget_pre_chat_ // ... truncated ... ``` #### CustomAttributeDefinitionPolicy (app/policies/custom_attribute_definition_policy.rb:1) ```ruby class CustomAttributeDefinitionPolicy < ApplicationPolicy def index? @account_user.administrator? || @account_user.agent? end def show? @account_user.administrator? || @account_user.agent? end def create? @account_user.administrator? end def update? @account_user.administrator? end def destroy? @account_user.administrator? end end ``` #### change (db/migrate/20230905060223_add_regex_to_custom_attribute_definition.rb:2) ```ruby def change add_column :custom_attribute_definitions, :regex_pattern, :string add_column :custom_attribute_definitions, :regex_cue, :string end ``` #### normalize_attribute_fields (app/models/custom_attribute_definition.rb:54) ```ruby def normalize_attribute_fields self.attribute_key = attribute_key.strip if attribute_key.present? self.attribute_display_name = attribute_display_name.strip if attribute_display_name.present? end ``` #### sync_widget_pre_chat_custom_fields (app/models/custom_attribute_definition.rb:59) ```ruby def sync_widget_pre_chat_custom_fields ::Inboxes::SyncWidgetPreChatCustomFieldsJob.perform_later(account, attribute_key) end ``` #### update_widget_pre_chat_custom_fields (app/models/custom_attribute_definition.rb:63) ```ruby def update_widget_pre_chat_custom_fields ::Inboxes::UpdateWidgetPreChatCustomFieldsJob.perform_later(account, self) end ``` #### attribute_must_not_conflict (app/models/custom_attribute_definition.rb:67) ```ruby def attribute_must_not_conflict model_keys = attribute_model.to_s.delete_suffix('_attribute').to_sym standard_attributes = STANDARD_ATTRIBUTES[model_keys] return if standard_attributes.blank? return unless attribute_key.in?(standard_attributes) errors.add(:attribute_key, I18n.t('errors.custom_attribute_definition.key_conflict')) end ``` #### index? (app/policies/custom_attribute_definition_policy.rb:2) ```ruby def index? @account_user.administrator? || @account_user.agent? end ``` #### show? (app/policies/custom_attribute_definition_policy.rb:6) ```ruby def show? @account_user.administrator? || @account_user.agent? end ``` #### create? (app/policies/custom_attribute_definition_policy.rb:10) ```ruby def create? @account_user.administrator? end ``` #### update? (app/policies/custom_attribute_definition_policy.rb:14) ```ruby def update? @account_user.administrator? end ``` #### destroy? (app/policies/custom_attribute_definition_policy.rb:18) ```ruby def destroy? @account_user.administrator? end ``` #### ApplicationRecord (app/models/application_record.rb:1) ```ruby class ApplicationRecord < ActiveRecord::Base include Events::Types self.abstract_class = true before_validation :validates_column_content_length # the models that exposed in email templates through liquid def droppables %w[Account Channel Conversation Inbox User Message] end # ModelDrop class should exist in app/drops def to_drop return unless droppables.include?(self.class.name) "#{self.class.name}Drop".constantize.new(self) end private # Generic validation for all columns of type string and text # Validates the length of the column to prevent DOS via large payloads # if a custom length validation is already present, skip the validation def validates_column_content_length self.class.columns.each do |column| check_and_validate_content_length(column) if column_of_type_string_or_text?(column) end end def column_of_type_string_or_text?(column) %i[string text].include?(column.type) end def check_and_validate_content_length(column) length_validator = self.class.validators_on(column.name).find { |v| v.kind == :length } validate_content_length(column) if length_validator.blank? end def validate_content_length(column) max_length = column.type == :text ? 20_000 : 255 return if self[column.name].nil? || self[column.name].length <= max_length errors.add(column.name.to_sym, "is too long (maximum is #{max_length} characters)") end def normalize_empty_string_to_nil(attrs = []) attrs // ... truncated ... ``` #### ApplicationPolicy (app/policies/application_policy.rb:1) ```ruby class ApplicationPolicy attr_reader :user_context, :user, :record, :account, :account_user def initialize(user_context, record) @user_context = user_context @user = user_context[:user] @account = user_context[:account] @account_user = user_context[:account_user] @record = record end def index? false end def show? scope.exists?(id: record.id) end def create? false end def new? create? end def update? false end def edit? update? end def destroy? false end def scope Pundit.policy_scope!(user_context, record.class) end class Scope attr_reader :user_context, :user, :scope, :account, :account_user def initialize(user_context, scope) @user_context = user_context @user = user_context[:user] @account = user_context[:account] @account_user = user_context[:account_user] @scope = scope end def resolve scope end end end ```