{"id":2933,"date":"2019-03-04T16:38:13","date_gmt":"2019-03-04T15:38:13","guid":{"rendered":"https:\/\/sgaul.de\/?p=2933"},"modified":"2019-03-04T14:01:24","modified_gmt":"2019-03-04T13:01:24","slug":"vollstaendige-dependent-einstellungen-in-rails-models-testen","status":"publish","type":"post","link":"https:\/\/sgaul.de\/2019\/03\/04\/vollstaendige-dependent-einstellungen-in-rails-models-testen\/","title":{"rendered":"Vollst\u00e4ndige Dependent-Einstellungen in Rails-Models testen"},"content":{"rendered":"\n

Ein selten, aber leider regelm\u00e4\u00dfig wiederkehrendes Problem sind Fremdschl\u00fcsselbeziehungen beim L\u00f6schen. In Rails muss auf Seite des Schl\u00fcsselziels definiert werden, ob ein Fremdschl\u00fcssel auf null gesetzt werden darf oder ob das ganze Model gel\u00f6scht werden muss. Vergisst man diese Konfiguration wirft die Datenbank beim L\u00f6schen einen Fehler. Um dies zu vermeiden m\u00f6chte ich alle Fremdschl\u00fcssel, die auf eine Model-Tabelle zeigen, \u00fcberpr\u00fcfen, ob sie entsprechend konfiguriert sind. Hierf\u00fcr verwende ich ein shared Example in Rspec.<\/p>\n\n\n\n

Fremdschl\u00fcssel finden<\/h3>\n\n\n\n

Die Schl\u00fcssel f\u00fcr das beschriebene Model lassen sich in zumindest mit Postgres folgenderma\u00dfen auflisten:<\/p>\n\n\n\n

def foreign_keys(to_table)\n  ActiveRecord::Base.connection.tables.map do |table_name|\n    ActiveRecord::Base.connection.foreign_keys(table_name).select{ |index| index.to_table == to_table }\n  end.flatten\nend<\/code><\/pre>\n\n\n\n

Konfigurierte Assoziationen finden<\/h3>\n\n\n\n

Die Assoziationen eines Models finden wir mit folgendem Ansatz. Wir ignorieren Assoziationen mit Scopes, da hier nicht ohne weiteres festgestellt werden kann, ob diese alle m\u00f6glichen Verbindungen abdecken. Auch beschr\u00e4nken wir uns auf Has-many- und Has-one-Beziehungen, da diese zumindest aktuell die einzig relevanten sind. Sollte hier etwas fehlen ist dies auch nicht weiter tragisch, da der Test im Zweifel lieber zu viel als zu wenig bem\u00e4ngeln soll.<\/p>\n\n\n\n

def safe_associations(target_class)\n  associations =\n    target_class.reflect_on_all_associations(:has_many) +\n    target_class.reflect_on_all_associations(:has_one)\n  associations.select{ |assoc| assoc.options[:dependent].in?(%i{destroy nullify}) && assoc.scope.nil? }\nend<\/code><\/pre>\n\n\n\n

Shared Example f\u00fcr verd\u00e4chtige Keys<\/h3>\n\n\n\n

Nun k\u00f6nnen die g\u00fcltigen Konfigurationen von den Fremdschl\u00fcsseln abziehen. Nur wenn die Differenz leer ist, sind alle Schl\u00fcssel konfiguriert. Da wir bei Scopes etwas \u00fcbervorsichtig sind, erlauben wir aber auch eine ignore-Option, um bestimmte Fremdschl\u00fcssel zu ignorieren:<\/p>\n\n\n\n

shared_examples 'a dependent model' do  |options = {}|\n  it 'has a dependent option for has_* associations' do\n    foreign_keys = foreign_keys(described_class.table_name)\n    associations = safe_associations(described_class)\n    ignore = options[:ignore] || []\n\n    suspicious_keys = \n      foreign_keys.map{ |key| \"#{key.from_table}.#{key.column}\" } -\n      associations.map{ |assoc| \"#{assoc.klass.table_name}.#{assoc.foreign_key}\" } -\n      ignore\n\n    expect(suspicious_keys).to be_empty\n  end\n\n  # ...\nend<\/code><\/pre>\n\n\n\n

Nun noch die Fehlermeldungen modifizieren und etwas Doku erg\u00e4nzen und schon kann das Example in Model-Specs eingebunden werden:<\/p>\n\n\n\n

describe BookWithChapters do\n  it_behaves_like 'a dependent model'\nend<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"

Ein selten, aber leider regelm\u00e4\u00dfig wiederkehrendes Problem sind Fremdschl\u00fcsselbeziehungen beim L\u00f6schen. In Rails muss auf Seite des Schl\u00fcsselziels definiert werden, ob ein Fremdschl\u00fcssel auf null gesetzt werden darf oder ob das ganze Model gel\u00f6scht werden muss. Vergisst man diese Konfiguration wirft die Datenbank beim L\u00f6schen einen Fehler. Um dies zu vermeiden m\u00f6chte ich alle Fremdschl\u00fcssel,… Vollst\u00e4ndige Dependent-Einstellungen in Rails-Models testen<\/span> weiterlesen<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[91],"tags":[597,529,553,570,552],"_links":{"self":[{"href":"https:\/\/sgaul.de\/wp-json\/wp\/v2\/posts\/2933"}],"collection":[{"href":"https:\/\/sgaul.de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sgaul.de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sgaul.de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/sgaul.de\/wp-json\/wp\/v2\/comments?post=2933"}],"version-history":[{"count":2,"href":"https:\/\/sgaul.de\/wp-json\/wp\/v2\/posts\/2933\/revisions"}],"predecessor-version":[{"id":2935,"href":"https:\/\/sgaul.de\/wp-json\/wp\/v2\/posts\/2933\/revisions\/2935"}],"wp:attachment":[{"href":"https:\/\/sgaul.de\/wp-json\/wp\/v2\/media?parent=2933"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sgaul.de\/wp-json\/wp\/v2\/categories?post=2933"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sgaul.de\/wp-json\/wp\/v2\/tags?post=2933"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}