I am trying to implement solr search into one of my rails project. My problem statement is to be able to search upon my models and show relevant results along with auto suggest. Could someone help me to complete it properly. At the moment I am trying to use sunspot solr although its not working for me as expected. I can see some indexing has been created in my project but the search is not working. Also there is no gem for auto suggest. Below are some snippets of my code. And yes I have gone through other links for solr and it did not solve my problem.
Category Model
class Category < ActiveRecord::Base
attr_accessible :name, :parent_category_id, :image, :image_file_name, :image_content_type, :image_file_size
has_and_belongs_to_many :events, :join_table => :categories_events
has_attached_file :image
searchable do
text :name
end
end
My Controller
if params.has_key?(:category)
puts "Inside Index Search"
puts params[:category]
#search = Category.ransack do
fulltext params[:category]
end
#category = #search.result.first
I am getting the results into #category and using it in my view to display.
Thanks in advance. I really appreciate your help. :-)
What does the method Category.ransack do ? I assume that you are using sunspot gem to integrate Solr into your project. If so use Category.search to search you index. Then use
#search.results
not
#search.result
So your code should look like this:
#search = Category.search do
fulltext params[:category]
end
#category = #search.results.first
You can also write it shorter:
#category = Category.search do
fulltext params[:category]
end.results.first
Remember that before you can use index in Solr, you have run:
rake sunspot:solr:reindex
If you are looking for information about implementing autocomplete on Solr, please read this article: http://olgagr.github.io/ruby/solr/how-to-implement-autocomplete-with-solr-and-ruby-on-rails
Some time ago I also struggled with this topic.
I solved it. There was a conflict between Solr and Ransack gem and that's why I could not get the resultset. It needed the below changes and its working now.
#search = Sunspot.search(Category) do
fulltext params[:search]
end
#category = #search.results
EDIT
My answer might not seem complete so just adding the code snippet that worked for me.
My Model
searchable do
text :name, :as => :name_textp
text :description, :as => :description_textp
text :category_strings, :as => :category_strings_textp
string :get_valid_dates, :multiple => true
integer :category_ids, :multiple => true
boolean :active
boolean :company_display
end
My Controller
search_results = Sunspot.search(Event) do
fulltext current_search
with(:category_ids, [ id ])
with(:active, true)
with(:company_display, true)
paginate(:page => #current_page, :per_page => page_size)
end
Hope it helps.
It conflicts of Solr & Ransack method search.
So please user solr_search instead of search
ex:
search = Product.solr_search do
fulltext 'random'
end
search.results
Related
I am using rails 4.0 with sunspot solr. I wish to do a this:
(read_flag IS TRUE) OR ( (read_flag IS NIL) AND (fulltext
params[:search] is true))
This is my code in (post.rb):
#search = Post.search do
any_of
with :model_id,params[:model_id]
all_of
with :model_id, nil
fulltext params[:search] do
fields(:title,:tags)
end
end
end
end
However, the code above will generate this error
undefined method 'fulltext' for #<Sunspot::DSL::Scope:0x007ff641ef2f38>
So I tried changing all_of to all. But it is not returning what I want.
The model file (post.rb) is indexed as per below
searchable do
integer :user_id
text :title, :boost => 2
integer :model_id
end
Can anyone advice on how should i structured my query? Couldn't find any relevant questions on stackoverflow.
I'm sure you have probably moved beyond this long ago, so for the benefit of anyone who might have found this question, I was struggling with a similar search using any_of and fulltext. I found ending the conjunction/disjunction before the fulltext seems to work:
#search = Post.search do
any_of
with :model_id,params[:model_id]
all_of
with :model_id, nil
end
end
fulltext params[:search] do
fields(:title,:tags)
end
end
No hair left on my head (and I have had lots :) ), I have been pulling out my hair and for the life of me I can't figure this out.
I have a one to many relations between 2 tables. I have installed the Datagrid Gem for reporting. I need to get the report from one model based on the other one.
Please have a look at my code.
reports_grid.rb
class ReportsGrid
include Datagrid
scope do
Land.includes(:estate)
end
filter(:estate, :enum, :select => proc { Estate.group("title").select("title").map {|c| [c.title] }})
column(:id, :header => "Land ID")
column(:current_stage, :header => "Stage")
column(:price)
column(:status)
end
reports_controller.rb
class ReportsController < ApplicationController
def index
#grid = ReportsGrid.new(params[:reports_grid]) do |scope|
if params[:reports_grid].present?
if params[:reports_grid][:estate].present?
scope.joins(:estate).where("estates.title = ? ",params[:reports_grid][:estate]).page(params[:page])
**# when I get the #grid.assets here all good and return correct number of rows**
else
scope.page(params[:page])
end
else
scope.page(params[:page])
end
end
end
end
Land.rb
belongs_to :estate
estate.rb
has_many :lands
Now when I go to /reports and try to run the filter I get the following error
PG::UndefinedColumn: ERROR: column lands.estate does not exist LINE 1: ..._id" WHERE (estates.title = 'Olive Gardens' ) AND "lands"."e... ^ : SELECT COUNT(*) FROM "lands" INNER JOIN "estates" ON "estates"."id" = "lands"."estate_id" WHERE (estates.title = 'Olive Gardens' ) AND "lands"."estate" = 'Olive Gardens'
Why is the Gem tries to add "lands"."estate" = 'Olive Gardens' to the query when I have defined it at the instance.
Please let me know if you need me to add anything. Thank you in advance.
Edit:
This is what I have done and worked in the Filter:
I have done this:
filter(:estate_id, :enum,
:select => lambda {Estate.all.map {|p| [p.title, p.id]}},
:multiple => false,
:include_blank => true
) do |value|
self.where(:lands => {:estate_id => value})
end
Do you it is a good approach?
I guess in the scope I could say Land.joins(:estate) then use the scope.all.map... in the query.
Datagrid filter designed to filter data but not to just be by default.
If you have some reason why estate should not filter data by itself then add :dummy => true option:
filter(:estate, :enum, :select => ..., :dummy => true)
But I'would recommend it. Do this instead and your hair will start growing instantly:
filter(:estate, :enum, :select => ...) do |scope, value|
scope.joins(:estate).where("estates.title = ? ", value)
end
It seems obvious from documentation here:
https://github.com/bogdan/datagrid/wiki/Filters#filter-block
Try using references
Land.includes(:estate).references(:estates)
So I am trying to implement multiple autocomplete using this gem and simple_form and am getting an error.
I tried this:
<%= f.input_field :neighborhood_id, collection: Neighborhood.order(:name), :url => autocomplete_neighborhood_name_searches_path, :as => :autocomplete, 'data-delimiter' => ',', :multiple => true, :class => "span8" %>
This is the error I get:
undefined method `to_i' for ["Alley Park, Madison"]:Array
In my params, it is sending this in neighborhood_id:
"search"=>{"neighborhood_id"=>["Alley Park, Madison"],
So it isn't even using the IDs for those values.
Does anyone have any ideas?
Edit 1:
In response to #jvnill's question, I am not explicitly doing anything with params[:search] in the controller. A search creates a new record, and is searching listings.
In my Searches Controller, create action, I am simply doing this:
#search = Search.create!(params[:search])
Then my search.rb (i.e. search model) has this:
def listings
#listings ||= find_listings
end
private
def find_listings
key = "%#{keywords}%"
listings = Listing.order(:headline)
listings = listings.includes(:neighborhood).where("listings.headline like ? or neighborhoods.name like ?", key, key) if keywords.present?
listings = listings.where(neighborhood_id: neighborhood_id) if neighborhood_id.present?
#truncated for brevity
listings
end
First of all, this would be easier if the form is returning the ids instead of the name of the neighborhood. I haven't used the gem yet so I'm not familiar how it works. Reading on the readme says that it will return ids but i don't know why you're only getting names. I'm sure once you figure out how to return the ids, you'll be able to change the code below to suit that.
You need to create a join table between a neighborhood and a search. Let's call that search_neighborhoods.
rails g model search_neighborhood neighborhood_id:integer search_id:integer
# dont forget to add indexes in the migration
After that, you'd want to setup your models.
# search.rb
has_many :search_neighborhoods
has_many :neighborhoods, through: :search_neighborhoods
# search_neighborhood.rb
belongs_to :search
belongs_to :neighborhood
# neighborhood.rb
has_many :search_neighborhoods
has_many :searches, through: :search_neighborhoods
Now that we've setup the associations, we need to setup the setters and the attributes
# search.rb
attr_accessible :neighborhood_names
# this will return a list of neighborhood names which is usefull with prepopulating
def neighborhood_names
neighborhoods.map(&:name).join(',')
end
# we will use this to find the ids of the neighborhoods given their names
# this will be called when you call create!
def neighborhood_names=(names)
names.split(',').each do |name|
next if name.blank?
if neighborhood = Neighborhood.find_by_name(name)
search_neighborhoods.build neighborhood_id: neighborhood.id
end
end
end
# view
# you need to change your autocomplete to use the getter method
<%= f.input :neighborhood_names, url: autocomplete_neighborhood_name_searches_path, as: :autocomplete, input_html: { data: { delimiter: ',', multiple: true, class: "span8" } %>
last but not the least is to update find_listings
def find_listings
key = "%#{keywords}%"
listings = Listing.order(:headline).includes(:neighborhood)
if keywords.present?
listings = listings.where("listings.headline LIKE :key OR neighborhoods.name LIKE :key", { key: "#{keywords}")
end
if neighborhoods.exists?
listings = listings.where(neighborhood_id: neighborhood_ids)
end
listings
end
And that's it :)
UPDATE: using f.input_field
# view
<%= f.input_field :neighborhood_names, url: autocomplete_neighborhood_name_searches_path, as: :autocomplete, data: { delimiter: ',' }, multiple: true, class: "span8" %>
# model
# we need to put [0] because it returns an array with a single element containing
# the string of comma separated neighborhoods
def neighborhood_names=(names)
names[0].split(',').each do |name|
next if name.blank?
if neighborhood = Neighborhood.find_by_name(name)
search_neighborhoods.build neighborhood_id: neighborhood.id
end
end
end
Your problem is how you're collecting values from the neighborhood Model
Neighborhood.order(:name)
will return an array of names, you need to also collect the id, but just display the names
use collect and pass a block, I beleive this might owrk for you
Neighborhood.collect {|n| [n.name, n.id]}
Declare a scope on the Neighborhood class to order it by name if you like to get theat functionality back, as that behavior also belongs in the model anyhow.
edit>
To add a scope/class method to neighborhood model, you'd typically do soemthing like this
scope :desc, where("name DESC")
Than you can write something like:
Neighborhood.desc.all
which will return an array, thus allowing the .collect but there are other way to get those name and id attributes recognized by the select option.
Now I'm using a gem called 'mailboxer' for messaging system.https://github.com/ging/mailboxer
I'd like to implement 'keyword search function' for inbox, sentbox, and trash.
If I don't care about search keyword, it is fetching the result without any problem by coding like this
inbox... #messages = current_user.mailbox.inbox.page(params[:page]).per(10)
sentbox... #messages = current_user.mailbox.sentbox.page(params[:page]).per(10)
trash... #messages = current_user.mailbox.trash.page(params[:page]).per(10)
But I sometimes want to filter the result with search keyword.
Assume search keyword was 'test' this time, the result should be only the records that contain 'test' within body attribute in notifications table.
How can I do this for each above such as inbox, sentbox, and trash??
I tried this but it didn't work at all :(
#messages = current_user.mailbox.inbox.search_messages(#search).page(params[:page]).per(10)
#messages = current_user.mailbox.sentbox.search_messages(#search).page(params[:page]).per(10)
#messages = current_user.mailbox.trash.search_messages(#search).page(params[:page]).per(10)
The solution is to monkey patch both the Mailboxer::Receipt and Mailboxer::Models::Messageable module to enable searching by box type:
class Mailboxer::Receipt < ActiveRecord::Base
...
if Mailboxer.search_enabled
searchable do
text :subject, :boost => 5 do
message.subject if message
end
text :body do
message.body if message
end
integer :receiver_id
# Add mailbox_type to sunspot search field
string :mailbox_type
boolean :trashed
boolean :deleted
end
end
end
and under the Messageable module, I've added new search__messages method for each mailbox type:
module Mailboxer
module Models
module Messageable
def search_messages(query)
# Replaces 'search' with alias 'solr_search' since ransack search clashes with solr search method
#search = Mailboxer::Receipt.solr_search do
fulltext query
with :receiver_id, self.id
end
#search.results.map { |r| r.conversation }.uniq
end
# Adds additional Mailboxer search functionality to search by box type
def search_inbox_messages(query)
#search = Mailboxer::Receipt.solr_search do
fulltext query
with :receiver_id, self.id
with(:mailbox_type).equal_to("inbox")
end
#search.results.map { |r| r.conversation }.uniq
end
def search_sent_messages(query)
#search = Mailboxer::Receipt.solr_search do
fulltext query
with :receiver_id, self.id
with(:mailbox_type).equal_to("sentbox")
end
#search.results.map { |r| r.conversation }.uniq
end
def search_trash_messages(query)
#search = Mailboxer::Receipt.solr_search do
fulltext query
with :receiver_id, self.id
with :trashed, true
with :deleted, false
end
#search.results.map { |r| r.conversation }.uniq
end
end
end
end
With this, in your controller you can simply use the newly defined methods to do the box type searching.
current_user.search_inbox_messages(query[:keywords])
current_user.search_sentbox_messages(query[:keywords])
current_user.search_trash_messages(query[:keywords])
IMPORTANT NOTE: Make sure to regenerate the Solr indexes after adding the new search fields, otherwise nothing will show up:
bundle exec rake sunspot:solr:reindex
Hope this helps!
I have a project model in my rails 3.1 application and I want to use Solr to run a search on it.
I defined the search like this:
searchable do
text :nr, :boost => 5 # nr is integer
text :name, :boost => 5
text :description, :boost => 2
text :client do
client.name
end
text :tasks do
tasks.map(&:name)
end
end
The project-nr, in my model just called nr, type integer, is the most used reference for finding a project.
Now besides having a search form I still want my projects ordered by the nr when no search was performed, but this does not work - my project seem to be in totally random order.
The code of my ProjectsController index action looks like this:
def index
#search = Project.search do
fulltext params[:search]
paginate :page => params[:page]
order_by :nr, :desc
end
#projects = #search.results
##projects = Project.active.visible.design.order("nr desc")
respond_to do |format|
format.html # index.html.erb
format.json { render json: #projects }
end
But when I visit then myapp/projects I get a
Sunspot::UnrecognizedFieldError in ProjectsController#index
No field configured for Project with name 'nr'
error...
any ideas what I need to do to order by nr. ?
thanks
Okay, I solved it by turning the nr field to an integer in my searchable:
searchable do
integer :nr
text :name, :boost => 5
text :description, :boost => 2
text :client do
client.name
end
text :tasks do
tasks.map(&:name)
end
end
Now I was able to order it nicely but I couldn't perform a text search on the project_nr anymore.
So I added a virtual attribute name_number to my Project model and instead searched on this field.
def name_number
"#{self.nr} - #{self.name[0..24]}"
end
Now I have ordering and searching in place... If there are other / better ideas, keep em coming!