Datamapper: How to count total score from has_many objects - ruby

I just started learning some database basics. I am using Ruby and the datamapper gem
I have two simple objects:
class Quote
include DataMapper::Resource
property :id, Serial
property :saying, String, :required => true
property :score, Integer, :default => 5
belongs_to :user
end
and
class User
include DataMapper::Resource
property :id, Serial
has n, :quotes
end
No I would like to get the total score of a user. The total score is the sum of the scores of all associated quotes of a user.
I tried something like
#totalscore = #user.quotes.inject(0) {|count, q| count + q.score}
but I guess this can't be the way I am supposed to use a database, right?
Any help is appreciated!
Best,
Tobi

I am not running the code, but by looking at the docs, I think something like this should work:
#totalscore = #user.quotes.sum :score

Related

Tracking wins & losses with PSQL/DataMapper

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

Datamapper - Get most associated N records for association ( ex: 5 most liked posts )

I am working on my blog, a Sinatra application that uses Datamapper as its ORM. I just added a feature for hash-tags. A hash-tag is as such, its a many-to-many relationship with a 'Story'. My need is to get the most popular/used 5 hash-tags to display on the sidebar.
Here is an abstract of my modals that are of interest.
The hash-tag model
class Hashtag
include DataMapper::Resource
has n, :stories, through: Resource
def self.default_repository_name
:default
end
property :id, Serial
property :created_at, DateTime
property :updated_at, DateTime
property :hashtag, String
end
The story model:
# File: models/story.rb
class Story
include DataMapper::Resource
def self.default_repository_name
:default
end
belongs_to :person
belongs_to :category
has 1, :story_content
has n, :comments
has n, :hashtags, through: Resource
property :id, Serial
property :created_at, DateTime
property :updated_at, DateTime
property :published, Boolean, default: false
property :privacy_level, Enum[ :private, :friends, :public ] # SNS access control for Twitter/FB only
end
Need: The most efficient way to get the most used n hash-tags.
Tag 1 ~> 1 story
Tag 2 ~> 2 stories
Tag 3 ~> 3 stories
For n = 2, I need ~> [Tag 2, Tag 3]
I am thinking of maintaining a counter and did it for some thing like 'Likes'. Wonder if there is any other way than cache the count of Stories per HashTag.
Since the top tags are not very likely to change very frequently, you could alternatively compute and cache them for a longer period of time, for example a day. Then, you can recompute the top five from scratch in a nightly job.
Pros:
No extra stories_count column
No maintenance of the stories_count (not that easy to handle!)
Fast queryability
Cons:
Top 5 may be out of date (not an option if you expect frequent changes)
Need to build and configure a nightly job

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.

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.

Chained aggregate call across association in DataMapper (ruby)

I am working on a simple budget app using Sinatra and DataMapper in Ruby.
I want to get the sum of all transactions across all income accounts within the last 30 days.
Something like Account.income_accounts.account_entries.sum(:amount, :transaction_date.gte => Date.today - 30) should work. Instead, the limiting condition on transaction_date is getting ignored, returning the sum of the amount for all entries for all income accounts.
Given the following:
class Account
include DataMapper::Resource
has n, :account_entries
property :id, Serial
property :name, String
property :acct_type, String
def self.income_accounts
all(:acct_type => 'Income')
end
end
class AccountEntry
include DataMapper::Resource
belongs_to :account
property :id, Serial
property :account_id, Integer
property :description, String
property :amount, BigDecimal
property :transaction_date, DateTime
end
I am properly requiring dm-aggregates. I am new to DataMapper. If it matters, I am using a sqlite3 database. I really don't want to resort to using ruby to sum the results. It also feels wrong to resort to executing raw SQL for this type of simple aggregate query.
Can anyone shed some light on this? I would love to be pointed in the right direction regarding chained finders in DataMapper, particularly with aggregates. My spelunking into the API and the DataMapper site hasn't yielded a solution as of yet.
I just wrote a small stand-alone script to test your example, and it appears to return the correct results. Please note I am using edge extlib, dm-core, and dm-more all installed from git:
#!/usr/bin/env ruby -Ku
# encoding: utf-8
require 'rubygems'
require 'dm-core'
require 'dm-aggregates'
DataMapper::Logger.new($stdout, :debug)
DataMapper.setup(:default, 'sqlite3::memory:')
class Account
include DataMapper::Resource
property :id, Serial
property :name, String
property :acct_type, String
has n, :account_entries
def self.income_accounts
all(:acct_type => 'Income')
end
end
class AccountEntry
include DataMapper::Resource
property :id, Serial
property :description, String
property :amount, BigDecimal
property :transaction_date, Date
belongs_to :account
end
DataMapper.auto_migrate!
account = Account.create(
:name => 'Test Account',
:acct_type => 'Income'
)
5.times do |n|
account.account_entries.create(
:description => "Account Entry #{n}",
:amount => 1.00,
:transaction_date => Date.today
)
end
puts Account.income_accounts.account_entries(:transaction_date.gte => Date.today - 30).sum(:amount).to_s('F') # => 5.0
Can you run the above program and let me know what it returns for you? If you get something other than 5.0, try updating to the latest packages and retry.
DateTime uses second as it's base unit Date.today - 30 is 30 seconds ago. Try Date.today - 30.days
Did you try DateTime.now-30 or maybe even Time.now-30*3600*24 instead of Date.today-30 ?
User error. I mucked around with to_s on DateTime to use the time formats in strftime. When removed, the chained aggregate worked as anticipated.

Resources