Skip to content

Commit 702501f

Browse files
authored
My Bookmarks updates (#562)
* Support bookmark sorting * Move bookmarkable_image_url to decorator * Add inactive? to facilitator so it's bookmarkable * Change bookmarkable_type dropdown to read from model constant rather than hard-coded values * Remove commented out code * Add bookmark count to bookmarks index * Fix pagination on bookmarks personal (infinite-scrolling isn't a thing yet) * Add type to bookmarked personal row * Show date bookmarked and bookmark count on personal row * Show linked user/created by on bookmarks personal row * Show date created and age ranges on bookmarkable personal_row * Update color of published content on profile * Split search and sort for bookmarks * Fix sql * Update tests to match new sort names and defaults
1 parent f703947 commit 702501f

File tree

12 files changed

+174
-126
lines changed

12 files changed

+174
-126
lines changed

app/controllers/bookmarks_controller.rb

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,35 @@ class BookmarksController < ApplicationController
22
before_action :set_breadcrumb
33

44
def index
5+
per_page = params[:number_of_items_per_page] || 25
56
bookmarks = Bookmark.search(params)
6-
@bookmarks = bookmarks.paginate(page: params[:page], per_page: 25)
7-
@bookmarks_count = bookmarks.size
7+
bookmarks = bookmarks.sorted(params[:sort])
8+
9+
@bookmarks = bookmarks.paginate(page: params[:page], per_page: per_page)
10+
@bookmarks_count = bookmarks.length
811
@windows_types_array = WindowsType::TYPES
9-
load_sortable_fields
12+
set_index_variables
1013
respond_to do |format|
1114
format.html
1215
format.js
1316
end
1417
end
1518

1619
def personal
20+
per_page = params[:number_of_items_per_page] || 25
1721
user = User.where(id: params[:user_id]).first if params[:user_id].present?
1822
user ||= current_user
1923
@user_name = user.full_name if user
2024
@viewing_self = user == current_user
25+
2126
bookmarks = Bookmark.search(params, user: user)
22-
@bookmarks_count = bookmarks.size
23-
@bookmarks = bookmarks.paginate(page: params[:page], per_page: 25)
24-
@windows_types_array = WindowsType::TYPES
27+
bookmarks = bookmarks.sorted(params[:sort])
28+
29+
@bookmarks_count = bookmarks.length
30+
@bookmarks = bookmarks.paginate(page: params[:page], per_page: per_page)
31+
32+
set_index_variables
2533

26-
load_sortable_fields
2734
respond_to do |format|
2835
format.html
2936
format.js
@@ -97,9 +104,12 @@ def tally
97104

98105
private
99106

100-
def load_sortable_fields
107+
def set_index_variables
101108
@sortable_fields = WindowsType.where("name NOT LIKE ?", "%COMBINED%")
102-
@windows_types = WindowsType.all
109+
@windows_types_array = WindowsType::TYPES
110+
bookmarkable_types = Bookmark::BOOKMARKABLE_MODELS
111+
@bookmarkable_types = bookmarkable_types.map{ |type| [ type, type ] }
112+
103113
end
104114

105115
def load_workshop_data

app/decorators/bookmark_decorator.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,14 @@ def bookmarkable_link
3737
bookmarkable.breadcrumb_link
3838
end
3939
end
40+
41+
def bookmarkable_image_url(fallback: 'missing.png')
42+
if bookmarkable.respond_to?(:images) && bookmarkable.images.first&.file&.attached?
43+
Rails.application.routes.url_helpers.rails_blob_path(bookmarkable.images.first.file, only_path: true)
44+
elsif bookmarkable_type == "Workshop"
45+
ActionController::Base.helpers.asset_path("workshop_default.jpg")
46+
else
47+
ActionController::Base.helpers.asset_path(fallback)
48+
end
49+
end
4050
end

app/decorators/facilitator_decorator.rb

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,8 @@ def description
99
"Facilitator Profile for #{first_name} #{last_name}"
1010
end
1111

