OpenProject is the leading open source project management software.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
openproject/lib/timelines/pagination/controller.rb

237 lines
6.4 KiB

#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
# Extending this module provides some convenience methods for easier setup of pagination inside a controller.
# It assumes there is just one pagination method to set up per model.
#
# To set up basic functions:
# timelines_paginate_model Project
#
# This sets up the action #paginate_projects inside the controller.
#
# To change this action:
#
# timelines_action_for Project, :my_own_action
#
# or use a block:
#
# timelines_action_for Project do
# do_something
# end
#
# To set up multiple models at once:
# timelines_paginate_models Project, User
#
# To change the call the model uses for pagination (signature as in Timelines::Pagination::Model#paginate_scope!):
# timelines_pagination_for Project, :my_own_pagination_method
# timelines_pagination_for Project do |scope, opts|
# do_something
# end
#
# To change the scope the model uses to search (signature as in Timelines::Pagination::Model#search_scope):
# timelines_search_for Project, :my_own_pagination_method
# timelines_search_for Project do |query|
# do_something
# end
#
# Note that this needs to return an actual scope or its corresponding hash.
#
module Timelines::Pagination::Controller
class Paginator
attr_accessor :model, :action, :pagination, :search, :controller, :last_action, :block, :response
def initialize(controller, model)
self.controller = controller
self.model = model
end
def self.resolve_model(model)
(model.respond_to?(:constantize) ? model.constantize : model)
end
def default_action
:"paginate_#{self.class.resolve_model(model).name.gsub('::', '_').underscore.downcase.pluralize}"
end
def default_pagination
:'paginate_scope!'
end
def default_search
:search_scope
end
def last_action
@last_action ||= action
end
def action
@action ||= default_action
end
def action=(action)
@action = action
refresh_action!
return @action
end
def search
@search ||= default_search
end
def pagination
@pagination ||= default_pagination
end
def block
@block ||= default_block
end
def response
@response ||= default_response_block
end
def changed?
last_action != action
end
def refresh_action!
undef_action!
define_action!
end
def undef_action!
controller.timelines_pagination = controller.timelines_pagination.delete(last_action)
# remove old
controller.send(:remove_method, last_action) if controller.respond_to? :last_action
end
def define_action!(block = default_block)
controller.timelines_pagination[action] = self
raise NameError, "method '#{action}' already defined in #{controller}" if controller.respond_to? action
controller.send(:define_method, action, block)
end
def default_block
Proc.new {
# TODO: less evilness
paginator = self.class.timelines_pagination[__method__]
size = params[:page_limit].to_i || 10
page = params[:page]
if page
page = page.to_i
methods = {}
[:pagination, :search].each do |meth|
methods[meth] = if paginator.send(meth).respond_to?(:call)
paginator.send(meth)
else
paginator.model.method(paginator.send(meth))
end
end
@paginated_items = methods[:pagination].call(methods[:search].call(params[:q]),
{ :page => page, :page_limit => size }
)
@more = @paginated_items.total_pages > page
@total = @paginated_items.total_entries
instance_eval(&(paginator.response))
end
}
end
def default_response_block
Proc.new {
respond_to do |format|
format.json { render :json => { :results =>
{ :items => @paginated_items.collect {|item| { :id => item.id, :name => item.name } },
:total => @total ? @total : @paginated_items.size,
:more => @more ? @more : 0 }
} }
end
}
end
end
def self.included(base)
base.extend self
end
def self.extended(base)
base.instance_eval do
unloadable
def timelines_paginate_models(*args)
args.each do |arg|
timelines_paginate_model(arg)
end
end
def timelines_paginate_model(model)
timelines_pagination_class.resolve_model(model)
timelines_pagination[model] = (timelines_pagination_class.new(self, model))
timelines_pagination[model].refresh_action!
end
def timelines_pagination_class
@timelines_pagination_class ||= Timelines::Pagination::Controller::Paginator
end
def timelines_pagination_class=(struct)
@timelines_pagination_class = struct
end
def timelines_pagination=(calls)
@timelines_pagination = calls
end
def timelines_pagination
@timelines_pagination ||= {}
end
# Has to return a method that takes a query as an argument
# See Timelines::pagination::Model#paginate_scope!
def timelines_pagination_for(model, call)
resolve_paginator_for(model).pagination = (call.respond_to?(:call) ? call : call.to_s.to_sym)
end
def timelines_search_for(model, call)
resolve_paginator_for(model).search = (call.respond_to?(:call) ? call : call.to_s.to_sym)
end
def timelines_action_for(model, call)
resolve_paginator_for(model).action = (call.respond_to?(:call) ? call : call.to_s.to_sym)
end
def timelines_response_for(model, call)
resolve_paginator_for(model).response = (call.respond_to?(:call) ? call : call.to_s.to_sym)
end
private
def resolve_paginator_for(model)
model = timelines_pagination_class.resolve_model(model)
inst = timelines_pagination.find { |_,pag| pag.model == model }[1]
if inst.nil?
raise ArgumentError, "Model #{model} is not being paginated. Call #timelines_paginate_model(s) first."
else
return inst
end
end
end
end
end