Tabelas sem classe são possíveis com o Datamapper?

8

Eu tenho uma classe Item com os seguintes atributos:

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

Como os dois últimos atributos terão vários valores, eu os removi e criei novos esquemas como:

itemID,required_skill( itemIDé chave estrangeira, itemID and required_skillé chave primária).

Agora, estou confuso sobre como criar / usar esta nova tabela. Aqui estão as opções que me vieram à mente:

1) O relacionamento entre Items e Required_skills é um para muitos, portanto, posso criar uma classe RequiredSkill, que pertence ao Item, que por sua vez possui n RequiredSkills. Então eu posso fazer Item.get(1).requiredskills. Isso me parece mais lógico e fornece métodos e consultas como:

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

2) Como as required_skills podem ser consideradas constantes (já que elas se assemelham a regras), eu posso colocá-las em um banco de dados hash ou gdbm ou em outra tabela sql e consulta a partir daí, que eu não prefiro.

Minha pergunta é: existe algo como uma tabela sem modela no datamapper, em que o datamapper é responsável pela criação e integridade da tabela e me permite consultá-lo da maneira do datamapper, mas não requer uma classe, como eu posso fazê-lo no sql ?

Resolvi meu problema usando a primeira maneira: criei uma nova classe para cada processo de normalização (que aparece como associação um para muitos acima). No entanto, eu sou novo na programação orientada a objetos e não sei se a criação de uma nova classe para cada normalização é a maneira usual de fazê-lo no datamapper, ou melhor, em um hack? Isso, e se existe uma maneira melhor de fazer isso, é o que eu gostaria de saber.

@JustinC

Relendo as associações do datamapper.org várias vezes, agora vejo que o datamapper certamente exige classes separadas para junções. Então você respondeu minha pergunta. No entanto, como Robert Harvey colocou a recompensa, sinto a responsabilidade de esperar um pouco mais por uma resposta sobre uma maneira dinâmica.

Seu código reclamou Cannot find the child_model Container for Item in containers. Eu consegui fazê-lo funcionar com o segundo exemplo de associação auto-referencial como abaixo (colocando aqui como referência a outras pessoas):

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

Então eu posso fazer:

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

para exigir itens e:

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

para listar requisitos, que são realmente ótimos.

Depois de ler sua resposta, vejo que ainda estou para normalizar ainda mais meu esquema de banco de dados. Para ser sincero, não vejo o ponto de definir a relação entre matérias-primas e produtos como auto-referencial. Quero dizer, se esse fosse um programa pequeno, certamente usaria um hash {:jelly => ":sugar => 3, :water => 5"}para refletir os itens e valores necessários, de acordo com o princípio YAGNI. Fazer isso como na primeira opção já me fornece consultas e métodos tão simples quanto os fornecidos pela associação auto-referencial. (No entanto, devo admitir que ele se parece mais com um procedimento armazenado do que com uma chamada de método para um objeto.)

Então, você poderia explicar os benefícios do uso de uma associação auto-referencial, difícil de entender / implementar para mim, em comparação com minha abordagem mais simples? Eu sou novo no OOP e me pergunto se estou meio que submodelo.

barerd
fonte

Respostas:

2

Eu acho que o que você está procurando no nível conceitual do SQL é uma tabela de junção (mapa, link, resolvedor, pivô também são nomes comuns para lidar com muitos e muitos relacionamentos). Essas tabelas de junção são geralmente tabelas intermediárias; no entanto, atributos adicionais podem e são frequentemente adicionados a eles.

A intenção do pseudo-esquema declarado é um pouco obscura, mas acho que o que você pretendeu é que os itens possam exigir várias habilidades; os itens também podem exigir outros itens, cada um com suas próprias habilidades necessárias, possivelmente com itens necessários e assim por diante, em muitos níveis. Cuidado com referências circulares em seus relacionamentos de auto-referência [muitos-para-muitos], como o que poderia acontecer em 'containerItemMaps'. O pseudo-esquema a seguir reflete como eu imagino a intenção do 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

A terminologia do ActiveRecord sugere 'has_and_belongs_to_many' como o tipo de associação que um relacionamento em um modelo de dados deve ser usado nessa situação. Para obter mais informações, consulte a página em Associações de datamapper.org . Especificamente, as seções intituladas 'Tem e pertence a muitos (ou muitos-para-muitos)' e 'Auto-referencial muitos para muitos relacionamentos'

Como não sou um cara ruby ​​neste momento, só posso confundir com o código ruby ​​para dar um exemplo, mas esta é minha melhor aproximação de como seria sua classe de item:

# 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

E a tabela de mapas para auto-referência de muitos a muitos itens, por exemplo, itens necessários:

#revised
class ComponentMap
  include DataMapper::Resource

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

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

Para completar:

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

Revisões:

Observando suas preocupações, instalei um interpretador e depurador para verificar o código compilado e o sql emitido era mais ideal. Originalmente, eu estava apenas fazendo um exame superficial da documentação. As estruturas acima devem produzir sql geralmente bem estruturado a partir dos mapeamentos.

Independentemente de quais campos e estruturas você usa e de qual ORM você escolhe (datamapper ou algum outro provedor), convém executar o código através do depurador e prestar atenção ao sql que ele emite, pois às vezes os mapeamentos não são necessariamente o que você pode esperar primeiro.

Uma segunda observação sobre as tabelas de junção (skillMap e componentMap): observe minha inclusão de campos adicionais (quantidade e domínio). Estes parecem ser um ajuste natural para o tipo de aplicativo originalmente descrito, mesmo que não tenha sido originalmente especificado. Em uma receita, alguns ingredientes são comuns entre muitas combinações diferentes, no entanto, a quantidade de receita para receita varia. Para habilidades, como ingredientes, o nível de habilidade necessário para executar determinadas atividades varia e, portanto, adicionei um campo de domínio à tabela de junções skillMap.

Obviamente, você provavelmente deseja adicionar regras de negócios e funções auxiliares apropriadas (para acessar a composição de coleções programaticamente, como adicionar e remover elementos, adicionar e remover grupos de elementos etc.).

Espero que isso demonstre um pouco melhor a razão pela qual você pode considerar e usar a tabela de junção em um hash direto. É claro que cada aplicativo específico é diferente, e talvez a capacidade de especificar aspectos adicionais do relacionamento entre itens e habilidade e itens e outros itens não seja necessária no seu caso.

Ter e utilizar o controle extra na definição explícita dos relacionamentos tem muitas vantagens em contar com um mapeamento dinâmico / mágico. Em alguns casos, sinto que é realmente necessário e, no caso de muitos para muitos, isso é demonstrado. Para um para muitos, é mais fácil deduzir o relacionamento, e usar um método mais dinâmico de gerar os mapeamentos (por exemplo, tem n,: <grupo de atributos>,: através de> Recurso) seria aceitável.

JustinC
fonte
Por favor, veja minha edição acima. Lamento não ter conseguido expressá-lo em palavras mais curtas.
27512 barerd
Muito obrigado. Nos dois dias, comparei várias opções para um inventário. Vi que, usando a has n, :items, :through => Inventoryabordagem, obtive consultas mais eficientes em comparação com uma abordagem semelhante a hash. Qual depurador você usou pelo caminho?
30512 barerd
Para as partes sql: 'DataMapper :: Logger.new ($ stdout,: debug)' E no lado do ruby, a gema 'ruby-debug'; ambos de dentro do eclipse
JustinC 30/10/12