Datamapper datetime property - ruby

I got stuck with some strange behavior of DataMapper's DateTime property.
Here's a simple code:
DataMapper.setup(:default, 'sqlite::/path/to/some/file.db')
class Event
include DataMapper::Resource
property :id, Serial
property :time, DateTime
end
I create one item:
e = Event.new
e.time = Time.now
e.save
And now strange things happen:
Time before save is ok.
In the database file time is also ok.
puts e.time.to_s
# 2011-05-01T22:38:49+02:00
But then I'm getting DateTime without "time" part.
puts Event.first.time.to_s
# 2011-05-01T00:00:00+02:00
Any ideas?

Unfortunately you've hit a bug in latest DataObjects. It will be fixed in next version, there's already a pull request that fixes the bug: https://github.com/datamapper/do/pull/9

Related

DataMapper does not see a property assigned with #

I'm getting started with Ruby and DataMapper and I stumbled upon a problem which I think does not make any sense. Let's say I have the following model :
class Foo
include DataMapper::Resource
property :id, Serial
property :date, Date, required: true
def initialize
#date = Date.today
end
end
I open up IRB to test my models, set up the database connection, and try to save a new foo:
> foo = Foo.new
> foo.date
=> #<Date: 2013-03-28 ((2456380j,0s,0n),+0s,2299161j)>
> foo.save
Then I get the following exception :
DataObjects::IntegrityError: foos.date may not be NULL
And that perfectly makes sense, because I marked the date as required. But the date is there! I assigned it in the constructor of the class! And if I don't initialize it with today's date and I try to save, I only get a validation error. No exception.
What I don't understand (and this is what I want you to answer to) is that if I replace
#date = Date.today
with
self.date = Date.today
it works! foo is saved correctly. Why is that? Is it a bug inside DataMapper?
After bringing up the issue with DataMapper's people, I've been told that this is by design. For dirty tracking to work, you must absolutely use the attribute writer and not set the instance variable directly.

How to get a (Ruby) DataMapper custom type to work?

I have a SchoolDay class that represents a school day: it can tell you the date, the semester, the term, the week, and the day. It can generate a string like "Sem1 13A Fri". To store these objects in the database, I want them serialized as a string.
Here is my DataMapper custom type code. I've sort of scraped ideas from the code in dm-types because (disappointingly) there is no real documentation for creating custom types. Sorry it's long.
module DataMapper
class Property
class SchoolDay < DataMapper::Property::String
#load_as ::SchoolRecord::DomainObjects::SchoolDay
# Commented out: the 'load_as' method is not found
def load(value)
# Take a string from the database and load it. We need a calendar!
val = case value
when ::String then calendar.schoolday(value)
when ::SR::DO::SchoolDay then value
else
# fail
end
end
def dump(value)
# Store a SchoolDay value into the database as a string.
case value
when SR::DO::SchoolDay
sd = value
"Sem#{sd.semester} #{sd.weekstr} #{sd.day}"
when ::String
value
else
# fail
end
end
def typecast(value)
# I don't know what this is supposed to do -- that is, when and why it
# is called -- but I am aping the behaviour of the Regexp custom type,
# which, like this one, stores as a String and loads as something else.
load(value)
end
# private methods calendar() and error_message() omitted
end
end
end
This code works for reading from the (SQLite) database, but not for creating new rows. The error message is:
Schoolday must be of type String
The code that defines the DataMapper resource and tries to create the record is:
class LessonDescription
include DataMapper::Resource
property :id, Serial
property :schoolday, SchoolDay # "Sem1 3A Fri"
property :class_label, String # "10"
property :period, Integer # (0..6), 0 being before school
property :description, Text # "Completed yesterday's worksheet. hw:(4-07)"
end
# ...
ld = LessonDescription.create(
schoolday: #schoolday,
class_label: #class_label,
period: #period,
description: description
)
Here is the code for the Regexp datamapper type in the dm-types library. It's so simple!
module DataMapper
class Property
class Regexp < String
load_as ::Regexp # NOTE THIS LINE
def load(value)
::Regexp.new(value) unless value.nil?
end
def dump(value)
value.source unless value.nil?
end
def typecast(value)
load(value)
end
end
end
end
For some reason, I cannot use the load_as line in my code.
To summarise: I am trying to create a custom type that translates between a SchoolDay (domain object) and a String (database representation). The translation is easy, and I've copied the code structure primarily from the DataMapper Regexp type. But when I try to save a SchoolDay, it complains that I'm not giving it a string. Frustratingly, I can't use the "load_as" method that the built-in and custom types all use, even though I have the latest gem. I can't find the "load_as" method defined anywhere in the source code for DataMapper, either. But it's called!
Sorry for the ridiculous length. Any help would be greatly appreciated, as would a pointer to a guide for creating these things that I have somehow missed.
It seems that the current code of dm-types at github hasn't made it to any official release -- that's why load_as doesn't work in your example. But try to add this method:
module DataMapper
class Property
class SchoolDay < DataMapper::Property::String
def custom?
true
end
end
end
end
That's working here.

How can I get Sinatra to return a record matching today's date?

My Sinatra app is a collection of notes. Each note is assigned a (future) date when it should be published:
class Note
include DataMapper::Resource
property :id, Serial
property :publish_date, Date
property :content, String
end
I'd like to create a route that will display only today's note, based on the publish_date:
get '/' do
...
erb :today
end
The note I want might be found using note.publish_date.to_s = Date.today.to_s but I can't seem to figure out the syntax to make this work. Thanks in advance for setting be straight!
Something like
get '/' do
Note.first(:publish_date => Date.today)
erb :today
end
perhaps?

Ruby DataMapper not saving property set in before hook

class Order
include Datamapper::Resource
property :birthday_day, String
property :birthday_month, String
property :birthday_year, String
property :birthday, Date
before :save do
#birthday = Date.new(#birthday_year.to_i, #birthday_month.to_i, #birthday_day.to_i)
end
end
It's a part of model, but it's enouth.
When save field (from irb or from sinatra application) :birthday not save in DB. But in irb, i see object, where :birthday exist and it Date format.
When change field manual (from irb):
f.birthday = Date.new
f.save
In object and in DB result appear (in obj as Date obj, in DB as "2010-2-3")
Help me please to understand, what wrong with before in model.
Sorry for my not good enlish.
You should use property mutator method. Setting the ivar doesn't trigger dirty tracking. Just do this:
self.birthday = Date.new(birthday_year.to_i, birthday_month.to_i, birthday_day.to_i)
Also it would be better to use Integer as the property type for year, month and day.
DataMapper documentation recommends #attribute_set
Sets the value of the attribute and marks the attribute as dirty if it has been changed so that it may be saved. Do not set from instance variables directly, but use this method.
In your case:
before :save do
set_attribute(:birthday => Date.new(self.birthday_year.to_i, self.birthday_month.to_i, self.birthday_day.to_i))
end
For what it's worth, unless I needed to use all the fields in SELECT criteria, I would save either the integer fields or the date, not both:
class Order
include Datamapper::Resource
property :birthday, Date
end
# change month
o = Order.create(:birthday => Date.new(...))
o.update(:birthday => Date.new(o.birthday.year, new_month, o.birthday.mday))
Or
class Order
include Datamapper::Resource
property :birthday_day, String
property :birthday_month, String
property :birthday_year, String
def birthday
if self.birthday_day && self.birthday_month && self.birthday_year
Date.new(self.birthday_day, ...)
end
end
end

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