Thibaud Clement
Thibaud Clement

Reputation: 6897

Rails 4: get next / previous record of an object that belongs to another object

In my Rails 4 app, I have a Post and a Calendar model: a calendar has_many posts and a post belong_to a calendar.

In the post show.html.erb view, located at /posts/:id, I want to allow users to navigate back and forth between the posts of the calendar to which the current post belongs to, with a "previous post" button and a "next post" button.

Here are my routes:

resources :calendars do
    resources :posts, shallow: true
  end
end

I know I will have something like that in my post show.html.erb view:

<% if @calendar.posts.count > 1 %>
  <%= link_to "< Previous", @previous_post %> | Post Preview | <%= link_to "Next >", @next_post %>
<% else %>
  Post Preview
<% end %>

So far, in my PostsController, I came up with:

def show
  @calendar = Calendar.find_by_id(@post.calendar_id)
  @posts = @calendar.posts
  @previous_post = @post.previous
  @next_post = @post.next
end

However, I am struggling to come up with the right definition of the previous and next methods (that you can see in the PostsController code above).

These methods must allow me to find — respectively — the post that is right before or right after the current post in @calendar.posts

How can I achieve that?

Upvotes: 0

Views: 2089

Answers (4)

Rich Seviora
Rich Seviora

Reputation: 1809

Your solution with next and previous commands relative to date is good, but doesn't work completely because you need to order the results chronologically as well. The where will filter the ones you don't want, but you need to make sure that the rest are in the order you desire.

So you'd have something like:

def next
  calendar.posts.where("time > ?", time).order(:time).first
end

def previous
  calendar.posts.where("time < ?", time).order(time: :desc).first
end

Edit: I'm assuming that time is a DateTime. If it is a Time ONLY without date information, you'll urgently want to change that to a DateTime field.

Upvotes: 4

Thibaud Clement
Thibaud Clement

Reputation: 6897

This is what I ended up doing:

post.rb

def next
  calendar.posts.where("id > ?", id).first
end

def previous
  calendar.posts.where("id < ?", id).last
end

posts_controller.rb

def show
  @calendar = Calendar.find_by_id(@post.calendar_id)
  @previous_post = @post.previous
  @next_post = @post.next
end

This is not an ideal solution, but it is working.

—————

UPDATE:

Because posts must be displayed in chronological order, I had to change the above code to:

#post.rb

    def next
      calendar.posts.where("time > ?", time).first
    end

    def previous
      calendar.posts.where("time < ?", time).last
    end

However, this is not working perfectly, as you can see on this gif:

enter image description here

It is almost as if the previous button still works based on post id and not time.

I did restart my server in case that was the problem, but it did not fix it.

Any idea how I can improve on this code?

—————

UPDATE 2: based on Richard Seviora's answer, I also tried this:

#post.rb

def next
    calendar.posts.where("date > ? AND time != ?", date, time).order(:time).first
  end

  def previous
    calendar.posts.where("date < ? AND time != ?", date, time).order(time: :desc).first
  end

Still not working as expected.

—————

Upvotes: 0

K M Rakibul Islam
K M Rakibul Islam

Reputation: 34328

Not a very ideal kind of solution, but in your case, should work and give you the previous and next posts of a @post from a @posts array.

Add get_next_previous_posts helper method and use it in your controller's show method:

  def show
    @calendar = Calendar.find_by_id(@post.calendar_id)
    @posts = @calendar.posts
    @previous_post, @next_post = get_next_and_previous_posts(@posts, @post)
  end

  private

  def get_next_and_previous_posts(posts, current_post)
    next_post = posts.detect { |p| p.id > current_post.id }
    prev_post = posts.reverse.detect { |p| p.id > current_post.id }
    [prev_post, next_post]
  end

Upvotes: 1

baron816
baron816

Reputation: 701

It sounds like you need some pagination. will_paginate or kaminari gems should do the trick (I prefer kaminari).

Upvotes: 0

Related Questions