Rails 4.1 - Thinking-Sphinx Search with many-to-many through relationships - ruby-on-rails

I'm making an app with three model objects, Team, Users and Member, Member is a join table between Team and Users. So each Team can have many members, and each User can be a member of multiple teams.
The relationship looks like this:
# Team.rb
has_many :members
has_many :users, through: :members
# User.rb
has_many :members
has_many :teams, through: :members
What I want to do is to search for members that are in a specific team. Currently I get no results.
My indexes looks like this:
# user_index
ThinkingSphinx::Index.define :user, :with => :real_time do
indexes name
indexes email
indexes about
has team_id, type: :integer
has created_at, type: :timestamp
has updated_at, type: :timestamp
indexes members.team.name, :as => :teams
end
# team_index
ThinkingSphinx::Index.define :team, :with => :real_time do
indexes name
has created_at, type: :timestamp
has updated_at, type: :timestamp
indexes members.user.name, :as => :members
end
# member_index.rb
ThinkingSphinx::Index.define :member, :with => :real_time do
has user_id, type: :integer
has team_id, type: :integer
has created_at, type: :timestamp
has updated_at, type: :timestamp
end
My members_controller index action (where I perform the search) - looks like this:
def index
#team = Team.find_by_id(params[:team_id])
#users = #team.users.search(params[:search], :page => params[:page], :per_page => 10)
end
I have checked that the team actually has users, but #users always returns 0. Any ideas on how I should do to make it work as I want?
Update
I use Rails: 4.1.4
And thinking-sphink: 3.1.1
My Sphinx query looks like this:
Sphinx Query (0.7ms) SELECT * FROM `member_core` WHERE MATCH('Anders') AND `team_id` = 2 AND `sphinx_deleted` = 0 LIMIT 0, 10
Sphinx Found 0 results
With slightly updated controller code:
#members = #team.members.search(params[:search], :page => params[:page], :per_page => 10)

So, the original cause of this issue was that there was a search query being provided, but no fields to match against. Adding fields fixed that, which is great.
The second issue, noted in the comments above, is that when you search for an email address, a query error is raised. This is because the # character signifies a field name in the query (to limit searches to a specific field - for example "#name Anders" to search for Anders within a field called name).
You have two ways around this... either you escape the query by wrapping it in ThinkingSphinx::Query.escape(params[:query]), or if you're searching for a specific email address, then I would suggest using ActiveRecord instead (given you're almost certainly going to have a unique constraint on email addresses in your database, hence there should only be zero or one matching records). The latter approach also means there's no need to have email as an indexed field, which is a bit more secure if you're letting anyone define your search queries via parameters. Letting someone search for 'gmail' and they get back all your users with gmail addresses is probably not a wise idea.

Related

ThinkingSphinx - Search through has_many association

