search engine!
This commit is contained in:
parent
b01f9e9027
commit
abb8392c16
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -9,6 +9,7 @@
|
|||
|
||||
# Ignore the default SQLite database.
|
||||
/db/*.sqlite3
|
||||
/db/sphinx
|
||||
|
||||
# Ignore all logfiles and tempfiles.
|
||||
/log/*.log
|
||||
|
|
4
Gemfile
4
Gemfile
|
@ -1,6 +1,6 @@
|
|||
source "https://rubygems.org"
|
||||
|
||||
gem "rails", "3.2.2"
|
||||
gem "rails", "3.2.6"
|
||||
|
||||
# Bundle edge Rails instead:
|
||||
# gem "rails", :git => "git://github.com/rails/rails.git"
|
||||
|
@ -27,6 +27,8 @@ gem "htmlentities"
|
|||
|
||||
gem "rdiscount"
|
||||
|
||||
gem "thinking-sphinx", "2.0.12"
|
||||
|
||||
group :test, :development do
|
||||
gem "rspec-rails", "~> 2.6"
|
||||
gem "machinist"
|
||||
|
|
108
Gemfile.lock
108
Gemfile.lock
|
@ -1,31 +1,31 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actionmailer (3.2.2)
|
||||
actionpack (= 3.2.2)
|
||||
mail (~> 2.4.0)
|
||||
actionpack (3.2.2)
|
||||
activemodel (= 3.2.2)
|
||||
activesupport (= 3.2.2)
|
||||
actionmailer (3.2.6)
|
||||
actionpack (= 3.2.6)
|
||||
mail (~> 2.4.4)
|
||||
actionpack (3.2.6)
|
||||
activemodel (= 3.2.6)
|
||||
activesupport (= 3.2.6)
|
||||
builder (~> 3.0.0)
|
||||
erubis (~> 2.7.0)
|
||||
journey (~> 1.0.1)
|
||||
rack (~> 1.4.0)
|
||||
rack-cache (~> 1.1)
|
||||
rack-cache (~> 1.2)
|
||||
rack-test (~> 0.6.1)
|
||||
sprockets (~> 2.1.2)
|
||||
activemodel (3.2.2)
|
||||
activesupport (= 3.2.2)
|
||||
sprockets (~> 2.1.3)
|
||||
activemodel (3.2.6)
|
||||
activesupport (= 3.2.6)
|
||||
builder (~> 3.0.0)
|
||||
activerecord (3.2.2)
|
||||
activemodel (= 3.2.2)
|
||||
activesupport (= 3.2.2)
|
||||
activerecord (3.2.6)
|
||||
activemodel (= 3.2.6)
|
||||
activesupport (= 3.2.6)
|
||||
arel (~> 3.0.2)
|
||||
tzinfo (~> 0.3.29)
|
||||
activeresource (3.2.2)
|
||||
activemodel (= 3.2.2)
|
||||
activesupport (= 3.2.2)
|
||||
activesupport (3.2.2)
|
||||
activeresource (3.2.6)
|
||||
activemodel (= 3.2.6)
|
||||
activesupport (= 3.2.6)
|
||||
activesupport (3.2.6)
|
||||
i18n (~> 0.6)
|
||||
multi_json (~> 1.0)
|
||||
arel (3.0.2)
|
||||
|
@ -36,26 +36,26 @@ GEM
|
|||
erubis (2.7.0)
|
||||
exception_notification (2.6.1)
|
||||
actionmailer (>= 3.0.4)
|
||||
execjs (1.3.2)
|
||||
execjs (1.4.0)
|
||||
multi_json (~> 1.0)
|
||||
hike (1.2.1)
|
||||
htmlentities (4.3.1)
|
||||
i18n (0.6.0)
|
||||
journey (1.0.3)
|
||||
jquery-rails (2.0.1)
|
||||
journey (1.0.4)
|
||||
jquery-rails (2.0.2)
|
||||
railties (>= 3.2.0, < 5.0)
|
||||
thor (~> 0.14)
|
||||
json (1.6.5)
|
||||
json (1.7.3)
|
||||
kgio (2.7.4)
|
||||
machinist (2.0)
|
||||
mail (2.4.4)
|
||||
i18n (>= 0.4.0)
|
||||
mime-types (~> 1.16)
|
||||
treetop (~> 1.4.8)
|
||||
mime-types (1.18)
|
||||
multi_json (1.1.0)
|
||||
mime-types (1.19)
|
||||
multi_json (1.3.6)
|
||||
mysql2 (0.3.11)
|
||||
nokogiri (1.5.4)
|
||||
nokogiri (1.5.5)
|
||||
polyglot (0.3.3)
|
||||
rack (1.4.1)
|
||||
rack-cache (1.2)
|
||||
|
@ -64,53 +64,58 @@ GEM
|
|||
rack
|
||||
rack-test (0.6.1)
|
||||
rack (>= 1.0)
|
||||
rails (3.2.2)
|
||||
actionmailer (= 3.2.2)
|
||||
actionpack (= 3.2.2)
|
||||
activerecord (= 3.2.2)
|
||||
activeresource (= 3.2.2)
|
||||
activesupport (= 3.2.2)
|
||||
rails (3.2.6)
|
||||
actionmailer (= 3.2.6)
|
||||
actionpack (= 3.2.6)
|
||||
activerecord (= 3.2.6)
|
||||
activeresource (= 3.2.6)
|
||||
activesupport (= 3.2.6)
|
||||
bundler (~> 1.0)
|
||||
railties (= 3.2.2)
|
||||
railties (3.2.2)
|
||||
actionpack (= 3.2.2)
|
||||
activesupport (= 3.2.2)
|
||||
railties (= 3.2.6)
|
||||
railties (3.2.6)
|
||||
actionpack (= 3.2.6)
|
||||
activesupport (= 3.2.6)
|
||||
rack-ssl (~> 1.3.2)
|
||||
rake (>= 0.8.7)
|
||||
rdoc (~> 3.4)
|
||||
thor (~> 0.14.6)
|
||||
raindrops (0.9.0)
|
||||
thor (>= 0.14.6, < 2.0)
|
||||
raindrops (0.10.0)
|
||||
rake (0.9.2.2)
|
||||
rdiscount (1.6.8)
|
||||
rdoc (3.12)
|
||||
json (~> 1.4)
|
||||
rspec (2.9.0)
|
||||
rspec-core (~> 2.9.0)
|
||||
rspec-expectations (~> 2.9.0)
|
||||
rspec-mocks (~> 2.9.0)
|
||||
rspec-core (2.9.0)
|
||||
rspec-expectations (2.9.0)
|
||||
riddle (1.5.2)
|
||||
rspec (2.11.0)
|
||||
rspec-core (~> 2.11.0)
|
||||
rspec-expectations (~> 2.11.0)
|
||||
rspec-mocks (~> 2.11.0)
|
||||
rspec-core (2.11.0)
|
||||
rspec-expectations (2.11.1)
|
||||
diff-lcs (~> 1.1.3)
|
||||
rspec-mocks (2.9.0)
|
||||
rspec-rails (2.9.0)
|
||||
rspec-mocks (2.11.1)
|
||||
rspec-rails (2.11.0)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
rspec (~> 2.9.0)
|
||||
sprockets (2.1.2)
|
||||
rspec (~> 2.11.0)
|
||||
sprockets (2.1.3)
|
||||
hike (~> 1.2)
|
||||
rack (~> 1.0)
|
||||
tilt (~> 1.1, != 1.3.0)
|
||||
sqlite3 (1.3.6)
|
||||
thor (0.14.6)
|
||||
thinking-sphinx (2.0.12)
|
||||
activerecord (>= 3.0.3)
|
||||
builder (>= 2.1.2)
|
||||
riddle (>= 1.5.2)
|
||||
thor (0.15.4)
|
||||
tilt (1.3.3)
|
||||
treetop (1.4.10)
|
||||
polyglot
|
||||
polyglot (>= 0.3.1)
|
||||
tzinfo (0.3.32)
|
||||
uglifier (1.2.4)
|
||||
tzinfo (0.3.33)
|
||||
uglifier (1.2.6)
|
||||
execjs (>= 0.3.0)
|
||||
multi_json (>= 1.0.2)
|
||||
multi_json (~> 1.3)
|
||||
unicorn (4.3.1)
|
||||
kgio (~> 2.6)
|
||||
rack
|
||||
|
@ -128,9 +133,10 @@ DEPENDENCIES
|
|||
machinist
|
||||
mysql2
|
||||
nokogiri
|
||||
rails (= 3.2.2)
|
||||
rails (= 3.2.6)
|
||||
rdiscount
|
||||
rspec-rails (~> 2.6)
|
||||
sqlite3
|
||||
thinking-sphinx (= 2.0.12)
|
||||
uglifier
|
||||
unicorn
|
||||
|
|
|
@ -297,7 +297,8 @@ div#footer a {
|
|||
/* stories */
|
||||
|
||||
ol.stories,
|
||||
ol.comments {
|
||||
ol.comments,
|
||||
ol.search_results {
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
|
@ -321,6 +322,16 @@ ol.comments.preview {
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
ol.search_results {
|
||||
margin-left: 20px;
|
||||
margin-bottom: 0em;
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
ol.search_results li.story {
|
||||
padding-bottom: 0.75em;
|
||||
}
|
||||
|
||||
div.voters {
|
||||
float: left;
|
||||
margin-top: -4px;
|
||||
|
@ -371,13 +382,13 @@ li.downvoted div.voters a.downvoter {
|
|||
border-top-color: gray;
|
||||
}
|
||||
|
||||
ol.stories li.story,
|
||||
ol.comments li.comment {
|
||||
li.story,
|
||||
li.comment {
|
||||
clear: both;
|
||||
padding-top: 0.4em;
|
||||
padding-bottom: 0.4em;
|
||||
}
|
||||
ol.comments li.comment {
|
||||
li.comment {
|
||||
padding-top: 0.5em;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
@ -389,21 +400,21 @@ li div.details {
|
|||
padding-top: 0.1em;
|
||||
}
|
||||
|
||||
ol.comments li.negative {
|
||||
li.negative {
|
||||
opacity: 0.7;
|
||||
color: gray !important;
|
||||
}
|
||||
ol.comments li.negative_3 {
|
||||
li.negative_3 {
|
||||
opacity: 0.4;
|
||||
}
|
||||
ol.comments li.negative_5 {
|
||||
li.negative_5 {
|
||||
opacity: 0.3;
|
||||
}
|
||||
ol.comments li.negative_7 {
|
||||
li.negative_7 {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
ol.comments li.highlighted {
|
||||
li.comment.highlighted {
|
||||
background-color: #ffffbf;
|
||||
}
|
||||
|
||||
|
@ -417,7 +428,7 @@ li .link a {
|
|||
text-decoration: none;
|
||||
}
|
||||
|
||||
ol.stories a.tag {
|
||||
li.story a.tag {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
@ -436,7 +447,7 @@ li .byline {
|
|||
color: #888;
|
||||
font-size: 8.5pt;
|
||||
}
|
||||
ol.stories li .byline {
|
||||
li.story .byline {
|
||||
margin-top: 1px;
|
||||
}
|
||||
li .byline a {
|
||||
|
@ -461,7 +472,8 @@ div.story_content {
|
|||
margin-bottom: 3em;
|
||||
}
|
||||
|
||||
div.morelink {
|
||||
div.morelink,
|
||||
div.page_link_buttons {
|
||||
margin-top: 1.5em;
|
||||
margin-left: 40px;
|
||||
}
|
||||
|
@ -470,6 +482,24 @@ div.morelink a {
|
|||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
div.page_link_buttons {
|
||||
font-weight: bold;
|
||||
margin-top: 2em;
|
||||
}
|
||||
div.page_link_buttons a {
|
||||
color: #666;
|
||||
border: 1px solid #d0d0d0;
|
||||
background-color: #f3f3f3;
|
||||
padding: 0.25em 0.5em;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
div.page_link_buttons a.cur {
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
|
||||
div.story_text {
|
||||
margin-bottom: 1.5em;
|
||||
|
|
24
app/controllers/search_controller.rb
Normal file
24
app/controllers/search_controller.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
class SearchController < ApplicationController
|
||||
def index
|
||||
@title = "Search"
|
||||
@cur_url = "/search"
|
||||
|
||||
@search = Search.new
|
||||
|
||||
if params[:q].present?
|
||||
@search.q = params[:q]
|
||||
@search.what = params[:what]
|
||||
@search.order = params[:order]
|
||||
|
||||
if params[:page]
|
||||
@search.page = params[:page].to_i
|
||||
end
|
||||
|
||||
if @search.valid?
|
||||
@search.search_for_user!(@user)
|
||||
end
|
||||
end
|
||||
|
||||
render :action => "index"
|
||||
end
|
||||
end
|
|
@ -17,6 +17,16 @@ class Comment < ActiveRecord::Base
|
|||
|
||||
MAX_EDIT_MINS = 45
|
||||
|
||||
define_index do
|
||||
indexes comment
|
||||
indexes user.username, :as => :author
|
||||
|
||||
has "(upvotes - downvotes)", :as => :score, :type => :integer,
|
||||
:sortable => true
|
||||
|
||||
has created_at
|
||||
end
|
||||
|
||||
validate do
|
||||
self.comment.to_s.strip == "" &&
|
||||
errors.add(:comment, "cannot be blank.")
|
||||
|
|
88
app/models/search.rb
Normal file
88
app/models/search.rb
Normal file
|
@ -0,0 +1,88 @@
|
|||
class Search
|
||||
include ActiveModel::Validations
|
||||
include ActiveModel::Conversion
|
||||
include ActiveModel::AttributeMethods
|
||||
extend ActiveModel::Naming
|
||||
|
||||
attr_accessor :q, :what, :order
|
||||
attr_accessor :results, :page, :total_results, :per_page
|
||||
|
||||
validates_length_of :q, :minimum => 2
|
||||
|
||||
def initialize
|
||||
@q = ""
|
||||
@what = "all"
|
||||
@order = "relevance"
|
||||
|
||||
@page = 1
|
||||
@per_page = 20
|
||||
|
||||
@results = []
|
||||
@total_results = 0
|
||||
end
|
||||
|
||||
def persisted?
|
||||
false
|
||||
end
|
||||
|
||||
def to_url_params
|
||||
[ :q, :what, :order ].map{|p| "#{p}=#{CGI.escape(self.send(p))}"
|
||||
}.join("&")
|
||||
end
|
||||
|
||||
def search_for_user!(user)
|
||||
opts = {
|
||||
:match_mode => :extended,
|
||||
:rank_mode => :bm25,
|
||||
:page => @page,
|
||||
:per_page => @per_page,
|
||||
}
|
||||
|
||||
if order == "newest"
|
||||
opts[:order] = :created_at
|
||||
opts[:sort_mode] = :desc
|
||||
elsif order == "points"
|
||||
opts[:order] = :score
|
||||
opts[:sort_mode] = :desc
|
||||
end
|
||||
|
||||
opts[:classes] = []
|
||||
if what == "all"
|
||||
opts[:classes] = [ Story, Comment ]
|
||||
elsif what == "comments"
|
||||
opts[:classes] = [ Comment ]
|
||||
elsif what == "stories"
|
||||
opts[:classes] = [ Story ]
|
||||
end
|
||||
|
||||
opts[:include] = [ :story, :user ]
|
||||
|
||||
# go go gadget search
|
||||
@results = ThinkingSphinx.search @q, opts
|
||||
@total_results = @results.total_entries
|
||||
|
||||
# bind votes for both types
|
||||
|
||||
if opts[:classes].include?(Comment) && user
|
||||
votes = Vote.comment_votes_by_user_for_comment_ids_hash(user.id,
|
||||
@results.select{|r| r.class == Comment }.map{|c| c.id })
|
||||
|
||||
@results.each do |r|
|
||||
if r.class == Comment && votes[r.id]
|
||||
r.current_vote = votes[r.id]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if opts[:classes].include?(Story) && user
|
||||
votes = Vote.story_votes_by_user_for_story_ids_hash(user.id,
|
||||
@results.select{|r| r.class == Story }.map{|s| s.id })
|
||||
|
||||
@results.each do |r|
|
||||
if r.class == Story && votes[r.id]
|
||||
r.vote = votes[r.id][:vote]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -14,14 +14,32 @@ class Story < ActiveRecord::Base
|
|||
MAX_EDIT_MINS = 30
|
||||
|
||||
attr_accessor :_comment_count
|
||||
attr_accessor :vote, :story_type, :already_posted_story, :fetched_content
|
||||
attr_accessor :vote, :already_posted_story, :fetched_content
|
||||
attr_accessor :new_tags, :tags_to_add, :tags_to_delete
|
||||
|
||||
attr_accessible :title, :description, :story_type, :tags_a
|
||||
attr_accessible :title, :description, :tags_a
|
||||
|
||||
before_create :assign_short_id
|
||||
after_create :mark_submitter
|
||||
after_save :deal_with_tags
|
||||
|
||||
define_index do
|
||||
indexes url
|
||||
indexes title
|
||||
indexes description
|
||||
indexes user.username, :as => :author
|
||||
indexes tags(:tag), :as => :tags
|
||||
|
||||
has created_at, :sortable => true
|
||||
has hotness, is_moderated, is_expired
|
||||
has "(upvotes - downvotes)", :as => :score, :type => :integer,
|
||||
:sortable => true
|
||||
|
||||
set_property :field_weights => {
|
||||
:title => 10,
|
||||
:tags => 5,
|
||||
}
|
||||
end
|
||||
|
||||
validate do
|
||||
if self.url.present?
|
||||
|
|
|
@ -23,7 +23,8 @@ class Vote < ActiveRecord::Base
|
|||
attr_accessible nil
|
||||
|
||||
def self.votes_by_user_for_stories_hash(user, stories)
|
||||
votes = []
|
||||
votes = {}
|
||||
|
||||
Vote.where(:user_id => user, :story_id => stories,
|
||||
:comment_id => nil).each do |v|
|
||||
votes[v.story_id] = v.vote
|
||||
|
@ -42,6 +43,48 @@ class Vote < ActiveRecord::Base
|
|||
|
||||
votes
|
||||
end
|
||||
|
||||
def self.story_votes_by_user_for_story_ids_hash(user_id, story_ids)
|
||||
if !story_ids.any?
|
||||
return {}
|
||||
end
|
||||
|
||||
votes = {}
|
||||
|
||||
cond = [ "user_id = ? AND comment_id IS NULL AND story_id IN (", user_id ]
|
||||
story_ids.each_with_index do |s,x|
|
||||
cond.push s
|
||||
cond[0] += (x == 0 ? "" : ",") + "?"
|
||||
end
|
||||
cond[0] += ")"
|
||||
|
||||
Vote.find(:all, :conditions => cond).each do |v|
|
||||
votes[v.story_id] = { :vote => v.vote, :reason => v.reason }
|
||||
end
|
||||
|
||||
votes
|
||||
end
|
||||
|
||||
def self.comment_votes_by_user_for_comment_ids_hash(user_id, comment_ids)
|
||||
if !comment_ids.any?
|
||||
return {}
|
||||
end
|
||||
|
||||
votes = {}
|
||||
|
||||
cond = [ "user_id = ? AND comment_id IN (", user_id ]
|
||||
comment_ids.each_with_index do |c,x|
|
||||
cond.push c
|
||||
cond[0] += (x == 0 ? "" : ",") + "?"
|
||||
end
|
||||
cond[0] += ")"
|
||||
|
||||
Vote.find(:all, :conditions => cond).each do |v|
|
||||
votes[v.comment_id] = { :vote => v.vote, :reason => v.reason }
|
||||
end
|
||||
|
||||
votes
|
||||
end
|
||||
|
||||
def self.vote_thusly_on_story_or_comment_for_user_because(vote, story_id,
|
||||
comment_id, user_id, reason, update_counters = true)
|
||||
|
|
|
@ -54,7 +54,7 @@ class="comment <%= comment.current_vote ? (comment.current_vote[:vote] == 1 ?
|
|||
<% end %>
|
||||
|
||||
<% if defined?(show_story) && show_story %>
|
||||
| on
|
||||
| on:
|
||||
<a href="<%= story.comments_url %>"><%= story.title %></a>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -38,8 +38,8 @@
|
|||
<a href="/settings"><%= @user.username %> (<%= @user.karma %>)</a>
|
||||
|
||||
<%= link_to "Logout", { :controller => "login", :action => "logout" },
|
||||
{ :confirm => "Are you sure you want to logout?",
|
||||
"method" => "post" } %>
|
||||
:data => { :confirm => "Are you sure you want to logout?" },
|
||||
:method => "post" %>
|
||||
<% else %>
|
||||
<a href="/login">Login</a>
|
||||
<% end %>
|
||||
|
|
83
app/views/search/index.html.erb
Normal file
83
app/views/search/index.html.erb
Normal file
|
@ -0,0 +1,83 @@
|
|||
<div class="box">
|
||||
<div class="legend">
|
||||
Search
|
||||
</div>
|
||||
|
||||
<%= form_tag "/search", :method => :get do %>
|
||||
<div class="boxline">
|
||||
<%= text_field_tag "q", @search.q, :size => 40 %>
|
||||
<input type="submit" value="Search">
|
||||
</div>
|
||||
|
||||
<div class="boxline">
|
||||
<label class="required">Include:</label>
|
||||
|
||||
<%= radio_button_tag "what", "all", @search.what == "all" %>
|
||||
<label for="search_what_all" class="normal">All</label>
|
||||
|
||||
|
||||
|
||||
<%= radio_button_tag "what", "stories", @search.what == "stories" %>
|
||||
<label for="search_what_stories" class="normal">Stories</label>
|
||||
|
||||
|
||||
|
||||
<%= radio_button_tag "what", "comments", @search.what == "comments" %>
|
||||
<label for="search_what_comments" class="normal">Comments</label>
|
||||
|
||||
<br>
|
||||
|
||||
<label class="required">Order By:</label>
|
||||
|
||||
<%= radio_button_tag "order", "relevance", @search.order == "relevance" %>
|
||||
<label for="search_order_relevance" class="normal">Relevance</label>
|
||||
|
||||
|
||||
|
||||
<%= radio_button_tag "order", "newest", @search.order == "newest" %>
|
||||
<label for="search_order_newest" class="normal">Newest</label>
|
||||
|
||||
|
||||
|
||||
<%= radio_button_tag "order", "points", @search.order == "points" %>
|
||||
<label for="search_order_points" class="normal">Points</label>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if @search.results.any? %>
|
||||
<div class="box">
|
||||
<p>
|
||||
<div class="legend">
|
||||
<%= @search.total_results %> result<%= @search.total_results == 1 ? "" :
|
||||
"s" %> for "<%= @search.q %>"
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ol class="search_results">
|
||||
<% @search.results.each do |res| %>
|
||||
<% if res.class == Story %>
|
||||
<%= render :partial => "stories/listdetail",
|
||||
:locals => { :story => res } %>
|
||||
<% elsif res.class == Comment %>
|
||||
<%= render :partial => "comments/comment",
|
||||
:locals => { :comment => res, :story => res.story,
|
||||
:show_story => true, :hide_voters => true } %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ol>
|
||||
|
||||
<% if @search.total_results > @search.per_page %>
|
||||
<div class="page_link_buttons">
|
||||
Page:
|
||||
|
||||
<% (@search.total_results.to_f / @search.per_page.to_f).ceil.
|
||||
times do |p| %>
|
||||
<a href="/search?<%= raw(@search.to_url_params) %>&page=<%= p + 1
|
||||
%>" class="<%= @search.page == p + 1 ? "cur" : "" %>"><%= p + 1
|
||||
%></a>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
|
@ -26,6 +26,8 @@ Lobsters::Application.routes.draw do
|
|||
match "/t/:tag" => "home#tagged", :as => "tag"
|
||||
match "/t/:tag/page/:page" => "home#tagged"
|
||||
|
||||
get "/search" => "search#index"
|
||||
|
||||
resources :stories do
|
||||
post "upvote"
|
||||
post "downvote"
|
||||
|
|
3
config/sphinx.yml
Normal file
3
config/sphinx.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
production:
|
||||
address: 127.0.0.1
|
||||
port: 9313
|
Loading…
Reference in a new issue