Datamapper has n relationship with multiple keys - ruby

I am working on a simple relationship with DataMapper, a ruby webapp to track games. A game belongs_to 4 players, and each player can have many games.
When I call player.games.size, I seem to be getting back a result of 0, for players that I know have games associated with them. I am currently able to pull the player associations off of game, but can't figure out why player.games is empty.
Do I need to define a parent_key on the has n association, or is there something else I'm missing?
class Game
belongs_to :t1_p1, :class_name => 'Player', :child_key => [:player1_id]
belongs_to :t1_p2, :class_name => 'Player', :child_key => [:player2_id]
belongs_to :t2_p1, :class_name => 'Player', :child_key => [:player3_id]
belongs_to :t2_p2, :class_name => 'Player', :child_key => [:player4_id]
...
end
class Player
has n, :games
...
end

Still haven't figured out a way that feels right, but for now I am using the following workaround. Anyone know of a better way to accomplish this?
class Player
has n, :games # accessor doesn't really function...
def games_played
Game.all(:conditions => ["player1_id=? or player2_id=? or player3_id=? or player4_id=?", id, id, id, id])
end
end

Have you tried the following:
class Game
has n, :Players, :through => Resource
end
class Player
has n, :Games, :through => Resource
end
I'm looking for a related bug now.

You should be able to use Single Table Inheritance to achieve the desired result. Although you may need to do some thinking about how you to handle players being Player One in one game and Player Two in another.
My example code is just for reference. It has not been tested but should work.
class Player
property :id, Serial
property :name, String
property :player_number, Discriminator
end
class PlayerOne < Player
has n, :games, :child_key => [ :player1_id ]
end
class PlayerTwo < Player
has n, :games, :child_key => [ :player2_id ]
end
class PlayerThree < Player
has n, :games, :child_key => [ :player3_id ]
end
class PlayerFour < Player
has n, :games, :child_key => [ :player4_id ]
end
class Game
belongs_to :player1, :class_name => 'PlayerOne', :child_key => [:player1_id]
belongs_to :player2, :class_name => 'PlayerTwo', :child_key => [:player2_id]
belongs_to :player1, :class_name => 'PlayerThree', :child_key => [:player3_id]
belongs_to :player2, :class_name => 'PlayerFour', :child_key => [:player4_id]
end

Related

Trouble with cascading in Active Record models

I have these active record models in my application
class Artist < ActiveRecord::Base
has_many :albums, :dependent => :delete_all, autosave: true
end
class Album < ActiveRecord::Base
attr_accessor :album_artist
has_many :tracks, :dependent => :delete_all, autosave: true
has_many :covers, :dependent => :delete_all, autosave: true
belongs_to :artist
end
class Track < ActiveRecord::Base
belongs_to :album
end
class Cover < ActiveRecord::Base
belongs_to :album
end
I'm trying, in my application, when i delete an Artist, his albums and, in consequence, the tracks and covers of his albums, get all deleted, in a cascade reaction.
The way it's implemented today, when i delete the Artist, only the Album is deleted too, leaving orphan records in my database.
Am i doin' anything wrong?
Instead of :dependent => :delete_all you need to configure:
:dependent => :destroy_all
Because delete will just delete all its associated objects directly from the database without calling their destroy method (what break cascading).
You might want to read 4.1.2.4 in http://guides.rubyonrails.org/association_basics.html#belongs-to-association-reference

ActiveRecord::HasManyThroughAssociationPolymorphicSourceError

I need a player to have many structures and the structure to belong to the player. Structure is a polymorphic relationship.
class Player < ActiveRecord::Base
has_many :player_structures
has_many :structures, :through => player_structures
end
class PlayerStructures < ActiveRecord::Base
belongs_to :structure, polymorphic: true
belongs_to :player
end
class StructureA < ActiveRecord::Base
has_one :player_structure, :as => :structure
has_one :player, :through => :player_structure
end
class StructureB < ActiveRecord::Base
has_one :player_structure, :as => :structure
has_one :player, :through => :player_structure
end
But if I pull out Player.first and ask for its structures, it gives:
ActiveRecord::HasManyThroughAssociationPolymorphicSourceError: Cannot have a has_many :through association 'Player#structures' on the polymorphic object 'Structure#structure'.
But it should be able to generate a SQL query where it finds all player_structures with its id, then fetches the structure based on the structure_id and structure_type. Why does this fail and how can I validly construct a polymorphic join table?
UPDATE
If I do what I want it to do manually, it works:
player_structures.collect(&:structure)
Rails, y u no do that?
I think you need to be more specific in defining your relationships in your Player model. For example:
class Player < ActiveRecord::Base
has_many :player_structures
has_many :structureas, :through => player_structures, :source => :structure, :source_type => 'StructureA'
has_many :structurebs, :through => player_structures, :source => :structure, :source_type => 'StructureB'
end
Then you can make a method that'll return all the structures defined in the relationships instead of having to access each one individually.

How to have an ordered model association allowing duplicates

