attribute.rb 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760
  1. class ObjectManager::Attribute < ApplicationModel
  2. include NotifiesClients
  3. self.table_name = 'object_manager_attributes'
  4. belongs_to :object_lookup, class_name: 'ObjectLookup'
  5. validates :name, presence: true
  6. store :screens
  7. store :data_option
  8. store :data_option_new
  9. =begin
  10. list of all attributes
  11. result = ObjectManager::Attribute.list_full
  12. result = [
  13. {
  14. name: 'some name',
  15. display: '...',
  16. }.
  17. ],
  18. =end
  19. def self.list_full
  20. result = ObjectManager::Attribute.all.order('position ASC, name ASC')
  21. attributes = []
  22. assets = {}
  23. result.each { |item|
  24. attribute = item.attributes
  25. attribute[:object] = ObjectLookup.by_id(item.object_lookup_id)
  26. attribute.delete('object_lookup_id')
  27. attributes.push attribute
  28. }
  29. attributes
  30. end
  31. =begin
  32. add a new attribute entry for an object
  33. ObjectManager::Attribute.add(
  34. object: 'Ticket',
  35. name: 'group_id',
  36. display: 'Group',
  37. data_type: 'select',
  38. data_option: {
  39. relation: 'Group',
  40. relation_condition: { access: 'rw' },
  41. multiple: false,
  42. null: true,
  43. translate: false,
  44. },
  45. active: true,
  46. screens: {
  47. create: {
  48. '-all-' => {
  49. required: true,
  50. },
  51. },
  52. edit: {
  53. 'ticket.agent' => {
  54. required: true,
  55. },
  56. },
  57. },
  58. position: 20,
  59. created_by_id: 1,
  60. updated_by_id: 1,
  61. created_at: '2014-06-04 10:00:00',
  62. updated_at: '2014-06-04 10:00:00',
  63. force: true
  64. editable: false,
  65. to_migrate: false,
  66. to_create: false,
  67. to_delete: false,
  68. to_config: false,
  69. )
  70. preserved name are
  71. /(_id|_ids)$/
  72. possible types
  73. # input
  74. data_type: 'input',
  75. data_option: {
  76. default: '',
  77. type: 'text', # text|email|url|tel
  78. maxlength: 200,
  79. null: true,
  80. note: 'some additional comment', # optional
  81. },
  82. # select
  83. data_type: 'select',
  84. data_option: {
  85. default: 'aa',
  86. options: {
  87. 'aa' => 'aa (comment)',
  88. 'bb' => 'bb (comment)',
  89. },
  90. maxlength: 200,
  91. nulloption: true,
  92. null: false,
  93. multiple: false, # currently only "false" supported
  94. translate: true, # optional
  95. note: 'some additional comment', # optional
  96. },
  97. # checkbox
  98. data_type: 'checkbox',
  99. data_option: {
  100. default: 'aa',
  101. options: {
  102. 'aa' => 'aa (comment)',
  103. 'bb' => 'bb (comment)',
  104. },
  105. null: false,
  106. translate: true, # optional
  107. note: 'some additional comment', # optional
  108. },
  109. # integer
  110. data_type: 'integer',
  111. data_option: {
  112. default: 5,
  113. min: 15,
  114. max: 999,
  115. null: false,
  116. note: 'some additional comment', # optional
  117. },
  118. # boolean
  119. data_type: 'boolean',
  120. data_option: {
  121. default: true,
  122. options: {
  123. true => 'aa',
  124. false => 'bb',
  125. },
  126. null: false,
  127. translate: true, # optional
  128. note: 'some additional comment', # optional
  129. },
  130. # datetime
  131. data_type: 'datetime',
  132. data_option: {
  133. future: true, # true|false
  134. past: true, # true|false
  135. diff: 12, # in hours
  136. null: false,
  137. note: 'some additional comment', # optional
  138. },
  139. # date
  140. data_type: 'date',
  141. data_option: {
  142. future: true, # true|false
  143. past: true, # true|false
  144. diff: 15, # in days
  145. null: false,
  146. note: 'some additional comment', # optional
  147. },
  148. # textarea
  149. data_type: 'textarea',
  150. data_option: {
  151. default: '',
  152. rows: 15,
  153. null: false,
  154. note: 'some additional comment', # optional
  155. },
  156. # richtext
  157. data_type: 'richtext',
  158. data_option: {
  159. default: '',
  160. null: false,
  161. note: 'some additional comment', # optional
  162. },
  163. =end
  164. def self.add(data)
  165. force = data[:force]
  166. data.delete(:force)
  167. # lookups
  168. if data[:object]
  169. data[:object_lookup_id] = ObjectLookup.by_name(data[:object])
  170. end
  171. data.delete(:object)
  172. data[:name].downcase!
  173. # check new entry - is needed
  174. record = ObjectManager::Attribute.find_by(
  175. object_lookup_id: data[:object_lookup_id],
  176. name: data[:name],
  177. )
  178. if record
  179. # do not allow to overwrite certain attributes
  180. if !force
  181. data.delete(:editable)
  182. data.delete(:to_create)
  183. data.delete(:to_migrate)
  184. data.delete(:to_delete)
  185. data.delete(:to_config)
  186. end
  187. # if data_option has changed, store it for next migration
  188. if !force
  189. if record[:data_option] != data[:data_option]
  190. # do we need a database migration?
  191. if record[:data_option][:maxlength] && data[:data_option][:maxlength] && record[:data_option][:maxlength].to_s != data[:data_option][:maxlength].to_s
  192. data[:to_migrate] = true
  193. end
  194. record[:data_option_new] = data[:data_option]
  195. data.delete(:data_option)
  196. data[:to_config] = true
  197. end
  198. end
  199. # update attributes
  200. data.each { |key, value|
  201. record[key.to_sym] = value
  202. }
  203. # check editable & name
  204. if !force
  205. record.check_editable
  206. record.check_name
  207. end
  208. record.check_datatype
  209. record.save!
  210. return record
  211. end
  212. # do not allow to overwrite certain attributes
  213. if !force
  214. data[:editable] = true
  215. data[:to_create] = true
  216. data[:to_migrate] = true
  217. data[:to_delete] = false
  218. end
  219. record = ObjectManager::Attribute.new(data)
  220. # check editable & name
  221. if !force
  222. record.check_editable
  223. record.check_name
  224. end
  225. record.check_datatype
  226. record.save!
  227. record
  228. end
  229. =begin
  230. remove attribute entry for an object
  231. ObjectManager::Attribute.remove(
  232. object: 'Ticket',
  233. name: 'group_id',
  234. )
  235. use "force: true" to delete also not editable fields
  236. =end
  237. def self.remove(data)
  238. # lookups
  239. if data[:object]
  240. data[:object_lookup_id] = ObjectLookup.by_name(data[:object])
  241. end
  242. data[:name].downcase!
  243. # check newest entry - is needed
  244. record = ObjectManager::Attribute.find_by(
  245. object_lookup_id: data[:object_lookup_id],
  246. name: data[:name],
  247. )
  248. if !record
  249. raise "ERROR: No such field #{data[:object]}.#{data[:name]}"
  250. end
  251. if !data[:force] && !record.editable
  252. raise "ERROR: #{data[:object]}.#{data[:name]} can't be removed!"
  253. end
  254. # if record is to create, just destroy it
  255. if record.to_create
  256. record.destroy
  257. return true
  258. end
  259. record.to_delete = true
  260. record.save
  261. end
  262. =begin
  263. get the attribute model based on object and name
  264. attribute = ObjectManager::Attribute.get(
  265. object: 'Ticket',
  266. name: 'group_id',
  267. )
  268. =end
  269. def self.get(data)
  270. # lookups
  271. if data[:object]
  272. data[:object_lookup_id] = ObjectLookup.by_name(data[:object])
  273. end
  274. data[:name].downcase!
  275. ObjectManager::Attribute.find_by(
  276. object_lookup_id: data[:object_lookup_id],
  277. name: data[:name],
  278. )
  279. end
  280. =begin
  281. get user based list of used object attributes
  282. attribute_list = ObjectManager::Attribute.by_object('Ticket', user)
  283. returns:
  284. [
  285. { name: 'api_key', display: 'API KEY', tag: 'input', null: true, edit: true, maxlength: 32 },
  286. { name: 'api_ip_regexp', display: 'API IP RegExp', tag: 'input', null: true, edit: true },
  287. { name: 'api_ip_max', display: 'API IP Max', tag: 'input', null: true, edit: true },
  288. ]
  289. =end
  290. def self.by_object(object, user)
  291. # lookups
  292. if object
  293. object_lookup_id = ObjectLookup.by_name(object)
  294. end
  295. # get attributes in right order
  296. result = ObjectManager::Attribute.where(
  297. object_lookup_id: object_lookup_id,
  298. active: true,
  299. to_create: false,
  300. to_delete: false,
  301. ).order('position ASC, name ASC')
  302. attributes = []
  303. result.each { |item|
  304. data = {
  305. name: item.name,
  306. display: item.display,
  307. tag: item.data_type,
  308. #:null => item.null,
  309. }
  310. if item.data_option[:permission] && item.data_option[:permission].any?
  311. next if !user
  312. hint = false
  313. item.data_option[:permission].each { |permission|
  314. next if !user.permissions?(permission)
  315. hint = true
  316. break
  317. }
  318. next if !hint
  319. end
  320. if item.screens
  321. data[:screen] = {}
  322. item.screens.each { |screen, permission_options|
  323. data[:screen][screen] = {}
  324. permission_options.each { |permission, options|
  325. if permission == '-all-'
  326. data[:screen][screen] = options
  327. elsif user && user.permissions?(permission)
  328. data[:screen][screen] = options
  329. end
  330. }
  331. }
  332. end
  333. if item.data_option
  334. data = data.merge(item.data_option.symbolize_keys)
  335. end
  336. attributes.push data
  337. }
  338. attributes
  339. end
  340. =begin
  341. get user based list of object attributes as hash
  342. attribute_list = ObjectManager::Attribute.by_object_as_hash('Ticket', user)
  343. returns:
  344. {
  345. 'api_key' => { name: 'api_key', display: 'API KEY', tag: 'input', null: true, edit: true, maxlength: 32 },
  346. 'api_ip_regexp' => { name: 'api_ip_regexp', display: 'API IP RegExp', tag: 'input', null: true, edit: true },
  347. 'api_ip_max' => { name: 'api_ip_max', display: 'API IP Max', tag: 'input', null: true, edit: true },
  348. }
  349. =end
  350. def self.by_object_as_hash(object, user)
  351. list = by_object(object, user)
  352. hash = {}
  353. list.each { |item|
  354. hash[ item[:name] ] = item
  355. }
  356. hash
  357. end
  358. =begin
  359. discard migration changes
  360. ObjectManager::Attribute.discard_changes
  361. returns
  362. true|false
  363. =end
  364. def self.discard_changes
  365. ObjectManager::Attribute.where('to_create = ?', true).each(&:destroy)
  366. ObjectManager::Attribute.where('to_delete = ? OR to_config = ?', true, true).each { |attribute|
  367. attribute.to_migrate = false
  368. attribute.to_delete = false
  369. attribute.to_config = false
  370. attribute.data_option_new = {}
  371. attribute.save
  372. }
  373. true
  374. end
  375. =begin
  376. check if we have pending migrations of attributes
  377. ObjectManager::Attribute.pending_migration?
  378. returns
  379. true|false
  380. =end
  381. def self.pending_migration?
  382. return false if migrations.empty?
  383. true
  384. end
  385. =begin
  386. get list of pending attributes migrations
  387. ObjectManager::Attribute.migrations
  388. returns
  389. [record1, record2, ...]
  390. =end
  391. def self.migrations
  392. ObjectManager::Attribute.where('to_create = ? OR to_migrate = ? OR to_delete = ? OR to_config = ?', true, true, true, true)
  393. end
  394. =begin
  395. start migration of pending attribute migrations
  396. ObjectManager::Attribute.migration_execute
  397. returns
  398. [record1, record2, ...]
  399. to send no browser reload event, pass false
  400. ObjectManager::Attribute.migration_execute(false)
  401. =end
  402. def self.migration_execute(send_event = true)
  403. # check if field already exists
  404. execute_db_count = 0
  405. execute_config_count = 0
  406. migrations.each { |attribute|
  407. model = Kernel.const_get(attribute.object_lookup.name)
  408. # remove field
  409. if attribute.to_delete
  410. if model.column_names.include?(attribute.name)
  411. ActiveRecord::Migration.remove_column model.table_name, attribute.name
  412. reset_database_info(model)
  413. end
  414. execute_db_count += 1
  415. attribute.destroy
  416. next
  417. end
  418. # config changes
  419. if attribute.to_config
  420. execute_config_count += 1
  421. attribute.data_option = attribute.data_option_new
  422. attribute.data_option_new = {}
  423. attribute.to_config = false
  424. attribute.save!
  425. next if !attribute.to_create && !attribute.to_migrate && !attribute.to_delete
  426. end
  427. data_type = nil
  428. if attribute.data_type =~ /^input|select|richtext|textarea|checkbox$/
  429. data_type = :string
  430. elsif attribute.data_type =~ /^integer$/
  431. data_type = :integer
  432. elsif attribute.data_type =~ /^boolean|active$/
  433. data_type = :boolean
  434. elsif attribute.data_type =~ /^datetime$/
  435. data_type = :datetime
  436. elsif attribute.data_type =~ /^date$/
  437. data_type = :date
  438. end
  439. # change field
  440. if model.column_names.include?(attribute.name)
  441. if attribute.data_type =~ /^input|select|richtext|textarea|checkbox$/
  442. ActiveRecord::Migration.change_column(
  443. model.table_name,
  444. attribute.name,
  445. data_type,
  446. limit: attribute.data_option[:maxlength],
  447. null: true
  448. )
  449. elsif attribute.data_type =~ /^integer|datetime|date$/
  450. ActiveRecord::Migration.change_column(
  451. model.table_name,
  452. attribute.name,
  453. data_type,
  454. default: attribute.data_option[:default],
  455. null: true
  456. )
  457. elsif attribute.data_type =~ /^boolean|active$/
  458. ActiveRecord::Migration.change_column(
  459. model.table_name,
  460. attribute.name,
  461. data_type,
  462. default: attribute.data_option[:default],
  463. null: true
  464. )
  465. else
  466. raise "Unknown attribute.data_type '#{attribute.data_type}', can't update attribute"
  467. end
  468. # restart processes
  469. attribute.to_migrate = false
  470. attribute.save!
  471. reset_database_info(model)
  472. execute_db_count += 1
  473. next
  474. end
  475. # create field
  476. if attribute.data_type =~ /^input|select|richtext|textarea|checkbox$/
  477. ActiveRecord::Migration.add_column(
  478. model.table_name,
  479. attribute.name,
  480. data_type,
  481. limit: attribute.data_option[:maxlength],
  482. null: true
  483. )
  484. elsif attribute.data_type =~ /^integer$/
  485. ActiveRecord::Migration.add_column(
  486. model.table_name,
  487. attribute.name,
  488. data_type,
  489. default: attribute.data_option[:default],
  490. null: true
  491. )
  492. elsif attribute.data_type =~ /^boolean|active$/
  493. ActiveRecord::Migration.add_column(
  494. model.table_name,
  495. attribute.name,
  496. data_type,
  497. default: attribute.data_option[:default],
  498. null: true
  499. )
  500. elsif attribute.data_type =~ /^datetime|date$/
  501. ActiveRecord::Migration.add_column(
  502. model.table_name,
  503. attribute.name,
  504. data_type,
  505. default: attribute.data_option[:default],
  506. null: true
  507. )
  508. else
  509. raise "Unknown attribute.data_type '#{attribute.data_type}', can't create attribute"
  510. end
  511. # restart processes
  512. attribute.to_create = false
  513. attribute.to_migrate = false
  514. attribute.to_delete = false
  515. attribute.save!
  516. reset_database_info(model)
  517. execute_db_count += 1
  518. }
  519. # sent maintenance message to clients
  520. if send_event
  521. if execute_db_count.nonzero?
  522. if ENV['APP_RESTART_CMD']
  523. AppVersion.set(true, 'restart_auto')
  524. sleep 4
  525. Delayed::Job.enqueue(Observer::AppVersionRestartJob.new(ENV['APP_RESTART_CMD']))
  526. else
  527. AppVersion.set(true, 'restart_manual')
  528. end
  529. elsif execute_config_count.nonzero?
  530. AppVersion.set(true, 'config_changed')
  531. end
  532. end
  533. true
  534. end
  535. def self.reset_database_info(model)
  536. model.connection.schema_cache.clear!
  537. model.reset_column_information
  538. end
  539. def check_name
  540. return if !name
  541. if name =~ /_(id|ids)$/i || name =~ /^id$/i
  542. raise 'Name can\'t get used, *_id and *_ids are not allowed'
  543. elsif name =~ /\s/
  544. raise 'Spaces in name are not allowed'
  545. elsif name !~ /^[a-z0-9_]+$/
  546. raise 'Only letters from a-z, numbers from 0-9, and _ are allowed'
  547. elsif name !~ /[a-z]/
  548. raise 'At least one letters is needed'
  549. elsif name =~ /^(destroy|true|false|integer|select|drop|create|alter|index|table|varchar|blob|date|datetime|timestamp)$/
  550. raise "#{name} is a reserved word, please choose a different one"
  551. # do not allow model method names as attributes
  552. else
  553. model = Kernel.const_get(object_lookup.name)
  554. record = model.new
  555. if record.respond_to?(name.to_sym) && !record.attributes.key?(name)
  556. raise "#{name} is a reserved word, please choose a different one"
  557. end
  558. end
  559. true
  560. end
  561. def check_editable
  562. return if editable
  563. raise 'Attribute not editable!'
  564. end
  565. def check_datatype
  566. if !data_type
  567. raise 'Need data_type param'
  568. end
  569. if data_type !~ /^(input|user_autocompletion|checkbox|select|datetime|date|tag|richtext|textarea|integer|autocompletion_ajax|boolean|user_permission|active)$/
  570. raise "Invalid data_type param '#{data_type}'"
  571. end
  572. if !data_option
  573. raise 'Need data_type param'
  574. end
  575. if data_option[:null].nil?
  576. raise 'Need data_option[:null] param with true or false'
  577. end
  578. # validate data_option
  579. if data_type == 'input'
  580. raise 'Need data_option[:type] param' if !data_option[:type]
  581. raise "Invalid data_option[:type] param '#{data_option[:type]}'" if data_option[:type] !~ /^(text|password|tel|fax|email|url)$/
  582. raise 'Need data_option[:maxlength] param' if !data_option[:maxlength]
  583. raise "Invalid data_option[:maxlength] param #{data_option[:maxlength]}" if data_option[:maxlength].to_s !~ /^\d+?$/
  584. end
  585. if data_type == 'richtext'
  586. raise 'Need data_option[:maxlength] param' if !data_option[:maxlength]
  587. raise "Invalid data_option[:maxlength] param #{data_option[:maxlength]}" if data_option[:maxlength].to_s !~ /^\d+?$/
  588. end
  589. if data_type == 'integer'
  590. [:min, :max].each { |item|
  591. raise "Need data_option[#{item.inspect}] param" if !data_option[item]
  592. raise "Invalid data_option[#{item.inspect}] param #{data_option[item]}" if data_option[item].to_s !~ /^\d+?$/
  593. }
  594. end
  595. if data_type == 'select' || data_type == 'checkbox'
  596. raise 'Need data_option[:default] param' if !data_option.key?(:default)
  597. raise 'Invalid data_option[:options] or data_option[:relation] param' if data_option[:options].nil? && data_option[:relation].nil?
  598. if !data_option.key?(:maxlength)
  599. data_option[:maxlength] = 255
  600. end
  601. if !data_option.key?(:nulloption)
  602. data_option[:nulloption] = true
  603. end
  604. end
  605. if data_type == 'boolean'
  606. raise 'Need data_option[:default] param true|false|undefined' if !data_option.key?(:default)
  607. raise 'Invalid data_option[:options] param' if data_option[:options].nil?
  608. end
  609. if data_type == 'datetime'
  610. raise 'Need data_option[:future] param true|false' if data_option[:future].nil?
  611. raise 'Need data_option[:past] param true|false' if data_option[:past].nil?
  612. raise 'Need data_option[:diff] param in hours' if data_option[:diff].nil?
  613. end
  614. if data_type == 'date'
  615. raise 'Need data_option[:future] param true|false' if data_option[:future].nil?
  616. raise 'Need data_option[:past] param true|false' if data_option[:past].nil?
  617. raise 'Need data_option[:diff] param in days' if data_option[:diff].nil?
  618. end
  619. end
  620. end