[DataMapper] many-to-many through model

138 views
Skip to first unread message

Don French

unread,
May 24, 2010, 6:20:13 PM5/24/10
to DataMapper
I have three tables:
class Name
include DataMapper::Resource

property :id, Serial
property :normalized_name, String, :unique_index =>
true, :required => true
property :name, String, :required => true
property :link_status, Enum, :flags =>
[:new, :linked], :default => :new
has n, :people_names
has n, :people, :through => :people_names

def name=(value)
attribute_set(:name, value.upcase)
attribute_set(:normalized_name, Name.normalize(value))
end

def Name.normalize(orig_name)
n = orig_name.upcase
n.gsub!(',', '')
n.gsub!('.', '')
n.gsub!(' ', '')
n
end
end

class Person
include DataMapper::Resource
property :id, Serial
property :name, String
has n, :people_names
has n, :names, :through => :people_names
end

class PeopleName
include DataMapper::Resource
property :id, Serial
property :link_type, Enum, :flags =>
[:validated, :unvalidated], :default => :unvalidated
belongs_to :person
belongs_to :name
end

I create a Person object as p and a Name object as n, when I assign
p.names << name and n.person << person I can then do p.names and I get
the name assigned, same with n.person I get the right person. But how
do I access the :link_type property from the PeopleName table. If I
do a PeopleName.all I get an empty array.

How do I access the additional information in the join table?

Don French

--
You received this message because you are subscribed to the Google Groups "DataMapper" group.
To post to this group, send email to datam...@googlegroups.com.
To unsubscribe from this group, send email to datamapper+...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/datamapper?hl=en.

Dan Sosedov

unread,
May 24, 2010, 7:13:21 PM5/24/10
to DataMapper
If i understand correctly, you need to get data from this middle table
(PeopleName)?

attr = Person.get(YOUR_ID).people_names.first.link_type

So anyway you have to know what is your ID that refers to both
objects.

But i see the problem here, since you have many-to-many rels, you
might want to add additional field to your Person model to store
current name.
So it might look like this.

class Person
...
property :id, Serial
property :current_name, Integer, :required => true, :index => true

def current_name_attrs
self.names.get(self.current_name).person_names.first
end
end

And, take a look at my example:

----------------------------------------------------------------------------------

require 'rubygems'
require 'dm-core'
require 'dm-types'

DataMapper.setup(:default, 'sqlite3::memory:')

class Person
include DataMapper::Resource

property :id, Serial
property :title, String, :required => true

has n, :person_names
has n, :names, :through => :person_names

def pending_name
return self.person_names.first(:status => :pending).name
end
end

class Name
include DataMapper::Resource

property :id, Serial
property :title, String, :required => true

has n, :person_names
has n, :persons, :through => :person_names
end

class PersonName
include DataMapper::Resource

property :id, Serial
property :status, Enum[:active, :pending], :default => :active

belongs_to :person
belongs_to :name
end

DataMapper.auto_migrate!

# --------------------------------------------------------------------

p = Person.new(:title => "Person 1")
p.names << Name.new(:title => "Name 1")
p.names << Name.new(:title => "Name 2")
p.save

p = Person.new(:title => "Person 2")
p.names << Name.new(:title => "Name 3")
p.names << Name.new(:title => "Name 4", :person_names =>
[PersonName.new(:status => :pending, :person => p)])
p.save

people = Person.all
people.each do |p|
puts "#{p.id} - #{p.title}"
p.names.each do |n|
puts "-> #{n.title}, status => #{n.person_names.first.status}"
end
end

puts Person.last.pending_name.inspect


OR, you can write your own SQL query to reach your point.

class Model
def custom_method
return adapter.query("SQL....")
end
end

Don French

unread,
May 24, 2010, 9:30:56 PM5/24/10
to DataMapper
That just does not make sense. When you do the:
> p.names << Name.new(:title => "Name 1")
> p.names << Name.new(:title => "Name 2")
and the reverse for names I would think the join table would have the
proper ids for the associations, but if you query the join table it is
empty. You added a separate create on the join table, why was the
entry not done when the initial join was created. Is there another
magic table that is actually holding the joins and the through a
secondary table?

Don French

Dan Sosedov

unread,
May 24, 2010, 10:28:55 PM5/24/10
to DataMapper
Sorry man, my bad.
Here is another sample, i used your schema and optimized it a little.
I home this is what you`re looking for - http://pastie.org/975601

require 'rubygems'
require 'data_objects'
require 'dm-core'
require 'dm-types'
require 'dm-validations'
require 'dm-migrations'
require 'dm-aggregates'
require 'do_sqlite3'

DataMapper.setup(:default, 'sqlite3::memory:')

class Name
include DataMapper::Resource

property :id, Serial
property :normalized_name, String, :unique_index =>
true, :required => false
property :name, String, :required => true
property :link_status, Enum[:new, :linked], :default => :new

has n, :people_names
has n, :people, :through => :people_names

before :save, :before_save

def before_save
@name.strip.upcase!
@normalized_name = @name.gsub(/[\.\,\s]/,'')
end
end

class Person
include DataMapper::Resource
property :id, Serial
property :name, String
has n, :people_names
has n, :names, :through => :people_names
end

class PeopleName
include DataMapper::Resource
property :id, Serial
property :link_type, Enum[:validated, :unvalidated], :default
=> :unvalidated
belongs_to :person
belongs_to :name
end

DataMapper::auto_migrate!

#
----------------------------------------------------------------------

p = Person.new(:name => "James Bond")
p.people_names << PeopleName.new(:name => Name.new(:name => "James
Bond"))
p.people_names << PeopleName.new(:name => Name.new(:name => "J.
Bond"))
p.people_names << PeopleName.new(:name => Name.new(:name => "Bond.
J"))
if p.save
puts "Person: #{p.inspect}"
puts "Names:"
p.names.each do |name|
puts "-> Name: #{name.inspect}"
name.people_names.each do |pn|
puts "----> #{pn.inspect}"
end
end
end
Reply all
Reply to author
Forward
0 new messages