Class: BulkDependencyEraser::QuerySchemaParser
- Defined in:
- lib/bulk_dependency_eraser/query_schema_parser.rb
Constant Summary collapse
- DEFAULT_OPTS =
{ verbose: false, # Some associations scopes take parameters. # - We would have to instantiate if we wanted to apply that scope filter. instantiate_if_assoc_scope_with_arity: false, force_destroy_restricted: false, }
Constants inherited from Base
Base::DEFAULT_DB_BLANK_WRAPPER, Base::DEFAULT_DB_READ_WRAPPER, Base::DEFAULT_DB_WRITE_WRAPPER, Base::DEFAULT_KLASS_MAPPED_SCOPE_WRAPPER, Base::DEFAULT_SCOPE_WRAPPER, Base::DEPENDENCY_DESTROY, Base::DEPENDENCY_DESTROY_IGNORE_REFLECTION_TYPES, Base::DEPENDENCY_NULLIFY, Base::DEPENDENCY_RESTRICT, Base::POLY_KLASS_NAME
Instance Attribute Summary collapse
-
#circular_dependency_klasses ⇒ Object
readonly
Returns the value of attribute circular_dependency_klasses.
-
#dependencies_per_klass ⇒ Object
readonly
Returns the value of attribute dependencies_per_klass.
-
#full_schema_parser ⇒ Object
readonly
Returns the value of attribute full_schema_parser.
-
#initial_class ⇒ Object
readonly
attr_accessor :deletion_list, :nullification_list.
Attributes inherited from Base
Instance Method Summary collapse
-
#association_parser(parent_class, association_name, dependency_type, dependency_path) ⇒ Object
Used to iterate through each destroyable association, and recursively call ‘deletion_query_parser’.
- #association_parser_belongs_to(parent_class, association_name, dependency_type, dependency_path) ⇒ Object
-
#association_parser_has_many(parent_class, association_name, dependency_type, dependency_path) ⇒ Object
Handles the :has_many association type - handles it’s polymorphic associations internally (easier on the has_many).
- #execute ⇒ Object
-
#find_klasses_from_polymorphic_dependency(klass) ⇒ Object
In this example the klass would be the polymorphic klass - i.e.
-
#initialize(query:, opts: {}) ⇒ QuerySchemaParser
constructor
A new instance of QuerySchemaParser.
-
#klass_dependencies_parser(klass, klass_action:, dependency_path: []) ⇒ Object
-
if was a dependency from a polymophic class, then iterate through the klasses.
-
-
#traverse_restricted_dependency?(parent_class, reflection) ⇒ Boolean
return [Boolean] - true if valid - false if not valid.
Methods inherited from Base
Methods included from Utils::Methods
Constructor Details
#initialize(query:, opts: {}) ⇒ QuerySchemaParser
Returns a new instance of QuerySchemaParser.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/bulk_dependency_eraser/query_schema_parser.rb', line 19 def initialize query:, opts: {} if query.is_a?(ActiveRecord::Relation) @initial_class = query.klass else # current_query is a normal rails class @initial_class = query end # @dependencies_per_klass Structure # { # <ActiveRecord::Base> => { # <ActiveRecord::Reflection::AssociationReflection> => <ActiveRecord::Base> # } # } @dependencies_per_klass = {} # @circular_dependency_klasses Structure # { # <ActiveRecord::Base> => [ # # Path of dependencies that start and end with the key class # <ActiveRecord::Base>, # <ActiveRecord::Base>, # <ActiveRecord::Base>, # ] # } @circular_dependency_klasses = {} @full_schema_parser = BulkDependencyEraser::FullSchemaParser.new(opts:) super(opts:) end |
Instance Attribute Details
#circular_dependency_klasses ⇒ Object (readonly)
Returns the value of attribute circular_dependency_klasses.
14 15 16 |
# File 'lib/bulk_dependency_eraser/query_schema_parser.rb', line 14 def circular_dependency_klasses @circular_dependency_klasses end |
#dependencies_per_klass ⇒ Object (readonly)
Returns the value of attribute dependencies_per_klass.
13 14 15 |
# File 'lib/bulk_dependency_eraser/query_schema_parser.rb', line 13 def dependencies_per_klass @dependencies_per_klass end |
#full_schema_parser ⇒ Object (readonly)
Returns the value of attribute full_schema_parser.
15 16 17 |
# File 'lib/bulk_dependency_eraser/query_schema_parser.rb', line 15 def full_schema_parser @full_schema_parser end |
#initial_class ⇒ Object (readonly)
attr_accessor :deletion_list, :nullification_list
12 13 14 |
# File 'lib/bulk_dependency_eraser/query_schema_parser.rb', line 12 def initial_class @initial_class end |
Instance Method Details
#association_parser(parent_class, association_name, dependency_type, dependency_path) ⇒ Object
Used to iterate through each destroyable association, and recursively call ‘deletion_query_parser’.
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/bulk_dependency_eraser/query_schema_parser.rb', line 125 def association_parser(parent_class, association_name, dependency_type, dependency_path) reflection = parent_class.reflect_on_association(association_name) reflection_type = reflection.class.name raise "No dependency set for #{parent_class} and it's association: #{association_name}" unless dependency_type case reflection_type when 'ActiveRecord::Reflection::HasManyReflection' association_parser_has_many(parent_class, association_name, dependency_type, dependency_path) when 'ActiveRecord::Reflection::HasOneReflection' association_parser_has_many(parent_class, association_name, dependency_type, dependency_path) when 'ActiveRecord::Reflection::BelongsToReflection' association_parser_belongs_to(parent_class, association_name, dependency_type, dependency_path) else ("Unsupported association type for #{parent_class.name}'s association '#{association_name}': #{reflection_type}") end end |
#association_parser_belongs_to(parent_class, association_name, dependency_type, dependency_path) ⇒ Object
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/bulk_dependency_eraser/query_schema_parser.rb', line 210 def association_parser_belongs_to(parent_class, association_name, dependency_type, dependency_path) raise "parent_class missing" unless parent_class raise "#{parent_class} - association_name: missing" unless association_name raise "#{parent_class} - dependency_type: missing" unless dependency_type raise "#{parent_class} - dependency_path: nil" if dependency_path.nil? reflection = parent_class.reflect_on_association(association_name) reflection_type = reflection.class.name is_polymorphic = reflection.[:polymorphic] if is_polymorphic assoc_klass = find_klasses_from_polymorphic_dependency(parent_class).map(&:constantize) @dependencies_per_klass[parent_class.name] += assoc_klass.map(&:name) else assoc_klass = reflection.klass @dependencies_per_klass[parent_class.name] << assoc_klass.name end specified_primary_key = reflection.[:primary_key] || 'id' specified_foreign_key = reflection.[:foreign_key] || "#{association_name}_id" # Check to see if foreign_key exists in our parent table unless parent_class.column_names.include?(specified_foreign_key) report_error( " For #{parent_class.name}'s association '#{association_name}': Could not determine the assoc's foreign key. Foreign key should have been '#{specified_foreign_key}', but did not exist on the #{parent_class.table_name} table. " ) return end if ( DEPENDENCY_DESTROY.include?(dependency_type) || DEPENDENCY_NULLIFY.include?(dependency_type) && traverse_restricted_dependency?(parent_class, reflection) ) klass_dependencies_parser(assoc_klass, klass_action: dependency_type, dependency_path: dependency_path.dup << parent_class.name) end end |
#association_parser_has_many(parent_class, association_name, dependency_type, dependency_path) ⇒ Object
Handles the :has_many association type
-
handles it’s polymorphic associations internally (easier on the has_many)
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/bulk_dependency_eraser/query_schema_parser.rb', line 145 def association_parser_has_many(parent_class, association_name, dependency_type, dependency_path) raise "parent_class missing" unless parent_class raise "#{parent_class} - association_name: missing" unless association_name raise "#{parent_class} - dependency_type: missing" unless dependency_type raise "#{parent_class} - dependency_path: nil" if dependency_path.nil? reflection = parent_class.reflect_on_association(association_name) reflection_type = reflection.class.name assoc_klass = reflection.klass assoc_klass_name = assoc_klass.name @dependencies_per_klass[parent_class.name] << assoc_klass.name # If there is an association scope present, check to see how many parameters it's using # - if there's any parameter, we have to either skip it or instantiate it to find it's dependencies. if reflection.scope&.arity&.nonzero? && opts_c.instantiate_if_assoc_scope_with_arity == false report_error( "#{parent_class.name} and '#{association_name}' - scope has instance parameters. Use :instantiate_if_assoc_scope_with_arity option?" ) return end # Look for manually specified keys in the assocation first specified_primary_key = reflection.[:primary_key]&.to_s specified_foreign_key = reflection.[:foreign_key]&.to_s # For polymorphic_associations specified_foreign_type = nil # handle foreign_key edge cases if specified_foreign_key.nil? if reflection.[:as] specified_foreign_type = "#{reflection.[:as]}_type" specified_foreign_key = "#{reflection.[:as]}_id" else specified_foreign_key = parent_class.table_name.singularize + "_id" end end # Check to see if foreign_key exists in association class's table unless assoc_klass.column_names.include?(specified_foreign_key) report_error( " For '#{assoc_klass.name}': Could not determine the assoc's foreign key. Foreign key should have been '#{specified_foreign_key}', but did not exist on the #{assoc_klass.table_name} table. " ) return end unless specified_foreign_type.nil? || assoc_klass.column_names.include?(specified_foreign_type) report_error( " For '#{assoc_klass.name}': Could not determine the assoc's foreign key type. Foreign key type should have been '#{specified_foreign_type}', but did not exist on the #{assoc_klass.table_name} table. " ) end if DEPENDENCY_RESTRICT.include?(dependency_type) && traverse_restricted_dependency?(parent_class, reflection) klass_dependencies_parser(assoc_klass, klass_action: dependency_type, dependency_path: dependency_path.dup << parent_class.name) else klass_dependencies_parser(assoc_klass, klass_action: dependency_type, dependency_path: dependency_path.dup << parent_class.name) end end |
#execute ⇒ Object
47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/bulk_dependency_eraser/query_schema_parser.rb', line 47 def execute unless full_schema_parser.execute merge_errors(full_schema_parser.errors, 'FullSchemaParser: ') return false end klass_dependencies_parser(initial_class, klass_action: :destroy) @dependencies_per_klass.each do |key, values| @dependencies_per_klass[key] = values.uniq end return true end |
#find_klasses_from_polymorphic_dependency(klass) ⇒ Object
In this example the klass would be the polymorphic klass
-
i.e. Attachment belongs_to: :attachable, dependent: :destroy
We’re looking for klasses in the flat map that have a has_many :attachments, as: :attachable
253 254 255 256 257 258 259 260 261 |
# File 'lib/bulk_dependency_eraser/query_schema_parser.rb', line 253 def find_klasses_from_polymorphic_dependency(klass) found_klasses = [] flat_dependencies_per_klass.each do |flat_klass_name, klass_dependencies| if klass_dependencies[:has_many].values.include?(klass.name) found_klasses << flat_klass_name end end found_klasses end |
#klass_dependencies_parser(klass, klass_action:, dependency_path: []) ⇒ Object
-
if was a dependency from a polymophic class, then iterate through the klasses.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/bulk_dependency_eraser/query_schema_parser.rb', line 64 def klass_dependencies_parser klass, klass_action:, dependency_path: [] if klass.is_a?(Array) klass.each do |klass_subset| klass_dependencies_parser(klass_subset, klass_action:, dependency_path:) end return end unless DEPENDENCY_DESTROY.include?(klass_action) || DEPENDENCY_NULLIFY.include?(klass_action) raise "invalid klass action: #{klass_action}" end raise "invalid klass: #{klass}" unless klass < ActiveRecord::Base # Not a circular dependency if the repetitious klass has a nullify action. if DEPENDENCY_DESTROY.include?(klass_action) && dependency_path.include?(klass.name) index = dependency_path.index(klass.name) circular_dependency = dependency_path[index..] + [klass.name] circular_dependency_klasses[klass.name] = circular_dependency return end # We don't need to consider dependencies for a klass that is being nullified. return if DEPENDENCY_NULLIFY.include?(klass_action) # already parsed, doesn't need to be parsed again. return if dependencies_per_klass.include?(klass.name) @dependencies_per_klass[klass.name] = [] # We're including :restricted dependencies destroy_associations = klass.reflect_on_all_associations.select do |reflection| dependency_type = reflection.&.dig(:dependent)&.to_sym DEPENDENCY_DESTROY.include?(dependency_type) end nullify_associations = klass.reflect_on_all_associations.select do |reflection| dependency_type = reflection.&.dig(:dependent)&.to_sym DEPENDENCY_NULLIFY.include?(dependency_type) end # Iterate through the assoc names, if there are any :through assocs, then rename the association # - Rails interpretation of any dependencies of a :through association is to apply it to # the leaf association at the end of the :through chain(s) association_dependencies = {} ( destroy_associations.map(&:name) + nullify_associations.map(&:name) ).collect do |assoc_name| root_association_name = find_root_association_from_through_assocs(klass, assoc_name) association_dependencies[root_association_name] = klass.reflect_on_association(assoc_name)..dig(:dependent) end # Using association names as keys helps remove duplicates - from dependent options on through associations and root associations. association_dependencies.each do |association_name, dependency_type| association_parser(klass, association_name, dependency_type, dependency_path) end end |
#traverse_restricted_dependency?(parent_class, reflection) ⇒ Boolean
return [Boolean]
-
true if valid
-
false if not valid
266 267 268 269 270 271 272 273 274 275 276 277 278 |
# File 'lib/bulk_dependency_eraser/query_schema_parser.rb', line 266 def traverse_restricted_dependency? parent_class, reflection # Return true if we're going to destroy all restricted return true if opts_c.force_destroy_restricted report_error( " #{parent_class.name}'s assoc '#{reflection.name}' has a restricted dependency type. If you still wish to destroy, use the 'force_destroy_restricted: true' option " ) return false end |