Add a gallery page of a member's past gardens

/members/:slug/gardens/past shows their inactive gardens as photo cards:
when each was in use, how many plantings, and the crops grown in it. The
owner (or a collaborator) can mark a garden active again from its card.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Brenda WallaceandClaude Sonnet 5 committed 2026-09-22 17:42:26 +12:00
1 parent 1e80abb70c
commit dcf59ff4e7
8 files changed
+186

No files matched your search

+38
View File
@@ -0,0 +1,38 @@
// A member's past gardens: a gallery of cards, a photo and what was grown.
.past-garden-card {
overflow: hidden;
.card-img-top {
display: block;
width: 100%;
aspect-ratio: 3 / 2;
object-fit: cover;
}
.card-title {
font-weight: 500;
}
.past-garden-meta {
color: #666;
font-size: 0.9rem;
}
.past-garden-crops {
display: flex;
flex-wrap: wrap;
margin: 0;
padding: 0;
li {
margin: 0 0.5rem 0.5rem 0;
}
}
.past-garden-more {
align-self: center;
color: #666;
font-size: 0.9rem;
}
}
+1
View File
@@ -24,6 +24,7 @@
@import "notifications";
@import "plantings";
@import "garden_cards";
@import "past_gardens";
@import "photos";
@import "posts";
@import "seeds";
+15
View File
@@ -1,6 +1,10 @@
# frozen_string_literal: true
class GardensController < DataController
# Anyone can look at a member's past gardens, as they can their current ones.
skip_before_action :authenticate_member!, only: :past
skip_load_and_authorize_resource only: :past
def index
@owner = Member.find_by!(slug: params[:member_slug]) if params[:member_slug].present?
@show_all = params[:all] == '1'
@@ -17,6 +21,17 @@ class GardensController < DataController
respond_with(@gardens)
end
# A member's gardens that are no longer active, as a gallery of what they grew.
def past
@owner = Member.confirmed.find_by!(slug: params[:member_slug])
owned = Garden.inactive.where(owner: @owner)
collaborating = Garden.inactive.where(id: GardenCollaborator.where(member: @owner).select(:garden_id))
@gardens = owned.or(collaborating)
.includes(:owner, plantings: { crop: { parent: :parent } })
.order(updated_at: :desc)
.paginate(page: params[:page], per_page: 12)
end
def show
@current_plantings = @garden.plantings.current.where.not(failed: true).includes(:crop, :owner).order(planted_at: :desc)
@current_activities = @garden.activities.current.includes(:owner).order(created_at: :desc)
+9
View File
@@ -11,6 +11,15 @@ module GardensHelper
}
end
# "2022–2024 · 12 plantings": when a past garden was in use, by its plantings.
def garden_history_summary(garden)
plantings = garden.plantings.to_a
years = plantings.filter_map { |planting| planting.planted_at&.year }
years = [garden.created_at.year] if years.empty?
range = years.min == years.max ? years.min.to_s : "#{years.min}–#{years.max}"
"#{range} · #{pluralize(plantings.size, 'planting')}"
end
def display_garden_description(garden)
if garden.description.nil?
"no description provided."
+23
View File
@@ -0,0 +1,23 @@
- crops = garden.plantings.map(&:crop).uniq
- shown = 8
.card.h-100.past-garden-card
= link_to garden_path(garden) do
= image_tag garden_image_path(garden), alt: "Image of #{garden.name}", class: 'card-img-top'
.card-body
%h2.h5.card-title= link_to garden.name, garden_path(garden)
%p.past-garden-meta= garden_history_summary(garden)
- if garden.description.present?
%p.card-text= truncate(garden.description, length: 130, separator: ' ', omission: '...')
- if crops.any?
%ul.past-garden-crops.list-unstyled{'aria-label': 'Grown here'}
- crops.first(shown).each do |crop|
%li
= link_to crop_path(crop) do
.chip.crop-chip
= crop_icon(crop)
= crop.name
- if crops.size > shown
%li.past-garden-more= "and #{crops.size - shown} more"
- if can?(:edit, garden)
.card-footer
= garden_mark_active_button(garden, classes: 'btn btn-sm btn-outline-success')
+31
View File
@@ -0,0 +1,31 @@
- content_for :title, "#{@owner}'s past gardens"
- content_for :breadcrumbs do
%li.breadcrumb-item= link_to 'Gardens', gardens_path
%li.breadcrumb-item= link_to "#{@owner}'s gardens", member_gardens_path(@owner)
%li.breadcrumb-item.active= link_to 'Past gardens', member_past_gardens_path(@owner)
%h1 #{@owner}'s past gardens
.row
.col-md-2
%small
%a{href: "#content"}
Skip to main content
= render 'layouts/nav', model: Garden
= link_to "Current gardens", member_gardens_path(@owner), class: 'nav-link'
%hr/
= render @owner
.col-md-10#content
- if @gardens.empty?
%p
There are no past gardens to show.
- if @owner == current_member
A garden you mark as inactive will appear here.
- else
%p.lead Gardens that are no longer in use, and what was grown in them.
.row.row-cols-1.row-cols-sm-2.row-cols-lg-3.g-4
- @gardens.each do |garden|
.col= render 'gardens/past_card', garden: garden
.mt-3= will_paginate @gardens
+2
View File
@@ -115,6 +115,8 @@ Rails.application.routes.draw do
resources :timeline
resources :members, param: :slug do
# Before the gardens resource, so "past" isn't taken for a garden's id.
get 'gardens/past' => 'gardens#past', as: :past_gardens
resources :gardens
resources :seeds
resources :plantings
@@ -0,0 +1,67 @@
# frozen_string_literal: true
require 'rails_helper'
describe "A member's past gardens" do
let(:owner) { create(:member, login_name: 'gardener') }
let(:tomato) { create(:tomato) }
let!(:retired) { create(:inactive_garden, owner: owner, name: 'Old plot', description: 'Where it all began') }
let!(:current) { create(:garden, owner: owner, name: 'Current plot') }
before do
create(:planting, owner: owner, garden: retired, crop: tomato, planted_at: Date.new(2021, 10, 1))
create(:planting, owner: owner, garden: retired, crop: create(:maize), planted_at: Date.new(2023, 2, 1))
end
context 'when nobody is signed in' do
before { visit member_past_gardens_path(owner) }
it 'shows only the inactive gardens, with when they were used and what was grown' do
expect(page).to have_link 'Old plot', href: garden_path(retired)
expect(page).to have_no_text 'Current plot'
expect(page).to have_text '2021–2023 · 2 plantings'
expect(page).to have_text 'Where it all began'
expect(page).to have_link tomato.name, href: crop_path(tomato)
end
it 'does not offer to reactivate' do
expect(page).to have_no_link 'Mark as active'
end
it 'links back to the current gardens' do
expect(page).to have_link 'Current gardens', href: member_gardens_path(owner)
end
end
context 'when the owner is signed in' do
include_context 'signed in member'
let(:owner) { member }
before { visit member_past_gardens_path(owner) }
it 'lets them reactivate a garden' do
click_link 'Mark as active'
expect(retired.reload).to be_active
end
end
context "when someone else's past gardens" do
include_context 'signed in member'
before { visit member_past_gardens_path(owner) }
it 'does not let a signed in visitor reactivate them' do
expect(page).to have_link 'Old plot'
expect(page).to have_no_link 'Mark as active'
end
end
context 'with no past gardens' do
before { visit member_past_gardens_path(create(:member)) }
it 'says so' do
expect(page).to have_text 'There are no past gardens to show.'
end
end
end