Compare commits

..

182 Commits

Author SHA1 Message Date
google-labs-jules[bot]
4b9763e1da feat: Add problem tracking feature
This commit introduces a new `Problem` model, analogous to `Crop`, to allow users to track problems they have on their plantings (e.g., aphids on tomatoes).

Key features:
- A new `Problem` model that can be curated by admins (`problem_wranglers`).
- Users can associate problems with their plantings and upload photos of the problems.
- Aggregated problem information is displayed on the crop detail page (e.g., "Problems: aphids (27), blight (13)").
- Users can mention problems in posts (e.g., `[aphids](problem)`), which automatically links to the problem's page.
- Admin functionality for reviewing and approving new problem suggestions.

Resolves merge conflict in app/controllers/plantings_controller.rb
2025-09-07 11:47:53 +00:00
Daniel O'Connor
8709ac6e8f Mark unique 2025-09-01 11:26:05 +00:00
Daniel O'Connor
d75f503b16 Add DB schema 2025-09-01 11:04:48 +00:00
Daniel O'Connor
1dfc5abca9 Add DB schema 2025-09-01 11:04:44 +00:00
Daniel O'Connor
cc97916bac Add problems table 2025-09-01 11:01:46 +00:00
Daniel O'Connor
2134b934f8 Merge branch 'dev' into feature/problems 2025-09-01 19:45:25 +09:30
google-labs-jules[bot]
c8cfb5d42a feat: Add problem tracking feature
This commit introduces a new `Problem` model, analogous to `Crop`, to allow you to track problems you have on your plantings (e.g., aphids on tomatoes).

