Rails: Next/Previous button for images in database - ruby-on-rails

I have images where you can click next and previous, but I'm having trouble because when I'm viewing the last image in my database (that is associated with user_id), it show's routing error with :id => nil.
This is happening because there's no more data after the image for that user. So how can I set it will rotate their images even if they get to their last image in database (if next), and vice versa, if previous.
This is in my model
def self.s_prev(img)
first(:conditions => ["created_at < ?", img.created_at], :order => "created_at desc")
end
def self.s_next(img)
first(:conditions => ["created_at > ?", img.created_at], :order => "created_at asc")
end
These is my links
<%= link_to "Previous", user_image_path(#image.user_id, #user.images.s_prev(#image)) if user_image_path(#image.user_id, #user.styles.s_prev(#image)) %>
<%= link_to "Next", user_image_path(#image.user_id, #user.images.s_next(#image)) if user_image_path(#image.user_id, #user.images.s_next(#image)) %>
Apparently the if statements don't help me, for some reason
Lets say there's these database id for images table:
id user_id
1 14
2 14
3 14
4 15
So say if I'm on this page: localhost:3000/users/14/images/2
The page will show, and the links will show, but when I click on next, I'll get an error because there is no user_id => 15 and id => 4, where the next button is trying to get localhost:3000/users/14/images/4 for the page in ...users/14/images/3
So how do I avoid this issue and only display next/previous links associated with user?

def self.s_prev(img)
ordered = scoped.order("created_at desc")
ordered.first(:conditions => ["created_at < ?", img.created_at]) || ordered.first
end
def self.s_next(img)
ordered = scoped.order("created_at asc")
ordered.first(:conditions => ["created_at > ?", img.created_at]) || ordered.first
end

Related

Can't sort table in rails

I'm having trouble sorting a single-column table in Rails. Each row represents a single object (an article) and contains all of its attributes (name, content, created_at, user, etc.). The search function works fine (Article.where) but I can't seem to sort the table by any attributes, i.e. Article.order('attribute'). The default, which I can't change, is created_at desc. Am I overlooking something?
Here is my controller:
def index
#title="Home"
if params[:search]
#search=params[:search]
#articles=Article.where('name LIKE ? OR category LIKE ?', "%#{params[:search]}%", "%#{params[:search]}%").paginate(:per_page => 15, :page => params[:page])
else
#articles=Article.order('name').paginate(:per_page => 15, :page => params[:page])
end
end
And view:
<table>
<%= render #articles%>
</table>
<%= will_paginate #articles, :previous_label => "Prev", :next_label => "Next" %>
Use reorder to override any default ordering.
Article.reorder('name').paginate(:per_page => 15, :page => params[:page])
I recommend my gem simple-search for these problems. It may be too simple, but worth a shot.

Rails Craiglist like list of items

So I have a state model and city model associated with has_many and belongs_to. I want to display a page with each state with its associated cities underneath.
I created a page controller and page called "Locations" and manually entered in
<%= link_to "Allentown", allentown_path %>
which then takes you to the allentown page.
On the allentown page I filtered the listings by adding this code to the pages controller
def allentown
#title = "Allentown Listings"
#tattoo_briefs = TattooBrief.where( :city_id => 1 ).find(:all, :order => "id DESC" )
end
I know this isn't DRY. Also can get very cumberson if I have 200 cities. Is there a better way?
You need to add a resource to your routes:
routes.rb
resources :city
That essentially gives you all the RESTful actions for the City model. Then, in your controller, use the show action to..wait for it..show your city page
cities_controller.rb
def show
#city = City.find(params[:id])
#title = "#{#city.name} Listings"
#tattoo_briefs = TattooBrief.where( :city_id => params[:id] ).find(:all, :order => "id DESC" )
end
you can still modify this by studying more on routes and controllers from the rails api. With added knowledge, you can get to allentown by modifying your route to use the city name instead of the id.

Next and Previous records

In my show.html.erb file I have two links for next and previous like so
<%= link_to("Previous Post", #question.previous) if #question.previous %>
<%= link_to("Next Post", #question.next) if #question.next %>
.previous and .next are methods in my model which finds either the current id + 1 or - 1
like so in my model
def previous
Question.find_by_id(id - 1, :select => 'id')
end
def next
Question.find_by_id(id + 1, :select => 'id')
end
But rather than incrementing and decrementing blindly as I might have deleted a "Question" so i'll get an error, could I find the next/previous "Question" from the scope of the query the show action was accessed from?
Have you tried something like this?
def previous
Question.limit(1).order("id DESC").where("id < ?", id)
end
def next
Question.limit(1).order("id DESC").where("id > ?", id)
end
Try Act as Ordered.
https://github.com/imedo/acts_as_ordered

Ruby/Rails - Group By performance

I have a collection of Episodes which is connected to a Season and a Show.
I need to display them as such:
Show title
....Season number 1
........Episode name
........Episode name
....Season number 2
........Episode name
........Episode name
My controller:
def index
#show_ids = Following.find_all_by_user_id(current_user.id).collect(&:show_id)
#seen_ids = SeenEpisode.find_all_by_user_id(current_user.id).collect(&:episode_id)
if #seen_ids.any?
#episodes = Episode.find(:all, :conditions => ["show_id IN (?) AND episodes.id NOT IN (?)", #show_ids, #seen_ids], :joins => [:show, :season])
else
#episodes = Episode.find(:all, :conditions => ["show_id IN (?)", #show_ids], :joins => [:show, :season])
end
end
My view:
<ul>
<% #episodes.group_by(&:show).each do |show, episodes| %>
<li><h2><%= show.name %></h2></li>
<% episodes.group_by(&:season).each do |season, episodes| %>
<li><strong><%= season.number %></strong></li>
<% episodes.each do |episode| %>
<li><%= episode.name %></li>
<% end %>
<% end %>
<% end %>
</ul>
This works fine, although I know this is not a good method, and the performance is SHIT (like 10 seconds for about 150 records). How can I have a grouping like this with good performance?
Also, how can I refactor this?
if #seen_ids.any?
#episodes = Episode.find(:all, :conditions => ["show_id IN (?) AND episodes.id NOT IN (?)", #show_ids, #seen_ids], :joins => [:show, :season])
else
#episodes = Episode.find(:all, :conditions => ["show_id IN (?)", #show_ids], :joins => [:show, :season])
end
First, make sure your database has indexes on any foreign key columns you're querying against (I generally index anything_id as a matter of course:
add_index :episodes, :show_id
add_index :followings, :user_id
To clean up your finds, try something like this (from your updated post):
#episodes = Episode.scoped(
:conditions => ["show_id IN (?)", #show_ids],
:include => :show )
if #seen_ids.present?
#episodes = #episodes.scoped(
:conditions => "seen_episodes.show_id IS NULL",
:joins => :seen_episodes )
end
The above assumes you're using Rails 2 (since you were using the .find(:all) syntax...) but you can clean that up further by using .where, etc. instead of .scoped if you're on Rails 3.
using the "NOT IN" clause is generally slow. Instead left join on the SeenEpisode table and add a condition where SeenEpisode is NULL
Episode.find(:all, :joins => "LEFT JOIN SeenEpisode ON SeenEpisode.show_id = Episode.show_id", :conditions => "SeenEpisode.show_id IS NULL")
Note that I omitted some of the clauses for clarity. What this does is keep all records from Episode and add in columns from SeenEpisode that match. The condition then takes out those matching records.
I noticed that my db got queried Select * from shows Where Id = 100 for each record in the loop (show.name). I'm guessing the join did not work because of ambiguous named columns (episodes.name and shows.name)
This is what I ended up with.
query:
#episodes = Episode.find(:all, :select => "episodes.*, shows.name AS show_name", :conditions => ["show_id IN (?) AND seen_episodes.episode_id IS NULL", #show_ids], :joins => "INNER JOIN shows ON shows.id = episodes.show_id LEFT JOIN seen_episodes ON seen_episodes.episode_id = episodes.id")
view:
<ul>
<% #episodes.group_by(&:show_name).each do |show_name, episodes| %>
<li><h2><%= show_name %></h2></li>
<% episodes.group_by(&:season_number).each do |season_number, episodes| %>
<li><strong><%= season_number %></strong></li>
<% episodes.each do |episode| %>
<li><%= episode.name %></li>
<% end %>
<% end %>
<% end %>
</ul>
Also, I already had the season_number in a "cache column" on each episode.
I think this is OK. The query is not very pretty, but at least I like the result:
Completed in 109ms (View: 31, DB: 15)

Searching in Ruby on Rails - How do I search on each word entered and not the exact string?

I have built a blog application w/ ruby on rails and I am trying to implement a search feature. The blog application allows for users to tag posts. The tags are created in their own table and belong_to :post. When a tag is created, so is a record in the tag table where the name of the tag is tag_name and associated by post_id. Tags are strings.
I am trying to allow a user to search for any word tag_name in any order. Here is what I mean. Lets say a particular post has a tag that is 'ruby code controller'. In my current search feature, that tag will be found if the user searches for 'ruby', 'ruby code', or 'ruby code controller'. It will not be found if the user types in 'ruby controller'.
Essentially what I am saying is that I would like each word entered in the search to be searched for, not necessarily the 'string' that is entered into the search.
I have been experimenting with providing multiple textfields to allow the user to type in multiple words, and also have been playing around with the code below, but can't seem to accomplish the above. I am new to ruby and rails so sorry if this is an obvious question and prior to installing a gem or plugin I thought I would check to see if there was a simple fix. Here is my code:
View: /views/tags/index.html.erb
<% form_tag tags_path, :method => 'get' do %>
<p>
<%= text_field_tag :search, params[:search], :class => "textfield-search" %>
<%= submit_tag "Search", :name => nil, :class => "search-button" %>
</p>
<% end %>
TagsController
def index
#tags = Tag.search(params[:search]).paginate :page => params[:page], :per_page => 5
#tagsearch = Tag.search(params[:search])
#tag_counts = Tag.count(:group => :tag_name,
:order => 'count_all DESC', :limit => 100)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #tags }
end
end
Tag Model
class Tag < ActiveRecord::Base
belongs_to :post
validates_length_of :tag_name, :maximum=>42
validates_presence_of :tag_name
def self.search(search)
if search
find(:all, :order => "created_at DESC", :conditions => ['tag_name LIKE ?', "%#{search}%"])
else
find(:all, :order => "created_at DESC")
end
end
end
If I read your problem correctly, you want to return a row if the tag names for the row matches one of the words passed in the query string.
You can rewrite your search method as follows:
def self.search(search)
all :conditions => (search ? { :tag_name => search.split} : [])
end
If you need partial matching then do the following:
def self.search(str)
return [] if str.blank?
cond_text = str.split.map{|w| "tag_name LIKE ? "}.join(" OR ")
cond_values = str.split.map{|w| "%#{w}%"}
all(:conditions => (str ? [cond_text, *cond_values] : []))
end
Edit 1
If you want pass multiple search strings then:
def self.search(*args)
return [] if args.blank?
cond_text, cond_values = [], []
args.each do |str|
next if str.blank?
cond_text << "( %s )" % str.split.map{|w| "tag_name LIKE ? "}.join(" OR ")
cond_values.concat(str.split.map{|w| "%#{w}%"})
end
all :conditions => [cond_text.join(" AND "), *cond_values]
end
Now you can make calls such as:
Tag.search("Ruby On Rails")
Tag.search("Ruby On Rails", "Houston")
Tag.search("Ruby On Rails", "Houston", "TX")
Tag.search("Ruby On Rails", "Houston", "TX", "Blah")
Tag.search("Ruby On Rails", "Houston", "TX", "Blah", ....) # n parameters
Caveat:
The wild card LIKE searches are not very efficient(as they don't use the index). You should consider using Sphinx (via ThinkingSphinx) OR Solr(via SunSpot) if you have lot of data.
You can try to set up ferret, or if you are really bend on just using rails, try this:
# Break the search string into words
words = params[:search].blank? ? [] : params[:search].split(' ')
conditions = [[]] # Why this way? You'll know soon
words.each do |word|
conditions[0] << ["tag_name LIKE ?"]
conditions << "%#{word}%"
end
conditions[0] = conditions.first.join(" OR ") # Converts condition string to include " OR " easily ;-)
# Proceed to find using `:conditions => conditions` in your find
hope this helps =)
Sounds like you need a full text search. The best search integration right now is with Sphinx and the Thinking_Sphinx plugin. I have used it on several projects and it's super easy to setup.
You do need to install sphinx on your host so if you are using a shared host that could present some issues.
You could also use full text search in a MyISAM MySQL database, but performance on that is pretty poor.
Once you have your sphinx installed you just put what you want to index in your model and call model.search. The results will be a list of model objects. It supports will_paginate as well.
I'd suggest looking at Searchlogic if you don't want to use a separate fulltext search engine (Ferret, Sphinx, etc). It makes simple searches extremely easy, although you may not want to use it in a public facing area without lots of testing.
Also check out the Railscast on it: http://railscasts.com/episodes/176-searchlogic
1.You can do some coding in your controller post as such:-
<pre>
def show
#post = Post.find(params[:id])
#tag_counts = Tag.count(:group => :name, :order => 'updated_at DESC', :limit => 10)
respond_to do |format|
format.html # show.html.erb
format.json { render json: #post }
end
end
</pre>
2.Now make some changes in your view file:-
<pre>
<b>Tags:</b>
<%= join_tags(#post) %>
<%unless #tag_counts.nil?%>
<% #tag_counts.each do |tag_name, tag_count| %>
<tr><td><%= link_to(tag_name, posts_path(:name => tag_name)) %></td>
<td>(<%=tag_count%>)</td>
</tr><% end %>
<%end%>
</pre>
3. And one important thing is that there should be many to many relationship between tags and post.

Resources