Postgresql date comparison with two timestamps without timezone - ruby-on-rails

I have an error while trying to compare datetimes with rails and postgresql
Now, I created the table using this simple schema:
def change
create_table :events do |t|
t.string :name
t.datetime :start_time
end
end
I try then to get events in the future using this scope:
scope :active_events, ->(id) { includes(:event).where(:foursquare_id => id).where('events.start_time => now()::timestamp') }
That translate in postgresql as :
SELECT COUNT(DISTINCT "venues"."id")
FROM "venues"
LEFT OUTER JOIN "events" ON "events"."venue_id" = "venues"."id"
WHERE "venues"."foursquare_id" = '4ada1e5ff964a5209f1e21e3'
AND (events.start_time => now()::timestamp)
Now I have this error that shows up then :
PG::UndefinedFunction: ERROR: operator does not exist: timestamp without time zone => timestamp without time zone
LINE 1: ...'4ada1e5ff964a5209f1e21e3' AND (events.start_time => now()::...
and I'm really not sure to understand this. Normally they are of the same data type I assume they would be able to compare but it doesn't seem to be the case.
What am I missing here ?

The operator you're looking for is >=, => is usually used with hstore.

Related

convert string to datetime when making activerecord db query

I have the following table in my DB:
class CreateGoogleRecords < ActiveRecord::Migration
def change
create_table :google_records do |t|
t.string :user_id
t.string :date
t.text :stats
t.string :account_name
t.integer :total_conversions
t.decimal :total_cost
t.timestamps
end
end
end
I'm looking to create a table inside a view that groups together records by month (I can't use "date created because sometimes they are scraped in bulk from an API).
There is a lot of legacy code involved so rather than convert the column to datetime I was hoping I could convert the date string to a datetime object when performing the query.
I've tried writing a scope like:
scope :stats_for_reports, ->(start_date, end_date, user_ids) { select('user_id, sum(total_cost) as total_cost, sum(total_conversions) as total_conversions')
.where('date >= ? and date <= ?', start_date, end_date)
.where(user_id: user_ids)
.group(DateTime.parse(:date).month.to_s)}
but I receive a TypeError: can't convert Symbol into String error.
In the console I've been trying things like:
GoogleRecord.where(date: date_start..date_end).group{ |m| DateTime.parse(m.date).month }
or
GoogleRecord.where(date: date_start..date_end).group(:date).to_date
Am I on the right track with any of these?
Have you considered using ActiveRecord before_save, after_save, after_initialize? You may be able to create a DateWrapper (very similar to the EncryptionWrapper below) and convert the string to a date transparent to the rest of the code.
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html

How to save datetime fields with validations? [Ruby / Rails 4 / Postgres]

Ok, so I need help with datetime database fields.
Let's say my table is called "events" and has a datetime field named "starts_at". I have confirmed this in my schema.rb file (technically I am using Postgres):
create_table "events", force: true do |t|
t.string "name", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.datetime "starts_at
...
end
In my event.rb model, I have a validation to make sure the starts_at datetime is set:
attr_accessor :starts_at
validates :starts_at, presence: true
I'm testing this via the rails console and I can't get it to save any value, let alone create any validation errors. What gives? For example:
e = Event.new
e.name = 'Post 1'
e.starts_at = DateTime.now.utc
e.save
It appears to save, but there is no validation error, no mention of "starts_at" in the displayed query. Starts_at is nil in the database. My schema defines it as a "datetime" field but it ignores my DateTime variable. I thought, ok, maybe it is technically a string field and rails doesn't auto-convert for me:
e = Event.new
e.name = 'Post 2'
e.starts_at = DateTime.now.utc.to_s
e.save
Same thing. Thinking that the resulting format is being rejected in Postgres, I try this:
e = Event.new
e.name = 'Post 3'
e.starts_at = DateTime.now.utc.strftime('%Y-%m-%d %H:%M:%S')
e.save
I thought it might work with:
e.starts_at = Time.now.utc
Or, for Unix timestamp integer style:
e.starts_at = DateTime.now.utc.to_i
Nope, nothing works. What am I doing wrong?
Some questions:
How do I get a datetime field to accept my input -- any input! -- and actually save it to the database? Do I need to know which time format is ultimately being used by the database type (Postgres, MySQL, etc) and adjust accordingly? i.e. How does database agnosticism apply?
How can I update my validation to check if the database actually accepted my input? It's counterintuitive that I am validating the presence of my starts_at variable, but it will in fact allow it to be saved as nil.
Remove this line:
attr_accessor :starts_at
It masks the original setter that comes with Rails and sets a instance variable instead.
If there is a column (like the starts_at column here) in the database then there is no need to define a getter oder setter method on your own.
Probable issue is that Rails is protecting you from mass assignment of variables.
You need to remove attr_accessor on that columns that are saved as NULL in the database.
Explained really well here - What is attr_accessor in Ruby? and Difference between attr_accessor and attr_accessible

Sort by date span

Let's say we have the following model.
create_table :meetings do |t|
t.datetime :started_at
t.datetime: ended_at
end
class Meeting < ActiveRecord::base
end
How would I order a meetings_result, so that the longest meeting is the first meeting in the collection and the shortest meeting the last.
Something like
Meeting.order(longest(started_at..ended_at))
Obviously that doesn't work.
How would I achieve this, preferably without using raw SQL?
I don't think you can do it without using raw SQL.
Using Raw SQL:
Meeting.order('(ended_at - start_at) DESC')
(works with PostGreSQL)
No SQL? Two options come to mind. Create an array of hashes and sort it there, or add another column in the db and sort on that.
# How many records in the meetings table? This array of hashes could get huge.
meetings_array = []
Meeting.all.each do |meeting|
meetings_array << {id: meeting.id, started_at: meeting.started_at, ended_at: meeting.ended_at , duration: meeting.ended_at - meeting.started_at }
end
meetings_array.sort_by { |hsh| hsh[:duration] }
Or, create another column:
# Is it worth adding another column?
create_table :meetings do |t|
t.datetime :started_at
t.datetime :ended_at
t.datetime :duration
end
Update this column whenever you have both started_at and ended_at. Then you can:
Meeting.order("duration")

Looking for best way to retrieve business hours from database

I'm using Ruby on Rails and I'm storing business hours like this:
CREATE TABLE "business_hours" (
"id" integer NOT NULL PRIMARY KEY,
"business_id" integer NOT NULL FOREIGN KEY REFERENCES "businesses",
"day" integer NOT NULL,
"open_time" time,
"close_time" time)
(which came from the thread at:
Storing Business Hours in a Database )
Now I want to pull the hours out for each day of the week and display them, and I'm trying to find the best (or at least a good) way.
Should I just have a helper method that loops through getting the days (from 0..6) for a given business_id and assign it to a variable for the associated day? I feel like there must be a better way -- with an array, or something, but it's hurting my head thinking about it, because I also have a form of 'select's where any of the hours for a given business can be updated at once.
Thanks for any guidance!
Use the enum column plugin to declare the day field as a enum field.
class BusinessHours < ActiveRecord::Migration
def self.up
create_table :business_hours do |t|
t.integer :business_id, :null => false
t.enum :day, :limit =>[:sun, :mon, :tue, :wed, :thu, :fri, :sat], :nill => false
t.time :open_time, :null => false
t.time :close_time, :null => false
end
end
def self.down
drop_table :business_hours
end
end
Now when you do find on the BusinessHour model you will get the day as a string.
b = BusinessHour.find_by_business_id(2).first
p b.day.to_s.camelize #prints Sun/Mon/Tue etc.
You can use the enum_select and enum_radio form helpers to create list box/radio button group for the enum group:
Since the number of days in a week really is fixed, you can join the table 6 times (plus the original) and do a query for a single row. I'd probably just do a single query and loop through the rows though.
Have you considered serializing the business hours? Using serialization you are essentially storing objects in the database.
class BusinessHour < ActiveRecord::Base
serialize :hours
...
end
BusinessHour.create :business => #business, :hours =>
{:mon => [mon_start_time, mon_end_time], :wed => [wed_start_time, wed_end_time],
...}
Personally I would go with the bitwise approach described in linked question. All you really need to do to make it work is write new accessor methods.
It would be easier to find the business and use the associations to retrieve the business_hours rows.
Try this in your view
<% #business.business_hours.each do |hrs| %>
<%= hrs.day_name %>: Open-<%= hrs.open_time %> Close-<%= hrs.close_time %>
<%- end -%>
In your business_hour.rb model file, create a default scope to make sure the days are always listed in order. You can also create the day_name method to make it easier to display the day.
default_scope :order => 'day ASC'
def day_name
case self.day
when 0 then "Sun"
when 1 then "Mon"
...
end
end

Change starting id number

I have an 'Account' model in Rails with its corresponding 'accounts' table in the database. If I wipe the database and start over, the 'account_id' field will always start at 1 and count up from there. I would like to change the starting number, so that, when the very first account is created in a fresh database, the 'account_id' is, say, 1000. Is there a way to do that in Rails, or do I need specialized database-dependent SQL code?
For the sake of illustration, here is a simplified version of my 'accounts' table:
create_table "accounts", :force => true do |t|
t.string "email", :null => false
t.string "crypted_password", :null => false
t.string "name", :null => false
t.boolean "email_verified", :default => false
end
for PostgreSQL:
execute("ALTER SEQUENCE accounts_id_seq START with 1000 RESTART;")
see https://www.postgresql.org/docs/current/static/sql-altersequence.html
You'll need to do some specialized database-dependent SQL to get this functionality.
If you're using MySQL, you can add the following code to your migration after the create_table code:
execute("ALTER TABLE tbl AUTO_INCREMENT = 1000")
For sqlite
sequences are stored in the table sqlite_sequence (name,seq)
Check first if the sequence already exists?
select name,seq from sqlite_sequence where name = 'accounts'
if sequence.empty?
insert into sqlite_sequence(name,seq) values('accounts', 1000);
else
update sqlite_sequence set seq = 1000 where name = 'accounts';
A pure Ruby, database-independent approach could be:
class MyModel
before_create do
self.id = [1000, (self.class.maximum(:id) || 0) + 1].max if self.id.nil?
end
end
When you're creating lots of records at once, this may not perform so well though.
Another possible concept might be to simply use a start_at variable in your model file?
Such as define a base number such as start_at = 53131 and then...
Make an accessor method (could call it "key") which adds your start_at number to your database's real ID before returning it.
And you could make a attr writer method that subtracts the start_at before saving the key, that may not even be necessary depending on your implementation.
Example in pseudo-code so bear with me.
class FakeModel
attr_accessible :name
start_at = 53121
def self.find_by_key(key)
find_by_id(key-start_at))
end
def key
(self.id+start_at)
end
end
Not sure how practical this is or if it would even work 100% but at least you wouldn't have to modify the database to handle it.
in SQL Server:
execute('DBCC CHECKIDENT (accounts, reseed, 1000)')
In my case, the development environment and the production environment are using different type of database.
This code block will run the relevant execution accordin to DB type - just put it in the relevant migration:
puts 'Migration trys to set initial account ID to adapter:' + ActiveRecord::Base.connection.adapter_name
case ActiveRecord::Base.connection.adapter_name
when 'MySQL'
execute('ALTER TABLE accounts AUTO_INCREMENT = 1000')
when 'SQLServer'
execute('DBCC CHECKIDENT (accounts, reseed, 1000)')
when 'SQLite'
begin
execute('insert into sqlite_sequence(name,seq) values(\'accounts\', 1000);')
rescue
puts 'insert error... updating'
end
execute('update sqlite_sequence set seq = 1000 where name = \'accounts\';')
else
puts "cant recognize the database"
end

Resources