DataMapper - create new value for relational DB - heroku

I have a relational DB defined as follows. How can I enter a new value, where B belongs to A. The code given below doesn't seem to work.
Thanks
class A
include DataMapper::Resource
property :id, Serial, :key => true
property :name, String
belongs_to :b
end
class B
include DataMapper::Resource
property :id, Serial, :key => true
property :name, String
has n, :as
end
Create new value
# Create new value
post '/create' do
a = A.new
b = B.new
b.attributes = params
b.belongs_to = a #problem is here
b.save
redirect("/info/#{a.id}")
end

#belongs_to is a model (class) method and you use it to declare ManyToOne relationship.
In your example you should use "<<" method like this:
b.as << a
That will add "a" instance to "as" collection and associate both resources.

[...] How can I enter a new value, where B belongs to A. The code given below doesn't seem to work.
Your code implies you're after A belonging to B, but your question is the reverse so I'll show how to do that, i.e., B belongs to A.
class A
include DataMapper::Resource
property :id, Serial, :key => true
property :name, String
has n, :bs # A has many B's
end
class B
include DataMapper::Resource
property :id, Serial, :key => true
property :name, String
belongs_to :a, :required => false # B has only 1 A
end
Note your has and belongs_to are reversed here. I also added required => false to the belongs_to side because DataMapper will silently refuse to save your model if ever don't have b.a before calling save—once you're comfortable with it you can remove the required false if you desire.
Here are two ways you can use that model:
# Create new value
post '/create' do
a = A.new
a.save
b = B.new
b.attributes = params
b.a = a
b.save
redirect("/info/#{a.id}")
end
This example is generally the same as yours, but I added a save call for A. Note this may not be necessary, I'm not in a good place to test this particular case; in the past I've found DataMapper will save some related objects automatically but not others so I've developed the habit of always saving explicitly to prevent confusion.
# Create new value
post '/create' do
a = A.create
b = a.bs.create(params)
redirect("/info/#{a.id}")
end
In the second example I call create on the many-side of the relationship, this makes a new B, associates it with "a", sets the params given, and saves it immediately. The result is the same as the previous example.
If you're just getting familiar with DataMapper, you may find it helpful to add the following to your app:
DataMapper::Model.raise_on_save_failure = true
This will cause DataMapper to give you errors and backtraces in cases like the above, more info here.

Related

DataMapper: one-to-many relationship with custom name?

I'm trying to build a small model consisting of two entities. For the purposes of this question, call them A and B. A has a one-to-many relationship to several Bs; this means that each B belongs_to an A.
In this particular case, I'd like to call the relationship from B back to A something other than a. I think I got close with the following:
class A
include DataMapper::Resource
property :id, Serial
has n, :bs
end
class B
include DataMapper::Resource
property :id, Serial
belongs_to :owner, 'A'
end
The important bit here is the belongs_to :owner, 'A' line in B. With this, I can successfully:
Create and save an instance of A
Query that A for its bs and get an empty array back
Create an instance of B, assigning its owner to be the A I made earlier
However, when I go to save that instance of B, I run into trouble – calling save returns false. If I print the B, I see that it has two attributes: one called owner_id and another called a_id.
What else do I need to do with this model to rename the relationship from B back to A? Is such a rename even possible?
Figured it out. The owning entity (A) needs to explicitly specify the child keys that it wants created for the relationship:
class A
include DataMapper::Resource
property :id, Serial
has n, :bs, :child_key => [ 'owner_id' ]
end
class B
include DataMapper::Resource
property :id, Serial
belongs_to :owner, 'A'
end
With this change, I only see one relationship attribute created on B, and I'm able to save instances of B that I create.

Create JSON from 2 associated Datamapper models

Here is my question.
I have 2 associated Datamapper models:
class Task
include DataMapper::Resource
property :id, Serial
property :date, Date
property :amount, Float
belongs_to :project, :required => true
end
class Project
include DataMapper::Resource
property :id, Serial
property :name, String, :required => true
property :desc, Text
belongs_to :company
has n, :tasks
end
My goal is to created JSON that will contain task date, amount and project name, that should be matched by project_id. At the moment JSON generation has following look:
Task.all.to_json(:only => [:date, :amount, :project_id])
I can access project_id from Task model, but have no idea how to add respective project name from Project model for every task. In SQL it looks like join:
select tasks.date, tasks.amount, projects.name from tasks
inner join projects
on tasks.project_id = projects.id;
Can you suggest correct way to create final JSON, using Datamapper way, but not SQL?
Thank you.
I have found solution for my problem. Here it is:
# create new structure to store merged result
Task_entry = Struct.new(:date, :amount, :pname)
# array to get results from database
all_task_items = Array.new
# run through result and fill the array with required data
Task.all.each do |task|
task_item = Task_entry.new(task.date, task.amount, task.project.name)
all_task_items << task_item
end
all_task_items.to_json # generate json
It works for me well. Hope it can be helpful.

One-to-one DataMapper association

