How do I search by date in PGSQL? - ruby-on-rails-3

I am getting this error:
ActionView::Template::Error (PGError: ERROR: operator does not exist: timestamp without time zone ~~ unknown LINE 1: ... "articles" WHERE ("articles"."created_at" LIKE '2010...
I have an archive controller where I can dynamically display articles by genre and year, month and day, with whichever of those fields are available in the url. In mysqlite, I had this index action:
def index
filter_title
#articles = Article.where(:created_at.matches % date_builder, :genre.matches % genre_builder).order("created_at DESC")
respond_to do |format|
format.json { render :json => #articles }
format.xml { render :xml => #articles }
format.html
end
end
And this date_builder function
def date_builder
#date = ""
#date += params[:year] if !(params[:year].nil?)
#date += "-" + params[:month] if !(params[:month].nil?)
#date += "-" + params[:day] if !(params[:day].nil?)
#date += "%"
#date
end
that would use metawhere to find dates that matched the part of the string that I supplied. This worked perfectly in Sqlite. I have migrated my application onto heroku and PGSQL, and it doesn't allow me to do this. What is the solution? Thank you!

In postgres, a timestamp is not stored as a string like in sqllite:
select 'A' where localtimestamp like '2011-04-04%';
ERROR: operator does not exist: timestamp without time zone ~~ unknown
how about using >= instead?
select 'A' where localtimestamp >= '2011-04-04';
?column?
----------
A
(1 row)

While I liked #JackPDouglas answer, I wanted to try and keep sql code out of my project as much as possible, so i ended up doing this:
...
date_builder
if !(params[:year].nil?)
#articles = Article.where(
{:created_at.gt => #datelower} &
{:created_at.lt => (#datehigher - 1.second)} &
:genre.matches % genre_builder
).order("created_at DESC")
else
...
and this method for the date:
def date_builder
if !(params[:day].nil?)
#datelower = Time.utc(params[:year], params[:month], params[:day])
#datehigher = #datelower + 1.day
elsif !(params[:month].nil?)
#datelower = Time.utc(params[:year], params[:month], 01)
#datehigher = #datelower + 1.month
elsif !(params[:year].nil?)
#datelower = Time.utc(params[:year], 01, 01)
#datehigher = #datelower + 1.year
end
end

Related

Check for Dates Overlap in Ruby on Rails

I have a model that has two columns (started_at and ended_at). I want to add a custom validator that ensures that no other record exists with dates that overlap with the record I'm validating. So far I have:
# app/models/event.rb
class Event < ActiveRecord::Base
validates_with EventValidator
end
# app/validators/event_validator.rb
class EventValidator < ActiveModel::Validator
attr_reader :record
def validate(record)
#record = record
validate_dates
end
private
def validate_dates
started_at = record.started_at
ended_at = record.ended_at
arel_table = record.class.arel_table
# This is where I'm not quite sure what type of query I need to perform...
constraints = arel_table[:started_at].gteq(ended_at)
.and(arel_table[:ended_at].lteq(started_at))
if record.persisted?
constraints = constraints
.and(arel_table[:id].not_eq(record.id))
end
if record.class.where(constraints).exists?
record.error[:base] << "Overlaps with another event"
end
end
end
I don't know exactly what query I need to ensurethat there is no overlapping. Any help is greatly appreciated
I don't use Arel but I think the query should be:
constraints = arel_table[:started_at].lteq(ended_at)
.and(arel_table[:ended_at].gteq(started_at))
Two periods overlap when
period1.start < period2.end
period1.end > period2.start
Have a look at Validates Overlap gem
You can either use it instead your code or take condition code from it
# Return the condition string depend on exclude_edges option.
def condition_string(starts_at_attr, ends_at_attr)
except_option = Array(options[:exclude_edges]).map(&:to_s)
starts_at_sign = except_option.include?(starts_at_attr.to_s.split(".").last) ? "<" : "<="
ends_at_sign = except_option.include?(ends_at_attr.to_s.split(".").last) ? ">" : ">="
query = []
query << "(#{ends_at_attr} IS NULL OR #{ends_at_attr} #{ends_at_sign} :starts_at_value)"
query << "(#{starts_at_attr} IS NULL OR #{starts_at_attr} #{starts_at_sign} :ends_at_value)"
query.join(" AND ")
end
I would construct a query that looks something like this:
Event.exists?( 'started_at < ? AND ended_at > ?', ended_at, started_at )
If this returns true, an overlapping record exists.

Destroy Record if Dependant record is not found

I am tring to write a script that checks to see if the corresponding record exist. if not i want to delete the record of reference.
I am new to ruby.
if Hotload.find(lead.id).exists?
leadid = Lead.find(lead.id)
leadid.destroy
end
error message:
ActiveRecord::RecordNotFound (Couldn't find Hotload with id=148):
app/controllers/users_controller.rb:64:in `block in leads'
app/controllers/users_controller.rb:47:in `leads'
UPDATE:
Here is complete Function:
def leads
load_leads = []
truck_leads = []
hotload_leads = []
#I just added another route for users/:id/leads and kept users/leads because I wasn't sure if it was used anywhere else.
#leads = Lead.where(:user_id => params[:id] || current_user.id)
#leads.each do |lead|
if lead.post_type == 'load'
load_leads.push lead.type_id
elsif lead.post_type == 'truck'
truck_leads.push lead.type_id
elsif lead.post_type == 'hotload'
hotload_leads.push lead.type_id
unless Hotload.where(:id => lead.id).exists?
lead_id = Lead.find(lead.id)
lead_id.destroy
end
end
end
#saved_loads = Load.find(load_leads, :order => "#{sort_load_column + " " + sort_direction}").paginate(:page => params[:load_leads_page], :per_page => 10)
#saved_trucks = Truck.find(truck_leads,:order => "#{sort_truck_column + " " + sort_direction}").paginate(:page => params[:truck_leads_page], :per_page => 10)
#saved_hotloads = Hotload.find(hotload_leads,:order => "#{sort_load_column + " " + sort_direction}").paginate(:page => params[:hotload_leads_page], :per_page => 10)
end
Error Now
ActiveRecord::RecordNotFound (Couldn't find all Hotloads with IDs (1590, 1479, 1478, 1468, 1476) (found 4 results, but was looking for 5)):
app/controllers/users_controller.rb:62:in `leads'
You should change the code to
if Hotload.find_by(id: lead.id).blank?
leadid = Lead.find(lead.id)
leadid.destroy
end
If you look at the documentation for find, you'll see that it will raise an error if one or more ids cannot be found.
Try using where instead.
if Hotload.where(:id => lead.id).exists?
lead_id = Lead.find(lead.id)
lead_id.destroy
end

Mongoid dynamic query

This must be an easy one, but I'm stuck...
So I'm using Rails#3 with Mongoid and want to dynamically build query that would depend upon passed parameters and then execute find().
Something like
def select_posts
query = :all # pseudo-code here
if (params.has_key?(:author))
query += where(:author => params[:author]) # this is pseudo-code again
end
if (params.has_key?(:post_date))
query += where(:create_date => params[:post_date]) # stay with me
end
#post_bodies = []
Post.find(query).each do |post| # last one
#post_bodies << post.body
end
respond_to do |format|
format.html
format.json { render :json => #post_bodies }
end
end
You have a few different options to go with here - depending on how complex your actual application is going to get. Using your example directly - you could end up with something like:
query = Post.all
query = query.where(:author => params[:author]) if params.has_key?(:author)
query = query.where(:create_date => params[:post_date]) if params.has_key?(:post_date)
#post_bodies = query.map{|post| post.body}
Which works because queries (Criteria) in Mongoid are chainable.
Alternatively, if you're going to have lots more fields that you wish to leverage, you could do the following:
query = Post.all
fields = {:author => :author, :post_date => :create_date}
fields.each do |params_field, model_field|
query = query.where(model_field => params[params_field]) if params.has_key?(params_field)
end
#post_bodies = query.map{|post| post.body}
And finally, you can take it one level further and properly nest your form parameters, and name the parameters so that they match with your model, so that your params object looks something like this:
params[:post] = {:author => "John Smith", :create_date => "1/1/1970", :another_field => "Lorem ipsum"}
Then you could just do:
#post_bodies = Post.where(params[:post]).map{|post| post.body}
Of course, with that final example, you'd want to sanitize the input fields - to prevent malicious users from tampering with the behaviour.

How to convert postgresql column into a float for a scoped search in rails

I am trying to search my postgresql db in rails. I followed the Railscasts #111 Advanced Search tutorial and it is working for the name and category of my items column in plain text. However, I want to set a min/max price on my search as well which is where I come into my problem. In my db my price is stored as a string in the format "AU $49.95". Can I convert this into a float on the fly in my scoped search? If so how? If not, what should I do?
Here is the code:
search.rb
class Search < ActiveRecord::Base
attr_accessible :keywords, :catagory, :minimum_price, :maximum_price
def items
#items ||= find_items
end
private
def find_items
scope = Item.scoped({})
scope = scope.scoped :conditions => ["to_tsvector('english', items.name) ## plainto_tsquery(?)", "%#{keywords}%"] unless keywords.blank?
scope = scope.scoped :conditions => ["items.price >= ?", "AU \$#{minimum_price.to_s}"] unless minimum_price.blank?
# scope = scope.scoped :conditions => ["items.price <= ?", "AU \$#{maximum_price.to_s}"] unless maximum_price.blank?
scope = scope.scoped :conditions => ["to_tsvector('english', items.catagory) ## ?", catagory] unless catagory.blank?
scope
end
end
searches_controller.rb
class SearchesController < ApplicationController
def new
#search = Search.new
end
def create
#search = Search.new(params[:search])
if #search.save
redirect_to #search, :notice => "Successfully created search."
else
render :action => 'new'
end
end
def show
#search = Search.find(params[:id])
end
end
Thanks for reading this far!
Use the data type numeric or money for exact numerical calculation without rounding errors - and sorting as a number (not as text).
Converting string literal to numeric should not be a performance problem at all.

ActiveRecord DateTime comparison

I understand that Rails store the Time in UTC.
I have out_date and in_date instances of DateTime
I have this query:
reservations = Reservation.where("bookable_id = :bookable_id AND bookable_type = :bookable_type AND status <> :status AND ((:out_date >= check_out_date AND :out_date <= check_in_date) OR (:in_date <= check_in_date AND :in_date >= check_out_date) OR (:out_date <= check_in_date AND :in_date >= check_in_date))", :bookable_id => params[:bookable_id], :bookable_type => params[:bookable_type], :status => Reservation::STATUS_CHECKED_IN, :out_date => out_date, :in_date => in_date)
I always get a null set even though I should get a return tuple.
I have tried these varients:
out_date.utc
out_date.utc.to_s(:db)
Nothing seems to be working. How to construct this query?
Controller Code:
in_date = DateTime.strptime params[:checkin], "%Y-%m-%d %H:%M"
out_date = DateTime.strptime params[:checkout], "%Y-%m-%d %H:%M"
#check collision with non-recurring reservations
reservations = Reservation.where("bookable_id = :bookable_id AND bookable_type = :bookable_type AND status <> :status AND ((:out_date >= check_out_date AND :out_date <= check_in_date) OR (:in_date <= check_in_date AND :in_date >= check_out_date) OR (:out_date <= check_in_date AND :in_date >= check_in_date))", :bookable_id => params[:bookable_id], :bookable_type => params[:bookable_type], :status => Reservation::STATUS_CHECKED_IN, :out_date => out_date, :in_date => in_date)
logger.info(reservations)
if !reservations.empty?
#error_message = "This Asset has already been reserved for those dates"
return
end
Also on the Rails Console, the simpler query fails:
1.9.3p0 :007 > Reservation.find_by_id 31
Reservation Load (0.7ms) SELECT `reservations`.* FROM `reservations` WHERE `reservations`.`id` = 31 LIMIT 1
=> #<Reservation id: 31, bookable_id: 11, bookable_type: "Asset", user_id: 1, check_out_date: "2012-07-07 08:00:00", check_in_date: "2012-07-07 10:00:00", notes: "rec", status: "Ready", is_recurring: true, repeat_count: 5>
1.9.3p0 :009 > Reservation.where " ? >= check_out_date AND ? <= check_in_date",DateTime.new(2012,7,7,9),DateTime.new(2012,7,7,9)
Reservation Load (0.5ms) SELECT `reservations`.* FROM `reservations` WHERE ( '2012-07-07 09:00:00' >= check_out_date AND '2012-07-07 09:00:00' <= check_in_date)
=> []
I read the updated code and, apparently, it's correct. Make sure the out_date and in_date are DateTime objects and not null objects.
You don't need to format the query, it should be handled for you.
If you want to debug the query SQL, you can use the .to_sql
reservations = Reservation.where("...") # your query
puts reservation.to_sql
# => "SELECT ..."
Print the query and check if the value is correctly formatted.
The mistake was because of the the database time being stored in UTC and the local time being different and not satisfying the query condition.