has_history.rb 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. # Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
  2. module HasHistory
  3. extend ActiveSupport::Concern
  4. included do
  5. attr_accessor :history_changes_last_done
  6. after_create :history_create
  7. after_update :history_update
  8. after_destroy :history_destroy
  9. end
  10. =begin
  11. log object create history, if configured - will be executed automatically
  12. model = Model.find(123)
  13. model.history_create
  14. =end
  15. def history_create
  16. history_log('created', created_by_id)
  17. end
  18. =begin
  19. log object update history with all updated attributes, if configured - will be executed automatically
  20. model = Model.find(123)
  21. model.history_update
  22. =end
  23. def history_update
  24. return if !saved_changes?
  25. # return if it's no update
  26. return if new_record?
  27. # new record also triggers update, so ignore new records
  28. changes = saved_changes
  29. history_changes_last_done&.each do |key, value|
  30. if changes.key?(key) && changes[key] == value
  31. changes.delete(key)
  32. end
  33. end
  34. self.history_changes_last_done = changes
  35. # logger.info 'updated ' + self.changes.inspect
  36. return if changes['id'] && !changes['id'][0]
  37. ignored_attributes = self.class.instance_variable_get(:@history_attributes_ignored) || []
  38. ignored_attributes += %i[created_at updated_at created_by_id updated_by_id]
  39. changes.each do |key, value|
  40. next if ignored_attributes.include?(key.to_sym)
  41. # get attribute name
  42. attribute_name = key.to_s
  43. if attribute_name[-3, 3] == '_id'
  44. attribute_name = attribute_name[ 0, attribute_name.length - 3 ]
  45. end
  46. value_id = []
  47. value_str = [ value[0], value[1] ]
  48. if key.to_s[-3, 3] == '_id'
  49. value_id[0] = value[0]
  50. value_id[1] = value[1]
  51. if respond_to?(attribute_name) && send(attribute_name)
  52. relation_class = send(attribute_name).class
  53. if relation_class && value_id[0]
  54. relation_model = relation_class.lookup(id: value_id[0])
  55. if relation_model
  56. if relation_model['name']
  57. value_str[0] = relation_model['name']
  58. elsif relation_model.respond_to?(:fullname)
  59. value_str[0] = relation_model.send(:fullname)
  60. end
  61. end
  62. end
  63. if relation_class && value_id[1]
  64. relation_model = relation_class.lookup(id: value_id[1])
  65. if relation_model
  66. if relation_model['name']
  67. value_str[1] = relation_model['name']
  68. elsif relation_model.respond_to?(:fullname)
  69. value_str[1] = relation_model.send(:fullname)
  70. end
  71. end
  72. end
  73. end
  74. end
  75. data = {
  76. history_attribute: attribute_name,
  77. value_from: value_str[0].to_s,
  78. value_to: value_str[1].to_s,
  79. id_from: value_id[0],
  80. id_to: value_id[1],
  81. }
  82. # logger.info "HIST NEW #{self.class.to_s}.find(#{self.id}) #{data.inspect}"
  83. history_log('updated', updated_by_id, data)
  84. end
  85. end
  86. =begin
  87. delete object history, will be executed automatically
  88. model = Model.find(123)
  89. model.history_destroy
  90. =end
  91. def history_destroy
  92. History.remove(self.class.to_s, id)
  93. end
  94. =begin
  95. create history entry for this object
  96. organization = Organization.find(123)
  97. result = organization.history_log('created', user_id)
  98. returns
  99. result = true # false
  100. =end
  101. def history_log(type, user_id, attributes = {})
  102. attributes.merge!(
  103. o_id: self['id'],
  104. history_type: type,
  105. history_object: self.class.name,
  106. related_o_id: nil,
  107. related_history_object: nil,
  108. created_by_id: user_id,
  109. updated_at: updated_at,
  110. created_at: updated_at,
  111. ).merge!(history_log_attributes)
  112. History.add(attributes)
  113. end
  114. # callback function to overwrite
  115. # default history log attributes
  116. # gets called from history_log
  117. def history_log_attributes
  118. {}
  119. end
  120. =begin
  121. get history log for this object
  122. organization = Organization.find(123)
  123. result = organization.history_get
  124. returns
  125. result = [
  126. {
  127. :type => 'created',
  128. :object => 'Organization',
  129. :created_by_id => 3,
  130. :created_at => "2013-08-19 20:41:33",
  131. },
  132. {
  133. :type => 'updated',
  134. :object => 'Organization',
  135. :attribute => 'note',
  136. :o_id => 1,
  137. :id_to => nil,
  138. :id_from => nil,
  139. :value_from => "some note",
  140. :value_to => "some other note",
  141. :created_by_id => 3,
  142. :created_at => "2013-08-19 20:41:33",
  143. },
  144. ]
  145. to get history log for this object with all assets
  146. organization = Organization.find(123)
  147. result = organization.history_get(true)
  148. returns
  149. result = {
  150. :history => [
  151. { ... },
  152. { ... },
  153. ],
  154. :assets => {
  155. ...
  156. }
  157. }
  158. =end
  159. def history_get(fulldata = false)
  160. relation_object = history_relation_object
  161. if !fulldata
  162. return History.list(self.class.name, self['id'], relation_object)
  163. end
  164. # get related objects
  165. history = History.list(self.class.name, self['id'], relation_object, true)
  166. history[:list].each do |item|
  167. record = item['object'].constantize.lookup(id: item['o_id'])
  168. if record.present?
  169. history[:assets] = record.assets(history[:assets])
  170. end
  171. next if !item['related_object']
  172. record = item['related_object'].constantize.lookup(id: item['related_o_id'])
  173. if record.present?
  174. history[:assets] = record.assets(history[:assets])
  175. end
  176. end
  177. {
  178. history: history[:list],
  179. assets: history[:assets],
  180. }
  181. end
  182. def history_relation_object
  183. @history_relation_object ||= self.class.instance_variable_get(:@history_relation_object) || []
  184. end
  185. # methods defined here are going to extend the class, not the instance of it
  186. class_methods do
  187. =begin
  188. serve method to ignore model attributes in historization
  189. class Model < ApplicationModel
  190. include HasHistory
  191. history_attributes_ignored :create_article_type_id, :preferences
  192. end
  193. =end
  194. def history_attributes_ignored(*attributes)
  195. @history_attributes_ignored = attributes
  196. end
  197. =begin
  198. serve method to ignore model attributes in historization
  199. class Model < ApplicationModel
  200. include HasHistory
  201. history_relation_object 'Some::Relation::Object'
  202. end
  203. =end
  204. def history_relation_object(*attributes)
  205. @history_relation_object ||= []
  206. @history_relation_object |= attributes
  207. end
  208. end
  209. end