I'm very new to DataMapper, and I'm trying to create models for the following scenario:
I've got a number of users (with a user name, password etc.), who can also be players or referees or both (so Single Table Inheritance is not an option). The base models would be:
class User
include DataMapper::Resource
property :id, Serial
# Other user properties go here
end
class Player
include DataMapper::Resource
property :id, Serial
# Other player properties go here
# Some kind of association goes here
end
class Referee
include DataMapper::Resource
property :id, Serial
# Other referee properties go here
# Some kind of association goes here
end
DataMapper.finalize
I'm not sure, though, what kinds of associations to add to Player and Referee. With belongs_to :user, multiple players can be associated with the same user, which doesn't make sense in my context. In RDBMS terms I guess what I want is a unique constraint on the foreign key in the Players and Referees tables.
How do I accomplish this in my DataMapper model? Do I have to perform the check myself in a validation?
There are different ways you could do this. Here's one option:
class User
include DataMapper::Resource
property :id, Serial
# Other properties...
has 1, :referee, :required => false
has 1, :player, :required => false
end
class Referee
include DataMapper::Resource
# DON'T include "property :id, Serial" here
# Other properties...
belongs_to :user, :key => true
end
class Player
include DataMapper::Resource
# DON'T include "property :id, Serial" here
# Other properties...
belongs_to :user, :key => true
end
Act on the referee/player models like:
u = User.create(...)
u.referee = Referee.create(...)
u.player = Player.create(...)
u.player.kick_ball() # or whatever you want to call
u.player.homeruns
u.referee.flag_play() # or whatever.
See if this works. I haven't actually tested it but it should be good.
The previous answer works other than :required => false is not recognized for has 1 properties.
It's also confusing because for has n properties, you can use new right on the property or otherwise treat it as a collection. In your example, you would be tempted to code
u = User.create ...
u.referee.create ...
But that fails in the case of has 1 because the property is a single value, which begins life as nil and so you have to use the method the previous answer indicates. Also, having to explicitly make the belongs_to association into the key is a little confusing.
It does seem to execute validations and have the right association actions (so u.save will also save the referred-to Referee). I just wish it were more consistent between has n and has 1.

Destroying "has n, :things, :through => Resource" associations in DataMapper

I have a collection class called MySet:
class MySet
include DataMapper::Resource
property :id, Serial
has n, :my_elements, :through => Resource
def add integer
unless my_elements.first(:integer => integer)
my_element = MyElement.create :integer => integer
my_elements << my_element
my_elements.save
end
self
end
def has_integer? integer
!my_elements.first(:integer => integer).nil?
end
def delete integer
if has_integer? integer
my_elements.first(:integer => integer).destroy
my_elements.save
end
self
end
def size
my_elements.size
end
end
and an element class called MyElement:
class MyElement
include DataMapper::Resource
property :id, Serial
property :integer, Integer
end
I want to be able to add and delete elements to and from MySet. However, the following spec:
describe MySet do
subject do
MySet.create
end
it "adds and deletes" do
subject.add 1
subject.delete 1
subject.size.should == 0
end
end
fails with:
Failure/Error: subject.size.should == 0
expected: 0
got: 1 (using ==)
This is similar to the problem described in DataMapper has n through Resource DELETE (Remove from association) not working except that MyElement does not specify an association with MySet. However, I think the solution in that post is what I am using and it does not appear to work.
Take a look at the Collection documentation:
http://rubydoc.info/github/datamapper/dm-core/master/DataMapper/Collection
http://rubydoc.info/github/datamapper/dm-core/master/DataMapper/Associations/ManyToMany/Collection
I think some of the things you're doing are extraneous, and MyElement.create should probably be MyElement.first_or_create but more importantly, the part where you destroy one of the MyElement's is not quite right. It seems like what you're trying to do is remove it from the "my_elements" collection, so you should find it, and then "delete" it from the collection:
def delete integer
element = my_elements.first(:integer => integer)
my_elements.delete(element)
self
end
(destroying it may also be part of what you're trying to do, and you can do that separately).
Just destroying it like you were doing may actually work (I'm not sure), but you'd probably have to reload the resource in your test to see the updated size:
subject.reload
subject.size.should == 0

Correct way to make a DataMapper association

I want to have a table of users. These users shall have n contacts and n messages..
My code is:
...
class User
include DataMapper::Resource
property :id, Serial, :key => true
property :nickname, String
has n, :contacts
has n, :messages
end
class Contact
include DataMapper::Resource
belongs_to :user
property :id, Serial, :key => true
property :authgiven, String
has 1, :user
end
class Message
include DataMapper::Resource
belongs_to :user
property :id, Serial, :key => true
property :data, String
end
#apply models (validation etc.)
DataMapper.finalize
...
There are no errors initializing DataMapper, but when I try to create a new User or whatever, save always returns false... Can someone please point out what is wrong?
I'm quite new to DataMapper, it always worked for me with simple tables without relationships, so I believe it has to do with the way I declared the 1:n relationship...
Hey you should remove that has 1, :user line from Contact model and you should be good.

Resources