Is passing multiple params to thinking_sphinx search method absolutely impossible? - ruby-on-rails

I have a page that when visted brings up the most recently active users. Above the users are some filtering options such as filtering by one or a combination of:
location
gender
sexual preference
age range
country
I'm using a form_tag helper for my form. My issue is passing each of these parameters to my controller:
class BrowsersController < ApplicationController
def index
#default_image = "/assets/default_avatar.jpg"
#users = Profile.search params[:search], :page => params[:page], :per_page => 26
end
end
If I was searching with one field with the param "Search" I would be fine but I have multiple fields, select menu's on my form. How am I suppose to pass that info to my controller in order to filter the search results?
I'm sure I'm not the first to use search filtering in ruby on rails
<%= form_tag browsers_path, :method => 'get' do %>
<p>
Location: <%= text_field_tag :location %><br />
Gender: <%= select_tag :gender,
options_for_select([["Select", nil],
["Male", 1],
["Female", 2]]) %>
<br />
<%= submit_tag "Search", :name => nil %>
</p>
<% end %>
<br />
Kind regards
update
#users = Profile.search 'HERE IS WHERE THE POWER LIES', :page => params[:page], :per_page => 20, :conditions_all => { :gender => params[:gender], :location => params[:location]}
I use :conditions_all to get my field params and in rails server logs I can see that they are being picked up.. now I just need to some how get them all seen by thinking sphinx
Update 2
i have has gender in the define_index block and indexes location because it seems i need at least 1 field.
this working to return genders:
#users = Profile.search params[:location], :page => params[:page], :per_page => 40, :with => { :gender => [params[:gender]]}
I've tried to check for both location and gender and it seems to work but I'll double check in console because it's returning 1 female in united kingdom out of 1000 and that could be wrong but I'll double check in console and edit this update appropriately.

I'm not quite sure where you got :conditions_all from - if you're dealing with fields, then :conditions is what you're after:
#users = Profile.search 'HERE IS WHERE THE POWER LIES',
:page => params[:page],
:per_page => 20,
:conditions => {:gender => params[:gender], :location => params[:location]}
But, it sounds like you've got gender as an attribute instead of a field - and so, you want to filter on it instead:
#users = Profile.search 'HERE IS WHERE THE POWER LIES',
:page => params[:page],
:per_page => 20,
:conditions => {:location => params[:location]},
:with => {:gender => params[:gender]}

As I said here, I will try to explain a bit of Thinking Sphinx and my suggested approach to solve your problem.
Let's say you have the following Profile model:
# == Schema Information
#
# Table name: access_number_campaigns
#
# id :integer
# latitude :float
# longitude :float
# created_at :datetime
# updated_at :datetime
class Profile < ActiveRecord::Base
GENDER = {1 => "Male", 2 => "Female"}
belongs_to :country
has_and_belongs_to_many :sexual_preferences
has_and_belongs_to_many :age_ranges
end
And you may have those models:
class SexualPreference < ActiveRecord::Base
has_and_belongs_to_many :profiles
end
class AgeRange < ActiveRecord::Base
has_and_belongs_to_many :profiles
end
class Country < ActiveRecord::Base
has_many :profiles
end
Then you may define your ThinkingSphinx index in the following schema:
# within model Profile
define_index
has "RADIANS(latitude)", :as => :latitude, :type => :float
has "RADIANS(longitude)", :as => :longitude, :type => :float
has sexual_preferences(:id), :as => :sexual_preference_ids
has age_ranges(:id), :as => :age_range_ids
has country_id, :type => :integer
has gender, :type => :integer
end
And you can create a class to build the following ThinkingSphinx query to handle all needed associations and attributes:
Profile.search "your keywords", :page => 1, :per_page => 10, :with => {"#geodist" => 0.0..NUMBER_OF_METERS, :with_all=>{:sexual_preference_ids=>["1", "2", "3"], :age_range_ids=>["1", "2", "3"], :country_id => ["123"], :gender => ["1"]} }
You can see above a Thinking Sphinx search query with :with and :with_all hashes included. There is also an important Sphinx's #geodist function called. I hope you have readable example and the best reference for Thinking Sphinx gem you can find here:
Thinking Sphinx reference
Section about indexing
Section about searchin
Section about geodist
I hope you enjoy reading my example and very clear Thinking Sphinx reference.

