Klassenlose Tabellen mit Datamapper möglich?

8

Ich habe eine Item-Klasse mit folgenden Attributen:

itemId,name,weight,volume,price,required_skills,required_items.

Da die letzten beiden Attribute mehrwertig sein werden, habe ich sie entfernt und neue Schemata erstellt wie:

itemID,required_skill( itemIDist Fremdschlüssel, itemID and required_skillist Primärschlüssel.)

Jetzt bin ich verwirrt, wie diese neue Tabelle erstellt / verwendet wird. Hier sind die Optionen, die mir in den Sinn kamen:

1) Die Beziehung zwischen Items und Required_skills ist eins zu viele, daher kann ich eine RequiredSkill-Klasse erstellen, die zu Item gehört und wiederum n RequiredSkills hat. Dann kann ich tun Item.get(1).requiredskills. Das klingt für mich am logischsten und bietet mir Methoden und Abfragen wie:

sugar.components.create :raw_material => water.name
sugar.components.create :raw_material => sugarcane.name
sugar.components
sugar.components.count

2) Da require_skills durchaus als Konstanten betrachtet werden können (da sie Regeln ähneln), kann ich sie in eine Hash- oder GDBM-Datenbank oder eine andere SQL-Tabelle einfügen und von dort aus abfragen, was ich nicht bevorzuge.

Meine Frage ist: Gibt es so etwas wie eine modelllose Tabelle in Datamapper, in der Datamapper für die Erstellung und Integrität der Tabelle verantwortlich ist und es mir ermöglicht, sie auf Datamapper-Weise abzufragen, aber keine Klasse erfordert, wie ich es in SQL tun kann ?

Ich habe mein Problem auf die erste Weise gelöst: Ich habe für jeden Normalisierungsprozess eine neue Klasse erstellt (die oben als Eins-zu-Viele-Zuordnung angezeigt wird). Ich bin jedoch neu in der objektorientierten Programmierung und weiß nicht, ob das Erstellen einer neuen Klasse für jede solche Normalisierung die übliche Methode im Datamapper oder eher ein Hack ist. Dies und ob es einen besseren Weg gibt, dies zu tun, würde ich gerne wissen.

@ JustinC

Die Rereading datamapper.org Verbände mehrmals, jetzt sehe ich , dass DataMapper erfordert sicherlich separate Klassen für Joins. Also hast du meine Frage beantwortet. Seit Robert Harvey das Kopfgeld platziert hat, fühle ich mich jedoch verpflichtet, etwas länger auf eine Antwort über einen dynamischen Weg zu warten.

Ihr Code hat sich beschwert Cannot find the child_model Container for Item in containers. Ich habe es geschafft, es mit dem zweiten Beispiel einer selbstreferenziellen Assoziation wie unten zum Laufen zu bringen (hier als Referenz für andere):

class Item
  class Link
    include DataMapper::Resource
    storage_names[:default] = "requirement_links"

    belongs_to :require, "Item", :key => true
    belongs_to :required, "Item", :key => true
  end

  include DataMapper::Resource

  property :id, Serial
  property :name, String, :required => true

  has n, :links_to_required_items, "Item::Link", :child_key => [:require_id]
  has n, :links_to_requiring_items, "Item::Link", :child_key => [:required_id]

  has n, :required_items, self,
    :through => :links_to_required_items,
    :via => :required
  has n, :requiring_items, self,
    :through => :links_to_requiring_items,
    :via => :require

 def require(others)
    required_items.concat(Array(others))
    save
    self
  end

  def unrequire(others)
    links_to_required_items.all(:required => Array(others)).destroy!
    reload
    self
  end
end

Also kann ich tun:

jelly = Item.get :name => "Jelly"
sugar = Item.get :name => "Sugar"
jelly.require sugar

Gegenstände benötigen und:

jelly.required_items.each { |i|; puts i.name }

Anforderungen aufzulisten, die wirklich toll sind.

