application_model.rb 38 KB

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