Related

Tire - sort by number of related object

I have something like this in my model:
class User < ActiveRecord::Model
has_many :followers, :through => :as_followed_followings, :class_name => "User", :foreign_key => "follower_id", :uniq => true
...
def self.search(params)
tire.search(load: true, page: params[:page], per_page: params[:per]) do
end
end
end
I would like the return an array of users ordered by the count of followers that user has.
So what is the correct way to define mappings and indexes to search through nested object with count property ?
Thanks for reading.
I have solved this problem by creating another field on users table and index that value instead of performing nested search in runtime.
This may not be the best solution but I have deadline. Any better solution is welcome :)
Trying with ...
AsFollowedFollowing.count(:group => :follower_id, :order => "count_all DESC")
This is Ordered Hash that contains user_id => count
If you want to loop this hash, try following
# in controller
#counts = AsFollowedFollowing.count(:group => :follower_id, :order => "count_all DESC")
#users = User.where(:id => #counts.map{|k,_| k})
#in view
<% #counts.each do |k, v| %>
<% user = #users.find{|u| u.id == k} %>
<div><%= user.name %> | <%= v %></div>
<% end %>

Thinking Sphinx order if attribute exists else ignore

If have multiple indexed models that have a verification level which need to be sorted by their verification level.
But I also have a couple of models without a verification level.
ThinkingSphinx.search #search.q, :order => 'class_crc ASC, #relevance DESC, verification_level DESC', :page => page, :limit => limit, :classes => #search.types_as_models
The problem is I can rank by verification_level on models that have that attribute but it errors on models without a verification_level
index ground_core,ground_delta,user_core,user_delta: sort-by attribute 'verification_level' not found
Example player define_index with verification_level
define_index do
indexes [first_name, last_name], :as => :name, :sortable => true
where "state = 1"
has created_at, updated_at, verification_level
set_property :delta => ThinkingSphinx::Deltas::ResqueDelta
end
Example User define_index without verification_level
define_index do
indexes [first_name, last_name], :as => :name, :sortable => true
has created_at, updated_at
set_property :delta => ThinkingSphinx::Deltas::ResqueDelta
end
Is there a way to sort when it is on a model and ignore when it isnt?
My solution was to fake a verification level on models which didn't require it by adding it in the define_index block
has '0', :as => :verification_level, :type => :integer

Thinking Sphinx without condition

