I have a many to many association in which the join table has additional columns. I'm modeling a deck management application for Magic the Gathering, and I have the following tables:
DB.create_table(:mtg_database) do Integer :gatherer_id primary_key [:gatherer_id] String :name String :cost String :spell_type String :power_toughness String :rules String :sets end
DB.create_table(:deck) do primary_key :id String :name String :format String :description end
DB.create_table(:deck_card) do foreign_key :deck_id, :deck foreign_key :gatherer_id, :mtg_database primary_key [:deck_id, :gatherer_id] Integer :qty end
The deck_card table contains the relation between a deck and the cards it contains. For each deck and card, it has an additional column named qty which contains the number of such card a deck has. What I would like to do is have a method in a deck model that allows me to add a card to a deck, which will handle the qty field correctly (set it to 1 the first time a card is added to a deck, increment it from that point on). I have managed to make it work, checking an old message from this list related to books and locations, as so:
class Card < Sequel::Model(:mtg_database) unrestrict_primary_key one_to_many :deck_card end
class DeckCard < Sequel::Model(:deck_card) many_to_one :card, :key => :gatherer_id many_to_one :deck end
1.- Is this the best way to handle this? 2.- I would now need a way to iterate through all the deck's cards having access to both the card info (all columns in mtg_database table) along with the qty column from the join table. What would be the best way to do that? 3.- I had to add the unrestrict_primary_key in the Card model. Until I did, I wasn't able to create cards setting the gatherer_id, which is an external id that I want to have as pk of that table. Am I doing something wrong? I'm creating Cards like this:
Sorry if this is a double post. I sent it right after subscribing to the group yesterday, and I haven't received it or see it in groups.google.com.
Hi,
I have a many to many association in which the join table has additional columns. I'm modeling a deck management application for Magic the Gathering, and I have the following tables:
DB.create_table(:mtg_database) do Integer :gatherer_id primary_key [:gatherer_id] String :name String :cost String :spell_type String :power_toughness String :rules String :sets end
DB.create_table(:deck) do primary_key :id String :name String :format String :description end
DB.create_table(:deck_card) do foreign_key :deck_id, :deck foreign_key :gatherer_id, :mtg_database primary_key [:deck_id, :gatherer_id] Integer :qty end
The deck_card table contains the relation between a deck and the cards it contains. For each deck and card, it has an additional column named qty which contains the number of such card a deck has. What I would like to do is have a method in a deck model that allows me to add a card to a deck, which will handle the qty field correctly (set it to 1 the first time a card is added to a deck, increment it from that point on). I have managed to make it work, checking an old message from this list related to books and locations, as so:
class Card < Sequel::Model(:mtg_database) unrestrict_primary_key one_to_many :deck_card end
class DeckCard < Sequel::Model(:deck_card) many_to_one :card, :key => :gatherer_id many_to_one :deck end
class Deck < Sequel::Model(:deck) many_to_many :cards, :join_table=>:deck_card, :right_key => :gatherer_id, :order=> :name one_to_many :deck_card def deck_card(card) deck_card_dataset[:gatherer_id => card.gatherer_id] end def add_card_to_deck card deck_card = deck_card card if deck_card deck_card.update({:qty => deck_card.qty + 1}) else DeckCard.create({:card => card, :deck => self, :qty => 1}) end end end
This works and I'm able to add cards to decks like this:
But I have some questions: 1.- Is this the best way to handle this? 2.- I would now need a way to iterate through all the deck's cards having access to both the card info (all columns in mtg_database table) along with the qty column from the join table. What would be the best way to do that? 3.- I had to add the unrestrict_primary_key in the Card model. Until I did, I wasn't able to create cards setting the gatherer_id, which is an external id that I want to have as pk of that table. Am I doing something wrong? I'm creating Cards like this:
On Nov 21, 8:44 am, Jesús Gabriel y Galán <jgabrielyga...@gmail.com> wrote:
> Hi,
> I have a many to many association in which the join table has > additional columns. I'm modeling a deck management application for > Magic the Gathering, and I have the following tables:
> DB.create_table(:mtg_database) do > Integer :gatherer_id > primary_key [:gatherer_id]
You can probably just use "primary key :gatherer_id" here
> The deck_card table contains the relation between a deck and the cards > it contains. For each deck and card, it has an additional column named > qty which contains the number of such card a deck has. What I would > like to do is have a method in a deck model that allows me to add a > card to a deck, which will handle the qty field correctly (set it to 1 > the first time a card is added to a deck, increment it from that point > on). I have managed to make it work, checking an old message from this > list related to books and locations, as so:
> class Card < Sequel::Model(:mtg_database) > unrestrict_primary_key > one_to_many :deck_card > end
> class DeckCard < Sequel::Model(:deck_card) > many_to_one :card, :key => :gatherer_id > many_to_one :deck > end
It looks fine if you aren't worried about concurrent access. For concurrent access, there's a race condition when you are updating. The correct way to handle concurrent access would be to do:
> 2.- I would now need a way to iterate through all the deck's cards > having access to both the card info (all columns in mtg_database > table) along with the qty column from the join table. What would be > the best way to do that?
In your many_to_many :cards association, add a :select=>[:mtg_database.*, :deck_card__qty]. Then you can just do deck.cards, and each entry in the array would have a :qty entry in the values hash for the number of that card in the deck.
> 3.- I had to add the unrestrict_primary_key in the Card model. Until I > did, I wasn't able to create cards setting the gatherer_id, which is > an external id that I want to have as pk of that table. Am I doing > something wrong? I'm creating Cards like this:
It should only affect mass assignment (which create uses). There's nothing wrong with it, though you have to be more careful with user input if you unrestrict access to the primary key.
On Thu, Nov 24, 2011 at 3:08 AM, Jeremy Evans <jeremyeva...@gmail.com> wrote: > On Nov 21, 8:44 am, Jesús Gabriel y Galán <jgabrielyga...@gmail.com> > wrote: >> Hi,
>> I have a many to many association in which the join table has >> additional columns. I'm modeling a deck management application for >> Magic the Gathering, and I have the following tables:
>> DB.create_table(:mtg_database) do >> Integer :gatherer_id >> primary_key [:gatherer_id]
> You can probably just use "primary key :gatherer_id" here
I understood this would create an autoincrementing column, whereas I want to set the id myself, because it comes from an external system.
>> The deck_card table contains the relation between a deck and the cards >> it contains. For each deck and card, it has an additional column named >> qty which contains the number of such card a deck has. What I would >> like to do is have a method in a deck model that allows me to add a >> card to a deck, which will handle the qty field correctly (set it to 1 >> the first time a card is added to a deck, increment it from that point >> on). I have managed to make it work, checking an old message from this >> list related to books and locations, as so:
>> class Card < Sequel::Model(:mtg_database) >> unrestrict_primary_key >> one_to_many :deck_card >> end
>> class DeckCard < Sequel::Model(:deck_card) >> many_to_one :card, :key => :gatherer_id >> many_to_one :deck >> end
> It looks fine if you aren't worried about concurrent access. For > concurrent access, there's a race condition when you are updating. > The correct way to handle concurrent access would be to do:
OK, you mean to do this in the else part of the check right? I mean I need to do different things for the first insertion and the subsequent ones, and I understand this statement needs that the row in the deck_card table already exists, correct? If so, wouldn't there still be a race condition?
def add_card_to_deck card deck_card = deck_card card if deck_card deck_card_dataset.filter(:gatherer_id => card.gatherer_id).update(:qty=>:qty+1) else DeckCard.create({:card => card, :deck => self, :qty => 1}) end end
Or am I misunderstanding and you meant a different thing?
>> 2.- I would now need a way to iterate through all the deck's cards >> having access to both the card info (all columns in mtg_database >> table) along with the qty column from the join table. What would be >> the best way to do that?
> In your many_to_many :cards association, add > a :select=>[:mtg_database.*, :deck_card__qty]. Then you can just do > deck.cards, and each entry in the array would have a :qty entry in the > values hash for the number of that card in the deck.
Great, thanks, I'll try this.
>> 3.- I had to add the unrestrict_primary_key in the Card model. Until I >> did, I wasn't able to create cards setting the gatherer_id, which is >> an external id that I want to have as pk of that table. Am I doing >> something wrong? I'm creating Cards like this:
> It should only affect mass assignment (which create uses). There's > nothing wrong with it, though you have to be more careful with user > input if you unrestrict access to the primary key.
OK, but my question is why is that needed when the PK is not an autoincrementing id? If I need to build a model in which I need to setup the id myself when creating, as is my case, what should I do? Is this the recommended way? Until I add that line I was getting errors when I tried to set the gatherer_id in a Card object.
> On Thu, Nov 24, 2011 at 3:08 AM, Jeremy Evans <jeremyeva...@gmail.com> wrote: > > On Nov 21, 8:44 am, Jesús Gabriel y Galán <jgabrielyga...@gmail.com> > > wrote: > >> Hi,
> >> I have a many to many association in which the join table has > >> additional columns. I'm modeling a deck management application for > >> Magic the Gathering, and I have the following tables:
> OK, you mean to do this in the else part of the check right?
No, in the if part of the check. The else part (which does the create), is fine, though you could remove the :qty=>1 if you make that the default.
> I mean I need to do different things for the first insertion and the > subsequent ones, > and I understand this statement needs that the row in the deck_card > table already exists, correct?
Yes, but you are still checking for existance in the if statement.
> If so, wouldn't there still be a race condition?
Yes, there is in the sense that if two processes/threads attempt to create at the same time. However, the primary key constraint in the deck_card table will prevent that, so an error would be raised by one of the processes/threads in that case.
> Or am I misunderstanding and you meant a different thing?
That's basically right.
> >> 3.- I had to add the unrestrict_primary_key in the Card model. Until I > >> did, I wasn't able to create cards setting the gatherer_id, which is > >> an external id that I want to have as pk of that table. Am I doing > >> something wrong? I'm creating Cards like this:
> > It should only affect mass assignment (which create uses). There's > > nothing wrong with it, though you have to be more careful with user > > input if you unrestrict access to the primary key.
> OK, but my question is why is that needed when the PK is not an > autoincrementing id?
If the primary key is not autoincrementing, then you are more likely to want unrestricted access to it. However, Sequel defaults to restricted access for all primary keys, not just those that are autoincrementing.
> If I need to build a model in which I need to setup the id myself when > creating, as is my case, what should I do?
Using unrestrict_primary_key is fine. Just be careful with user input.
> Is this the recommended way? Until I add that line I was getting > errors when I tried to set the gatherer_id in a Card object.
There's no problem with enabling unrestricted access. The alternative in your case would be to call the setter method manually: