Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails best way to get previous and next active record object

I need to get the previous and next active record objects with Rails. I did it, but don't know if it's the right way to do that.

What I've got:

Controller:

@product = Product.friendly.find(params[:id])

order_list = Product.select(:id).all.map(&:id)

current_position = order_list.index(@product.id)

@previous_product = @collection.products.find(order_list[current_position - 1]) if order_list[current_position - 1]
@next_product = @collection.products.find(order_list[current_position + 1]) if order_list[current_position + 1]

@previous_product ||= Product.last
@next_product ||= Product.first

product_model.rb

default_scope -> {order(:product_sub_group_id => :asc, :id => :asc)}

So, the problem here is that I need to go to my database and get all this ids to know who is the previous and the next.

Tried to use the gem order_query, but it did not work for me and I noted that it goes to the database and fetch all the records in that order, so, that's why I did the same but getting only the ids.

All the solutions that I found was with simple order querys. Order by id or something like a priority field.

like image 525
William Weckl Avatar asked Sep 04 '14 12:09

William Weckl


People also ask

What is the difference between find and Find_by in Rails?

The additional difference between find() and find_by() is that find could only be used to search by primary key (usually the 'id') while the find_by() requires and searches by attribute (either passed as hash like Employee. find_by(name: 'Mike') or using the Employee.

What is Active Record :: Base in Rails?

ActiveRecord::Base indicates that the ActiveRecord class or module has a static inner class called Base that you're extending. Edit: as Mike points out, in this case ActiveRecord is a module... ActiveRecord is defined as a module in Rails, github.com/rails/rails/tree/master/activerecord/lib/…

What does pluck return in Rails?

In Rails, pluck is a shortcut to select one or more attributes without loading the corresponding records just to filter out the selected attributes. It returns an Array of attribute values.

How does Callback work in Rails?

Callbacks are methods that get called at certain moments of an object's life cycle. With callbacks it is possible to write code that will run whenever an Active Record object is created, saved, updated, deleted, validated, or loaded from the database.


4 Answers

Write these methods in your Product model:

class Product

  def next
    self.class.where("id > ?", id).first
  end

  def previous
    self.class.where("id < ?", id).last
  end

end

Now you can do in your controller:

@product = Product.friendly.find(params[:id])

@previous_product = @product.next
@next_product = @product.previous

Please try it, but its not tested. Thanks

like image 177
Rails Guy Avatar answered Sep 30 '22 22:09

Rails Guy


I think it would be faster to do it with only two SQL requests, that only select two rows (and not the entire table). Considering that your default order is sorted by id (otherwise, force the sorting by id) :

@previous_product = Product.where('id < ?', params[:id]).last
@next_product = Product.where('id > ?', params[:id]).first

If the product is the last, then @next_product will be nil, and if it is the first, then, @previous_product will be nil.

like image 26
Jean-Théo Avatar answered Oct 04 '22 22:10

Jean-Théo


There's no easy out-of-the-box solution.

A little dirty, but working way is carefully sorting out what conditions are there for finding next and previous items. With id it's quite easy, since all ids are different, and Rails Guy's answer describes just that: in next for a known id pick a first entry with a larger id (if results are ordered by id, as per defaults). More than that - his answer hints to place next and previous into the model class. Do so.

If there are multiple order criteria, things get complicated. Say, we have a set of rows sorted by group parameter first (which can possibly have equal values on different rows) and then by id (which id different everywhere, guaranteed). Results are ordered by group and then by id (both ascending), so we can possibly encounter two situations of getting the next element, it's the first from the list that has elements, that (so many that):

  • have the same group and a larger id
  • have a larger group

Same with previous element: you need the last one from the list

  • have the same group and a smaller id
  • have a smaller group

Those fetch all next and previous entries respectively. If you need only one, use Rails' first and last (as suggested by Rails Guy) or limit(1) (and be wary of the asc/desc ordering).

like image 42
D-side Avatar answered Oct 04 '22 22:10

D-side


This is what order_query does. Please try the latest version, I can help if it doesn't work for you:

class Product < ActiveRecord::Base
  order_query :my_order,
    [:product_sub_group_id, :asc], 
    [:id, :asc]     
  default_scope -> { my_order } 
end

@product.my_order(@collection.products).next
@collection.products.my_order_at(@product).next

This runs one query loading only the next record. Read more on Github.

like image 35
glebm Avatar answered Oct 03 '22 22:10

glebm