I have a following model:
class Article < ActiveRecord::Base
define_index do
indexes content
indexes :name, sortable: true
has type
end
end
and special type of an article is:
class About < Article
end
and the same for Contact
I would like to have searchable articles index action without articles with type of "About" or "Contact"
class ArticlesController < ApplicationController
load_and_authorize_resource
def index
#articles = Article.search(params[:search],
:with_all => {:type => nil},
:page => params[:page],
:per_page => 10)
end
end
But the #articles instance variable contains everytime also "About" & "Contact" articles.
This is very strange (seems like will_paginate messing everything up):
#articles = Article.search(
:without => {:type => %w(About Contact)}).include?(About.first) # false
#articles = Article.search(
:without => {:type => %w(About Contact)},
:page => 1,
:per_page => 1000).include?(About.first) # true
=============================================================================
Finally I did:
class Article < ActiveRecord::Base
define_index do
indexes content
indexes :name, sortable: true
has "CRC32(type)", :as => :article_type, :type => :integer
end
end
and:
class ArticlesController < ApplicationController
load_and_authorize_resource
def index
#articles = Article.search(params[:search],
:without => {:article_type => ["About".to_crc32, "Contact".to_crc32]},
:page => params[:page],
:per_page => 10)
end
end
and it works. Thanks guys!
From the Thinking Sphinx FAQ:
Filtering on String Attributes
While you can have string columns as
attributes in Sphinx, they aren’t stored as strings. Instead, Sphinx
figures out the alphabetical order, and gives each string an integer
value to make them useful for sorting. However, this means it’s close
to impossible to filter on these attributes.
So, to get around this, there’s two options: firstly, use integer
attributes instead, if you possibly can. This works for small result
sets (for example: gender). Otherwise, you might want to consider
manually converting the string to a CRC integer value:
has "CRC32(category)", :as => :category, :type => :integer
This way, you can filter on it like so:
Article.search 'pancakes', :with => { :category => 'Ruby'.to_crc32 }
Of course, this isn’t amazingly clean, but it will work quite well.
You should also take note that CRC32 encoding can have collisions, so
it’s not the perfect solution.
Exclude About & Contact type from conditions like -
#articles = Article.search(params[:search],
:without => {:type => %w(About Contact)},
:page => params[:page],
:per_page => 10)
OR
I am not very sure on below but you can give it try..
define_index do
indexes content
indexes :name, sortable: true
indexes(:type), :as => :article_type
end
#articles = Article.search(params[:search],
:without => {:article_type => %w(About Contact)},
:page => params[:page],
:per_page => 10)
Also, make sure inside your database records you have type attribute got inserted properly..
Couldn't you use "with" instead of "with_all"? For example:
#articles = Article.search(params[:search],
:with => {:type => nil},
:page => params[:page],
:per_page => 10)

How do I update a child db table when populating database in ruby on ralls?