I have two models, Song and Show. A Show is an ordered list of Songs, in which the same Song can be listed multiple times.
That is, there should be an ordered array (or hash or anything) somewhere in Show that can contain Song1, Song2, Song1, Song3 and allow re-ordering, inserting, or deleting from that array.
I cannot figure out how to model this with ActiveRecord associations. I'm guessing I need some sort of special join table with a column for the index, but apart from starting to code my SQL directly, is there a way to do this with Rails associations?
Some code as I have it now (but doesn't work properly):
class Song < ActiveRecord::Base
attr_accessible :title
has_and_belongs_to_many :shows
end
class Show < ActiveRecord::Base
attr_accessible :date
has_and_belongs_to_many :songs
end
song1 = Song.create(title: 'Foo')
song2 = Song.create(title: 'Bar')
show1 = Show.create(date: 'Tomorrow')
show1.songs << song1 << song2 << song1
puts "show1 size = #{show1.songs.size}" # 3
show1.delete_at(0) # Should delete the first instance of song1, but leave the second instance
puts "show1 size = #{show1.songs.size}" # 2
show1.reload
puts "show1 size = #{show1.songs.size}" # 3 again, annoyingly
Inserting might look like:
show1.songs # Foo, Bar, Foo
song3 = Song.create(title: 'Baz')
show1.insert(1, song3)
show1.songs # Foo, Baz, Bar, Foo
And reordering might (with a little magic) look something like:
show1.songs # Foo, Bar, Foo
show1.move_song_from(0, to: 1)
show1.songs # Bar, Foo, Foo
You're on the right track with the join table idea:
class Song < ActiveRecord::Base
attr_accessible :title
has_many :playlist_items
has_many :shows, :through => :playlist_items
end
class PlaylistItem < ActiveRecord::Base
belongs_to :shows #foreign_key show_id
belongs_to :songs #foreign_key song_id
end
class Show < ActiveRecord::Base
attr_accessible :date
has_many :playlist_items
has_many :songs, :through => :playlist_items
end
Then you can do stuff like user.playlist_items.create :song => Song.last
My current solution to this is a combination of has_many :through and acts_as_list. It was not the easiest thing to find information on combining the two correctly. One of the hurdles, for example, was that acts_as_list uses an index starting at 1, while the array-like methods created by the ActiveRecord association start at 0.
Here's how my code ended up. Note that I had to specify explicit methods to modify the join table (for most of them anyway); I'm not sure if there's a cleaner way to make those work.
class Song < ActiveRecord::Base
attr_accessible :title
has_many :playlist_items, :order => :position
has_many :shows, :through => :playlist_items
end
class PlaylistItem < ActiveRecord::Base
attr_accessible :position, :show_id, :song_id
belongs_to :shows
belongs_to :songs
acts_as_list :scope => :show
end
class Show < ActiveRecord::Base
attr_accessible :date
has_many :playlist_items, :order => :position
has_many :songs, :through => :playlist_items, :order => :position
def song_at(index)
self.songs.find_by_id(self.playlist_items[index].song_id)
end
def move_song(index, options={})
raise "A :to option is required." unless options.has_key? :to
self.playlist_items[index].insert_at(options[:to] + 1) # Compensate for acts_as_list starting at 1
end
def add_song(location)
self.songs << location
end
def remove_song_at(index)
self.playlist_items.delete(self.playlist_items[index])
end
end
I added a 'position' column to my 'playlist_items' table, as per the instructions that came with acts_as_list. It's worth noting that I had to dig into the API for acts_as_list to find the insert_at method.

creating Models with sqlite3 + datamapper + ruby

How do you build a model with the following associations (i tried but couldn't get it to work):
each Order has: a Customer, a SalesRep, many OrderLine that each has an item.
I tried: when I do: Customer.all(Customer.orders.order_lines.item.sku.like => "%BLUE%")
the output is :[]
instead of: '[#<"Customer #id=1 #name="Dan Kubb">]'
When I delete SalesRep: it works.
Customer
has n, :orders
has n, :items, :through => :order
SalesRep
has n, :orders
has n, :items, :through => :order
Order
belongs_to :customer
belongs_to :technician
has n, :order_lines
has n, :items, :through => :order_line
OrderLine
belongs_to :order
belongs_to :item
Item
has n, :order_lines
Since the output isn't an error, it could be that you don't actually have the data in your database.
Try the following with irb -r your_models_file.rb:
- c = Customer.create(:name => "Dan Kubb")
o = Order.new(:customer => c) # Create and add technician unless it's :required => false
i = Item.create(:sku => "BLUE") # Plus any other required fields
ol = OrderLine.create(:order => o, :item => i)
o.order_lines << ol; o.save
That should create the records needed for this to work. Try this out, and if it doesn't work, post your entire models file so we can get a better idea of what's up.

Polymorphic has_many self-referential

I have A number of models (Article, Video, Photo)
Now I am trying to create a related_to association, such that
An article can have many other articles, videos and photos related to it. As can videos and photos.
Heres what I have tried:
module ActsAsRelatable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def acts_as_relatable
has_many :related_items, :as => :related
has_many :source_items, :as => :source, :class_name => 'RelatedItem'
end
end
end
class RelatedItem < ActiveRecord::Base
belongs_to :source, :polymorphic => true
belongs_to :related, :polymorphic => true
end
Then I have added acts_as_relatable to my three models (Article, Video, Photo) and included the module in ActiveRecord::Base
When trying in ./script/console I get it to add the related items and the ids work correctly however the source_type and related_type are always the same (the object that related_items was called from) I want the related_item to be the other model name.
Any ideas anyone?
I would use the has many polymorphs plugin since it supports double sided polymorphism you can do something like this:
class Relating < ActiveRecord::Base
belongs_to :owner, :polymorphic => true
belongs_to :relative, :polymorphic => true
acts_as_double_polymorphic_join(
:owners => [:articles, :videos, :photos],
:relatives => [:articles, :videos, :photos]
)
end
and don't forget the db migration:
class CreateRelatings < ActiveRecord::Migration
def self.up
create_table :relating do |t|
t.references :owner, :polymorphic => true
t.references :relative, :polymorphic => true
end
end
def self.down
drop_table :relatings
end
end
I don't know if "Relating" is a good name, but you get the idea. Now an article, video and photo can be related to another article, video or photo.

Resources