Use cursored pagination, remove WillPaginate

This commit is contained in:
Georg Gadinger 2020-04-20 23:03:57 +02:00
parent c292f51957
commit 101b3b68d3
49 changed files with 470 additions and 119 deletions

View File

@ -22,8 +22,6 @@ gem 'haml', '~> 5.0'
gem 'bootstrap-sass', '~> 3.4.0' gem 'bootstrap-sass', '~> 3.4.0'
gem 'bootswatch-rails' gem 'bootswatch-rails'
gem 'sweetalert-rails' gem 'sweetalert-rails'
gem 'will_paginate'
gem 'will_paginate-bootstrap'
gem 'devise', '~> 4.0' gem 'devise', '~> 4.0'
gem 'devise-i18n' gem 'devise-i18n'
gem 'devise-async' gem 'devise-async'

View File

@ -499,9 +499,6 @@ GEM
websocket-driver (0.7.1) websocket-driver (0.7.1)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.4) websocket-extensions (0.1.4)
will_paginate (3.3.0)
will_paginate-bootstrap (1.0.2)
will_paginate (>= 3.0.3)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
@ -584,8 +581,6 @@ DEPENDENCIES
uglifier (>= 1.3.0) uglifier (>= 1.3.0)
unicorn unicorn
web-console (< 4.0.0) web-console (< 4.0.0)
will_paginate
will_paginate-bootstrap
BUNDLED WITH BUNDLED WITH
2.1.4 2.1.4

View File

@ -3,6 +3,13 @@ class GroupController < ApplicationController
def index def index
@group = current_user.groups.find_by_name!(params[:group_name]) @group = current_user.groups.find_by_name!(params[:group_name])
@timeline = @group.timeline.paginate(page: params[:page]) @timeline = @group.cursored_timeline(last_id: params[:last_id])
@timeline_last_id = @timeline.map(&:id).min
@more_data_available = !@group.cursored_timeline(last_id: @timeline_last_id, size: 1).count.zero?
respond_to do |format|
format.html
format.js
end
end end
end end

View File

@ -2,28 +2,37 @@ class InboxController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
def show def show
@inbox = Inbox.where(user: current_user) @inbox = current_user.cursored_inbox(last_id: params[:last_id])
.order(:created_at).reverse_order @inbox_last_id = @inbox.map(&:id).min
.paginate(page: params[:page]) @more_data_available = !current_user.cursored_inbox(last_id: @inbox_last_id, size: 1).count.zero?
@inbox_count = Inbox.where(user: current_user).count @inbox_count = current_user.inboxes.count
if params[:author].present? if params[:author].present?
begin begin
@author = true @author = true
@target_user = User.where('LOWER(screen_name) = ?', params[:author].downcase).first! @target_user = User.where('LOWER(screen_name) = ?', params[:author].downcase).first!
@inbox_author = current_user.inboxes.joins(:question) @inbox_author = @inbox.joins(:question)
.where(questions: { user_id: @target_user.id, author_is_anonymous: false }) .where(questions: { user_id: @target_user.id, author_is_anonymous: false })
.paginate(page: params[:page]) @inbox_author_count = current_user.inboxes
@inbox_author_count = current_user.inboxes.joins(:question) .joins(:question)
.where(questions: { user_id: @target_user.id, author_is_anonymous: false }) .where(questions: { user_id: @target_user.id, author_is_anonymous: false })
.count .count
if @inbox_author.empty? if @inbox_author.empty?
@empty = true @empty = true
flash.now[:info] = "No questions from @#{params[:author]} found, showing default entries instead!" flash.now[:info] = "No questions from @#{params[:author]} found, showing default entries instead!"
else else
@inbox = @inbox_author @inbox = @inbox_author
@inbox_count = @inbox_author_count @inbox_count = @inbox_author_count
@inbox_last_id = @inbox.map(&:id).min
@more_data_available = !current_user.cursored_inbox(last_id: @inbox_last_id, size: 1)
.joins(:question)
.where(questions: { user_id: @target_user.id, author_is_anonymous: false })
.count
.zero?
end end
rescue rescue => e
NewRelic::Agent.notice_error(e)
flash.now[:error] = "No user with the name @#{params[:author]} found, showing default entries instead!" flash.now[:error] = "No user with the name @#{params[:author]} found, showing default entries instead!"
@not_found = true @not_found = true
end end

View File

