mazing
mazing

Reputation: 743

Rails 5 previous or next post ONLY from certain tag

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

Upvotes: 10

Views: 553

Answers (4)

Bogdan Agafonov
Bogdan Agafonov

Reputation: 157

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? %>

Upvotes: 3

Peter Balaban
Peter Balaban

Reputation: 643

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

Upvotes: 4

Sean
Sean

Reputation: 983

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

Upvotes: 7

arjun
arjun

Reputation: 1614

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.

Upvotes: 4

Related Questions