12-
DISPLAY_FIELDS = {
13-
"First name" => :first_name,
14-
"Last name" => :last_name,
15-
"Primary email address" => :primary_email_address,
16-
"Primary email type" => :primary_email_address_type,
17-
"Street address" => :street_address,
18-
"City" => :city,
19-
"State" => :state,
20-
"ZIP" => :zip,
21-
"Country" => :country,
22-
"Mailing address type" => :mailing_address_type,
23-
"Phone number" => :phone_number,
24-
"Phone number type" => :phone_number_type
25-
}
26-
27-
def display_fields
28-
DISPLAY_FIELDS.map do |label, method|
29-
{ label: label, value: object.send(method) }
30-
end
12+
def inactive?
13+
!user ? false : user&.inactive?
3114
end
3215

3316
def pronouns_display

app/models/application_record.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
class ApplicationRecord < ActiveRecord::Base
22
self.abstract_class = true
3-
end
3+
4+
def bookmarks_count
5+
if self.respond_to?(:bookmarks)
6+
self.bookmarks.length
7+
else
8+
0
9+
end
10+
end
11+
end

app/models/bookmark.rb

Lines changed: 79 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,39 @@ class Bookmark < ApplicationRecord
22
belongs_to :user
33
belongs_to :bookmarkable, polymorphic: true
44

5+
BOOKMARKABLE_MODELS = ["CommunityNews", "Event", "Facilitator", "Project", "Resource", "Story", "StoryIdea",
6+
"Workshop", "WorkshopIdea", "WorkshopLog", "WorkshopVariation"]
7+
58
scope :for_workshops, -> { where(bookmarkable_type: 'Workshop') }
69
scope :bookmarkable_type, -> (bookmarkable_type) { bookmarkable_type.present? ? where(bookmarkable_type: bookmarkable_type) : all }
710
scope :bookmarkable_attributes, -> (bookmarkable_type, bookmarkable_id) {
811
bookmarkable_type.present? && bookmarkable_id.present? ? where(bookmarkable_type: bookmarkable_type,
912
bookmarkable_id: bookmarkable_id) : all }
1013

