attribute.rb 27 KB

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