attribute.rb 27 KB

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