Nachdem ich Ihre Antwort gelesen habe, sehe ich, dass ich mein Datenbankschema noch weiter normalisieren muss. Um ehrlich zu sein, sehe ich keinen Sinn darin, die Beziehung zwischen Rohstoffen und Produkten als selbstreferenziell zu definieren. Ich meine, wenn das ein kleines Programm wäre, würde ich sicherlich einen Hash verwenden {:jelly => ":sugar => 3, :water => 5"}, der die erforderlichen Elemente und Beträge gemäß dem YAGNI-Prinzip widerspiegelt. Wenn ich es wie in der ersten Option mache, bekomme ich bereits Abfragen und Methoden, die so einfach sind wie die, die von der selbstreferenziellen Zuordnung bereitgestellt werden. (Ich muss jedoch zugeben, dass es eher wie eine gespeicherte Prozedur als wie ein Methodenaufruf für ein Objekt aussieht.)

Könnten Sie also etwas dagegen haben, die Vorteile einer selbstreferenziellen Assoziation zu erklären, die für mich im Vergleich zu meinem einfacheren Ansatz schwer zu verstehen / zu implementieren ist? Ich bin neu bei OOP und frage mich, ob ich irgendwie untermodelliert bin.

Barerd
quelle

Antworten:

2

Ich denke, was Sie auf einer konzeptionellen SQL-Ebene suchen, ist eine Junction-Tabelle (Map, Link, Resolver, Pivot sind auch gebräuchliche Namen für die Behandlung vieler bis vieler Beziehungen). Diese Verbindungstabellen sind im Allgemeinen Zwischentabellen. Es können und werden jedoch häufig zusätzliche Attribute hinzugefügt.

Die Absicht des angegebenen Pseudo-Schemas ist etwas trübe, aber ich denke, Sie haben beabsichtigt, dass Gegenstände mehrere Fähigkeiten erfordern können. Gegenstände können auch andere Gegenstände erfordern, von denen jeder seine eigenen Fähigkeiten besitzt, möglicherweise seine eigenen erforderlichen Gegenstände und so weiter bis zu vielen Ebenen. Achten Sie auf Zirkelverweise in Ihren [vielen-zu-vielen] selbstreferenzierenden Beziehungen, z. B. was in 'containerItemMaps' passieren kann. Das folgende Pseudoschema spiegelt wider, wie ich mir die OP-Absicht vorstelle:

items (itemId PK, itemName, weight, volume, price)

skillMaps ( (itemId, skillId) PK)

skills (skillId PK, skillName)

containerItemMaps ( (containerItemId, componentItemId) PK)
    -- containerItemId is the parent/requiring item id
    -- componentItemId is the child/required item id

In der ActiveRecord-Terminologie wird 'has_and_belongs_to_many' als Zuordnungstyp für eine Beziehung in einem Datenmodell vorgeschlagen, die in dieser Situation verwendet werden soll. Weitere Informationen finden Sie auf der Seite unter datamapper.org Associations . Insbesondere die Abschnitte mit dem Titel "Hat und gehört zu vielen (oder vielen zu vielen)" und "Selbstreferenzielle viele zu vielen Beziehungen".

Da ich zu diesem Zeitpunkt kein Ruby-Typ bin, kann ich nur mit Ruby-Code verwechseln, um ein Beispiel zu nennen. Dies ist jedoch meine beste Annäherung an das, wie Ihre Artikelklasse aussehen würde:

# revised
class Item
   include DataMapper::Resource

   property :id, Serial

   property :name, String, :required => true
   property :weight, Float
   property :volume, Float
   property :price, Float 

   has n, :componentMaps, :child_key => [:container_id]
   has n, :components, self, :through => :componentMaps, :via => :component

   has n, :skillMaps, :child_key => [:skill_id]
   has n, :skills, :through => :skillMaps, :via => :skill    
end

Und die Kartentabelle für die Selbstreferenzierung vieler bis vieler Elemente, z. B. erforderliche Elemente:

#revised
class ComponentMap
  include DataMapper::Resource

  belongs_to :container, 'Item', :key => true
  belongs_to :component, 'Item', :key => true

  property :quantity, Integer, :default => 1
end

Zur Vollständigkeit:

