{"id":37,"date":"2008-08-01T18:54:43","date_gmt":"2008-08-01T18:54:43","guid":{"rendered":"http:\/\/euve3303.vserver.de\/stefan\/blog\/?p=39"},"modified":"2011-12-02T21:02:14","modified_gmt":"2011-12-02T20:02:14","slug":"richtiges-many-to-many-mit-grails","status":"publish","type":"post","link":"https:\/\/cogito-ergo-blog.de\/blog\/2008\/08\/01\/richtiges-many-to-many-mit-grails\/","title":{"rendered":"Richtiges Many-To-Many mit Grails"},"content":{"rendered":"<p style=\"margin-bottom: 0cm;\">Will man mit GRAILS ein Real-World-System beschreiben und dabei das schnelle Erstellen von CRUD-Applikationen nutzen, steht man schnell vor dem Problem dass<br \/>\ndamit Many-To-Many Verkn\u00fcpfungen nicht gehen.<\/p>\n<p style=\"margin-bottom: 0cm;\">Es wird zwar angeboten, wie bei One-To-Many, aber wenn \u201eAdd &#8230;\u201c gew\u00e4hlt wird, kann zwar ein neues Child angelegt werden, aber die Verbindung wird nicht<br \/>\nhergestellt.<\/p>\n<p style=\"margin-bottom: 0cm;\">Auch beim One-To-Many gibt es auf der Many-Side Einschr\u00e4nkungen: es kann nur ein Child per \u201eAdd &#8230;\u201c hinzugef\u00fcgt werden. Weder wird das Zuordnen eines bereits<br \/>\nangelegten Childs erm\u00f6glicht, noch wird das L\u00f6schen angeboten.<\/p>\n<p style=\"margin-bottom: 0cm;\">Ausserdem ist die Doku nicht konsistent: Beim Domain-Mapping mit GORM hei\u00dft es:<\/p>\n<p style=\"margin-left: 1.25cm; margin-bottom: 0cm;\">\u201eGrails supports many-to-many relationships by defining a <code>hasMany<\/code> on both<br \/>\nsides of the relationship and having a <code>belongsTo<\/code> on the side that owns the relationship\u201c.<\/p>\n<p style=\"margin-bottom: 0cm;\">Richtig ist aber das <code>belongsTo<\/code> geh\u00f6rt auf die Child-Side, nicht auf die Parent-Side, so wie es auch im zugeh\u00f6rigen Beispiel ist.<\/p>\n<p style=\"margin-bottom: 0cm;\">Wichtig ist hier n\u00e4mlich:<\/p>\n<p style=\"margin-left: 1.25cm; margin-bottom: 0cm;\">\u201eThe owning side of the relationship, in this case <code>Author<\/code>, takes<br \/>\nresponsibility for persisting the relationship and is the only side<br \/>\nthat can cascade saves across.\u201c<\/p>\n<p style=\"margin-bottom: 0cm;\">Also nur die Parent-Side hat den Cascading-Save und somit sollte nur von dort aus ge\u00e4ndert werden. Jedenfalls werden nur die von der Parent-Side ausgemachten<br \/>\n\u00c4nderungen ohne weiteres persistiert. W\u00fcrde man von der Client-Side aus \u00e4ndern, dann m\u00fc\u00dfte die zugeh\u00f6rige \u00c4nderung im Parent explizit gesetzt und persistiert werden.<\/p>\n<p style=\"margin-bottom: 0cm;\">Um die Unterst\u00fctzung f\u00fcr Many-To-Many in den mit Scaffolding generierten Views zu bekommen, sind die Templates anzupassen. Wie das geht beschreibt der folgende<br \/>\nArtikel.<\/p>\n<p><!--more--><\/p>\n<p style=\"margin-bottom: 0cm;\">Viel von den folgenden Codeschnipseln und Konzepten ist durch andere Artikel inspiriert (siehe Referenzen). Was hier zus\u00e4tzlich dargestellt wird, ist die komplette Logik zu<br \/>\ngenerieren auch in den Controllern. Und zu guter letzt ist ein komplettes Beispiel beigef\u00fcgt.<\/p>\n<p style=\"margin-bottom: 0cm;\">Der erste Schritt f\u00fcr eigene, angepasste Templates ist immer:<\/p>\n<p style=\"margin-bottom: 0cm;\"><span style=\"font-family: courier new,courier,monospace;\">grails install-templates<\/span><\/p>\n<p style=\"margin-bottom: 0cm;\">Damit kopiert <a href=\"http:\/\/www.grails.org\/\">GRAILS<\/a> die Standard-Templates lokal ins Projekt und benutzt fortan diese.<\/p>\n<p style=\"margin-bottom: 0cm;\">Dann sucht man im Verzeichnis src\/templates\/scaffolding das renderEditor.template. Dieses ist f\u00fcr das Erzeugen der Edit-Controls f\u00fcr die einzelnen Properties<br \/>\neiner Domain-Klasse zust\u00e4ndig.<\/p>\n<p style=\"margin-bottom: 0cm;\">Hier gibt es eine Methode \u201erenderOneToMany\u201c, die zun\u00e4chst eine Liste der bereits<br \/>\nverkn\u00fcpften Objekte mit Link auf deren \u201eShow\u201c-Url ausgibt und dann das schon bekannte \u201eAdd &#8230;\u201c anbietet. Dieses \u201eAdd\u201c wird durch folgende Zeile erzeugt:<\/p>\n<pre class=\"brush: groovy; title: ; notranslate\" title=\"\">\r\nif( property.oneToMany ) {\r\n    pw.println\r\n&quot; &lt;span\r\nclass=\\&quot;buttons\\&quot;&gt;&lt;g:link\r\n   controller=\\&quot;${property.referencedDomainClass.propertyName}\\&quot;\r\nparams=\\&quot;['${domainClass.propertyName}.id':${domainClass.propertyName}?.id]\\&quot;\r\naction=\\&quot;create\\&quot;  class=\\&quot;create\\&quot;&gt;Add&lt;\/g:link&gt;&lt;\/span&gt;&quot;     \r\n }      \r\n if( property.isOwningSide() ) { \r\n \tpw.println\r\n&quot; &lt;span\r\nclass=\\&quot;buttons\\&quot;&gt;&lt;g:link\r\ncontroller=\\&quot;${property.referencedDomainClass.propertyName}\\&quot;\r\nparams=\\&quot;['${domainClass.propertyName}.id':${domainClass.propertyName}?.id,\r\n'source':'${domainClass.propertyName}',\r\n'class':'${property.referencedDomainClass.name}',\r\n'dest':'${property.name}','callback':'link']\\&quot;\r\naction=\\&quot;list\\&quot; class=\\&quot;save\\&quot;&gt;Assoc&lt;\/g:link&gt;&lt;\/span&gt;&quot;;   \r\n \tpw.println\r\n&quot; &lt;span\r\nclass=\\&quot;buttons\\&quot;&gt;&lt;g:link\r\ncontroller=\\&quot;${property.referencedDomainClass.propertyName}\\&quot;\r\nparams=\\&quot;['${domainClass.propertyName}.id':${domainClass.propertyName}?.id,\r\n'source':'${domainClass.propertyName}',\r\n'class':'${property.referencedDomainClass.name}',\r\n'dest':'${property.name}','callback':'unlink']\\&quot;\r\naction=\\&quot;list\\&quot; class=\\&quot;delete\\&quot;&gt;Remove&lt;\/g:link&gt;&lt;\/span&gt;&quot;;    \r\n }\r\n<\/pre>\n<p>Dadurch werden zus\u00e4tzlich ein \u201eAssoc &#8230;\u201c zum Verbinden und ein \u201eRemove &#8230;\u201c angeboten, falls man von der \u201eOwning-Side\u201c aus das Objekt editiert.<\/p>\n<p style=\"margin-bottom: 0cm;\">Somit kann man einerseits auch beim One-To-Many die Assoziation vom Parent aus direkt \u00e4ndern. Aber \u2013 viel wichtiger \u2013 Many-To-Many funktioniert so endlich wie<br \/>\ngew\u00fcnscht.<\/p>\n<p style=\"margin-bottom: 0cm;\">Um das Bild komplett zu machen, fehlen allerdings noch zwei Bausteine. Die neuen Buttons \u201eAssoc\u201c und \u201eRemove\u201c leiten zun\u00e4chst beide zur \u201eList\u201c-Action der verkn\u00fcpften Klasse weiter, somit wird der normale List-View verwendet, um das Element auszuw\u00e4hlen, welches verkn\u00fcpft bzw. gel\u00f6scht werden soll. Damit diese Auswahl funktioniert, muss der List-View entsprechend erweitert werden. Im Template<br \/>\nlist.gsp wird der \u201eShow\u201c-Link:<\/p>\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n&lt;g:link action=&quot;show&quot; id=&quot;\\${${propertyName}.id}&quot;&gt;\r\n\t\\${fieldValue(bean:${propertyName}, field:'${p.name}')}\r\n&lt;\/g:link&gt;\r\n<\/pre>\n<p>fallweise durch einen \u201eChoose\u201c-Link ersetzt:<\/p>\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n&lt;g:if test=&quot;\\${params.callback}&quot;&gt;\r\n  &lt;g:link action=&quot;choose&quot; params=&quot;\\${params}&quot; id=&quot;\\${${propertyName}.id}&quot;&gt;\r\n      \\${${propertyName}.${p.name}?.encodeAsHTML()}\r\n  &lt;\/g:link&gt;\r\n&lt;\/g:if&gt;\r\n\r\n&lt;g:if test=&quot;\\${!params.callback}&quot;&gt;\r\n  &lt;g:link action=&quot;show&quot; id=&quot;\\${${propertyName}.id}&quot;&gt;\r\n     \\${${propertyName}.${p.name}?.encodeAsHTML()}\r\n  &lt;\/g:link&gt;\r\n&lt;\/g:if&gt;\r\n<\/pre>\n<p style=\"margin-bottom: 0cm;\">Dies geschieht \u2013 wie man sieht \u2013 immer dann, wenn der Callback-Parameter gesetzt ist. Dieser Callback-Parameter wiederum beschreibt, was das \u201eChoose\u201c jeweils bewirken soll, n\u00e4mlich entweder ein \u201eLink\u201c oder ein \u201eUnlink\u201c. Damit das so funktioniert leitet die \u201eChoose\u201c-Action im Controller jeweils auf die \u201eLink\u201c oder \u201eUnlink\u201c-Action weiter.<\/p>\n<h3>Controller-Templates<\/h3>\n<p style=\"margin-bottom: 0cm;\">Diese drei neuen Actions sind in den standardm\u00e4ssig generierten Controllern nicht vorhanden. Auch hier muss also das Template f\u00fcr den Controller angepasst werden.<\/p>\n<p style=\"margin-bottom: 0cm;\">Das Template f\u00fcr die Controller findet sich ebenso unter src\/templates\/scaffolding und hei\u00dft Controller.groovy. Hier werden am Ende die folgenden Zeilen eingef\u00fcgt:<\/p>\n<pre class=\"brush: groovy; title: ; notranslate\" title=\"\">\r\ndef choose = {\r\n   redirect(controller:params.source,action:params.callback,params:params)\r\n}\r\n\r\ndef link = {  def ${propertyName} = ${className}.get(params[&quot;${propertyName}.id&quot;])\r\ndef toLink = grailsApplication.getClassForName( params[&quot;class&quot;]).get(params[&quot;id&quot;])\r\ndef d = params['dest']\r\n  ${propertyName}.&quot;\\${d}&quot;.add( toLink );\r\n  render(view:'edit',model:[${propertyName}:${propertyName}])\r\n}\r\n\r\ndef unlink = {  def ${propertyName} = ${className}.get(params[&quot;${propertyName}.id&quot;])\r\ndef toUnlink = grailsApplication.getClassForName( params[&quot;class&quot;]).get(params[&quot;id&quot;])\r\ndef d = params['dest']\r\n  ${propertyName}.&quot;\\${d}&quot;.remove( toUnlink );\r\n  render(view:'edit',model:[${propertyName}:${propertyName}])\r\n}\r\n<\/pre>\n<p style=\"margin-bottom: 0cm;\">Mit den \u201eLink\u201c und \u201eUnlink\u201c Actions kann jeder Controller zwischen beliebigen Domain-Klassen per \u201eadd\u201c und \u201eremove\u201c Verbindungen erzeugen oder wieder entfernen.<\/p>\n<p style=\"margin-bottom: 0cm;\">Mit diesen drei \u2013 eigentlich minimalen \u2013 \u00c4nderungen funktionieren nun auch Many-To-Many Verkn\u00fcpfungen mit GRAILS und Scaffolding out of the box.<\/p>\n<h3>Ausblick<\/h3>\n<p style=\"margin-bottom: 0cm;\">Mit der vorgeschlagenen L\u00f6sung wird zur Auswahl beim L\u00f6schen und Verkn\u00fcpfen der List-View \u201emissbraucht\u201c. Das ist nicht in jedem Fall optimal. Denkbar w\u00e4re auch ein eigener Choose-View, der eine sch\u00f6nere Darstellung hat evtl. auch im Popup-Fenster.<\/p>\n<p style=\"margin-bottom: 0cm;\">Eine weitere Unsch\u00f6nheit ist die Tatsache, dass der List-View auch beim L\u00f6schen immer alle Elemente anzeigt und nicht nur die aktuell verkn\u00fcpften.<\/p>\n<h3>Referenzen<\/h3>\n<p style=\"margin-bottom: 0cm;\"><a href=\"http:\/\/www.ibm.com\/developerworks\/web\/library\/j-grails04158\/index.html\">http:\/\/www.ibm.com\/developerworks\/web\/library\/j-grails04158\/index.html<\/a><\/p>\n<p style=\"margin-bottom: 0cm;\"><a href=\"http:\/\/www.stainlesscode.com\/site\/comments\/grails_one_to_many_scaffolding\/\">http:\/\/www.stainlesscode.com\/site\/comments\/grails_one_to_many_scaffolding\/<\/a><\/p>\n<p style=\"margin-bottom: 0cm;\"><a href=\"http:\/\/reverttoconsole.com\/2008\/06\/grails-manytomany-gorm-example\/\">http:\/\/reverttoconsole.com\/2008\/06\/grails-manytomany-gorm-example\/<\/a><\/p>\n<h3>Beispiel<\/h3>\n<p style=\"margin-bottom: 0cm;\">Download <a href=\"\/blog\/uploads\/grails-demo.rar\">hier<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Will man mit GRAILS ein Real-World-System beschreiben und dabei das schnelle Erstellen von CRUD-Applikationen nutzen, steht man schnell vor dem Problem dass damit Many-To-Many Verkn\u00fcpfungen nicht gehen. Es wird zwar angeboten, wie bei One-To-Many, aber wenn \u201eAdd &#8230;\u201c gew\u00e4hlt wird, &hellip; <a href=\"https:\/\/cogito-ergo-blog.de\/blog\/2008\/08\/01\/richtiges-many-to-many-mit-grails\/\">Weiterlesen <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9],"tags":[],"_links":{"self":[{"href":"https:\/\/cogito-ergo-blog.de\/blog\/wp-json\/wp\/v2\/posts\/37"}],"collection":[{"href":"https:\/\/cogito-ergo-blog.de\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cogito-ergo-blog.de\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cogito-ergo-blog.de\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cogito-ergo-blog.de\/blog\/wp-json\/wp\/v2\/comments?post=37"}],"version-history":[{"count":5,"href":"https:\/\/cogito-ergo-blog.de\/blog\/wp-json\/wp\/v2\/posts\/37\/revisions"}],"predecessor-version":[{"id":10028,"href":"https:\/\/cogito-ergo-blog.de\/blog\/wp-json\/wp\/v2\/posts\/37\/revisions\/10028"}],"wp:attachment":[{"href":"https:\/\/cogito-ergo-blog.de\/blog\/wp-json\/wp\/v2\/media?parent=37"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cogito-ergo-blog.de\/blog\/wp-json\/wp\/v2\/categories?post=37"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cogito-ergo-blog.de\/blog\/wp-json\/wp\/v2\/tags?post=37"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}