Sunday, October 22, 2017

Rails 5 previous or next post ONLY from certain tag

Leave a Comment

I have a resource named posts, of which there are many. However, each post can have multiple tags. I want users to be able to go to the previous post and next post, ONLY from the tag that was selected. I have it working for all posts from the database in previous next, but when I click on a tag and it shows all the tags, prev/next doesn't adhere to what the tag is.

If I visit the url in association with the code defined in routes.rb, get 'tags/:tag', to: 'posts#index', as: :tag, it'll list all the tags in an index. I don't want this, I want a user to be able to click previous or next and only do so on posts that are associated with a tag.

Note: I am using the friendly_id gem

controllers/posts_controller.rb

  def index     @posts = Post.all      if params[:tag]       @posts = Post.tagged_with(params[:tag])     else       @posts = Post.all     end    end 

models/post.rb

# tags    acts_as_taggable # Alias for acts_as_taggable_on :tags  def next     Post.where("id > ?", id).order(id: :asc).limit(1).first end  def prev      Post.where("id < ?", id).order(id: :desc).limit(1).first end 

show.html.erb

<%= link_to "← Previous Question", @post.prev, :class => 'button previous-question' %>  <%= link_to "Next Question →", @post.next, :class => 'button next-question' %> 

routes.rb

 # TAGS   get 'tags/:tag', to: 'posts#index', as: :tag 

4 Answers

Answers 1

I think you are going to have to pass around that tag parameter (although you should probably make it a helper method)

models/post.rb

def next tag   Post.where("id > ?", id).tagged_with(tag).order(id: :asc).limit(1).first end  def prev tag   Post.where("id < ?", id).tagged_with(tag).order(id: :desc).limit(1).first end 

show

<%= link_to "← Previous Question", post_path(@post.prev(current_tag).id, tag: current_tag), :class => 'button previous-question' %>  <%= link_to "Next Question →", post_path(@post.next(current_tag).id, tag: current_tag), :class => 'button next-question' %> 

controllers/posts_controller.rb

class PostsController < ApplicationController   helper_method :current_tag    #def show   #def index    private    def current_tag     params[:tag]   end end 

Answers 2

You can put this in your controller then, Post.where(["id < ?", id]).last for previous and Post.where(["id > ?", id]).first this for next.

What you are trying to do is the job of the controller. You can extend those based on your sorting.

I also found this gem. Would be much better for you to use.

Answers 3

Here is updated Sean's answer. It should work now. The problem appears when prev or next methods returning nil

models/post.rb

def next tag   result = Post.where("id > ?", id).tagged_with(tag).order(id: :asc).limit(1).first    result || very_first(tag) end  def prev tag   result = Post.where("id < ?", id).tagged_with(tag).order(id: :desc).limit(1).first    result || very_last(tag) end  def very_first tag   Post.tagged_with(tag).order(id: :asc).limit(1).first end  def very_last tag   Post.tagged_with(tag).order(id: :asc).limit(1).last end 

show

<% if @post.prev(current_tag) %>   <%= link_to "← Previous Question", post_path(@post.prev(current_tag).id, tag: current_tag), :class => 'button previous-question' %> <% end %>  <% if @post.next(current_tag) %>   <%= link_to "Next Question →", post_path(@post.next(current_tag).id, tag: current_tag), :class => 'button next-question' %> <% end %> 

controllers/posts_controller.rb

class PostsController < ApplicationController   helper_method :current_tag    #def show   #def index    private    def current_tag     params[:tag]   end end 

P.S. sorry Sean, I can't comment on your answer, so I just copied and fixed it

Answers 4

You can also take nested resources approach and change your routes like this:

resource :tag do   resource :post end 

It should give you routes structure so that /tags/:tag_id/posts will be pointing to all the posts for a given tag, and /tags/:tag_id/posts/:id will be pointing to exact post (or question?) tagged with that tag.

Then in posts controller, you should add before_filter :set_tag like this

before_filer :set_tag  def set_tag   @tag = Tag.find(params[:tag_id]) end 

index action would look like this

 def index     @posts = @tag.posts  end 

and will always show posts for that tag.

In the show action of posts controller you can get next and previous post links just like in the answers above.

You should also change all the post url helpers used in views to include current tag, e.g. posts_path -> tag_posts_path(@tag) where tag is current tag that was set in before_filter.

I highly recommend you not putting all those methods on model and create a presenter object for a post, e.g.

class PostPresenter   attr_reader :post   alias_method :current, :post    def initialize(post)     @post = post     @repo = post.class   end    def next     @repo.where('id > ?', post.id).first   end    def previous     @repo.where('id < ?', post.id).first   end end 

and

@presenter = PostPresenter.new(@post)

and a next post's link

<%= link_to "Next Question →",        tag_post_path(@presenter.next),        class: 'button next-question' if @presenter.next.present? %> 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment