Chained aggregate call across association in DataMapper (ruby) - 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.

Related

Ruby datamapper associations

I am just learning Ruby and datamapper, I have read the docs about associations from the official DataMapper site, but I still have two problems.
First whenever I add associated object, I can not see it when displaying all objects.
I have test class like:
class Test
include DataMapper::Resource
property :id, Serial
property :name, String
has 1, :phonen, :through => Resource
end
And then phonen class like:
class Phonen
include DataMapper::Resource
property :id, Serial
property :number, String
belongs_to :test
end
Then I am creating those 2 objects
#test = Test.create(
:name => "Name here"
)
#phone = Phonen.create(
:number => "Phone number"
)
#test.phonen = #phone
#test.save
And I want to display them like that (I want to return json)
get '/' do
Test.all.to_json
end
What am I doing wrong? maybe its something with the to_json...
I honestly don't know..
But I have one additional question to this topic, lets say I managed to connect those two classes, if I display JSON will I get Phonen { } or just inside class { }?
I know its probably very easy question, but I can't figure it out. That's why I decided to ask you guys. Thanks for help
Test.all
Is returning an active record association in array form, not a hash, when you try to convert to json it's failing.
You can try:
render json: Test.all
As asked in this question:
Ruby array to JSON and Rails JSON rendering

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.

How can I resolve the IllegalContextError when attempting to save a datamapper model?

When I try to this code, I get an IllegalContextError at the "self.save..." line. Can you tell me what I'm doing wrong?
I would just call the create method on Player without messing around with initialize, but I want a related week object to be created as part of the initialization.
require 'data_mapper'
DataMapper::setup(:default, "sqlite3://#{Dir.pwd}/prod.db")
class Player
include DataMapper::Resource
property :name, String, :key => true
property :sport, String
has n, :weeks
def initialize(name, sport, week)
self.save(:name => name, :sport => sport)
self.weeks.create(:id => "#{name}#{week}", :score => 0)
end
end
class Week
include DataMapper::Resource
property :id, String, :key => true
property :week, Integer
property :score, Integer
belongs_to :player
end
DataMapper.finalize.auto_migrate!
Player.new("jack", "golf", 5)
I understand that this is probably not the best way, so before you shoot my method down, please provide a better solution. I will probably accept your answer :)
It seems like the IllegalContextError is originating from the data_mapper validators.
The data_mapper docs on validators doesn't provide much info for a newbie to understand context AND in relation to validators.
Here is my hacky workaround. I override the validators by using the bang operator (!). The solution is as follows.
require 'data_mapper'
DataMapper::setup(:default, "sqlite3://#{Dir.pwd}/prod.db")
class Player
include DataMapper::Resource
property :name, String, :key => true
property :sport, String
has n, :weeks
def initialize(name, sport, week)
self[:name] = name
self[:sport] = sport
self[:week] = week
self.save!
self.weeks.create(:id => "#{name}#{week}", :score => 0)
end
end
class Week
include DataMapper::Resource
property :id, String, :key => true
property :week, Integer
property :score, Integer
belongs_to :player
end
DataMapper.finalize.auto_migrate!
Player.new("jack", "golf", 5)

Composite primary key is used only to access records but not save records in data_mapper

I have an app that tracks a player's progress over a fitness program. So every player has multiple weeks with the same :week_id
Week_id combined with the belongs_to relationship is the composite primary key for the Week record.
However, when I try to create two weeks with the same week_id that belong to different players, I get a "column week_id is not unique" error.
I feel like I am on the right track because when I want to fetch a week record, it tells me that I need two arguments to get it - the week_id and the player_id.
I am probably missing something simple here. I hope you can show me.
require "rubygems"
require "json"
require "sqlite3"
require "data_mapper"
require "bigdecimal"
DataMapper::setup(:default, "sqlite3://#{Dir.pwd}/prod.db")
DataMapper::Model.raise_on_save_failure = true
class Player
include DataMapper::Resource
property :name, String, :key => true
property :age, Integer
has n, :weeks
end
class Week
include DataMapper::Resource
property :week_id, Integer, :key => true
property :score, Integer
belongs_to :player, :key => true
end
DataMapper.finalize.auto_migrate!
#jack = Player.create(:name => "jack")
#jack.weeks.create(:week_id => 1)
#jill = Player.create(:name => "jill")
#jill.weeks.create(:week_id => 1)
Looks like you already came up with a solution, but I'll go ahead and post another answer for posterity, since I ran into the same issue.
It seems like declaring ':player_name' as a property that is a key as well as with the 'belongs_to' declaration, you end up with the same SQL to create the table, but it also recognizes that week_id is part of a composite key and does not need to be unique on its own. (It appears DataMapper has issues with composite keys split across 'property' and 'belongs_to' declarations.)
class Week
include DataMapper::Resource
property :week_id, Integer, :key => true
property :player_name, String, :key => true
property :score, Integer
belongs_to :player, :key => true
end
Results in the following SQL to create the table:
~ (0.000000) CREATE TABLE "weeks" ("week_id" INTEGER NOT NULL, "player_name" VARCHAR(50) NOT NULL, "score" INTEGER, PRIMARY KEY("week_id", "player_name"))
And your example code works without errors
So I ended up making a small change to my Week model like so
class Week
include DataMapper::Resource
property :id, String, :key => true
property :week_id, Integer
property :score, Integer
belongs_to :player
end
And whenever I create a new Player model I concatenate the foreign key and the week_id to create the id string
So my id string will look something like "jack1" for the first record for a player with an :id of "jack"
Not sure if this is the data_mapper way but it works.

Datamapper: How to count total score from has_many objects

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

Resources