Делаем простую пагинацию в Rails 3 ч. 1

августа 23, 2011  |  Published in Ruby on Rails, Ruby on Rails 3  |  6 Comments

Для совсем молодых: Пагинация — это разбирие контента на страницы, например у вас есть 1000 статей и выводить их все на одной странице — очень жестоко по отношению к посетителю сайта. Вместо вывода всего содержимого на одной странице, мы разбиваем его на страницы, например по 10 статей на страницу и предоставляем ссылки на другие страницы.

Я отношусь к той когорте людей, что считает, что использование различных гемов для пагинации типа kaminari или will_paginate является лишним, точнее избыточным и не достаточно гибким. Кто-то может бросить в меня камень или помидор со словами: «Необходимо использовать готовые протестированые решения, а не писать свои велосипеды!», и будет прав, но, в случае с пагинацией я не согласен, ибо реализовать пагинацию очень просто. В этой статье я постараюсь привести пример разработки такой пагинации, которая будет удовлетворять вас, в 99% случаев.

Код нашей пагинации состоит из двух основных частей и роутинга:

1. Код выборки — отвечает за выборку записей из БД в соответствии со страницей для которой осуществляется выборка,
2. Код представления — отвечает за представление ссылок на страницы,
3. … и роутинг =)

Давайте создадим простое одномодельное приложение и «рыбу» — 1000 записей с «Lorem Ipsum».

Генерим скаффолдом совсем простое CRUD приложение:

$ rails -v
Rails 3.0.9

$ rails new pagination

$ cd pagination

$ rails g scaffold Post title:string content:text

$ rake db:migrate

Генерим 1000 записей:

$ rails c

1000.times do |n|
  ar = Post.new do |p|
    p.title = "Post #{n}"
    p.content = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
  end
  ar.save
end

Откроем модель Post(../app/models/post.rb) и запишем туда немного кода выборки:

class Post < ActiveRecord::Base
  PerPage = 3
  def self.page(pg)
    pg = pg.to_i
    self.order('id desc').offset((pg-1)*PerPage).limit(PerPage)
  end

  def self.pgcount
    count % PerPage == 0 ? count / PerPage : count / PerPage + 1
  end
end
Post.page 6
#=> [#<Post id: 985, title: "Post 984", content: "Lorem ipsum dolor sit amet, consectetur adipisicing...", created_at: "2011-08-23 21:11:30", updated_at: "2011-08-23 21:11:30">, #<Post id: 984, title: "Post 983", content: "Lorem ipsum dolor sit amet, consectetur adipisicing...", created_at: "2011-08-23 21:11:30", updated_at: "2011-08-23 21:11:30">, #<Post id: 983, title: "Post 982", content: "Lorem ipsum dolor sit amet, consectetur adipisicing...", created_at: "2011-08-23 21:11:30", updated_at: "2011-08-23 21:11:30">]

Post.pgcount
#=> 334

Теперь в контроллере PostsController мы можем использовать метод Post.page для выборки записей из БД по номеру страницы.

#../app/controllers/posts_controller.rb
class PostsController < ApplicationController

  def index
    @posts = Post.page(params[:page])
  end

#...

end

Теперь откроем файл роутинга и немного подправим роутинг для постов:

Pagination::Application.routes.draw do
  root :to=> "posts#index"

  resources :posts, :except => [:index, :show]

  match "(posts/show/:id)"  => "posts#show"
  match "posts(/:page)"     => "posts#index"
end

Теперь осталось добавить логику представления. Для этого открываем файл шаблона для экшена index (../app/views/posts/index.html.erb) и вставляем туда приведенный ниже фрагмент кода в то место, где хотим вставить ссылки на другие страницы:

<br />
<% Post.pgcount.times do |p|%>
  <%= link_to p+1, "/posts/#{p+1}" %>
<% end %>
<br />

Этот код, как не сложно догадаться напечатает ссылки на все страницы, от 1 до 334. Выглядит уродливо, правда?

В следующей части сделаем пагинацию более красивой и вынесем весь код из модели Post в ActiveRecord::Base для того, чтобы не повторять один и тот же код при реализации пагинации в других моделях, которые у нас могут появиться.

Данная статья опубликована благодаря 3 письмам, которые пришли мне на email с просьбой рассказать о реализации пагинации, одно из которых пришло относительно давно (ок. 3 месяцев назад), а два остальных более свежие. Не стесняйтесь — присылайте письма мне на email с просьбами опубликовать статью по разработке на Ruby on Rails, если я знаком с этой темой, то обязательно напишу.

Лучшая благодарность автору — ваши комментарии!

Tags: ,

Responses

  1. Mike Pakhomov says:

    августа 24, 2011 at 09:11 (#)

    Немного не по теме: добавьте кнопку Google+.

  2. says:

    августа 24, 2011 at 13:07 (#)

    Может для простой пагинации и достаточно своего решения, но иногда бывает этого недостаточно. Например, мне недавно очень помог метод paginate_by_sql из will_paginate.

    Я наоборот, люблю использовать как можно больше стороннего кода, браго в ruby/rails его очень просто использовать.

    Для познавательных целей статья весьма пригодится новичкам.

  3. admin says:

    августа 24, 2011 at 14:14 (#)

    Mike Pakhomov, спасибо за совет, давно думаю всяких кнопок соц. сетей натыкать, но как-то руки не доходят, сегодня посмотрю, какие есть плагины для WP и добавлю кнопку(и).

    Andy, в результате написания цикла статей должен получится тот же сторонний код, который потом можно будет заюзать. Надеюсь, он кому-то пригодится.

  4. says:

    августа 25, 2011 at 04:25 (#)

    admin, использовать готовые решения необходимо не только для того что бы решить быстро задачу, но идля того что бы другим разработчикам, а они обязательно будут, было проще поддерживать продукт. Что же касается образовательных целей, тут я полностью согласен, такие «велосипеды» полезно писать и разбирать, но не стоит этот код использовать на production сервере, лучше разобрать уже существующее решение и использовать его, плюсов уйма :)

  5. Evgeniy says:

    сентября 14, 2011 at 06:52 (#)

    C mysql не работает, Mysql::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘-15′ at line 1: SELECT `mmservices`.* FROM `mmservices` ORDER BY date desc LIMIT 15 OFFSET -15

  6. Vladimir says:

    октября 23, 2012 at 05:06 (#)

    С MySQL работает, если немного подправить:
    [language]
    self.order(‘id desc’).offset((pg == 0 ? 0 : pg-1)*PerPage).limit(PerPage)
    [/language]

Leave a Response

Для подсветки кода используйте BB - коды: [language]...[/language], где language может быть: ruby, javascript, css, html.