Find_or_initialize_by Issue - ruby-on-rails

I have a DB Table for a Model entitled TradeDailyAverage. It has a date (DateTime) & averageprice (decimal) column
When I run this, I can't get the averageprice attribute to update:
newaverage = TradeDailyAverage.find_or_initialize_by_date(date)
newaverage.update_attributes :averageprice => dailyaverage
Further, when I run this, the date will show up, but the averageprice will not show up in rails console. It only shows up as blank:
newaverage = TradeDailyAverage.find_or_initialize_by_date(date)
puts newaverage.averageprice
puts newaverage.date
Is there anything special that I need to do to averageprice before I save it?
Here is all of the entire Rake Task for your reference:
averages = Trade.where('date >= ?', 7.days.ago).average(:price, :group => "DATE_TRUNC('day', date - INTERVAL '1 hour')")
# Loops through each daily average produced above
averages.each do |date, avg|
# Converts the BigDecimal to Floating Point(?)
averagefloat = avg.to_f
# Rounds the Daily Average to only two decimal points
dailyaverage = number_with_precision(averagefloat, :precision => 2)
newaverage = TradeDailyAverage.find_or_initialize_by_date(date)
newaverage.update_attributes :averageprice => dailyaverage

If you want to use find_or_initialize_by you need to think carefully about the implications. Lets take your first example:
newaverage = TradeDailyAverage.find_or_initialize_by_date(date)
newaverage.update_attributes :averageprice => dailyaverage
This should work, when the TradeDailyAverage for the given date is already in the database. It should not work however, when you get a new record back. The reason is simply because a new record is not persisted to the database. There is no way for update_attributes to update a non persisted record. You have two options here:
1.) Do not use update_attributes but assign the value and call save. This works for both, new and created records:
newaverage = TradeDailyAverage.find_or_initialize_by_date(date)
newaverage.averageprice = dailyaverage
newaverage.save
2.) Do not use find_or_initialize_by but find_or_create_by. This way if the record does not exist, a new one is directly written to the database. Now update_attributes should work because you always get persisted records back. Note that this approach has the drawback that you save records without an averageprice to the database. I would not recommend that.
The explanation above should also explain your second example:
newaverage = TradeDailyAverage.find_or_initialize_by_date(date)
puts newaverage.averageprice
puts newaverage.date
This should output the averageprice and the date for persisted records. If you get a newly initialized record back though, it will only display the date. Since you only initialized the record with a date object, there is no way that the averageprice is already set.

My issue was simply that upon saving to my database, PostgreSQL was changing the hourly time, possibly due to a timezone issue. Thus, all of my instances above were new, and I couldn't update attributes of an existing model. So, I converted my datetime data to dates, changed my date db column to date instead of datetime, and now everything is working smoothly. Yves gives some great info above though which helped me later on.

Related

Getting Unix Time from Active Record ruby on rails

I have one table Bandwidth where I am saving user_traffic and created_at fields. I want to return user_name and created_at but I want date to be in Unix Time stamp in ruby on rails without using Query but by activerecord.
Bandwidth.all.select("user_traffic,created_at")
Above return both but in normal date format, but I want it to be in Unix
created_at: "2019-06-26 11:28:39", user_traffic: 0
I tried following and it works, but I could not get other column in this.
Bandwidth.find_by(id: 2).created_at.to_i
It return just timestamp which is perfect but how can I add other columns in this query. I think it is using model function to_i
=> 1561548975
If you don't need the relation later, you can use map to make any data format you want.
EX:
Bandwidth.all
.select(:user_traffic, :created_at)
.map{|b| [b.user_traffic, b.created_at.to_i]}

'Time' being changed to a 'Datetime' after being added to a hash

