5

Ok, so this is driving me crazy. I have read the Associations article and example and been trying to work this out for the las three days and I'm tired of this making me feel dumb, so...

How does one set up associations with DataMapper?

(I am using DM with Sinatra with SQLite3. Everything word fine for single tables with multiple values etc. It's when I start to try to associate them that I start getting errors.)

Let's say I have an Orchard full of Apple Trees. Each Tree has many Apples. Each Apple has many Seeds. Therefore each tree has many Seeds through its Apples

require 'sinatra'
require 'datamapper'

DataMapper::setup(:default, "sqlite3://#{Dir.pwd}/orchard.db")

# Trees in the orchard.
class Tree
  include DataMapper::Resource
  property :id, Serial

  has n, :apples
  has n, :seeds, :through => :apples
end

# Apples on a Tree.
class Apple
  include DataMapper::Resource
  property :id, Serial

  belongs_to :tree
  belongs_to :seed
end

# Seeds in an Apple
class Seed
  include DataMapper::Resource
  property :id, Serial

  has n, :apple
  has n, :tree, :through => apple 
end

DataMapper.finalize.auto_upgrade!

Is that correct? I keep getting various errors when I try to run this. Mostly along the lines of invalid association or cannot create column NULL with value NULL etc. What am I not understanding about this relationship?

Further, Once I have a working model how do I go about getting information from it?

If there are 3 trees:

Tree.all.count 
=> 3

If there are 2 apples:

Apple.all
=>[#<Apple @id=1>, #<Apple @id=2>]  

Ok cool. But how many Apples does Tree #2 have? How many Seeds does Tree #4 have? How many Apples in total? How many Seeds in total?

Any help would be greatly appreciated.

Don Graziano
  • 431
  • 5
  • 15

3 Answers3

12

Your model seems a bit confused:

Let's say I have an Orchard full of Apple Trees. Each Tree has many Apples. Each Apple has many Seeds. Therefore each tree has many Seeds through its Apples.

So a tree has many apples, an apple belongs to a tree and has many seeds, and a seed belongs to an apple (and ultimately a single tree).

We can almost (but not quite) take that language as it is and use it to create the associations. After a little translation to get the syntax right we get this:

# Trees in the orchard.
class Tree
  include DataMapper::Resource
  property :id, Serial

  has n, :apples    # "a tree has many apples"
  has n, :seeds, :through => :apples
end

# Apples on a Tree.
class Apple
  include DataMapper::Resource
  property :id, Serial

  belongs_to :tree # "an apple belongs to a tree..."
  has n, :seeds    # "...and has many seeds"
end

# Seeds in an Apple
class Seed
  include DataMapper::Resource
  property :id, Serial

  belongs_to :apple  # "and a seed belongs to an apple"
end

In your code you have seeds having multiple apples and trees, which doesn't really make any sense.

As for querying:

But how many Apples does Tree #2 have?

Assuming you mean the Tree with id == 2:

tree_2 = Tree.get(2)
apples_of_tree_2 = tree_2.apples # this gives an array of apples
count_of_apples_of_tree_2 = tree_2.apples.count

How many Seeds does Tree #4 have?

The association we added to the Tree model has n, :seeds, :through => :apples means we have a seeds method available in Tree objects.

Tree.get(4).seeds.count

How many Apples in total? How many Seeds in total?

Simply:

Apple.count  # note singular not plural (it's a class method on Apple)
Seed.count

Try loading this new model into irb (you might need to delete your orchard.db file when you change the model), and then playing around with some of the queries and creation methods, hopefully that'll give you a better idea of what's going on.

Creating associations

(See the section "Adding To Associations" on the Associations page.)

To add an existing Apple to a Tree:

a_tree.apples << an_apple

Note that a Tree isn't associated with a single Apple but a collection (it has n Apples), so the method created is apples (i.e. it's pluralized), and there's no method apple which is why you're seeing the no method error.

You can also create a new Apple associated with a Tree directly:

a_tree.apples.new    #created but not saved to database
a_tree.apples.create #created and saved to database

You can also create the association the other way round, from the Apple side:

an_other_apple = Apple.new
an_other_apple.tree = a_tree

but you need to be careful doing it this way, as the new apple won't show up in the a_trees collection of Apples (a_tree.apples won't include an_other_apple). In order for it to appear you need to save the apple, and then call reload on the Tree:

an_other_apple.save
a_tree.reload

You need to watch out with this, as you can end up with an Apple that appears to be in two Trees at the same time if you're not careful.

Community
  • 1
  • 1
matt
  • 74,317
  • 7
  • 140
  • 183
  • Thank you very much! I was able to get my database structure to work. I read the Docs on queries & creation methods, but I'm still a little confused about how to create an apple for a particular tree or a seed for a particular apple. – Don Graziano Sep 23 '11 at 17:23
  • In IRB tree.apple = Tree.new yields undefined method for 'apple=' I assume this means that I'm not selecting the appropriate target or passing in the correct attributes. What would the command to create a new Apple on a partiular tree be? Or a Seed in a particular Apple? – Don Graziano Sep 23 '11 at 17:31
  • @DonGraziano I've added some examples of creating associations. I hope these help. – matt Sep 23 '11 at 23:37
  • Thanks that actualy helped alot. For some reason it just wasn't clicking from reading the Docs and other people's examples. I'm just haveing one last problem. Let's say that each model also has a name value, tree_name, apple_name, seed_name. I would like to be able to find by that name. ie. `Tree.get('Wanda')` but if I add `:key, => true` to tree_name `Tree.get('Wanda')` tells me attribute error 1 for 2. `Tree.get(1, 'Wanda')` returns `< @Id = 1, @tree_name = "Wanda">`. I read te Docs and thought that adding the `:key, => true` could be added to any property to make it a key value. – Don Graziano Sep 26 '11 at 15:02
  • @DonGraziano Adding `property :name, String, :key => true` whilst keeping the `property :id, Serial` line will create a composite key (`id` and `name`). With `get` you need to specify the whole key to identify the record, so you need `Tree.get(1, 'Wanda')`. Removing the `id` property will leave the key as being just the name, so `Tree.get('Wanda')` will work (the name will need to be unique in this case). – matt Sep 26 '11 at 20:47
  • Thats what I was thinking the problem was. Thanks for all your help man! – Don Graziano Sep 29 '11 at 13:25
  • So, Another question. While using sinatra, Lets say I have a form to create a new Seed. The form posts the information correctly. and it is persisted to the database, but it always gives me a `TypeError can't dump anonymous class`. I would assume that this is because I am creating the seed from the tree thru the apple? I have a tree stored in `session[:tree]`. On my `get` route I do `@tree = session[:tree]`. Then on my post I do `s = @tree.apples.seed.new`. If I do `s.name = wanda`. and then `w.save`. I get the TypeError with no trace back. 0o – Don Graziano Oct 06 '11 at 15:57
1

I think it's a pluralization issue - don't you hate that?

# Seeds in an Apple
class Seed
  include DataMapper::Resource
  property :id, Serial

  has n, :apples  # here
  has n, :tree, :through => :apples  # and here
end
Eric G
  • 1,232
  • 8
  • 15
0

Without much knowledge of Datamapper and after skipping through the datamapper documentation, what about these answers?

How many apples does tree 2 have:

Tree.get(2).apples.count

How many seed does tree 4 have:

Tree.get(4).apples.inject(0) {|count, apple| count + apple.seeds.count}
topek
  • 17,226
  • 3
  • 33
  • 43