Filtering Sphinx search results by date range - ruby-on-rails

I have Widget.title, Widget.publish_ at, and Widget.unpublish_ at. It's a rails app with thinking_sphinx running, indexing once a night. I want to find all Widgets that have 'foo' in the title, and are published (publish _at < Time.now, unpublish _at > Time.now).
To get pagination to work properly, I really want to do this in a sphinx query. I have
'has :publish_at, :unpublish_at' to get the attributes, but what's the syntax for 'Widget.search("foo #publish_ at > #{Time.now}",:match _mode=>:extended'? Is this even possible?

Yep, easily possible, just make sure you're covering the times in your indexes:
class Widget < ActiveRecord::Base
define_index do
indexes title
has publish_at
has unpublish_at
...
end
To pull it based purely off the dates, a small amount of trickery is required due to sphinx requiring a bounded range (x..y as opposed to x>=y). The use of min/max value is very inelegant, but I'm not aware of a good way around it at the moment.
min_time = Time.now.advance(:years => -10)
max_time = Time.now.advance(:years => 10)
title = "foo"
Widget.search title, :with => {:publish_at => min_time..Time.now, :unpublish_at => Time.now..max_time}

I haven't used sphinx with rails yet.
But this is possible by the Sphinx API.
What you need to do is to set a datetime attribute at your sphinx.conf.
And don't forget to use UNIX_TIMESTAMP(publish_at), UNIX_TIMESTAMP(unpublish_at) at your index select.

Related

Datamapper: Sorting results through association

I'm working on a Rails 3.2 app that uses Datamapper as its ORM. I'm looking for a way to sort a result set by an attribute of the associated model. Specifically I have the following models:
class Vehicle
include DataMapper::Resource
belongs_to :user
end
class User
include DataMapper::Resource
has n, :vehicles
end
Now I want to be able to query the vehicles and sort them by the name of the driver. I tried the following but neither seems to work with Datamapper:
> Vehicle.all( :order => 'users.name' )
ArgumentError: +options[:order]+ entry "users.name" does not map to a property in Vehicle
> Vehicle.all( :order => { :users => 'name' } )
ArgumentError: +options[:order]+ entry [:users, "name"] of an unsupported object Array
Right now I'm using Ruby to sort the result set post-query but obviously that's not helping performance any, also it stops me from further chaining on other scopes.
I spent some more time digging around and finally turned up an old blog which has a solution to this problem. It involves manually building the ordering query in DataMapper.
From: http://rhnh.net/2010/12/01/ordering-by-a-field-in-a-join-model-with-datamapper
def self.ordered_by_vehicle_name direction = :asc
order = DataMapper::Query::Direction.new(vehicle.name, direction)
query = all.query
query.instance_variable_set("#order", [order])
query.instance_variable_set("#links", [relationships['vehicle'].inverse])
all(query)
end
This will let you order by association and still chain on other scopes, e.g.:
User.ordered_by_vehicle_name(:desc).all( :name => 'foo' )
It's a bit hacky but it does what I wanted it to do at least ;)
Note: I'm not familiar with DataMapper and my answer might not be within the standards and recommendations of using DataMapper, but it should hopefully give you the result you're looking for.
I've been looking through various Google searches and the DataMapper documentation and I haven't found a way to "order by assocation attribute". The only solution I have thought of is "raw" SQL.
The query would look like this.
SELECT vehicles.* FROM vehicles
LEFT JOIN users ON vehicles.user_id = users.id
ORDER BY users.name
Unfortunately, from my understanding, when you directly query the database you won't get the Vehicle object, but the data from the database.
From the documentation: http://datamapper.org/docs/find.html. It's near the bottom titled "Talking directly to your data-store"
Note that this will not return Zoo objects, rather the raw data straight from the database
Vehicle.joins(:user).order('users.name').all
or in Rails 2.3,
Vehicle.all(:joins => "inner join users on vehicles.user_id = user.id", :order => 'users.name')

Query based on calculated fields with Mongoid

I want to implement a scope overdue in a model Invoice to return all invoices that exceeded the date, until they had to be paid. I have the fields invoice_date, :type => Date and days_for_payment, :type => Integer.
In my previous version, which was built on ActiveRecord, I could use the query
Invoice.where("invoice_date + days_for_payment < ?", Date.today)
This query made the calculation on the DB side.
Is there a way to get the same thing done with Mongoid? Or does anyone know a good workaround (proc, lambda, etc.)?
I use mongoid '2.4.12'
Found the answer myself. With the prefix this.* I can reference to the fields. And I can use JavaScript functions. MongoDB gets cooler and cooler!
So here is my solution:
class Invoice
include Mongoid::Document
field :invoice_date, :type => Date
field :days_for_payment, :type => Integer
...
scope :overdue, where("(Math.round(this.invoice_date.getTime() / 1000) + (this.days_for_payment * 24 * 3600)) < #{Time.now.to_i}")
...
end
Timestamp creation in js works different. So I had to get rid of the last three numbers and round them.
If anybody knows a more elegant way, please let me know.
My only problem left is, that I can't store a Date object to MongoDB. It always tells me I have to use Time. I think I better upgrade mongoid to 3.0.1.
I am not sure about mongoid, if you are querying mongodb directly you can use the $where operator. It is not recommended as it doesn't use indexes. If you have another condition that filters the records to a small set then you can use the $where to further filter it.

How do I get the search to use the attr_accessor?

Ok so i have a date field that i need to search on, but i need to search on it by day like in a mysql query
search_conditions << ["DAY(open_date) != ?", event.thursday.day] if options[:thur].blank?
and i need to do this condition with Thinking Sphinx so i tried this
attr_accessor :event_day
def event_day
self.start_date.day
end
#thinking sphinx configurations for the event search
define_index do
indexes event_day
...
...
and in the search i tried this
search_string = "#event_day -#{event.thursday.day}" unless options[:thur].blank?
but i keep getting this error
index event_core: query error: no field 'event_day' found in schema
Any way to make this work
You can't use a ruby attribute in an SQL query. Rails isn't that clever.
You need to write SQL that replicates that function, or filter the results of a query through it, e.g.
#my_query.where(:a => "b").select { |rec| rec.some_method == "some value" }
As Michael's pointed out, Ruby attributes aren't accessible by Sphinx - it talks directly to your database.
So, either you can create a column that holds the event day value, and reference that via Sphinx, or you can create a field that uses a SQL function (which could vary, depending on MySQL or PostgreSQL) that extracts the day from the start_date column - not particularly complex. It'd probably end up looking like this:
indexes "GET_DAY_FROM_DATE(start_date)", :as => :event_day

thinking_sphinx results order

I have no experience with thinking_sphinx (I take pride in the fact I even got it working).
I would like to sort my results based on relevance to the search and how recent they are. Maybe, 5X for relevance, 1X for time. (I'd have to play with that to get it right). Obviously if there's no search criteria, I'd like it to sort just by time.
I know I need to add the created_at column to the search model, but not as indexes (what term do I use?)
Report controller:
def index
#reports = Report.search params[:search]
# unknown sorting code here
end
Report model:
define_index do
indexes apparatus
indexes body
indexes comments.body, as => :comment_body
????? created_at
end
You would just do:
define_index do
indexes apparatus
indexes body
indexes comments.body, as => :comment_body
has created_at
end
By using has you just denote whatever fields it needs but isn't indexing on
For search sorting, you need to read the Sphinx docs for how you think you'd want them weighted and sorted:
http://freelancing-god.github.com/ts/en/searching.html#sorting
http://freelancing-god.github.com/ts/en/searching.html#fieldweights
By default, Sphinx sorts based on how relevant it thinks the results are to the given inputs.

How do I create a 'Most Recently Popular' bar for content in Ruby on Rails?

I'm a noob so please forgive if this is an easy question but I'm trying to create a 'Most Recently Popular' output for specific content on a rails project.
Right now the object I am pulling from has an attribute revision.background_title. I want to calculate popularity by finding the number of specific background_title's added over the past seven days then put them in order. For example if there are 4 background_title's named 'awesomecontent' then that would be above one that has 1 background_title named 'less awesome content'
This pulls all of them:
#revisions = Revision.find(:all, :order => "created_at desc")
Thanks.
You can use the basic ActiveRecord find method to do this. The code would end up looking something like this:
#revisions = Revision.all(
:select => "background_title, count(*) count", # Return title and count
:group => 'background_title', # Group by the title
:order => '2 desc' # Order by the count descending
)
To see the output, you could then do something like this:
#revisions.each do |revision|
puts "Revision #{revision.background_title} appears #{revision.count} times"
end
giving
Revision z appears 10 times
Revision a appears 3 times
Revision b appears 2 times
Another option would be to take a look at ActiveRecord::Calculations:
http://api.rubyonrails.org/classes/ActiveRecord/Calculations/ClassMethods.html
Calculations supports a count method that also supports the group option. However, going this route will give you back a hash containing the background_title as the key and the count as the value. Personally, find the first method more useful.

Resources