I have an Item class with the following attributes:
itemId,name,weight,volume,price,required_skills,required_items
.
Since the last two attributes are going to be multivalued, I removed them and create new schemes like:
itemID,required_skill
(itemID
is foreign key, itemID and required_skill
is primary key.)
Now, I'm confused how to create/use this new table. Here are the options that came to my mind:
1) The relationship between Items and Required_skills is one-to-many, so I may create a RequiredSkill class, which belongs_to Item, which in turn has n RequiredSkills. Then I can do Item.get(1).requiredskills
. This sounds most logical to me and provides me methods and queries like:
sugar.components.create :raw_material => water.name
sugar.components.create :raw_material => sugarcane.name
sugar.components
sugar.components.count
2) Since required_skills may well be thought of as constants (since they resemble rules), I may put them into a hash or gdbm database or another sql table and query from there, which I don't prefer.
My question is: is there something like a modelless table in datamapper, where datamapper is responsible from the creation and integrity of the table and allows me to query it in datamapper way, but does not require a class, like I may do it in sql?
I solved my problem by using the first way: I created a new class for each normalization process (which appears as one-to-many association above). However, I am new to object oriented programming and I don't know if creating a new class for each such normalization is the usual way to do it in datamapper, or rather a hack? This, and whether there is a better way to do this, is what I would like to know.
@JustinC
Rereading the datamapper.org Associations several times, now I see that datamapper certainly requires separate classes for joins. So you answered my question. However, since Robert Harvey placed the bounty, I feel the responsibility to wait a little more for a response about a dynamic way.
Your code complained with Cannot find the child_model Container for Item in containers
. I managed to get it work with the second example of self referential association like below (putting here as a reference to others):
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
So I can do:
jelly = Item.get :name => "Jelly"
sugar = Item.get :name => "Sugar"
jelly.require sugar
to require items and:
jelly.required_items.each { |i|; puts i.name }
to list requirements, which are really great.
After reading your answer, I see that I am yet to normalize my database schema further. To be honest, I don't see the point of defining the relationship between rawmaterials and products as self referential. I mean, if that would be a small program, I certainly would use a hash like {:jelly => ":sugar => 3, :water => 5"}
to reflect required items and amounts, according to YAGNI principle. Doing it as in the 1st option already provides me queries and methods as simple as the ones provided by the self referential association. (However, I must admit that it looks more like a stored procedure rather than a method call to an object.)
So, could you very mind to explain the benefits of using a self referential association, which is difficult to understand/implement for me, compared to my simpler approach? I am new to OOP and wonder if I'm sort of undermodelling.