@ -3,16 +3,28 @@ class NotificationsController < ApplicationController
def index def index
@type = params[:type] @type = params[:type]
@notifications = if @type == 'all' @notifications = cursored_notifications_for(type: @type, last_id: params[:last_id])
Notification.for(current_user) @notifications_last_id = @notifications.map(&:id).min
elsif @type == 'new' @more_data_available = !cursored_notifications_for(type: @type, last_id: @notifications_last_id, size: 1).count.zero?
Notification.for(current_user).where(new: true)
else
Notification.for(current_user).where('LOWER(target_type) = ?', @type)
end.paginate(page: params[:page])
respond_to do |format| respond_to do |format|
format.html format.html
format.js format.js
end end
end end
private
def cursored_notifications_for(type:, last_id:, size: nil)
cursor_params = { last_id: last_id, size: size }.compact
case type
when 'all'
Notification.cursored_for(current_user, **cursor_params)
when 'new'
Notification.cursored_for(current_user, new: true, **cursor_params)
else
Notification.cursored_for_type(current_user, type, **cursor_params)
end
end
end end

View File

@ -2,7 +2,10 @@ class PublicController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
def index def index
@timeline = Answer.joins(:user).where(users: { privacy_allow_public_timeline: true }).all.reverse_order.paginate(page: params[:page]) @timeline = Answer.cursored_public_timeline(last_id: params[:last_id])
@timeline_last_id = @timeline.map(&:id).min
@more_data_available = !Answer.cursored_public_timeline(last_id: @timeline_last_id, size: 1).count.zero?
respond_to do |format| respond_to do |format|
format.html format.html
format.js format.js

View File

@ -1,7 +1,10 @@
class QuestionController < ApplicationController class QuestionController < ApplicationController
def show def show
@question = Question.find(params[:id]) @question = Question.find(params[:id])
@answers = @question.answers.reverse_order.paginate(page: params[:page]) @answers = @question.cursored_answers(last_id: params[:last_id])
@answers_last_id = @answers.map(&:id).min
@more_data_available = !@question.cursored_answers(last_id: @answers_last_id, size: 1).count.zero?
respond_to do |format| respond_to do |format|
format.html format.html
format.js format.js

View File

@ -1,7 +1,12 @@
# frozen_string_literal: true
class StaticController < ApplicationController class StaticController < ApplicationController
def index def index
if user_signed_in? if user_signed_in?
@timeline = current_user.timeline.paginate(page: params[:page]) @timeline = current_user.cursored_timeline(last_id: params[:last_id])
@timeline_last_id = @timeline.map(&:id).min
@more_data_available = !current_user.cursored_timeline(last_id: @timeline_last_id, size: 1).count.zero?
respond_to do |format| respond_to do |format|
format.html format.html
format.js format.js

View File

@ -5,7 +5,9 @@ class UserController < ApplicationController
def show def show
@user = User.where('LOWER(screen_name) = ?', params[:username].downcase).first! @user = User.where('LOWER(screen_name) = ?', params[:username].downcase).first!
@answers = @user.answers.reverse_order.paginate(page: params[:page]) @answers = @user.cursored_answers(last_id: params[:last_id])
@answers_last_id = @answers.map(&:id).min
@more_data_available = !@user.cursored_answers(last_id: @answers_last_id, size: 1).count.zero?
if user_signed_in? if user_signed_in?
notif = Notification.where(target_type: "Relationship", target_id: @user.active_relationships.where(target_id: current_user.id).pluck(:id), recipient_id: current_user.id, new: true).first notif = Notification.where(target_type: "Relationship", target_id: @user.active_relationships.where(target_id: current_user.id).pluck(:id), recipient_id: current_user.id, new: true).first
@ -72,7 +74,9 @@ class UserController < ApplicationController
def followers def followers
@title = 'Followers' @title = 'Followers'
@user = User.where('LOWER(screen_name) = ?', params[:username].downcase).first! @user = User.where('LOWER(screen_name) = ?', params[:username].downcase).first!
@users = @user.followers.reverse_order.paginate(page: params[:page]) @users = @user.cursored_followers(last_id: params[:last_id])
@users_last_id = @users.map(&:id).min
@more_data_available = !@user.cursored_followers(last_id: @users_last_id, size: 1).count.zero?
@type = :friend @type = :friend
render 'show_follow' render 'show_follow'
end end
@ -80,7 +84,9 @@ class UserController < ApplicationController
def friends def friends
@title = 'Following' @title = 'Following'
@user = User.where('LOWER(screen_name) = ?', params[:username].downcase).first! @user = User.where('LOWER(screen_name) = ?', params[:username].downcase).first!
@users = @user.friends.reverse_order.paginate(page: params[:page]) @users = @user.cursored_friends(last_id: params[:last_id])
@users_last_id = @users.map(&:id).min
@more_data_available = !@user.cursored_friends(last_id: @users_last_id, size: 1).count.zero?
@type = :friend @type = :friend
render 'show_follow' render 'show_follow'
end end
@ -88,7 +94,9 @@ class UserController < ApplicationController
def questions def questions
@title = 'Questions' @title = 'Questions'
@user = User.where('LOWER(screen_name) = ?', params[:username].downcase).first! @user = User.where('LOWER(screen_name) = ?', params[:username].downcase).first!
@questions = @user.questions.where(author_is_anonymous: false).reverse_order.paginate(page: params[:page]) @questions = @user.cursored_questions(last_id: params[:last_id])
@questions_last_id = @questions.map(&:id).min
@more_data_available = !@user.cursored_questions(last_id: @questions_last_id, size: 1).count.zero?
end end
def data def data

