Thinking Sphinx Faceted Search Implementation Examples? - ruby-on-rails

I am putting together a repository-type rails 3 site.
I have Thinking Sphinx installed and working on my site, insomuch as I can enter urls like localhost:3000/articles?search=test&page=2 and it will return the expected results.
I'm new to Rails (and web dev in general); I think I can manage the model and controller aspects of this, but the views so far have me stumped. I have a sidebar I would like to use as the search interface. The closest I have come is this (rendered as part of a sidebar partial):
<% form_tag #search, :method => :get do %>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search"%>
<% end %>
The search method is in my articles#index controller, and when I test it when the browser is pointed to that page (routed as /articles), it works as expected, but with this odd url: localhost:3000/articles?utf8=✓&search=test&commit=Search. When the browser is first pointed to the root path, nothing happens.
So, I think these are the main issues I need to address:
EDIT - solved (see below)
Should I move the search methods to their own controller, or should they be part of the articles controller? For now, Article will be the only model indexed.
EDIT - solved (see below)
Does anyone have any good example code of a faceted search view using Rails 3 and Thinking Sphinx? Like I said, I am something of a neophyte and am a little flustered by the documentation that skims by the view implementation. However, I am fairly adept at reading and interpreting code as long as it is reasonably complete.
Thanks in advance!
Solved:
How do I make the 'Search' button call the index method before trying to search? (I have solved this by replacing #search with articles_path).
Solved using will_paginate, which I had trouble with before, but which seems to be working now.

Hey,
This is an extract of how my site worked before i switched to solr
Product has many categories, we tell sphinx that we want to index them as facets
class Product < ActiveRecord::Base {
has_many :categorisations, :dependent => :destroy
has_many :categories, :through => :categorisations
define_index do
indexes product_name,
indexes description
indexes categories(:name), :as => :category,:facet => true
end
}
Results Controller
class ResultsController < ApplicationController
def index
#facets = Product.facets params[:qt], :conditions => {:category => params[:category}},:page => params[:page], :per_page => 20
#products = #facets.for
end
end
And then in the view you can do something like
<% #facets.each do |facet, facet_options| %>
<span><%= facet %></span>
<ul>
<% facet_options.each do |option, count| %>
<li><%= link_to "#{option} (#{count})",
:params => {facet => option, :page => 1} %></li>
<% end %>
</ul>
<% end %>

Related

How do I set my rails app up so that I can click a letter and see only the entries starting with that letter?

My rails app basically displays a dictionary of sorts, which is stored in a database. I've set up a basic rails app with scaffold to display this on a webpage, but I can't for the life of me figure out how to set this next bit up. Basically I want a row of the 26 alphabetical letters, and when you click on a letter (say 'A'), I only want to see the dictionary entries of that letter.
My index.html.erb file:
<% for char in 'A'..'Z' %>
<%= link_to( "#{char}", "/words/lettersearch", :id => "#{char}") %>
<% end %>
my words_controller.rb file method for doing this:
def lettersearch
#word = Word.find(:all, :conditions => ["word LIKE ?", "#{params[:word]}%"])
end
It then outputs the #word to another page.
My problem is that it just outputs the entire dictionary again, no matter what letter I click on the index page. I know it's to do with the routing, as it never seems to actually run the 'lettersearch' method - it's always trying to run through the 'Show' method that's defined by default earlier in the controller.
Anyone able to give me a quick hand with how to route this thing through? I'm pretty new to rails and I don't understand the workings of link_to very well at all.
routes.rb:
Dictionary::Application.routes.draw do
resources :words do
post 'search', :on => :collection
post 'lettersearch', :on => :collection
end
#Rest of routes.rb still commented apart from
match ':controller(/:action(/:id))(.:format)'
You are passing in a param called :id but in your controller you use a param called :word
Also, update your code to use newer syntax
World.where(["word like ?", "#{params[:id]}%"])
And your view code can be cleaned as well
<% for char in 'A'..'Z' %>
<%= link_to char, "/words/lettersearch", :id => char %>
<% end %>
Your routes file only has a POST route to letter search, but a link_to is a GET request. So whats happening is the GET request is hitting /words/:id via GET which is the show action by default, and the params[:id] inside that request will be "lettersearch"
You could run the search in your index. I think that might be what you want to do. In your index controller you could put:
#orders = Order.lettersearch(params[:word])
and then in your model:
def lettersearch(word)
if lettersearch
#word = Word.find(:all, :conditions => ["word LIKE ?", "#{word}%"])
else
all
end
end

How to display a tag cloud from Acts as Taggable On on an index page in Ruby on Rails 3.2.7?

I'm very new to Ruby on Rails, so there's probably a simple solution I'm missing.
The tldr version - how do I display an Acts As Taggable On tag cloud of distinct (i.e. no repeating) tags assigned to all instances of a particular model on that model's index page?
The longer version - I have a model called Video in which I have successfully managed to implement a tagging feature using Acts as Taggable On and this fantastic tutorial.
What I'd like to do now is, on the Video's index page (index.html.erb), to display a summary of all the individual tags that a user has assigned to individual videos. For example, lets say I have three videos, each tagged as follows:
Video 1: great, banana, book
Video 2: small, great, apple
Video 3: rubbish, small, banana
I'd like the index page to display the following list of tags:
great, banana, book, small, apple, rubbish.
The code for my model (elided) is as follows:
class Video < ActiveRecord::Base
attr_accessible :tag_list # Lots of other fields in here as well, but not relevant
acts_as_taggable_on :tags
end
The code in my Video helper is as follows:
module VideosHelper
include ActsAsTaggableOn::TagsHelper
end
Finally, as per the gem's documentation, I've added the following code to my controller:
class VideosController < ApplicationController
def tag_cloud
#tags = Video.tag_counts_on(:tags)
end
end
So, what code should I be adding to the index page of my view? I tried the following, again as per the documentation:
<% tag_cloud(#tags, %w(css1 css2 css3 css4)) do |tag, css_class| %>
<%= link_to tag.name, { :action => :tag, :id => tag.name }, :class => css_class %>
<% end %>
But this returns the following error when I go to the Video index page:
undefined method `empty?' for nil:NilClass
As I say, I'm obviously missing something simple, but I'm completely new to Rails (and Ruby) so I'm still finding my feet.
OK, after hacking about a bit, I think I've found a solution, in case anyone else wondering how to do this happens to stumble across this question.
However, please be aware that I am very much a beginner at RoR, so this is probably not the best solution - if I'm doing anything wrong, or if you have a better solution, feel free to let me know!
Add this code in your view to display the list of tags for a particular model in order:
#tags = ActsAsTaggableOn::Tag.all(:order=>'name')
<% if #tags.count > 0 %>
<ul>
<% #tags.each do |tag| %>
<li><%= link_to tag.name, tagged_url(:tag => tag.name) %></li>
<% end %>
</ul>
<% else %>
<p>There are no tags on the system.</p>
<% end %>
This results in a very basic display and, due to my inexperience I advise using this approach with caution - I'm sure it's not the best, or even the "safest", method, so beware!

ransack search form in header partial: No Ransack::Search object was provided to search_form_for

First of all, I'm new to RoR, so the answer may be obvious, in which case I apologize. I've looked around and haven't found anything that helps.
I'm trying to have a search form at the header of every web page on my app that will search through the names of all my "buckets". Here is the relevant code:
In app/views/layouts/_header.html.erb (within a nav bar):
<% search_form_for #q do |f| %>
<%= f.label :name_cont %>
<%= f.text_field :name_cont %>
<%= f.submit %>
<% end %>
In app/controllers/buckets_controller.rb:
def index
unless params[:q].blank?
#q = Bucket.search(params[:q])
#buckets = #q.result.paginate(:page => params[:page])
else
#buckets = Bucket.find(:all, :limit => 5).paginate(:page => params[:page])
end
end
I understand the last part isn't that great: what I'm trying to do is if I'm just accessing the bucket index page (not by searching), i display the 5 most recently created buckets. When I search for something in the header form, I access the index page but only show the buckets that hit the search. (would a better way to handle it to have a search page separate from my index page?)
I found this issue which is pretty much identical, but I still don't see how I handle #q if every page is going to have the form on it--surely I don't have to alter every controller's every action?
Sorry in advance for any frustration my noobishness my cause you!
As others have said, you need to utilize the ApplicationController's before_filter. Though ernie himself seems not to recommend this, the implementation is simple.
First, use the advanced Ransack options to set your path for your search thusly
#config/routes.rb
resources :buckets do
collection do
match 'search' => 'buckets#search', via: [:get, :post], as: :search
end
end
Second, update your BucketsController to include the following custom action:
#controllers/buckets_controller.rb
def search
index
render :index
end
Nothing yet out of the ordinary. If you currently try to search you will get the error from your original question. Your definition of the variable q is correctly implemented, but you will have to move it to the ApplicationController like so:
#controllers/application_controller.rb
before_filter :set_global_search_variable
def set_global_search_variable
#q = Bucket.search(params[:q])
end
Finally, update your search form to pass in the correct search options
#layouts/_header.html.erb
<% search_form_for #q, url: search_buckets_path, html: { method: :post } do |f| %>
<%= f.label :name_cont %>
<%= f.text_field :name_cont %>
<%= f.submit %>
<% end %>
No, you do not need to edit all your controllers.
You can use ApplicationController for all your "common" controller needs. Read up on it in the guides http://guides.rubyonrails.org/action_controller_overview.html and the API docs http://api.rubyonrails.org/classes/ActionController/Base.html
The key here is, when you generated your new rails app, you'll notice it created the file .../app/controllers/action_controller.rb and that class derives from ActionController::Base. Then, if you again use the rails generator to create a controller for your app, you'll notice your new controller class derives from ApplicationController (not ::Base). That means that the application_controller.rb is the parent controller class for your app. That means everything in it is available to all your app controllers. It's easy to abuse, so be judicious.
Looks like this is not possible. This is a comment from Ernie the gem author.
You'd have to handle the Ransack-required stuff in a before_filter or
(ick) in the view partial itself. If you're putting a search field on
every single part of the site, I'd recommend you strongly consider
whether ransack is the right tool for the job, as well. You might want
some sort of inverted index search setup like sphinx, solr, etc.
https://github.com/ernie/ransack/issues/3

Using Websolr with Heroku to do full text search

Hi I am having trouble trying to figure out how to implement a search form globally across my application. I have a series of posts that need to be searchable by users that are signed in and not signed in. I have added this code in my post model:
searchable do
text :content, :default_boost => 2
text :body, :default_boost => 1.5
end
However, I do not know where to go from there to create a search field across all pages and make it show the results I need. I am new to rails and would be happy to post more information if someone is willing to help me out.
First, you should add your search field like explained in this railscast: http://railscasts.com/episodes/37-simple-search-form
Since your search isn't specific to a particular model, use a generic controller name instead of ProjectsController though.
Then, you should replace the ActiveRecord finder by the use of the Sunspot DSL.
Here is an sample code to help get you started:
page = #page = params[:page] && params[:page].to_i || 1
#search = Sunspot.search(Realty) do # search_ids
per_page = params[:per_page] && params[:per_page].to_i || 10
# not adapted to your case
with(:equipments).all_of params['equip'].split(' ') if params['equip']
case params[:sort]
when "average_rating"
order_by :average_rating, :desc
when "type"
order_by :type, :asc
end
paginate :page => page, :per_page => per_page
# other criteria...
end
In your view, you can then iterate through #search.results
<%= will_paginate #search.results %>
<% #search.results.each do |hit| %>
<%# 'path' contains the stored polymorphic_path of each model object #%>
<% link_to hit.stored('path') do %>
<p><%= hit.stored('content') %></p>
<% end %>
<% end %>
Last, using WebSolR instead of a standard SolR server is quite simple, you can follow the setup instructions at https://github.com/onemorecloud/websolr-rails.
Edit:
As Nick commented, you should totally go to http://docs.heroku.com/websolr.
Thanks Nick !

Rails category navigation with tags

I want to create a navigation system for my web application where there are several main categories for articles and several sub tags for each category.
For example, in category "Writing" there might be subcategories like "Essays," "Poetry" and "Fiction."
Each article would have a list of tags. If someone clicks the tag "Italy" on the fiction category show page I wouldn't want articles with the same tag of "Italy" from other categories like Essays or Poetry to appear on that page.
When a tag is clicked, the show page would be a category show page with a tag filter for all the articles with that particular tag in that category. It would not be a Tag show page.
Also, I'd like a sidebar with related tags. These would be tags that are in the same articles as those displayed on the category show page in descending order. I got related tags to work on the Tag page itself but not with the category constraint.
So far, I can get category pages and tag pages to work seperately, but I'd like to integrate the two.
I'm currently using Acts as Taggable on Steroids for the Tags and Acts as Tree for the categories.
Any ideas?
I think you are mixing two concepts here, a least in the question, perhaps not in your application. You have a category list modeled with acts_as_tree. That supports subcategories. The tags should not belong to subcategories. You want to filter by tag and category, more or less. You should be able to write simple queries that return lists of articles, such as:
#articles = Article.find(:all,
:include => "categories"
:conditions => ["(category_id = ? OR categories.parent_id = ?) AND tag_id = ?",
category_id, category_id, tag_id])
One more complex approach that I have used to do this is by doing faceted navigation with Solr. It supports combining these filters with full text search.
Matt, thanks a lot! This seems a lot simpler than what I had originally thought of.
However, my goal was to include subcategories in the navigation. The idea is that the parent categories would have everything in the subcategories and that if you clicked the sub categories it would narrow it down. I think that I'll check out Solr thought and see if it makes my code simpler.
After a bit of trial and error I got my code to work. It seems messy though. Any suggestions on simplification?
BTW some of these methods are defined through AATOS
In categories view#show:
<% for article in #articles %>
<%= link_to article.name, article %>
<% unless article.tag_list.empty? %>
<p class="tags">
Tags:
<%= render :partial => article.tags %>
</p>
<% end %>
And for the tag/navigation sidebar I have:
<% tag_cloud #tags, %w(css1 css2 css3 css4) do |tag, css_class| %>
<%= link_to tag, category_path(:filter => tag.name), :class => css_class %>
<% end %>
The category model, (not ideal but it works)
class Category < ActiveRecord::Base
acts_as_tree
has_many :articles
has_many :child_articles, :through => :children, :source => :articles
def grand_articles
children.map {|c| c.child_articles}.flatten.uniq
end
def to_param
name
end
def family
child_articles + articles + grand_articles
end
end
Finally the ugly controller code:
class CategoriesController < ApplicationController
def show
#category = Category.find_by_name(params[:id])
if (params[:filter]).nil?
#articles = #category.family
else
#articles = Article.find_tagged_with(params[:filter],
:conditions => ["articles.id IN (?)", #category.family])
end
#tags =
if (params[:filter]).nil?
Article.tag_counts :conditions => ["articles.id IN (?)", #category.family]
else
Article.find_related_tags(params[:filter],
:conditions => ["tags.id IN (?)", #category.family.map {|a| a.tags}.flatten.uniq])
end
It works but again it's ugly and brittle code. I'm sure I've committed some terrible sins here. Any suggestions for cleaning it up is appreciated.

Resources