Rails & Sqlite question - comparing dates to db timestamps - ruby-on-rails

I'm trying to do this: map the total sales on a day to an array of dates for highcharts (yes my project is effectively exactly the same as the railscast example).
I'm unfortunately just ending up with a lot of 0s; I believe the piece in my model:
def self.total_revenue_on(date)
where("date(created_at) = ?", date).sum(:amt)
end
is failing to match the date to the datetime written in my database, e.g. "2011-07-21 09:22:28.388944+0000". Pretty sure that's where it's failing because if I remove the timezone piece manually from my database (get rid of "+0000" and leave just "2011-07-21 09:22:28.388944") it works just fine.
I think this is really a rails/sqlite question: am I storing the timestamp improperly, or comparing improperly? Any help is greatly appreciated!

The best practice is to use to_s(:db) for referencing datetimes in a database in Rails. Try:
def self.total_revenue_on(date)
where("date(created_at) = ?", date.to_s(:db)).sum(:amt)
end

OK, I managed to solve this by using a different lookup method:
def self.total_revenue_on(date)
where("datetime >= ? and datetime < ?", date, date + 1.day).sum(:amt)
end
Still completely perplexed by the problem with the original, but this seems to be working.

Related

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, ........")

How to use where clause on methods rather than database columns?

One problem I often run into in Rails is this:
Let's say I have an invoices table with a date and a days column.
How can I retrieve all invoices which are due?
class Invoice < ActiveRecord::Base
def self.due
where("due_date > ?", Date.today) # this doesn't work because there is no database column "due_date"
end
private
def due_date
date + days
end
end
Can anybody tell me how to do this without having to add a database column due_date to my invoices table?
Thanks for any help.
In PostgreSQL, adding an integer to a date adds that many days:
date '2001-09-28' + integer '7' = date '2001-10-05'
so you can simply say:
where('due_date + days > :today', :today => Date.today)
However, SQLite doesn't really have a date type at all, it stores dates as ISO 8601 strings. That means that adding a number to a date will end up concatenating the strings and that's sort of useless. SQLite does have a date function though:
date(timestring, modifier, modifier, ...)
[...]
All five date and time functions take a time string as an argument. The time string is followed by zero or more modifiers.
so you can say things like date('2014-01-22', '+ 11 days') to do your date arithmetic. That leaves you with this:
where("date(due_date, '+' || days || ' days') > :today", :today => Date.today)
Thankfully, ISO 8601 date strings compare properly as strings so > still works.
Now you're stuck with two versions of the same simple query. You could check what sort of thing self.connection is to differentiate between dev/SQLite and production/PostgreSQL or you could look at Rails.env.production?. This of course leaves a hole in your test suite.
I think you should stop developing on top of SQLite if you intend on deploying on top of PostgreSQL and you should do that right now to minimize the pain and suffering. The truth is that any non-trivial application will be wedded to the database you use in production or you will have to expend significant effort (including running your test suite against all the different databases you use) to maintain database portability. Database independence is a nice idea in theory but wholly impractical unless someone is prepared to cover the non-trivial costs (in time and treasure) that such independence requires. ORMs won't protect you from the differences between databases unless your application is yet another "15 minute blog" toy.
You could do something like:
class Invoice < ActiveRecord::Base
def self.due
Invoice.all.select { |invoice| invoice.due_date > Date.today }
end
private
def due_date
date + days
end
end

Rails query timestamp between two hours

I have a problem in Ruby on Rails, where I need to allow a user to set a two time columns, and then query all instances of that model that have the current time within the hours of the two timestamps. Days do not matter, just within the times specified be the start and end timestamps.
Thanks,
Brian
Something like this should work (assuming MySQL):
Model.where("TIME(created_at) BETWEEN '07:00:00' AND '09:00:00'")
You can convert a Time instance to this format with:
Time.now.strftime("%T") #=> "23:02:25"
If the start time is larger than the end time you could reverse the logic, so instead of:
Model.where("TIME(created_at) BETWEEN '23:00:00' AND '01:59:59'")
You could use:
Model.where("TIME(created_at) NOT BETWEEN '02:00:00' AND '22:59:59'")
You might be looking for something like
Model.where("starts_at >= ? AND ends_at =< ?", Time.current, Time.current)
Alternatively, you can use placeholder conditions to make the query more concise.
Client.where("created_at >= :start_date AND created_at <= :end_date",
{start_date: params[:start_date], end_date: params[:end_date]})
How you solve this problem will depend on what database you're using b/c as far as I'm aware there aren't any ActiveRecord helpers for this type of operation. But there are date functions that should help you figure this out
Postgres Date functions
Mysql Date functions
So for example, in Postgres you might try
extract(hour from timestamp 'a') BETWEEN (extract(hour from timestamp 'b') AND extract(hour from timestamp 'c'))

DateTime arithmetic in an ActiveRecord query (PostgreSQL)

I have two datetime columns in a User/users table: created_at and birthdate. I'd like to find users whose birthdate is less than 13 years before their creation date.
The equivalent Rails if statement would be ((created_at - birthdate) < 13.years)
How do I translate this to an ActiveRecord query (one that'll work on PostgreSQL)? Is this even possible, or will I have to iterate over all records manually?
The easiest way to do this is to use an interval, then it is pretty much a straight transliteration of the Rails version:
User.where(%q{created_at - birthdate < interval '13 years'})
The difference between two timestamps (or a timestamp and a date) is an interval so you just need the appropriate value on the right side of your comparison.
You simply have to formulate that in PostgreSQL syntax inside your where clause.
For MySQL this would look similar to this using the datediff function:
User.where("DATEDIFF(created_at, birthdate) > (13 * 365)")
13*356 is there to represent 3 years in days since datediff returns difference in days.
I would then encapsulate that in a scope-like function like the following:
class User < ActiveRecord::Model
def self.age_difference(years)
where("DATEDIFF(created_at, birthdate) > (? * 365)", years)
end
end
So you can call it:
User.age_difference(13).each do |user|
puts user.inspect
end
I guess it's similar in Postgres.

How do I write an activerecord query that only looks at the time component of a datetime field?

Is it possible to do an activerecord query that only looks at the time component of a datetime field?
e.g. Battle.where('start_time < ? and start_time > ?','12:00','06:00')
to find all battles that were started between 6am and 12pm regardless of the day they occurred? In this example, start_time is defined as a datetime.
The only way to do this is using a SQL function, if you're on MySQL you could do it like this:
Battle.where( 'HOUR( start_time ) >= ? AND HOUR( start_time ) <= ?', 12, 6 )
But this is hugely inefficient and is always going to generate a full table scan and you surely don't want that.
The best solution is to add columns with the hour values alone at your battle model, index them and query directly on them like this:
Battle.where( 'start_time_hour >= ? start_time_hour <= ?', 12, 6 )
Just add an before_save callback that sets this values before saving your Battle model based on the start_time property.
EDIT
BTW, if you're using PostgreSQL, it's capable of creating an index on the result of a function, so, if you don't mind having to stick with PostgreSQL you could just use the SQL function. And before you ask, no, i don't know how it works, I only know the database allows you to do so and that this is not available on MySQL.

Resources