attribute.rb 26 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. class ObjectManager::Attribute < ApplicationModel
  3. include HasDefaultModelUserRelations
  4. include ChecksClientNotification
  5. include CanSeed
  6. DATA_TYPES = %w[
  7. input
  8. user_autocompletion
  9. checkbox
  10. select
  11. multiselect
  12. tree_select
  13. multi_tree_select
  14. datetime
  15. date
  16. tag
  17. richtext
  18. textarea
  19. integer
  20. autocompletion_ajax
  21. autocompletion_ajax_customer_organization
  22. autocompletion_ajax_external_data_source
  23. boolean
  24. user_permission
  25. group_permissions
  26. active
  27. ].freeze
  28. self.table_name = 'object_manager_attributes'
  29. belongs_to :object_lookup, optional: true
  30. validates :name, presence: true
  31. validates :data_type, inclusion: { in: DATA_TYPES, msg: '%{value} is not a valid data type' }
  32. validate :inactive_must_be_unused_by_references, unless: :active?
  33. validate :data_type_must_not_change, on: :update
  34. validate :json_field_only_on_postgresql, on: :create
  35. validates_with ObjectManager::Attribute::DataOptionValidator
  36. store :screens
  37. store :data_option
  38. store :data_option_new
  39. before_validation :set_base_options
  40. before_create :ensure_multiselect
  41. before_update :ensure_multiselect
  42. scope :active, -> { where(active: true) }
  43. scope :editable, -> { where(editable: true) }
  44. scope :for_object, lambda { |name_or_klass|
  45. id = ObjectLookup.lookup(name: name_or_klass.to_s)
  46. where(object_lookup_id: id)
  47. }
  48. =begin
  49. list of all attributes
  50. result = ObjectManager::Attribute.list_full
  51. result = [
  52. {
  53. name: 'some name',
  54. display: '...',
  55. }.
  56. ],
  57. =end
  58. def self.list_full
  59. result = ObjectManager::Attribute.reorder('position ASC, name ASC')
  60. references = ObjectManager::Attribute.attribute_to_references_hash
  61. attributes = []
  62. result.each do |item|
  63. attribute = item.attributes
  64. attribute[:object] = ObjectLookup.by_id(item.object_lookup_id)
  65. attribute.delete('object_lookup_id')
  66. # an attribute is deletable if it is both editable and not referenced by other Objects (Triggers, Overviews, Schedulers)
  67. deletable = true
  68. not_deletable_reason = ''
  69. if ObjectManager::Attribute.attribute_used_by_references?(attribute[:object], attribute['name'], references)
  70. deletable = false
  71. not_deletable_reason = ObjectManager::Attribute.attribute_used_by_references_humaniced(attribute[:object], attribute['name'], references)
  72. end
  73. attribute[:deletable] = attribute['editable'] && deletable == true
  74. if not_deletable_reason.present?
  75. attribute[:not_deletable_reason] = "This attribute is referenced by #{not_deletable_reason} and thus cannot be deleted!"
  76. end
  77. attributes.push attribute
  78. end
  79. attributes
  80. end
  81. =begin
  82. add a new attribute entry for an object
  83. ObjectManager::Attribute.add(
  84. object: 'Ticket',
  85. name: 'group_id',
  86. display: __('Group'),
  87. data_type: 'tree_select',
  88. data_option: {
  89. relation: 'Group',
  90. relation_condition: { access: 'full' },
  91. multiple: false,
  92. null: true,
  93. translate: false,
  94. belongs_to: 'group',
  95. },
  96. active: true,
  97. screens: {
  98. create: {
  99. '-all-' => {
  100. required: true,
  101. },
  102. },
  103. edit: {
  104. 'ticket.agent' => {
  105. required: true,
  106. },
  107. },
  108. },
  109. position: 20,
  110. created_by_id: 1,
  111. updated_by_id: 1,
  112. created_at: '2014-06-04 10:00:00',
  113. updated_at: '2014-06-04 10:00:00',
  114. force: true
  115. editable: false,
  116. to_migrate: false,
  117. to_create: false,
  118. to_delete: false,
  119. to_config: false,
  120. )
  121. preserved name are
  122. /(_id|_ids)$/
  123. possible types
  124. # input
  125. data_type: 'input',
  126. data_option: {
  127. default: '',
  128. type: 'text', # text|email|url|tel
  129. maxlength: 200,
  130. null: true,
  131. note: 'some additional comment', # optional
  132. link_template: '', # optional
  133. },
  134. # select
  135. data_type: 'select',
  136. data_option: {
  137. default: 'aa',
  138. options: {
  139. 'aa' => 'aa (comment)',
  140. 'bb' => 'bb (comment)',
  141. },
  142. maxlength: 200,
  143. nulloption: true,
  144. null: false,
  145. multiple: false, # currently only "false" supported
  146. translate: true, # optional
  147. note: 'some additional comment', # optional
  148. link_template: '', # optional
  149. },
  150. # tree_select
  151. data_type: 'tree_select',
  152. data_option: {
  153. default: 'aa',
  154. options: [
  155. {
  156. 'value' => 'aa',
  157. 'name' => 'aa (comment)',
  158. 'children' => [
  159. {
  160. 'value' => 'aaa',
  161. 'name' => 'aaa (comment)',
  162. },
  163. {
  164. 'value' => 'aab',
  165. 'name' => 'aab (comment)',
  166. },
  167. {
  168. 'value' => 'aac',
  169. 'name' => 'aac (comment)',
  170. },
  171. ]
  172. },
  173. {
  174. 'value' => 'bb',
  175. 'name' => 'bb (comment)',
  176. 'children' => [
  177. {
  178. 'value' => 'bba',
  179. 'name' => 'aaa (comment)',
  180. },
  181. {
  182. 'value' => 'bbb',
  183. 'name' => 'bbb (comment)',
  184. },
  185. {
  186. 'value' => 'bbc',
  187. 'name' => 'bbc (comment)',
  188. },
  189. ]
  190. },
  191. ],
  192. maxlength: 200,
  193. nulloption: true,
  194. null: false,
  195. multiple: false, # currently only "false" supported
  196. translate: true, # optional
  197. note: 'some additional comment', # optional
  198. },
  199. # checkbox
  200. data_type: 'checkbox',
  201. data_option: {
  202. default: 'aa',
  203. options: {
  204. 'aa' => 'aa (comment)',
  205. 'bb' => 'bb (comment)',
  206. },
  207. null: false,
  208. translate: true, # optional
  209. note: 'some additional comment', # optional
  210. },
  211. # integer
  212. data_type: 'integer',
  213. data_option: {
  214. default: 5,
  215. min: 15,
  216. max: 999,
  217. null: false,
  218. note: 'some additional comment', # optional
  219. },
  220. # boolean
  221. data_type: 'boolean',
  222. data_option: {
  223. default: true,
  224. options: {
  225. true => 'aa',
  226. false => 'bb',
  227. },
  228. null: false,
  229. translate: true, # optional
  230. note: 'some additional comment', # optional
  231. },
  232. # datetime
  233. data_type: 'datetime',
  234. data_option: {
  235. future: true, # true|false
  236. past: true, # true|false
  237. diff: 12, # in hours
  238. null: false,
  239. note: 'some additional comment', # optional
  240. },
  241. # date
  242. data_type: 'date',
  243. data_option: {
  244. future: true, # true|false
  245. past: true, # true|false
  246. diff: 15, # in days
  247. null: false,
  248. note: 'some additional comment', # optional
  249. },
  250. # textarea
  251. data_type: 'textarea',
  252. data_option: {
  253. default: '',
  254. rows: 15,
  255. null: false,
  256. note: 'some additional comment', # optional
  257. },
  258. # richtext
  259. data_type: 'richtext',
  260. data_option: {
  261. default: '',
  262. null: false,
  263. note: 'some additional comment', # optional
  264. },
  265. =end
  266. def self.add(data)
  267. force = data[:force]
  268. data.delete(:force)
  269. # lookups
  270. if data[:object]
  271. data[:object_lookup_id] = ObjectLookup.by_name(data[:object])
  272. end
  273. data.delete(:object)
  274. data[:name].downcase!
  275. # check new entry - is needed
  276. record = ObjectManager::Attribute.find_by(
  277. object_lookup_id: data[:object_lookup_id],
  278. name: data[:name],
  279. )
  280. if record
  281. # do not allow to overwrite certain attributes
  282. if !force
  283. data.delete(:editable)
  284. data.delete(:to_create)
  285. data.delete(:to_migrate)
  286. data.delete(:to_delete)
  287. data.delete(:to_config)
  288. end
  289. # if data_option has changed, store it for next migration
  290. if !force
  291. %i[name display data_type position active].each do |key|
  292. next if record[key] == data[key]
  293. record[:data_option_new] = data[:data_option] if data[:data_option] # bring the data options over as well, when there are changes to the fields above
  294. data[:to_config] = true
  295. break
  296. end
  297. if record[:data_option] != data[:data_option]
  298. # do we need a database migration?
  299. if record[:data_option][:maxlength] && data[:data_option][:maxlength] && record[:data_option][:maxlength].to_s != data[:data_option][:maxlength].to_s
  300. data[:to_migrate] = true
  301. end
  302. record[:data_option_new] = data[:data_option]
  303. data.delete(:data_option)
  304. data[:to_config] = true
  305. end
  306. end
  307. # update attributes
  308. data.each do |key, value|
  309. record[key.to_sym] = value
  310. end
  311. # check editable & name
  312. if !force
  313. record.check_editable
  314. record.check_name
  315. end
  316. record.save!
  317. return record
  318. end
  319. # add maximum position only for new records with blank position
  320. if !record && data[:position].blank?
  321. maximum_position = where(object_lookup_id: data[:object_lookup_id]).maximum(:position)
  322. data[:position] = maximum_position.present? ? maximum_position + 1 : 1
  323. end
  324. # do not allow to overwrite certain attributes
  325. if !force
  326. data[:editable] = true
  327. data[:to_create] = true
  328. data[:to_migrate] = true
  329. data[:to_delete] = false
  330. end
  331. record = ObjectManager::Attribute.new(data)
  332. # check editable & name
  333. if !force
  334. record.check_editable
  335. record.check_name
  336. end
  337. record.save!
  338. record
  339. end
  340. =begin
  341. remove attribute entry for an object
  342. ObjectManager::Attribute.remove(
  343. object: 'Ticket',
  344. name: 'group_id',
  345. )
  346. use "force: true" to delete also not editable fields
  347. =end
  348. def self.remove(data)
  349. # lookups
  350. if data[:object]
  351. data[:object_lookup_id] = ObjectLookup.by_name(data[:object])
  352. elsif data[:object_lookup_id]
  353. data[:object] = ObjectLookup.by_id(data[:object_lookup_id])
  354. else
  355. raise 'need object or object_lookup_id param!'
  356. end
  357. data[:name].downcase!
  358. # check newest entry - is needed
  359. record = ObjectManager::Attribute.find_by(
  360. object_lookup_id: data[:object_lookup_id],
  361. name: data[:name],
  362. )
  363. if !record
  364. raise "No such field #{data[:object]}.#{data[:name]}"
  365. end
  366. if !data[:force] && !record.editable
  367. raise "#{data[:object]}.#{data[:name]} can't be removed!"
  368. end
  369. # check to make sure that no triggers, overviews, or schedulers references this attribute
  370. if ObjectManager::Attribute.attribute_used_by_references?(data[:object], data[:name])
  371. text = ObjectManager::Attribute.attribute_used_by_references_humaniced(data[:object], data[:name])
  372. raise "#{data[:object]}.#{data[:name]} is referenced by #{text} and thus cannot be deleted!"
  373. end
  374. # if record is to create, just destroy it
  375. if record.to_create
  376. record.destroy
  377. return true
  378. end
  379. record.to_delete = true
  380. record.save
  381. end
  382. =begin
  383. get the attribute model based on object and name
  384. attribute = ObjectManager::Attribute.get(
  385. object: 'Ticket',
  386. name: 'group_id',
  387. )
  388. =end
  389. def self.get(data)
  390. # lookups
  391. if data[:object]
  392. data[:object_lookup_id] = ObjectLookup.by_name(data[:object])
  393. end
  394. ObjectManager::Attribute.find_by(
  395. object_lookup_id: data[:object_lookup_id],
  396. name: data[:name].downcase,
  397. )
  398. end
  399. =begin
  400. discard migration changes
  401. ObjectManager::Attribute.discard_changes
  402. returns
  403. true|false
  404. =end
  405. def self.discard_changes
  406. ObjectManager::Attribute.where(to_create: true).each(&:destroy)
  407. ObjectManager::Attribute.where('to_delete = ? OR to_config = ?', true, true).each do |attribute|
  408. attribute.to_migrate = false
  409. attribute.to_delete = false
  410. attribute.to_config = false
  411. attribute.data_option_new = {}
  412. attribute.save
  413. end
  414. true
  415. end
  416. =begin
  417. check if we have pending migrations of attributes
  418. ObjectManager::Attribute.pending_migration?
  419. returns
  420. true|false
  421. =end
  422. def self.pending_migration?
  423. return false if migrations.blank?
  424. true
  425. end
  426. =begin
  427. get list of pending attributes migrations
  428. ObjectManager::Attribute.migrations
  429. returns
  430. [record1, record2, ...]
  431. =end
  432. def self.migrations
  433. ObjectManager::Attribute.where('to_create = ? OR to_migrate = ? OR to_delete = ? OR to_config = ?', true, true, true, true)
  434. end
  435. def self.attribute_historic_options(attribute)
  436. historical_options = attribute.data_option[:historical_options] || {}
  437. if attribute.data_option[:options].present?
  438. historical_options = historical_options.merge(data_options_hash(attribute.data_option[:options]))
  439. end
  440. if attribute.data_option_new[:options].present?
  441. historical_options = historical_options.merge(data_options_hash(attribute.data_option_new[:options]))
  442. end
  443. historical_options
  444. end
  445. def self.data_options_hash(options, result = {})
  446. return options if options.is_a?(Hash)
  447. return {} if !options.is_a?(Array)
  448. options.each do |option|
  449. result[ option[:value] ] = option[:name]
  450. if option[:children].present?
  451. data_options_hash(option[:children], result)
  452. end
  453. end
  454. result
  455. end
  456. =begin
  457. start migration of pending attribute migrations
  458. ObjectManager::Attribute.migration_execute
  459. returns
  460. [record1, record2, ...]
  461. to send no browser reload event, pass false
  462. ObjectManager::Attribute.migration_execute(false)
  463. =end
  464. def self.migration_execute(send_event = true)
  465. # check if field already exists
  466. execute_db_count = 0
  467. execute_config_count = 0
  468. migrations.each do |attribute|
  469. model = attribute.object_lookup.name.constantize
  470. # remove field
  471. if attribute.to_delete
  472. if model.column_names.include?(attribute.name)
  473. ActiveRecord::Migration.remove_column model.table_name, attribute.name
  474. reset_database_info(model)
  475. end
  476. execute_db_count += 1
  477. attribute.destroy
  478. next
  479. end
  480. # config changes
  481. if attribute.to_config
  482. execute_config_count += 1
  483. if attribute.data_type =~ %r{^(multi|tree_)?select$} && attribute.data_option[:options]
  484. attribute.data_option_new[:historical_options] = attribute_historic_options(attribute)
  485. end
  486. attribute.data_option = attribute.data_option_new
  487. attribute.data_option_new = {}
  488. attribute.to_config = false
  489. attribute.save!
  490. next if !attribute.to_create && !attribute.to_migrate && !attribute.to_delete
  491. end
  492. if %r{^(multi|tree_)?select$}.match?(attribute.data_type)
  493. attribute.data_option[:historical_options] = attribute_historic_options(attribute)
  494. end
  495. data_type = nil
  496. case attribute.data_type
  497. when %r{^(input|select|tree_select|richtext|textarea|checkbox)$}
  498. data_type = :string
  499. when 'autocompletion_ajax_external_data_source'
  500. data_type = :jsonb
  501. when %r{^(multiselect|multi_tree_select)$}
  502. data_type = if Rails.application.config.db_column_array
  503. :string
  504. else
  505. :json
  506. end
  507. when %r{^(integer|user_autocompletion)$}
  508. data_type = :integer
  509. when %r{^(boolean|active)$}
  510. data_type = :boolean
  511. when %r{^datetime$}
  512. data_type = :datetime
  513. when %r{^date$}
  514. data_type = :date
  515. end
  516. # change field
  517. if model.column_names.include?(attribute.name)
  518. case attribute.data_type
  519. when %r{^(input|select|tree_select|richtext|textarea|checkbox)$}
  520. ActiveRecord::Migration.change_column(
  521. model.table_name,
  522. attribute.name,
  523. data_type,
  524. limit: attribute.data_option[:maxlength],
  525. null: true
  526. )
  527. when %r{^(multiselect|multi_tree_select)$}
  528. options = {
  529. null: true,
  530. }
  531. if Rails.application.config.db_column_array
  532. options[:array] = true
  533. end
  534. ActiveRecord::Migration.change_column(
  535. model.table_name,
  536. attribute.name,
  537. data_type,
  538. options,
  539. )
  540. when 'autocompletion_ajax_external_data_source'
  541. options = {
  542. null: true,
  543. }
  544. ActiveRecord::Migration.change_column(
  545. model.table_name,
  546. attribute.name,
  547. data_type,
  548. options,
  549. )
  550. when %r{^(integer|user_autocompletion|datetime|date)$}, %r{^(boolean|active)$}
  551. ActiveRecord::Migration.change_column(
  552. model.table_name,
  553. attribute.name,
  554. data_type,
  555. default: attribute.data_option[:default],
  556. null: true
  557. )
  558. else
  559. raise "Unknown attribute.data_type '#{attribute.data_type}', can't update attribute"
  560. end
  561. # restart processes
  562. attribute.to_create = false
  563. attribute.to_migrate = false
  564. attribute.to_delete = false
  565. attribute.save!
  566. reset_database_info(model)
  567. execute_db_count += 1
  568. next
  569. end
  570. # create field
  571. case attribute.data_type
  572. when %r{^(input|select|tree_select|richtext|textarea|checkbox)$}
  573. ActiveRecord::Migration.add_column(
  574. model.table_name,
  575. attribute.name,
  576. data_type,
  577. limit: attribute.data_option[:maxlength],
  578. null: true
  579. )
  580. when %r{^(multiselect|multi_tree_select)$}
  581. options = {
  582. null: true,
  583. }
  584. if Rails.application.config.db_column_array
  585. options[:array] = true
  586. end
  587. ActiveRecord::Migration.add_column(
  588. model.table_name,
  589. attribute.name,
  590. data_type,
  591. **options,
  592. )
  593. when 'autocompletion_ajax_external_data_source'
  594. options = {
  595. null: true,
  596. }
  597. ActiveRecord::Migration.add_column(
  598. model.table_name,
  599. attribute.name,
  600. data_type,
  601. **options,
  602. )
  603. when %r{^(integer|user_autocompletion)$}, %r{^(boolean|active)$}, %r{^(datetime|date)$}
  604. ActiveRecord::Migration.add_column(
  605. model.table_name,
  606. attribute.name,
  607. data_type,
  608. default: attribute.data_option[:default],
  609. null: true
  610. )
  611. else
  612. raise "Unknown attribute.data_type '#{attribute.data_type}', can't create attribute"
  613. end
  614. # restart processes
  615. attribute.to_create = false
  616. attribute.to_migrate = false
  617. attribute.to_delete = false
  618. attribute.save!
  619. reset_database_info(model)
  620. execute_db_count += 1
  621. end
  622. # Clear caches so new attribute defaults can be set (#5075)
  623. Rails.cache.clear
  624. # sent maintenance message to clients
  625. if send_event
  626. if execute_db_count.nonzero?
  627. Zammad::Restart.perform
  628. elsif execute_config_count.nonzero?
  629. AppVersion.set(true, 'config_changed')
  630. end
  631. end
  632. true
  633. end
  634. =begin
  635. where attributes are used in conditions
  636. result = ObjectManager::Attribute.attribute_to_references_hash
  637. result = {
  638. ticket.category: {
  639. Trigger: ['abc', 'xyz'],
  640. Overview: ['abc1', 'abc2'],
  641. },
  642. ticket.field_b: {
  643. Trigger: ['abc'],
  644. Overview: ['abc1', 'abc2'],
  645. },
  646. },
  647. =end
  648. def self.attribute_to_references_hash
  649. attribute_list = {}
  650. attribute_to_references_hash_objects
  651. .map { |elem| elem.select(:name, :condition) }
  652. .flatten
  653. .each do |item|
  654. item.condition.each_key do |condition_key|
  655. attribute_list[condition_key] ||= {}
  656. attribute_list[condition_key][item.class.name] ||= []
  657. next if attribute_list[condition_key][item.class.name].include?(item.name)
  658. attribute_list[condition_key][item.class.name].push item.name
  659. end
  660. end
  661. attribute_list
  662. end
  663. =begin
  664. models that may reference attributes
  665. =end
  666. def self.attribute_to_references_hash_objects
  667. Models.all.keys.select { |elem| elem.include? ChecksConditionValidation }
  668. end
  669. =begin
  670. is certain attribute used by triggers, overviews or schedulers
  671. ObjectManager::Attribute.attribute_used_by_references?('Ticket', 'attribute_name')
  672. =end
  673. def self.attribute_used_by_references?(object_name, attribute_name, references = attribute_to_references_hash)
  674. references.each_key do |reference_key|
  675. local_object, local_attribute = reference_key.split('.')
  676. next if local_object != object_name.downcase
  677. next if local_attribute != attribute_name
  678. return true
  679. end
  680. false
  681. end
  682. =begin
  683. is certain attribute used by triggers, overviews or schedulers
  684. result = ObjectManager::Attribute.attribute_used_by_references('Ticket', 'attribute_name')
  685. result = {
  686. Trigger: ['abc', 'xyz'],
  687. Overview: ['abc1', 'abc2'],
  688. }
  689. =end
  690. def self.attribute_used_by_references(object_name, attribute_name, references = attribute_to_references_hash)
  691. result = {}
  692. references.each do |reference_key, relations|
  693. local_object, local_attribute = reference_key.split('.')
  694. next if local_object != object_name.downcase
  695. next if local_attribute != attribute_name
  696. relations.each do |relation, relation_names|
  697. result[relation] ||= []
  698. result[relation].push relation_names.sort
  699. end
  700. break
  701. end
  702. result
  703. end
  704. =begin
  705. is certain attribute used by triggers, overviews or schedulers
  706. text = ObjectManager::Attribute.attribute_used_by_references_humaniced('Ticket', 'attribute_name', references)
  707. =end
  708. def self.attribute_used_by_references_humaniced(object_name, attribute_name, references = nil)
  709. result = if references.present?
  710. ObjectManager::Attribute.attribute_used_by_references(object_name, attribute_name, references)
  711. else
  712. ObjectManager::Attribute.attribute_used_by_references(object_name, attribute_name)
  713. end
  714. not_deletable_reason = ''
  715. result.each do |relation, relation_names|
  716. if not_deletable_reason.present?
  717. not_deletable_reason += '; '
  718. end
  719. not_deletable_reason += "#{relation}: #{relation_names.sort.join(',')}"
  720. end
  721. not_deletable_reason
  722. end
  723. def self.reset_database_info(model)
  724. model.connection.schema_cache.clear!
  725. model.reset_column_information
  726. # rebuild columns cache to reduce the risk of
  727. # race conditions in re-setting it with outdated data
  728. model.columns
  729. end
  730. def check_name
  731. return if !name
  732. if name.match?(%r{.+?_(id|ids)$}i)
  733. errors.add(:name, __("can't be used because *_id and *_ids are not allowed"))
  734. end
  735. if name.match?(%r{\s})
  736. errors.add(:name, __('spaces are not allowed'))
  737. end
  738. if !name.match?(%r{^[a-z0-9_]+$})
  739. errors.add(:name, __("only lowercase letters, numbers, and '_' are allowed"))
  740. end
  741. if !name.match?(%r{[a-z]})
  742. errors.add(:name, __('at least one letter is required'))
  743. end
  744. # do not allow model method names as attributes
  745. reserved_words = %w[destroy true false integer select drop create alter index table varchar blob date datetime timestamp url icon initials avatar permission validate subscribe unsubscribe translate search _type _doc _id id action scope constructor]
  746. if name.match?(%r{^(#{reserved_words.join('|')})$})
  747. errors.add(:name, __('%{name} is a reserved word'), name: name)
  748. end
  749. # fixes issue #2236 - Naming an attribute "attribute" causes ActiveRecord failure
  750. begin
  751. ObjectLookup.by_id(object_lookup_id).constantize.instance_method_already_implemented? name
  752. rescue ActiveRecord::DangerousAttributeError
  753. errors.add(:name, __('%{name} is a reserved word'), name: name)
  754. end
  755. record = object_lookup.name.constantize.new
  756. if new_record? && (record.respond_to?(name.to_sym) || record.attributes.key?(name))
  757. errors.add(:name, __('%{name} already exists'), name: name)
  758. end
  759. if errors.present?
  760. raise ActiveRecord::RecordInvalid, self
  761. end
  762. true
  763. end
  764. def check_editable
  765. return if editable
  766. errors.add(:name, __('attribute is not editable'))
  767. raise ActiveRecord::RecordInvalid, self
  768. end
  769. def local_data_option
  770. send(local_data_attr)
  771. end
  772. def local_data_option=(val)
  773. send(:"#{local_data_attr}=", val)
  774. end
  775. private
  776. # when setting default values for boolean fields,
  777. # favor #nil? tests over ||= (which will overwrite `false`)
  778. def set_base_options
  779. local_data_option[:null] = true if local_data_option[:null].nil?
  780. case data_type
  781. when %r{^((multi|tree_)?select|checkbox)$}
  782. local_data_option[:nulloption] = true if local_data_option[:nulloption].nil?
  783. local_data_option[:maxlength] ||= 255
  784. when 'autocompletion_ajax_external_data_source'
  785. local_data_option[:nulloption] = true if local_data_option[:nulloption].nil?
  786. end
  787. end
  788. def inactive_must_be_unused_by_references
  789. return if !ObjectManager::Attribute.attribute_used_by_references?(object_lookup.name, name)
  790. human_reference = ObjectManager::Attribute.attribute_used_by_references_humaniced(object_lookup.name, name)
  791. text = "#{object_lookup.name}.#{name} is referenced by #{human_reference} and thus cannot be set to inactive!"
  792. # Adding as `base` to prevent `Active` prefix which does not look good on error message shown at the top of the form.
  793. errors.add(:base, text)
  794. end
  795. def data_type_must_not_change
  796. allowable_changes = %w[tree_select multi_tree_select select multiselect input checkbox]
  797. return if !data_type_changed?
  798. return if (data_type_change - allowable_changes).empty?
  799. errors.add(:data_type, __("can't be altered after creation (you can delete the attribute and create another with the desired value)"))
  800. end
  801. def json_field_only_on_postgresql
  802. return if data_type != 'autocompletion_ajax_external_data_source'
  803. return if ActiveRecord::Base.connection_db_config.configuration_hash[:adapter] == 'postgresql'
  804. errors.add(:data_type, __('can only be created on postgresql databases'))
  805. end
  806. def local_data_attr
  807. @local_data_attr ||= to_config ? :data_option_new : :data_option
  808. end
  809. def ensure_multiselect
  810. return if data_type != 'multiselect' && data_type != 'multi_tree_select'
  811. return if data_option && data_option[:multiple] == true
  812. data_option[:multiple] = true
  813. end
  814. end