models.rb 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. class Models
  3. include ApplicationLib
  4. =begin
  5. get list of models
  6. result = Models.all
  7. returns
  8. {
  9. Some::Classname1 => {
  10. attributes: ['id', 'name', '...'],
  11. reflections: ...model.reflections...,
  12. table: 'some_classname1s',
  13. },
  14. Some::Classname2 => {
  15. attributes: ['id', 'name', '...']
  16. reflections: ...model.reflections...
  17. table: 'some_classname2s',
  18. },
  19. }
  20. =end
  21. def self.all
  22. @all ||= begin
  23. all = {}
  24. dir = Rails.root.join('app/models').to_s
  25. tables = ActiveRecord::Base.connection.tables
  26. Dir.glob("#{dir}/**/*.rb") do |entry|
  27. next if entry.match?(%r{application_model}i)
  28. next if entry.match?(%r{channel/}i)
  29. next if entry.match?(%r{observer/}i)
  30. next if entry.match?(%r{store/provider/}i)
  31. next if entry.match?(%r{models/concerns/}i)
  32. next if entry.match?(%r{models/object_manager/attribute/validation/}i)
  33. entry.gsub!(dir, '')
  34. entry = entry.to_classname
  35. model_class = entry.constantize
  36. next if !model_class.respond_to? :new
  37. next if !model_class.respond_to? :table_name
  38. table_name = model_class.table_name # handle models where not table exists, pending migrations
  39. next if tables.exclude?(table_name)
  40. model_object = model_class.new
  41. next if !model_object.respond_to? :attributes
  42. all[model_class] = {}
  43. all[model_class][:attributes] = model_class.attribute_names
  44. all[model_class][:reflections] = model_class.reflections
  45. all[model_class][:table] = model_class.table_name
  46. # puts model_class
  47. # puts "rrrr #{all[model_class][:attributes]}"
  48. # puts " #{model_class.attribute_names.inspect}"
  49. end
  50. all
  51. end
  52. end
  53. =begin
  54. get list of searchable models for UI
  55. result = Models.searchable
  56. returns
  57. [Model1, Model2, Model3]
  58. =end
  59. def self.searchable
  60. @searchable ||= Models.all.keys.select { |model| model.respond_to?(:search_preferences) }
  61. end
  62. =begin
  63. get list of indexable models
  64. result = Models.indexable
  65. returns
  66. [Model1, Model2, Model3]
  67. =end
  68. def self.indexable
  69. @indexable ||= Models.all.keys.select { |model| model.method_defined?(:search_index_update_backend) }
  70. end
  71. =begin
  72. This method counts how many time the given object was referenced.
  73. It returns the count partitioned by the class and attribute name.
  74. result = Models.references('User', 2)
  75. returns
  76. {
  77. 'Some::Classname1' => {
  78. attribute1: 12,
  79. attribute2: 6,
  80. },
  81. 'Some::Classname2' => {
  82. updated_by_id: 12,
  83. created_by_id: 6,
  84. },
  85. }
  86. =end
  87. def self.references(object_name, object_id, include_zero = false)
  88. object_name = object_name.to_s
  89. # check if model exists
  90. object_name.constantize.find(object_id)
  91. list = all
  92. references = {}
  93. # find relations via attributes
  94. ref_attributes = ["#{object_name.downcase}_id"]
  95. # for users we do not define relations for created_by_id &
  96. # updated_by_id - add it here directly
  97. if object_name == 'User'
  98. ref_attributes.push 'created_by_id'
  99. ref_attributes.push 'updated_by_id'
  100. ref_attributes.push 'out_of_office_replacement_id'
  101. end
  102. list.each do |model_class, model_attributes|
  103. if !references[model_class.to_s]
  104. references[model_class.to_s] = {}
  105. end
  106. next if !model_attributes[:attributes]
  107. ref_attributes.each do |item|
  108. next if model_attributes[:attributes].exclude?(item)
  109. count = model_class.where(item => object_id).count
  110. next if count.zero? && !include_zero
  111. if !references[model_class.to_s][item]
  112. references[model_class.to_s][item] = 0
  113. end
  114. Rails.logger.debug { "FOUND (by id) #{model_class}->#{item} #{count}!" }
  115. references[model_class.to_s][item] += count
  116. end
  117. end
  118. # find relations via reflections
  119. list.each do |model_class, model_attributes| # rubocop:disable Style/CombinableLoops
  120. next if !model_attributes[:reflections]
  121. model_attributes[:reflections].each_value do |reflection_value|
  122. next if reflection_value.macro != :belongs_to
  123. col_name = "#{reflection_value.name}_id"
  124. next if ref_attributes.include?(col_name)
  125. if reflection_value.options[:class_name] == object_name
  126. count = model_class.where(col_name => object_id).count
  127. next if count.zero? && !include_zero
  128. if !references[model_class.to_s][col_name]
  129. references[model_class.to_s][col_name] = 0
  130. end
  131. Rails.logger.debug { "FOUND (by ref without class) #{model_class}->#{col_name} #{count}!" }
  132. references[model_class.to_s][col_name] += count
  133. end
  134. next if reflection_value.options[:class_name]
  135. next if reflection_value.name != object_name.downcase.to_sym
  136. count = model_class.where(col_name => object_id).count
  137. next if count.zero? && !include_zero
  138. if !references[model_class.to_s][col_name]
  139. references[model_class.to_s][col_name] = 0
  140. end
  141. Rails.logger.debug { "FOUND (by ref with class) #{model_class}->#{col_name} #{count}!" }
  142. references[model_class.to_s][col_name] += count
  143. end
  144. end
  145. # cleanup, remove models with empty references
  146. references.each do |k, v|
  147. next if v.present?
  148. references.delete(k)
  149. end
  150. references
  151. end
  152. =begin
  153. get reference total of a models
  154. count = Models.references_total('User', 2)
  155. returns
  156. count # 1234
  157. =end
  158. def self.references_total(object_name, object_id)
  159. references = references(object_name, object_id)
  160. total = 0
  161. references.each_value do |model_references|
  162. model_references.each_value do |count|
  163. total += count
  164. end
  165. end
  166. total
  167. end
  168. =begin
  169. merge model references to other model
  170. result = Models.merge('User', 2, 4711) # Object, object_id_of_primary, object_id_which_should_be_merged
  171. returns
  172. true # false
  173. =end
  174. def self.merge(object_name, object_id_primary, object_id_to_merge, force = false)
  175. # if lower x references to update, do it right now
  176. if force
  177. total = references_total(object_name, object_id_to_merge)
  178. if total > 1000
  179. raise "Can't merge object because object has more then 1000 (#{total}) references, please contact your system administrator."
  180. end
  181. end
  182. # update references
  183. references = references(object_name, object_id_to_merge)
  184. references.each do |model, attributes|
  185. model_object = model.constantize
  186. # collect items and attributes to update
  187. items_to_update = {}
  188. attributes.each_key do |attribute|
  189. Rails.logger.debug { "#{object_name}: #{model}.#{attribute}->#{object_id_to_merge}->#{object_id_primary}" }
  190. model_object.where(attribute => object_id_to_merge).each do |item|
  191. if !items_to_update[item.id]
  192. items_to_update[item.id] = item
  193. end
  194. items_to_update[item.id][attribute.to_sym] = object_id_primary
  195. end
  196. end
  197. # update items
  198. ActiveRecord::Base.transaction do
  199. items_to_update.each_value(&:save!)
  200. end
  201. end
  202. ExternalSync.migrate(object_name, object_id_primary, object_id_to_merge)
  203. true
  204. end
  205. end