I am implementing a full text search API for my rails apps, and so far have been having great success with Thinking Sphinx.
I now want to implement a date range search, and keep getting the "bad value for range" error.
Here is a snippet of the controller code, and i'm a bit stuck on what to do next.
#search_options = { :page => params[:page], :per_page => params[:per_page]||50 }
unless params[:since].blank?
# make sure date is in specified format - YYYY-MM-DD
d = nil
begin
d = DateTime.strptime(params[:since], '%Y-%m-%d')
rescue
raise ArgumentError, "Value for since parameter is not a valid date - please use format YYYY-MM-DD"
end
#search_options.merge!(:with => {:post_date => d..Time.now.utc})
end
logger.info #search_options
#posts = Post.search(params[:q], #search_options)
When I have a look at the log, I am seeing this bit which seems to imply the date hasn't been converted into the same time format as the Time.now.utc.
withpost_date2010-05-25T00:00:00+00:00..Tue Jun 01 17:45:13 UTC 2010
Any ideas? Basically I am trying to have the API request pass in a "since" date to see all posts after a certain date. I am specifying that the date should be in the YYYY-MM-DD format.
Thanks for your help.
Chris
EDIT: I just changed the date parameters merge statement to this
#search_options.merge!(:with => {:post_date => d.to_date..DateTime.now})
and now I get this error
undefined method `to_i' for Tue, 25 May 2010:Date
So obviously there is something still not setup right...
lets say d = "2010-12-10"
:post_date => (d.to_time.to_i..Time.now.to_i) would have gotten you there. I just did this in my project and it works great
I finally solved this, but it takes a slightly different approach but it works fine.
I was trying to put the date-range search inside a sphinx_scope (in the model) or as a :condition or :with (in the controller). This did not work, so instead I had to implement it inside the define_index in the model.
So what I did was put a check in the define_index to see if a record fell within a date range, the date range being defined by some SQL code, as shown below. In this case, I wanted to see if "start_date" fell within a date between now and 30 days ago, and an "end_date" fell within today and 30 days from now.
If the dates fell within the ranges, the code below causes the :live to be 0 or 1, depending on whether it falls outside or inside the date ranges (respectively):
define index do
# fields:
...
# attributes:
has "CASE WHEN start_date > DATE_ADD(NOW(), INTERVAL -30 DAY) AND end_date < DATE_ADD(NOW(), INTERVAL 30 DAY) THEN 1 ELSE 0 END", :type => :integer, :as => :live
...
# delta:
...
end
Then in your controller, all you have to do is check if :live => 1 to obtain all records that have start_dates and end_dates within the date ranges.
I used a sphinx_scope like this:
sphinx_scope(:live) {
{ :with => { :live => 1 } }
}
and then in my controller:
#models = Model.live.search(...)
To make sure it works well, you of course need to implement frequent reindexing to make sure the index is up to date, i.e. the correct records are :live => 1 or 0!
Anyway, this is probably a bit late for you now, but I implemented it and it works like a charm!!!
Wouldn't it work if you replaced
d = DateTime.strptime(params[:since], '%Y-%m-%d')
by
Time.parse(params[:since]).strftime("%Y-%m-%d")
(It seems the first one doesn't return a date in the expected format)
Related
I need to test a specific array of dates to ensure that they are in the correct format, however I cannot use 'parse' because, if I do, the dates that are incorrect are sorted out. For instance, if I have an incorrect date with a month "13", it adds another year and sets the month to 1.
My code pulls in the dates from an SQL query:
table_birth_dates = self.class.connection.execute("SELECT birth_date FROM #{temp_table_name}").values.flatten
[
[0] "1980-30-54",
[1] "1980-30-54",
[2] "2020-09-10",
[3] "1890-10-30"
]
yr = 1900
year_test = table_birth_dates.select{|d| Date.parse(d).year < yr}
This now gives me an ArgumentError: invalid date.
I thought of using:
splitted_birth_date = table_birth_dates.first.split("-")
splitted_birth_date.first.to_i > 1900?
but if I try to loop through all of my dates, I'm not able to manipulate anything via splitting:
table_birth_dates.each do |birth_date|
birth_date.split("-")
end
What can I do with this?
I need to test a specific array of dates to ensure that they are in
the correct format...
If you get an error it means that the date is incorrect, you could rescue that error and do anything you want with that date to make it valid or whatever.
table_birth_dates.each do |birth_date|
begin
if Date.parse(d).year < yr
# do smth
end
rescue ArgumentError => e
# do smth with `d`
end
end
You could combine your select and split approaches together:
table_birth_dates.select { |d| d.split('-').first.to_i < 1900 }
#=> ["1890-10-30"]
might be a stupid question...
I am a new to ruby and recently I am writing a rake task to merge multiple tables into a general one. One thing I need to do is to fetch the date from the database and then convert the date into two integers as year and month and then save them into two separate columns.
I finished this task file last week but unfortunately that file is removed by accident, so I have to write the code again. I didn't remember how I manipulated the date in the original file, I think that the way I took in the original file is way more straightforward than the current code. The current code is as follows.
fetched_time=DateTime.strptime(pr.fetched_time,"%Y-%m-%d")
dr.year = fetched_time.strftime('%Y').to_i
dr.month = fetched_time.strftime('%m').to_i
I have tried many key words to search, but none of the results is helpful. Is the following code the best way to convert the date string to integer?
Thank you very much.
Yes possible, by using Date#year:
require 'date'
d = Date.parse("20-08-2013")
d.year # => 2013
now = Time.now.to_s
# => "2013-09-10 11:09:14 -0500"
fetched_time=DateTime.strptime(now, "%Y-%m-%d").to_s
# => "2013-09-10T00:00:00+00:00"
year = Date.parse(fetched_time).year
# => 2013
month = Date.parse(fetched_time).month
# => 9
year.class
# => Fixnum
month.class
# => Fixnum
Or
fetched_date=Date.strptime(now, "%Y-%m-%d").to_s
# => "2013-09-10"
date = Date.parse(fetched_date)
# => #<Date: 2013-09-10 ((2456546j,0s,0n),+0s,2299161j)>
Wouldn't you rather use a Date object than a String anyway? What do timestamps consist of? I'm new to Rails and ActiveRecord.
What are you setting your ActiveRecord::Base.default_timezone = # to be?
In case you want to know what those extra numbers are in a Date object
try pluging them in to
Date.jd(2299161)
# => #<Date: 1582-10-15 ((2299161j,0s,0n),+0s,2299161j)>
Date.jd(2456546)
# => #<Date: 2013-09-10 ((2456546j,0s,0n),+0s,2299161j)>
They are Julian Day Numbers. That last one is for calendar reform for Italy and some catholic countries.
Date::ITALY
# => 2299161
I'm still very new to Rails but moving along fairly smoothly I would say. So for practice I'm working on what's supposed to be a simple application where a user can input their weight and that info, over a 30 day period, is displayed to them via a Highcharts line graph using the Lazy Highcharts gem. I followed Ryan Bates' Railscast #223 to get started.
My Issue:
Ok, so the inputs are showing up except that on the days that a user doesn't input a value it gets displayed on the chart as '0' (the line drops to bottom of the graph), instead of connecting to the next point on any given day. Not sure if all that makes sense so here's a screenshot:
I found this solution:
Highcharts - Rails Array Includes Empty Date Entries
However, when I implement the first option found on that page (convert 0 to null):
(30.days.ago.to_date..Date.today).map { |date| wt = Weight.pounds_on(date).to_f; wt.zero? ? "null" : wt }
the points show up but the line does not, nor do the tooltips...this leads me to think that something is breaking the js. Nothing is apparently wrong in the console though..? From here I thought it might be a matter of using Highchart's 'connectNulls' option but that didn't work either. When implementing the second option (reject method):
(30.days.ago.to_date..Date.today).map { |date| Weight.pounds_on(date).to_f}.reject(&:zero?)
it completely removes all dates that are null from the chart, which messes up the structure completely because the values are supposed to be displayed based on their created_at date.
So back to square one, this is what I'm left with (chart plotting zeros for days without inputs).
Model:
class Weight < ActiveRecord::Base
attr_accessible :notes, :weight_input
validates :weight_input, presence: true
validates :user_id, presence: true
belongs_to :user
def self.pounds_on(date)
where("date(created_at) = ?", date).pluck(:weight_input).last
end
end
Controller:
def index
#weights = current_user.weights.all
#startdate = 30.days.ago.to_date
#pounds = (30.days.ago.to_date..Date.today).map { |date| Weight.pounds_on(date).to_f }
#h = LazyHighCharts::HighChart.new('graph') do |f|
f.options[:title][:text] = " "
f.options[:chart][:defaultSeriesType] = "area"
f.options[:chart][:inverted] = false
f.options[:chart][:zoomType] = 'x'
f.options[:legend][:layout] = "horizontal"
f.options[:legend][:borderWidth] = "0"
f.series(:pointInterval => 1.day, :pointStart => #startdate, :name => 'Weight (lbs)', :color => "#2cc9c5", :data => #pounds )
f.options[:xAxis] = {:minTickInterval => 1, :type => "datetime", :dateTimeLabelFormats => { day: "%b %e"}, :title => { :text => nil }, :labels => { :enabled => true } }
end
respond_to do |format|
format.html # index.html.erb
format.json { render json: #weights }
end
end
Does anyone have a solution for this? I guess I could be going about this all wrong so any help is much appreciated.
This is a HighCharts specfic. You need to pass in the timestamp into your data array vs. defining it for the dataset.
For each data point, I set [time,value] as a tuple.
[ "Date.UTC(#{date.year}, #{date.month}, #{date.day})" , Weight.pounds_on(date).to_f ]
You will still need to remove the zero's in w/e fashion you like, but the data will stay with the proper day value.
I do think you need to remove :pointInterval => 1.day
General tips
You should also, look at optimizing you query for pound_on, as you are doing a DB call per each point on the chart. Do a Weight.where(:created_at => date_start..date_end ).group("Date(created_at)").sum(:weight_input) which will give you an array of the created_at dates with the sum for each day.
ADDITIONS
Improved SQL Query
This leans on sql to do what it does best. First, use where to par down the query to the records you want (in this case past 30 days). Select the fields you need (created_at and weight_input). Then start an inner join that runs a sub_query to group the records by day, selecting the max value of created_at. When they match, it kicks back the greatest (aka last entered) weight input for that given day.
#last_weight_per_day = Weight.where(:created_at => 30.days.ago.beginning_of_day..Time.now.end_of_day)
select("weights.created_at , weights.weight_input").
joins("
inner join (
SELECT weights.weight_input, max(weights.created_at) as max_date
FROM weights
GROUP BY weights.weight_input , date(weights.created_at)
) weights_dates on weights.created_at = weights_dates.max_date
")
With this you should be able #last_weight_per_day like so. This should not have 0 / nil values assuming you have validated them in your DB. And should be pretty quick at it too.
#pounds = #last_weight_per_day.map{|date| date.weight_input.to_f}
I have an app where there is always a current contest (defined by start_date and end_date datetime). I have the following code in the application_controller.rb as a before_filter.
def load_contest
#contest_last = Contest.last
#contest_last.present? ? #contest_leftover = (#contest_last.end_date.utc - Time.now.utc).to_i : #contest_leftover = 0
if #contest_last.nil?
Contest.create(:start_date => Time.now.utc, :end_date => Time.now.utc + 10.minutes)
elsif #contest_leftover < 0
#winner = Organization.order('votes_count DESC').first
#contest_last.update_attributes!(:organization_id => #winner.id, :winner_votes => #winner.votes_count) if #winner.present?
Organization.update_all(:votes_count => 0)
Contest.create(:start_date => #contest_last.end_date.utc, :end_date => Time.now.utc + 10.minutes)
end
end
My questions:
1) I would like to change the :end_date to something that signifies next Sunday at a certain time (eg. next Sunday at 8pm). Similarly, I could then set the :start_date to to the previous Sunday at a certain time. I saw that there is a sunday() class (http://api.rubyonrails.org/classes/Time.html#method-i-sunday), but not sure how to specify a certain time on that day.
2) For this situation of always wanting the current contest, is there a better way of loading it in the app? Would caching it be better and then reloading if a new one is created? Not sure how this would be done, but seems to be more efficient.
Thanks!
Start with this snippet. In rails, given a date/time object d, the expression
d - wday.day
Gives the last Sunday not after that date. i.e. if the day itself is a Sunday the result will be d.
Then to get the next sunday:
sunday1 = d - wday.day
sunday2 = sunday1 + 7.day
And to set the times for both:
sunday1 = (d - wday.day).change(:hour => 8,:min => 30)
sunday2 = (sunday1 + 7.day).change(:hour => 20)
No need for any external modules. Rails (but not core ruby!) has all the date/time manipulation functionality you need.
Note how the change function works: if you pass it :hour only, it will reset the minute and second to 0. If you pass it :hour and :minute, it will reset only the second. See the docs for details.
One last thing: be mindful of what time zone you are using.
I'm implementing the Timezone support in Rails 2.1+, and I've run into an apparent bug in the way the data is pulled from the db. Let me try to set it up.
The "deals" model contains an "offer_date" datetime field. Let's say I have two records with these offer_dates:
Deal 1: 2009-12-29 23:59:59 -0500
Deal 2: 2009-12-28 23:59:59 -0500
This is correct: dates should mark the last second of its given day.
Now, when it comes time to find the deal for today, I have the following AR query:
#deal = Deal.first(:conditions=>['offer_date > ?', Time.now.beginning_of_day], :order=>"offer_date ASC")
In this case, although it's the 29th today, it returns the record ostensibly for the 28th. Why? Here's what happens in the console:
>> #deal = Deal.first(:conditions=>['offer_date > ?', Time.now.beginning_of_day], :order=>"offer_date ASC")
=> #<Deal id: 2, <blah blah blah...>, offer_date: "2009-12-29 04:59:00">
It's shifting the time forward by 5 hours, putting yesterday's day into the next. But when I do this:
>> #deal.offer_date
=> Mon, 28 Dec 2009 23:59:00 EST -05:00
I get the right time. What the heck??? Suffice to say, I need that date to work properly, but I can't figure out where my issue is. Any assistance you can provide would be greatly appreciated!
See my prior rsponse on Time vs Time.zone for AR date queries.
Instead of using Time.now, try using Time.zone.now
Of course you have to set this and everything. This tutorial seems helpful.
the Time class doesn't care about the time zone in the implementation of #to_s, therefore you have to use
Time.zone.now # or any other TimeWithZone object
in your finders/named_scopes/find calls. Or you could read through http://marklunds.com/articles/one/402 and then put
module ActiveRecord
module ConnectionAdapters # :nodoc:
module Quoting
# Convert dates and times to UTC so that the following two will be equivalent:
# Event.all(:conditions => ["start_time > ?", Time.zone.now])
# Event.all(:conditions => ["start_time > ?", Time.now])
def quoted_date(value)
value.respond_to?(:utc) ? value.utc.to_s(:db) : value.to_s(:db)
end
end
end
end
into your environment.rb or into an initializer.