My controller creates something called start_time. When I print start_time's value before it's added to a LittleClassSession hash, here's what I get:
22:45:00
Okay, it looks like a value with the type time. After it's added to the hash, I ask the controller what the :start_time value is.
#little_class_session = LittleClassSession.new({
...
:start_time => start_time
})
puts #little_class_session.start_time
Here's what it puts:
2000-01-01 22:45:00 UTC
It appears to be formatted like a datetime, but asking what the .class of the start_time attribute is returns:
Time
The LittleClassSession start_time column is a time in the table (I can verify this by checking the type in the Rails console) but was a datetime when the model was created.
What could be causing this?
While your database may support a "time" column (meaning just a time with no date information), Rails by default does not (largely because neither does Ruby's standard library -- even a Time contains date information). As such, when you assign it to your model, Rails is coercing it into the type it knows how to deal with, DateTime. So, you have a few options:
Ignore the date part of the time when you use it.
Use a gem like tod to deal with your time-only types, and follow the guidelines in the README for hooking it up to Rails.
Store start_time_hour and start_time_minutes in two separate columns, and work with them as needed (e.g, Date.current + start_time_hour.hours + start_time_minutes.minutes).
Hope that helps!

How to update thousands of records

I have to update an age column based on the value in a date of birth column. There are thousands of records to update.
How do I do this using rails?
Is this the right way to do it?
User.update_all(:age => some_method);
def some_method
age = Date.today.year - dob.year
end
Yes, update_all is the right method but no, you can't do it like this. Your some_method will only get called once to set up a database call (I assume you're persisting to a database). You'll then get an error because dob won't be recognised in the scope of the User class.
You'll need to translate your date logic to SQL functions.
Something like (for mysql):
User.update_all("age = year(now()) -
year(dob) -
(DATE_FORMAT(now(), '%m%d') < DATE_FORMAT(dob, '%m%d'))")
(NB. the date_format stuff is so that you get the right age for people who's birthdays are later in the year than the current date - see this question for more details)
The other option is to use one of the batches functionality in rails.
User.where(some_condition).find_in_batches do |group_of_users|
# some logic
# e.g. group_of_users.update_all(:age => some_logic)
end
This would lock your db for less time. Note that you should pretty much always update with a condition in mind. I can't think of many cases you would want to update an entire table every time something happens.
There are a few options checkout the rails docs or the api.
your query is right.
There are many way to update record in a batch/lot.
But, I think that your query is best. Because it is rails query that will support every condition for all database.
for updating more than one attributes
Model.update_all(:column1 => value1, :column2 => value2, ........)
or
you can use :
Model.update_all("column1 = value1, column2 = value2, ........")

Timestamp can't be saved - postgresql

I have the below code snippet:
class Foo
include DataMapper::Resource
property :id, Serial
property :timestamp, DateTime
end
I just want to convert the current time to ms:
class Time
def to_ms
(self.to_f * 1000.0).to_i
end
end
def current_time
time = Time.now
return time.to_ms
end
time = current_time # => 1352633569151
but when I am going to save the Foo with above timestamp, then it can't be saved to the database and I'm not getting any error message.
foo = Foo.new
foo.timestamp = time
foo.save
Any idea?
are you using a correct format for your :datetime property?
should be like:
DateTime.now.to_s
=> "2012-11-11T14:04:02+02:00"
or a "native" DateTime object, without any conversions.
DataMapper will carry to convert it to according values based on adapter used.
also, to have exceptions raised when saving items:
DataMapper::Model.raise_on_save_failure = true
that's a global setting, i.e. all models will raise exceptions.
to make only some model to behave like this:
YourModel.raise_on_save_failure = true
http://datamapper.org/docs/create_and_destroy.html
See "Raising an exception when save fails" chapter
btw, to see what's wrong with your item before saving it, use and item.valid? and item.errors
foo = Foo.new
foo.timestamp = time
if foo.valid?
foo.save
else
p foo.errors
end
I replicated your code and got following error:
#errors={:timestamp=>["Timestamp must be of type DateTime"]}
See live demo here
The PostgreSQL data types would be timestamp or timestamp with time zone. But that contradicts what you are doing. You take the epoch value and multiply by 1000. You'd have to save that as integer or some numeric type.
More about the handling of timestamps in Postgres in this related answer.
I would save the value as timestamp with time zone as is (no multiplication). You can always extract ms out of it if need should be.
If you need to translate the Unix epoch value back to a timestamp, use:
SELECT to_timestamp(1352633569.151);
--> timestamptz 2012-11-11 12:32:49.151+01
Just save "now"
If you actually want to save "now", i.e. the current point in time, then let Postgres do it for you. Just make sure the database server has a reliable local time - install ntp. This is generally more reliable, accurate and simple.
Set the DEFAULT of the timestamp column to now() or CURRENT_TIMESTAMP.
If you want timestamp instead of timestamptz you can still use now(), which is translated to "local" time according to the servers timezone setting. Or, to get the time for a given time zone:
now() AT ZIME ZONE 'Europe/Vienna' -- your time zone here
Or, in your particular case, since you seem to want only three fractional digits: now()::timestamp(3) or CURRENT_TIMESTAMP(3) or CURRENT_TIMESTAMP(3) AT ZIME ZONE 'Europe/Vienna'.
Or, if you define the type of the column as timestamp(3), all timestamp values are coerced to the type and rounded to 3 fractional decimal digits automatically.
So this would be all you need:
CREATE TABLE tbl (
-- other columns
,ts_column timestamp(3) DEFAULT now()
);
The value is set automatically on INSERT, you don't even have to mention the column.
If you want to update it ON UPDATE, add a TRIGGER like this:
Trigger function:
CREATE OR REPLACE FUNCTION trg_up_ts()
RETURNS trigger AS
$BODY$
BEGIN
NEW.ts_column := now();
RETURN NEW;
END
$BODY$ LANGUAGE plpgsql VOLATILE
Trigger:
CREATE TRIGGER log_up_ts
BEFORE UPDATE ON tbl
FOR EACH ROW EXECUTE PROCEDURE trg_up_ts();
Now, everything works automatically.
If that's not what you are after, #slivu's answer seems to cover the Ruby side just nicely.
I'm not familiar with PostgreSQL, but why are you assigning a Fixnum (time) to timestamp (which is a DateTime)? Your model must be failing to convert time to a proper DateTime value before generating the SQL.
Try foo.save!. I'm pretty sure you'll see an error, either reported from PostgreSQL, saying 1352633569151 is not a valid value for the table column, or your model will say it can't parse 1352633569151 to a valid DateTime.
foo.timestamp = Time.now or foo.timestamp = '2012-11-11 00:00:00' is something that'll work.

SQLite /Rails datetime query problems

I'm building a Rails application, which is creating orders from a schedule. The schedule has a time in format hh:mm, and ticks for each day of the week. A method occasionally checks the schedules, and creates any orders required by the schedule.
Firstly, I build up the time for this week's order in a Ruby DateTime object, then check if it exists, and create if not e.g.:
order = Order.where( :delivery_datetime = del_datetime )
unless order.any?
Order.create( :status => 'Estimated', :delivery_datetime => del_datetime )
end
That works as expected on my machine, but when other people picked it up from the repository, it would recreate the orders every time. I investigated the SQL it was using, and the problem seemed to be it was creating a where clause slightly different to the insert statement:
SELECT COUNT(*) FROM orders WHERE delivery_datetime = '2011-06-30 18:00:00.000000'
INSERT INTO orders (delivery_datetime) VALUES ('2011-06-30 18:00:00.000000000')
So the difference is the three extra zeroes in the partial second field. I understand SQLite doesn't have real date types, so these are different just because the strings are different. The problem I am having now is that I can't seem to force the format of the inserted string. E.g. even if I do the following:
Order.create( :status => 'Estimated',
:delivery_datetime => del_datetime.strftime( '%Y-%m-%d %H:%M' ) )
the insert statement still uses a 'standard' format - but with 6 zeroes on my instance, and 9 on another.
No answers! Not seen that on Stack Overflow before. Right now, I have 'fixed' it by declaring the database field as a string, and then using strftime to ensure the date format remains the same. This works, but doesn't seem ideal.
After reading all the stuff within Sqlite docs about datatypes being a 'misfeature', I'm thinking of dropping it for Postgres or similar. I want datetimes to be datetimes, and for it not to randomly decide that 18:00:00.000000 and 18:00:00.000000000 are different times...

Resources