attribute.rb 16 KB

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