Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting up Associations with DataMapper & Sinatra

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.

like image 407
Don Graziano Avatar asked Sep 22 '11 21:09

Don Graziano


2 Answers

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.

like image 152
matt Avatar answered Oct 12 '22 16:10

matt


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
like image 28
Eric G Avatar answered Oct 12 '22 16:10

Eric G