I have the following tables in my database:
User(s) - has one profile, has many PhotoAlbums
Profile(s) - belongs to user
PhotoAlbum(s) - belongs to user, has many photos
Photo(s) - belongs to PhotoAlbum
Faker works fine when it comes to populating my users table but I now wish to update my profiles table also but it doesn't work at all. The rows in the db still remain empty. I have no errors showing up when I run rake db:populate.
Am I missing something, please help me see where I'm going wrong and help me come up with a solution as this will help me when I need to populate my other tables too. What I learn from this answer can help me with other tasks I have coming up.
libs/task/sample_data.rake
namespace :db do
desc "Create user records in the development database."
task :populate => :environment do
require 'faker'
def randomDate(params={})
years_back = params[:year_range] || 5
latest_year = params [:year_latest] || 0
year = (rand * (years_back)).ceil + (Time.now.year - latest_year - years_back)
month = (rand * 12).ceil
day = (rand * 31).ceil
series = [date = Time.local(year, month, day)]
if params[:series]
params[:series].each do |some_time_after|
series << series.last + (rand * some_time_after).ceil
end
return series
end
date
end
def decimal_selection_array(start,limit,step_size=1)
decimal_array = (start..limit).step(step_size).map{|i| i.to_s}.to_a
decimal_array.insert(0,"Below #{start.to_f}")
decimal_array.insert(-1,"Above #{limit.to_f}")
end
100.times do |n|
username = "#{Faker::Name.first_name}#{n}"
u = User.create!(
:username => username,
:email => Faker::Internet.email,
:password => "foobar"
)
u.profile.update_attributes(
:motd => Faker::Lorem.words,
#Profile details
:first_name => Faker::Name.first_name,
:last_name => Faker::Name.last_name,
:birthday => randomDate(:year_range => 60, :year_latest => 22),
:gender => (1..2).to_a.sample,
:marital_status => (1..7).to_a.sample,
:sexual_preference => (1..3).to_a.sample,
:ethnicity => (1..10).to_a.sample,
:country => Faker::Address.country,
:location => Faker::Address.country,
#About the user
:about_me => Faker::Lorem.paragraph,
#Personal stats
:height => decimal_selection_array(5.0,7.0,0.1).to_a.sample,
:body_type => (1..7).to_a.sample,
:eye_colour => (1..6).to_a.sample,
:drugs => (1..4).to_a.sample,
:alcohol => (1..4).to_a.sample,
:cigarettes => (1..3).to_a.sample,
:likes => Faker::Lorem.sentence,
:dislikes => Faker::Lorem.sentence,
:bad_habits => Faker::Lorem.sentence,
#Favourite things
:food => Faker::Lorem.sentence,
:music => Faker::Lorem.sentence,
:television => Faker::Lorem.sentence,
:book => Faker::Lorem.sentence,
:animal => Faker::Lorem.sentence,
:place => Faker::Lorem.sentence,
:possesion => Faker::Lorem.sentence
)
end
end
end
Kind regards
User profile (u.profile) does not exist when calling u.profile.update_attributes(params).
You should call u.create_profile(params) instead.
Using nested attributes could also help.
I solved this issue by:
commenting out:
before_safe :build_profile
in my User model
and my editing my rake task to look like this:
# before running this task comment out: before_create :build_profile in user.rb in order to make this work correctly
namespace :db do
desc "Create user records in the development database."
task :populate => :environment do
require 'faker'
def randomDate
"#{(1900..2012).to_a.sample}-#{(1..12).to_a.sample}-#{(1..28).to_a.sample}"
end
def decimal_selection_array(start,limit,step_size=1)
decimal_array = (start..limit).step(step_size).map{|i| i.to_s}.to_a
decimal_array.insert(0,"Below #{start.to_f}")
decimal_array.insert(-1,"Above #{limit.to_f}")
end
1000.times do |n|
username = "#{Faker::Name.first_name}#{n+1}"
User.create!(
:username => username.gsub(/[^0-9a-z]/i, ''),
:email => Faker::Internet.email,
:password => "foobar"
)
Profile.create!(
:user_id => "#{n+1}",
:motd => Faker::Lorem.sentence,
#Profile details
:first_name => Faker::Name.first_name.gsub(/[^a-z]/i, ''),
:last_name => Faker::Name.last_name.gsub(/[^a-z]/i, ''),
:birthday => randomDate,
:gender => (1..2).to_a.sample,
:marital_status => (1..7).to_a.sample,
:sexual_preference => (1..3).to_a.sample,
:ethnicity => (1..10).to_a.sample,
:country => Faker::Address.country,
:location => Faker::Address.country,
#About the user
:about_me => Faker::Lorem.paragraph,
#Personal stats
:height => decimal_selection_array(5.0,7.0,0.1).to_a.sample,
:body_type => (1..7).to_a.sample,
:hair => (1..7).to_a.sample,
:eye_colour => (1..6).to_a.sample,
:drugs => (1..4).to_a.sample,
:alcohol => (1..4).to_a.sample,
:cigarettes => (1..3).to_a.sample,
:likes => Faker::Lorem.sentence,
:dislikes => Faker::Lorem.sentence,
:bad_habits => Faker::Lorem.sentence,
#Favourite things
:food => Faker::Lorem.sentence,
:sport => Faker::Lorem.sentence,
:music => Faker::Lorem.sentence,
:television => Faker::Lorem.sentence,
:book => Faker::Lorem.sentence,
:animal => Faker::Lorem.sentence,
:place => Faker::Lorem.sentence,
:possession => Faker::Lorem.sentence
)
end
end
end
I had to make a few adjustments to what Faker was passing into my db as things were failing validation. Things like usernames with characters other than letters and numbers and this also happened with last_name too. So I had to filter all that stuff out and then I ran the take task and successfully updated my database with 1000 users with profile information.
All I have to do is uncomment the before_create any time I want to add sample data to stop profile rows being created for users on create.
I also added user_id to attr_accessible in my Profile model so that the user_id column could be populated with numbers 1 to 1000 which would match them up with a user.

