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
<ul>
<% compiler.host_targets.mappings.each do |host, targets| -%>
<li>
Host: <%= host.name %>
Targets: <%= targets.to_sentence %>
</li>
<% end -%>
</ul>
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.
<select name="compiler[host_targets][][host_id]">
...
</select>
funktioniert wie gewünscht. Für die target_ids könnte man es so
<input type="checkbox" value="1"
name="compiler[host_targets][][target_ids][]" />
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 Schuerig
mailto:michael@schuerig.de
http://www.schuerig.de/michael/
on 19.04.2008 02:36
on 19.04.2008 09:56
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 :-) Evtl. gibts im Umfeld des LInks auch weitere Lösungsansätze für Deine Wünsche. Schöne Grüße, Björn
on 20.04.2008 18:54
On Saturday 19 April 2008, Michael Schuerig 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. <select name="compiler[host_targets][120870757932348][host_id]"> ... </select> <input type="checkbox" name="compiler[host_targets][120870757932348][target_ids][]" /> 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 Schuerig mailto:michael@schuerig.de http://www.schuerig.de/michael/