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.
Related
I have a User class which allows people to register and play a game; I also have a Game class, which contains the logic of said game (RPS).
When people register, their information is held in a psql database. The informations is obtained using params. It looks like this:
Class User
attr_reader :weapon
include DataMapper::Resource
property :id, Serial
property :name, String, required: true
property :email, String, required: true, unique: true
property :password_digest, Text
attr_reader :password
attr_accessor :password_confirmation
validates_confirmation_of :password
validates_format_of :email, as: :email_address
has n, :games
The corresponding Game class, contains this DB logic:
class Game
include DataMapper::Resource
property :id, Serial
property :win, ?
property :lose, ?
belongs_to :user
My issue is that I really don't know how to keep a record of how many games the user has won/lost. Do I need (or should I have separate classes for wins and losses? What key type should I use (serial/int)? All I want is for 'win' or 'lose' to increment by one each time the player...well, wins or loses.
All help/knowledge shared is greatly appreciated.
Thanks.
One way to do it could be to add two integer columsn "wins" and "losses" to User, and make helper methods to increment:
property :wins, Integer, default: 0
property :losses, Integer, default: 0
# usage: user.increment(:wins) or user.increment(:losses)
def increment(type)
update({ type => (send(:type) + 1) })
end
Here's my models:
class Item
include DataMapper::Resource
property :id, Serial
has 1, :firstitem
has 1, :seconditem
end
class FirstItem
include DataMapper::Resource
property :id, Serial
belongs_to :item
end
class SecondItem
include DataMapper::Resource
property :id, Serial
belongs_to :item
end
Now, my question is this - if I want FirstItem and SecondItem to be different models but want them both to potentially be part of Item (but only one of the two, so a record with FirstItem will not also have a SecondItem), I could make a has 1 relationship for both of them, and only one of them gets filled.
So in a relational database, does it make sense to do this? Is there a better, more efficient way of defining this relationship?
What you want is a polymorphic association, which sadly, DataMapper does not support. Try looking into ActiveRecord instead; you can easily use it with Sinatra.
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.
I'm building a small Ruby application using DataMapper and Sinatra, and I'm trying to define a basic blog model:
The blog has multiple Users
I have a collection of Posts, each of which is posted by a User
Each Post has a set of Comments
Each Comment can have its own set of Comments - this can repeat several levels deep
I'm running into trouble getting the self-referential relation between comments going due to the fact that each Comment belongs_to a Post. My classes right now look like this:
class User
include DataMapper::Resource
property :id, Serial
property :username, String
property :password, String
has n, :post
end
class Post
include DataMapper::Resource
property :id, Serial
property :content, Text
belongs_to :user
has n, :comment
end
class Comment
include DataMapper::Resource
property :id, Serial
property :content, Text
belongs_to :user
belongs_to :post
end
I'm following the guide at Associations and building a new object (CommentConnection) to link two comments together, but my issue is that each subcomment shouldn't belong to a Post as implied by the Comment class.
My first instinct was to extract out a superclass for Comments, so that one subclass could be "top-level" and belong to a post, while the other kind of comment belongs to another comment. Unfortunately, when I do that I run into issues with the comment IDs becoming null.
What's the best way to model this kind of recursive comment relationship in DataMapper?
What you need is a self referential join in Comments, e.g., each Comment can have a parent comment. Try the following:
class Comment
include DataMapper::Resource
property :id, Serial
property :content, Text
has n, :replies, :child_key => [ :original_id ]
belongs_to :original, self, :required => false #Top level comments have none.
belongs_to :user
belongs_to :post
end
This will allow you to have replies to any given comment, although accessing them may get a little nasty (slow) if the volume gets high. If you get this working and want something more sophisticated you could look at nested sets, I believe there is a nested sets plugin for DataMapper but I haven't used.
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.