Compare commits

...

90 Commits

Author SHA1 Message Date
Daniel O'Connor
75807ba2af Merge branch 'dev' of https://github.com/Growstuff/growstuff into datepicker 2025-09-01 13:22:47 +00:00
Daniel O'Connor
b69d1bd14b Merge pull request #4189 from Growstuff/remove-openfarm-service
Remove openfarm service
2025-09-01 22:34:52 +09:30
Daniel O'Connor
468e34a551 Remove openfarm service 2025-09-01 12:56:22 +00:00
Daniel O'Connor
8385beb406 Merge pull request #4188 from Growstuff/remove-dead-gems
Remove haml-lint-extractor
2025-09-01 21:55:26 +09:30
google-labs-jules[bot]
0f4803392d Add seed source to Seed model (#4186)
* Add seed source to Seed model

* Update _form.html.haml

* Add to schema

* Default option

* Default option

* Fix test

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: Daniel O'Connor <daniel.oconnor@gmail.com>
2025-09-01 21:47:31 +09:30
Daniel O'Connor
d185ce495f Remove haml-lint-extractor 2025-09-01 12:13:08 +00:00
Daniel O'Connor
90bd70603b Add a lot of indexes (#4187) 2025-09-01 21:06:58 +09:30
Daniel O'Connor
a4db05c0f6 Add a lot of indexes 2025-09-01 11:25:02 +00:00
google-labs-jules[bot]
0079513b35 Merge pull request #4183 from Growstuff/feature/timeline-likes
Feature: Display likes on timeline
2025-09-01 19:51:24 +09:30
Daniel O'Connor
508ee5260d Merge pull request #4180 from Growstuff/fix/profile-bio-link
Fix: Only show 'add a bio' link on own profile
2025-09-01 15:39:52 +09:30
google-labs-jules[bot]
ee2fffd25b Fix: Only show 'add a bio' link on own profile
The 'add a bio' link on the member profile page was previously shown
based on the `can? :edit, @member` ability check. This caused an issue
for admins, who could see the link on other users' profiles, but the
link would incorrectly lead to their own settings page.

This change modifies the condition to be `member_signed_in? && current_member == @member`.
This ensures the link is only displayed when a logged-in user is
viewing their own profile, which is the correct and intended behavior.
2025-08-31 22:48:41 +00:00
Daniel O'Connor
c92b912b28 Fix link 2025-08-31 15:46:18 +09:30
dependabot[bot]
f665fba91a Merge pull request #4077 from Growstuff/dependabot/bundler/terser-1.2.6 2025-08-31 06:02:22 +00:00
Daniel O'Connor
3578fbdf64 Merge branch 'dev' into dependabot/bundler/terser-1.2.6 2025-08-31 15:24:25 +09:30
Daniel O'Connor
0289786891 Seeds for trade - avoid showing expired seeds on homepage. (#4176)
* Improve date visibility

* Ensure when seeding seeds, it's false

* Typo
2025-08-31 14:39:42 +09:30
Daniel O'Connor
7dc66cb199 Merge pull request #4173 from Growstuff/translate-confirm
Garden Delete - Extract strings and fix missing translation bug
2025-08-31 14:39:32 +09:30
Daniel O'Connor
417602cb85 Merge pull request #4175 from Growstuff/btn-group-vertical
Crops > Card > Apply Btn group vertical
2025-08-31 14:20:10 +09:30
Daniel O'Connor
7a2760b086 Fix text display wonkyness 2025-08-31 04:21:01 +00:00
Daniel O'Connor
9e0e21b565 Fix display 2025-08-31 04:19:07 +00:00
Daniel O'Connor
a481cb7c06 Merge pull request #4171 from Growstuff/feature/pwa-install-instructions
Add PWA installation instructions to homepage
2025-08-31 13:17:22 +09:30
Daniel O'Connor
a177a53d57 Merge pull request #4172 from Growstuff/fix-width
Fix width of ready to harvest
2025-08-31 13:17:09 +09:30
Daniel O'Connor
abbe9ee7ca Update spec/features/home/home_spec.rb 2025-08-31 13:01:51 +09:30
Daniel O'Connor
48d2f0a224 Fix width of ready to harvest 2025-08-31 03:30:24 +00:00
Daniel O'Connor
074644d5c8 Adjust specs 2025-08-31 03:27:04 +00:00
Daniel O'Connor
2e2e47d405 Make links bold, not all of the stats text 2025-08-31 03:25:56 +00:00
Daniel O'Connor
0544abcd8f Github lure 2025-08-31 03:16:28 +00:00
Daniel O'Connor
73fb43fc4f Styling 2025-08-31 03:00:07 +00:00
Daniel O'Connor
532afafa0e Restyle slightly 2025-08-31 02:55:02 +00:00
google-labs-jules[bot]
378bd0d8f1 feat: Add PWA installation instructions to homepage
This commit adds instructions for mobile users on how to install the Growstuff website as a Progressive Web App (PWA).

The changes include:
- A new section on the homepage with instructions for both iOS and Android devices. This section is only visible to logged-out users.
- New translations for the instructions in the `en.yml` locale file.
- Basic styling for the new section.
- Updated feature tests to verify the new section's visibility.
2025-08-31 02:25:55 +00:00
Daniel O'Connor
bb943fe0c6 Merge pull request #4168 from Growstuff/menu
Fix Menu (a bit), Fix mobile UX for Crops
2025-08-30 02:17:15 +09:30
Daniel O'Connor
28742d4936 Fix crop button annoyance 2025-08-29 16:33:42 +00:00
Daniel O'Connor
d7e28e79fe Improve menu again 2025-08-29 16:22:55 +00:00
Daniel O'Connor
322bca9281 Merge pull request #4166 from Growstuff/age_in_days
Deal with age_in_days.nil?
2025-08-30 01:15:25 +09:30
Daniel O'Connor
7c081541eb Deal with age_in_days.nil? 2025-08-29 23:46:27 +09:30
Daniel O'Connor
475514a8f0 Merge pull request #4164 from Growstuff/bg-dark
Partially improve menu on mobile
2025-08-29 23:21:35 +09:30
Daniel O'Connor
bb3fd3bcb7 Merge pull request #4155 from Growstuff/remove-openfarm.cc
Remove openfarm.cc links
2025-08-29 23:21:20 +09:30
Daniel O'Connor
5c86622823 Partially improve menu on mobile 2025-08-29 12:51:32 +00:00
Daniel O'Connor
fefd387481 Merge pull request #4162 from Growstuff/fix-failed
Fix current plantings not to show failed
2025-08-29 21:38:20 +09:30
Daniel O'Connor
93de9b35bc Fix current plantings not to show failed 2025-08-29 11:51:46 +00:00
Daniel O'Connor
3d62cfcf81 Update 20240810160507_add_language_to_alternate_names.rb
Fix migration
2025-08-29 20:12:59 +09:30
Daniel O'Connor
8044640023 Merge branch 'dev' into remove-openfarm.cc 2025-08-29 19:56:09 +09:30
Daniel O'Connor
ab29de3d04 Remove dead test 2025-08-27 14:45:57 +00:00
Daniel O'Connor
9ae90c1b7e Merge branch 'dev' into remove-openfarm.cc 2025-08-27 23:43:29 +09:30
Daniel O'Connor
e7090631ab Remove partial 2025-08-27 14:12:13 +00:00
Daniel O'Connor
fadf5154e4 Remove openfarm links (defunct) 2025-08-27 14:11:01 +00:00
Daniel O'Connor
13ed098172 Remove openfarm links (defunct) 2025-08-27 14:09:55 +00:00
dependabot[bot]
6d44a2a780 Bump terser from 1.2.5 to 1.2.6
Bumps [terser](https://github.com/ahorek/terser-ruby) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/ahorek/terser-ruby/releases)
- [Changelog](https://github.com/ahorek/terser-ruby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ahorek/terser-ruby/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: terser
  dependency-version: 1.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 04:49:25 +00:00
Daniel O'Connor
62406a6573 Merge branch 'dev' into datepicker 2025-03-29 14:24:22 +10:30
Daniel O'Connor
53e90f57dd Merge branch 'dev' into datepicker 2024-10-14 00:41:45 +10:30
Daniel O'Connor
54acc2c108 Merge branch 'dev' into datepicker 2024-10-14 00:10:34 +10:30
Daniel O'Connor
0639cd5ab8 Merge branch 'dev' into datepicker 2024-10-13 23:54:46 +10:30
Daniel O'Connor
41c4ae1448 Merge branch 'dev' into datepicker 2024-10-13 23:51:09 +10:30
Daniel O'Connor
99221d7cf3 Merge branch 'dev' into datepicker 2024-10-13 23:40:14 +10:30
Daniel O'Connor
eeb8c84c31 Merge branch 'dev' into datepicker 2024-10-13 23:30:36 +10:30
Daniel O'Connor
e65dd4ef85 Merge branch 'dev' into datepicker 2024-10-13 23:05:10 +10:30
Daniel O'Connor
40ed3c8007 Merge branch 'dev' into datepicker 2024-10-13 22:54:03 +10:30
Daniel O'Connor
035decb132 Update spec/features/plantings/planting_a_crop_spec.rb 2024-10-13 22:53:17 +10:30
Daniel O'Connor
7102552bfe Update spec/features/seeds/adding_seeds_spec.rb 2024-10-13 22:52:39 +10:30
Daniel O'Connor
e1270512dc Update app/views/seeds/_form.html.haml 2024-10-13 22:52:05 +10:30
Daniel O'Connor
8d40355355 Update app/views/harvests/_form.html.haml 2024-10-13 22:51:04 +10:30
Daniel O'Connor
919c3dbf37 Merge branch 'dev' into datepicker 2024-10-13 22:47:40 +10:30
Daniel O'Connor
3e7a58fcfc Merge branch 'dev' into datepicker 2024-10-13 22:38:28 +10:30
Daniel O'Connor
fc301558e1 Merge branch 'dev' into datepicker 2024-10-13 22:21:39 +10:30
Daniel O'Connor
2523bea154 Merge branch 'dev' into datepicker 2024-10-13 22:11:42 +10:30
Daniel O'Connor
f89d64ac3a Mark spec pending 2024-10-13 10:28:29 +00:00
Daniel O'Connor
65a0540e3d Mark spec pending 2024-10-13 10:19:51 +00:00
Daniel O'Connor
654aa318c4 Fix specs 2024-10-13 10:05:52 +00:00
Daniel O'Connor
5128c8be0e Fix specs 2024-10-13 09:55:20 +00:00
Daniel O'Connor
0e1578aeef Fix specs 2024-10-13 09:45:10 +00:00
Daniel O'Connor
c6b5cc61da Fix specs 2024-10-13 09:30:36 +00:00
Daniel O'Connor
dddc85c338 Fix specs 2024-10-13 09:19:10 +00:00
Daniel O'Connor
69e764dd63 Fix specs 2024-10-13 09:01:29 +00:00
Daniel O'Connor
8aa0f98196 Fix specs 2024-10-13 09:00:00 +00:00
Daniel O'Connor
4c8c54eadd Fix specs 2024-10-13 08:48:42 +00:00
Daniel O'Connor
9c6797e850 Swap to html5 control 2024-10-13 08:17:40 +00:00
Daniel O'Connor
a5f774f043 Spec no longer possible 2024-10-13 03:59:12 +00:00
Daniel O'Connor
fe0d4295be Spec no longer possible 2024-10-13 03:57:53 +00:00
Daniel O'Connor
c5bdac4a5c Merge branch 'dev' of https://github.com/Growstuff/growstuff into datepicker 2024-10-13 03:56:30 +00:00
Daniel O'Connor
195602288c Fix test 2024-10-13 03:29:29 +00:00
Daniel O'Connor
c78a347002 Mark required 2024-10-13 02:59:41 +00:00
Daniel O'Connor
5178e1257e Merge branch 'dev' into datepicker 2024-10-13 13:16:22 +10:30
Daniel O'Connor
03f8acdd1d Add placehikder 2024-10-13 02:41:22 +00:00
Daniel O'Connor
426cb3ed37 Remove redundant UI element 2024-10-13 02:36:16 +00:00
Daniel O'Connor
4716b33d82 Mark required 2024-10-13 02:33:35 +00:00
Daniel O'Connor
329c3ddddd Mark required 2024-10-13 02:31:59 +00:00
Daniel O'Connor
3405d224b4 Add input validations 2024-10-13 02:29:17 +00:00
Daniel O'Connor
bd858a0b23 Mark required 2024-10-13 02:26:27 +00:00
Daniel O'Connor
defc3def4f Allow autosuggests to be required 2024-10-13 02:26:16 +00:00
Daniel O'Connor
4b0e228525 Swap to native datepicker 2024-10-13 02:24:20 +00:00
Daniel O'Connor
0f9e151c15 Swap to native datepicker 2024-10-13 02:22:49 +00:00
56 changed files with 341 additions and 245 deletions

View File

@@ -178,7 +178,6 @@ group :development, :test do
gem 'dotenv-rails'
# cli utils
gem 'haml-i18n-extractor', require: false
gem 'haml_lint', '>= 0.25.1', require: false # Checks haml files for goodness
gem 'i18n-tasks', require: false # adds tests for finding missing and unused translations
gem 'rspectre', require: false # finds unused code in specs

View File

@@ -294,12 +294,6 @@ GEM
temple (>= 0.8.2)
thor
tilt
haml-i18n-extractor (0.5.9)
activesupport
haml
highline
tilt
trollop (= 1.16.2)
haml-rails (2.1.0)
actionpack (>= 5.1)
activesupport (>= 5.1)
@@ -694,14 +688,13 @@ GEM
temple (0.10.4)
terminal-table (4.0.0)
unicode-display_width (>= 1.1.1, < 4)
terser (1.2.5)
terser (1.2.6)
execjs (>= 0.3.0, < 3)
thor (1.4.0)
thread_safe (0.3.6)
tilt (2.6.1)
timecop (0.9.10)
timeout (0.4.3)
trollop (1.16.2)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (3.1.5)
@@ -788,7 +781,6 @@ DEPENDENCIES
gibbon (~> 1.2.0)
gravatar-ultimate
haml
haml-i18n-extractor
haml-rails
haml_lint (>= 0.25.1)
hashie (>= 3.5.3)

View File

@@ -1,7 +1,13 @@
.crop-icon {
height: 1em;
}
.card-footer {
.btn-group-vertical {
.btn {
text-wrap: initial
}
}
}
.crop-thumbnail {
.text {
bottom: 0;

View File

@@ -1,6 +1,8 @@
// stats shown on homepage. eg. "999 members..."
.stats {
font-weight: bold;
a {
font-weight: bold;
}
}
.crops,

View File

@@ -10,9 +10,33 @@
width: 100%;
}
.navbar .nav > li {
#navbarSupportedContent {
ul {
flex-direction: column-reverse;
flex-wrap: nowrap;
li.nav-item {
display: block;
a {
display: grid;
grid-template-columns: 2em 1fr 2em;
}
a.dropdown-toggle::after {
width: 100%;
text-align: right;
}
}
}
}
.crop-actions {
flex-direction: column;
width: 100%;
a {
margin: auto;
display: block;
}
}
.navbar .navbar-form {
padding-left: 0;

View File

@@ -10,6 +10,7 @@ body {
.navbar {
flex-wrap: nowrap;
align-items: flex-start
}
.navbar-brand {
.site-name {
@@ -367,9 +368,6 @@ ul.thumbnail-buttons {
h1 {
font-size: 400%;
}
.stats a {
color: $black;
}
// signup widget on homepage
.signup {

View File

@@ -4,7 +4,7 @@ class GardensController < DataController
def index
@owner = Member.find_by(slug: params[:member_slug])
@show_all = params[:all] == '1'
@show_jump_to = params[:member_slug].present? ? true : false
@show_jump_to = params[:member_slug].present? || false
@gardens = @gardens.includes(:owner)
@gardens = @gardens.active unless @show_all
@@ -18,7 +18,7 @@ class GardensController < DataController
end
def show
@current_plantings = @garden.plantings.current.includes(:crop, :owner).order(planted_at: :desc)
@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)
@finished_plantings = @garden.plantings.finished.includes(:crop)
@suggested_companions = Crop.approved.where(

View File

@@ -19,9 +19,7 @@ class SeedsController < DataController
where['parent_planting'] = @planting.id
end
if params[:tradeable_to].present?
where['tradeable_to'] = params[:tradeable_to]
end
where['tradeable_to'] = params[:tradeable_to] if params[:tradeable_to].present?
@show_all = (params[:all] == '1')
where['finished'] = false unless @show_all
@@ -45,6 +43,7 @@ class SeedsController < DataController
def new
@seed = Seed.new
@seed.source = 'my own seed saving'
if params[:planting_slug]
@planting = Planting.find_by(slug: params[:planting_slug])
@@ -58,6 +57,8 @@ class SeedsController < DataController
def create
@seed = Seed.new(seed_params)
@seed.source ||= 'my own seed saving'
@seed.finished ||= false
@seed.owner = current_member
@seed.crop = @seed.parent_planting.crop if @seed.parent_planting
flash[:notice] = "Successfully added #{@seed.crop} seed to your stash." if @seed.save
@@ -85,7 +86,7 @@ class SeedsController < DataController
:crop_id, :description, :quantity, :plant_before,
:parent_planting_id, :saved_at,
:days_until_maturity_min, :days_until_maturity_max,
:organic, :gmo,
:organic, :gmo, :source,
:heirloom, :tradable_to, :slug,
:finished, :finished_at
)

View File

@@ -21,6 +21,10 @@ module ApplicationHelper
classes
end
def count_github_contibutors
File.open(Rails.root.join('CONTRIBUTORS.md')).readlines.grep(/^-/).size
end
# Produces a cache key for uniquely identifying cached fragments.
def cache_key_for(klass, identifier = "all")
count = klass.count

View File

@@ -53,7 +53,7 @@ module ButtonsHelper
link_to t('buttons.mark_as_inactive'),
garden_path(garden, garden: { active: 0 }),
method: :put, class: classes,
data: { confirm: 'All plantings associated with this garden will be marked as finished. Are you sure?' }
data: { confirm: I18n.t('gardens.confirm_deactivate') }
end
def create_button(model_to_create, path, icon, label)

View File

@@ -7,6 +7,8 @@ module EventHelper
def event_description(event)
render "#{event.event_type.pluralize}/description", event_model: resolve_model(event)
rescue ActionView::MissingTemplate
"#{event.event_type.humanize.downcase}d"
end
def resolve_model(event)

View File

@@ -39,10 +39,6 @@ module OpenFarmData
fetch_attr('common_names')
end
def guides_count
fetch_attr('guides_count')
end
def binomial_name
fetch_attr('binomial_name')
end

View File

@@ -94,9 +94,9 @@ module PredictPlanting
private
def calculate_percentage_grown
return 0 if age_in_days < 0
return 0 if age_in_days.to_i < 0
percent = (age_in_days / expected_lifespan.to_f) * 100
percent = (age_in_days.to_f / expected_lifespan.to_f) * 100
(percent > 100 ? 100 : percent)
end
end

View File

@@ -59,7 +59,8 @@ module SearchSeeds
search('*', limit:,
where: {
finished: false,
tradable: true
tradable: true,
_or: [{ plant_before: nil }, { plant_before: { lt: Date.today } }]
},
boost_by: [:created_at],
load: false)

View File

@@ -12,6 +12,8 @@ class Seed < ApplicationRecord
ORGANIC_VALUES = ['certified organic', 'non-certified organic', 'conventional/non-organic', 'unknown'].freeze
GMO_VALUES = ['certified GMO-free', 'non-certified GMO-free', 'GMO', 'unknown'].freeze
HEIRLOOM_VALUES = %w(heirloom hybrid unknown).freeze
SOURCE_VALUES = ['seed catalogue', 'retail outlet', 'seed bank or similar institution',
'traded from another person', 'my own seed saving', 'other/unknown'].freeze
#
# Relationships
@@ -44,6 +46,9 @@ class Seed < ApplicationRecord
validates :heirloom, allow_blank: false,
inclusion: { in: HEIRLOOM_VALUES, message: "You must say whether the seeds" \
"are heirloom, hybrid, or unknown" }
validates :source, allow_blank: true,
inclusion: { in: SOURCE_VALUES, message: "You must say where the seeds are from," \
"or that you don't know" }
#
# Delegations

View File

@@ -1,108 +0,0 @@
# frozen_string_literal: true
BASE = 'https://openfarm.cc/api/v1/'
# BASE = 'http://127.0.0.1:3000/api/v1/'
class OpenfarmService
def initialize
@cropbot = Member.find_by(login_name: 'cropbot')
end
def import!
Crop.all.order(updated_at: :desc).each do |crop|
Rails.logger.debug { "#{crop.id}, #{crop.name}" }
update_crop(crop) if crop.valid?
end
end
def update_crop(crop)
openfarm_record = fetch(crop.name)
if openfarm_record.present? && openfarm_record.is_a?(String)
Rails.logger.info(openfarm_record)
elsif openfarm_record.present? && openfarm_record.fetch('data', false)
crop.update! openfarm_data: openfarm_record.fetch('data', false)
save_companions(crop, openfarm_record)
save_photos(crop)
else
Rails.logger.debug "\tcrop not found on Open Farm"
crop.update!(openfarm_data: false)
end
end
def save_companions(crop, openfarm_record)
companions = openfarm_record.fetch('data').fetch('relationships').fetch('companions').fetch('data')
crops = openfarm_record.fetch('included', []).select { |rec| rec["type"] == 'crops' }
CropCompanion.transaction do
companions.each do |com|
companion_crop_hash = crops.detect { |c| c.fetch('id') == com.fetch('id') }
companion_crop_name = companion_crop_hash.fetch('attributes').fetch('name').downcase
companion_crop = Crop.where('lower(name) = ?', companion_crop_name).first
companion_crop = Crop.create!(name: companion_crop_name, requester: @cropbot, approval_status: "pending") if companion_crop.nil?
crop.companions << companion_crop unless crop.companions.where(id: companion_crop.id).any?
end
end
end
def save_photos(crop)
pictures = fetch_pictures(crop.name)
pictures.each do |picture|
data = picture.fetch('attributes')
Rails.logger.debug(data)
next unless data.fetch('image_url').start_with? 'http'
next if Photo.find_by(source_id: picture.fetch('id'), source: 'openfarm')
photo = Photo.new(
source_id: picture.fetch('id'),
source: 'openfarm',
owner: @cropbot,
thumbnail_url: data.fetch('thumbnail_url'),
fullsize_url: data.fetch('image_url'),
title: 'Open Farm photo',
license_name: 'No rights reserved',
link_url: "https://openfarm.cc/en/crops/#{name_to_slug(crop.name)}"
)
if photo.valid?
Photo.transaction do
photo.save
PhotoAssociation.find_or_create_by! photo:, photographable: crop
end
Rails.logger.debug { "\t saved photo #{photo.id} #{photo.source_id}" }
else
Rails.logger.warn "Photo not valid"
end
end
end
def fetch(name)
conn.get("crops/#{name_to_slug(name)}.json").body
rescue NoMethodError
Rails.logger.debug "error fetching crop"
Rails.logger.debug "BODY: "
Rails.logger.debug body
end
def name_to_slug(name)
CGI.escape(name.gsub(' ', '-').downcase)
end
def fetch_all(page)
conn.get("crops.json?page=#{page}").body.fetch('data', {})
end
def fetch_pictures(name)
body = conn.get("crops/#{name_to_slug(name)}/pictures.json").body
body.fetch('data', false)
rescue StandardError
Rails.logger.debug "Error fetching photos"
Rails.logger.debug []
end
private
def conn
Faraday.new BASE do |conn|
conn.response :json, content_type: /\bjson$/
conn.adapter Faraday.default_adapter
end
end
end

View File

@@ -18,10 +18,20 @@ class TimelineService
.union_all(photos_query)
.union_all(seeds_query)
.union_all(activities_query)
.union_all(likes_query)
.where.not(event_at: nil)
.order(event_at: :desc)
end
def self.likes_query
Like
.select("likes.id",
"'like' as event_type",
"likes.created_at as event_at",
"likes.member_id as owner_id",
"null as crop_id")
end
def self.activities_query
Activity.select(
:id,

View File

@@ -39,9 +39,7 @@
= link_to "Add a planting.", new_planting_path
.col-md-4
= f.date_field :due_date,
value: @activity.due_date ? @activity.due_date.to_fs(:ymd) : '',
label: 'When?'
= f.date_field :due_date, value: @activity.due_date ? @activity.due_date.to_fs(:ymd) : '', label: 'When?'
%hr

View File

@@ -1,6 +1,6 @@
- if crop.approved? && signed_in?
.btn-group{"aria-label" => "Crop Actions", role: "group"}
.btn-group.crop-actions{"aria-label" => "Crop Actions", role: "group"}
= render 'plantings/modal', planting: Planting.new(crop: crop, owner: current_member)
= render 'harvests/modal', harvest: Harvest.new(crop: @crop, owner: current_member)
= render 'seeds/modal', seed: Seed.new(crop: @crop, owner: current_member)

View File

@@ -25,9 +25,9 @@
Last harvest expected
%strong= crop.median_days_to_last_harvest
days after planting
- if member_signed_in?
.card-footer
.d-flex.justify-content-between
= render 'plantings/modal', planting: Planting.new(crop: crop, owner: current_member)
- #= render 'harvests/modal', harvest: Harvest.new(crop: crop, owner: current_member)
= render 'seeds/modal', seed: Seed.new(crop: crop, owner: current_member)
- if member_signed_in?
.card-footer
.d-flex.btn-group-vertical
= render 'plantings/modal', planting: Planting.new(crop: crop, owner: current_member)
- #= render 'harvests/modal', harvest: Harvest.new(crop: crop, owner: current_member)
= render 'seeds/modal', seed: Seed.new(crop: crop, owner: current_member)

View File

@@ -1,6 +0,0 @@
- if crop.guides_count.present? && crop.guides_count.positive?
%p
There are
= link_to "https://openfarm.cc/en/crops/#{CGI.escape @crop.name.gsub(' ', '-').downcase}" do
#{crop.guides_count} growing guides on Open Farm

View File

@@ -74,7 +74,6 @@
.card-body
%h4 How to grow #{@crop.name.pluralize}
= render 'grown_for', crop: @crop
= render 'planting_advice', crop: @crop
- if @crop.parent
%hr/
%p.parent-crop
@@ -124,13 +123,6 @@
= icon 'fas', 'external-link-alt'
Wikipedia (English)
%li.list-group-item
= link_to "https://openfarm.cc/en/crops/#{CGI.escape @crop.name.gsub(' ', '-')}",
class: 'card-link',
target: "_blank",
rel: "noopener noreferrer" do
= icon 'fas', 'external-link-alt'
OpenFarm - Growing guide
%li.list-group-item
= link_to "https://www.gardenate.com/plant/#{CGI.escape @crop.name}",
target: "_blank",

View File

@@ -15,5 +15,4 @@
- if can?(:destroy, garden)
.dropdown-divider
= delete_button(garden, classes: 'dropdown-item text-danger',
message: 'All plantings associated with this garden will also be deleted. Are you sure?')
= delete_button(garden, classes: 'dropdown-item text-danger', message: 'gardens.confirm_delete')

View File

@@ -66,8 +66,7 @@
- if can?(:destroy, @garden)
.dropdown-divider
= delete_button(@garden, classes: 'dropdown-item text-danger',
message: 'All plantings associated with this garden will also be deleted. Are you sure?')
= delete_button(@garden, classes: 'dropdown-item text-danger', message: 'gardens.confirm_delete')
%section
%h2 Current activities in garden

View File

@@ -30,7 +30,7 @@
= link_to "Request new crops.", new_crop_path
.col-md-4
= f.date_field :harvested_at, value: @harvest.harvested_at ? @harvest.harvested_at.to_fs(:ymd) : '', label: 'When?'
= f.date_field :harvested_at, value: @harvest.harvested_at ? @harvest.harvested_at.to_fs(:ymd) : '', label: 'When?', required: true
.col-12
= f.form_group :plant_part_id, label: { text: "Harvested Plant Part" } do
.row

View File

@@ -4,6 +4,6 @@
member: link_to(t('.member_linktext', count: Member.confirmed.size.to_i), members_path),
number_crops: link_to(t('.number_crops_linktext', count: Crop.count.to_i), crops_path),
number_plantings: link_to(t('.number_plantings_linktext', count: Planting.count.to_i), plantings_path),
number_gardens: link_to(t('.number_gardens_linktext', count: Garden.count.to_i), gardens_path))
number_gardens: link_to(t('.number_gardens_linktext', count: Garden.count.to_i), gardens_path),
contributors: link_to(count_github_contibutors, 'https://github.com/Growstuff/growstuff/blob/dev/CONTRIBUTORS.md', target: '_blank', rel: 'noopener'),
github: link_to('GitHub', 'http://github.com/Growstuff/growstuff', target: '_blank', rel: 'noopener'))

View File

@@ -8,6 +8,7 @@
%p= render 'stats', cached: true
.col
%br
%p
- if current_member.plantings.active.any?
= link_to member_path(current_member, anchor: "#content"), class: 'btn btn-dark' do
@@ -61,3 +62,16 @@
%section.members
= cute_icon
= render 'members', cached: true
.col-12.col-lg-6
%section.pwa-install
= cute_icon
%h2.text-center= t('home.pwa_title')
.index-cards
.card
.card-body
%h3= t('home.pwa_ios_title')
%p= t('home.pwa_ios_steps_html')
.card
.card-body
%h3= t('home.pwa_android_title')
%p= t('home.pwa_android_steps_html')

View File

@@ -6,6 +6,6 @@
%span.site-name Growstuff
.nav= render 'crops/search_bar'
.nav
%button.navbar-toggler{ "aria-controls" => "navbarSupportedContent", "aria-expanded" => "false", "aria-label" => "Toggle navigation", "data-bs-target" => "#navbarSupportedContent", "data-bs-toggle" => "collapse", type: "button" }
%i.fas.fa-ellipsis-v.navbar-toggler-icon
%button.navbar-toggler.ml-auto{ "aria-controls" => "navbarSupportedContent", "aria-expanded" => "false", "aria-label" => "Toggle navigation", "data-bs-target" => "#navbarSupportedContent", "data-bs-toggle" => "collapse", type: "button" }
%span.navbar-toggler-icon
= render 'layouts/menu'

View File

@@ -1,5 +1,5 @@
#navbarSupportedContent.collapse.navbar-collapse
%ul.navbar-nav.mr-auto
%ul.navbar-nav.mr-auto.bg-dark
- if signed_in?
%li.nav-item
= link_to timeline_index_path, method: :get, class: 'nav-link text-white' do
@@ -30,7 +30,9 @@
- cache("everyone-menu", expires_in: 1.week) do
%li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}= t('.crops')
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}
%span
= t('.crops')
.dropdown-menu
= link_to crops_path, class: 'dropdown-item' do
= t('.browse_crops')
@@ -44,7 +46,9 @@
= harvest_icon
= t('.harvests')
%li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}= t('.community')
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}
%span
= t('.community')
.dropdown-menu{"aria-labelledby" => "navbarDropdown"}
= link_to t('.community_map'), places_path, class: 'dropdown-item'
= link_to t('.browse_members'), members_path, class: 'dropdown-item'
@@ -54,7 +58,9 @@
- if member_signed_in?
- if current_member.role?(:crop_wrangler) || current_member.role?(:admin)
%li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}= t('.admin')
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}
%span
= t('.admin')
.dropdown-menu{"aria-labelledby" => "navbarDropdown"}
- if current_member.role?(:crop_wrangler)
= link_to t('.crop_wrangling'), wrangle_crops_path, class: 'dropdown-item'

View File

@@ -0,0 +1 @@
#{link_to event_model.member, event_model.member} liked #{link_to event_model.likeable.class.name.downcase, event_model.likeable}

View File

@@ -1,5 +1,5 @@
- cache member do
.card.card-double
.card
.card-body
%h4.login-name= link_to member, member
%div

View File

@@ -28,7 +28,7 @@
%a{href: "#content"}
Skip to main content
- if @member.bio.blank?
- if can? :edit, @member
- if member_signed_in? && current_member == @member
= link_to "Add a bio to complete your profile.", edit_member_registration_path
- else
#{@member.login_name} hasn't written a bio yet.
@@ -82,8 +82,8 @@
.col-md-10#content
.row
%section.order-3.order-md-1.col-12= render "map", member: @member
- if @harvesting.size.positive?
%section.harvests.order-2.order-md-1
- if @harvesting.size.positive?
%section.harvests.order-2.order-md-1.col-12
%h2 Ready to harvest
.index-cards
- @harvesting.each do |planting|

View File

@@ -21,7 +21,7 @@
- if planting.finish_is_predicatable?
.card.fact-card
%h3 Progress
- if planting.age_in_days < 0
- if planting.age_in_days.to_i < 0
%strong Planned
- else
%strong #{planting.age_in_days}/#{planting.expected_lifespan}
@@ -45,7 +45,7 @@
%h3 Growing
%strong= seedling_icon
%span
- if planting.age_in_days < 0
- if planting.age_in_days.to_i < 0
Planting planned
- else
Planting is still growing today

View File

@@ -32,7 +32,7 @@
label: 'Where did you plant it?')
= link_to "Add a garden.", new_garden_path
.col-md-4
= f.text_field :planted_at,
= f.date_field :planted_at,
value: @planting.planted_at ? @planting.planted_at.to_fs(:ymd) : '',
class: 'add-datepicker', label: 'When?', title: "Plan out your future plantings by forward dating, and subscribe to your iCalendar feed for reminders to plant"
@@ -50,9 +50,8 @@
= f.check_box :finished, label: t('buttons.mark_as_finished'), title: t('.finish_helper')
.col-md-6
= f.text_field :finished_at,
= f.date_field :finished_at,
value: @planting.finished_at ? @planting.finished_at.to_fs(:ymd) : '',
class: 'add-datepicker',
label: 'Finished date',
placeholder: 'optional'
.row

View File

@@ -39,10 +39,10 @@
- elsif @planting.percentage_grown.present?
#{@planting.percentage_grown.to_i}%
- if @planting.finish_is_predicatable?
- if @planting.age_in_days < 0
- if @planting.age_in_days.to_i < 0
%strong Planned
- else
%strong #{@planting.age_in_days}/#{@planting.expected_lifespan} days
%strong #{@planting.age_in_days.to_i}/#{@planting.expected_lifespan} days
= render 'timeline', planting: @planting
= render 'likes/likes', object: @planting

View File

@@ -16,14 +16,20 @@
%p
- if seed.quantity
.badge.badge-info #{seed.quantity} seeds
- if seed.organic != 'unknown'
.badge.badge-success.seedtitle--organic= seed.organic
- if seed.gmo != 'unknown'
.badge.badge-success.seedtitle--gmo= seed.gmo
- if seed.heirloom != 'unknown'
.badge.badge-success.seedtitle--heirloom= seed.heirloom
- if seed.tradable
.card-footer
.d-flex.w-100.justify-content-between
%ul
- if seed.organic != 'unknown'
%li
%small.seedtitle--organic= seed.organic
- if seed.gmo != 'unknown'
%li
%small.seedtitle--gmo= seed.gmo
- if seed.heirloom != 'unknown'
%li
%small.seedtitle--heirloom= seed.heirloom
.card-footer
.d-flex.w-100.justify-content-between
- if seed.tradable
%small Will trade #{seed.tradable_to}
/ %a.btn.btn-sm{href: "#"} Request
- if seed.plant_before
%small Plant before #{seed.plant_before}

View File

@@ -28,38 +28,40 @@
= link_to "Request new crops.", new_crop_path
.row
.col-12.col-md-4
= f.text_field :saved_at,
= f.date_field :saved_at,
value: @seed.saved_at ? @seed.saved_at.to_fs(:ymd) : '',
class: 'add-datepicker', label: 'When were the seeds harvested/saved?'
label: 'When were the seeds harvested/saved?'
.col-12.col-md-4= f.number_field :quantity, label: 'Quantity', min: 1
.col-12.col-md-4
= f.text_field :plant_before, class: 'add-datepicker',
value: @seed.plant_before ? @seed.plant_before.to_fs(:ymd) : ''
= f.date_field :plant_before, value: @seed.plant_before ? @seed.plant_before.to_fs(:ymd) : ''
.row
.col-12.col-md-4
= f.check_box :finished, label: t('buttons.mark_as_finished')
.col-12.col-md-4
= f.text_field :finished_at, class: 'add-datepicker', value: @seed.finished_at ? @seed.finished_at.to_fs(:ymd) : ''
= f.date_field :finished_at, value: @seed.finished_at ? @seed.finished_at.to_fs(:ymd) : ''
.col-12.col-md-4
%span.help-inline= t('.finish_helper')
.row
-# TODO: Range control?
.col-md-6= f.number_field :days_until_maturity_min, label_as_placeholder: true, label: 'min', prepend: 'Days until maturity', min: 1
.col-md-6= f.number_field :days_until_maturity_max, label_as_placeholder: true, label: 'max', prepend: 'to', append: "days", min: 1
.row
.col-md-4
= f.select(:organic, Seed::ORGANIC_VALUES, {label: 'Organic?', wrapper: { class: 'required'}, required: true}, default: 'unknown')
.col-md-4
= f.select(:gmo, Seed::GMO_VALUES, {label: 'GMO?', wrapper: { class: 'required'}, required: true}, default: 'unknown')
.col-md-4
= f.select(:heirloom, Seed::HEIRLOOM_VALUES, {label: 'Heirloom?', wrapper: { class: 'required'}, required: true}, default: 'unknown')
.col-md-3
= f.select(:organic, Seed::ORGANIC_VALUES, { label: 'Organic?', wrapper: { class: 'required' }, required: true }, default: 'unknown')
.col-md-3
= f.select(:gmo, Seed::GMO_VALUES, { label: 'GMO?', wrapper: { class: 'required' }, required: true }, default: 'unknown')
.col-md-3
= f.select(:heirloom, Seed::HEIRLOOM_VALUES, { label: 'Heirloom?', wrapper: { class: 'required' }, required: true }, default: 'unknown')
.col-md-3
= f.select(:source, Seed::SOURCE_VALUES, { label: 'Source?', wrapper: { class: 'required' }, required: true }, default: 'unknown')
= f.text_area :description, rows: 6
%hr/
= t('.trade_help', site_name: ENV['GROWSTUFF_SITE_NAME'])
= f.select(:tradable_to, Seed::TRADABLE_TO_VALUES, {label: 'Will trade', wrapper: { class: 'required'}, required: true})
= f.select(:tradable_to, Seed::TRADABLE_TO_VALUES, { label: 'Will trade', wrapper: { class: 'required' }, required: true })
%span.help_inline
- if current_member.location.blank?
Don't forget to

View File

@@ -5,6 +5,5 @@
= edit_icon
.hide{id: "date--#{model.id}-#{field.to_s}"}
= bootstrap_form_for(model) do |f|
= f.date_field field,
value: model.send(field) ? model.send(field).to_fs(:ymd) : '', label: 'When?'
= f.date_field field, value: model.send(field) ? model.send(field).to_fs(:ymd) : '', label: 'When?'
= f.submit :save

View File

@@ -12,6 +12,5 @@
- elsif field_type == :select
= f.select field, collection
- elsif field_type == :date
= f.date_field field,
value: model.send(field) ? model.send(field).to_fs(:ymd) : '', label: 'When?'
= f.date_field field, value: model.send(field) ? model.send(field).to_fs(:ymd) : '', label: 'When?'
= f.submit :save

View File

@@ -0,0 +1,2 @@
- likeable = like.likeable
= render "timeline/likeables/#{likeable.class.name.downcase}", likeable: likeable

View File

@@ -14,6 +14,7 @@
= link_to owner, owner
= event_description(event)
= render 'timeline/photos', photo: resolve_model(event) if event.event_type == 'photo'
= render 'timeline/like', like: resolve_model(event) if event.event_type == 'like'
%small
- if event.event_at.present?
- if event.event_at.kind_of?(Date)

View File

@@ -0,0 +1 @@
= render 'timeline/photos', photo: likeable

View File

@@ -0,0 +1,6 @@
.card.my-2
.card-body
%blockquote.blockquote.mb-0
%p= truncate(likeable.body, length: 140)
%footer.blockquote-footer
= link_to "view post", likeable

View File

@@ -123,6 +123,8 @@ en:
no_plantings: no plantings
plantingsthumbnail: plantings/thumbnail
updated: Garden was successfully updated.
confirm_delete: All plantings associated with this garden will also be deleted. Are you sure?
confirm_deactivate: All plantings associated with this garden will be marked as finished. Are you sure?
harvests:
created: Harvest was successfully created.
harvest_something: Harvest something
@@ -175,7 +177,6 @@ en:
Our team includes volunteers from all walks of life and all skill levels. To get involved,
visit %{talk_link} or find more information on the %{wiki_link}.
get_involved_title: Get Involved
github_linktext: Github
open_data_body_html: >
We're building a database of crops, planting advice, seed sources, and other information that anyone
can use for free, under a %{creative_commons_link}. You can use this data for research, to build apps,
@@ -207,10 +208,15 @@ en:
view_all: View all seeds
stats:
member_linktext: "%{count} members"
message_html: So far, %{member} have planted %{number_crops} %{number_plantings} in %{number_gardens}.
message_html: So far, %{member} have planted %{number_crops} %{number_plantings} in %{number_gardens}; and %{contributors} people have contributed to our code on %{github}!
number_crops_linktext: "%{count} crops"
number_gardens_linktext: "%{count} gardens"
number_plantings_linktext: "%{count} times"
pwa_android_steps_html: 1. Tap the three dots in the top right corner of Chrome.<br>2. Tap <strong>Install app</strong> or <strong>Add to Home screen</strong>.
pwa_android_title: For Android
pwa_ios_steps_html: 1. Tap the <strong>Share</strong> button in Safari.<br>2. Scroll down and tap <strong>Add to Home Screen</strong>'.
pwa_ios_title: For iOS (iPhone/iPad)
pwa_title: Want to install Growstuff on your phone?
label:
days_until_harvest: "%{number} days"
weeks_until_harvest: "%{number} weeks until harvest"

View File

@@ -2,6 +2,6 @@
class AddLanguageToAlternateNames < ActiveRecord::Migration[7.2]
def change
add_column :alternate_names, :language, :string, null: false
add_column :alternate_names, :language, :string
end
end

View File

@@ -0,0 +1,6 @@
class AddSourceToSeeds < ActiveRecord::Migration[7.2]
def change
add_column :seeds, :source, :string
add_index :seeds, :source
end
end

View File

@@ -0,0 +1,53 @@
class AddIndexesCrops < ActiveRecord::Migration[7.2]
def change
add_index :alternate_names, :crop_id
add_index :alternate_names, :creator_id
add_index :alternate_names, :language
add_index :comments, %i(commentable_type commentable_id)
add_index :comments, :author_id
add_index :crop_companions, %i(crop_a_id crop_b_id)
add_index :crops, :creator_id
add_index :crops, :parent_id
add_index :follows, %i(follower_id followed_id)
add_index :forums, :owner_id
add_index :harvests, :crop_id
add_index :harvests, :owner_id
add_index :harvests, :plant_part_id
add_index :members_roles, %i(member_id role_id)
add_index :notifications, :sender_id
add_index :notifications, :recipient_id
add_index :orders_products, %i(order_id product_id)
add_index :photo_associations, :crop_id # TODO: Is this still in use?
add_index :photos, :owner_id
add_index :photos, :source_id
add_index :photos_plantings, %i(photo_id planting_id)
add_index :plant_parts, :slug, unique: true
add_index :plantings, :crop_id
add_index :plantings, :garden_id
add_index :plantings, :owner_id
add_index :plantings, :parent_seed_id
add_index :posts, :forum_id
add_index :scientific_names, :crop_id
add_index :scientific_names, :creator_id
add_index :seeds, :owner_id
add_index :seeds, :crop_id
add_index :seeds, :parent_planting_id
end
end

View File

@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.2].define(version: 2025_08_24_162600) do
ActiveRecord::Schema[7.2].define(version: 2025_09_01_110545) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -68,6 +68,9 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_24_162600) do
t.datetime "created_at", precision: nil
t.datetime "updated_at", precision: nil
t.string "language"
t.index ["creator_id"], name: "index_alternate_names_on_creator_id"
t.index ["crop_id"], name: "index_alternate_names_on_crop_id"
t.index ["language"], name: "index_alternate_names_on_language"
end
create_table "authentications", id: :serial, force: :cascade do |t|
@@ -209,6 +212,8 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_24_162600) do
t.datetime "created_at", precision: nil
t.datetime "updated_at", precision: nil
t.string "commentable_type"
t.index ["author_id"], name: "index_comments_on_author_id"
t.index ["commentable_type", "commentable_id"], name: "index_comments_on_commentable_type_and_commentable_id"
end
create_table "crop_companions", force: :cascade do |t|
@@ -216,6 +221,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_24_162600) do
t.integer "crop_b_id", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.index ["crop_a_id", "crop_b_id"], name: "index_crop_companions_on_crop_a_id_and_crop_b_id"
end
create_table "crop_posts", id: false, force: :cascade do |t|
@@ -246,7 +252,9 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_24_162600) do
t.jsonb "openfarm_data"
t.integer "harvests_count", default: 0
t.integer "photo_associations_count", default: 0
t.index ["creator_id"], name: "index_crops_on_creator_id"
t.index ["name"], name: "index_crops_on_name"
t.index ["parent_id"], name: "index_crops_on_parent_id"
t.index ["requester_id"], name: "index_crops_on_requester_id"
t.index ["slug"], name: "index_crops_on_slug", unique: true
end
@@ -256,6 +264,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_24_162600) do
t.integer "followed_id"
t.datetime "created_at", precision: nil
t.datetime "updated_at", precision: nil
t.index ["follower_id", "followed_id"], name: "index_follows_on_follower_id_and_followed_id"
end
create_table "forums", id: :serial, force: :cascade do |t|
@@ -265,6 +274,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_24_162600) do
t.datetime "created_at", precision: nil
t.datetime "updated_at", precision: nil
t.string "slug"
t.index ["owner_id"], name: "index_forums_on_owner_id"
t.index ["slug"], name: "index_forums_on_slug", unique: true
end
@@ -328,6 +338,9 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_24_162600) do
t.float "si_weight"
t.integer "planting_id"
t.integer "likes_count", default: 0
t.index ["crop_id"], name: "index_harvests_on_crop_id"
t.index ["owner_id"], name: "index_harvests_on_owner_id"
t.index ["plant_part_id"], name: "index_harvests_on_plant_part_id"
t.index ["planting_id"], name: "index_harvests_on_planting_id"
end
@@ -464,6 +477,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_24_162600) do
create_table "members_roles", id: false, force: :cascade do |t|
t.integer "member_id"
t.integer "role_id"
t.index ["member_id", "role_id"], name: "index_members_roles_on_member_id_and_role_id"
end
create_table "notifications", id: :serial, force: :cascade do |t|
@@ -477,11 +491,14 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_24_162600) do
t.datetime "updated_at", precision: nil
t.string "notifiable_type"
t.index ["notifiable_type", "notifiable_id"], name: "index_notifications_on_notifiable_type_and_notifiable_id"
t.index ["recipient_id"], name: "index_notifications_on_recipient_id"
t.index ["sender_id"], name: "index_notifications_on_sender_id"
end
create_table "orders_products", id: false, force: :cascade do |t|
t.integer "order_id"
t.integer "product_id"
t.index ["order_id", "product_id"], name: "index_orders_products_on_order_id_and_product_id"
end
create_table "photo_associations", id: :serial, force: :cascade do |t|
@@ -491,6 +508,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_24_162600) do
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.integer "crop_id"
t.index ["crop_id"], name: "index_photo_associations_on_crop_id"
t.index ["photographable_id", "photographable_type", "photo_id"], name: "items_to_photos_idx", unique: true
t.index ["photographable_id", "photographable_type"], name: "photographable_idx"
end
@@ -511,12 +529,15 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_24_162600) do
t.string "source"
t.integer "comments_count", default: 0
t.index ["fullsize_url"], name: "index_photos_on_fullsize_url", unique: true
t.index ["owner_id"], name: "index_photos_on_owner_id"
t.index ["source_id"], name: "index_photos_on_source_id"
t.index ["thumbnail_url"], name: "index_photos_on_thumbnail_url", unique: true
end
create_table "photos_plantings", id: false, force: :cascade do |t|
t.integer "photo_id"
t.integer "planting_id"
t.index ["photo_id", "planting_id"], name: "index_photos_plantings_on_photo_id_and_planting_id"
end
create_table "photos_seeds", id: false, force: :cascade do |t|
@@ -531,6 +552,17 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_24_162600) do
t.datetime "updated_at", precision: nil
t.string "slug"
t.integer "harvests_count", default: 0
t.index ["slug"], name: "index_plant_parts_on_slug", unique: true
end
create_table "planting_problems", force: :cascade do |t|
t.bigint "planting_id"
t.bigint "problem_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["planting_id", "problem_id"], name: "index_planting_problems_on_planting_id_and_problem_id", unique: true
t.index ["planting_id"], name: "index_planting_problems_on_planting_id"
t.index ["problem_id"], name: "index_planting_problems_on_problem_id"
end
create_table "plantings", id: :serial, force: :cascade do |t|
@@ -554,6 +586,10 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_24_162600) do
t.integer "harvests_count", default: 0
t.integer "likes_count", default: 0
t.boolean "failed", default: false, null: false
t.index ["crop_id"], name: "index_plantings_on_crop_id"
t.index ["garden_id"], name: "index_plantings_on_garden_id"
t.index ["owner_id"], name: "index_plantings_on_owner_id"
t.index ["parent_seed_id"], name: "index_plantings_on_parent_seed_id"
t.index ["slug"], name: "index_plantings_on_slug", unique: true
end
@@ -568,9 +604,36 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_24_162600) do
t.integer "likes_count", default: 0
t.integer "comments_count", default: 0
t.index ["created_at", "author_id"], name: "index_posts_on_created_at_and_author_id"
t.index ["forum_id"], name: "index_posts_on_forum_id"
t.index ["slug"], name: "index_posts_on_slug", unique: true
end
create_table "problem_posts", force: :cascade do |t|
t.bigint "problem_id"
t.bigint "post_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["post_id"], name: "index_problem_posts_on_post_id"
t.index ["problem_id", "post_id"], name: "index_problem_posts_on_problem_id_and_post_id", unique: true
t.index ["problem_id"], name: "index_problem_posts_on_problem_id"
end
create_table "problems", force: :cascade do |t|
t.string "name"
t.string "reason_for_rejection"
t.string "rejection_notes"
t.string "approval_status", default: "pending", null: false
t.bigint "requester_id"
t.bigint "creator_id"
t.string "slug"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["creator_id"], name: "index_problems_on_creator_id"
t.index ["name"], name: "index_problems_on_name"
t.index ["requester_id"], name: "index_problems_on_requester_id"
t.index ["slug"], name: "index_problems_on_slug"
end
create_table "roles", id: :serial, force: :cascade do |t|
t.string "name", null: false
t.text "description"
@@ -590,6 +653,8 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_24_162600) do
t.string "gbif_rank"
t.string "gbif_status"
t.string "wikidata_id"
t.index ["creator_id"], name: "index_scientific_names_on_creator_id"
t.index ["crop_id"], name: "index_scientific_names_on_crop_id"
end
create_table "seeds", id: :serial, force: :cascade do |t|
@@ -611,7 +676,12 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_24_162600) do
t.date "finished_at"
t.integer "parent_planting_id"
t.date "saved_at"
t.string "source"
t.index ["crop_id"], name: "index_seeds_on_crop_id"
t.index ["owner_id"], name: "index_seeds_on_owner_id"
t.index ["parent_planting_id"], name: "index_seeds_on_parent_planting_id"
t.index ["slug"], name: "index_seeds_on_slug", unique: true
t.index ["source"], name: "index_seeds_on_source"
end
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
@@ -622,6 +692,12 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_24_162600) do
add_foreign_key "mailboxer_receipts", "mailboxer_notifications", column: "notification_id", name: "receipts_on_notification_id"
add_foreign_key "photo_associations", "crops"
add_foreign_key "photo_associations", "photos"
add_foreign_key "planting_problems", "plantings"
add_foreign_key "planting_problems", "problems"
add_foreign_key "plantings", "seeds", column: "parent_seed_id", name: "parent_seed", on_delete: :nullify
add_foreign_key "problem_posts", "posts"
add_foreign_key "problem_posts", "problems"
add_foreign_key "problems", "members", column: "creator_id"
add_foreign_key "problems", "members", column: "requester_id"
add_foreign_key "seeds", "plantings", column: "parent_planting_id", name: "parent_planting", on_delete: :nullify
end