class SkillMap
  include DataMapper::Resource

  belongs_to :item, 'Item', :key => true
  belongs_to :skill, 'Skill', :key => true

  property :mastery, Enum[:none, :aprentice, :journeyman, :craftsman, :master ], :default => :none

end

class Skill
    include DataMapper::Resource

    property :id, Serial
    property :name, String, :required => true

    has n, :skillMap, :child_key =>[:skill_id]     
end

Überarbeitungen:

Als ich Ihre Bedenken bemerkte, installierte ich einen Interpruter und einen Debugger, um den kompilierten Code zu überprüfen, und die ausgegebene SQL war idealer. Ursprünglich hatte ich nur eine flüchtige Prüfung der Dokumentation. Die obigen Strukturen sollten aus den Zuordnungen im Allgemeinen gut strukturiertes SQL erzeugen.

Unabhängig davon, welche Felder und Strukturen Sie verwenden und welches ORM Sie auswählen (Datamapper oder ein anderer Anbieter), möchten Sie den Code über den Debugger ausführen und auf die SQL achten, die er ausgibt, da die Zuordnungen manchmal nicht unbedingt erforderlich sind was Sie zuerst erwarten könnten.

Ein zweiter Hinweis zu den Junction-Tabellen (SkillMap und ComponentMap): Beachten Sie, dass ich zusätzliche Felder (Menge und Beherrschung) einbeziehe. Diese scheinen natürlich zu der ursprünglich beschriebenen Art von Anwendung zu passen, obwohl sie ursprünglich nicht spezifiziert wurden. In einem Rezept sind einige Zutaten in vielen verschiedenen Kombinationen üblich, jedoch variiert die Menge von Rezept zu Rezept. Für Fertigkeiten wie Zutaten variiert die für bestimmte Aktivitäten erforderliche Fertigkeitsstufe. Daher habe ich der Fertigkeitstabelle der Kreuzungstabelle ein Meisterschaftsfeld hinzugefügt.

Natürlich möchten Sie wahrscheinlich geeignete Geschäftsregeln und Hilfsfunktionen hinzufügen (für den programmgesteuerten Zugriff auf die Zusammensetzung von Sammlungen, z. B. Hinzufügen und Entfernen von Elementen, Hinzufügen und Entfernen von Elementgruppen usw.).

Hoffentlich zeigt dies einen etwas besseren Grund, warum Sie die Junction-Tabelle über einen geraden Hash betrachten und verwenden möchten. Natürlich ist jede spezifische Anwendung anders, und möglicherweise ist die Fähigkeit, zusätzliche Aspekte der Beziehung zwischen Gegenständen und Fertigkeiten sowie Gegenständen und anderen Gegenständen anzugeben, in Ihrem Fall nicht erforderlich.

Die zusätzliche Kontrolle bei der expliziten Definition der Beziehungen zu haben und zu nutzen, hat viele Vorteile gegenüber der Verwendung einer dynamischen / magischen Zuordnung. In einigen Fällen halte ich es für wirklich notwendig, und ich denke, im Fall von vielen zu vielen wird dies demonstriert. Für Eins-zu-Viele ist die Beziehung leichter abzuleiten, und die Verwendung einer dynamischeren Methode zum Generieren der Zuordnungen (z. B. hat n ,: <Attributgruppe> ,: bis => Ressource) wäre akzeptabel.

JustinC
quelle
Bitte beachten Sie meine Bearbeitung oben. Entschuldigung, dass ich es nicht in kürzeren Worten ausdrücken konnte.
Barerd
Vielen Dank. In den 2 Tagen habe ich verschiedene Optionen für ein Inventar verglichen. Ich habe gesehen, dass ich mit dem has n, :items, :through => InventoryAnsatz effizientere Abfragen erhalten habe als mit einem Hash-ähnlichen Ansatz. Welchen Debugger haben Sie übrigens benutzt?
Barerd
Für die SQL-Teile: 'DataMapper :: Logger.new ($ stdout ,: debug)' Und auf der Rubinseite das Juwel 'Ruby-Debug'; beide aus der Finsternis
JustinC