application_model.rb 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601
  1. # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
  2. class ApplicationModel < ActiveRecord::Base
  3. include ApplicationModel::Assets
  4. include ApplicationModel::HistoryLogBase
  5. include ApplicationModel::ActivityStreamBase
  6. include ApplicationModel::SearchIndexBase
  7. self.abstract_class = true
  8. before_create :check_attributes_protected, :check_limits, :cache_delete, :fill_up_user_create
  9. before_update :check_limits, :fill_up_user_update
  10. before_destroy :destroy_dependencies
  11. after_create :cache_delete
  12. after_update :cache_delete
  13. after_touch :cache_delete
  14. after_destroy :cache_delete
  15. after_create :attachments_buffer_check
  16. after_update :attachments_buffer_check
  17. after_create :activity_stream_create
  18. after_update :activity_stream_update
  19. before_destroy :activity_stream_destroy
  20. after_create :history_create
  21. after_update :history_update
  22. after_destroy :history_destroy
  23. after_create :search_index_update
  24. after_update :search_index_update
  25. after_touch :search_index_update
  26. after_destroy :search_index_destroy
  27. before_destroy :recent_view_destroy
  28. # create instance accessor
  29. class << self
  30. attr_accessor :activity_stream_support_config, :history_support_config, :search_index_support_config, :attributes_with_associations_support_config
  31. end
  32. attr_accessor :history_changes_last_done
  33. def check_attributes_protected
  34. import_class_list = ['Ticket', 'Ticket::Article', 'History', 'Ticket::State', 'Ticket::StateType', 'Ticket::Priority', 'Group', 'User', 'Role' ]
  35. # do noting, use id as it is
  36. return if !Setting.get('system_init_done')
  37. return if Setting.get('import_mode') && import_class_list.include?(self.class.to_s)
  38. self[:id] = nil
  39. end
  40. =begin
  41. remove all not used model attributes of params
  42. result = Model.param_cleanup(params)
  43. for object creation, ignore id's
  44. result = Model.param_cleanup(params, true)
  45. returns
  46. result = params # params with valid attributes of model
  47. =end
  48. def self.param_cleanup(params, new_object = false)
  49. if params.respond_to?('permit!')
  50. params.permit!
  51. end
  52. if params.nil?
  53. raise ArgumentError, "No params for #{self}!"
  54. end
  55. data = {}
  56. params.each { |key, value|
  57. data[key.to_sym] = value
  58. }
  59. # ignore id for new objects
  60. if new_object && params[:id]
  61. data.delete(:id)
  62. end
  63. # only use object attributes
  64. clean_params = {}
  65. new.attributes.each { |attribute, _value|
  66. next if !data.key?(attribute.to_sym)
  67. clean_params[attribute.to_sym] = data[attribute.to_sym]
  68. }
  69. # we do want to set this via database
  70. param_validation(clean_params)
  71. end
  72. =begin
  73. set relations of model based on params
  74. model = Model.find(1)
  75. result = model.param_set_associations(params)
  76. returns
  77. result = true|false
  78. =end
  79. def param_set_associations(params)
  80. # set relations by id/verify if ref exists
  81. self.class.reflect_on_all_associations.map { |assoc|
  82. real_ids = assoc.name.to_s[0, assoc.name.to_s.length - 1] + '_ids'
  83. real_ids = real_ids.to_sym
  84. next if !params.key?(real_ids)
  85. list_of_items = params[real_ids]
  86. if params[real_ids].class != Array
  87. list_of_items = [ params[real_ids] ]
  88. end
  89. list = []
  90. list_of_items.each { |item_id|
  91. next if !item_id
  92. lookup = assoc.klass.lookup(id: item_id)
  93. # complain if we found no reference
  94. if !lookup
  95. raise ArgumentError, "No value found for '#{assoc.name}' with id #{item_id.inspect}"
  96. end
  97. list.push item_id
  98. }
  99. #p "SEND #{real_ids} = #{list.inspect}"
  100. send("#{real_ids}=", list)
  101. }
  102. # set relations by name/lookup
  103. self.class.reflect_on_all_associations.map { |assoc|
  104. real_ids = assoc.name.to_s[0, assoc.name.to_s.length - 1] + '_ids'
  105. next if !respond_to?(real_ids)
  106. real_values = assoc.name.to_s[0, assoc.name.to_s.length - 1] + 's'
  107. real_values = real_values.to_sym
  108. next if !respond_to?(real_values)
  109. next if !params[real_values]
  110. next if params[real_values].class != Array
  111. list = []
  112. class_object = assoc.klass
  113. params[real_values].each { |value|
  114. lookup = nil
  115. if class_object == User
  116. if !lookup
  117. lookup = class_object.lookup(login: value)
  118. end
  119. if !lookup
  120. lookup = class_object.lookup(email: value)
  121. end
  122. else
  123. lookup = class_object.lookup(name: value)
  124. end
  125. # complain if we found no reference
  126. if !lookup
  127. raise ArgumentError, "No lookup value found for '#{assoc.name}': #{value.inspect}"
  128. end
  129. list.push lookup.id
  130. }
  131. #p "SEND #{real_ids} = #{list.inspect}"
  132. send("#{real_ids}=", list)
  133. }
  134. end
  135. =begin
  136. get relations of model based on params
  137. model = Model.find(1)
  138. attributes = model.attributes_with_associations
  139. returns
  140. hash with attributes and association ids
  141. =end
  142. def attributes_with_associations
  143. key = "#{self.class}::aws::#{id}"
  144. cache = Cache.get(key)
  145. return cache if cache
  146. # get relations
  147. attributes = self.attributes
  148. self.class.reflect_on_all_associations.map { |assoc|
  149. real_ids = assoc.name.to_s[0, assoc.name.to_s.length - 1] + '_ids'
  150. next if self.class.attributes_with_associations_support_config && self.class.attributes_with_associations_support_config[:ignore][real_ids.to_sym] == true
  151. next if !respond_to?(real_ids)
  152. attributes[real_ids] = send(real_ids)
  153. }
  154. Cache.write(key, attributes)
  155. attributes
  156. end
  157. =begin
  158. get relation name of model based on params
  159. model = Model.find(1)
  160. attributes = model.attributes_with_relation_names
  161. returns
  162. hash with attributes, association ids, association names and relation name
  163. =end
  164. def attributes_with_relation_names
  165. # get relations
  166. attributes = attributes_with_associations
  167. self.class.reflect_on_all_associations.map { |assoc|
  168. next if !respond_to?(assoc.name)
  169. ref = send(assoc.name)
  170. next if !ref
  171. if ref.respond_to?(:first)
  172. attributes[assoc.name.to_s] = []
  173. ref.each { |item|
  174. if item[:login]
  175. attributes[assoc.name.to_s].push item[:login]
  176. next
  177. end
  178. next if !item[:name]
  179. attributes[assoc.name.to_s].push item[:name]
  180. }
  181. if ref.count.positive? && attributes[assoc.name.to_s].empty?
  182. attributes.delete(assoc.name.to_s)
  183. end
  184. next
  185. end
  186. if ref[:login]
  187. attributes[assoc.name.to_s] = ref[:login]
  188. next
  189. end
  190. next if !ref[:name]
  191. attributes[assoc.name.to_s] = ref[:name]
  192. }
  193. # fill created_by/updated_by
  194. {
  195. 'created_by_id' => 'created_by',
  196. 'updated_by_id' => 'updated_by',
  197. }.each { |source, destination|
  198. next if !attributes[source]
  199. user = User.lookup(id: attributes[source])
  200. next if !user
  201. attributes[destination] = user.login
  202. }
  203. # remove forbitten attributes
  204. %w(password token tokens token_ids).each { |item|
  205. attributes.delete(item)
  206. }
  207. attributes
  208. end
  209. =begin
  210. remove all not used params of object (per default :updated_at, :created_at, :updated_by_id and :created_by_id)
  211. result = Model.param_validation(params)
  212. returns
  213. result = params # params without listed attributes
  214. =end
  215. def self.param_validation(data)
  216. # we do want to set this via database
  217. [:action, :controller, :updated_at, :created_at, :updated_by_id, :created_by_id, :updated_by, :created_by].each { |key|
  218. data.delete(key)
  219. }
  220. data
  221. end
  222. =begin
  223. do name/login/email based lookup for associations
  224. params = {
  225. login: 'some login',
  226. firstname: 'some firstname',
  227. lastname: 'some lastname',
  228. email: 'some email',
  229. organization: 'some organization',
  230. roles: ['Agent', 'Admin'],
  231. }
  232. attributes = Model.param_association_lookup(params)
  233. returns
  234. attributes = params # params with possible lookups
  235. attributes = {
  236. login: 'some login',
  237. firstname: 'some firstname',
  238. lastname: 'some lastname',
  239. email: 'some email',
  240. organization_id: 123,
  241. role_ids: [2,1],
  242. }
  243. =end
  244. def self.param_association_lookup(params)
  245. data = {}
  246. params.each { |key, value|
  247. data[key.to_sym] = value
  248. }
  249. data.symbolize_keys!
  250. available_attributes = attribute_names
  251. reflect_on_all_associations.map { |assoc|
  252. value = data[assoc.name.to_sym]
  253. next if !value # next if we do not have a value
  254. ref_name = "#{assoc.name}_id"
  255. # handle _id values
  256. if available_attributes.include?(ref_name) # if we do have an _id attribute
  257. next if data[ref_name.to_sym] # next if we have already the _id filled
  258. # get association class and do lookup
  259. class_object = assoc.klass
  260. lookup = nil
  261. if class_object == User
  262. if value.class == String
  263. if !lookup
  264. lookup = class_object.lookup(login: value)
  265. end
  266. if !lookup
  267. lookup = class_object.lookup(email: value)
  268. end
  269. else
  270. raise ArgumentError, "String is needed as ref value #{value.inspect} for '#{assoc.name}'"
  271. end
  272. else
  273. lookup = class_object.lookup(name: value)
  274. end
  275. # complain if we found no reference
  276. if !lookup
  277. raise ArgumentError, "No lookup value found for '#{assoc.name}': #{value.inspect}"
  278. end
  279. # release data value
  280. data.delete(assoc.name.to_sym)
  281. # remember id reference
  282. data[ref_name.to_sym] = lookup.id
  283. next
  284. end
  285. next if value.class != Array
  286. next if value.empty?
  287. next if value[0].class != String
  288. # handle _ids values
  289. ref_names = "#{assoc.name[0, assoc.name.length - 1]}_ids"
  290. generic_object_tmp = new
  291. next unless generic_object_tmp.respond_to?(ref_names) # if we do have an _ids attribute
  292. next if data[ref_names.to_sym] # next if we have already the _ids filled
  293. # get association class and do lookup
  294. class_object = assoc.klass
  295. lookup_ids = []
  296. value.each { |item|
  297. lookup = nil
  298. if class_object == User
  299. if item.class == String
  300. if !lookup
  301. lookup = class_object.lookup(login: item)
  302. end
  303. if !lookup
  304. lookup = class_object.lookup(email: item)
  305. end
  306. else
  307. raise ArgumentError, "String is needed in array ref as ref value #{value.inspect} for '#{assoc.name}'"
  308. end
  309. else
  310. lookup = class_object.lookup(name: item)
  311. end
  312. # complain if we found no reference
  313. if !lookup
  314. raise ArgumentError, "No lookup value found for '#{assoc.name}': #{item.inspect}"
  315. end
  316. lookup_ids.push lookup.id
  317. }
  318. # release data value
  319. data.delete(assoc.name.to_sym)
  320. # remember id reference
  321. data[ref_names.to_sym] = lookup_ids
  322. }
  323. data
  324. end
  325. =begin
  326. set created_by_id & updated_by_id if not given based on UserInfo (current session)
  327. Used as before_create callback, no own use needed
  328. result = Model.fill_up_user_create(params)
  329. returns
  330. result = params # params with updated_by_id & created_by_id if not given based on UserInfo (current session)
  331. =end
  332. def fill_up_user_create
  333. if self.class.column_names.include? 'updated_by_id'
  334. if UserInfo.current_user_id
  335. if updated_by_id && updated_by_id != UserInfo.current_user_id
  336. logger.info "NOTICE create - self.updated_by_id is different: #{updated_by_id}/#{UserInfo.current_user_id}"
  337. end
  338. self.updated_by_id = UserInfo.current_user_id
  339. end
  340. end
  341. return if !self.class.column_names.include? 'created_by_id'
  342. return if !UserInfo.current_user_id
  343. if created_by_id && created_by_id != UserInfo.current_user_id
  344. logger.info "NOTICE create - self.created_by_id is different: #{created_by_id}/#{UserInfo.current_user_id}"
  345. end
  346. self.created_by_id = UserInfo.current_user_id
  347. end
  348. =begin
  349. set updated_by_id if not given based on UserInfo (current session)
  350. Used as before_update callback, no own use needed
  351. result = Model.fill_up_user_update(params)
  352. returns
  353. result = params # params with updated_by_id & created_by_id if not given based on UserInfo (current session)
  354. =end
  355. def fill_up_user_update
  356. return if !self.class.column_names.include? 'updated_by_id'
  357. return if !UserInfo.current_user_id
  358. self.updated_by_id = UserInfo.current_user_id
  359. end
  360. def cache_update(o)
  361. cache_delete if respond_to?('cache_delete')
  362. o.cache_delete if o.respond_to?('cache_delete')
  363. end
  364. def cache_delete
  365. keys = []
  366. # delete by id caches
  367. keys.push "#{self.class}::#{id}"
  368. # delete by id with attributes_with_associations caches
  369. keys.push "#{self.class}::aws::#{id}"
  370. # delete by name caches
  371. if self[:name]
  372. keys.push "#{self.class}::#{name}"
  373. end
  374. # delete by login caches
  375. if self[:login]
  376. keys.push "#{self.class}::#{login}"
  377. end
  378. keys.each { |key|
  379. Cache.delete(key)
  380. }
  381. # delete old name / login caches
  382. if changed?
  383. if changes.key?('name')
  384. name = changes['name'][0]
  385. key = "#{self.class}::#{name}"
  386. Cache.delete(key)
  387. end
  388. if changes.key?('login')
  389. name = changes['login'][0]
  390. key = "#{self.class}::#{name}"
  391. Cache.delete(key)
  392. end
  393. end
  394. end
  395. def self.cache_set(data_id, data)
  396. key = "#{self}::#{data_id}"
  397. Cache.write(key, data)
  398. end
  399. def self.cache_get(data_id)
  400. key = "#{self}::#{data_id}"
  401. Cache.get(key)
  402. end
  403. =begin
  404. generate uniq name (will check name of model and generates _1 sequenze)
  405. Used as before_update callback, no own use needed
  406. name = Model.genrate_uniq_name('some name')
  407. returns
  408. result = 'some name_X'
  409. =end
  410. def self.genrate_uniq_name(name)
  411. return name if !find_by(name: name)
  412. (1..100).each { |counter|
  413. name = "#{name}_#{counter}"
  414. exists = find_by(name: name)
  415. next if exists
  416. break
  417. }
  418. name
  419. end
  420. =begin
  421. lookup model from cache (if exists) or retrieve it from db, id, name, login or email possible
  422. result = Model.lookup(id: 123)
  423. result = Model.lookup(name: 'some name')
  424. result = Model.lookup(login: 'some login')
  425. result = Model.lookup(email: 'some login')
  426. returns
  427. result = model # with all attributes
  428. =end
  429. def self.lookup(data)
  430. if data[:id]
  431. cache = cache_get(data[:id])
  432. return cache if cache
  433. record = find_by(id: data[:id])
  434. cache_set(data[:id], record)
  435. return record
  436. elsif data[:name]
  437. cache = cache_get(data[:name])
  438. return cache if cache
  439. # do lookup with == to handle case insensitive databases
  440. records = if Rails.application.config.db_case_sensitive
  441. where('LOWER(name) = LOWER(?)', data[:name])
  442. else
  443. where(name: data[:name])
  444. end
  445. records.each { |loop_record|
  446. if loop_record.name == data[:name]
  447. cache_set(data[:name], loop_record)
  448. return loop_record
  449. end
  450. }
  451. return
  452. elsif data[:login]
  453. cache = cache_get(data[:login])
  454. return cache if cache
  455. # do lookup with == to handle case insensitive databases
  456. records = if Rails.application.config.db_case_sensitive
  457. where('LOWER(login) = LOWER(?)', data[:login])
  458. else
  459. where(login: data[:login])
  460. end
  461. records.each { |loop_record|
  462. if loop_record.login == data[:login]
  463. cache_set(data[:login], loop_record)
  464. return loop_record
  465. end
  466. }
  467. return
  468. elsif data[:email]
  469. cache = cache_get(data[:email])
  470. return cache if cache
  471. # do lookup with == to handle case insensitive databases
  472. records = if Rails.application.config.db_case_sensitive
  473. where('LOWER(email) = LOWER(?)', data[:email])
  474. else
  475. where(email: data[:email])
  476. end
  477. records.each { |loop_record|
  478. if loop_record.email == data[:email]
  479. cache_set(data[:email], loop_record)
  480. return loop_record
  481. end
  482. }
  483. return
  484. end
  485. raise ArgumentError, 'Need name, id, login or email for lookup()'
  486. end
  487. =begin
  488. create model if not exists (check exists based on id, name, login, email or locale)
  489. result = Model.create_if_not_exists(attributes)
  490. returns
  491. result = model # with all attributes
  492. =end
  493. def self.create_if_not_exists(data)
  494. if data[:id]
  495. record = find_by(id: data[:id])
  496. return record if record
  497. elsif data[:name]
  498. # do lookup with == to handle case insensitive databases
  499. records = if Rails.application.config.db_case_sensitive
  500. where('LOWER(name) = LOWER(?)', data[:name])
  501. else
  502. where(name: data[:name])
  503. end
  504. records.each { |loop_record|
  505. return loop_record if loop_record.name == data[:name]
  506. }
  507. elsif data[:login]
  508. # do lookup with == to handle case insensitive databases
  509. records = if Rails.application.config.db_case_sensitive
  510. where('LOWER(login) = LOWER(?)', data[:login])
  511. else
  512. where(login: data[:login])
  513. end
  514. records.each { |loop_record|
  515. return loop_record if loop_record.login == data[:login]
  516. }
  517. elsif data[:email]
  518. # do lookup with == to handle case insensitive databases
  519. records = if Rails.application.config.db_case_sensitive
  520. where('LOWER(email) = LOWER(?)', data[:email])
  521. else
  522. where(email: data[:email])
  523. end
  524. records.each { |loop_record|
  525. return loop_record if loop_record.email == data[:email]
  526. }
  527. elsif data[:locale] && data[:source]
  528. # do lookup with == to handle case insensitive databases
  529. records = if Rails.application.config.db_case_sensitive
  530. where('LOWER(locale) = LOWER(?) AND LOWER(source) = LOWER(?)', data[:locale], data[:source])
  531. else
  532. where(locale: data[:locale], source: data[:source])
  533. end
  534. records.each { |loop_record|
  535. return loop_record if loop_record.source == data[:source]
  536. }
  537. end
  538. create(data)
  539. end
  540. =begin
  541. Model.create_if_not_exists with ref lookups
  542. result = Model.create_if_not_exists_with_ref(attributes)
  543. returns
  544. result = model # with all attributes
  545. =end
  546. def self.create_if_not_exists_with_ref(data)
  547. data = param_association_lookup(data)
  548. create_or_update(data)
  549. end
  550. =begin
  551. create or update model (check exists based on id, name, login, email or locale)
  552. result = Model.create_or_update(attributes)
  553. returns
  554. result = model # with all attributes
  555. =end
  556. def self.create_or_update(data)
  557. if data[:id]
  558. record = find_by(id: data[:id])
  559. if record
  560. record.update_attributes(data)
  561. return record
  562. end
  563. record = new(data)
  564. record.save
  565. return record
  566. elsif data[:name]
  567. # do lookup with == to handle case insensitive databases
  568. records = if Rails.application.config.db_case_sensitive
  569. where('LOWER(name) = LOWER(?)', data[:name])
  570. else
  571. where(name: data[:name])
  572. end
  573. records.each { |loop_record|
  574. if loop_record.name == data[:name]
  575. loop_record.update_attributes(data)
  576. return loop_record
  577. end
  578. }
  579. record = new(data)
  580. record.save
  581. return record
  582. elsif data[:login]
  583. # do lookup with == to handle case insensitive databases
  584. records = if Rails.application.config.db_case_sensitive
  585. where('LOWER(login) = LOWER(?)', data[:login])
  586. else
  587. where(login: data[:login])
  588. end
  589. records.each { |loop_record|
  590. if loop_record.login.casecmp(data[:login]).zero?
  591. loop_record.update_attributes(data)
  592. return loop_record
  593. end
  594. }
  595. record = new(data)
  596. record.save
  597. return record
  598. elsif data[:email]
  599. # do lookup with == to handle case insensitive databases
  600. records = if Rails.application.config.db_case_sensitive
  601. where('LOWER(email) = LOWER(?)', data[:email])
  602. else
  603. where(email: data[:email])
  604. end
  605. records.each { |loop_record|
  606. if loop_record.email.casecmp(data[:email]).zero?
  607. loop_record.update_attributes(data)
  608. return loop_record
  609. end
  610. }
  611. record = new(data)
  612. record.save
  613. return record
  614. elsif data[:locale]
  615. # do lookup with == to handle case insensitive databases
  616. records = if Rails.application.config.db_case_sensitive
  617. where('LOWER(locale) = LOWER(?)', data[:locale])
  618. else
  619. where(locale: data[:locale])
  620. end
  621. records.each { |loop_record|
  622. if loop_record.locale.casecmp(data[:locale]).zero?
  623. loop_record.update_attributes(data)
  624. return loop_record
  625. end
  626. }
  627. record = new(data)
  628. record.save
  629. return record
  630. else
  631. raise ArgumentError, 'Need name, login, email or locale for create_or_update()'
  632. end
  633. end
  634. =begin
  635. Model.create_or_update with ref lookups
  636. result = Model.create_or_update(attributes)
  637. returns
  638. result = model # with all attributes
  639. =end
  640. def self.create_or_update_with_ref(data)
  641. data = param_association_lookup(data)
  642. create_or_update(data)
  643. end
  644. =begin
  645. activate latest change on create, update, touch and destroy
  646. class Model < ApplicationModel
  647. latest_change_support
  648. end
  649. =end
  650. def self.latest_change_support
  651. after_create :latest_change_set_from_observer
  652. after_update :latest_change_set_from_observer
  653. after_touch :latest_change_set_from_observer
  654. after_destroy :latest_change_set_from_observer_destroy
  655. end
  656. def latest_change_set_from_observer
  657. self.class.latest_change_set(updated_at)
  658. end
  659. def latest_change_set_from_observer_destroy
  660. self.class.latest_change_set(nil)
  661. end
  662. def self.latest_change_set(updated_at)
  663. key = "#{new.class.name}_latest_change"
  664. expires_in = 31_536_000 # 1 year
  665. if updated_at.nil?
  666. Cache.delete(key)
  667. else
  668. Cache.write(key, updated_at, { expires_in: expires_in })
  669. end
  670. end
  671. =begin
  672. get latest updated_at object timestamp
  673. latest_change = Ticket.latest_change
  674. returns
  675. result = timestamp
  676. =end
  677. def self.latest_change
  678. key = "#{new.class.name}_latest_change"
  679. updated_at = Cache.get(key)
  680. # if we do not have it cached, do lookup
  681. if !updated_at
  682. o = select(:updated_at).order(updated_at: :desc).limit(1).first
  683. if o
  684. updated_at = o.updated_at
  685. latest_change_set(updated_at)
  686. end
  687. end
  688. updated_at
  689. end
  690. =begin
  691. activate client notify support on create, update, touch and destroy
  692. class Model < ApplicationModel
  693. notify_clients_support
  694. end
  695. =end
  696. def self.notify_clients_support
  697. after_create :notify_clients_after_create
  698. after_update :notify_clients_after_update
  699. after_touch :notify_clients_after_touch
  700. after_destroy :notify_clients_after_destroy
  701. end
  702. =begin
  703. notify_clients_after_create after model got created
  704. used as callback in model file
  705. class OwnModel < ApplicationModel
  706. after_create :notify_clients_after_create
  707. after_update :notify_clients_after_update
  708. after_touch :notify_clients_after_touch
  709. after_destroy :notify_clients_after_destroy
  710. [...]
  711. =end
  712. def notify_clients_after_create
  713. # return if we run import mode
  714. return if Setting.get('import_mode')
  715. logger.debug "#{self.class.name}.find(#{id}) notify created " + created_at.to_s
  716. class_name = self.class.name
  717. class_name.gsub!(/::/, '')
  718. PushMessages.send(
  719. message: {
  720. event: class_name + ':create',
  721. data: { id: id, updated_at: updated_at }
  722. },
  723. type: 'authenticated',
  724. )
  725. end
  726. =begin
  727. notify_clients_after_update after model got updated
  728. used as callback in model file
  729. class OwnModel < ApplicationModel
  730. after_create :notify_clients_after_create
  731. after_update :notify_clients_after_update
  732. after_touch :notify_clients_after_touch
  733. after_destroy :notify_clients_after_destroy
  734. [...]
  735. =end
  736. def notify_clients_after_update
  737. # return if we run import mode
  738. return if Setting.get('import_mode')
  739. logger.debug "#{self.class.name}.find(#{id}) notify UPDATED " + updated_at.to_s
  740. class_name = self.class.name
  741. class_name.gsub!(/::/, '')
  742. PushMessages.send(
  743. message: {
  744. event: class_name + ':update',
  745. data: { id: id, updated_at: updated_at }
  746. },
  747. type: 'authenticated',
  748. )
  749. end
  750. =begin
  751. notify_clients_after_touch after model got touched
  752. used as callback in model file
  753. class OwnModel < ApplicationModel
  754. after_create :notify_clients_after_create
  755. after_update :notify_clients_after_update
  756. after_touch :notify_clients_after_touch
  757. after_destroy :notify_clients_after_destroy
  758. [...]
  759. =end
  760. def notify_clients_after_touch
  761. # return if we run import mode
  762. return if Setting.get('import_mode')
  763. logger.debug "#{self.class.name}.find(#{id}) notify TOUCH " + updated_at.to_s
  764. class_name = self.class.name
  765. class_name.gsub!(/::/, '')
  766. PushMessages.send(
  767. message: {
  768. event: class_name + ':touch',
  769. data: { id: id, updated_at: updated_at }
  770. },
  771. type: 'authenticated',
  772. )
  773. end
  774. =begin
  775. notify_clients_after_destroy after model got destroyed
  776. used as callback in model file
  777. class OwnModel < ApplicationModel
  778. after_create :notify_clients_after_create
  779. after_update :notify_clients_after_update
  780. after_touch :notify_clients_after_touch
  781. after_destroy :notify_clients_after_destroy
  782. [...]
  783. =end
  784. def notify_clients_after_destroy
  785. # return if we run import mode
  786. return if Setting.get('import_mode')
  787. logger.debug "#{self.class.name}.find(#{id}) notify DESTOY " + updated_at.to_s
  788. class_name = self.class.name
  789. class_name.gsub!(/::/, '')
  790. PushMessages.send(
  791. message: {
  792. event: class_name + ':destroy',
  793. data: { id: id, updated_at: updated_at }
  794. },
  795. type: 'authenticated',
  796. )
  797. end
  798. =begin
  799. serve methode to configure and enable search index support for this model
  800. class Model < ApplicationModel
  801. search_index_support
  802. ignore_attributes: {
  803. create_article_type_id: true,
  804. create_article_sender_id: true,
  805. article_count: true,
  806. },
  807. ignore_ids: [1,2,4]
  808. end
  809. =end
  810. def self.search_index_support(data = {})
  811. @search_index_support_config = data
  812. end
  813. =begin
  814. update search index, if configured - will be executed automatically
  815. model = Model.find(123)
  816. model.search_index_update
  817. =end
  818. def search_index_update
  819. config = self.class.search_index_support_config
  820. return if !config
  821. return if config[:ignore_ids] && config[:ignore_ids].include?(id)
  822. # start background job to transfer data to search index
  823. return if !SearchIndexBackend.enabled?
  824. Delayed::Job.enqueue(ApplicationModel::BackgroundJobSearchIndex.new(self.class.to_s, id))
  825. end
  826. =begin
  827. delete search index object, will be executed automatically
  828. model = Model.find(123)
  829. model.search_index_destroy
  830. =end
  831. def search_index_destroy
  832. config = self.class.search_index_support_config
  833. return if !config
  834. return if config[:ignore_ids] && config[:ignore_ids].include?(id)
  835. SearchIndexBackend.remove(self.class.to_s, id)
  836. end
  837. =begin
  838. reload search index with full data
  839. Model.search_index_reload
  840. =end
  841. def self.search_index_reload
  842. config = @search_index_support_config
  843. return if !config
  844. tolerance = 5
  845. tolerance_count = 0
  846. all_ids = select('id').all.order('created_at DESC')
  847. all_ids.each { |item_with_id|
  848. next if config[:ignore_ids] && config[:ignore_ids].include?(item_with_id.id)
  849. item = find(item_with_id.id)
  850. begin
  851. item.search_index_update_backend
  852. rescue => e
  853. logger.error "Unable to send #{item.class}.find(#{item.id}) backend: #{e.inspect}"
  854. tolerance_count += 1
  855. raise "Unable to send #{item.class}.find(#{item.id}) backend: #{e.inspect}" if tolerance_count == tolerance
  856. end
  857. }
  858. end
  859. =begin
  860. serve methode to configure and enable activity stream support for this model
  861. class Model < ApplicationModel
  862. activity_stream_support permission: 'admin.user'
  863. end
  864. =end
  865. def self.activity_stream_support(data = {})
  866. @activity_stream_support_config = data
  867. end
  868. =begin
  869. log object create activity stream, if configured - will be executed automatically
  870. model = Model.find(123)
  871. model.activity_stream_create
  872. =end
  873. def activity_stream_create
  874. return if !self.class.activity_stream_support_config
  875. activity_stream_log('create', self['created_by_id'])
  876. end
  877. =begin
  878. log object update activity stream, if configured - will be executed automatically
  879. model = Model.find(123)
  880. model.activity_stream_update
  881. =end
  882. def activity_stream_update
  883. return if !self.class.activity_stream_support_config
  884. return if !changed?
  885. # default ignored attributes
  886. ignore_attributes = {
  887. created_at: true,
  888. updated_at: true,
  889. created_by_id: true,
  890. updated_by_id: true,
  891. }
  892. if self.class.activity_stream_support_config[:ignore_attributes]
  893. self.class.activity_stream_support_config[:ignore_attributes].each { |key, value|
  894. ignore_attributes[key] = value
  895. }
  896. end
  897. log = false
  898. changes.each { |key, _value|
  899. # do not log created_at and updated_at attributes
  900. next if ignore_attributes[key.to_sym] == true
  901. log = true
  902. }
  903. return if !log
  904. activity_stream_log('update', self['updated_by_id'])
  905. end
  906. =begin
  907. delete object activity stream, will be executed automatically
  908. model = Model.find(123)
  909. model.activity_stream_destroy
  910. =end
  911. def activity_stream_destroy
  912. return if !self.class.activity_stream_support_config
  913. ActivityStream.remove(self.class.to_s, id)
  914. end
  915. =begin
  916. serve methode to configure and enable history support for this model
  917. class Model < ApplicationModel
  918. history_support
  919. end
  920. class Model < ApplicationModel
  921. history_support ignore_attributes: { article_count: true }
  922. end
  923. =end
  924. def self.history_support(data = {})
  925. @history_support_config = data
  926. end
  927. =begin
  928. log object create history, if configured - will be executed automatically
  929. model = Model.find(123)
  930. model.history_create
  931. =end
  932. def history_create
  933. return if !self.class.history_support_config
  934. #logger.debug 'create ' + self.changes.inspect
  935. history_log('created', created_by_id)
  936. end
  937. =begin
  938. log object update history with all updated attributes, if configured - will be executed automatically
  939. model = Model.find(123)
  940. model.history_update
  941. =end
  942. def history_update
  943. return if !self.class.history_support_config
  944. return if !changed?
  945. # return if it's no update
  946. return if new_record?
  947. # new record also triggers update, so ignore new records
  948. changes = self.changes
  949. if history_changes_last_done
  950. history_changes_last_done.each { |key, value|
  951. if changes.key?(key) && changes[key] == value
  952. changes.delete(key)
  953. end
  954. }
  955. end
  956. self.history_changes_last_done = changes
  957. #logger.info 'updated ' + self.changes.inspect
  958. return if changes['id'] && !changes['id'][0]
  959. # default ignored attributes
  960. ignore_attributes = {
  961. created_at: true,
  962. updated_at: true,
  963. created_by_id: true,
  964. updated_by_id: true,
  965. }
  966. if self.class.history_support_config[:ignore_attributes]
  967. self.class.history_support_config[:ignore_attributes].each { |key, value|
  968. ignore_attributes[key] = value
  969. }
  970. end
  971. changes.each { |key, value|
  972. # do not log created_at and updated_at attributes
  973. next if ignore_attributes[key.to_sym] == true
  974. # get attribute name
  975. attribute_name = key.to_s
  976. if attribute_name[-3, 3] == '_id'
  977. attribute_name = attribute_name[ 0, attribute_name.length - 3 ]
  978. end
  979. value_id = []
  980. value_str = [ value[0], value[1] ]
  981. if key.to_s[-3, 3] == '_id'
  982. value_id[0] = value[0]
  983. value_id[1] = value[1]
  984. if respond_to?(attribute_name) && send(attribute_name)
  985. relation_class = send(attribute_name).class
  986. if relation_class && value_id[0]
  987. relation_model = relation_class.lookup(id: value_id[0])
  988. if relation_model
  989. if relation_model['name']
  990. value_str[0] = relation_model['name']
  991. elsif relation_model.respond_to?('fullname')
  992. value_str[0] = relation_model.send('fullname')
  993. end
  994. end
  995. end
  996. if relation_class && value_id[1]
  997. relation_model = relation_class.lookup(id: value_id[1])
  998. if relation_model
  999. if relation_model['name']
  1000. value_str[1] = relation_model['name']
  1001. elsif relation_model.respond_to?('fullname')
  1002. value_str[1] = relation_model.send('fullname')
  1003. end
  1004. end
  1005. end
  1006. end
  1007. end
  1008. data = {
  1009. history_attribute: attribute_name,
  1010. value_from: value_str[0].to_s,
  1011. value_to: value_str[1].to_s,
  1012. id_from: value_id[0],
  1013. id_to: value_id[1],
  1014. }
  1015. #logger.info "HIST NEW #{self.class.to_s}.find(#{self.id}) #{data.inspect}"
  1016. history_log('updated', updated_by_id, data)
  1017. }
  1018. end
  1019. =begin
  1020. delete object history, will be executed automatically
  1021. model = Model.find(123)
  1022. model.history_destroy
  1023. =end
  1024. def history_destroy
  1025. return if !self.class.history_support_config
  1026. History.remove(self.class.to_s, id)
  1027. end
  1028. =begin
  1029. serve methode to configure and attributes_with_associations support for this model
  1030. class Model < ApplicationModel
  1031. attributes_with_associations(ignore: { user_ids: => true })
  1032. end
  1033. =end
  1034. def self.attributes_with_associations_support(data = {})
  1035. @attributes_with_associations_support_config = data
  1036. end
  1037. =begin
  1038. get list of attachments of this object
  1039. item = Model.find(123)
  1040. list = item.attachments
  1041. returns
  1042. # array with Store model objects
  1043. =end
  1044. def attachments
  1045. Store.list(object: self.class.to_s, o_id: id)
  1046. end
  1047. =begin
  1048. store attachments for this object
  1049. item = Model.find(123)
  1050. item.attachments = [ Store-Object1, Store-Object2 ]
  1051. =end
  1052. def attachments=(attachments)
  1053. self.attachments_buffer = attachments
  1054. # update if object already exists
  1055. return if !(id && id.nonzero?)
  1056. attachments_buffer_check
  1057. end
  1058. =begin
  1059. return object and assets
  1060. data = Model.full(123)
  1061. data = {
  1062. id: 123,
  1063. assets: assets,
  1064. }
  1065. =end
  1066. def self.full(id)
  1067. object = find(id)
  1068. assets = object.assets({})
  1069. {
  1070. id: id,
  1071. assets: assets,
  1072. }
  1073. end
  1074. =begin
  1075. get assets of object list
  1076. list = [
  1077. {
  1078. object => 'Ticket',
  1079. o_id => 1,
  1080. },
  1081. {
  1082. object => 'User',
  1083. o_id => 121,
  1084. },
  1085. ]
  1086. assets = Model.assets_of_object_list(list, assets)
  1087. =end
  1088. def self.assets_of_object_list(list, assets = {})
  1089. list.each { |item|
  1090. require item['object'].to_filename
  1091. record = Kernel.const_get(item['object']).find(item['o_id'])
  1092. assets = record.assets(assets)
  1093. if item['created_by_id']
  1094. user = User.find(item['created_by_id'])
  1095. assets = user.assets(assets)
  1096. end
  1097. if item['updated_by_id']
  1098. user = User.find(item['updated_by_id'])
  1099. assets = user.assets(assets)
  1100. end
  1101. }
  1102. assets
  1103. end
  1104. =begin
  1105. get assets and record_ids of selector
  1106. model = Model.find(123)
  1107. assets = model.assets_of_selector('attribute_name_of_selector', assets)
  1108. =end
  1109. def assets_of_selector(selector, assets = {})
  1110. # get assets of condition
  1111. models = Models.all
  1112. send(selector).each { |item, content|
  1113. attribute = item.split(/\./)
  1114. next if !attribute[1]
  1115. begin
  1116. attribute_class = attribute[0].to_classname.constantize
  1117. rescue => e
  1118. logger.error "Unable to get asset for '#{attribute[0]}': #{e.inspect}"
  1119. next
  1120. end
  1121. reflection = attribute[1].sub(/_id$/, '')
  1122. #reflection = reflection.to_sym
  1123. next if !models[attribute_class]
  1124. next if !models[attribute_class][:reflections]
  1125. next if !models[attribute_class][:reflections][reflection]
  1126. next if !models[attribute_class][:reflections][reflection].klass
  1127. attribute_ref_class = models[attribute_class][:reflections][reflection].klass
  1128. if content['value'].class == Array
  1129. content['value'].each { |item_id|
  1130. attribute_object = attribute_ref_class.find_by(id: item_id)
  1131. if attribute_object
  1132. assets = attribute_object.assets(assets)
  1133. end
  1134. }
  1135. else
  1136. attribute_object = attribute_ref_class.find_by(id: content['value'])
  1137. if attribute_object
  1138. assets = attribute_object.assets(assets)
  1139. end
  1140. end
  1141. }
  1142. assets
  1143. end
  1144. =begin
  1145. touch references by params
  1146. Model.touch_reference_by_params(
  1147. object: 'Ticket',
  1148. o_id: 123,
  1149. )
  1150. =end
  1151. def self.touch_reference_by_params(data)
  1152. object_class = Kernel.const_get(data[:object])
  1153. object = object_class.lookup(id: data[:o_id])
  1154. return if !object
  1155. object.touch
  1156. rescue => e
  1157. logger.error e.message
  1158. logger.error e.backtrace.inspect
  1159. end
  1160. private
  1161. def attachments_buffer
  1162. @attachments_buffer_data
  1163. end
  1164. def attachments_buffer=(attachments)
  1165. @attachments_buffer_data = attachments
  1166. end
  1167. def attachments_buffer_check
  1168. # do nothing if no attachment exists
  1169. return 1 if attachments_buffer.nil?
  1170. # store attachments
  1171. article_store = []
  1172. attachments_buffer.each do |attachment|
  1173. article_store.push Store.add(
  1174. object: self.class.to_s,
  1175. o_id: id,
  1176. data: attachment.content,
  1177. filename: attachment.filename,
  1178. preferences: attachment.preferences,
  1179. created_by_id: created_by_id,
  1180. )
  1181. end
  1182. attachments_buffer = nil
  1183. end
  1184. =begin
  1185. delete object recent viewed list, will be executed automatically
  1186. model = Model.find(123)
  1187. model.recent_view_destroy
  1188. =end
  1189. def recent_view_destroy
  1190. RecentView.log_destroy(self.class.to_s, id)
  1191. end
  1192. =begin
  1193. check string/varchar size and cut them if needed
  1194. =end
  1195. def check_limits
  1196. attributes.each { |attribute|
  1197. next if !self[ attribute[0] ]
  1198. next if self[ attribute[0] ].class != String
  1199. next if self[ attribute[0] ].empty?
  1200. column = self.class.columns_hash[ attribute[0] ]
  1201. next if !column
  1202. limit = column.limit
  1203. if column && limit
  1204. current_length = attribute[1].to_s.length
  1205. if limit < current_length
  1206. logger.warn "WARNING: cut string because of database length #{self.class}.#{attribute[0]}(#{limit} but is #{current_length}:#{attribute[1]})"
  1207. self[ attribute[0] ] = attribute[1][ 0, limit ]
  1208. end
  1209. end
  1210. # strip 4 bytes utf8 chars if needed
  1211. if column && self[ attribute[0] ]
  1212. self[attribute[0]] = self[ attribute[0] ].utf8_to_3bytesutf8
  1213. end
  1214. }
  1215. end
  1216. =begin
  1217. destroy object dependencies, will be executed automatically
  1218. =end
  1219. def destroy_dependencies
  1220. end
  1221. end