View File

@@ -173,7 +173,8 @@ def load_test_users
organic: select_random_item(Seed::ORGANIC_VALUES),
gmo: select_random_item(['certified GMO-free', 'non-certified GMO-free', 'GMO', 'unknown']), # Strangely, this doesn't want to work as Seed:GMO_VALUES
heirloom: select_random_item(Seed::HEIRLOOM_VALUES),
parent_planting: @user.plantings.first
parent_planting: @user.plantings.first,
finished: false
)
photo = Photo.create!(

View File

@@ -76,10 +76,6 @@ describe "crop detail page", :js do
expect(page).to have_link "Wikipedia (English)", href: crop.en_wikipedia_url
end
it "has a link to OpenFarm" do
expect(page).to have_link "OpenFarm - Growing guide"
end
it "has a link to gardenate" do
expect(page).to have_link "Gardenate - Planting reminders",
href: "https://www.gardenate.com/plant/#{CGI.escape crop.name}"
@@ -144,7 +140,7 @@ describe "crop detail page", :js do
let(:crop) { FactoryBot.create(:annual_crop) }
describe 'with harvests' do
include_examples "predicts harvest"
it_behaves_like "predicts harvest"
end
it "predicts lifespan" do
@@ -165,7 +161,7 @@ describe "crop detail page", :js do
end
describe 'with harvests' do
include_examples "predicts harvest"
it_behaves_like "predicts harvest"
end
it "describes Perennial crops" do
@@ -181,7 +177,7 @@ describe "crop detail page", :js do
end
describe 'with harvests' do
include_examples "predicts harvest"
it_behaves_like "predicts harvest"
end
end
end

View File

@@ -85,21 +85,22 @@ describe "home page", :search do
end
context 'when anonymous' do
include_examples 'show crops'
include_examples 'show plantings'
include_examples 'show harvests'
include_examples 'shows seeds'
include_examples 'is accessible'
it_behaves_like 'show crops'
it_behaves_like 'show plantings'
it_behaves_like 'show harvests'
it_behaves_like 'shows seeds'
it_behaves_like 'is accessible'
it { is_expected.to have_text 'community of food gardeners' }
it { is_expected.to have_text 'Want to install Growstuff on your phone?' }
end
context "when signed in" do
include_context 'signed in member'
include_examples 'show crops'
include_examples 'show plantings'
include_examples 'show harvests'
include_examples 'shows seeds'
include_examples 'is accessible'
it_behaves_like 'show crops'
it_behaves_like 'show plantings'
it_behaves_like 'show harvests'
it_behaves_like 'shows seeds'
it_behaves_like 'is accessible'
describe 'should say welcome' do
before { visit root_path }

View File

@@ -62,9 +62,9 @@ describe "Planting a crop", :js, :search do
before do
visit new_planting_path
@a_past_date = 15.days.ago.strftime("%Y-%m-%d")
@right_now = Time.zone.today.strftime("%Y-%m-%d")
@a_future_date = 1.year.from_now.strftime("%Y-%m-%d")
@a_past_date = 15.days.ago
@right_now = Time.zone.today
@a_future_date = 1.year.from_now
end
it "shows that it is not planted yet" do
@@ -111,7 +111,7 @@ describe "Planting a crop", :js, :search do
fill_in "How many?", with: 42
select "cutting", from: "Planted from"
select "semi-shade", from: "Sun or shade?"
fill_in "When?", with: '2013-03-10'
fill_in "When?", with: Time.new(2013, 3, 10)
fill_in "Tell us more about it", with: "It's rad."
fill_in "Finished date", with: @a_future_date
click_button "Save"
@@ -185,7 +185,7 @@ describe "Planting a crop", :js, :search do
click_link 'Actions'
click_link "Edit"
check "finished"
fill_in "Finished date", with: "2015-06-25"
fill_in "Finished date", with: Time.new(2015, 06, 25)
click_button "Save"
expect(page).to have_content "planting was successfully updated"
expect(page).to have_content "Finished"
@@ -196,9 +196,9 @@ describe "Planting a crop", :js, :search do
select_from_autocomplete "maize"
choose(member.gardens.first.name)
within "form#new_planting" do
fill_in "When?", with: "2014-07-01"
fill_in "When?", with: Time.new(2014, 7, 1)
check "Mark as finished"
fill_in "Finished date", with: "2014-08-30"
fill_in "Finished date", with: Time.new(2014, 8, 30)
uncheck 'Mark as finished'
end
@@ -276,7 +276,7 @@ describe "Planting a crop", :js, :search do
fill_autocomplete "crop", with: "mai"
select_from_autocomplete "maize"
within "form#new_planting" do
fill_in "When", with: "2015-10-15"
fill_in "When", with: Time.new(2015, 10, 15)
fill_in "How many?", with: 42
select "cutting", from: "Planted from"
select "sun", from: "Sun or shade?"

View File

@@ -33,7 +33,7 @@ describe "Seeds", :js, :search do
select_from_autocomplete "maize"
within "form#new_seed" do
fill_in "Quantity", with: 42
fill_in "Plant before", with: "2014-06-15"
fill_in "Plant before", with: TIme.new(2014, 6, 15)
fill_in "min", with: 999
fill_in "max", with: 1999
select "certified organic", from: "Organic?"

View File

@@ -49,6 +49,7 @@ describe "seeds", :js do
click_link 'Edit'
expect(page).to have_current_path edit_seed_path(seed), ignore_query: true
fill_in 'Quantity', with: seed.quantity * 2
select 'traded from another person', from: 'Source'
click_button 'Save'
expect(page).to have_current_path seed_path(seed), ignore_query: true
end

View File

@@ -17,6 +17,10 @@ describe "timeline", :js do
let!(:friend_harvest) { FactoryBot.create(:planting, owner: friend2, planted_at: 3.years.ago) }
let!(:finished_planting) { FactoryBot.create(:finished_planting, owner: friend1) }
let!(:no_planted_at_planting) { FactoryBot.create(:planting, owner: friend2, planted_at: nil) }
let!(:friend_photo) { FactoryBot.create(:photo, owner: friend1) }
let!(:friend_post) { FactoryBot.create(:post, author: friend2) }
let!(:liked_post) { FactoryBot.create(:like, likeable: friend_photo, member: friend2) }
let!(:liked_photo) { FactoryBot.create(:like, likeable: friend_post, member: friend1) }
before do
login_as(member)
@@ -28,6 +32,8 @@ describe "timeline", :js do
it { expect(page).to have_link href: planting_path(friend_harvest) }
it { expect(page).to have_link href: planting_path(finished_planting) }
it { expect(page).to have_no_link href: planting_path(no_planted_at_planting) }
it { expect(page).to have_link href: photo_path(friend_photo) }
it { expect(page).to have_link href: post_path(friend_post) }
end
describe 'shows the friends you follow' do

View File

@@ -22,6 +22,6 @@ describe "plantings/_form" do
end
it "has a free-form text field containing the planting date in ISO format" do
assert_select "input#planting_planted_at[type='text'][value='2013-03-01']"
assert_select "input#planting_planted_at[type='date'][value='2013-03-01']"
end
end