Tablas sin clases posibles con Datamapper?

8

Tengo una clase de artículo con los siguientes atributos:

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

Como los dos últimos atributos serán multivalorizados, los eliminé y creé nuevos esquemas como:

itemID,required_skill( itemIDes la clave externa, itemID and required_skilles la clave primaria).

Ahora, estoy confundido sobre cómo crear / usar esta nueva tabla. Estas son las opciones que me vinieron a la mente:

1) La relación entre Items y Required_skills es de uno a muchos, por lo que puedo crear una clase RequiredSkill, que pertenece a Item, que a su vez tiene n RequiredSkills. Entonces puedo hacer Item.get(1).requiredskills. Esto me parece más lógico y me proporciona métodos y consultas como:

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

2) Dado que required_skills bien puede considerarse como constantes (ya que se parecen a las reglas), puedo ponerlas en una base de datos hash o gdbm u otra tabla sql y consultar desde allí, lo que no prefiero.

Mi pregunta es: ¿hay algo así como una tabla sin modelo en datamapper, donde datamapper es responsable de la creación e integridad de la tabla y me permite consultarla en forma de datamapper, pero no requiere una clase, como puedo hacerlo en sql ?

Resolví mi problema usando la primera forma: creé una nueva clase para cada proceso de normalización (que aparece como una asociación de uno a muchos arriba). Sin embargo, soy nuevo en la programación orientada a objetos y no sé si crear una nueva clase para cada normalización de este tipo es la forma habitual de hacerlo en datamapper, o más bien un hack? Esto, y si hay una mejor manera de hacerlo, es lo que me gustaría saber.

@JustinC

Al releer las asociaciones de datamapper.org varias veces, ahora veo que datamapper ciertamente requiere clases separadas para las uniones. Entonces respondiste mi pregunta. Sin embargo, desde que Robert Harvey colocó la recompensa, siento la responsabilidad de esperar un poco más por una respuesta sobre una forma dinámica.

Tu código se quejó con Cannot find the child_model Container for Item in containers. Logré que funcione con el segundo ejemplo de asociación autorreferencial como se muestra a continuación (poniendo aquí como referencia a otros):

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

Entonces puedo hacer:

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

para requerir artículos y:

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

para enumerar los requisitos, que son realmente geniales.

Después de leer su respuesta, veo que todavía tengo que normalizar más mi esquema de base de datos. Para ser honesto, no veo el punto de definir la relación entre materias primas y productos como autorreferencial. Quiero decir, si ese fuera un pequeño programa, ciertamente usaría un hash {:jelly => ":sugar => 3, :water => 5"}para reflejar los artículos y cantidades requeridas, de acuerdo con el principio de YAGNI. Hacerlo como en la primera opción ya me proporciona consultas y métodos tan simples como los proporcionados por la asociación autorreferencial. (Sin embargo, debo admitir que se parece más a un procedimiento almacenado que a una llamada a un objeto).

Entonces, ¿le importaría explicar los beneficios de usar una asociación autorreferencial, que es difícil de entender / implementar para mí, en comparación con mi enfoque más simple? Soy nuevo en OOP y me pregunto si soy una especie de submodelo.

Barerd
fuente

Respuestas:

2

Creo que lo que está buscando a nivel conceptual de SQL es una tabla de unión (mapa, enlace, resolución, pivote también son nombres comunes para manejar relaciones de muchas a muchas). Estas tablas de unión son generalmente tablas intermedias; sin embargo, los atributos adicionales pueden y a menudo se les agregan.

La intención del pseudo esquema indicado es un poco turbia, pero creo que lo que pretendía es que los elementos pueden requerir múltiples habilidades; los artículos también pueden requerir otros artículos, cada uno con sus propias habilidades requeridas, posiblemente los artículos necesarios, y así sucesivamente, a muchos niveles de profundidad. Tenga cuidado con las referencias circulares en sus relaciones de autorreferencia [de muchos a muchos], como lo que podría suceder en 'containerItemMaps'. El siguiente pseudo esquema refleja cómo imagino la intención del OP:

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

La terminología de ActiveRecord sugiere 'has_and_belongs_to_many' como el tipo de asociación de una relación en un modelo de datos para usar en esta situación. Para obtener más información, puede consultar la página de asociaciones de datamapper.org . Específicamente, las secciones tituladas 'Tiene y pertenece a muchas (o muchas-a-muchas)' y 'Relaciones autorreferenciales de muchas a muchas'.

Debido a que no soy un tipo rubí en este momento, solo puedo confundirme con el código rubí para dar un ejemplo, pero esta es mi mejor aproximación de cómo se vería su clase de elemento:

# 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

Y la tabla de mapas para hacer referencia a muchos o muchos elementos, por ejemplo, elementos necesarios:

#revised
class ComponentMap
  include DataMapper::Resource

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

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

Por completitud:

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

Revisiones

Tomando nota de sus preocupaciones, fui e instalé un interpruter y depurador para verificar el código compilado y el sql emitido era más ideal. Originalmente, solo estaba saliendo del examen superficial de la documentación. Las estructuras anteriores deberían producir sql generalmente bien estructurado a partir de las asignaciones.

No importa qué campos y estructuras use, y no importa qué ORM elija (datamapper u otro proveedor), querrá ejecutar el código a través del depurador y prestar atención al sql que emite, ya que a veces las asignaciones no son necesariamente lo que podrías esperar primero.

Una segunda nota sobre las tablas de unión (skillMap y componentMap): tenga en cuenta mi inclusión de campos adicionales (cantidad y dominio). Estos parecen ser un ajuste natural para el tipo de aplicación descrita originalmente, aunque no se haya especificado originalmente. En una receta, algunos ingredientes son comunes entre muchas combinaciones diferentes, sin embargo, la cantidad varía de una receta a otra. Para las habilidades, como los ingredientes, el nivel de habilidad necesario para realizar ciertas actividades varía y, por lo tanto, agregué un campo de dominio al skillMap de la tabla de unión.

Por supuesto, probablemente desee agregar reglas comerciales apropiadas y funciones auxiliares (para acceder a la composición de colecciones mediante programación, como agregar y eliminar elementos, agregar y eliminar grupos de elementos, etc.).

Esperemos que esto demuestre un poco mejor la razón por la que es posible que desee considerar y usar la tabla de unión sobre un hash directo. Por supuesto, cada aplicación específica es diferente, y tal vez la capacidad de especificar aspectos adicionales de la relación entre elementos y habilidades y elementos y otros elementos no sea necesaria en su caso.

Tener y utilizar el control adicional para definir las relaciones explícitamente tiene muchas ventajas sobre confiar en un mapeo dinámico / mágico. En algunos casos, siento que es realmente necesario, y creo que en el caso de muchos a muchos, esto se demuestra. Para uno a muchos, la relación es más fácil de inferir, y sería aceptable utilizar un método más dinámico para generar las asignaciones (por ejemplo, tiene n,: <grupo de atributos>,: a través de => Recurso).

JustinC
fuente
Por favor vea mi edición arriba. Lamento no poder expresarlo en palabras más cortas.
Barerd
Muchas gracias. Durante los 2 días, comparé varias opciones para un inventario. Vi que al usar el has n, :items, :through => Inventoryenfoque, obtuve consultas más eficientes en comparación con un enfoque similar al hash. ¿Qué depurador usaste por cierto?
Barerd
Para las porciones sql: 'DataMapper :: Logger.new ($ stdout,: debug)' Y en el lado rubí, la gema 'ruby-debug'; ambos desde dentro del eclipse
JustinC