Ternäre Beziehungen in Model u nd View

Ich möchte eine Beziehung zwischen Model-Klassen modellieren, die
ähnlich wie die folgende ist. Es gibt die Klassen Compiler und System.
Ein Compiler läuft auf bestimmten Systemen und kann davon abhängig
Code für bestimmte Zielsysteme erzeugen.

Relational gedacht würde ich das so angehen

create_table :compiler_host_targets, :id => false do |t|
t.integer :compiler_id, :null => false
t.integer :host_system_id, :null => false
t.integer :target_system_id, :null => false
end
add_index :host_guest_virtualizations,
[:compiler_id, :host_system_id, :target_system_id],
:unique => true

Damit kann ich in Rails aber nicht viel anfangen, ich muss die
Beziehung “modelisieren” (und darf dabei den Primary Key (id) natürlich
nicht weglassen).

class CompilerHostTarget < ActiveRecord::Base
belongs_to :compiler
belongs_to :host_system, :class_name => ‘System’
belongs_to :target_system, :class_name => ‘System’
end

Da ich das von der Klasse Compiler aus verwenden möchte, muss die etwa
so aussehen

class Compiler < ActiveRecord::Base
has_many :host_targets, :class_name => ‘CompilerHostTarget’ do
def mappings
self.inject(Hash.new { |h, k| h[k] = [] }) do |memo, mpg|
memo[mpg.host_os] << mpg.guest_os
memo
end
end
end
end

Für die Anzeige habe ich damit alles was ich brauche

    <% compiler.host_targets.mappings.each do |host, targets| -%>
  • Host: <%= host.name %> Targets: <%= targets.to_sentence %>
  • <% end -%>

Leider bin ich damit noch ein gutes Stück davon entfernt, diese
Beziehungen auch bearbeiten zu können. Dabei ist das geringere Problem
per Ajax oder purem JavaScript Formularelemente einzufügen und zu
entfernen. Wozu mir die passende Eingebung fehlt, ich noch nicht einmal
sicher bin, ob es überhaupt geht, das ist, wie ich die Elemente –
Selects für Hosts, Checkboxen für Targets – benennen muss, damit ich
einen Params-Hash bekomme, der so aussieht:

{ “compiler” => {
“host_targets” => [
{ “host_id” => “1”, “target_ids” => [“1”, “2”] },
{ “host_id” => “2”, “target_ids” => [“2”] }
]
}
}

Wobei auch hier die host_ids nicht das Problem sind.

...

funktioniert wie gewünscht. Für die target_ids könnte man es so

versuchen – und scheitern. Denn dabei geht die Zuordnung von Targets zu
bestimmten Hosts verloren. Es kann so also nicht funktionieren und
entsprechend merkwürdig sieht der Params-Hash aus, den Rails in einem
Beispielfall mit durchaus selektierten Checkboxen produziert hat

{ “compiler” => {
“host_targets” => [
{ “target_ids” => [], “host_id” => “1” },
{ “target_ids” => [] },
{ “target_ids” => [], “host_id” => “2” },
{ “target_ids” => [] },
{ “target_ids” => [] },
{ “target_ids” => [] }
]
}
}

Soweit der Schadensbericht. Wenn mir nun jemand erklären könnte, wie es
geht…

Michael


Michael S.
mailto:[email protected]
http://www.schuerig.de/michael/

Hallo Michael,
ich hab mich nicht weiter damit beschäftig, aber es gibt (mindestens)
ein
Plugin für zusammengesetzte Primärschlüssel:
z.B. http://compositekeys.rubyforge.org/
Vielleicht ist das ein erster Ansatz? Schau Dir mal den Code an, evtl.
sollte man da auch vorsichtig sein :slight_smile:
Evtl. gibts im Umfeld des LInks auch weitere Lösungsansätze für Deine
Wünsche.

Schöne Grüße,

Björn

On Saturday 19 April 2008, Michael S. wrote:

Leider bin ich damit noch ein gutes Stück davon entfernt, diese
Beziehungen auch bearbeiten zu können. Dabei ist das geringere
Problem per Ajax oder purem JavaScript Formularelemente einzufügen
und zu entfernen.

Für bestimmte Werte von “gelöst”, habe ich das Problem inzwischen so
gelöst:

Ich generiere zufällige Indizes, die allein dazu dienen, das Host-Select
und die Target-Checkboxen einander zuzuordnen.



In der Compiler-Model-Klasse sieht das dann so aus:

class Compiler < ActiveRecord::Base
has_many :host_targets, :class_name => ‘CompilerHostTarget’,
:dependent => :delete_all do
def mappings
self.inject(Hash.new { |h, k| h[k] = [] }) do |hash, mpg|
hash[mpg.host_os] << mpg.guest_os
hash
end
end
end

def host_targets=(host_targets_params)
  # Loesche *alle* bisherigen host-target-Kombinationen
  # fuer diesen Compiler
  host_targets.clear
  mappings = merge_host_target_mappings(host_targets_params)
  # Fuege alle jetzt gueltigen host-target-Kombinationen
  # fuer diesen Compiler ein
  mappings.each do |host_id, target_ids|
    target_ids.each do |gid|
      host_targets.build(:host_os_id => host_id,
        :target_os_id => gid)
    end
  end
end

private

def merge_host_target_mappings(host_targets_params)
  host_targets_params.values.inject(Hash.new { |h, k| h[k] = [] })

do |hash, host_targets|
host_id = host_targets[:host_id].to_i
unless host_id == 0
hash[host_id] |=
Array(host_targets[:target_ids]).map(&:to_i).reject(&:zero?)
end
hash
end
end
end

Das funktioniert so nur, weil die CompilerHostTarget-Objekte keine
Identität unabhängig von ihrer eindeutigen Wertkombination haben, es
sind reine Wertobjekte.

Nach wie vor würden mich Verbesserungsvorschläge interessieren.

Michael


Michael S.
mailto:[email protected]
http://www.schuerig.de/michael/