14+
scope :sort_by_newest, -> { order(created_at: :desc) }
15+
scope :sort_by_popularity, -> {
16+
select("bookmarks.*, COUNT(all_b.id) as popularity")
17+
.joins("LEFT JOIN bookmarks all_b ON all_b.bookmarkable_id = bookmarks.bookmarkable_id AND
18+
all_b.bookmarkable_type = bookmarks.bookmarkable_type")
19+
.group("bookmarks.id")
20+
.order("popularity DESC") }
21+
22+
1123
def self.search(params, user: nil)
1224
bookmarks = user ? user.bookmarks : self.all
1325
bookmarks = bookmarks.filter_by_params(params)
26+
bookmarks = bookmarks.sorted(params[:sort])
27+
bookmarks
28+
end
1429

15-
sort = params[:sort].presence || "title"
16-
17-
case sort
18-
when "title"
19-
bookmarks = bookmarks
20-
.joins(<<~SQL)
21-
LEFT JOIN workshops ON bookmarks.bookmarkable_type = 'Workshop' AND workshops.id = bookmarks.bookmarkable_id
22-
-- LEFT JOIN stories ON bookmarks.bookmarkable_type = 'Story' AND stories.id = bookmarks.bookmarkable_id
23-
LEFT JOIN resources ON bookmarks.bookmarkable_type = 'Resource' AND resources.id = bookmarks.bookmarkable_id
24-
LEFT JOIN events ON bookmarks.bookmarkable_type = 'Event' AND events.id = bookmarks.bookmarkable_id
25-
SQL
26-
.order(Arel.sql("COALESCE(workshops.title, resources.title, events.title) ASC")) # stories.title,
27-
when "led"
28-
bookmarks = bookmarks.where(bookmarkable_type: "Workshop")
29-
.joins("INNER JOIN workshops ON bookmarks.bookmarkable_id = workshops.id")
30-
.order("workshops.led_count DESC")
31-
when "bookmark_count"
32-
counts = bookmarks.group(:bookmarkable_type, :bookmarkable_id)
33-
.select(:bookmarkable_type, :bookmarkable_id, "COUNT(*) AS total_bookmarks")
34-
bookmarks = bookmarks
35-
.joins("LEFT JOIN (#{counts.to_sql}) AS counts
36-
ON counts.bookmarkable_type = bookmarks.bookmarkable_type
37-
AND counts.bookmarkable_id = bookmarks.bookmarkable_id")
38-
.order(Arel.sql("COALESCE(counts.total_bookmarks,0) DESC"))
39-
when "created"
40-
bookmarks = bookmarks.order(created_at: :desc)
30+
def self.sorted(sort_by=nil) # sort and sort_by are namespaced
31+
sort_by ||= "newest"
32+
case sort_by
33+
when "newest" then self.sort_by_newest
34+
when "title" then self.sort_by_title
35+
when "popularity" then self.sort_by_popularity
36+
else self.sort_by_newest
4137
end
42-
43-
bookmarks
4438
end
4539

4640
def self.filter_by_params(params={})
@@ -49,26 +43,74 @@ def self.filter_by_params(params={})
4943
bookmarks = bookmarks.bookmarkable_type(params[:bookmarkable_type])
5044
bookmarks = bookmarks.bookmarkable_attributes(params[:bookmarkable_type],
5145
params[:bookmarkable_id])
52-
bookmarks = bookmarks.title(params[:title])
53-
bookmarks = bookmarks.user_name(params[:user_name])
54-
bookmarks = bookmarks.windows_type(params[:windows_type])
46+
bookmarks = bookmarks.title(params[:title]) if params[:title].present?
47+
bookmarks = bookmarks.user_name(params[:user_name]) if params[:user_name].present?
48+
bookmarks = bookmarks.windows_type(params[:windows_type]) if params[:windows_type].present?
5549

5650
bookmarks
5751
end
5852

53+
def self.sort_by_title
54+
bookmarks = self.joins(<<~SQL)
55+
LEFT JOIN community_news ON community_news.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'CommunityNews'
56+
LEFT JOIN events ON events.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Event'
57+
LEFT JOIN facilitators ON facilitators.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Facilitator'
58+
LEFT JOIN projects ON projects.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Project'
59+
LEFT JOIN resources ON resources.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Resource'
60+
LEFT JOIN stories ON stories.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Story'
61+
LEFT JOIN story_ideas ON story_ideas.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'StoryIdea'
62+
LEFT JOIN workshops ON workshops.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Workshop'
63+
LEFT JOIN workshop_ideas ON workshop_ideas.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'WorkshopIdea'
64+
LEFT JOIN workshop_logs ON workshop_logs.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'WorkshopLog'
65+
LEFT JOIN workshop_variations ON workshop_variations.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'WorkshopVariation'
66+
SQL
67+
bookmarks.order(Arel.sql(<<~SQL.squish)
68+
LOWER(
69+
COALESCE(
70+
community_news.title,
71+
events.title,
72+
CONCAT(facilitators.first_name, ' ', facilitators.last_name),
73+
projects.name,
74+
resources.title,
75+
stories.title,
76+
story_ideas.title,
77+
workshops.title,
78+
workshop_ideas.title,
79+
DATE_FORMAT(workshop_logs.date, '%Y-%m-%d'),
80+
workshop_variations.name
81+
)
82+
) ASC,
83+
bookmarks.created_at DESC
84+
SQL
85+
)
86+
end
87+
5988
def self.title(title)
6089
return all unless title.present?
6190

6291
bookmarks = self.all
6392
bookmarks = bookmarks.joins(<<~SQL)
64-
LEFT JOIN workshops ON workshops.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Workshop'
65-
-- LEFT JOIN stories ON stories.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Story'
66-
LEFT JOIN resources ON resources.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Resource'
67-
LEFT JOIN events ON events.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Event'
93+
LEFT JOIN community_news ON community_news.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'CommunityNews'
94+
LEFT JOIN events ON events.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Event'
95+
LEFT JOIN facilitators ON facilitators.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Facilitator'
96+
LEFT JOIN projects ON projects.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Project'
97+
LEFT JOIN resources ON resources.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Resource'
98+
LEFT JOIN stories ON stories.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Story'
99+
LEFT JOIN story_ideas ON story_ideas.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'StoryIdea'
100+
LEFT JOIN workshops ON workshops.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Workshop'
101+
LEFT JOIN workshop_ideas ON workshop_ideas.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'WorkshopIdea'
102+
LEFT JOIN workshop_logs ON workshop_logs.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'WorkshopLog'
103+
LEFT JOIN workshop_variations ON workshop_variations.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'WorkshopVariation'
68104
SQL
69105

70106
bookmarks.where(
71-
"workshops.title LIKE :title OR events.title LIKE :title OR resources.title LIKE :title", # OR stories.title LIKE :title
107+
"community_news.title LIKE :title OR events.title LIKE :title OR facilitators.first_name LIKE :title OR
108+
facilitators.last_name LIKE :title OR projects.name LIKE :title OR resources.title LIKE :title OR
109+
stories.title LIKE :title OR workshops.title LIKE :title OR workshop_ideas.title LIKE :title OR
110+
story_ideas.body LIKE :title OR -- searching body for story ideas (title exists but isn't used in UI)
111+
DATE_FORMAT(workshop_logs.date, '%Y-%m-%d') LIKE :title OR -- no title on workshop_logs
112+
workshop_variations.name LIKE :title -- searching name for workshop variations (title doesn't exist)
113+
",
72114
title: "%#{title}%"
73115
)
74116
end
@@ -143,13 +185,5 @@ def self.user_name(user_name)
143185
)
144186
end
145187

146-
def bookmarkable_image_url(fallback: 'missing.png')
147-
if bookmarkable.respond_to?(:images) && bookmarkable.images.first&.file&.attached?
148-
Rails.application.routes.url_helpers.rails_blob_path(bookmarkable.images.first.file, only_path: true)
149-
elsif bookmarkable_type == "Workshop"
150-
ActionController::Base.helpers.asset_path("workshop_default.jpg")
151-
else
152-
ActionController::Base.helpers.asset_path(fallback)
153-
end
154-
end
188+
155189
end

app/views/bookmarks/_personal_row.html.erb

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<!-- Image -->
2020
<div class="w-12 h-8 bg-gray-100 rounded overflow-hidden">
2121
<%= link_to polymorphic_path(bookmarkable) do %>
22-
<%= image_tag(bookmark.bookmarkable_image_url, class: "object-cover w-full h-full",
22+
<%= image_tag(bookmark.decorate.bookmarkable_image_url, class: "object-cover w-full h-full",
2323
alt: "#{ bookmarkable.object.class.name } image") %>
2424
<% end %>
2525
</div>
@@ -32,52 +32,64 @@
3232

3333

3434
<!-- 2️⃣ Title / author / metadata -->
35-
<div class="flex flex-col gap-1 min-w-[40ch] max-w-[40ch] break-words">
35+
<div class="flex flex-col gap-1 min-w-[60ch] max-w-[60ch] break-words">
3636
<h3 class="text-lg font-bold text-gray-800 flex items-center gap-2">
37-
<%= link_to "#{bookmarkable.title}#{" [HIDDEN]" if bookmarkable.inactive?}",
37+
<% if bookmarkable.respond_to?(:type) %>
38+
<% type_name = " (#{bookmarkable.type})" %>
39+
<% elsif bookmarkable.respond_to?(:windows_type) %>
40+
<% type_name = " (#{bookmarkable.windows_type.short_name})" if bookmarkable.windows_type %>
41+
<% else %>
42+
<% type_name = "" %>
43+
<% end %>
44+
45+
<%= link_to "#{bookmarkable.title}#{ type_name }#{" [HIDDEN]" if bookmarkable.inactive?}",
3846
polymorphic_path(bookmarkable),
3947
class: "hover:underline",
4048
data: { turbo: false, } %>
4149
</h3>
50+
<div class="text-sm text-gray-600">
51+
You bookmarked on
52+
<%= bookmark.created_at.strftime("%B %d, %Y") %>
53+
</div>
54+
<div class="text-sm text-gray-600">
55+
Bookmarked by community
56+
<%= bookmark.bookmarkable.bookmarks_count %> times
57+
</div>
4258
</div>
4359

4460
<!-- 3️⃣ Description -->
4561
<div class="flex-1 text-sm text-gray-700">
4662
<div class="text-sm text-gray-600">
47-
Author:
48-
<% if bookmark.bookmarkable_type == "Workshop" && bookmarkable.user %>
49-
<%= link_to bookmarkable.author_name,
63+
Created by:
64+
<% if bookmarkable.respond_to?(:user) && bookmarkable.user %>
65+
<%= link_to bookmarkable.user.full_name,
5066
generate_facilitator_user_path(bookmarkable.user),
5167
class: "hover:underline",
5268
data: { turbo: false, } %>
5369
<% elsif bookmark.bookmarkable_type == "Workshop" %>
5470
<%= bookmarkable.full_name.present? ? bookmarkable.full_name : "Facilitator" %>
55-
<% elsif bookmarkable.respond_to?(:created_by) %>
56-
<%= bookmarkable.created_by.present? ? bookmarkable.created_by.name : "User" %>
71+
<% elsif bookmarkable.respond_to?(:created_by) && bookmarkable.created_by %>
72+
<%= link_to bookmarkable.object.created_by.full_name,
73+
generate_facilitator_user_path(bookmarkable.created_by),
74+
class: "hover:underline",
75+
data: { turbo: false, } %>
76+
<% else %>
77+
User
5778
<% end %>
5879
</div>
5980

6081
<div class="text-sm text-gray-600">
6182
<span>
62-
Type/date:
63-
<% if bookmark.bookmarkable_type == "Workshop" && bookmarkable.windows_type.present? %>
64-
<%= "#{bookmarkable.windows_type.short_name} - #{bookmarkable.date}" %>
65-
<% elsif bookmark.bookmarkable_type == "Event" %>
83+
Date:
84+
<% if bookmark.bookmarkable_type == "Workshop" || bookmark.bookmarkable_type == "Event" %>
6685
<%= bookmarkable.date %>
6786
<% else %>
6887
<%= bookmarkable.created_at.strftime("%B %d, %Y") %>
6988
<% end %>
7089
<% if bookmark.bookmarkable_type == "Workshop" && bookmarkable.age_ranges.any? %>
71-
(<%= bookmarkable.age_ranges.pluck(:name).to_sentence %>)
90+
<br>Age ranges: <%= bookmarkable.age_ranges.pluck(:name).to_sentence %>
7291
<% end %>
7392
</span>
7493
</div>
75-
76-
<div class="text-sm text-gray-600">
77-
<span>
78-
Bookmarked on:
79-
<%= bookmark.created_at.strftime("%B %d, %Y") %>
80-
</span>
81-
</div>
8294
</div>
8395
</div>

app/views/bookmarks/_search_fields.html.erb

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,9 @@
3232
<%= label_tag :bookmarkable_type, "Bookmark type", class: "block text-sm font-medium text-gray-700 mb-1" %>
3333
<div class="relative">
3434
<%= select_tag :bookmarkable_type,
35-
options_for_select([["All types", ""],
36-
["Workshops", "Workshop"],
37-
["Stories", "Story"],
38-
["Resources", "Resource"],
39-
["Community news", "CommunityNews"],
40-
["Events", "Event"],
41-
],
35+
options_for_select(@bookmarkable_types,
4236
params[:bookmarkable_type]),
37+
include_blank: "All types",
4338
class: "w-full rounded-lg border border-gray-300 px-3 py-2 text-gray-800 shadow-sm
4439
focus:border-blue-500 focus:ring focus:ring-blue-200 focus:outline-none",
4540
onchange: "this.form.submit()" %>
@@ -62,7 +57,6 @@
6257

6358
<!-- Clear filters / placeholder for additional fields -->
6459
<div class="flex items-center space-x-2">
65-
<%#= render 'workshops/windows_type_fields', windows_types: @windows_types %>
6660
<%= link_to 'Clear filters', bookmarks_path, class: "btn btn-utility-outline" %>
6761
</div>
6862
</div>

0 commit comments

Comments
 (0)