Key features:
- A new `Problem` model that can be curated by admins (`problem_wranglers`).
- You can associate problems with your plantings and upload photos of the problems.
- Aggregated problem information is displayed on the crop detail page (e.g., "Problems: aphids (27), blight (13)").
- You can mention problems in posts (e.g., `[aphids](problem)`), which automatically links to the problem's page.
- Admin functionality for reviewing and approving new problem suggestions.
2025-09-01 07:52:48 +00:00
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
f5a03b8991 Merge pull request #4161 from Growstuff/dependabot/bundler/rubocop-1.80.1
Bump rubocop from 1.80.0 to 1.80.1
2025-08-29 19:57:29 +09:30
Daniel O'Connor
8044640023 Merge branch 'dev' into remove-openfarm.cc 2025-08-29 19:56:09 +09:30
Daniel O'Connor
d1d718df9e Add One click ask AI prompts for companion planting and growing guides (#4159)
* One click ask AI prompts

* One click ask AI prompts

* Update app/views/crops/show.html.haml
2025-08-29 19:54:38 +09:30
dependabot[bot]
da4bb17df8 Bump rubocop from 1.80.0 to 1.80.1
Bumps [rubocop](https://github.com/rubocop/rubocop) from 1.80.0 to 1.80.1.
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.80.0...v1.80.1)

---
updated-dependencies:
- dependency-name: rubocop
  dependency-version: 1.80.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-28 08:59:30 +00:00
dependabot[bot]
0320cbe5ad Bump chartkick from 5.1.5 to 5.2.0 (#4072)
Bumps [chartkick](https://github.com/ankane/chartkick) from 5.1.5 to 5.2.0.
- [Changelog](https://github.com/ankane/chartkick/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ankane/chartkick/compare/v5.1.5...v5.2.0)

---
updated-dependencies:
- dependency-name: chartkick
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Daniel O'Connor <daniel.oconnor@gmail.com>
2025-08-28 00:16:34 +09:30
google-labs-jules[bot]
4d3c4ca10d Merge pull request #4154 from Growstuff/finish-expired-seeds-task
Add maintenance task to finish expired seeds
2025-08-28 00:16:16 +09:30
Daniel O'Connor
ab29de3d04 Remove dead test 2025-08-27 14:45:57 +00:00
Daniel O'Connor
749134a7de Remove Openfarm data fetching (#4157) 2025-08-28 00:15:03 +09:30
Daniel O'Connor
1657a527e9 Remove Openfarm data fetching 2025-08-27 14:20:22 +00:00
Daniel O'Connor
9ae90c1b7e Merge branch 'dev' into remove-openfarm.cc 2025-08-27 23:43:29 +09:30
google-labs-jules[bot]
ba6ec689c5 Merge pull request #4150 from Growstuff/feature/failed-plantings
Add failed status to plantings
2025-08-27 23:42:53 +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
Daniel O'Connor
df2853edd3 Merge pull request #4153 from Growstuff/age_in_days
Fix age in days, percentage grown calculations for future dates
2025-08-27 23:31:48 +09:30
Daniel O'Connor
948bb78656 Fix percentage grown 2025-08-27 13:44:44 +00:00
Daniel O'Connor
9c8ae50188 Fix https://github.com/Growstuff/growstuff/issues/3844 by avoiding future finished dates being considered past dates 2025-08-27 13:36:12 +00:00
dependabot[bot]
0ee6260272 Merge pull request #4152 from Growstuff/dependabot/bundler/scout_apm-5.7.1 2025-08-26 23:30:40 +00:00
dependabot[bot]
bf528220ab Bump scout_apm from 5.7.0 to 5.7.1
Bumps [scout_apm](https://github.com/scoutapp/scout_apm_ruby) from 5.7.0 to 5.7.1.
- [Changelog](https://github.com/scoutapp/scout_apm_ruby/blob/master/CHANGELOG.markdown)
- [Commits](https://github.com/scoutapp/scout_apm_ruby/compare/v5.7.0...v5.7.1)

---
updated-dependencies:
- dependency-name: scout_apm
  dependency-version: 5.7.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-26 23:22:37 +00:00
dependabot[bot]
fd89cc6bce Merge pull request #4151 from Growstuff/dependabot/bundler/rubocop-1.80.0 2025-08-26 23:21:29 +00:00
dependabot[bot]
08a3890ba2 Bump rubocop from 1.79.2 to 1.80.0
Bumps [rubocop](https://github.com/rubocop/rubocop) from 1.79.2 to 1.80.0.
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.79.2...v1.80.0)

---
updated-dependencies:
- dependency-name: rubocop
  dependency-version: 1.80.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-25 14:33:48 +00:00
Daniel O'Connor
956c73cd1e This rake task iterates through all photos and removes any that have a 404 status on their fullsize_url. This will help to keep the database clean of broken photo references. (#4149)
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2025-08-25 10:26:57 +09:30
google-labs-jules[bot]
8e74d1796a This rake task iterates through all photos and removes any that have a 404 status on their fullsize_url. This will help to keep the database clean of broken photo references. 2025-08-24 23:17:26 +00:00
google-labs-jules[bot]
a98990ccd2 Add transplant feature for plantings (#4133)
* Add ability to transplant a planting

* Fix view tests

* Transplantable gardens

* Add spec

---------

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-08-24 22:31:14 +09:30
google-labs-jules[bot]
ac1463e2cf Add international alternate names for crops (#4132)
* I will add the international alternate names for the crops.

* Mark required

* Update factory

* Add placeholder

* Fix seeds

* Add language, though hardcoded to EN in most places

---------

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-08-24 21:44:41 +09:30
google-labs-jules[bot]
8564ec7a7c Add comments to photos (#4130)
* Add comments to photos

Extend the photo show page to support comments by logged in users.

- Make the Comment model polymorphic.
- Update the Photo and Post models to have comments.
- Update the comments controller to handle the polymorphic association.
- Update the photo show page to display comments and a comment form.
- Create a reusable comments partial.

* Add migration

* Fix tests

* Fix tests

* Slightly fix tests

* Fix variables

* Add field

* Refactor slightly

* Refactor slightly

* Refactor slightly

* Refactor

* Photos respond to this as well

* Refactor to polymorphic_url

* Rename

* Wrong relationship

* Refactor and fix tests

* Fix relationships

* Fix rendering

* Fix tests

* Fix model tests

* Fix test

* Fix test

* Fix test

* Fix test

* Fix controller spec

* Fix view tests

---------

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-08-24 21:10:16 +09:30
Daniel O'Connor
79a54351e3 Delete spec/tasks/openfarm_rake_spec.rb 2025-08-24 21:02:13 +09:30
dependabot[bot]
48aa2aafd3 Merge pull request #4066 from Growstuff/dependabot/bundler/oj-3.16.11 2025-08-24 08:15:56 +00:00
Daniel O'Connor
3b648925dd Merge branch 'dev' into dependabot/bundler/oj-3.16.11 2025-08-24 17:36:31 +09:30
Daniel O'Connor
d04ffbeddd Merge pull request #4146 from Growstuff/remove-twitter-auth
Remove twitter authentication
2025-08-24 17:35:56 +09:30
Daniel O'Connor
e1367613e6 Merge branch 'dev' into remove-twitter-auth 2025-08-24 17:08:19 +09:30
Daniel O'Connor
70f78f1175 Merge pull request #4148 from Growstuff/CloCkWeRX-patch-2
Delete lib/tasks/openfarm.rake
2025-08-24 17:07:19 +09:30
Daniel O'Connor
6131fdf141 Delete lib/tasks/openfarm.rake 2025-08-24 17:07:02 +09:30
Daniel O'Connor
44101e07fb Merge branch 'mainline' into dev 2025-08-24 16:40:08 +09:30
Daniel O'Connor
b4c1104af0 Merge branch 'dev' into remove-twitter-auth 2025-08-24 16:36:42 +09:30
Daniel O'Connor
3d4ba954e7 Remove defunct detail 2025-08-24 07:05:28 +00:00
Daniel O'Connor
b8f7f95f32 Merge pull request #4144 from Growstuff/grid-layout
Improve profile page display slightly
2025-08-24 16:33:33 +09:30
google-labs-jules[bot]
0b639d5940 Remove twitter authentication
This change removes the twitter authentication feature from the application.

It removes the `omniauth-twitter` gem and all related code from controllers, views, and tests. It also removes the twitter icon and environment variable settings.
2025-08-24 07:03:20 +00:00
Daniel O'Connor
f216ddc368 Merge pull request #4145 from Growstuff/CloCkWeRX-patch-2
Update CONTRIBUTORS.md
2025-08-24 16:32:55 +09:30
Daniel O'Connor
967c0f4638 Update CONTRIBUTORS.md 2025-08-24 16:32:40 +09:30
Daniel O'Connor
cac0e3cb12 Update CONTRIBUTORS.md 2025-08-24 16:32:03 +09:30
Daniel O'Connor
65406b9e56 Update CONTRIBUTORS.md 2025-08-24 16:31:24 +09:30
Daniel O'Connor
224109aaf8 Merge pull request #4143 from Growstuff/feature/amend-delete-pictures-task
Amend delete_pictures rake task to remove legacy S3 photos
2025-08-24 16:30:23 +09:30
Daniel O'Connor
bec1ec1879 Merge pull request #4136 from Growstuff/dependabot/github_actions/actions/checkout-5
Bump actions/checkout from 4 to 5
2025-08-24 16:29:06 +09:30
Daniel O'Connor
e88e54b0c1 Image redundant now 2025-08-24 06:52:37 +00:00
google-labs-jules[bot]
76a6c1d849 Merge pull request #4131 from Growstuff/feature/add-social-media-links
feat: Add social media links to user profiles
2025-08-24 16:21:05 +09:30
Daniel O'Connor
b7f4de782d Styling 2025-08-24 06:50:53 +00:00
Daniel O'Connor
112a626941 Adjust Bio layout 2025-08-24 06:48:40 +00:00
google-labs-jules[bot]
b72aeab136 Amend delete_pictures rake task to remove legacy S3 photos
The `delete_pictures` rake task in `openfarm.rake` is amended to
also remove Photos where the `fullsize_url` starts with
`https://s3.amazonaws.com/openfarm-project/`.

This change helps in cleaning up legacy photos that are no longer
needed. The task description has also been updated to reflect this
change.
2025-08-24 06:21:19 +00:00
dependabot[bot]
a7d100fe7e Merge pull request #4137 from Growstuff/dependabot/bundler/selenium-webdriver-4.35.0 2025-08-14 14:15:43 +00:00
dependabot[bot]
810a9e39e3 Bump selenium-webdriver from 4.34.0 to 4.35.0
Bumps [selenium-webdriver](https://github.com/SeleniumHQ/selenium) from 4.34.0 to 4.35.0.
- [Release notes](https://github.com/SeleniumHQ/selenium/releases)
- [Changelog](https://github.com/SeleniumHQ/selenium/blob/trunk/rb/CHANGES)
- [Commits](https://github.com/SeleniumHQ/selenium/compare/selenium-4.34.0...selenium-4.35.0)

---
updated-dependencies:
- dependency-name: selenium-webdriver
  dependency-version: 4.35.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-14 14:09:03 +00:00
dependabot[bot]
b320435ddf Merge pull request #4141 from Growstuff/dependabot/bundler/rubocop-rails-2.33.3 2025-08-14 14:07:43 +00:00
dependabot[bot]
2f7ce2721f Bump rubocop-rails from 2.32.0 to 2.33.3
Bumps [rubocop-rails](https://github.com/rubocop/rubocop-rails) from 2.32.0 to 2.33.3.
- [Release notes](https://github.com/rubocop/rubocop-rails/releases)
- [Changelog](https://github.com/rubocop/rubocop-rails/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop-rails/compare/v2.32.0...v2.33.3)

---
updated-dependencies:
- dependency-name: rubocop-rails
  dependency-version: 2.33.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-14 13:53:23 +00:00
dependabot[bot]
c9e19203a9 Merge pull request #4142 from Growstuff/dependabot/bundler/rails-7.2.2.2 2025-08-14 13:52:01 +00:00
dependabot[bot]
005efab3be Bump rails from 7.2.2.1 to 7.2.2.2
Bumps [rails](https://github.com/rails/rails) from 7.2.2.1 to 7.2.2.2.
- [Release notes](https://github.com/rails/rails/releases)
- [Commits](https://github.com/rails/rails/compare/v7.2.2.1...v7.2.2.2)

---
updated-dependencies:
- dependency-name: rails
  dependency-version: 7.2.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-14 07:08:38 +00:00
dependabot[bot]
46599937d9 Merge pull request #4138 from Growstuff/dependabot/bundler/rspec-rails-8.0.2 2025-08-13 09:47:01 +00:00
dependabot[bot]
496a6ac2b6 Bump rspec-rails from 8.0.1 to 8.0.2
Bumps [rspec-rails](https://github.com/rspec/rspec-rails) from 8.0.1 to 8.0.2.
- [Changelog](https://github.com/rspec/rspec-rails/blob/main/Changelog.md)
- [Commits](https://github.com/rspec/rspec-rails/compare/v8.0.1...v8.0.2)

---
updated-dependencies:
- dependency-name: rspec-rails
  dependency-version: 8.0.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-13 07:17:23 +00:00
dependabot[bot]
9efb1f8486 Bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 15:37:57 +00:00
Daniel O'Connor
59bd6f3450 Merge pull request #4065 from Growstuff/dependabot/bundler/rake-13.3.0
Bump rake from 13.2.1 to 13.3.0
2025-08-10 22:47:04 +09:30
dependabot[bot]
3e9bf73297 Merge pull request #4067 from Growstuff/dependabot/bundler/bullet-8.0.8 2025-08-10 08:50:09 +00:00
Daniel O'Connor
dcff643637 Merge branch 'dev' into dependabot/bundler/bullet-8.0.8 2025-08-10 17:53:48 +09:30
dependabot[bot]
a83966aa92 Merge pull request #4108 from Growstuff/dependabot/bundler/scout_apm-5.7.0 2025-08-10 07:40:46 +00:00
dependabot[bot]
1c47e421b9 Bump scout_apm from 5.6.4 to 5.7.0
Bumps [scout_apm](https://github.com/scoutapp/scout_apm_ruby) from 5.6.4 to 5.7.0.
- [Changelog](https://github.com/scoutapp/scout_apm_ruby/blob/master/CHANGELOG.markdown)
- [Commits](https://github.com/scoutapp/scout_apm_ruby/compare/v5.6.4...v5.7.0)

---
updated-dependencies:
- dependency-name: scout_apm
  dependency-version: 5.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 07:19:44 +00:00
Daniel O'Connor
6289ab0951 Bump faker from 3.5.1 to 3.5.2 (#4082)
Bumps [faker](https://github.com/faker-ruby/faker) from 3.5.1 to 3.5.2.
- [Release notes](https://github.com/faker-ruby/faker/releases)
- [Changelog](https://github.com/faker-ruby/faker/blob/main/CHANGELOG.md)
- [Commits](https://github.com/faker-ruby/faker/compare/v3.5.1...v3.5.2)

---
updated-dependencies:
- dependency-name: faker
  dependency-version: 3.5.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-10 16:48:30 +09:30
dependabot[bot]
d127647eeb Merge pull request #4105 from Growstuff/dependabot/bundler/puma-6.6.1 2025-08-10 07:05:39 +00:00
dependabot[bot]
9eda4bb2f2 Bump puma from 6.6.0 to 6.6.1
Bumps [puma](https://github.com/puma/puma) from 6.6.0 to 6.6.1.
- [Release notes](https://github.com/puma/puma/releases)
- [Changelog](https://github.com/puma/puma/blob/master/History.md)
- [Commits](https://github.com/puma/puma/compare/v6.6.0...v6.6.1)

---
updated-dependencies:
- dependency-name: puma
  dependency-version: 6.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 06:44:35 +00:00
dependabot[bot]
f1d524130b Merge pull request #4110 from Growstuff/dependabot/bundler/rspec-activemodel-mocks-1.3.0 2025-08-10 06:43:20 +00:00
dependabot[bot]
e4de08f47f Bump rspec-activemodel-mocks from 1.2.1 to 1.3.0
Bumps [rspec-activemodel-mocks](https://github.com/rspec/rspec-activemodel-mocks) from 1.2.1 to 1.3.0.
- [Changelog](https://github.com/rspec/rspec-activemodel-mocks/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rspec/rspec-activemodel-mocks/compare/v1.2.1...v1.3.0)

---
updated-dependencies:
- dependency-name: rspec-activemodel-mocks
  dependency-version: 1.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 06:29:48 +00:00
Daniel O'Connor
0b4820e5df Add rspec-retry (#4129)
* Add rspec-retry

* Add config

* Swap to rspec-rebound, which is a fork

* Swap to rspec-rebound, which is a fork
2025-08-10 15:55:52 +09:30
dependabot[bot]
d264905aa9 Bump faraday from 2.13.2 to 2.13.4 (#4101)
Bumps [faraday](https://github.com/lostisland/faraday) from 2.13.2 to 2.13.4.
- [Release notes](https://github.com/lostisland/faraday/releases)
- [Changelog](https://github.com/lostisland/faraday/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lostisland/faraday/compare/v2.13.2...v2.13.4)

---
updated-dependencies:
- dependency-name: faraday
  dependency-version: 2.13.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-10 15:54:37 +09:30
Daniel O'Connor
25d2c5f854 Merge branch 'dev' into dependabot/bundler/bullet-8.0.8 2025-08-10 15:46:09 +09:30
google-labs-jules[bot]
d383c8e2e4 Add filtering for tradeable seeds (#4111)
* feat: Add filtering for tradeable seeds

This change introduces a new feature to filter seeds based on their tradeability.

- Adds a link on the homepage to view all tradeable seeds.
- Modifies the seeds controller to support filtering by `tradeable_to`, allowing multiple values to be selected.
- Adds links to the seeds index page to filter by the various `tradeable_to` values.

* Update index.html.haml

---------

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-08-10 15:44:01 +09:30
dependabot[bot]
ed89163311 Bump faker from 3.5.1 to 3.5.2
Bumps [faker](https://github.com/faker-ruby/faker) from 3.5.1 to 3.5.2.
- [Release notes](https://github.com/faker-ruby/faker/releases)
- [Changelog](https://github.com/faker-ruby/faker/blob/main/CHANGELOG.md)
- [Commits](https://github.com/faker-ruby/faker/compare/v3.5.1...v3.5.2)

---
updated-dependencies:
- dependency-name: faker
  dependency-version: 3.5.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 05:43:20 +00:00
dependabot[bot]
26de3e7c5e Bump oj from 3.16.10 to 3.16.11
Bumps [oj](https://github.com/ohler55/oj) from 3.16.10 to 3.16.11.
- [Release notes](https://github.com/ohler55/oj/releases)
- [Changelog](https://github.com/ohler55/oj/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/ohler55/oj/compare/v3.16.10...v3.16.11)

---
updated-dependencies:
- dependency-name: oj
  dependency-version: 3.16.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 05:37:12 +00:00
Daniel O'Connor
6ae2de7e47 Merge pull request #4086 from Growstuff/mailboxer-translations
Mailboxer translations
2025-08-10 14:57:39 +09:30
Daniel O'Connor
da74c19c2a Merge pull request #4094 from Growstuff/dependabot/bundler/recaptcha-5.20.1
Bump recaptcha from 5.19.0 to 5.20.1
2025-08-10 14:57:23 +09:30
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
d767a011a4 Merge pull request #4126 from Growstuff/split-up-ci
Split up ci
2025-08-10 13:53:47 +09:30
Daniel O'Connor
bbe6a3fd36 Merge pull request #4123 from Growstuff/view-transitions
Opt into view transitions
2025-08-10 13:52:34 +09:30
dependabot[bot]
3eb2fe7637 Bump recaptcha from 5.19.0 to 5.20.1
Bumps [recaptcha](https://github.com/ambethia/recaptcha) from 5.19.0 to 5.20.1.
- [Changelog](https://github.com/ambethia/recaptcha/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ambethia/recaptcha/compare/v5.19.0...v5.20.1)

---
updated-dependencies:
- dependency-name: recaptcha
  dependency-version: 5.20.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 04:16:17 +00:00
dependabot[bot]
7f0af10637 Merge pull request #4109 from Growstuff/dependabot/bundler/rubocop-1.79.2 2025-08-10 04:15:01 +00:00
dependabot[bot]
75d93bb3c3 Bump rake from 13.2.1 to 13.3.0
Bumps [rake](https://github.com/ruby/rake) from 13.2.1 to 13.3.0.
- [Release notes](https://github.com/ruby/rake/releases)
- [Changelog](https://github.com/ruby/rake/blob/master/History.rdoc)
- [Commits](https://github.com/ruby/rake/compare/v13.2.1...v13.3.0)

---
updated-dependencies:
- dependency-name: rake
  dependency-version: 13.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 04:04:03 +00:00
Daniel O'Connor
c49f2bab19 Merge branch 'dev' into view-transitions 2025-08-10 13:24:41 +09:30
dependabot[bot]
ed71023306 Bump rubocop from 1.79.1 to 1.79.2
Bumps [rubocop](https://github.com/rubocop/rubocop) from 1.79.1 to 1.79.2.
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.79.1...v1.79.2)

---
updated-dependencies:
- dependency-name: rubocop
  dependency-version: 1.79.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 03:47:36 +00:00
Daniel O'Connor
59d95dfd77 Split up 2025-08-10 03:22:32 +00:00
Daniel O'Connor
e999c5870f Merge branch 'dev' into split-up-ci 2025-08-10 12:51:39 +09:30
Daniel O'Connor
072ec1dd82 Merge pull request #4125 from Growstuff/set-window-size
Specs: Target a desktop, fixing tests in codespaces at least.
2025-08-10 12:51:06 +09:30
Daniel O'Connor
8ecae23d61 Split up posts 2025-08-10 03:20:57 +00:00
Daniel O'Connor
d20ceb54b5 Avoid double running plantings features 2025-08-10 03:19:24 +00:00
Daniel O'Connor
7e3be99aac Comment out and explain why 2025-08-10 03:12:58 +00:00
Daniel O'Connor
d99d4a1bbe Disable dev shm usage on some environments 2025-08-10 03:08:57 +00:00
Daniel O'Connor
ea530754aa Disable dev shm usage on some environments 2025-08-10 03:06:38 +00:00
Daniel O'Connor
4bd322d8ca Target a desktop 2025-08-10 02:55:34 +00:00
Daniel O'Connor
5fc41ca50f Opt into view transitions 2025-08-10 02:47:44 +00:00
Daniel O'Connor
5da8f815d3 Fix stupid mistake 2025-08-10 02:27:36 +00:00
Daniel O'Connor
d71370c0bc Merge pull request #4122 from Growstuff/dev
Release 65.2
2025-08-10 11:54:12 +09:30
Daniel O'Connor
d0d31edd5e Merge pull request #4121 from Growstuff/delete-openfarm-pictures-rake-task
Swap to iteration for now, so callbacks are happy
2025-08-10 11:52:14 +09:30
Daniel O'Connor
67a2005f1e Swap to iteration for now, so callbacks are happy 2025-08-10 02:21:39 +00:00
Daniel O'Connor
c58ca74b53 Merge pull request #4120 from Growstuff/dev
Release 65.1
2025-08-10 11:39:11 +09:30
Daniel O'Connor
b528b40052 Merge pull request #4119 from Growstuff/delete-openfarm-pictures-rake-task
Swap to iteration for now, so callbacks are happy
2025-08-10 11:36:10 +09:30
Daniel O'Connor
3b17265a92 Swap to iteration for now, so callbacks are happy 2025-08-10 02:05:29 +00:00
Daniel O'Connor
0d22706d42 Merge pull request #4118 from Growstuff/dev
August 2025 Release
2025-08-10 11:17:05 +09:30
Daniel O'Connor
f8c93f16db Merge pull request #4113 from Growstuff/delete-openfarm-pictures-rake-task
Add rake task to delete pictures with source OpenFarm
2025-08-10 11:16:05 +09:30
Daniel O'Connor
8006d26c6e Fix source 2025-08-10 01:45:29 +00:00
Daniel O'Connor
2766082696 Merge branch 'dev' of https://github.com/Growstuff/growstuff into delete-openfarm-pictures-rake-task 2025-08-10 01:43:11 +00:00
Daniel O'Connor
d887d5c788 Merge pull request #4116 from Growstuff/dev-fixed
Add crowdin tools
2025-08-10 11:09:32 +09:30
Daniel O'Connor
0634719afc Add crowdin tools 2025-08-10 01:38:53 +00:00
Daniel O'Connor
a13cf93aeb Merge pull request #4115 from Growstuff/3.3.8
Bump to Ruby 3.3.8
2025-08-10 11:07:34 +09:30
Daniel O'Connor
04376f15a2 Merge pull request #4114 from Growstuff/add-crowdin-support
Add CrowdIn translations support
2025-08-10 10:59:16 +09:30
Daniel O'Connor
82d3f84e0c Ruby 3.3.8 2025-08-10 01:09:08 +00:00
Daniel O'Connor
af6cee2f16 Merge branch 'dev' into dependabot/bundler/bullet-8.0.8 2025-08-10 10:36:54 +09:30
google-labs-jules[bot]
4f705ff37b Add CrowdIn translations support
This change adds support for CrowdIn to manage translations.

- Adds the `crowdin-cli` gem to the `Gemfile`.
- Adds a `crowdin.yml` configuration file to the project root.
2025-08-10 01:02:39 +00:00
google-labs-jules[bot]
4829d5513c This commit adds a rake task to delete all pictures that have 'OpenFarm' as their source. This is useful for cleaning up data imported from OpenFarm. 2025-08-10 00:58:03 +00:00
Daniel O'Connor
d1419aaca4 Merge pull request #4104 from Growstuff/dependabot/bundler/rubocop-1.79.1
Bump rubocop from 1.78.0 to 1.79.1
2025-08-05 18:29:49 +09:30
dependabot[bot]
2458afc451 Bump rubocop from 1.78.0 to 1.79.1
Bumps [rubocop](https://github.com/rubocop/rubocop) from 1.78.0 to 1.79.1.
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.78.0...v1.79.1)

---
updated-dependencies:
- dependency-name: rubocop
  dependency-version: 1.79.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-04 23:21:45 +00:00
Daniel O'Connor
278be7067f Merge pull request #4107 from Growstuff/dependabot/bundler/haml_lint-0.66.0
Bump haml_lint from 0.62.0 to 0.66.0
2025-08-05 08:50:22 +09:30
dependabot[bot]
bc71c4a706 Bump haml_lint from 0.62.0 to 0.66.0
Bumps [haml_lint](https://github.com/sds/haml-lint) from 0.62.0 to 0.66.0.
- [Release notes](https://github.com/sds/haml-lint/releases)
- [Changelog](https://github.com/sds/haml-lint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sds/haml-lint/compare/v0.62.0...v0.66.0)

---
updated-dependencies:
- dependency-name: haml_lint
  dependency-version: 0.66.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-04 23:06:28 +00:00
Daniel O'Connor
b1a42a2ed6 Merge pull request #4106 from Growstuff/dependabot/bundler/pg-1.6.1
Bump pg from 1.5.9 to 1.6.1
2025-08-05 08:35:03 +09:30
dependabot[bot]
fe22c92a63 Bump pg from 1.5.9 to 1.6.1
Bumps [pg](https://github.com/ged/ruby-pg) from 1.5.9 to 1.6.1.
- [Changelog](https://github.com/ged/ruby-pg/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ged/ruby-pg/compare/v1.5.9...v1.6.1)

---
updated-dependencies:
- dependency-name: pg
  dependency-version: 1.6.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-04 09:49:20 +00:00
Daniel O'Connor
f00b31cf46 Merge pull request #4093 from Growstuff/dependabot/bundler/nokogiri-1.18.9
Bump nokogiri from 1.18.8 to 1.18.9
2025-07-25 20:32:04 +09:30
dependabot[bot]
7cfee1d89c Bump nokogiri from 1.18.8 to 1.18.9
---
updated-dependencies:
- dependency-name: nokogiri
  dependency-version: 1.18.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-22 03:24:14 +00:00
Daniel O'Connor
6c89cef5bc Merge pull request #4078 from Growstuff/dependabot/bundler/icalendar-2.11.2
Bump icalendar from 2.11.0 to 2.11.2
2025-07-17 08:56:42 +09:30
dependabot[bot]
5d79aa3e60 Bump icalendar from 2.11.0 to 2.11.2
Bumps [icalendar](https://github.com/icalendar/icalendar) from 2.11.0 to 2.11.2.
- [Changelog](https://github.com/icalendar/icalendar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/icalendar/icalendar/compare/v2.11.0...v2.11.2)

---
updated-dependencies:
- dependency-name: icalendar
  dependency-version: 2.11.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-16 23:17:21 +00:00
Daniel O'Connor
29c8d87703 Merge pull request #4071 from Growstuff/dependabot/bundler/factory_bot_rails-6.5.0
Bump factory_bot_rails from 6.4.4 to 6.5.0
2025-07-17 08:46:41 +09:30
Daniel O'Connor
83ef779c11 Merge pull request #4076 from Growstuff/dependabot/bundler/rspec-rails-8.0.1
Bump rspec-rails from 8.0.0 to 8.0.1
2025-07-17 08:46:20 +09:30
Daniel O'Connor
4ada3e1585 Merge pull request #4080 from Growstuff/dependabot/bundler/selenium-webdriver-4.34.0
Bump selenium-webdriver from 4.32.0 to 4.34.0
2025-07-17 08:46:00 +09:30
Daniel O'Connor
88f5622d0c Merge branch 'dev' into dependabot/bundler/bullet-8.0.8 2025-07-15 09:11:20 +09:30
dependabot[bot]
fa6752c668 Bump selenium-webdriver from 4.32.0 to 4.34.0
Bumps [selenium-webdriver](https://github.com/SeleniumHQ/selenium) from 4.32.0 to 4.34.0.
- [Release notes](https://github.com/SeleniumHQ/selenium/releases)
- [Changelog](https://github.com/SeleniumHQ/selenium/blob/trunk/rb/CHANGES)
- [Commits](https://github.com/SeleniumHQ/selenium/compare/selenium-4.32.0...selenium-4.34.0)

---
updated-dependencies:
- dependency-name: selenium-webdriver
  dependency-version: 4.34.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-13 10:53:23 +00:00
dependabot[bot]
f9186c1a8a Bump rspec-rails from 8.0.0 to 8.0.1
Bumps [rspec-rails](https://github.com/rspec/rspec-rails) from 8.0.0 to 8.0.1.
- [Changelog](https://github.com/rspec/rspec-rails/blob/main/Changelog.md)
- [Commits](https://github.com/rspec/rspec-rails/compare/v8.0.0...v8.0.1)

---
updated-dependencies:
- dependency-name: rspec-rails
  dependency-version: 8.0.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-13 10:53:10 +00:00
dependabot[bot]
82dcd1d86c Bump factory_bot_rails from 6.4.4 to 6.5.0
Bumps [factory_bot_rails](https://github.com/thoughtbot/factory_bot_rails) from 6.4.4 to 6.5.0.
- [Release notes](https://github.com/thoughtbot/factory_bot_rails/releases)
- [Changelog](https://github.com/thoughtbot/factory_bot_rails/blob/main/NEWS.md)
- [Commits](https://github.com/thoughtbot/factory_bot_rails/compare/v6.4.4...v6.5.0)

---
updated-dependencies:
- dependency-name: factory_bot_rails
  dependency-version: 6.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-13 10:52:57 +00:00
Daniel O'Connor
b2146e791f Merge pull request #4083 from Growstuff/dependabot/bundler/faraday-2.13.2
Bump faraday from 2.13.1 to 2.13.2
2025-07-13 20:01:54 +09:30
dependabot[bot]
a9c33a30d2 Bump faraday from 2.13.1 to 2.13.2
Bumps [faraday](https://github.com/lostisland/faraday) from 2.13.1 to 2.13.2.
- [Release notes](https://github.com/lostisland/faraday/releases)
- [Changelog](https://github.com/lostisland/faraday/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lostisland/faraday/compare/v2.13.1...v2.13.2)

---
updated-dependencies:
- dependency-name: faraday
  dependency-version: 2.13.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-13 10:30:42 +00:00
Daniel O'Connor
497cb6627d Merge pull request #4085 from Growstuff/dependabot/bundler/rubocop-1.78.0
Bump rubocop from 1.76.0 to 1.78.0
2025-07-13 19:59:21 +09:30
dependabot[bot]
a8a96ae125 Bump rubocop from 1.76.0 to 1.78.0
Bumps [rubocop](https://github.com/rubocop/rubocop) from 1.76.0 to 1.78.0.
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.76.0...v1.78.0)

---
updated-dependencies:
- dependency-name: rubocop
  dependency-version: 1.78.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-13 07:37:05 +00:00
Daniel O'Connor
4dcafdb117 Merge pull request #4092 from Growstuff/fix-ci3
Drop simplecov for now
2025-07-13 16:44:29 +09:30
Daniel O'Connor
bd132c32fa Drop simplecov for now 2025-07-13 07:04:02 +00:00
Daniel O'Connor
2b5ed05628 Merge pull request #4087 from Growstuff/remove-codeclimate
Yeet codeclimate into the sun
2025-07-13 15:56:00 +09:30
Daniel O'Connor
df9f8445ad Yeet codeclimate into the sun 2025-07-13 06:22:31 +00:00
dependabot[bot]
534d493693 Bump bullet from 8.0.7 to 8.0.8
Bumps [bullet](https://github.com/flyerhzm/bullet) from 8.0.7 to 8.0.8.
- [Changelog](https://github.com/flyerhzm/bullet/blob/main/CHANGELOG.md)
- [Commits](https://github.com/flyerhzm/bullet/compare/8.0.7...8.0.8)

---
updated-dependencies:
- dependency-name: bullet
  dependency-version: 8.0.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-09 09:27:09 +00:00
dependabot[bot]
3bfa1af67b Merge pull request #4060 from Growstuff/dependabot/bundler/jquery-ui-rails-8.0.0 2025-06-09 09:26:01 +00:00
dependabot[bot]
e5e0aacfeb Bump jquery-ui-rails from 413265e to 8.0.0
Bumps [jquery-ui-rails](https://github.com/jquery-ui-rails/jquery-ui-rails) from `413265e` to 8.0.0. This release includes the previously tagged commit.
- [Release notes](https://github.com/jquery-ui-rails/jquery-ui-rails/releases)
- [Commits](413265e81f...v8.0.0)

---
updated-dependencies:
- dependency-name: jquery-ui-rails
  dependency-version: 8.0.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-09 09:03:05 +00:00
dependabot[bot]
962cf0bca7 Merge pull request #4068 from Growstuff/dependabot/bundler/rubocop-1.76.0 2025-06-09 09:01:37 +00:00
dependabot[bot]
419ee298ff Bump rubocop from 1.75.5 to 1.76.0
Bumps [rubocop](https://github.com/rubocop/rubocop) from 1.75.5 to 1.76.0.
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.75.5...v1.76.0)

---
updated-dependencies:
- dependency-name: rubocop
  dependency-version: 1.76.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-04 07:03:20 +00:00
Daniel O'Connor
1462279b60 Merge pull request #4011 from Growstuff/dev
March 2025-ish release
2025-03-29 16:43:24 +10:30
77 changed files with 1066 additions and 1659 deletions

View File

@@ -1,47 +0,0 @@
plugins:
brakeman:
enabled: false # codeclimate's brakeman is stuck in rails 5 rules
bundler-audit:
enabled: true
coffeelint:
enabled: true
duplication:
enabled: true
config:
languages:
- ruby
- javascript
editorconfig:
enabled: true
eslint:
enabled: true
fixme:
enabled: true
haml-lint:
enabled: true
nodesecurity:
enabled: true
rubocop:
enabled: true
channel: "rubocop-1-11"
scss-lint:
enabled: true
shellcheck:
enabled: true
ratings:
paths:
- "**.rb"
- "**.ru"
- "**.js"
- "**.coffee"
- "**.scss"
- "**.haml"
- Gemfile.lock
exclude_paths:
- config/
- db/
- spec/
- public/
- app/assets/stylesheets/bootstrap-accessibility.css
- app/assets/javascripts/bootstrap*
- app/assets/stylesheets/leaflet_overrides.scss

102
.github/workflows/ci-features-places.yml vendored Normal file
View File

@@ -0,0 +1,102 @@
name: CI Features - Admin
on: [pull_request]
jobs:
rspec:
runs-on: ubuntu-latest
services:
db:
image: postgres
env:
##
# The Postgres service fails its docker health check unless you
# specify these environment variables
#
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: growstuff_test
ports: ['5432:5432']
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
APP_DOMAIN_NAME: localhost:3000
APP_PROTOCOL: http
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
DATABASE_URL: postgres://postgres:postgres@localhost:5432/growstuff_test
DEVISE_SECRET_KEY: secret
ELASTIC_SEARCH_VERSION: "7.5.1-amd64"
GROWSTUFF_EMAIL: "noreply@test.growstuff.org"
GROWSTUFF_FLICKR_KEY: secretkey"
GROWSTUFF_FLICKR_SECRET: secretsecret
GROWSTUFF_SITE_NAME: "Growstuff (travis)"
RAILS_ENV: test
RAILS_SECRET_TOKEN: supersecret
steps:
- name: Checkout this repo
uses: actions/checkout@v4
- name: Configure sysctl limits
run: |
sudo swapoff -a
sudo sysctl -w vm.swappiness=1
sudo sysctl -w fs.file-max=262144
sudo sysctl -w vm.max_map_count=262144
- name: Start Elasticsearch
uses: elastic/elastic-github-actions/elasticsearch@master
with:
stack-version: 7.5.1
##
# Cache Yarn modules
#
# See https://github.com/actions/cache/blob/master/examples.md#node---yarn for details
#
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Setup yarn cache
uses: actions/cache@v4
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install required OS packages
run: |
sudo apt-get -y install libpq-dev google-chrome-stable
- name: Install NodeJS
uses: actions/setup-node@v4
with:
node-version: '12'
- name: Install Ruby (version given by .ruby-version) and Bundler
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Install required JS packages
run: yarn install
- name: install chrome
run: sudo apt-get install google-chrome-stable
- name: Prepare database for testing
run: bundle exec rails db:prepare
- name: precompile assets
run: bundle exec rails assets:precompile
- name: index into elastic search
run: bundle exec rails search:reindex
- name: Run rspec (places/)
run: bundle exec rspec spec/features/places/ -fd

102
.github/workflows/ci-features-posts.yml vendored Normal file
View File

@@ -0,0 +1,102 @@
name: CI Features - Admin
on: [pull_request]
jobs:
rspec:
runs-on: ubuntu-latest
services:
db:
image: postgres
env:
##
# The Postgres service fails its docker health check unless you
# specify these environment variables
#
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: growstuff_test
ports: ['5432:5432']
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
APP_DOMAIN_NAME: localhost:3000
APP_PROTOCOL: http
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
DATABASE_URL: postgres://postgres:postgres@localhost:5432/growstuff_test
DEVISE_SECRET_KEY: secret
ELASTIC_SEARCH_VERSION: "7.5.1-amd64"
GROWSTUFF_EMAIL: "noreply@test.growstuff.org"
GROWSTUFF_FLICKR_KEY: secretkey"
GROWSTUFF_FLICKR_SECRET: secretsecret
GROWSTUFF_SITE_NAME: "Growstuff (travis)"
RAILS_ENV: test
RAILS_SECRET_TOKEN: supersecret
steps:
- name: Checkout this repo
uses: actions/checkout@v4
- name: Configure sysctl limits
run: |
sudo swapoff -a
sudo sysctl -w vm.swappiness=1
sudo sysctl -w fs.file-max=262144
sudo sysctl -w vm.max_map_count=262144
- name: Start Elasticsearch
uses: elastic/elastic-github-actions/elasticsearch@master
with:
stack-version: 7.5.1
##
# Cache Yarn modules
#
# See https://github.com/actions/cache/blob/master/examples.md#node---yarn for details
#
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Setup yarn cache
uses: actions/cache@v4
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install required OS packages
run: |
sudo apt-get -y install libpq-dev google-chrome-stable
- name: Install NodeJS
uses: actions/setup-node@v4
with:
node-version: '12'
- name: Install Ruby (version given by .ruby-version) and Bundler
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Install required JS packages
run: yarn install
- name: install chrome
run: sudo apt-get install google-chrome-stable
- name: Prepare database for testing
run: bundle exec rails db:prepare
- name: precompile assets
run: bundle exec rails assets:precompile
- name: index into elastic search
run: bundle exec rails search:reindex
- name: Run rspec (posts/)
run: bundle exec rspec spec/features/posts/ -fd

View File

@@ -107,19 +107,5 @@ jobs:
- name: Run rspec (photos/)
run: bundle exec rspec spec/features/photos/ -fd
- name: Run rspec (places/)
run: bundle exec rspec spec/features/places/ -fd
- name: Run rspec (plantings/)
run: bundle exec rspec spec/features/plantings/ -fd
- name: Run rspec (posts/)
run: bundle exec rspec spec/features/posts/ -fd
- name: Run rspec (rss/)
run: bundle exec rspec spec/features/rss/ -fd
- name: Report to code climate
run: |
gem install codeclimate-test-reporter
codeclimate-test-reporter
run: bundle exec rspec spec/features/rss/ -fd

View File

@@ -127,9 +127,4 @@ jobs:
run: bundle exec rspec spec/routing/ -fd --fail-fast
- name: Run rspec (request)
run: bundle exec rspec spec/requests/ -fd --fail-fast
- name: Report to code climate
run: |
gem install codeclimate-test-reporter
codeclimate-test-reporter
run: bundle exec rspec spec/requests/ -fd --fail-fast

View File

@@ -1 +1 @@
3.3.7
3.3.8

View File

@@ -175,6 +175,7 @@ group :development, :test do
gem 'rubocop-rspec_rails'
gem 'webrat' # provides HTML matchers for view tests
gem 'crowdin-cli' # for translations
gem 'dotenv-rails'
# cli utils
@@ -188,15 +189,16 @@ end
group :test do
gem 'axe-core-capybara'
gem 'axe-core-rspec'
gem 'codeclimate-test-reporter', require: false
gem 'rails-controller-testing'
gem 'selenium-webdriver'
gem 'timecop'
gem 'vcr'
gem "rspec-rebound"
gem "percy-capybara", "~> 5.0.0"
end
group :travis do
gem 'platform-api'
end
gem "percy-capybara", "~> 5.0.0"

View File

@@ -135,14 +135,14 @@ GEM
descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
base64 (0.2.0)
base64 (0.3.0)
bcrypt (3.1.20)
benchmark (0.4.0)
benchmark (0.4.1)
better_errors (2.10.1)
erubi (>= 1.0.0)
rack (>= 0.9.0)
rouge (>= 1.0.0)
bigdecimal (3.1.9)
bigdecimal (3.2.2)
bluecloth (2.2.0)
bonsai-elasticsearch-rails (7.0.1)
elasticsearch-model (< 8)
@@ -156,7 +156,7 @@ GEM
actionpack (>= 6.1)
activemodel (>= 6.1)
builder (3.3.0)
bullet (8.0.7)
bullet (8.0.8)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
byebug (12.0.0)
@@ -185,8 +185,6 @@ GEM
ssrf_filter (~> 1.0)
chartkick (5.1.5)
childprocess (5.0.0)
codeclimate-test-reporter (1.0.9)
simplecov (<= 0.13)
coderay (1.1.3)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
@@ -202,6 +200,14 @@ GEM
concurrent-ruby (1.3.5)
connection_pool (2.5.3)
crass (1.0.6)
crowdin-api (1.12.0)
open-uri (>= 0.1.0, < 0.2.0)
rest-client (>= 2.0.0, < 2.2.0)
crowdin-cli (0.2.2)
crowdin-api (>= 0.2.0)
gli (>= 2.7.0)
i18n (>= 0.6.4)
rubyzip (>= 1.0.0)
csv (3.3.1)
csv_shaper (1.4.0)
activesupport (>= 3.0.0)
@@ -222,15 +228,15 @@ GEM
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
diff-lcs (1.6.1)
diff-lcs (1.6.2)
discard (1.4.0)
activerecord (>= 4.2, < 9.0)
docile (1.1.5)
domain_name (0.6.20240107)
dotenv (3.1.8)
dotenv-rails (3.1.8)
dotenv (= 3.1.8)
railties (>= 6.1)
drb (2.2.1)
drb (2.2.3)
dumb_delegator (1.1.0)
elasticsearch (7.0.0)
elasticsearch-api (= 7.0.0)
@@ -245,23 +251,24 @@ GEM
elasticsearch-transport (7.0.0)
faraday
multi_json
erb (5.0.1)
erubi (1.13.1)
erubis (2.7.0)
excon (1.2.5)
logger
execjs (2.10.0)
factory_bot (6.5.0)
activesupport (>= 5.0.0)
factory_bot_rails (6.4.4)
factory_bot (6.5.4)
activesupport (>= 6.1.0)
factory_bot_rails (6.5.0)
factory_bot (~> 6.5)
railties (>= 5.0.0)
faker (3.5.1)
railties (>= 6.1.0)
faker (3.5.2)
i18n (>= 1.8.11, < 2)
faraday (2.13.1)
faraday (2.13.4)
faraday-net_http (>= 2.0, < 3.5)
json
logger
faraday-net_http (3.4.0)
faraday-net_http (3.4.1)
net-http (>= 0.5.0)
ffi (1.16.3)
flickraw (0.9.10)
@@ -276,6 +283,8 @@ GEM
gibbon (1.2.1)
httparty
multi_json (>= 1.9.0)
gli (2.22.2)
ostruct
globalid (1.2.1)
activesupport (>= 6.1)
gravatar-ultimate (2.0.0)
@@ -296,7 +305,7 @@ GEM
activesupport (>= 5.1)
haml (>= 4.0.6)
railties (>= 5.1)
haml_lint (0.62.0)
haml_lint (0.66.0)
haml (>= 5.0)
parallel (~> 1.10)
rainbow
@@ -312,6 +321,9 @@ GEM
webrick
highline (3.1.2)
reline
http-accept (1.7.0)
http-cookie (1.0.8)
domain_name (~> 0.5)
httparty (0.22.0)
csv
mini_mime (>= 1.0.0)
@@ -329,7 +341,7 @@ GEM
rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.8, >= 1.8.1)
terminal-table (>= 1.5.1)
icalendar (2.11.0)
icalendar (2.11.2)
base64
ice_cube (~> 0.16)
logger
@@ -348,7 +360,7 @@ GEM
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (2.12.0)
json (2.13.2)
json-schema (5.1.0)
addressable (~> 2.8)
jsonapi-resources (0.10.7)
@@ -393,6 +405,10 @@ GEM
matrix (0.4.2)
memcachier (0.0.2)
method_source (1.1.0)
mime-types (3.7.0)
logger
mime-types-data (~> 3.2025, >= 3.2025.0507)
mime-types-data (3.2025.0805)
mimemagic (0.4.3)
nokogiri (~> 1)
rake
@@ -416,11 +432,12 @@ GEM
timeout
net-smtp (0.5.0)
net-protocol
netrc (0.11.0)
nio4r (2.7.4)
nokogiri (1.18.8)
nokogiri (1.18.9)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.18.8-x86_64-linux-gnu)
nokogiri (1.18.9-x86_64-linux-gnu)
racc (~> 1.4)
oauth (0.5.6)
oj (3.16.10)
@@ -438,15 +455,17 @@ GEM
omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1)
rack
open-uri (0.1.0)
orm_adapter (0.5.0)
ostruct (0.6.1)
ostruct (0.6.2)
parallel (1.27.0)
parser (3.3.8.0)
parser (3.3.9.0)
ast (~> 2.4.1)
racc
percy-capybara (5.0.0)
capybara (>= 3)
pg (1.5.9)
pg (1.6.1)
pg (1.6.1-x86_64-linux)
platform-api (3.8.0)
heroics (~> 0.1.1)
moneta (~> 1.0.0)
@@ -463,11 +482,11 @@ GEM
date
stringio
public_suffix (6.0.1)
puma (6.6.0)
puma (6.6.1)
nio4r (~> 2.0)
query_diet (0.7.2)
racc (1.8.1)
rack (2.2.15)
rack (2.2.17)
rack-cors (2.0.2)
rack (>= 2.0.0)
rack-protection (3.2.0)
@@ -498,7 +517,7 @@ GEM
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1)
rails-dom-testing (2.2.0)
rails-dom-testing (2.3.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
@@ -523,41 +542,47 @@ GEM
zeitwerk (~> 2.6)
rainbow (3.1.1)
raindrops (0.20.1)
rake (13.2.1)
rake (13.3.0)
rate_throttle_client (0.1.2)
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (~> 1.0)
rdoc (6.13.1)
rdoc (6.14.2)
erb
psych (>= 4.0.0)
recaptcha (5.19.0)
recaptcha (5.20.1)
redis-client (0.23.2)
connection_pool
regexp_parser (2.10.0)
regexp_parser (2.11.1)
reline (0.6.1)
io-console (~> 0.5)
responders (3.1.1)
actionpack (>= 5.2)
railties (>= 5.2)
rest-client (2.1.0)
http-accept (>= 1.7.0, < 2.0)
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
rexml (3.4.1)
rouge (4.1.2)
rspec (3.13.0)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-activemodel-mocks (1.2.1)
rspec-activemodel-mocks (1.3.0)
activemodel (>= 3.0)
activesupport (>= 3.0)
rspec-mocks (>= 2.99, < 4.0)
rspec-core (3.13.3)
rspec-core (3.13.5)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.4)
rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.4)
rspec-mocks (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-rails (8.0.0)
rspec-rails (8.0.1)
actionpack (>= 7.2)
activesupport (>= 7.2)
railties (>= 7.2)
@@ -565,7 +590,9 @@ GEM
rspec-expectations (~> 3.13)
rspec-mocks (~> 3.13)
rspec-support (~> 3.13)
rspec-support (3.13.3)
rspec-rebound (0.2.1)
rspec-core (~> 3.3)
rspec-support (3.13.4)
rspectre (0.2.0)
parser (>= 3.3.7.1)
prism (~> 1.3)
@@ -581,7 +608,7 @@ GEM
rswag-ui (2.16.0)
actionpack (>= 5.2, < 8.1)
railties (>= 5.2, < 8.1)
rubocop (1.75.5)
rubocop (1.79.2)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
@@ -589,10 +616,10 @@ GEM
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.44.0, < 2.0)
rubocop-ast (>= 1.46.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.44.1)
rubocop-ast (1.46.0)
parser (>= 3.3.7.2)
prism (~> 1.4)
rubocop-capybara (2.22.1)
@@ -635,13 +662,13 @@ GEM
sprockets (> 3.0)
sprockets-rails
tilt
scout_apm (5.6.4)
scout_apm (5.7.0)
parser
searchkick (5.3.1)
activemodel (>= 6.1)
hashie
securerandom (0.4.1)
selenium-webdriver (4.32.0)
selenium-webdriver (4.34.0)
base64 (~> 0.2)
logger (~> 1.4)
rexml (~> 3.2, >= 3.2.5)
@@ -653,11 +680,6 @@ GEM
logger
rack (>= 2.2.4)
redis-client (>= 0.22.2)
simplecov (0.13.0)
docile (~> 1.1.0)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
sprockets (3.7.5)
base64
concurrent-ruby (~> 1.0)
@@ -669,14 +691,14 @@ GEM
ssrf_filter (1.1.2)
stringio (3.1.7)
sysexits (1.2.0)
temple (0.10.3)
temple (0.10.4)
terminal-table (4.0.0)
unicode-display_width (>= 1.1.1, < 4)
terser (1.2.5)
execjs (>= 0.3.0, < 3)
thor (1.3.2)
thor (1.4.0)
thread_safe (0.3.6)
tilt (2.6.0)
tilt (2.6.1)
timecop (0.9.10)
timeout (0.4.3)
trollop (1.16.2)
@@ -718,7 +740,7 @@ GEM
webrick
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.7.2)
zeitwerk (2.7.3)
PLATFORMS
ruby
@@ -744,9 +766,9 @@ DEPENDENCIES
capybara-email
capybara-screenshot
chartkick
codeclimate-test-reporter
coffee-rails
comfortable_mexican_sofa!
crowdin-cli
csv_shaper
dalli
database_cleaner
@@ -805,6 +827,7 @@ DEPENDENCIES
responders
rspec-activemodel-mocks
rspec-rails
rspec-rebound
rspectre
rswag-api
rswag-specs
@@ -834,7 +857,7 @@ DEPENDENCIES
xmlrpc
RUBY VERSION
ruby 3.3.7p123
ruby 3.3.8p144
BUNDLED WITH
2.4.22

View File

@@ -1,7 +1,6 @@
# 🌱 Growstuff
![Build status](https://github.com/Growstuff/growstuff/workflows/CI/badge.svg)
[![Code Climate](https://codeclimate.com/github/Growstuff/growstuff/badges/gpa.svg)](https://codeclimate.com/github/Growstuff/growstuff)
Welcome to the Growstuff project.

View File

@@ -30,3 +30,7 @@
@import "predictions";
@import "homepage";
@import "maps";
@view-transition {
navigation: auto;
}

View File

@@ -24,9 +24,6 @@ class ActivitiesController < DataController
end
def show
# @activity is loaded by load_and_authorize_resource.
# We need to ensure comments are eager-loaded.
@activity = Activity.includes(comments: :author).find(params[:id])
respond_with @activity
end

View File

@@ -13,66 +13,43 @@ class CommentsController < ApplicationController
end
def new
@commentable = find_commentable
if @commentable
@comment = @commentable.comments.new
@comments = @commentable.comments.post_order # Assuming post_order is generic enough or will be adapted
respond_with(@comment) # Changed from @comments to @comment, or @commentable
@comment = Comment.new
@post = Post.find_by(id: params[:post_id])
if @post
@comments = @post.comments
respond_with(@comments)
else
redirect_to(request.referer || root_url,
alert: "Cannot add a comment to a non-existent or unspecified item.")
alert: "Can't post a comment on a non-existent post")
end
end
def edit
# @comment is loaded by load_and_authorize_resource
@comments = @comment.commentable.comments.post_order # Assuming post_order is generic
@comments = @comment.post.comments
end
def create
@commentable = find_commentable
if @commentable
@comment = @commentable.comments.new(comment_params)
@comment.author = current_member
@comment.save
respond_with @comment, location: @comment.commentable # Redirect to the commentable parent
else
redirect_to(request.referer || root_url,
alert: "Cannot create comment for a non-existent or unspecified item.")
end
@comment = Comment.new(comment_params)
@comment.author = current_member
@comment.save
respond_with @comment, location: @comment.post
end
def update
# @comment is loaded by load_and_authorize_resource
@comment.update(comment_params) # body is permitted by comment_params
respond_with @comment, location: @comment.commentable # Redirect to the commentable parent
@comment.update(body: comment_params['body'])
respond_with @comment, location: @comment.post
end
def destroy
# @comment is loaded by load_and_authorize_resource
@commentable = @comment.commentable # Store before destroying
@post = @comment.post
@comment.destroy
respond_with @comment, location: @commentable # Redirect to the commentable parent
respond_with(@post)
end
private
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
model_name = $1.classify
# Ensure model_name is one of the expected commentable types
# to prevent arbitrary model lookups.
allowed_commentables = %w[Post Photo Planting Harvest Activity]
if allowed_commentables.include?(model_name)
return model_name.constantize.find_by(id: value)
end
end
end
nil
end
def comment_params
params.require(:comment).permit(:body) # Removed post_id
params.require(:comment).permit(:body, :post_id)
end
end

View File

@@ -75,6 +75,7 @@ class CropsController < ApplicationController
end
def show
@problems = Problem.joins(plantings: :crop).where(crops: { id: @crop.id }).distinct
respond_to do |format|
format.html do
@posts = @crop.posts.order(created_at: :desc).paginate(page: params[:page])

View File

@@ -32,9 +32,6 @@ class HarvestsController < DataController
end
def show
# @harvest is loaded by load_and_authorize_resource.
# We need to ensure comments are eager-loaded.
@harvest = Harvest.includes(comments: :author).find(params[:id])
@matching_plantings = matching_plantings if @harvest.owner == current_member
@photos = @harvest.photos.order(created_at: :desc).paginate(page: params[:page])
respond_with(@harvest)

View File

@@ -20,9 +20,6 @@ class PhotosController < ApplicationController
end
def show
# @photo is loaded by load_and_authorize_resource.
# We need to ensure comments are eager-loaded.
@photo = Photo.includes(comments: :author).find(params[:id])
@crops = Crop.distinct.joins(:photo_associations).where(photo_associations: { photo: @photo })
respond_with(@photo)
end

View File

@@ -34,9 +34,6 @@ class PlantingsController < DataController
end
def show
# @planting is loaded by load_and_authorize_resource.
# We need to ensure comments are eager-loaded.
@planting = Planting.includes(comments: :author).find(params[:id])
@photos = @planting.photos.includes(:owner).order(date_taken: :desc)
@harvests = Harvest.search(where: { planting_id: @planting.id })
@current_activities = @planting.activities.current.includes(:owner).order(created_at: :desc)
@@ -110,7 +107,7 @@ class PlantingsController < DataController
:crop_id, :description, :garden_id, :planted_at,
:parent_seed_id,
:quantity, :sunniness, :planted_from, :finished,
:finished_at
:finished_at, :failed, problem_ids: []
)
end

View File

@@ -0,0 +1,76 @@
# frozen_string_literal: true
class ProblemsController < ApplicationController
before_action :authenticate_member!, except: %i(index show)
load_and_authorize_resource id_param: :slug, class: Problem
respond_to :html, :json
def index
@problems = Problem.approved.popular.paginate(page: params[:page])
@num_requested_problems = requested_problems.size if current_member
respond_with @problems
end
def requested
@requested = requested_problems.paginate(page: params[:page])
respond_with @requested
end
def show
@problem = Problem.friendly.find(params[:id])
@plantings = @problem.plantings.paginate(page: params[:page])
respond_with @problem
end
def new
@problem = Problem.new
respond_with @problem
end
def edit; end
def create
@problem = Problem.new(problem_params)
if current_member.role? :problem_wrangler
@problem.creator = current_member
else
@problem.requester = current_member
@problem.approval_status = "pending"
end
@problem.save
respond_with @problem
end
def update
if can?(:wrangle, @problem)
@problem.approval_status = 'rejected' if params.fetch("reject", false)
@problem.approval_status = 'approved' if params.fetch("approve", false)
end
@problem.update(problem_params)
respond_with @problem
end
def wrangle
@approval_status = params[:approval_status]
@problems = case @approval_status
when "pending"
Problem.pending_approval
when "rejected"
Problem.rejected
else
Problem.recent
end.paginate(page: params[:page])
@problem_wranglers = Role.problem_wranglers
respond_with @problems
end
private
def problem_params
params.require(:problem).permit(:name, :reason_for_rejection, :rejection_notes)
end
def requested_problems
current_member.requested_problems.pending_approval
end
end

View File

@@ -19,6 +19,10 @@ class SeedsController < DataController
where['parent_planting'] = @planting.id
end
if params[:tradeable_to].present?
where['tradeable_to'] = params[:tradeable_to]
end
@show_all = (params[:all] == '1')
where['finished'] = false unless @show_all

View File

@@ -32,7 +32,7 @@ module ApplicationHelper
# of HAML, Tilt, and dynamic compilation with interpolated ruby.
def markdownify(text)
translator = Haml::Filters::GrowstuffMarkdown.new
translator.expand_members!(translator.expand_crops!(text.to_s))
translator.expand_members!(translator.expand_problems!(translator.expand_crops!(text.to_s)))
end
#

View File

@@ -34,6 +34,8 @@ class Ability
# are wranglers or admins
cannot :read, Crop
can :read, Crop, approval_status: "approved"
cannot :read, Problem
can :read, Problem, approval_status: "approved"
# scientific names should only be viewable if associated crop is approved
cannot :read, ScientificName
can :read, ScientificName do |sn|
@@ -56,6 +58,8 @@ class Ability
# members can see even rejected or pending crops if they requested it
can :read, Crop, requester_id: member.id
can :requested, Crop # see list of crops they've requested
can :read, Problem, requester_id: member.id
can :requested, Problem
# managing your own user settings
can :update, Member, id: member.id
@@ -82,8 +86,14 @@ class Ability
can :gbif, Crop
end
if member.role? :problem_wrangler
can :wrangle, Problem
can :manage, Problem
end
# any member can create a crop provisionally
can :create, Crop
can :create, Problem
# can create & destroy their own authentications against other sites.
can :create, Authentication
@@ -98,19 +108,7 @@ class Ability
can :destroy, Like, member_id: member.id
can :create, Comment
can :update, Comment, author_id: member.id
can :destroy, Comment do |comment|
is_author = comment.author_id == member.id
is_commentable_owner = false
if comment.commentable.present?
if comment.commentable.respond_to?(:owner_id) && comment.commentable.owner_id == member.id
is_commentable_owner = true
# Posts use author_id for their "owner"
elsif comment.commentable.respond_to?(:author_id) && comment.commentable.author_id == member.id
is_commentable_owner = true
end
end
is_author || is_commentable_owner
end
can :destroy, Comment, author_id: member.id
# same deal for gardens and plantings
can :create, Garden

View File

@@ -2,31 +2,26 @@
class Comment < ApplicationRecord
belongs_to :author, class_name: 'Member', inverse_of: :comments
belongs_to :commentable, polymorphic: true, counter_cache: true
belongs_to :post, counter_cache: true
scope :post_order, -> { order(created_at: :asc) } # for display on post page
after_create do
recipient = if commentable.respond_to?(:author)
commentable.author.id
elsif commentable.respond_to?(:owner)
commentable.owner.id
end
recipient = post.author.id
sender = author.id
# don't send notifications to yourself
if recipient && recipient != sender
if recipient != sender
Notification.create(
recipient_id: recipient,
sender_id: sender,
subject: "#{author} commented on your #{commentable.class.name.downcase}",
subject: "#{author} commented on #{post.subject}",
body:,
commentable_id: commentable.id,
commentable_type: commentable.class.name
post_id: post.id
)
end
end
def to_s
"#{author.login_name} commented on #{commentable.class.name.downcase} ##{commentable.id}"
"#{author.login_name} commented on #{post.subject}"
end
end

View File

@@ -17,6 +17,7 @@ class Crop < ApplicationRecord
has_many :scientific_names, dependent: :delete_all
has_many :alternate_names, dependent: :delete_all
has_many :plantings, dependent: :destroy
has_many :problems, through: :plantings
has_many :seeds, dependent: :destroy
has_many :harvests, dependent: :destroy
has_many :photo_associations, dependent: :delete_all, inverse_of: :crop

View File

@@ -42,6 +42,10 @@ class Member < ApplicationRecord
inverse_of: :requester
has_many :created_crops, class_name: 'Crop', foreign_key: 'creator_id', dependent: :nullify,
inverse_of: :creator
has_many :requested_problems, class_name: 'Problem', foreign_key: 'requester_id', dependent: :nullify,
inverse_of: :requester
has_many :created_problems, class_name: 'Problem', foreign_key: 'creator_id', dependent: :nullify,
inverse_of: :creator
has_many :created_alternate_names, class_name: 'AlternateName', foreign_key: 'creator_id', inverse_of: :creator
has_many :created_scientific_names, class_name: 'ScientificName', foreign_key: 'creator_id', inverse_of: :creator

View File

@@ -3,7 +3,7 @@
class Notification < ApplicationRecord
belongs_to :sender, class_name: 'Member', inverse_of: :sent_notifications
belongs_to :recipient, class_name: 'Member', inverse_of: :notifications
belongs_to :commentable, polymorphic: true, optional: true
belongs_to :post, optional: true
validates :subject, length: { maximum: 255 }

View File

@@ -24,6 +24,8 @@ class Planting < ApplicationRecord
belongs_to :crop, counter_cache: true
has_many :harvests, dependent: :destroy
has_many :activities, dependent: :destroy
has_many :planting_problems, dependent: :destroy
has_many :problems, through: :planting_problems
#
# Ancestry of food

View File

@@ -0,0 +1,8 @@
# frozen_string_literal: true
class PlantingProblem < ApplicationRecord
include PhotoCapable
belongs_to :planting
belongs_to :problem
end

View File

@@ -13,11 +13,14 @@ class Post < ApplicationRecord
has_many :comments, dependent: :destroy
has_many :crop_posts, dependent: :delete_all
has_many :crops, through: :crop_posts
has_many :problem_posts, dependent: :delete_all
has_many :problems, through: :problem_posts
after_create :send_notification
#
# Triggers
after_save :update_crop_posts_association
after_save :update_problem_posts_association
default_scope { joins(:author).merge(Member.kept) } # Ensures the owner still exists
@@ -75,6 +78,17 @@ class Post < ApplicationRecord
end
end
def update_problem_posts_association
problems.clear
# look for problems mentioned in the post. eg. [aphids](problem)
body.scan(Haml::Filters::GrowstuffMarkdown::PROBLEM_REGEX) do |_m|
problem_name = Regexp.last_match(1)
problem = Problem.case_insensitive_name(problem_name).first
# create association
problems << problem if problem && problems.exclude?(problem)
end
end
def send_notification
recipients = []
sender = author.id

88
app/models/problem.rb Normal file
View File

@@ -0,0 +1,88 @@
# frozen_string_literal: true
class Problem < ApplicationRecord
extend FriendlyId
include PhotoCapable
include SearchCrops # Note: This might need to be adapted to SearchProblems
friendly_id :name, use: %i(slugged finders)
##
## Relationships
belongs_to :creator, class_name: 'Member', optional: true, inverse_of: :created_problems
belongs_to :requester, class_name: 'Member', optional: true, inverse_of: :requested_problems
has_many :planting_problems, dependent: :delete_all
has_many :plantings, through: :planting_problems
has_many :problem_posts, dependent: :delete_all
has_many :posts, through: :problem_posts, dependent: :delete_all
##
## Scopes
scope :recent, -> { approved.order(created_at: :desc) }
scope :popular, -> { approved.order(Arel.sql("plantings_count desc, lower(name) asc")) }
scope :pending_approval, -> { where(approval_status: "pending") }
scope :approved, -> { where(approval_status: "approved") }
scope :rejected, -> { where(approval_status: "rejected") }
scope :interesting, -> { approved.has_photos }
scope :has_photos, -> { includes(:photos).where.not(photos: { id: nil }) }
##
## Validations
validates :reason_for_rejection, presence: true, if: :rejected?
validate :must_be_rejected_if_rejected_reasons_present
validate :must_have_meaningful_reason_for_rejection
validates :name, uniqueness: { scope: :approval_status }, if: :pending?
def to_s
name
end
def to_param
slug
end
def pending?
approval_status == "pending"
end
def approved?
approval_status == "approved"
end
def rejected?
approval_status == "rejected"
end
def approval_statuses
%w(rejected pending approved)
end
def reasons_for_rejection
["already in database", "not a pest or disease", "not enough information", "other"]
end
def rejection_explanation
return rejection_notes if reason_for_rejection == "other"
reason_for_rejection
end
def self.case_insensitive_name(name)
where(["lower(problems.name) = :value", { value: name.downcase }])
end
private
def must_be_rejected_if_rejected_reasons_present
return if rejected?
return unless reason_for_rejection.present? || rejection_notes.present?
errors.add(:approval_status, "must be rejected if a reason for rejection is present")
end
def must_have_meaningful_reason_for_rejection
return unless reason_for_rejection == "other" && rejection_notes.blank?
errors.add(:rejection_notes, "must be added if the reason for rejection is \"other\"")
end
end

View File

@@ -0,0 +1,6 @@
# frozen_string_literal: true
class ProblemPost < ApplicationRecord
belongs_to :problem
belongs_to :post
end

View File

@@ -8,7 +8,7 @@ class Role < ApplicationRecord
has_and_belongs_to_many :members
class << self
%i(crop_wranglers admins).each do |method|
%i(crop_wranglers admins problem_wranglers).each do |method|
define_method method do
slug = method.to_s.singularize.dasherize
Role.where(slug:).try(:first).try(:members)

View File

@@ -44,8 +44,5 @@
%a{name: 'plantings'}
= render 'plantings/card', planting: @activity.planting
%section.comments.mt-4
= render 'comments/comments', commentable: @activity
.col-md-4.col-xs-12

View File

@@ -1,21 +0,0 @@
.comments-section.mt-4
%h4.mb-3 Comments
- if commentable.comments.any?
.list-group
- commentable.comments.post_order.each do |comment|
.list-group-item.p-0.mb-2.border-0
= render 'comments/comment', comment: comment
- else
%p No comments yet.
- if can? :create, Comment # Assuming a general ability to create comments
.mt-3
= link_to "Add Comment", new_polymorphic_path([commentable, Comment.new]), class: 'btn btn-primary'
%hr/
-# This section is for rendering the new/edit form directly on the page if needed,
-# but the primary "Add Comment" link above goes to the comments/new page.
-# If @new_comment is passed, it means we want to show the form.
- if defined?(@new_comment) && @new_comment && can?(:create, Comment) # Check @new_comment specifically
%h5.mt-3 Leave a comment
= render 'comments/form', commentable: commentable, comment: @new_comment

View File

@@ -3,14 +3,14 @@
- if content_for? :title
%h1.h2-responsive.text-center
%strong=yield :title
= form_for(commentable ? [commentable, comment] : comment, html: { class: "form-horizontal" }) do |f|
- if comment.errors.any?
= form_for(@comment, html: { class: "form-horizontal" }) do |f|
- if @comment.errors.any?
#error_explanation
%h2
= pluralize(comment.errors.size, "error")
= pluralize(@comment.errors.size, "error")
prohibited this comment from being saved:
%ul
- comment.errors.full_messages.each do |msg|
- @comment.errors.full_messages.each do |msg|
%li= msg
.md-form
@@ -21,3 +21,6 @@
= render partial: "shared/markdown_help"
.actions.text-right
= f.submit 'Post comment', class: 'btn btn-primary'
- if defined?(@post)
.field
= f.hidden_field :post_id, value: @post.id

View File

@@ -1,2 +1,7 @@
%h2 Edit Comment
= render 'comments/form', commentable: @comment.commentable, comment: @comment
= content_for :title, "Editing comment"
%p
Editing comment on
= link_to @comment.post.subject, @comment.post
= render 'form'

View File

@@ -1,5 +1,11 @@
%h2
Add comment to
= @commentable.class.name.downcase
= content_for :title, "New comment"
= render 'comments/form', commentable: @commentable, comment: @comment
%section.blog-post
.card.post{ id: "post-#{@post.id}" }
.card-header
%h2.display-3= @post.subject
.card-body= render "posts/single", post: @post || @comment.post, subject: true
= render partial: "posts/comments", locals: { post: @post || @comment.post }
= render 'form'

View File

@@ -3,4 +3,3 @@
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

@@ -30,6 +30,15 @@
- @crop.companions.each do |companion|
= render 'crops/tiny', crop: companion
- if @problems.any?
%section.problems
%h2 Problems
- @problems.group_by(&:name).each do |problem_name, problems|
- problem = problems.first
= link_to problem_path(problem) do
= problem_name
%span.badge.badge-secondary= problems.size
%section.photos
= cute_icon
= render 'crops/photos', crop: @crop

View File

@@ -66,8 +66,5 @@
Havested from
= link_to @harvest.planting, @harvest.planting
%section.comments.mt-4
= render 'comments/comments', commentable: @harvest
.col-md-4.col-xs-12
= render @harvest.crop

View File

@@ -5,5 +5,3 @@
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))

View File

@@ -51,7 +51,8 @@
%section.seeds
= cute_icon
= render 'seeds'
%p.text-right= link_to "#{t('home.seeds.view_all')} »", seeds_path, class: 'btn btn-block'
%p.text-right
= link_to "#{t('home.seeds.view_all')} »", seeds_path(tradeable_to: ['locally', 'nationally', 'internationally']), class: 'btn btn-block'
.col-12.col-lg-6
%section.discussion.text-center
= cute_icon

View File

@@ -46,8 +46,4 @@
- else
= @photo.license_name
= render "associations", photo: @photo
.row
.col-md-9
= render 'comments/comments', commentable: @photo
= render "associations", photo: @photo

View File

@@ -47,6 +47,11 @@
= f.number_field :quantity, label: 'How many?', min: 1
= f.text_area :description, rows: 6, label: 'Tell us more about it'
= f.collection_check_boxes :problem_ids, Problem.approved.order(:name), :id, :name do |b|
.form-check
= b.check_box
= b.label
.row
.col-md-6
= f.check_box :finished, label: 'Mark as finished'

View File

@@ -51,6 +51,23 @@
.col-md-8.col-xs-12
%section= render 'facts', planting: @planting
- if @planting.problems.any?
%section.problems
%h2 Problems
- @planting.planting_problems.each do |planting_problem|
.card
.card-header
%h3= planting_problem.problem.name
.card-body
- if planting_problem.photos.any?
.row
- planting_problem.photos.each do |photo|
.col-md-4
= image_tag photo.thumbnail_url, class: 'img-fluid'
- else
%p No photos of this problem for this planting yet.
- if @planting.description.present?
= cute_icon
.card
@@ -79,9 +96,6 @@
.col-md-12
%p Nothing is currently planned here.
%section.comments.mt-4
= render 'comments/comments', commentable: @planting
.col-md-4.col-xs-12
= render @planting.crop

View File

@@ -9,4 +9,3 @@
- else
%h2 There are no comments yet

View File

@@ -22,6 +22,10 @@
= render "shared/signin_signup",
to: "or to start using #{ENV['GROWSTUFF_SITE_NAME']} to track what you're planting and harvesting"
- content_for :buttonbar do
- if @post.comments.count > 10 && can?(:create, Comment)
= link_to 'Comment', new_comment_path(post_id: @post.id), class: 'btn'
- content_for :breadcrumbs do
%li.breadcrumb-item= link_to @post.author, @post.author
%li.breadcrumb-item= link_to 'posts', member_posts_path(member_slug: @post.author.slug)
@@ -43,10 +47,13 @@
.card-footer
= render 'likes/likes', object: @post
.float-right
-# Link removed as it's now part of the _comments partial
- if can? :create, Comment
= link_to new_comment_path(post_id: @post.id), class: 'btn' do
= icon 'fas', 'comment'
Comment
%section.comments
= render 'comments/comments', commentable: @post
= render "comments", post: @post
.col-md-4.col-12
= render @post.author

View File

@@ -0,0 +1,54 @@
= bootstrap_form_for(@problem) do |f|
- if @problem.errors.any?
#error_explanation.alert.alert-warning{role: "alert"}
%h3
= pluralize(@problem.errors.size, "error")
prohibited this problem from being saved:
%ul
- @problem.errors.full_messages.each do |msg|
%li= msg
.card.col-12.col-md-8.mx-auto.float-none.white
.card-header
- if content_for? :title
%h1.h2-responsive.text-center
%strong=yield :title
.card-body
- if can? :wrangle, @problem
%p
%span.help-block
As a problem wrangler, you can approve or reject problem suggestions.
%h2 Basic information
.form-group#new_problem
= f.text_field :name, required: true
%span.help-block
The common name for the problem, in English (required).
- if can? :wrangle, @problem
Wranglers: please ensure this is singular, and capitalize
proper nouns only.
- if (can?(:wrangle, @problem) && @problem.requester) || (cannot?(:wrangle, @problem) && @problem.new_record?)
%h2 Problem request notes
= f.text_area :request_notes, rows: 3, id: 'request_notes', label: 'Comments'
- unless can? :wrangle, @problem
%p
When you submit this form, your suggestion will be sent to our team of
volunteer problem wranglers for review. We'll let you know the outcome as soon as we can.
- if can?(:wrangle, @problem) && @problem.requester
= f.select(:reason_for_rejection, @problem.reasons_for_rejection, include_blank: true)
= f.text_area :rejection_notes, rows: 3
%span.help-block
Please provide additional notes why this problem request was rejected if the above reasons do not apply.
.card-footer
.text-right
- if @problem.approved?
= f.submit 'Save'
- else
= f.submit 'Reject', class: 'btn btn-danger', name: 'reject'
= f.submit 'Approve and save', class: 'btn btn-success', name: 'approve'

View File

@@ -0,0 +1,10 @@
- cache problem do
.card.problem-thumbnail
= link_to problem_path(id: problem.slug) do
= image_tag(problem.thumbnail_url.presence || placeholder_image,
alt: "Image of #{problem.name}",
class: 'img img-card')
.text
%h3.problem-name= link_to problem.name, problem_path(id: problem.slug)

View File

@@ -0,0 +1,30 @@
- content_for :title, "Edit problem: #{@problem.name}"
- if @problem.approval_status == "approved"
- if @problem.requester
%p
Requested by #{link_to @problem.requester, @problem.requester}
#{distance_of_time_in_words(@problem.created_at, Time.zone.now)} ago.
%p
Approved by #{link_to @problem.creator, @problem.creator}.
- else
%p
Added by
= link_to @problem.creator, @problem.creator
#{distance_of_time_in_words(@problem.created_at, Time.zone.now)} ago.
- elsif @problem.approval_status == "pending"
.alert.alert-danger
%p
Requested by #{link_to @problem.requester, @problem.requester}
#{distance_of_time_in_words(@problem.created_at, Time.zone.now)} ago.
%p
Status: #{@problem.approval_status}.
- elsif @problem.approval_status == "rejected"
.alert.alert-danger
%p
Requested by #{link_to @problem.requester, @problem.requester}
#{distance_of_time_in_words(@problem.created_at, Time.zone.now)} ago.
%p
Status: #{@problem.approval_status} by #{link_to @problem.creator, @problem.creator}.
= render 'form'

View File

@@ -0,0 +1,13 @@
- content_for :title, t('.title')
- content_for :breadcrumbs do
%li.breadcrumb-item.active= link_to 'Problems', problems_path
%section.problems
%h2= t('.title')
= will_paginate @problems
.index-cards
- @problems.each do |p|
= render 'problems/thumbnail', problem: p
= will_paginate @problems

View File

@@ -0,0 +1,15 @@
- content_for :title, (can?(:wrangle, @problem) ? "New problem" : "Suggest a problem")
- unless can? :wrangle, @problem
%p
Thanks for taking the time to suggest a problem! Our problem database is
managed by volunteers, and we appreciate your help. Here are some
things to consider when suggesting a new problem:
%ul
%li
First, you might want to search our problems
to make sure we don't have it already, perhaps under an alternate name.
%li
The Growstuff database only contains problems related to growing edible plants.
= render 'form'

View File

@@ -0,0 +1,26 @@
- content_for :title, @problem.name
- content_for :breadcrumbs do
%li.breadcrumb-item= link_to 'Problems', problems_path
%li.breadcrumb-item.active= link_to @problem.name.capitalize, @problem
.jumbotron
%h1= @problem.name
.row
.col-md-9
%section.plantings
%h2 Plantings with this problem
= will_paginate @plantings
.index-cards
- @plantings.each do |p|
= render 'plantings/thumbnail', planting: p
= will_paginate @plantings
.col-md-3
.card
.card-body
%h4.card-title= @problem.name
%p.card-text
This problem has been reported on
= pluralize(@problem.plantings.count, 'planting')
so far.

View File

@@ -0,0 +1,51 @@
- content_for :title, "Problem Wrangling"
%h1 Problem Wrangling
%nav.nav
= link_to "Add Problem", new_problem_path, class: 'btn'
%section.problem_wranglers
%h2 Problem Wranglers
- @problem_wranglers.each do |problem_wrangler|
= render 'members/tiny', member: problem_wrangler
%hr/
%section
%h2 Problems
%ul#myTab.nav.nav-tabs{role: "tablist"}
%li.nav-item
%a#home-tab.nav-link{ href: wrangle_problems_path, role: "tab", class: @approval_status.blank? ? 'active' : ''}
Recently added
%li.nav-item
%a#profile-tab.nav-link{ href: wrangle_problems_path(approval_status: "pending"), role: "tab", class: @approval_status == "pending" ? 'active' : ''}
Pending approval
%li.nav-item
%a#contact-tab.nav-link{ href: wrangle_problems_path(approval_status: "rejected"), role: "tab", class: @approval_status == "rejected" ? 'active' : ''} Rejected
%table.table.table-striped.table-bordered.table-sm{id: @approval_status.blank? ? 'recently-added-problems' : "#{@approval_status}-problems" }
%tr
%th Name
%th Requested by
- if @approval_status == "rejected"
%th Rejected by
- if @approval_status != "rejected" && @approval_status != "pending"
%th Added by
%th When
- @problems.each do |p|
%tr
%td
= link_to edit_problem_path(p) do
= icon 'fas', 'bug'
= p.name
%td= p.requester.present? ? (link_to p.requester, p.requester) : "N/A"
- unless @approval_status == "pending"
%td= p.creator.present? ? (link_to p.creator, p.creator) : "N/A"
%td
= distance_of_time_in_words(p.created_at, Time.zone.now)
ago.
= page_entries_info @problems
= will_paginate @problems

View File

@@ -17,6 +17,14 @@
= check_box_tag 'active', 'all', @show_all
include finished
%hr/
%section.filters
%h2 Tradeable
%ul.nav.flex-column
%li.nav-item= link_to "All tradable seeds", seeds_path(tradeable_to: ['locally', 'nationally', 'internationally'])
- Seed::TRADABLE_TO_VALUES.each do |value|
- unless value == 'nowhere'
%li.nav-item= link_to value.capitalize, seeds_path(tradeable_to: value)
%hr/
- if @owner
= render @owner
- if @crop

View File

@@ -5,7 +5,7 @@ Mailboxer.setup do |config|
config.uses_emails = true
# Configures the default from for emails sent for Messages and Notifications
config.default_from = "no-reply@growstuff.org"
config.default_from = "Growstuff <#{ENV.fetch('GROWSTUFF_EMAIL', "no-reply@growstuff.org")}>"
# Configures the methods needed by mailboxer
# config.email_method = :email

View File

@@ -0,0 +1,7 @@
en:
mailboxer:
message_mailer:
subject_new: "New Notification: %{subject}"
subject_reply: "New Reply: %{subject}"
notification_mailer:
subject: "New notification: %{subject}"

View File

@@ -28,17 +28,13 @@ Rails.application.routes.draw do
resources :photos, only: :index
end
concern :commentable do
resources :comments, only: [:new, :create, :edit, :update, :destroy], shallow: true
end
resources :gardens, concerns: :has_photos, param: :slug do
get 'timeline' => 'charts/gardens#timeline', constraints: { format: 'json' }
resources :garden_collaborators
end
resources :plantings, concerns: [:has_photos, :commentable], param: :slug do
resources :plantings, concerns: :has_photos, param: :slug do
resources :harvests
resources :seeds
collection do
@@ -51,11 +47,11 @@ Rails.application.routes.draw do
get 'crop/:crop' => 'seeds#index', as: 'seeds_by_crop', on: :collection
end
resources :harvests, concerns: [:has_photos, :commentable], param: :slug do
resources :harvests, concerns: :has_photos, param: :slug do
get 'crop/:crop' => 'harvests#index', as: 'harvests_by_crop', on: :collection
end
resources :posts, concerns: :commentable do
resources :posts do
get 'author/:author' => 'posts#index', as: 'by_author', on: :collection
end
@@ -66,7 +62,7 @@ Rails.application.routes.draw do
end
resources :alternate_names
resources :plant_parts
resources :photos, concerns: :commentable
resources :photos
resources :photo_associations, only: :destroy
@@ -93,6 +89,13 @@ Rails.application.routes.draw do
end
end
resources :problems, param: :slug do
collection do
get 'requested'
get 'wrangle'
end
end
resources :comments
resources :forums
@@ -116,7 +119,7 @@ Rails.application.routes.draw do
end
resources :messages
resources :activities, concerns: :commentable, param: :slug
resources :activities, param: :slug
resources :conversations do
collection do
delete 'destroy_multiple'

22
crowdin.yml Normal file
View File

@@ -0,0 +1,22 @@
# Hi there!
#
# This is a configuration file for the Crowdin CLI.
# You'll need to replace the placeholder project_id with your actual
# project ID from CrowdIn.
#
# For more information, see the Crowdin CLI documentation:
# https://support.crowdin.com/enterprise/cli-v3/
#
project_id: "your-project-id"
api_token_env: "CROWDIN_API_TOKEN"
base_path: "."
files:
- source: '/config/locales/en.yml'
translation: '/config/locales/%two_letters_code%.yml'
# The 'ignore' property is used to exclude files from processing.
# We are ignoring the existing Japanese translation file.
ignore:
- '/config/locales/ja.yml'
- source: '/config/locales/*.en.yml'
translation: '/config/locales/%file_name%.%two_letters_code%.yml'

View File

@@ -1,32 +0,0 @@
class AddCommentableToComments < ActiveRecord::Migration[6.0]
def up
add_column :comments, :commentable_id, :integer
add_column :comments, :commentable_type, :string
add_index :comments, [:commentable_type, :commentable_id]
# Data migration
execute <<-SQL
UPDATE comments
SET commentable_id = post_id,
commentable_type = 'Post'
WHERE post_id IS NOT NULL;
SQL
remove_column :comments, :post_id
end
def down
add_column :comments, :post_id, :integer
# Data migration back
execute <<-SQL
UPDATE comments
SET post_id = commentable_id
WHERE commentable_type = 'Post';
SQL
remove_index :comments, [:commentable_type, :commentable_id]
remove_column :comments, :commentable_type
remove_column :comments, :commentable_id
end
end

View File

@@ -4,6 +4,7 @@
class Haml::Filters
class GrowstuffMarkdown
CROP_REGEX = /(?<!\\)\[([^\[\]]+?)\]\(crop\)/
PROBLEM_REGEX = /(?<!\\)\[([^\[\]]+?)\]\(problem\)/
MEMBER_REGEX = /(?<!\\)\[([^\[\]]+?)\]\(member\)/
MEMBER_AT_REGEX = /(?<!\\)(@\w+)/
MEMBER_ESCAPE_AT_REGEX = /(?<!\\)\\(?=@\w+)/
@@ -42,6 +43,25 @@ class Haml::Filters
end
end
def expand_problems!(text)
# turn [aphids](problem) into [aphids](http://growstuff.org/problems/aphids)
text.gsub(PROBLEM_REGEX) do
problem_str = Regexp.last_match(1)
# find problem case-insensitively
problem = Problem.where('lower(name) = ?', problem_str.downcase).first
problem_link problem, problem_str
end
end
def problem_link(problem, link_text)
if problem
url = Rails.application.routes.url_helpers.problem_url(problem, only_path: true)
"[#{link_text}](#{url})"
else
link_text
end
end
def crop_link(crop, link_text)
if crop
url = Rails.application.routes.url_helpers.crop_url(crop, only_path: true)

View File

@@ -8,4 +8,18 @@ namespace :openfarm do
Rails.logger = Logger.new(STDOUT)
OpenfarmService.new.import!
end
desc "Delete all pictures with source OpenFarm"
task delete_pictures: :environment do
puts "Deleting pictures with source OpenFarm..."
photos_to_delete = Photo.where(source: 'openfarm')
count = photos_to_delete.count
photos_to_delete.each do |photo|
photo.associations.each do |photo_association|
photo_association.delete
end
photo.delete
end
puts "Deleted #{count} pictures."
end
end

View File

@@ -31,316 +31,95 @@ describe CommentsController do
end
end
# Shared examples for commentable controllers
RSpec.shared_examples "a commentable controller" do |commentable_factory_name, commentable_param_key|
let(:commentable_owner) { FactoryBot.create(:member) }
let(:comment_author) { FactoryBot.create(:member) }
let(:admin_user) { FactoryBot.create(:member, :admin) }
let!(:commentable) do
if [:post].include?(commentable_factory_name)
FactoryBot.create(commentable_factory_name, author: commentable_owner)
else
FactoryBot.create(commentable_factory_name, owner: commentable_owner)
describe "GET new" do
let(:post) { FactoryBot.create(:post) }
describe "with valid params" do
before { get :new, params: { post_id: post.id } }
let(:old_comment) { FactoryBot.create(:comment, post:) }
it "picks up post from params" do
expect(assigns(:post)).to eq(post)
end
it "assigns the old comments as @comments" do
expect(assigns(:comments)).to eq [old_comment]
end
end
describe "GET #new" do
context "when not logged in" do
before { sign_out member }
it "redirects to login" do
get :new, params: { commentable_param_key => commentable.id }
expect(response).to redirect_to(new_member_session_path)
end
end
it "dies if no post specified" do
get :new
expect(response).not_to be_successful
end
end
context "when logged in" do
before { sign_in comment_author }
it "assigns @commentable and new @comment" do
get :new, params: { commentable_param_key => commentable.id }
expect(assigns(:commentable)).to eq(commentable)
expect(assigns(:comment)).to be_a_new(Comment)
expect(response).to render_template(:new)
end
describe "GET edit" do
let(:post) { FactoryBot.create(:post) }
it "redirects if commentable not found" do
get :new, params: { commentable_param_key => -1 }
expect(response).to redirect_to(request.referer || root_url)
expect(flash[:alert]).to match(/Cannot add a comment to a non-existent or unspecified item/)
end
before { get :edit, params: { id: comment.to_param } }
describe "my comment" do
let!(:comment) { FactoryBot.create(:comment, author: member, post:) }
let!(:old_comment) { FactoryBot.create(:comment, post:, created_at: Time.zone.yesterday) }
it "assigns previous comments as @comments" do
expect(assigns(:comments)).to eq([comment, old_comment])
end
end
describe "POST #create" do
let(:valid_comment_params) { { body: "This is a great comment." } }
let(:invalid_comment_params) { { body: "" } }
describe "not my comment" do
let(:comment) { FactoryBot.create(:comment, post:) }
context "when not logged in" do
before { sign_out member }
it "redirects to login" do
post :create, params: { commentable_param_key => commentable.id, comment: valid_comment_params }
expect(response).to redirect_to(new_member_session_path)
end
end
it { expect(response).not_to be_successful }
end
end
context "when logged in" do
before { sign_in comment_author }
describe "PUT update" do
before { put :update, params: { id: comment.to_param, comment: valid_attributes } }
context "with valid params" do
it "creates a new Comment" do
expect {
post :create, params: { commentable_param_key => commentable.id, comment: valid_comment_params }
}.to change(Comment, :count).by(1)
end
describe "my comment" do
let(:comment) { FactoryBot.create(:comment, author: member) }
it "assigns the comment's author to current_member" do
post :create, params: { commentable_param_key => commentable.id, comment: valid_comment_params }
expect(Comment.last.author).to eq(comment_author)
end
it "redirects to the commentable's show page" do
post :create, params: { commentable_param_key => commentable.id, comment: valid_comment_params }
expect(response).to redirect_to(commentable)
end
end
context "with invalid params" do
it "does not create a comment" do
expect {
post :create, params: { commentable_param_key => commentable.id, comment: invalid_comment_params }
}.not_to change(Comment, :count)
end
it "re-renders the 'new' template (or commentable show with errors)" do
# The controller currently redirects if commentable is not found in create,
# but for invalid comment params, it should re-render or show errors.
# The current controller's create action saves and then responds.
# If save fails, it typically re-renders the form via respond_with.
# For this test, we'll assume it re-renders 'new' if save fails.
# A more precise test would check the response if @comment.save fails.
# For now, we'll check that it doesn't redirect to the commentable if save fails.
post :create, params: { commentable_param_key => commentable.id, comment: invalid_comment_params }
# Depending on how `respond_with` handles failure for new comment on commentable,
# it might render the commentable's show page or the comments/new template.
# The key is that it shouldn't be a successful redirect to the commentable.
# And @comment.errors should be present.
expect(assigns(:comment).errors).not_to be_empty
# Check for re-render of new or specific error handling view
# For now, checking that it's not a successful redirect if save fails.
# This might need adjustment based on actual controller error flow.
# A common pattern is to render the 'new' template again or the parent's show page.
# The `respond_with @comment, location: @comment.commentable` will try to redirect if valid.
# If invalid, it should re-render the action that led to the form.
# For `create` failing, it's often the `new` view or the parent resource's view.
# The controller has `respond_with @comment, location: @comment.commentable`.
# If `@comment` is not persisted, `respond_with` might render the `new` template by convention,
# or the template of the action (`create.js.erb` or `create.html.erb` if they exist).
# Given the setup, it's likely to re-render 'new' or the controller action's default.
# Let's assume for now the form is on the `new` page.
# This is a weak assertion. A better one would be to check for `render_template(:new)`
# if the controller is set up to do that on failure.
# However, `respond_with` is tricky. It might also render the `commentable` show page with errors.
# The `new` action in controller renders `respond_with(@comment)`.
# The `create` action has `respond_with @comment, location: @comment.commentable`.
# If `@comment.save` fails, `respond_with` will typically render the `new` template by default
# if `create.html.haml` doesn't exist, or it might try to render `commentable` show page
# with errors displayed by the form partial.
# Given the form is rendered via `comments/new`, let's assume it re-renders new.
# This part of the test may need refinement based on actual error rendering flow.
# A simple check: ensure it doesn't redirect to the commentable.
expect(response).not_to redirect_to(commentable_path(commentable))
# And that @commentable is still assigned for the form.
expect(assigns(:commentable)).to eq(commentable)
end
end
it "redirects if commentable not found" do
post :create, params: { commentable_param_key => -1, comment: valid_comment_params }
expect(response).to redirect_to(request.referer || root_url)
expect(flash[:alert]).to match(/Cannot create comment for a non-existent or unspecified item/)
end
it "redirects to the comment's post" do
expect(response).to redirect_to(comment.post)
end
end
describe "GET #edit" do
let!(:comment) { FactoryBot.create(:comment, commentable: commentable, author: comment_author) }
describe "not my comment" do
let(:comment) { FactoryBot.create(:comment) }
context "when not logged in" do
before { sign_out member }
it "redirects to login" do
get :edit, params: { id: comment.id }
expect(response).to redirect_to(new_member_session_path)
end
end
context "as comment author" do
before { sign_in comment_author }
it "assigns @comment and renders edit" do
get :edit, params: { id: comment.id }
expect(assigns(:comment)).to eq(comment)
expect(response).to render_template(:edit)
end
end
context "as admin" do
before { sign_in admin_user }
it "assigns @comment and renders edit" do
get :edit, params: { id: comment.id }
expect(assigns(:comment)).to eq(comment)
expect(response).to render_template(:edit)
end
end
context "as unauthorized user" do
let(:other_user) { FactoryBot.create(:member) }
before { sign_in other_user }
it "redirects or shows error" do
get :edit, params: { id: comment.id }
expect(response).to redirect_to(root_url) # Or some other unauthorized path
expect(flash[:alert]).to match(/You are not authorized to access this page./)
end
end
it { expect(response).not_to be_successful }
end
describe "PUT #update" do
let!(:comment) { FactoryBot.create(:comment, commentable: commentable, author: comment_author, body: "Original body") }
let(:updated_body) { "Updated comment body." }
describe "attempting to change post_id" do
let(:post) { FactoryBot.create(:post, subject: 'our post') }
let(:other_post) { FactoryBot.create(:post, subject: 'the other post') }
let(:valid_attributes) { { post_id: other_post.id, body: "kōrero" } }
let(:comment) { FactoryBot.create(:comment, author: member, post:) }
context "when not logged in" do
before { sign_out member }
it "redirects to login" do
put :update, params: { id: comment.id, comment: { body: updated_body } }
expect(response).to redirect_to(new_member_session_path)
end
end
context "as comment author" do
before { sign_in comment_author }
context "with valid params" do
it "updates the comment" do
put :update, params: { id: comment.id, comment: { body: updated_body } }
comment.reload
expect(comment.body).to eq(updated_body)
end
it "redirects to commentable show page" do
put :update, params: { id: comment.id, comment: { body: updated_body } }
expect(response).to redirect_to(commentable_path(commentable))
end
end
context "with invalid params (empty body)" do
it "does not update the comment and re-renders edit" do
put :update, params: { id: comment.id, comment: { body: "" } }
comment.reload
expect(comment.body).to eq("Original body") # Should not change
expect(response).to render_template(:edit)
end
end
end
context "as admin" do
before { sign_in admin_user }
it "updates the comment" do
put :update, params: { id: comment.id, comment: { body: updated_body } }
comment.reload
expect(comment.body).to eq(updated_body)
expect(response).to redirect_to(commentable_path(commentable))
end
end
context "as unauthorized user" do
let(:other_user) { FactoryBot.create(:member) }
before { sign_in other_user }
it "redirects or shows error" do
put :update, params: { id: comment.id, comment: { body: updated_body } }
expect(response).to redirect_to(root_url)
expect(flash[:alert]).to match(/You are not authorized to access this page./)
end
end
end
describe "DELETE #destroy" do
let!(:comment_to_delete) { FactoryBot.create(:comment, commentable: commentable, author: comment_author) }
context "when not logged in" do
before { sign_out member }
it "redirects to login" do
delete :destroy, params: { id: comment_to_delete.id }
expect(response).to redirect_to(new_member_session_path)
end
end
context "as comment author" do
before { sign_in comment_author }
it "deletes the comment" do
expect {
delete :destroy, params: { id: comment_to_delete.id }
}.to change(Comment, :count).by(-1)
end
it "redirects to commentable show page" do
delete :destroy, params: { id: comment_to_delete.id }
expect(response).to redirect_to(commentable_path(commentable))
end
end
context "as commentable owner" do
before { sign_in commentable_owner }
it "deletes the comment" do
# Ensure comment_author is not the same as commentable_owner for this test case
expect(comment_to_delete.author).not_to eq(commentable_owner)
expect {
delete :destroy, params: { id: comment_to_delete.id }
}.to change(Comment, :count).by(-1)
end
it "redirects to commentable show page" do
delete :destroy, params: { id: comment_to_delete.id }
expect(response).to redirect_to(commentable_path(commentable))
end
end
context "as admin" do
before { sign_in admin_user }
it "deletes the comment" do
expect {
delete :destroy, params: { id: comment_to_delete.id }
}.to change(Comment, :count).by(-1)
end
it "redirects to commentable show page" do
delete :destroy, params: { id: comment_to_delete.id }
expect(response).to redirect_to(commentable_path(commentable))
end
end
context "as unauthorized user" do
let(:other_user) { FactoryBot.create(:member) }
before { sign_in other_user }
it "does not delete the comment and redirects or shows error" do
expect {
delete :destroy, params: { id: comment_to_delete.id }
}.not_to change(Comment, :count)
expect(response).to redirect_to(root_url)
expect(flash[:alert]).to match(/You are not authorized to access this page./)
end
it "does not change post_id" do
comment.reload
expect(comment.post_id).to eq(post.id)
end
end
end
# Apply shared examples for each commentable type
context "for Post" do
it_behaves_like "a commentable controller", :post, :post_id
end
describe "DELETE destroy" do
before { delete :destroy, params: { id: comment.to_param } }
context "for Photo" do
it_behaves_like "a commentable controller", :photo, :photo_id
end
describe "my comment" do
let(:comment) { FactoryBot.create(:comment, author: member) }
context "for Planting" do
it_behaves_like "a commentable controller", :planting, :planting_id
end
it "redirects to the post the comment was on" do
expect(response).to redirect_to(comment.post)
end
end
context "for Harvest" do
it_behaves_like "a commentable controller", :harvest, :harvest_id
end
describe "not my comment" do
let(:comment) { FactoryBot.create(:comment) }
context "for Activity" do
it_behaves_like "a commentable controller", :activity, :activity_id
it { expect(response).not_to be_successful }
end
end
end

View File

@@ -1,19 +0,0 @@
# frozen_string_literal: true
FactoryBot.define do
factory :activity do
association :owner, factory: :member
sequence(:name) { |n| "Test Activity #{n}" }
description { "This is a test activity." }
category { "General" } # Example category
due_date { Time.zone.today + 1.week }
# Optional associations, uncomment and adjust if Activity model has these
# association :garden, factory: :garden
# association :planting, factory: :planting
trait :finished do
finished { true }
end
end
end

View File

@@ -2,30 +2,9 @@
FactoryBot.define do
factory :comment do
association :author, factory: :member # Explicitly use :member factory for author
post
author
sequence(:body) { |n| "OMG LOL #{n}" }
# Default to associating with a post if no specific commentable is provided
association :commentable, factory: :post
trait :for_post do
association :commentable, factory: :post
end
trait :for_photo do
association :commentable, factory: :photo
end
trait :for_planting do
association :commentable, factory: :planting
end
trait :for_harvest do
association :commentable, factory: :harvest
end
trait :for_activity do
association :commentable, factory: :activity
end
# because our commenters are more polite than YouTube's
end
end

View File

@@ -1,17 +0,0 @@
# frozen_string_literal: true
FactoryBot.define do
factory :member do
sequence(:login_name) { |n| "member#{n}" }
sequence(:email) { |n| "member#{n}@example.com" }
password { "password123" }
password_confirmation { "password123" }
confirmed_at { Time.zone.now } # Assuming Devise confirmable is used
trait :admin do
after(:create) { |member| member.add_role(:admin) }
end
# Add other traits if needed, e.g., for specific member states or roles
end
end

View File

@@ -1,38 +0,0 @@
# frozen_string_literal: true
FactoryBot.define do
factory :planting do
association :owner, factory: :member
association :crop
association :garden
planted_at { Time.zone.today - 1.month }
description { "My awesome planting." }
quantity { 1 } # Example attribute
# Add traits if needed
trait :finished do
finished { true }
finished_at { Time.zone.today - 1.day }
end
end
end
# Assuming Crop and Garden factories are needed and might not exist
# Minimal Crop factory
FactoryBot.define do
factory :crop do
sequence(:name) { |n| "Test Crop #{n}" }
# Add other necessary attributes for Crop
# e.g., approval_status if relevant for comments
approval_status { 'approved' } # Default to approved for simplicity
end
end
# Minimal Garden factory
FactoryBot.define do
factory :garden do
association :owner, factory: :member
sequence(:name) { |n| "Test Garden #{n}" }
# Add other necessary attributes for Garden
end
end

View File

@@ -1,37 +0,0 @@
# frozen_string_literal: true
FactoryBot.define do
factory :post do
association :author, factory: :member
association :forum # Assuming posts belong to a forum
sequence(:subject) { |n| "Test Post Subject #{n}" }
sequence(:body) { |n| "This is the body of test post #{n}." }
# Add traits if needed, e.g., for posts with photos, crops, etc.
trait :with_photos do
transient do
photos_count { 1 }
end
after(:create) do |post, evaluator|
create_list(:photo, evaluator.photos_count, owner: post.author, post_id: post.id) # Assuming Photo has post_id or similar
end
end
trait :with_crops do
transient do
crops_count { 1 }
end
after(:create) do |post, evaluator|
create_list(:crop, evaluator.crops_count, posts: [post]) # Assuming a has_and_belongs_to_many or has_many :through
end
end
end
end
# Assuming a Forum model exists and has a factory
FactoryBot.define do
factory :forum do
sequence(:name) { |n| "Test Forum #{n}" }
# Add other attributes for Forum as needed
end
end

View File

@@ -1,156 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.feature "Commenting on Activities", type: :feature, js: true do
let(:activity_owner) { FactoryBot.create(:member, login_name: "ActivityOwner") }
let(:commenter) { FactoryBot.create(:member, login_name: "Commenter") }
let(:other_member) { FactoryBot.create(:member, login_name: "OtherMember") }
let(:admin) { FactoryBot.create(:member, :admin, login_name: "AdminUser") }
let!(:activity) { FactoryBot.create(:activity, owner: activity_owner, name: "Gardening Day") }
def login_as(user)
visit new_member_session_path
fill_in "Login Name or Email", with: user.login_name
fill_in "Password", with: user.password
click_button "Log in"
expect(page).to have_content("Signed in successfully")
end
describe "User comments on an Activity" do
before do
login_as(commenter)
visit activity_path(activity)
end
it "allows a user to create a comment" do
expect(page).to have_content("Comments")
click_link "Add Comment"
expect(page).to have_current_path(new_activity_comment_path(activity))
expect(page).to have_content("Add comment to activity")
fill_in "comment_body", with: "Sounds like a fun activity!"
click_button "Post comment"
expect(page).to have_current_path(activity_path(activity))
expect(page).to have_content("Sounds like a fun activity!")
expect(page).to have_content(commenter.login_name)
end
end
describe "Editing comments on an Activity" do
let!(:comment_to_edit) { FactoryBot.create(:comment, commentable: activity, author: commenter, body: "Initial activity comment") }
context "as comment author" do
before do
login_as(commenter)
visit activity_path(activity)
find('.comment-body', text: "Initial activity comment").ancestor('.comment').find_button('Actions').click
click_link "Edit"
end
it "allows the author to edit their comment" do
expect(page).to have_current_path(edit_comment_path(comment_to_edit))
fill_in "comment_body", with: "Updated activity comment."
click_button "Post comment"
expect(page).to have_current_path(activity_path(activity))
expect(page).to have_content("Updated activity comment.")
expect(page).not_to have_content("Initial activity comment")
end
end
context "as admin" do
before do
login_as(admin)
visit activity_path(activity)
find('.comment-body', text: "Initial activity comment").ancestor('.comment').find_button('Actions').click
click_link "Edit"
end
it "allows admin to edit any comment" do
fill_in "comment_body", with: "Admin edited this activity comment."
click_button "Post comment"
expect(page).to have_content("Admin edited this activity comment.")
end
end
context "as unauthorized user" do
before do
login_as(other_member)
visit activity_path(activity)
end
it "does not show edit link for other's comment" do
comment_element = find('.comment-body', text: "Initial activity comment").ancestor('.comment')
expect(comment_element).not_to have_link("Edit")
expect(comment_element).not_to have_button("Actions")
end
end
end
describe "Deleting comments on an Activity" do
let!(:comment_to_delete) { FactoryBot.create(:comment, commentable: activity, author: commenter, body: "Delete this activity comment") }
context "as comment author" do
before do
login_as(commenter)
visit activity_path(activity)
find('.comment-body', text: "Delete this activity comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows the author to delete their comment" do
expect(page).not_to have_content("Delete this activity comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as activity owner" do
before do
login_as(activity_owner)
visit activity_path(activity)
find('.comment-body', text: "Delete this activity comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows the activity owner to delete any comment on their activity" do
expect(page).not_to have_content("Delete this activity comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as admin" do
before do
login_as(admin)
visit activity_path(activity)
find('.comment-body', text: "Delete this activity comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows admin to delete any comment" do
expect(page).not_to have_content("Delete this activity comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as unauthorized user" do
let!(:another_comment) { FactoryBot.create(:comment, commentable: activity, author: activity_owner, body: "Activity owner's comment") }
before do
login_as(other_member)
visit activity_path(activity)
end
it "does not show delete link for other's comment" do
comment_element = find('.comment-body', text: "Activity owner's comment").ancestor('.comment')
expect(comment_element).not_to have_link("Delete")
comment_element_2 = find('.comment-body', text: "Delete this activity comment").ancestor('.comment')
expect(comment_element_2).not_to have_link("Delete")
end
end
end
end

View File

@@ -0,0 +1,38 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'Commenting on a post' do
include_context 'signed in member'
let(:member) { create(:member) }
let(:post) { create(:post, author: member) }
before { visit new_comment_path post_id: post.id }
include_examples 'is accessible'
it "creating a comment" do
fill_in "comment_body", with: "This is a sample test for comment"
click_button "Post comment"
expect(page).to have_content "comment was successfully created."
expect(page).to have_content "Posted by"
page.percy_snapshot(page, name: 'Posting a comment')
end
context "editing a comment" do
let(:existing_comment) { create(:comment, post:, author: member) }
before do
visit edit_comment_path existing_comment
end
include_examples 'is accessible'
it "saving edit" do
fill_in "comment_body", with: "Testing edit for comment"
click_button "Post comment"
expect(page).to have_content "comment was successfully updated."
expect(page).to have_content "edited at"
end
end
end

View File

@@ -1,160 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.feature "Commenting on Harvests", type: :feature, js: true do
let(:harvest_owner) { FactoryBot.create(:member, login_name: "HarvestOwner") }
let(:commenter) { FactoryBot.create(:member, login_name: "Commenter") }
let(:other_member) { FactoryBot.create(:member, login_name: "OtherMember") }
let(:admin) { FactoryBot.create(:member, :admin, login_name: "AdminUser") }
# Ensure crop, planting, and garden are created for the harvest
let(:crop) { FactoryBot.create(:crop) }
let(:garden) { FactoryBot.create(:garden, owner: harvest_owner) } # Harvest owner also owns garden for simplicity
let(:planting) { FactoryBot.create(:planting, owner: harvest_owner, crop: crop, garden: garden) }
let!(:harvest) { FactoryBot.create(:harvest, owner: harvest_owner, planting: planting, description: "My test harvest") }
def login_as(user)
visit new_member_session_path
fill_in "Login Name or Email", with: user.login_name
fill_in "Password", with: user.password
click_button "Log in"
expect(page).to have_content("Signed in successfully")
end
describe "User comments on a Harvest" do
before do
login_as(commenter)
visit harvest_path(harvest)
end
it "allows a user to create a comment" do
expect(page).to have_content("Comments")
click_link "Add Comment"
expect(page).to have_current_path(new_harvest_comment_path(harvest))
expect(page).to have_content("Add comment to harvest")
fill_in "comment_body", with: "This harvest looks bountiful!"
click_button "Post comment"
expect(page).to have_current_path(harvest_path(harvest))
expect(page).to have_content("This harvest looks bountiful!")
expect(page).to have_content(commenter.login_name)
end
end
describe "Editing comments on a Harvest" do
let!(:comment_to_edit) { FactoryBot.create(:comment, commentable: harvest, author: commenter, body: "Initial harvest comment") }
context "as comment author" do
before do
login_as(commenter)
visit harvest_path(harvest)
find('.comment-body', text: "Initial harvest comment").ancestor('.comment').find_button('Actions').click
click_link "Edit"
end
it "allows the author to edit their comment" do
expect(page).to have_current_path(edit_comment_path(comment_to_edit))
fill_in "comment_body", with: "Updated harvest comment."
click_button "Post comment"
expect(page).to have_current_path(harvest_path(harvest))
expect(page).to have_content("Updated harvest comment.")
expect(page).not_to have_content("Initial harvest comment")
end
end
context "as admin" do
before do
login_as(admin)
visit harvest_path(harvest)
find('.comment-body', text: "Initial harvest comment").ancestor('.comment').find_button('Actions').click
click_link "Edit"
end
it "allows admin to edit any comment" do
fill_in "comment_body", with: "Admin edited this harvest comment."
click_button "Post comment"
expect(page).to have_content("Admin edited this harvest comment.")
end
end
context "as unauthorized user" do
before do
login_as(other_member)
visit harvest_path(harvest)
end
it "does not show edit link for other's comment" do
comment_element = find('.comment-body', text: "Initial harvest comment").ancestor('.comment')
expect(comment_element).not_to have_link("Edit")
expect(comment_element).not_to have_button("Actions")
end
end
end
describe "Deleting comments on a Harvest" do
let!(:comment_to_delete) { FactoryBot.create(:comment, commentable: harvest, author: commenter, body: "Delete this harvest comment") }
context "as comment author" do
before do
login_as(commenter)
visit harvest_path(harvest)
find('.comment-body', text: "Delete this harvest comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows the author to delete their comment" do
expect(page).not_to have_content("Delete this harvest comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as harvest owner" do
before do
login_as(harvest_owner)
visit harvest_path(harvest)
find('.comment-body', text: "Delete this harvest comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows the harvest owner to delete any comment on their harvest" do
expect(page).not_to have_content("Delete this harvest comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as admin" do
before do
login_as(admin)
visit harvest_path(harvest)
find('.comment-body', text: "Delete this harvest comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows admin to delete any comment" do
expect(page).not_to have_content("Delete this harvest comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as unauthorized user" do
let!(:another_comment) { FactoryBot.create(:comment, commentable: harvest, author: harvest_owner, body: "Harvest owner's comment") }
before do
login_as(other_member)
visit harvest_path(harvest)
end
it "does not show delete link for other's comment" do
comment_element = find('.comment-body', text: "Harvest owner's comment").ancestor('.comment')
expect(comment_element).not_to have_link("Delete")
comment_element_2 = find('.comment-body', text: "Delete this harvest comment").ancestor('.comment')
expect(comment_element_2).not_to have_link("Delete")
end
end
end
end

View File

@@ -1,158 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.feature "Commenting on Photos", type: :feature, js: true do
let(:photo_owner) { FactoryBot.create(:member, login_name: "PhotoOwner") }
let(:commenter) { FactoryBot.create(:member, login_name: "Commenter") }
let(:other_member) { FactoryBot.create(:member, login_name: "OtherMember") }
let(:admin) { FactoryBot.create(:member, :admin, login_name: "AdminUser") }
let!(:photo) { FactoryBot.create(:photo, owner: photo_owner, title: "Beautiful Sunset") }
def login_as(user)
visit new_member_session_path
fill_in "Login Name or Email", with: user.login_name
fill_in "Password", with: user.password
click_button "Log in"
expect(page).to have_content("Signed in successfully")
end
describe "User comments on a Photo" do
before do
login_as(commenter)
visit photo_path(photo)
end
it "allows a user to create a comment" do
expect(page).to have_content("Comments")
click_link "Add Comment" # From the _comments partial
expect(page).to have_current_path(new_photo_comment_path(photo))
expect(page).to have_content("Add comment to photo")
fill_in "comment_body", with: "What a stunning photo!"
click_button "Post comment"
expect(page).to have_current_path(photo_path(photo))
expect(page).to have_content("What a stunning photo!")
expect(page).to have_content(commenter.login_name)
end
end
describe "Editing comments on a Photo" do
let!(:comment_to_edit) { FactoryBot.create(:comment, commentable: photo, author: commenter, body: "Initial comment body") }
context "as comment author" do
before do
login_as(commenter)
visit photo_path(photo)
# Find the comment section and then the edit button within it.
# This assumes the comment body is unique enough or we can find by specific data-testid attributes if added.
find('.comment-body', text: "Initial comment body").ancestor('.comment').find_button('Actions').click
click_link "Edit"
end
it "allows the author to edit their comment" do
expect(page).to have_current_path(edit_comment_path(comment_to_edit))
fill_in "comment_body", with: "Updated comment body here."
click_button "Post comment"
expect(page).to have_current_path(photo_path(photo))
expect(page).to have_content("Updated comment body here.")
expect(page).not_to have_content("Initial comment body")
end
end
context "as admin" do
before do
login_as(admin)
visit photo_path(photo)
find('.comment-body', text: "Initial comment body").ancestor('.comment').find_button('Actions').click
click_link "Edit"
end
it "allows admin to edit any comment" do
fill_in "comment_body", with: "Admin edited this comment."
click_button "Post comment"
expect(page).to have_content("Admin edited this comment.")
end
end
context "as unauthorized user" do
before do
login_as(other_member)
visit photo_path(photo)
end
it "does not show edit link for other's comment" do
# Check within the specific comment's scope
comment_element = find('.comment-body', text: "Initial comment body").ancestor('.comment')
expect(comment_element).not_to have_link("Edit")
expect(comment_element).not_to have_button("Actions") # Or check that Actions doesn't show Edit
end
end
end
describe "Deleting comments on a Photo" do
let!(:comment_to_delete) { FactoryBot.create(:comment, commentable: photo, author: commenter, body: "This will be deleted") }
context "as comment author" do
before do
login_as(commenter)
visit photo_path(photo)
find('.comment-body', text: "This will be deleted").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows the author to delete their comment" do
expect(page).not_to have_content("This will be deleted")
expect(page).to have_content("Comment was successfully destroyed.") # Assuming flash message
end
end
context "as photo owner" do
before do
login_as(photo_owner)
visit photo_path(photo)
find('.comment-body', text: "This will be deleted").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows the photo owner to delete any comment on their photo" do
expect(page).not_to have_content("This will be deleted")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as admin" do
before do
login_as(admin)
visit photo_path(photo)
find('.comment-body', text: "This will be deleted").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows admin to delete any comment" do
expect(page).not_to have_content("This will be deleted")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as unauthorized user" do
let!(:another_comment) { FactoryBot.create(:comment, commentable: photo, author: photo_owner, body: "Photo owner's comment") }
before do
login_as(other_member) # other_member is not admin, not photo_owner, not author of `another_comment`
visit photo_path(photo)
end
it "does not show delete link for other's comment" do
comment_element = find('.comment-body', text: "Photo owner's comment").ancestor('.comment')
expect(comment_element).not_to have_link("Delete")
# Also check the original comment_to_delete by commenter
comment_element_2 = find('.comment-body', text: "This will be deleted").ancestor('.comment')
expect(comment_element_2).not_to have_link("Delete")
end
end
end
end

View File

@@ -1,158 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.feature "Commenting on Plantings", type: :feature, js: true do
let(:planting_owner) { FactoryBot.create(:member, login_name: "PlantingOwner") }
let(:commenter) { FactoryBot.create(:member, login_name: "Commenter") }
let(:other_member) { FactoryBot.create(:member, login_name: "OtherMember") }
let(:admin) { FactoryBot.create(:member, :admin, login_name: "AdminUser") }
# Ensure crop and garden are created for the planting
let(:crop) { FactoryBot.create(:crop) }
let(:garden) { FactoryBot.create(:garden, owner: planting_owner) }
let!(:planting) { FactoryBot.create(:planting, owner: planting_owner, crop: crop, garden: garden, description: "My test planting") }
def login_as(user)
visit new_member_session_path
fill_in "Login Name or Email", with: user.login_name
fill_in "Password", with: user.password
click_button "Log in"
expect(page).to have_content("Signed in successfully")
end
describe "User comments on a Planting" do
before do
login_as(commenter)
visit planting_path(planting)
end
it "allows a user to create a comment" do
expect(page).to have_content("Comments")
click_link "Add Comment"
expect(page).to have_current_path(new_planting_comment_path(planting))
expect(page).to have_content("Add comment to planting")
fill_in "comment_body", with: "This planting looks great!"
click_button "Post comment"
expect(page).to have_current_path(planting_path(planting))
expect(page).to have_content("This planting looks great!")
expect(page).to have_content(commenter.login_name)
end
end
describe "Editing comments on a Planting" do
let!(:comment_to_edit) { FactoryBot.create(:comment, commentable: planting, author: commenter, body: "Initial planting comment") }
context "as comment author" do
before do
login_as(commenter)
visit planting_path(planting)
find('.comment-body', text: "Initial planting comment").ancestor('.comment').find_button('Actions').click
click_link "Edit"
end
it "allows the author to edit their comment" do
expect(page).to have_current_path(edit_comment_path(comment_to_edit))
fill_in "comment_body", with: "Updated planting comment."
click_button "Post comment"
expect(page).to have_current_path(planting_path(planting))
expect(page).to have_content("Updated planting comment.")
expect(page).not_to have_content("Initial planting comment")
end
end
context "as admin" do
before do
login_as(admin)
visit planting_path(planting)
find('.comment-body', text: "Initial planting comment").ancestor('.comment').find_button('Actions').click
click_link "Edit"
end
it "allows admin to edit any comment" do
fill_in "comment_body", with: "Admin edited this planting comment."
click_button "Post comment"
expect(page).to have_content("Admin edited this planting comment.")
end
end
context "as unauthorized user" do
before do
login_as(other_member)
visit planting_path(planting)
end
it "does not show edit link for other's comment" do
comment_element = find('.comment-body', text: "Initial planting comment").ancestor('.comment')
expect(comment_element).not_to have_link("Edit")
expect(comment_element).not_to have_button("Actions")
end
end
end
describe "Deleting comments on a Planting" do
let!(:comment_to_delete) { FactoryBot.create(:comment, commentable: planting, author: commenter, body: "Delete this planting comment") }
context "as comment author" do
before do
login_as(commenter)
visit planting_path(planting)
find('.comment-body', text: "Delete this planting comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows the author to delete their comment" do
expect(page).not_to have_content("Delete this planting comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as planting owner" do
before do
login_as(planting_owner)
visit planting_path(planting)
find('.comment-body', text: "Delete this planting comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows the planting owner to delete any comment on their planting" do
expect(page).not_to have_content("Delete this planting comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as admin" do
before do
login_as(admin)
visit planting_path(planting)
find('.comment-body', text: "Delete this planting comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows admin to delete any comment" do
expect(page).not_to have_content("Delete this planting comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as unauthorized user" do
let!(:another_comment) { FactoryBot.create(:comment, commentable: planting, author: planting_owner, body: "Planting owner's comment") }
before do
login_as(other_member)
visit planting_path(planting)
end
it "does not show delete link for other's comment" do
comment_element = find('.comment-body', text: "Planting owner's comment").ancestor('.comment')
expect(comment_element).not_to have_link("Delete")
comment_element_2 = find('.comment-body', text: "Delete this planting comment").ancestor('.comment')
expect(comment_element_2).not_to have_link("Delete")
end
end
end
end

View File

@@ -1,176 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.feature "Commenting on Posts", type: :feature, js: true do
# Use existing 'signed in member' shared context if it provides Capybara login helper
# Otherwise, define one locally or ensure one is available.
# For consistency with other new specs, defining a local login_as helper.
let(:post_author) { FactoryBot.create(:member, login_name: "PostAuthor") }
let(:commenter) { FactoryBot.create(:member, login_name: "Commenter") }
let(:other_member) { FactoryBot.create(:member, login_name: "OtherMember") }
let(:admin) { FactoryBot.create(:member, :admin, login_name: "AdminUser") }
# Ensure a forum is created for the post if your Post factory/model requires it
let!(:forum) { FactoryBot.create(:forum) }
let!(:post) { FactoryBot.create(:post, author: post_author, forum: forum, subject: "My Test Post") }
def login_as(user)
visit new_member_session_path
fill_in "Login Name or Email", with: user.login_name
fill_in "Password", with: user.password
click_button "Log in"
expect(page).to have_content("Signed in successfully")
end
# Include shared examples for accessibility if they exist and are relevant
# For now, focusing on the commenting CRUD operations.
# The original spec had: include_examples 'is accessible'
# If this shared example exists and is desired, it can be added to relevant `before` blocks.
describe "User comments on a Post" do
before do
login_as(commenter)
visit post_path(post)
end
it "allows a user to create a comment" do
expect(page).to have_content("Comments") # From the _comments partial
# The "Add Comment" link is now within the _comments partial
# If the _comments partial is set up to show the form directly or link to new, this will work.
# Assuming it has a link:
click_link "Add Comment"
expect(page).to have_current_path(new_post_comment_path(post))
expect(page).to have_content("Add comment to post")
fill_in "comment_body", with: "This is a great post!"
click_button "Post comment"
expect(page).to have_current_path(post_path(post)) # Should redirect back to the post
expect(page).to have_content("This is a great post!")
expect(page).to have_content(commenter.login_name)
# Check for flash message if your controller sets one, e.g.:
# expect(page).to have_content("Comment was successfully created.")
end
end
describe "Editing comments on a Post" do
let!(:comment_to_edit) { FactoryBot.create(:comment, commentable: post, author: commenter, body: "Initial post comment") }
context "as comment author" do
before do
login_as(commenter)
visit post_path(post)
find('.comment-body', text: "Initial post comment").ancestor('.comment').find_button('Actions').click
click_link "Edit"
end
it "allows the author to edit their comment" do
expect(page).to have_current_path(edit_comment_path(comment_to_edit))
fill_in "comment_body", with: "Updated post comment."
click_button "Post comment"
expect(page).to have_current_path(post_path(post))
expect(page).to have_content("Updated post comment.")
expect(page).not_to have_content("Initial post comment")
# Check for flash message if your controller sets one, e.g.:
# expect(page).to have_content("Comment was successfully updated.")
end
end
context "as admin" do
before do
login_as(admin)
visit post_path(post)
find('.comment-body', text: "Initial post comment").ancestor('.comment').find_button('Actions').click
click_link "Edit"
end
it "allows admin to edit any comment" do
fill_in "comment_body", with: "Admin edited this post comment."
click_button "Post comment"
expect(page).to have_content("Admin edited this post comment.")
end
end
context "as unauthorized user" do
before do
login_as(other_member)
visit post_path(post)
end
it "does not show edit link for other's comment" do
comment_element = find('.comment-body', text: "Initial post comment").ancestor('.comment')
expect(comment_element).not_to have_link("Edit")
expect(comment_element).not_to have_button("Actions")
end
end
end
describe "Deleting comments on a Post" do
let!(:comment_to_delete) { FactoryBot.create(:comment, commentable: post, author: commenter, body: "Delete this post comment") }
context "as comment author" do
before do
login_as(commenter)
visit post_path(post)
find('.comment-body', text: "Delete this post comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows the author to delete their comment" do
expect(page).not_to have_content("Delete this post comment")
expect(page).to have_content("Comment was successfully destroyed.") # Assuming flash message
end
end
context "as post author (owner of commentable)" do
before do
# Ensure commenter is not the post_author for this specific test
expect(commenter).not_to eq(post_author)
login_as(post_author)
visit post_path(post)
find('.comment-body', text: "Delete this post comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows the post author to delete any comment on their post" do
expect(page).not_to have_content("Delete this post comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as admin" do
before do
login_as(admin)
visit post_path(post)
find('.comment-body', text: "Delete this post comment").ancestor('.comment').find_button('Actions').click
accept_alert { click_link "Delete" }
end
it "allows admin to delete any comment" do
expect(page).not_to have_content("Delete this post comment")
expect(page).to have_content("Comment was successfully destroyed.")
end
end
context "as unauthorized user" do
# Create a comment by the post_author to test against
let!(:another_comment) { FactoryBot.create(:comment, commentable: post, author: post_author, body: "Post author's comment") }
before do
login_as(other_member) # other_member is not admin, not post_author, not author of `another_comment`
visit post_path(post)
end
it "does not show delete link for other's comment" do
comment_element = find('.comment-body', text: "Post author's comment").ancestor('.comment')
expect(comment_element).not_to have_link("Delete")
# Also check the original comment_to_delete by commenter
comment_element_2 = find('.comment-body', text: "Delete this post comment").ancestor('.comment')
expect(comment_element_2).not_to have_link("Delete")
end
end
end
end

View File

@@ -3,152 +3,53 @@
require 'rails_helper'
describe Comment do
context "associations" do
let(:member) { FactoryBot.create(:member) } # Common author
context "basic" do
let(:comment) { FactoryBot.create(:comment) }
it "belongs to a commentable (Post)" do
post = FactoryBot.create(:post, author: member)
comment = FactoryBot.create(:comment, commentable: post, author: member)
comment.commentable.should be_an_instance_of Post
end
it "belongs to a commentable (Photo)" do
photo = FactoryBot.create(:photo, owner: member)
comment = FactoryBot.create(:comment, commentable: photo, author: member)
comment.commentable.should be_an_instance_of Photo
end
it "belongs to a commentable (Planting)" do
planting = FactoryBot.create(:planting, owner: member)
comment = FactoryBot.create(:comment, commentable: planting, author: member)
comment.commentable.should be_an_instance_of Planting
end
it "belongs to a commentable (Harvest)" do
harvest = FactoryBot.create(:harvest, owner: member)
comment = FactoryBot.create(:comment, commentable: harvest, author: member)
comment.commentable.should be_an_instance_of Harvest
end
it "belongs to a commentable (Activity)" do
activity = FactoryBot.create(:activity, owner: member)
comment = FactoryBot.create(:comment, commentable: activity, author: member)
comment.commentable.should be_an_instance_of Activity
it "belongs to a post" do
comment.post.should be_an_instance_of Post
end
it "belongs to an author" do
comment = FactoryBot.create(:comment, author: member) # Default commentable is Post
comment.author.should be_an_instance_of Member
end
end
RSpec.shared_examples "comment notifications" do |commentable_type, commentable_factory_name|
let(:commentable_owner) { FactoryBot.create(:member) }
let(:comment_author) { FactoryBot.create(:member) }
let!(:commentable) do
# For :post, the owner is :author. For others, it's :owner.
if commentable_factory_name == :post
FactoryBot.create(commentable_factory_name, author: commentable_owner)
else
FactoryBot.create(commentable_factory_name, owner: commentable_owner)
end
end
context "notifications" do
it "sends a notification when a comment is posted" do
expect do
FactoryBot.create(:comment, commentable: commentable, author: comment_author)
FactoryBot.create(:comment)
end.to change(Notification, :count).by(1)
end
it "sets the notification fields correctly" do
comment = FactoryBot.create(:comment, commentable: commentable, author: comment_author)
notification = Notification.last # More robust than Notification.first
notification.sender.should eq comment.author
notification.recipient.should eq commentable_owner
notification.subject.should include "commented on your #{commentable_type.downcase}"
notification.body.should eq comment.body
notification.commentable_id.should eq commentable.id
notification.commentable_type.should eq commentable_type
it "sets the notification fields" do
@c = FactoryBot.create(:comment)
@n = Notification.first
@n.sender.should eq @c.author
@n.recipient.should eq @c.post.author
@n.subject.should include 'commented on'
@n.body.should eq @c.body
@n.post.should eq @c.post
end
it "doesn't send notifications to yourself (when comment author is commentable owner)" do
it "doesn't send notifications to yourself" do
@m = FactoryBot.create(:member)
@p = FactoryBot.create(:post, author: @m)
expect do
FactoryBot.create(:comment, commentable: commentable, author: commentable_owner)
FactoryBot.create(:comment, post: @p, author: @m)
end.not_to change(Notification, :count)
end
end
context "notifications for Post" do
include_examples "comment notifications", "Post", :post
end
context "notifications for Photo" do
include_examples "comment notifications", "Photo", :photo
end
context "notifications for Planting" do
include_examples "comment notifications", "Planting", :planting
end
context "notifications for Harvest" do
include_examples "comment notifications", "Harvest", :harvest
end
context "notifications for Activity" do
include_examples "comment notifications", "Activity", :activity
end
RSpec.shared_examples "comment to_s method" do |commentable_type, commentable_factory_name|
let(:commentable_owner) { FactoryBot.create(:member) }
let(:comment_author) { FactoryBot.create(:member, login_name: "CommenterUser") }
let!(:commentable) do
# For :post, the owner is :author. For others, it's :owner.
obj = if commentable_factory_name == :post
FactoryBot.create(commentable_factory_name, author: commentable_owner)
else
FactoryBot.create(commentable_factory_name, owner: commentable_owner)
end
# Ensure commentable has a consistent ID for the test if possible, or just use its class name
obj
end
let(:comment) { FactoryBot.create(:comment, commentable: commentable, author: comment_author) }
it "returns a descriptive string" do
expected_string = "#{comment_author.login_name} commented on #{commentable_type.downcase} ##{commentable.id}"
comment.to_s.should eq expected_string
end
end
context "to_s method for Post" do
include_examples "comment to_s method", "Post", :post
end
context "to_s method for Photo" do
include_examples "comment to_s method", "Photo", :photo
end
context "to_s method for Planting" do
include_examples "comment to_s method", "Planting", :planting
end
context "to_s method for Harvest" do
include_examples "comment to_s method", "Harvest", :harvest
end
context "to_s method for Activity" do
include_examples "comment to_s method", "Activity", :activity
end
context "ordering" do
before do
@m = FactoryBot.create(:member)
# Ensure the commentable for ordering test is a Post, as it was originally
@p = FactoryBot.create(:post, author: @m)
@c1 = FactoryBot.create(:comment, commentable: @p, author: @m)
@c2 = FactoryBot.create(:comment, commentable: @p, author: @m)
@c1 = FactoryBot.create(:comment, post: @p, author: @m)
@c2 = FactoryBot.create(:comment, post: @p, author: @m)
end
it 'has a scope for ASC order for displaying on commentable page' do # Renamed for clarity
it 'has a scope for ASC order for displaying on post page' do
described_class.post_order.should eq [@c1, @c2]
end
end

View File

@@ -2,11 +2,6 @@
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require 'simplecov'
# output coverage locally AND send it to coveralls
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([SimpleCov::Formatter::HTMLFormatter])
require 'spec_helper'
require File.expand_path('../config/environment', __dir__)
require 'rspec/rails'
@@ -20,13 +15,13 @@ require 'capybara-screenshot/rspec'
require 'axe-capybara'
require 'axe-rspec'
# TODO: We may want to trial options.add_argument('--disable-dev-shm-usage') ### optional
# Required for running in the dev container
Capybara.register_driver :selenium_chrome_customised_headless do |app|
options = Selenium::WebDriver::Options.chrome
options.add_argument("--headless")
options.add_argument("--no-sandbox")
options.add_argument("--window-size=1920,1080")
options.add_argument("--disable-dev-shm-usage")
# driver = Selenium::WebDriver.for :chrome, options: options
@@ -125,8 +120,8 @@ RSpec.configure do |config|
# Prevent Poltergeist from fetching external URLs during feature tests
config.before(:each, :js) do
# TODO: Why are we setting this page size then straight afterwards, maximising?
width = 1280
height = 1280
width = 1920
height = 1080
Capybara.current_session.driver.browser.manage.window.resize_to(width, height)
if page.driver.browser.respond_to?(:url_blacklist)
@@ -137,6 +132,9 @@ RSpec.configure do |config|
]
end
page.driver.browser.manage.window.maximize if page.driver.browser.respond_to?(:manage)
# Historically, we wanted to .maximize; but this actually undoes the resize_to step above
# with chrome headless
# page.driver.browser.manage.window.maximize if page.driver.browser.respond_to?(:manage)
# puts "Maximized window size: #{page.driver.browser.manage.window.size}"
end
end

View File

@@ -16,8 +16,8 @@
# users commonly want.
#
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
require 'simplecov'
require 'percy/capybara'
require 'rspec/rebound'
require 'vcr'
VCR.configure do |c|
@@ -27,8 +27,6 @@ VCR.configure do |c|
c.configure_rspec_metadata!
end
SimpleCov.start
RSpec.configure do |config|
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
@@ -126,4 +124,20 @@ RSpec.configure do |config|
# Remember which tests failed, so you can run rspec with the `--only-failures` flag.
config.example_status_persistence_file_path = "tmp/examples.txt"
# show retry status in spec process
config.verbose_retry = true
# show exception that triggers a retry if verbose_retry is set to true
config.display_try_failure_messages = true
# run retry only on features
config.around :each, :js do |ex|
ex.run_with_retry retry: 3
end
# callback to be run between retries
config.retry_callback = proc do |ex|
# run some additional clean up task - can be filtered by example metadata
Capybara.reset! if ex.metadata[:js]
end
end

View File

@@ -0,0 +1,33 @@
# frozen_string_literal: true
require 'rails_helper'
require 'rake'
describe 'openfarm:delete_pictures' do
before(:all) do
Rails.application.load_tasks
end
# We need to do this because Rake tasks normally output to STDOUT, but we
# don't want to clutter up the test output.
before(:each) do
$stdout = StringIO.new
end
after(:each) do
$stdout = STDOUT
end
it 'deletes pictures with source OpenFarm' do
create(:photo, source: 'OpenFarm')
create(:photo, source: 'flickr')
expect(Photo.where(source: 'OpenFarm').count).to eq(1)
expect(Photo.where(source: 'flickr').count).to eq(1)
Rake::Task['openfarm:delete_pictures'].invoke
expect(Photo.where(source: 'OpenFarm').count).to eq(0)
expect(Photo.where(source: 'flickr').count).to eq(1)
end
end