Thinking Sphinx and acts_as_taggable_on plugin

I installed Sphinx and Thinking Sphinx for ruby on rails 2.3.2.
When I search without conditions search works ok. Now, what I'd like to do is filter by tags, so, as I'm using the acts_as_taggable_on plugin, my Announcement model looks like this:
class Announcement < ActiveRecord::Base
acts_as_taggable_on :tags,:category
define_index do
indexes title, :as => :title, :sortable => true
indexes description, :as => :description, :sortable => true
indexes tags.name, :as => :tags
indexes category.name, :as => :category
has category(:id), :as => :category_ids
has tags(:id), :as => :tag_ids
end
For some reason, when I run the following command, it will bring just one announcement, that has nothing to do with what I expect. I've got many announcements, so I expected a lot of results instead.
Announcement.search params[:announcement][:search].to_s, :with => {:tag_ids => 1}, :page => params[:page], :per_page => 10
I guess something is wrong, and it's not searching correctly.
Can anyone give my a clue of what's going on?
Thanks,
Brian
Thinking Sphinx relies on associations in model. In common situations you only have to put index definition below your associations.
With acts_as_taggable_on plug-in you don't have tag-related associations in model file and when you write
indexes tags.name, :as => :tags
TS interprets it like:
CAST(`announcements`.`name` AS CHAR) AS `tags`
(look at sql_query in development.sphinx.conf, in my case).
I suppose that you have attribute name in model Announcement and don't run into error when rebuild index.
But we expect:
CAST(GROUP_CONCAT(DISTINCT IFNULL(`tags`.`name`, '0') SEPARATOR ' ') AS CHAR) AS `tags`
and:
LEFT OUTER JOIN `taggings` ON (`announcements`.`id` = `taggings`.`taggable_id`)
LEFT OUTER JOIN `tags` ON (`tags`.`id` = `taggings`.`tag_id`) AND taggings.taggable_type = 'Announcement'
To get things working just add tag-related associations in your model before you rebuild index:
class Announcement < ActiveRecord::Base
acts_as_taggable_on :tags,:category
has_many :taggings, :as => :taggable, :dependent => :destroy, :include => :tag, :class_name => "ActsAsTaggableOn::Tagging",
:conditions => "taggings.taggable_type = 'Announcement'"
#for context-dependent tags:
has_many :category_tags, :through => :taggings, :source => :tag, :class_name => "ActsAsTaggableOn::Tag",
:conditions => "taggings.context = 'categories'"
In define_index method:
indexes category_tags(:name), :as => :tags
has category_tags(:id), :as => :tag_ids, :facet => true
In controller:
#announcement_facets = Announcement.facets params[:search], :with => {:tag_ids => [...]}
#announcements = #announcement_facets.for.paginate( :page => params[:page], :per_page => 10 )
I found that simply defining the index thus:
Class Thing < ActiveRecord::Base
acts_as_taggable
define_index do
..other indexing...
indexes taggings.tag.name, :as => :tags
end
end
worked fine.
One possibility is that you need to declare the type for tag_ids as :multi because TS can get confused (I just discovered this here http://groups.google.com/group/thinking-sphinx/browse_thread/thread/9bd4572398f35712/14d4c1503f5959a9?lnk=gst&q=yanowitz#14d4c1503f5959a9).
But why not use the tag names to search? E.g.,
Announcement.search params[:announcement][:search].to_s, :conditions => {:tags => "my_tag"}, :page => params[:page], :per_page => 10
Or, if you need to search for multiple tags:
Announcement.search( "#{params[:announcement][:search].to_s} (#tags my_tag | #tags your_tag)", :page => params[:page], :per_page => 10 )
(as aside, you may want to sanitize/remove sphinx-control-characters from the user-provided query before using it).
For debugging, I would go into console and strip down your query as much as possible (eliminate pagination arguments, even the query (just do ""), etc.).

Resources