Sunspot Solr, Rails and Ordering - ruby-on-rails

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!

Related

Implement Solr Search into Rails Project

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

Get value from text field and store it as array

I have a text field in my database called departments where i want to store the list of departments. The user will enter the name of departments with comma separation. For example:
department1, deaprtment2, department3
I want this value to be stored as array when the user submits the form. Also, i want the list of departments to show as a drop-down. Finally, while updating the table , the department field should also be editable as before(update by entering texts separated by commas).
EDIT:
I have added this to my model:
class Org < ActiveRecord::Base
serialize :department, Array
attr_accessible :name, :department
before_validation :update_department
validates :name, presence: true
def update_department
if department_changed? and department.is_a?(String)
self.department = self.department.split(',').collect(&:strip)
end
end
end
and the view:
<%= f.text_area :department, :cols => "10", :rows => "10" %>
now Whenever i try to sign up, the department field already has [] present and when i try to update the department is already ["[department1", "department2]"].
I want [] to be removed while signing up and only department1, department2 to show up when updating.
Please Help.
The best way to do this would be via your models. I am assuming that you have a model called Org and another called Department and that you have defined a has many relationship between the two. All you then need to do is in your Org model add the following code:
def department_list
departments.collect { |d| d.department_name }.join(', ')
end
def department_list=(text)
if id && text
departments.destroy_all
text.split(',').each do |d|
departments.create(department_name: d.strip.capitalize)
end
end
end
Then in your view add a text box using #org.department_list.
EDIT:
Based on your expanded question, you have department field in an org model that you want to store and show as an array and but edit as a simple text field. My thoughts on this was that I don't like the idea of storing department data a field in org, it is a one to many relationship so department should be a separate model. I would remove the department field from org. Then create a migration to create a departments table. It should look something like this:
class CreateDeparments < ActiveRecord::Migration
def change
create_table :departments do |t|
t.integer :org_id
t.string :department_name
t.timestamps
end
end
end
Next in the Department model add this line of code:
belongs_to :org
In the org model add the following:
has_many :departments, dependent: :destroy
def department_list
departments.collect { |d| d.department_name }.join(', ')
end
def department_list=(text)
if id && text
departments.destroy_all
text.split(',').each do |d|
departments.create(department_name: d.strip.capitalize)
end
end
end
In your controllers and views you now have the following:
#org = Org.first
# List of departments as an array for a select
#org.departments
# A comma separated string for text boxes
#org.department_list
The department_list method can now be used to display the list in a text box and also be used to post and changes back. So you your view code just becomes this:
<%= f.text_area :department_list, :cols => "10", :rows => "10" %>
You will probably need to amend your org controller by changing the create to something like this:
def create
#org = Org.new(params[:org])
respond_to do |format|
if #org.save
#org.department_list = params[:org][:department_list]
format.html { redirect_to org_url,
notice: "#{#org.name} was successfully created" }
format.json { render json: #org,
status: :created, location: #org }
else
format.html { render action: "new" }
format.json { render json: #org.errors, status: :unprocessable_entity }
end
end
end
If you are still stuck I have a complete webiste on github that you can look through. For you it is orgs and departments and on mysite it is people and skills or people and credits. This is the link:
https://github.com/davesexton/CKCASTING

Getting rails3-autocomplete-jquery gem to work nicely with Simple_Form with multiple inputs

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.

How to implement 'search function' for Mailboxer's each box

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!

ruby/rails how to ignore a comma in when ordering

I have a price field for a product in a catalog. Sometimes the admin user is putting a comma when dealing with thousands (ex: $10,000) and sometimes he is just doing $6000. While I would like to simply tell him to do it one way or the other, I would also like to solve the issue programmatically.
The #show action responsible is here:
def show
#category = Category.find_by_url_name(params[:category_id])
#brand = Brand.find(params[:id])
#search = Product.find(:all, :conditions => ['brand_id = ? and category_id = ?', #brand.id, #category.id],
:order=> params[:order] || 'price DESC')
#products = #search.paginate(:page => params[:page], :per_page => 12 )
#meta_title = "#{#brand.name}"
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #brand }
end
end
I also have a sort_options helper in my application helper that is providing the ordering options to the site user:
def product_sort_options
options_for_select([
['', nil],
['Newest to Oldest', 'descend_by_date'],
['Oldest to Newest', 'ascend_by_date'],
['Price: Highest to Lowest', 'descend_by_price'],
['Price: Lowest to Highest', 'ascend_by_price'],
['Name', 'ascend_by_name']
])
end
any ideas?
To make it a full answer - price should not be a string. The fact that you have 300 products now is not a big deal.
Make a migration:
rails generate migration decimalise
Then edit it (db/migrate/*decimalise.rb), and write something like this:
class Decimalise < ActiveRecord::Migration
def up
connection = ActiveRecord::Base.connection()
# kill the weird chars in the string field
connection.execute("UPDATE products SET price = REPLACE(REPLACE(price, ',', ''), '$', '')")
# convert the string column into a decimal one
change_table :products do |t|
# adjust for your use case - this gives you values up to 9999999.99
# if you need more, increase the 10
t.column :price, :decimal, :precision => 10, :scale => 2
end
end
def down
change_table :products do |t|
t.column :price, :string, :limit => 10
end
end
end
then finally, run
rake db:migrate
(untested, you will probably need to tweak. also, back up your DB before any tinkering - I'll not be responsible for any data loss you suffer)
EDIT One thing I forgot: how to print it out.
<%= number_to_currency #product.price %>
should give you something like $1,999.99 for a price of 1999.99.
You can use String.gsub to search the commas and replace them by nothing.

Resources