View File

@ -1,4 +1,6 @@
class Answer < ApplicationRecord class Answer < ApplicationRecord
extend Answer::TimelineMethods
belongs_to :user belongs_to :user
belongs_to :question belongs_to :question
has_many :comments, dependent: :destroy has_many :comments, dependent: :destroy

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
module Answer::TimelineMethods
include CursorPaginatable
define_cursor_paginator :cursored_public_timeline, :public_timeline
def public_timeline
joins(:user)
.where(users: { privacy_allow_public_timeline: true })
.order(:created_at)
.reverse_order
end
end

View File

@ -0,0 +1,38 @@
# frozen_string_literal: true
module CursorPaginatable
extend ActiveSupport::Concern
module ClassMethods
# Defines a cursor paginator.
#
# This method will define a new method +name+, which accepts the keyword
# arguments +last_id+ for defining the last id the cursor will operate on,
# and +size+ for the amount of records it should return.
#
# @param name [Symbol] The name of the method for the cursor paginator
# @param scope [Symbol] The name of the method which returns an
# ActiveRecord scope.
#
# @example
# class User
# has_many :answers
#
# include CursorPaginatable
# define_cursor_paginator :cursored_answers, :recent_answers
#
# def recent_answers
# self.answers.order(:created_at).reverse_order
# end
# end
def define_cursor_paginator(name, scope, default_size: APP_CONFIG.fetch('items_per_page'))
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{name}(*args, last_id: nil, size: #{default_size}, **kwargs)
s = self.#{scope}(*args, **kwargs).limit(size)
s = s.where(s.arel_table[:id].lt(last_id)) if last_id.present?
s
end
RUBY
end
end
end

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class Group < ApplicationRecord class Group < ApplicationRecord
include Group::TimelineMethods
belongs_to :user belongs_to :user
has_many :group_members, dependent: :destroy has_many :group_members, dependent: :destroy
@ -22,9 +24,4 @@ class Group < ApplicationRecord
def remove_member(user) def remove_member(user)
GroupMember.where(group: self, user: user).first!.destroy GroupMember.where(group: self, user: user).first!.destroy
end end
# @return [Array] the groups' timeline
def timeline
Answer.where("user_id in (?)", members.pluck(:user_id)).order(:created_at).reverse_order
end
end end

View File

@ -0,0 +1,12 @@
# frozen_string_literal: true
module Group::TimelineMethods
include CursorPaginatable
define_cursor_paginator :cursored_timeline, :timeline
# @return [Array] the groups' timeline
def timeline
Answer.where('user_id in (?)', members.pluck(:user_id)).order(:created_at).reverse_order
end
end

View File

@ -3,10 +3,19 @@ class Notification < ApplicationRecord
belongs_to :target, polymorphic: true belongs_to :target, polymorphic: true
class << self class << self
include CursorPaginatable
define_cursor_paginator :cursored_for, :for
define_cursor_paginator :cursored_for_type, :for_type
def for(recipient, options={}) def for(recipient, options={})
self.where(options.merge!(recipient: recipient)).order(:created_at).reverse_order self.where(options.merge!(recipient: recipient)).order(:created_at).reverse_order
end end
def for_type(recipient, type, options={})
self.where(options.merge!(recipient: recipient)).where('LOWER(target_type) = ?', type).order(:created_at).reverse_order
end
def notify(recipient, target) def notify(recipient, target)
return nil unless target.respond_to? :notification_type return nil unless target.respond_to? :notification_type

View File

@ -1,4 +1,6 @@
class Question < ApplicationRecord class Question < ApplicationRecord
include Question::AnswerMethods
belongs_to :user belongs_to :user
has_many :answers, dependent: :destroy has_many :answers, dependent: :destroy
has_many :inboxes, dependent: :destroy has_many :inboxes, dependent: :destroy

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
module Question::AnswerMethods
include CursorPaginatable
define_cursor_paginator :cursored_answers, :ordered_answers
def ordered_answers
answers
.order(:created_at)
.reverse_order
end
end

View File

@ -1,4 +1,10 @@
class User < ApplicationRecord class User < ApplicationRecord
include User::AnswerMethods
include User::InboxMethods
include User::QuestionMethods
include User::RelationshipMethods
include User::TimelineMethods
# Include default devise modules. Others available are: # Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable # :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :async, :registerable, devise :database_authenticatable, :async, :registerable,
@ -100,11 +106,6 @@ class User < ApplicationRecord
end end
end end
# @return [Array] the users' timeline
def timeline
Answer.where("user_id in (?) OR user_id = ?", friend_ids, id).order(:created_at).reverse_order
end
# follows an user. # follows an user.
def follow(target_user) def follow(target_user)
active_relationships.create(target: target_user) active_relationships.create(target: target_user)

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
module User::AnswerMethods
include CursorPaginatable
define_cursor_paginator :cursored_answers, :ordered_answers
def ordered_answers
answers
.order(:created_at)
.reverse_order
end
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
module User::InboxMethods
include CursorPaginatable
define_cursor_paginator :cursored_inbox, :ordered_inbox
def ordered_inbox
inboxes
.includes(:question, :user)
.order(:created_at)
.reverse_order
end
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
module User::QuestionMethods
include CursorPaginatable
define_cursor_paginator :cursored_questions, :ordered_questions
def ordered_questions
questions
.order(:created_at)
.reverse_order
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
module User::RelationshipMethods
include CursorPaginatable
define_cursor_paginator :cursored_friends, :ordered_friends
define_cursor_paginator :cursored_followers, :ordered_followers
def ordered_friends
friends.reverse_order
end
def ordered_followers
followers.reverse_order
end
end

View File

@ -0,0 +1,12 @@
# frozen_string_literal: true
module User::TimelineMethods
include CursorPaginatable
define_cursor_paginator :cursored_timeline, :timeline
# @return [Array] the users' timeline
def timeline
Answer.where('user_id in (?) OR user_id = ?', friend_ids, id).order(:created_at).reverse_order
end
end

View File

@ -10,9 +10,9 @@
- @timeline.each do |answer| - @timeline.each do |answer|
= render 'shared/answerbox', a: answer = render 'shared/answerbox', a: answer
#pagination= will_paginate @timeline, renderer: BootstrapPagination::Rails, page_links: false = render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @timeline_last_id
- if @timeline.next_page - if @more_data_available
%button#load-more-btn.btn.btn-default{type: :button, data: { current_page: @timeline.current_page }} %button#load-more-btn.btn.btn-default{type: :button, data: { last_id: @timeline_last_id }}
= t 'views.actions.load' = t 'views.actions.load'
.visible-xs= render 'shared/links' .visible-xs= render 'shared/links'

View File

@ -1,8 +1,8 @@
$('#timeline').append('<% @timeline.each do |answer| $('#timeline').append('<% @timeline.each do |answer|
%><%= j render 'shared/answerbox', a: answer %><%= j render 'shared/answerbox', a: answer
%><% end %>'); %><% end %>');
<% if @timeline.next_page %> <% if @more_data_available %>
$('#pagination').html('<%= j will_paginate @timeline, renderer: BootstrapPagination::Rails, page_links: false %>'); $('#pagination').html('<%= j render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @timeline_last_id %>');
<% else %> <% else %>
$('#pagination, #load-more-btn').remove(); $('#pagination, #load-more-btn').remove();
<% end %> <% end %>

View File

@ -12,10 +12,10 @@
- if @inbox.empty? - if @inbox.empty?
= t 'views.inbox.empty' = t 'views.inbox.empty'
#pagination= will_paginate @inbox, renderer: BootstrapPagination::Rails, page_links: false = render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @inbox_last_id
- if @inbox.next_page - if @more_data_available
%button#load-more-btn.btn.btn-default{type: :button, data: { current_page: @inbox.current_page }} %button#load-more-btn.btn.btn-default{type: :button, data: { last_id: @inbox_last_id }}
= t 'views.actions.load' = t 'views.actions.load'
.col-md-9.col-xs-12.col-sm-8.visible-xs .col-md-9.col-xs-12.col-sm-8.visible-xs

View File

@ -1,9 +1,9 @@
$('#entries').append('<% @inbox.each do |i| $('#entries').append('<% @inbox.each do |i|
%><%= j render 'inbox/entry', i: i %><%= j render 'inbox/entry', i: i
%><% end %>'); %><% end %>');
<% if @inbox.next_page %> <% if @more_data_available %>
$('#pagination').html('<%= j will_paginate @inbox, renderer: BootstrapPagination::Rails, page_links: false %>'); $('#pagination').html('<%= j render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @inbox_last_id %>');
<% else %> <% else %>
$('#pagination, #load-more-btn').remove(); $('#pagination, #load-more-btn').remove();
<% end %> <% end %>
<% Inbox.where(id: @inbox.pluck(:id)).update_all(new: false) %> <% Inbox.where(id: @inbox.pluck(:id)).update_all(new: false) %>

View File

@ -18,9 +18,9 @@
- @notifications.each do |notification| - @notifications.each do |notification|
= render 'notifications/notification', notification: notification = render 'notifications/notification', notification: notification
#pagination= will_paginate @notifications, renderer: BootstrapPagination::Rails, page_links: false = render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @notifications_last_id, permitted_params: %i[type]
- if @notifications.next_page - if @more_data_available
%button#load-more-btn.btn.btn-default{type: :button, data: { current_page: @notifications.current_page }} %button#load-more-btn.btn.btn-default{type: :button, data: { last_id: @notifications_last_id }}
Load more Load more
- Notification.for(current_user).update_all(new: false) - Notification.for(current_user).update_all(new: false)

View File

@ -1,8 +1,8 @@
$('#notifications').append('<% @notifications.each do |notification| $('#notifications').append('<% @notifications.each do |notification|
%><%= j render 'notifications/notification', notification: notification %><%= j render 'notifications/notification', notification: notification
%><% end %>'); %><% end %>');
<% if @notifications.next_page %> <% if @more_data_available %>
$('#pagination').html('<%= j will_paginate @notifications, renderer: BootstrapPagination::Rails, page_links: false %>'); $('#pagination').html('<%= j render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @notifications_last_id, permitted_params: %i[type] %>');
<% else %> <% else %>
$('#pagination, #load-more-btn').remove(); $('#pagination, #load-more-btn').remove();
<% end %> <% end %>

View File

@ -10,9 +10,9 @@
- @timeline.each do |answer| - @timeline.each do |answer|
= render 'shared/answerbox', a: answer = render 'shared/answerbox', a: answer
#pagination= will_paginate @timeline, renderer: BootstrapPagination::Rails, page_links: false = render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @timeline_last_id
- if @timeline.next_page - if @more_data_available
%button#load-more-btn.btn.btn-default{type: :button, data: { current_page: @timeline.current_page }} %button#load-more-btn.btn.btn-default{type: :button, data: { last_id: @timeline_last_id }}
Load more Load more
.visible-xs= render 'shared/links' .visible-xs= render 'shared/links'

View File

@ -1,8 +1,8 @@
$('#timeline').append('<% @timeline.each do |answer| $('#timeline').append('<% @timeline.each do |answer|
%><%= j render 'shared/answerbox', a: answer %><%= j render 'shared/answerbox', a: answer
%><% end %>'); %><% end %>');
<% if @timeline.next_page %> <% if @more_data_available %>
$('#pagination').html('<%= j will_paginate @timeline, renderer: BootstrapPagination::Rails, page_links: false %>'); $('#pagination').html('<%= j render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @timeline_last_id %>');
<% else %> <% else %>
$('#pagination, #load-more-btn').remove(); $('#pagination, #load-more-btn').remove();
<% end %> <% end %>

View File

@ -8,10 +8,10 @@
- @answers.each do |a| - @answers.each do |a|
= render 'shared/answerbox', a: a, show_question: false = render 'shared/answerbox', a: a, show_question: false
#pagination= will_paginate @answers, renderer: BootstrapPagination::Rails, page_links: false = render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @answers_last_id
- if @answers.next_page - if @more_data_available
%button#load-more-btn.btn.btn-default{type: :button, data: { current_page: @answers.current_page }} %button#load-more-btn.btn.btn-default{type: :button, data: { last_id: @answers_last_id }}
Load more Load more
- if user_signed_in? and !current_user.answered? @question and current_user != @question.user and @question.user.privacy_allow_stranger_answers - if user_signed_in? and !current_user.answered? @question and current_user != @question.user and @question.user.privacy_allow_stranger_answers

View File

@ -1,8 +1,8 @@
$('#answers').append('<% @answers.each do |answer| $('#answers').append('<% @answers.each do |answer|
%><%= j render 'shared/answerbox', a: answer, show_question: false %><%= j render 'shared/answerbox', a: answer, show_question: false
%><% end %>'); %><% end %>');
<% if @answers.next_page %> <% if @more_data_available %>
$('#pagination').html('<%= j will_paginate @answers, renderer: BootstrapPagination::Rails, page_links: false %>'); $('#pagination').html('<%= j render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @answers_last_id %>');
<% else %> <% else %>
$('#pagination, #load-more-btn').remove(); $('#pagination, #load-more-btn').remove();
<% end %> <% end %>

View File

@ -0,0 +1,11 @@
-# this renders a pagination html to keep compatibility with the current pagination js
-# it _should_ be replaced with something else entirely later on.
- permitted_params ||= []
#pagination
%ul.pagination
%li.next{class: more_data_available ? nil : "disabled"}
- if more_data_available
%a{rel: :next, href: url_for(params.permit(*permitted_params).merge(last_id: last_id))}
Next page
- else
Next page

View File

@ -9,9 +9,9 @@
- @timeline.each do |answer| - @timeline.each do |answer|
= render 'shared/answerbox', a: answer = render 'shared/answerbox', a: answer
#pagination= will_paginate @timeline, renderer: BootstrapPagination::Rails, page_links: false = render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @timeline_last_id
- if @timeline.next_page - if @more_data_available
%button#load-more-btn.btn.btn-default{type: :button, data: { current_page: @timeline.current_page }} %button#load-more-btn.btn.btn-default{type: :button, data: { last_id: @timeline_last_id }}
Load more Load more
.visible-xs= render 'shared/links' .visible-xs= render 'shared/links'

View File

@ -1,8 +1,8 @@
$('#timeline').append('<% @timeline.each do |answer| $('#timeline').append('<% @timeline.each do |answer|
%><%= j render 'shared/answerbox', a: answer %><%= j render 'shared/answerbox', a: answer
%><% end %>'); %><% end %>');
<% if @timeline.next_page %> <% if @more_data_available %>
$('#pagination').html('<%= j will_paginate @timeline, renderer: BootstrapPagination::Rails, page_links: false %>'); $('#pagination').html('<%= j render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @timeline_last_id %>');
<% else %> <% else %>
$('#pagination, #load-more-btn').remove(); $('#pagination, #load-more-btn').remove();
<% end %> <% end %>

View File

@ -13,9 +13,9 @@
- @questions.each do |q| - @questions.each do |q|
= render 'shared/question', q: q, type: nil = render 'shared/question', q: q, type: nil
#pagination= will_paginate @questions, renderer: BootstrapPagination::Rails, page_links: false = render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @questions_last_id
- if @questions.next_page - if @more_data_available
%button#load-more-btn.btn.btn-default{type: :button, data: { current_page: @questions.current_page }} %button#load-more-btn.btn.btn-default{type: :button, data: { last_id: @questions_last_id }}
= t 'views.actions.load' = t 'views.actions.load'
.visible-xs= render 'shared/links' .visible-xs= render 'shared/links'

View File

@ -1,8 +1,8 @@
$('#questions').append('<% @questions.each do |q| $('#questions').append('<% @questions.each do |q|
%><%= j render 'shared/question', q: q, type: nil %><%= j render 'shared/question', q: q, type: nil
%><% end %>'); %><% end %>');
<% if @questions.next_page %> <% if @more_data_available %>
$('#pagination').html('<%= j will_paginate @questions, renderer: BootstrapPagination::Rails, page_links: false %>'); $('#pagination').html('<%= j render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @questions_last_id %>');
<% else %> <% else %>
$('#pagination, #load-more-btn').remove(); $('#pagination, #load-more-btn').remove();
<% end %> <% end %>

View File

@ -14,10 +14,10 @@
- @answers.each do |a| - @answers.each do |a|
= render 'shared/answerbox', a: a = render 'shared/answerbox', a: a
#pagination= will_paginate @answers, renderer: BootstrapPagination::Rails, page_links: false = render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @answers_last_id
- if @answers.next_page - if @more_data_available
%button#load-more-btn.btn.btn-default{type: :button, data: { current_page: @answers.current_page }} %button#load-more-btn.btn.btn-default{type: :button, data: { last_id: @answers_last_id }}
= t 'views.actions.load' = t 'views.actions.load'
.visible-xs= render 'shared/links' .visible-xs= render 'shared/links'
- if user_signed_in? - if user_signed_in?

View File

@ -1,8 +1,8 @@
$('#answers').append('<% @answers.each do |a| $('#answers').append('<% @answers.each do |a|
%><%= j render 'shared/answerbox', a: a %><%= j render 'shared/answerbox', a: a
%><% end %>'); %><% end %>');
<% if @answers.next_page %> <% if @more_data_available %>
$('#pagination').html('<%= j will_paginate @answers, renderer: BootstrapPagination::Rails, page_links: false %>'); $('#pagination').html('<%= j render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @answers_last_id %>');
<% else %> <% else %>
$('#pagination, #load-more-btn').remove(); $('#pagination, #load-more-btn').remove();
<% end %> <% end %>

View File

@ -14,10 +14,10 @@
.col-md-4.col-sm-6.col-xs-12 .col-md-4.col-sm-6.col-xs-12
= render 'shared/userbox', user: user = render 'shared/userbox', user: user
#pagination= will_paginate @users, renderer: BootstrapPagination::Rails, page_links: false = render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @users_last_id
- if @users.next_page - if @more_data_available
%button#load-more-btn.btn.btn-default{type: :button, data: { current_page: @users.current_page }} %button#load-more-btn.btn.btn-default{type: :button, data: { last_id: @users_last_id }}
= t 'views.actions.load' = t 'views.actions.load'
.visible-xs= render 'shared/links' .visible-xs= render 'shared/links'
- if user_signed_in? - if user_signed_in?

View File

@ -1,8 +1,8 @@
$('#users').append('<% @users.each do |user| $('#users').append('<% @users.each do |user|
%><div class="col-md-4 col-sm-6 col-xs-12"><%= j render 'shared/userbox', user: user %><div class="col-md-4 col-sm-6 col-xs-12"><%= j render 'shared/userbox', user: user
%></div><% end %>'); %></div><% end %>');
<% if @users.next_page %> <% if @more_data_available %>
$('#pagination').html('<%= j will_paginate @users, renderer: BootstrapPagination::Rails, page_links: false %>'); $('#pagination').html('<%= j render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @users_last_id %>');
<% else %> <% else %>
$('#pagination, #load-more-btn').remove(); $('#pagination, #load-more-btn').remove();
<% end %> <% end %>

View File

@ -1 +0,0 @@
WillPaginate.per_page = APP_CONFIG['items_per_page']

View File

@ -1,12 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
# workaround to get pagination right # workaround to get pagination right
if defined? WillPaginate
Kaminari.configure do |config|
config.page_method_name = :per_page_kaminari
end
end
RailsAdmin.config do |config| RailsAdmin.config do |config|
config.main_app_name = ['justask', 'Kontrollzentrum'] config.main_app_name = ['justask', 'Kontrollzentrum']

12
spec/factories/answer.rb Normal file
View File

@ -0,0 +1,12 @@
# frozen_string_literal: true
FactoryBot.define do
factory :answer do
transient do
question_content { Faker::Lorem.sentence }
end
content { Faker::Lorem.sentence }
question { FactoryBot.build(:question, content: question_content) }
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
FactoryBot.define do
factory :question do
user { nil }
content { Faker::Lorem.sentence }
author_is_anonymous { true }
end
end

View File

@ -2,9 +2,9 @@
FactoryBot.define do FactoryBot.define do
factory :user do factory :user do
sequence(:screen_name) { |i| "#{Faker::Internet.username(specifier: 0..12, separators: %w(_))}#{i}" } sequence(:screen_name) { |i| "#{Faker::Internet.username(specifier: 0..12, separators: %w[_])}#{i}" }
sequence(:email) { |i| "#{i}#{Faker::Internet.email}" } sequence(:email) { |i| "#{i}#{Faker::Internet.email}" }
password { "P4s5w0rD" } password { 'P4s5w0rD' }
confirmed_at { Time.now.utc } confirmed_at { Time.now.utc }
display_name { Faker::Name.name } display_name { Faker::Name.name }

View File

@ -1,28 +1,158 @@
# frozen_string_literal: true
require 'rails_helper' require 'rails_helper'
RSpec.describe User, :type => :model do RSpec.describe User, type: :model do
before :each do let!(:me) { FactoryBot.create :user }
@user = User.new(
describe 'basic assigns' do
before :each do
@user = User.new(
screen_name: 'FunnyMeme2004', screen_name: 'FunnyMeme2004',
password: 'y_u_no_secure_password?', password: 'y_u_no_secure_password?',
email: 'nice.meme@nsa.gov' email: 'nice.meme@nsa.gov'
) )
end
subject { @user }
it { should respond_to(:email) }
it '#email returns a string' do
expect(@user.email).to match 'nice.meme@nsa.gov'
end
it '#motivation_header has a default value' do
expect(@user.motivation_header).to match ''
end
it 'does not save an invalid screen name' do
@user.screen_name = '$Funny-Meme-%&2004'
expect { @user.save! }.to raise_error(ActiveRecord::RecordInvalid)
end
end end
subject { @user } # -- User::TimelineMethods --
it { should respond_to(:email) } shared_examples_for 'result is blank' do
it 'result is blank' do
it '#email returns a string' do expect(subject).to be_blank
expect(@user.email).to match 'nice.meme@nsa.gov' end
end end
it '#motivation_header has a default value' do describe '#timeline' do
expect(@user.motivation_header).to match '' subject { me.timeline }
context 'user answered nothing and is not following anyone' do
include_examples 'result is blank'
end
context 'user answered something and is not following anyone' do
let(:answer) { FactoryBot.create(:answer, user: me) }
let(:expected) { [answer] }
it 'includes the answer' do
expect(subject).to eq(expected)
end
end
context 'user answered something and follows users with answers' do
let(:user1) { FactoryBot.create(:user) }
let(:user2) { FactoryBot.create(:user) }
let(:answer1) { FactoryBot.create(:answer, user: user1, created_at: 12.hours.ago) }
let(:answer2) { FactoryBot.create(:answer, user: me, created_at: 1.day.ago) }
let(:answer3) { FactoryBot.create(:answer, user: user2, created_at: 10.minutes.ago) }
let(:answer4) { FactoryBot.create(:answer, user: user1, created_at: Time.now.utc) }
let!(:expected) do
[answer4, answer3, answer1, answer2]
end
before(:each) do
me.follow(user1)
me.follow(user2)
end
it 'includes all answers' do
expect(subject).to include(answer1)
expect(subject).to include(answer2)
expect(subject).to include(answer3)
expect(subject).to include(answer4)
end
it 'result is ordered by created_at in reverse order' do
expect(subject).to eq(expected)
end
end
end end
it 'does not save an invalid screen name' do describe '#cursored_timeline' do
@user.screen_name = '$Funny-Meme-%&2004' let(:last_id) { nil }
expect{@user.save!}.to raise_error(ActiveRecord::RecordInvalid)
subject { me.cursored_timeline(last_id: last_id, size: 3) }
context 'user answered nothing and is not following anyone' do
include_examples 'result is blank'
end
context 'user answered something and is not following anyone' do
let(:answer) { FactoryBot.create(:answer, user: me) }
let(:expected) { [answer] }
it 'includes the answer' do
expect(subject).to eq(expected)
end
end
context 'user answered something and follows users with answers' do
let(:user1) { FactoryBot.create(:user) }
let(:user2) { FactoryBot.create(:user) }
let!(:answer1) { FactoryBot.create(:answer, user: me, created_at: 1.day.ago) }
let!(:answer2) { FactoryBot.create(:answer, user: user1, created_at: 12.hours.ago) }
let!(:answer3) { FactoryBot.create(:answer, user: user2, created_at: 10.minutes.ago) }
let!(:answer4) { FactoryBot.create(:answer, user: user1, created_at: Time.now.utc) }
before(:each) do
me.follow(user1)
me.follow(user2)
end
context 'last_id is nil' do
let(:last_id) { nil }
let(:expected) do
[answer4, answer3, answer2]
end
it 'includes three answers' do
expect(subject).not_to include(answer1)
expect(subject).to include(answer2)
expect(subject).to include(answer3)
expect(subject).to include(answer4)
end
it 'result is ordered by created_at in reverse order' do
expect(subject).to eq(expected)
end
end
context 'last_id is answer2.id' do
let(:last_id) { answer2.id }
it 'includes answer1' do
expect(subject).to include(answer1)
expect(subject).not_to include(answer2)
expect(subject).not_to include(answer3)
expect(subject).not_to include(answer4)
end
end
context 'last_id is answer1.id' do
let(:last_id) { answer1.id }
include_examples 'result is blank'
end
end
end end
end end