I have a relationship between two models, Idea and Iteration. Each idea can have many iterations. The models looks like this;
# idea.rb
has_many :iterations, dependent: :destroy
# I also use with: :real_time
after_save ThinkingSphinx::RealTime.callback_for(:idea)
# iteration.rb
belongs_to :idea
after_save ThinkingSphinx::RealTime.callback_for(:idea, [:idea])
My indexes for Idea looks like this:
ThinkingSphinx::Index.define :idea, :with => :real_time do
[...]
indexes iterations.title, as: :iteration_titles
indexes iterations.description, as: :iteration_descriptions
has iterations.id, :as => :iteration_ids, type: :integer
[...]
end
And I search like this:
#ideas = #user.ideas.search ThinkingSphinx::Query.escape(params[:search]),
:with => {:team_id => #teams.collect(&:id)},
:page => params[:page],
:per_page => 10,
:order => 'created_at DESC'
Currently, searching for either Iteration title or description returns 0 hits. And I have performed:
rake ts:rebuild
rake ts:regenerate
rake ts:index
Have I missed something?
One of the differences between real-time indices and SQL-backed indices is that with SQL-backed indices, you refer to associations and columns in your index definition, but with real-time indices, you refer to methods.
And while iterations is an instance method within an idea, title, description and id are not methods on the object returned by iterations.
The easiest way to work through this has two parts. Firstly, add instance methods to Idea that return the data you want for the iteration-related fields and attributes:
def iteration_titles
iterations.collect(&:title).join(' ')
end
def iteration_descriptions
iterations.collect(&:description).join(' ')
end
def iteration_ids
iterations.collect &:id
end
And then use those methods in your index definition:
indexes iteration_titles, iteration_descriptions
has iteration_ids, :type => :integer, :multi => true
And then, run rake ts:regenerate to get it all set up (ts:rebuild and ts:index have no meaning for real-time indices).

ActiveRecord find with joins and associations?

I have a model TwitterUser that has_one website as shown in the model below:
class TwitterUser < ActiveRecord::Base
has_one :website, :foreign_key => :id, :primary_key => :website_id
end
I'm trying to run a query that will join TwitterUser with Website and get all TwitterUsers' with a website that has an updated_at date > a certain date, limited to 10 rows.
I thought this would give me what I wanted, but apparently it's not. What's wrong with it?
TwitterUser.includes().find(:all, :limit => 10, :conditions => ["websites.updated_at >= '2013-05-12 05:31:53.68059'"], :joins => :website)
In my database, my twitter_users table consist of a website_id field.
My websites table has an id field.
This should work.
TwitterUser.joins(:website).where("websites.updated_at >= '2013-05-12 05:31:53.68059'").limit(10)

Rewrite SQL query with tire

I'm trying to rewrite a query with tire.
This is the model that I have:
class User < ActiveRecord::Base
has_many :bookmarks, :dependent => :destroy
has_many :followed_venues, :through => :bookmarks, :source => :bookmarkable, :source_type => 'Venue'
end
A user can follow venues. And I need to search for venues that are followed by a certain users.
So far I've been doing it with ActiveRecord:
#user.followed_venues.where(["venues.title LIKE ?", "%"+params[:q]+"%"])
This is obviously not ideal, so I added elasticsearch to my app with tire.
How would I search for venues with tire, filtering by the user that is following them?
I'm going to post an answer to my own question.
So it's quite easy to just search venues. Standard Venue.tire.search {...} The problem is how to filter by user that follows venues. Instead of using the Venue model for searching, I decided to index bookmarks.
This is my bookmark model
class Bookmark < ActiveRecord::Base
belongs_to :bookmarkable, :polymorphic => true
def to_indexed_json
{
:title => bookmarkable.display_name,
:user_id => user_id
}.to_json
end
end
After this I have the user_id and the venue name in the index. Now search becomes as simple as this:
Bookmark.tire.search :load => {:include => 'bookmarkable'}, :page => page, :per_page => per_page do
query do
fuzzy :title => { :value => query.downcase, :min_similarity => 0.6, :prefix_length => 2}
end
filter :terms, {:bookmarkable_type => ["Venue"], :user_id => [user.id]}
end
Now this is not a complete solution. And I hope i'm even using filter :terms correctly. The result that I get back now is an array of bookmarks actually. But it's easy to load the actual venues for them, and maybe wrap it in a WillPaginate collection for better pagination on the frontend.
Any problems with this solution? How would it compare to what phoet suggested with putting user ids to the venue index?
i would create a document for each venue and then add a field with an array of all the user-ids that are following.
are you really sure, that this is a proper task for elasticsearch?
i guess it would be way easier to just search the index for the name of the venue and then look up the data you need in your relational database.

How to index boolean column in thinking sphinx using Ruby 1.8.7

I am new to ROR. i am using thinking sphinx. I need to index with one boolean field. that is, i list out records which are active is true.
define_index do
indexes car.name, :as => :car
indexes car_model.car_make.name, :as => :car_make
indexes city_name.city , :as=> :city_name
indexes car_active, :as=>:is_active, :type=>:boolean, :default=>true
end
I need to list out car details which are belong to active is true. can you help me?
If you want to filter on a boolean, then it's much better to have it as an attribute in Sphinx, instead of a field. Fields are the text data people will search for, attributes are the things you as a developer will order and filter by.
So, to set up that boolean column as an attribute:
define_index do
# fields
indexes car.name, :as => :car
indexes car_model.car_make.name, :as => :car_make
indexes city_name.city , :as=> :city_name
# attributes
has car_active
end
And then filtering:
Model.search 'foo', :with => {:car_active => true}

Defining indexes for associated models with Thinking Sphinx

What is the proper way of defining indexes on associated models with following configuration?
I have model Locality with lat and lng attributes and associated models Profile and User
class User < ActiveRecord::Base
has_one :user_profile
define_index do
# This doesn't work :(
has "RADIANS(user_profiles.localities.lat)", :as => :lat, :type => :float
has "RADIANS(user_profiles.localities.lng)", :as => :lng, :type => :float
end
end
end
class UserProfile < ActiveRecord::Base
belongs_to :user
belongs_to :locality
end
class Locality < ActiveRecord::Base
has_many :user_profiles
end
I need to define indexes for User model so I can perform geo-searches on it.
Thank you for the answers!
The problem is twofold:
Thinking Sphinx doesn't know that you want to join the user_profile and locality associations.
SQL snippets should be just that - standard SQL - so you can't chain associations within them.
The following should do the trick:
define_index do
# force the join on the associations
join user_profile.locality
# normal SQL:
has "RADIANS(localities.lat)", :as => :lat, :type => :float
has "RADIANS(localities.lng)", :as => :lng, :type => :float
end
Claudio's point of using singular references within the SQL is not correct - within SQL, you want table names. But you do want the correct association references in the join call (hence they're singular there).
Cheers
Don't really know the exact solution but:
You have a typo:
has_one :user_profile
define_index do
# This doesn't work :(
has "RADIANS(user_profiles.localities.lat)", :as => :lat, :type => :float
You are using "user_profiles" for the attribute, it should not be plural, try changing it to: "user_profile".
I repeat, I don't know if you can navigate through associations for this case, but if you can, this should be a typo.
You can read here for more information: http://freelancing-god.github.com/ts/en/geosearching.html

Resources