Compare commits

..

No commits in common. "233cea022abe176a5d4088f04af3766aa11cac39" and "28baaa3c3d5a906fd350603307b05946d3f1f750" have entirely different histories.

24 changed files with 311 additions and 425 deletions

View File

@ -7,4 +7,3 @@ public/packs
node_modules/ node_modules/
.yarn .yarn
.pnp.* .pnp.*
.claude

1
.gitignore vendored
View File

@ -37,4 +37,3 @@ yarn-debug.log*
.yarn-integrity .yarn-integrity
.yarn .yarn
.pnp.* .pnp.*
.claude

View File

@ -1 +1 @@
4.0.2 3.3.5

View File

@ -1,73 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Parsley is a personal recipe manager and meal planning app. It is a monolithic Rails 7.2 application with a Vue 3 SPA frontend.
**Backend:** Ruby 4.0.2, Rails 8.1.3, SQLite (dev/test), PostgreSQL (production)
**Frontend:** Vue 3 (Composition API / `<script setup>`), Pinia, Vue Router 4, Bulma CSS
**Asset bundling:** Shakapacker 8 (Webpack 5 + SWC)
**Testing:** RSpec, FactoryBot, Guard
## Common Commands
```bash
# Development
foreman start # Start Rails + Shakapacker dev server together
# Dependencies
bundle install && yarn install
# Database
bundle exec rake db:create db:migrate db:seed
bundle exec rake db:reset # Drop, recreate, seed
# Tests
bundle exec rspec # All tests
bundle exec rspec spec/models/recipe_spec.rb # Single file
bundle exec rspec spec/models/recipe_spec.rb:42 # Single test by line
bundle exec guard # Watch mode
# Build
bundle exec rake shakapacker:compile # Compile assets for production
```
**Test environment note:** Set `FAST=true` to skip FactoryBot linting on startup.
## Architecture
### Backend
**Models follow an `Ingredient` abstraction:** `Recipe` and `Food` both inherit from an abstract `Ingredient` base class. This allows recipes to contain both foods and other recipes as ingredients (`RecipeIngredient` is a polymorphic join). Polymorphic ingredient IDs are encoded as strings: `F{id}` for foods, `R{id}` for recipes.
**Custom unit conversion library** (`lib/unit_conversion/`): The `UnitConversion` module handles all quantity parsing and conversion. It is density-aware (can convert volume ↔ mass if food density is known) and supports custom per-food units defined in `FoodUnit` records. Key entry points: `UnitConversion.parse`, `UnitConversion.auto_unit`, `UnitConversion.with_custom_units`.
**Recipe scaling:** Recipes can be scaled by a factor and converted between unit systems (standard/metric) and unit types (mass/volume). Cache keys incorporate scale and unit system: `recipes/{id}/{scale}/{system}/{unit}`. Memcache is optional; enable with `RAILS_USE_MEMCACHE=true`.
**USDA nutrition data:** The `UsdaImporter` (lib/) imports from `vendor/data/usda/` during `db:seed`. `UsdaFood`/`UsdaFoodWeight` tables are a read-only cache; `Food` records link to them and inherit nutritional data.
**Serializers** (`app/serializers/`) handle all JSON API responses.
### Frontend
Vue 3 components live in `app/javascript/components/`. They use `<script setup>` Composition API style. Pinia stores (`app/javascript/stores/`) hold global state. Vue Router (`app/javascript/router/`) manages navigation.
Page-level components are named `The*` (e.g., `TheRecipeList`, `TheFood`, `TheCalculator`). Shared UI primitives are named `App*` (e.g., `AppModal`, `AppAutocomplete`, `AppIcon`).
### Key Environment Variables
| Variable | Purpose |
|----------|---------|
| `RAILS_USE_MEMCACHE` | Enable Memcache caching |
| `RAILS_MEMCACHE_HOST` | Memcache host (default: `memcache`) |
| `PARSLEY_DB_HOST/USER/PG_PASSWORD/DB_NAME` | Production PostgreSQL config |
### Docker
`docker-compose up` starts PostgreSQL, Memcached, Nginx, and two Rails service instances. See `docker-compose.yml` and `Dockerfile`.
### Seed Data
Default dev user: username `dan`, password `qwerty`. Comes with sample recipes and foods.

View File

@ -1,9 +1,8 @@
FROM ruby:4.0.2-trixie FROM ruby:3.3.5-bookworm
RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash - && \ RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash - && \
apt-get update && apt-get dist-upgrade -y && \ apt-get update && apt-get dist-upgrade -y && \
apt-get install -y \ apt-get install -y \
pkg-config \
nodejs \ nodejs \
nginx && \ nginx && \
apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/*

View File

@ -1,6 +1,6 @@
source 'https://rubygems.org' source 'https://rubygems.org'
gem 'rails', '8.1.3' gem 'rails', '7.2.1'
gem 'pg', '~> 1.5.8' gem 'pg', '~> 1.5.8'
gem 'shakapacker', '8.0.2' gem 'shakapacker', '8.0.2'

View File

@ -1,112 +1,106 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
action_text-trix (2.1.18) actioncable (7.2.1)
railties actionpack (= 7.2.1)
actioncable (8.1.3) activesupport (= 7.2.1)
actionpack (= 8.1.3)
activesupport (= 8.1.3)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
actionmailbox (8.1.3) actionmailbox (7.2.1)
actionpack (= 8.1.3) actionpack (= 7.2.1)
activejob (= 8.1.3) activejob (= 7.2.1)
activerecord (= 8.1.3) activerecord (= 7.2.1)
activestorage (= 8.1.3) activestorage (= 7.2.1)
activesupport (= 8.1.3) activesupport (= 7.2.1)
mail (>= 2.8.0) mail (>= 2.8.0)
actionmailer (8.1.3) actionmailer (7.2.1)
actionpack (= 8.1.3) actionpack (= 7.2.1)
actionview (= 8.1.3) actionview (= 7.2.1)
activejob (= 8.1.3) activejob (= 7.2.1)
activesupport (= 8.1.3) activesupport (= 7.2.1)
mail (>= 2.8.0) mail (>= 2.8.0)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
actionpack (8.1.3) actionpack (7.2.1)
actionview (= 8.1.3) actionview (= 7.2.1)
activesupport (= 8.1.3) activesupport (= 7.2.1)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
rack (>= 2.2.4) racc
rack (>= 2.2.4, < 3.2)
rack-session (>= 1.0.1) rack-session (>= 1.0.1)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
useragent (~> 0.16) useragent (~> 0.16)
actiontext (8.1.3) actiontext (7.2.1)
action_text-trix (~> 2.1.15) actionpack (= 7.2.1)
actionpack (= 8.1.3) activerecord (= 7.2.1)
activerecord (= 8.1.3) activestorage (= 7.2.1)
activestorage (= 8.1.3) activesupport (= 7.2.1)
activesupport (= 8.1.3)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (8.1.3) actionview (7.2.1)
activesupport (= 8.1.3) activesupport (= 7.2.1)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.11) erubi (~> 1.11)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
activejob (8.1.3) activejob (7.2.1)
activesupport (= 8.1.3) activesupport (= 7.2.1)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (8.1.3) activemodel (7.2.1)
activesupport (= 8.1.3) activesupport (= 7.2.1)
activerecord (8.1.3) activerecord (7.2.1)
activemodel (= 8.1.3) activemodel (= 7.2.1)
activesupport (= 8.1.3) activesupport (= 7.2.1)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activestorage (8.1.3) activestorage (7.2.1)
actionpack (= 8.1.3) actionpack (= 7.2.1)
activejob (= 8.1.3) activejob (= 7.2.1)
activerecord (= 8.1.3) activerecord (= 7.2.1)
activesupport (= 8.1.3) activesupport (= 7.2.1)
marcel (~> 1.0) marcel (~> 1.0)
activesupport (8.1.3) activesupport (7.2.1)
base64 base64
bigdecimal bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1) concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5) connection_pool (>= 2.2.5)
drb drb
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
json
logger (>= 1.4.2) logger (>= 1.4.2)
minitest (>= 5.1) minitest (>= 5.1)
securerandom (>= 0.3) securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5) tzinfo (~> 2.0, >= 2.0.5)
uri (>= 0.13.1) base64 (0.2.0)
base64 (0.3.0) bcrypt (3.1.20)
bcrypt (3.1.22) bigdecimal (3.1.8)
bigdecimal (4.1.2) bootsnap (1.18.4)
bootsnap (1.23.0)
msgpack (~> 1.2) msgpack (~> 1.2)
builder (3.3.0) builder (3.3.0)
coderay (1.1.3) coderay (1.1.3)
concurrent-ruby (1.3.6) concurrent-ruby (1.3.4)
connection_pool (3.0.2) connection_pool (2.4.1)
crass (1.0.6) crass (1.0.6)
csv (3.3.5) csv (3.3.0)
dalli (3.2.8) dalli (3.2.8)
database_cleaner (2.0.2) database_cleaner (2.0.2)
database_cleaner-active_record (>= 2, < 3) database_cleaner-active_record (>= 2, < 3)
database_cleaner-active_record (2.2.2) database_cleaner-active_record (2.2.0)
activerecord (>= 5.a) activerecord (>= 5.a)
database_cleaner-core (~> 2.0) database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1) database_cleaner-core (2.0.1)
date (3.5.1) date (3.3.4)
diff-lcs (1.6.2) diff-lcs (1.5.1)
drb (2.2.3) drb (2.2.1)
erb (6.0.3) erubi (1.13.0)
erubi (1.13.1) factory_bot (6.5.0)
factory_bot (6.5.6) activesupport (>= 5.0.0)
activesupport (>= 6.1.0) factory_bot_rails (6.4.3)
factory_bot_rails (6.4.4) factory_bot (~> 6.4)
factory_bot (~> 6.5)
railties (>= 5.0.0) railties (>= 5.0.0)
ffi (1.17.4) ffi (1.17.0)
formatador (1.2.3) formatador (1.1.0)
reline globalid (1.2.1)
globalid (1.3.0)
activesupport (>= 6.1) activesupport (>= 6.1)
guard (2.18.1) guard (2.18.1)
formatador (>= 0.2.4) formatador (>= 0.2.4)
@ -122,15 +116,12 @@ GEM
guard (~> 2.1) guard (~> 2.1)
guard-compat (~> 1.1) guard-compat (~> 1.1)
rspec (>= 2.99.0, < 4.0) rspec (>= 2.99.0, < 4.0)
i18n (1.14.8) i18n (1.14.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
io-console (0.8.2) io-console (0.7.2)
irb (1.17.0) irb (1.14.1)
pp (>= 0.6.0)
prism (>= 1.3.0)
rdoc (>= 4.0.0) rdoc (>= 4.0.0)
reline (>= 0.4.2) reline (>= 0.4.2)
json (2.19.4)
kaminari (1.2.2) kaminari (1.2.2)
activesupport (>= 4.1.0) activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.2) kaminari-actionview (= 1.2.2)
@ -144,136 +135,124 @@ GEM
kaminari-core (= 1.2.2) kaminari-core (= 1.2.2)
kaminari-core (1.2.2) kaminari-core (1.2.2)
liner (0.2.4) liner (0.2.4)
listen (3.10.0) listen (3.9.0)
logger
rb-fsevent (~> 0.10, >= 0.10.3) rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10) rb-inotify (~> 0.9, >= 0.9.10)
logger (1.7.0) logger (1.6.1)
loofah (2.25.1) loofah (2.22.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.12.0) nokogiri (>= 1.12.0)
lumberjack (1.4.2) lumberjack (1.2.10)
mail (2.9.0) mail (2.8.1)
logger
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
marcel (1.1.0) marcel (1.0.4)
memoizable (0.5.1) memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1)
method_source (1.1.0) method_source (1.1.0)
mini_mime (1.1.5) mini_mime (1.1.5)
mini_portile2 (2.8.9) mini_portile2 (2.8.7)
minitest (6.0.4) minitest (5.25.1)
drb (~> 2.0) msgpack (1.7.2)
prism (~> 1.5)
msgpack (1.8.0)
nenv (0.3.0) nenv (0.3.0)
net-imap (0.6.3) net-imap (0.4.16)
date date
net-protocol net-protocol
net-pop (0.1.2) net-pop (0.1.2)
net-protocol net-protocol
net-protocol (0.2.2) net-protocol (0.2.2)
timeout timeout
net-smtp (0.5.1) net-smtp (0.5.0)
net-protocol net-protocol
nio4r (2.7.5) nio4r (2.7.3)
nokogiri (1.19.2) nokogiri (1.16.7)
mini_portile2 (~> 2.8.2) mini_portile2 (~> 2.8.2)
racc (~> 1.4) racc (~> 1.4)
notiffany (0.1.3) notiffany (0.1.3)
nenv (~> 0.1) nenv (~> 0.1)
shellany (~> 0.0) shellany (~> 0.0)
oj (3.16.17) oj (3.16.6)
bigdecimal (>= 3.0) bigdecimal (>= 3.0)
ostruct (>= 0.2) ostruct (>= 0.2)
ostruct (0.6.3) ostruct (0.6.0)
package_json (0.2.0) package_json (0.1.0)
parslet (2.0.0) parslet (2.0.0)
pg (1.5.9) pg (1.5.8)
pp (0.6.3) pry (0.14.2)
prettyprint
prettyprint (0.2.0)
prism (1.9.0)
pry (0.16.0)
coderay (~> 1.1) coderay (~> 1.1)
method_source (~> 1.0) method_source (~> 1.0)
reline (>= 0.6.0) psych (5.1.2)
psych (5.3.1)
date
stringio stringio
puma (6.6.1) puma (6.4.3)
nio4r (~> 2.0) nio4r (~> 2.0)
racc (1.8.1) racc (1.8.1)
rack (3.2.6) rack (3.1.7)
rack-proxy (0.7.7) rack-proxy (0.7.7)
rack rack
rack-session (2.1.2) rack-session (2.0.0)
base64 (>= 0.1.0)
rack (>= 3.0.0) rack (>= 3.0.0)
rack-test (2.2.0) rack-test (2.1.0)
rack (>= 1.3) rack (>= 1.3)
rackup (2.3.1) rackup (2.1.0)
rack (>= 3) rack (>= 3)
rails (8.1.3) webrick (~> 1.8)
actioncable (= 8.1.3) rails (7.2.1)
actionmailbox (= 8.1.3) actioncable (= 7.2.1)
actionmailer (= 8.1.3) actionmailbox (= 7.2.1)
actionpack (= 8.1.3) actionmailer (= 7.2.1)
actiontext (= 8.1.3) actionpack (= 7.2.1)
actionview (= 8.1.3) actiontext (= 7.2.1)
activejob (= 8.1.3) actionview (= 7.2.1)
activemodel (= 8.1.3) activejob (= 7.2.1)
activerecord (= 8.1.3) activemodel (= 7.2.1)
activestorage (= 8.1.3) activerecord (= 7.2.1)
activesupport (= 8.1.3) activestorage (= 7.2.1)
activesupport (= 7.2.1)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 8.1.3) railties (= 7.2.1)
rails-controller-testing (1.0.5) rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1) actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1) activesupport (>= 5.0.1.rc1)
rails-dom-testing (2.3.0) rails-dom-testing (2.2.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
minitest minitest
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.7.0) rails-html-sanitizer (1.6.0)
loofah (~> 2.25) loofah (~> 2.21)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) nokogiri (~> 1.14)
railties (8.1.3) railties (7.2.1)
actionpack (= 8.1.3) actionpack (= 7.2.1)
activesupport (= 8.1.3) activesupport (= 7.2.1)
irb (~> 1.13) irb (~> 1.13)
rackup (>= 1.0.0) rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0, >= 1.2.2) thor (~> 1.0, >= 1.2.2)
tsort (>= 0.2)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
rake (13.4.2) rake (13.2.1)
rb-fsevent (0.11.2) rb-fsevent (0.11.2)
rb-inotify (0.11.1) rb-inotify (0.11.1)
ffi (~> 1.0) ffi (~> 1.0)
rdoc (7.2.0) rdoc (6.7.0)
erb
psych (>= 4.0.0) psych (>= 4.0.0)
tsort redcarpet (3.6.0)
redcarpet (3.6.1) reline (0.5.10)
reline (0.6.3)
io-console (~> 0.5) io-console (~> 0.5)
rspec (3.13.2) rspec (3.13.0)
rspec-core (~> 3.13.0) rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0) rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0) rspec-mocks (~> 3.13.0)
rspec-core (3.13.6) rspec-core (3.13.1)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-expectations (3.13.5) rspec-expectations (3.13.3)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-mocks (3.13.8) rspec-mocks (3.13.1)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-rails (7.0.2) rspec-rails (7.0.1)
actionpack (>= 7.0) actionpack (>= 7.0)
activesupport (>= 7.0) activesupport (>= 7.0)
railties (>= 7.0) railties (>= 7.0)
@ -281,9 +260,9 @@ GEM
rspec-expectations (~> 3.13) rspec-expectations (~> 3.13)
rspec-mocks (~> 3.13) rspec-mocks (~> 3.13)
rspec-support (~> 3.13) rspec-support (~> 3.13)
rspec-support (3.13.7) rspec-support (3.13.1)
securerandom (0.4.1) securerandom (0.3.1)
semantic_range (3.1.1) semantic_range (3.0.0)
shakapacker (8.0.2) shakapacker (8.0.2)
activesupport (>= 5.2) activesupport (>= 5.2)
package_json package_json
@ -292,28 +271,27 @@ GEM
semantic_range (>= 2.3.0) semantic_range (>= 2.3.0)
shellany (0.0.1) shellany (0.0.1)
signed_multiset (0.2.1) signed_multiset (0.2.1)
sqlite3 (2.1.1) sqlite3 (2.1.0)
mini_portile2 (~> 2.8.0) mini_portile2 (~> 2.8.0)
stringio (3.2.0) stringio (3.1.1)
thor (1.5.0) thor (1.3.2)
timeout (0.6.1) thread_safe (0.3.6)
tsort (0.2.0) timeout (0.4.1)
tzinfo (2.0.6) tzinfo (2.0.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
tzinfo-data (1.2026.1) tzinfo-data (1.2024.2)
tzinfo (>= 1.0.0) tzinfo (>= 1.0.0)
unitwise (2.3.0) unitwise (2.3.0)
liner (~> 0.2) liner (~> 0.2)
memoizable (~> 0.4) memoizable (~> 0.4)
parslet (~> 2.0) parslet (~> 2.0)
signed_multiset (~> 0.2) signed_multiset (~> 0.2)
uri (1.1.1) useragent (0.16.10)
useragent (0.16.11) webrick (1.8.2)
websocket-driver (0.8.0) websocket-driver (0.7.6)
base64
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
zeitwerk (2.7.5) zeitwerk (2.6.18)
PLATFORMS PLATFORMS
ruby ruby
@ -331,7 +309,7 @@ DEPENDENCIES
oj (~> 3.16.6) oj (~> 3.16.6)
pg (~> 1.5.8) pg (~> 1.5.8)
puma (~> 6.4) puma (~> 6.4)
rails (= 8.1.3) rails (= 7.2.1)
rails-controller-testing rails-controller-testing
redcarpet (~> 3.6.0) redcarpet (~> 3.6.0)
rspec-rails (~> 7.0.1) rspec-rails (~> 7.0.1)
@ -341,4 +319,4 @@ DEPENDENCIES
unitwise (~> 2.3.0) unitwise (~> 2.3.0)
BUNDLED WITH BUNDLED WITH
4.0.10 2.5.20

View File

@ -48,7 +48,7 @@ class LogsController < ApplicationController
@log.source_recipe = @recipe @log.source_recipe = @recipe
if @log.save if @log.save
render json: { id: @log.id } render json: { success: true }
else else
render json: @log.errors, status: :unprocessable_entity render json: @log.errors, status: :unprocessable_entity
end end

View File

@ -48,7 +48,7 @@ class RecipesController < ApplicationController
@recipe.user = current_user @recipe.user = current_user
if @recipe.save if @recipe.save
render json: { id: @recipe.id } render json: { success: true }
else else
render json: @recipe.errors, status: :unprocessable_entity render json: @recipe.errors, status: :unprocessable_entity
end end

View File

@ -42,7 +42,7 @@ class TaskItemsController < ApplicationController
new_status = !params[:invert].present? new_status = !params[:invert].present?
TaskItem.transaction do TaskItem.transaction do
@task_items = @task_list.task_items.find(ids) @task_items = @task_list.task_items.find(ids)
@task_items.each { |i| i.update_column(:completed, new_status) } @task_items.each { |i| i.update_attribute(:completed, new_status) }
end end
TaskChannel.update_task_list(@task_list) TaskChannel.update_task_list(@task_list)

View File

@ -8,27 +8,32 @@
<script setup> <script setup>
import { ref, watch } from "vue"; import { ref } from "vue";
import debounce from "lodash/debounce"; import debounce from "lodash/debounce";
const emit = defineEmits(["update:modelValue"]);
const props = defineProps({ const props = defineProps({
placeholder: { placeholder: {
required: false, required: false,
type: String, type: String,
default: "" default: ""
},
modelValue: {
required: false,
type: String,
default: ""
} }
}); });
const model = defineModel({ type: String, default: null }); const text = ref(null);
const text = ref(model.value);
watch(model, (val) => { text.value = val; });
const triggerInput = debounce(function() { const triggerInput = debounce(function() {
model.value = text.value; emit("update:modelValue", text.value);
}, },
250, 250,
{ leading: false, trailing: true }); { leading: false, trailing: true })
function userUpdateText(newText) { function userUpdateText(newText) {
if (text.value !== newText) { if (text.value !== newText) {
@ -37,4 +42,10 @@ function userUpdateText(newText) {
} }
} }
function propUpdateText(newText) {
if (text.value === null && text.value !== newText) {
text.value = newText;
}
}
</script> </script>

View File

@ -87,10 +87,10 @@
return api.getSearchIngredients(text); return api.getSearchIngredients(text);
} }
function searchItemSelected(selectedIngredient) { function searchItemSelected(ingredient) {
ingredient.value = selectedIngredient || null; ingredient.value = ingredient || null;
ingredient_name.value = selectedIngredient.name || null; ingredient_name.value = ingredient.name || null;
density.value = selectedIngredient.density || null; density.value = ingredient.density || null;
} }
function getErrors(type) { function getErrors(type) {

View File

@ -4,7 +4,7 @@
<food-edit :food="food" :validation-errors="validationErrors" action="Creating"></food-edit> <food-edit :food="food" :validation-errors="validationErrors" action="Creating"></food-edit>
<button type="button" class="button is-primary" @click="save">Save</button> <button type="button" class="button is-primary" @click="save">Save</button>
<router-link class="button is-secondary" to="/foods">Cancel</router-link> <router-link class="button is-secondary" to="/food">Cancel</router-link>
</div> </div>
</template> </template>

View File

@ -8,6 +8,11 @@
</div> </div>
</log-edit> </log-edit>
<div class="buttons">
<button type="button" class="button is-primary" @click="save">Save Log</button>
<router-link class="button is-secondary" to="/">Cancel</router-link>
</div>
</div> </div>
</template> </template>
@ -46,7 +51,7 @@
loadResource( loadResource(
api.postLog(log) api.postLog(log)
.then(data => router.push({ name: 'log', params: { id: data.id } })) .then(() => router.push('/'))
.catch(Errors.onlyFor(Errors.ApiValidationError, err => validationErrors.value = err.validationErrors())) .catch(Errors.onlyFor(Errors.ApiValidationError, err => validationErrors.value = err.validationErrors()))
); );
} }

View File

@ -8,6 +8,11 @@
</div> </div>
</log-edit> </log-edit>
<div class="buttons">
<button type="button" class="button is-primary" @click="save">Save Log</button>
<router-link class="button is-secondary" to="/">Cancel</router-link>
</div>
</div> </div>
</template> </template>
@ -40,7 +45,7 @@
validationErrors.value = {}; validationErrors.value = {};
loadResource( loadResource(
api.patchLog(log.value) api.patchLog(log.value)
.then(() => router.push({ name: 'log', params: { id: log.value.id } })) .then(() => router.push('/'))
.catch(Errors.onlyFor(Errors.ApiValidationError, err => validationErrors.value = err.validationErrors())) .catch(Errors.onlyFor(Errors.ApiValidationError, err => validationErrors.value = err.validationErrors()))
); );
} }

View File

@ -42,7 +42,7 @@
validationErrors.value = {}; validationErrors.value = {};
loadResource( loadResource(
api.postRecipe(recipe.value) api.postRecipe(recipe.value)
.then(data => router.push({ name: 'recipe', params: { id: data.id } })) .then(() => router.push('/'))
.catch(Errors.onlyFor(Errors.ApiValidationError, err => validationErrors.value = err.validationErrors())) .catch(Errors.onlyFor(Errors.ApiValidationError, err => validationErrors.value = err.validationErrors()))
); );
} }

View File

@ -25,10 +25,10 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<app-search-text placeholder="search names" :model-value="search.name" @update:modelValue="setSearchName($event)"></app-search-text> <app-search-text placeholder="search names" :value="search.name" @update:modelValue="setSearchName($event)"></app-search-text>
</td> </td>
<td> <td>
<app-search-text placeholder="search tags" :model-value="search.tags" @update:modelValue="setSearchTags($event)"></app-search-text> <app-search-text placeholder="search tags" :value="search.tags" @update:modelValue="setSearchTags($event)"></app-search-text>
</td> </td>
<td colspan="5"></td> <td colspan="5"></td>
</tr> </tr>

View File

@ -9,7 +9,7 @@ class User < ApplicationRecord
validates :username, presence: true, uniqueness: { case_sensitive: false } validates :username, presence: true, uniqueness: { case_sensitive: false }
def self.authenticate(username, password) def self.authenticate(username, password)
find_by(username: username)&.authenticate(password) find_by_username(username).try(:authenticate, password)
end end
def display_name def display_name

View File

@ -24,7 +24,7 @@ require_relative '../lib/unit_conversion'
module Parsley module Parsley
class Application < Rails::Application class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version. # Initialize configuration defaults for originally generated Rails version.
config.load_defaults 8.0 config.load_defaults 7.2
config.autoload_lib(ignore: %w(assets tasks unit_conversion unit_conversion.rb)) config.autoload_lib(ignore: %w(assets tasks unit_conversion unit_conversion.rb))

View File

@ -1,8 +1,10 @@
Rails.application.configure do Rails.application.configure do
# Verifies that versions and hashed value of the package contents in the project's package.json
config.webpacker.check_yarn_integrity = false
# Settings specified here will take precedence over those in config/application.rb. # Settings specified here will take precedence over those in config/application.rb.
# Code is not reloaded between requests. # Code is not reloaded between requests.
config.enable_reloading = false config.cache_classes = true
# Eager load code on boot. This eager loads most of Rails and # Eager load code on boot. This eager loads most of Rails and
# your application in memory, allowing both threaded web servers # your application in memory, allowing both threaded web servers
@ -66,8 +68,8 @@ Rails.application.configure do
# the I18n.default_locale when a translation cannot be found). # the I18n.default_locale when a translation cannot be found).
config.i18n.fallbacks = true config.i18n.fallbacks = true
# Don't log any deprecations. # Send deprecation notices to registered listeners.
config.active_support.report_deprecations = false config.active_support.deprecation = :notify
# Use default logging formatter so that PID and timestamp are not suppressed. # Use default logging formatter so that PID and timestamp are not suppressed.
config.log_formatter = ::Logger::Formatter.new config.log_formatter = ::Logger::Formatter.new

View File

@ -2,7 +2,7 @@ Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb. # Settings specified here will take precedence over those in config/application.rb.
# Code is not reloaded between requests. # Code is not reloaded between requests.
config.enable_reloading = false config.cache_classes = true
# Eager load code on boot. This eager loads most of Rails and # Eager load code on boot. This eager loads most of Rails and
# your application in memory, allowing both threaded web servers # your application in memory, allowing both threaded web servers
@ -56,8 +56,8 @@ Rails.application.configure do
# the I18n.default_locale when a translation cannot be found). # the I18n.default_locale when a translation cannot be found).
config.i18n.fallbacks = true config.i18n.fallbacks = true
# Don't log any deprecations. # Send deprecation notices to registered listeners.
config.active_support.report_deprecations = false config.active_support.deprecation = :notify
# Use default logging formatter so that PID and timestamp are not suppressed. # Use default logging formatter so that PID and timestamp are not suppressed.
config.log_formatter = ::Logger::Formatter.new config.log_formatter = ::Logger::Formatter.new

View File

@ -10,107 +10,107 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.1].define(version: 2018_09_15_134841) do ActiveRecord::Schema[7.2].define(version: 2018_09_15_134841) do
create_table "food_units", force: :cascade do |t| create_table "food_units", force: :cascade do |t|
t.integer "food_id", null: false t.integer "food_id", null: false
t.decimal "gram_weight", precision: 10, scale: 2, null: false
t.string "name", null: false t.string "name", null: false
t.decimal "gram_weight", precision: 10, scale: 2, null: false
t.index ["food_id"], name: "index_food_units_on_food_id" t.index ["food_id"], name: "index_food_units_on_food_id"
end end
create_table "foods", force: :cascade do |t| create_table "foods", force: :cascade do |t|
t.decimal "ash", precision: 10, scale: 2
t.integer "calcium"
t.decimal "carbohydrates", precision: 10, scale: 2
t.decimal "cholesterol", precision: 10, scale: 3
t.decimal "copper", precision: 10, scale: 3
t.datetime "created_at", precision: nil, null: false
t.string "density"
t.decimal "fiber", precision: 10, scale: 1
t.decimal "iron", precision: 10, scale: 2
t.integer "kcal"
t.decimal "lipids", precision: 10, scale: 2
t.integer "magnesium"
t.decimal "manganese", precision: 10, scale: 3
t.string "name" t.string "name"
t.string "ndbn", limit: 25 t.string "density"
t.text "notes" t.text "notes"
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.string "ndbn", limit: 25
t.decimal "water", precision: 10, scale: 2
t.decimal "protein", precision: 10, scale: 2
t.decimal "lipids", precision: 10, scale: 2
t.decimal "ash", precision: 10, scale: 2
t.decimal "carbohydrates", precision: 10, scale: 2
t.integer "kcal"
t.decimal "fiber", precision: 10, scale: 1
t.decimal "sugar", precision: 10, scale: 2
t.integer "user_id"
t.integer "calcium"
t.decimal "iron", precision: 10, scale: 2
t.integer "magnesium"
t.integer "phosphorus" t.integer "phosphorus"
t.integer "potassium" t.integer "potassium"
t.decimal "protein", precision: 10, scale: 2
t.integer "sodium" t.integer "sodium"
t.decimal "sugar", precision: 10, scale: 2
t.datetime "updated_at", precision: nil, null: false
t.integer "user_id"
t.integer "vit_a"
t.decimal "vit_b12", precision: 10, scale: 2
t.decimal "vit_b6", precision: 10, scale: 3
t.decimal "vit_c", precision: 10, scale: 1
t.decimal "vit_d", precision: 10, scale: 1
t.decimal "vit_e", precision: 10, scale: 2
t.decimal "vit_k", precision: 10, scale: 1
t.decimal "water", precision: 10, scale: 2
t.decimal "zinc", precision: 10, scale: 2 t.decimal "zinc", precision: 10, scale: 2
t.decimal "copper", precision: 10, scale: 3
t.decimal "manganese", precision: 10, scale: 3
t.decimal "vit_c", precision: 10, scale: 1
t.decimal "vit_b6", precision: 10, scale: 3
t.decimal "vit_b12", precision: 10, scale: 2
t.integer "vit_a"
t.decimal "vit_e", precision: 10, scale: 2
t.decimal "vit_d", precision: 10, scale: 1
t.decimal "vit_k", precision: 10, scale: 1
t.decimal "cholesterol", precision: 10, scale: 3
t.index ["ndbn"], name: "index_foods_on_ndbn" t.index ["ndbn"], name: "index_foods_on_ndbn"
end end
create_table "logs", force: :cascade do |t| create_table "logs", force: :cascade do |t|
t.datetime "created_at", precision: nil, null: false t.integer "user_id"
t.datetime "date", precision: nil
t.text "notes"
t.integer "rating"
t.integer "recipe_id" t.integer "recipe_id"
t.integer "source_recipe_id" t.integer "source_recipe_id"
t.datetime "date", precision: nil
t.integer "rating"
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false
t.integer "user_id" t.text "notes"
end end
create_table "notes", force: :cascade do |t| create_table "notes", force: :cascade do |t|
t.integer "user_id", null: false
t.text "content", null: false t.text "content", null: false
t.datetime "created_at", precision: nil, null: false t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false
t.integer "user_id", null: false
t.index ["user_id"], name: "index_notes_on_user_id" t.index ["user_id"], name: "index_notes_on_user_id"
end end
create_table "recipe_ingredients", force: :cascade do |t| create_table "recipe_ingredients", force: :cascade do |t|
t.datetime "created_at", precision: nil, null: false
t.integer "food_id" t.integer "food_id"
t.string "name"
t.text "preparation"
t.string "quantity"
t.integer "recipe_as_ingredient_id"
t.integer "recipe_id" t.integer "recipe_id"
t.string "name"
t.integer "sort_order" t.integer "sort_order"
t.string "quantity"
t.string "units" t.string "units"
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false
t.text "preparation"
t.integer "recipe_as_ingredient_id"
t.index ["recipe_id"], name: "index_recipe_ingredients_on_recipe_id" t.index ["recipe_id"], name: "index_recipe_ingredients_on_recipe_id"
end end
create_table "recipe_steps", force: :cascade do |t| create_table "recipe_steps", force: :cascade do |t|
t.datetime "created_at", precision: nil, null: false
t.integer "recipe_id" t.integer "recipe_id"
t.integer "sort_order" t.integer "sort_order"
t.text "step" t.text "step"
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false
t.index ["recipe_id"], name: "index_recipe_steps_on_recipe_id" t.index ["recipe_id"], name: "index_recipe_steps_on_recipe_id"
end end
create_table "recipes", force: :cascade do |t| create_table "recipes", force: :cascade do |t|
t.string "name"
t.text "description"
t.text "source"
t.string "yields"
t.integer "total_time"
t.integer "active_time" t.integer "active_time"
t.datetime "created_at", precision: nil, null: false t.datetime "created_at", precision: nil, null: false
t.boolean "deleted"
t.text "description"
t.boolean "is_ingredient"
t.boolean "is_log"
t.string "name"
t.float "rating"
t.text "source"
t.text "step_text"
t.integer "total_time"
t.datetime "updated_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false
t.boolean "deleted"
t.integer "user_id" t.integer "user_id"
t.string "yields" t.boolean "is_log"
t.float "rating"
t.text "step_text"
t.boolean "is_ingredient"
end end
create_table "recipes_tags", id: false, force: :cascade do |t| create_table "recipes_tags", id: false, force: :cascade do |t|
@ -121,94 +121,94 @@ ActiveRecord::Schema[8.1].define(version: 2018_09_15_134841) do
end end
create_table "tags", force: :cascade do |t| create_table "tags", force: :cascade do |t|
t.datetime "created_at", precision: nil, null: false
t.string "lowercase_name"
t.string "name" t.string "name"
t.string "lowercase_name"
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false
t.index ["lowercase_name"], name: "index_tags_on_lowercase_name", unique: true t.index ["lowercase_name"], name: "index_tags_on_lowercase_name", unique: true
end end
create_table "task_items", force: :cascade do |t| create_table "task_items", force: :cascade do |t|
t.boolean "completed" t.integer "task_list_id", null: false
t.datetime "created_at", precision: nil, null: false
t.string "name" t.string "name"
t.string "quantity" t.string "quantity"
t.integer "task_list_id", null: false t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false
t.boolean "completed"
t.index ["task_list_id"], name: "index_task_items_on_task_list_id" t.index ["task_list_id"], name: "index_task_items_on_task_list_id"
end end
create_table "task_lists", force: :cascade do |t| create_table "task_lists", force: :cascade do |t|
t.datetime "created_at", precision: nil, null: false
t.string "name"
t.datetime "updated_at", precision: nil, null: false
t.integer "user_id", null: false t.integer "user_id", null: false
t.string "name"
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.index ["user_id"], name: "index_task_lists_on_user_id" t.index ["user_id"], name: "index_task_lists_on_user_id"
end end
create_table "usda_food_weights", force: :cascade do |t| create_table "usda_food_weights", force: :cascade do |t|
t.integer "usda_food_id", null: false
t.decimal "amount", precision: 7, scale: 3 t.decimal "amount", precision: 7, scale: 3
t.datetime "created_at", precision: nil, null: false
t.string "description" t.string "description"
t.decimal "gram_weight", precision: 7, scale: 1 t.decimal "gram_weight", precision: 7, scale: 1
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false
t.integer "usda_food_id", null: false
t.index ["usda_food_id"], name: "index_usda_food_weights_on_usda_food_id" t.index ["usda_food_id"], name: "index_usda_food_weights_on_usda_food_id"
end end
create_table "usda_foods", force: :cascade do |t| create_table "usda_foods", force: :cascade do |t|
t.string "ndbn", limit: 25
t.string "long_description"
t.string "short_description"
t.decimal "water", precision: 10, scale: 2
t.integer "kcal"
t.decimal "protein", precision: 10, scale: 2
t.decimal "lipid", precision: 10, scale: 2
t.decimal "ash", precision: 10, scale: 2 t.decimal "ash", precision: 10, scale: 2
t.integer "calcium"
t.decimal "carbohydrates", precision: 10, scale: 2 t.decimal "carbohydrates", precision: 10, scale: 2
t.decimal "cholesterol", precision: 10, scale: 3
t.decimal "copper", precision: 10, scale: 3
t.datetime "created_at", precision: nil, null: false
t.decimal "fiber", precision: 10, scale: 1 t.decimal "fiber", precision: 10, scale: 1
t.decimal "sugar", precision: 10, scale: 2
t.decimal "gram_weight_1", precision: 9, scale: 2 t.decimal "gram_weight_1", precision: 9, scale: 2
t.decimal "gram_weight_2", precision: 9, scale: 2 t.decimal "gram_weight_2", precision: 9, scale: 2
t.string "gram_weight_desc_1" t.string "gram_weight_desc_1"
t.string "gram_weight_desc_2" t.string "gram_weight_desc_2"
t.text "ingredients"
t.decimal "iron", precision: 10, scale: 2
t.integer "kcal"
t.decimal "lipid", precision: 10, scale: 2
t.string "long_description"
t.integer "magnesium"
t.decimal "manganese", precision: 10, scale: 3
t.string "manufacturer"
t.string "ndbn", limit: 25
t.string "nutrient_unit"
t.integer "phosphorus"
t.integer "potassium"
t.decimal "protein", precision: 10, scale: 2
t.string "refuse_description" t.string "refuse_description"
t.integer "refuse_percent" t.integer "refuse_percent"
t.string "scientific_name" t.string "scientific_name"
t.string "short_description" t.datetime "created_at", precision: nil, null: false
t.integer "sodium"
t.string "source"
t.decimal "sugar", precision: 10, scale: 2
t.datetime "updated_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false
t.integer "vit_a" t.integer "calcium"
t.decimal "vit_b12", precision: 10, scale: 2 t.decimal "iron", precision: 10, scale: 2
t.decimal "vit_b6", precision: 10, scale: 3 t.integer "magnesium"
t.decimal "vit_c", precision: 10, scale: 1 t.integer "phosphorus"
t.decimal "vit_d", precision: 10, scale: 1 t.integer "potassium"
t.decimal "vit_e", precision: 10, scale: 2 t.integer "sodium"
t.decimal "vit_k", precision: 10, scale: 1
t.decimal "water", precision: 10, scale: 2
t.decimal "zinc", precision: 10, scale: 2 t.decimal "zinc", precision: 10, scale: 2
t.decimal "copper", precision: 10, scale: 3
t.decimal "manganese", precision: 10, scale: 3
t.decimal "vit_c", precision: 10, scale: 1
t.decimal "vit_b6", precision: 10, scale: 3
t.decimal "vit_b12", precision: 10, scale: 2
t.integer "vit_a"
t.decimal "vit_e", precision: 10, scale: 2
t.decimal "vit_d", precision: 10, scale: 1
t.decimal "vit_k", precision: 10, scale: 1
t.decimal "cholesterol", precision: 10, scale: 3
t.string "source"
t.string "manufacturer"
t.text "ingredients"
t.string "nutrient_unit"
t.index ["long_description"], name: "index_usda_foods_on_long_description" t.index ["long_description"], name: "index_usda_foods_on_long_description"
t.index ["ndbn"], name: "index_usda_foods_on_ndbn" t.index ["ndbn"], name: "index_usda_foods_on_ndbn"
end end
create_table "users", force: :cascade do |t| create_table "users", force: :cascade do |t|
t.boolean "admin" t.string "username"
t.datetime "created_at", precision: nil, null: false
t.string "email" t.string "email"
t.string "full_name" t.string "full_name"
t.string "password_digest" t.string "password_digest"
t.boolean "admin"
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false
t.string "username"
end end
end end

View File

@ -378,62 +378,49 @@ class UsdaImporter
end end
build_enumerator(opened_files).each_slice(500) do |slice| build_enumerator(opened_files).each_slice(500) do |slice|
now = Time.current UsdaFood.transaction do
food_attrs = [] slice.each do |data|
weight_groups = []
slice.each do |data| food = UsdaFood.new
food = UsdaFood.new
weight_hashes = []
data.each do |name, rows| data.each do |name, rows|
file_info = FILES[name] file_info = FILES[name]
obj = food
rows.each do |row|
if file_info[:map_into]
obj = food.send(file_info[:map_into]).build
end
if file_info[:static]
file_info[:static].each do |k, v|
obj.send("#{k}=", v)
end
end
rows.each do |row|
if file_info[:map_into]
w = {}
file_info[:static]&.each { |k, v| w[k.to_s] = v }
file_info[:map].each { |db, col| w[db.to_s] = row[col] }
weight_hashes << w
else
file_info[:static]&.each { |k, v| food.send("#{k}=", v) }
if file_info[:map_function] if file_info[:map_function]
file_info[:map_function].call(food, row) file_info[:map_function].call(obj, row)
else else
file_info[:map].each { |db, col| food.send("#{db}=", row[col]) } file_info[:map].each do |db, col|
obj.send("#{db}=", row[col])
end
end end
end end
end end
end
attrs = food.attributes.except('id') food.save!
attrs['created_at'] = now
attrs['updated_at'] = now
food_attrs << attrs
weight_groups << weight_hashes
end
result = UsdaFood.insert_all!(food_attrs, returning: %w[id ndbn])
id_idx = result.columns.index('id')
ndbn_idx = result.columns.index('ndbn')
ndbn_to_id = result.rows.each_with_object({}) { |row, h| h[row[ndbn_idx]] = row[id_idx] }
all_weights = []
food_attrs.each_with_index do |fa, i|
food_id = ndbn_to_id[fa['ndbn']]
weight_groups[i].each do |w|
all_weights << w.merge('usda_food_id' => food_id, 'created_at' => now, 'updated_at' => now)
end end
end end
UsdaFoodWeight.insert_all!(all_weights) if all_weights.any?
end end
ensure ensure
opened_files.each { |k, v| v.close } opened_files.each { |k, v| v.close }
sorted_files.each { |k, v| `rm #{v}` } sorted_files.each { |k, v| `rm #{v}` }
end end
Food.where('ndbn != ?', '').where('ndbn IS NOT NULL').find_each do |i| Food.where('ndbn != ?', '').where('ndbn IS NOT NULL').each do |i|
i.set_usda_food(i.usda_food) i.set_usda_food(i.usda_food)
i.save! i.save!
end end
@ -457,7 +444,7 @@ class UsdaImporter
loop do loop do
break if enumerate_data.values.all? { |d| d[:done] } break if enumerate_data.values.all? { |d| d[:done] }
current_ndbn = enumerate_data.select { |_, d| !d[:done] }.values.min_by { |d| d[:next_ndbn].to_i }[:next_ndbn] current_ndbn = enumerate_data.select { |_, d| !d[:done] }.values.map { |d| d[:next_ndbn] }.min
results = Hash.new { |hash, key| hash[key] = [] } results = Hash.new { |hash, key| hash[key] = [] }
enumerate_data.each do |name, data| enumerate_data.each do |name, data|

View File

@ -3,45 +3,19 @@ require 'usda_importer'
RSpec.describe UsdaImporter do RSpec.describe UsdaImporter do
subject(:import) { UsdaImporter.new(Rails.root.join('spec', 'test_data')).import } it 'imports' do
i = UsdaImporter.new(Rails.root.join('spec', 'test_data'))
it 'imports the correct number of foods with weights' do i.import
import
expect(UsdaFood.count).to eq 5 expect(UsdaFood.count).to eq 5
butter = UsdaFood.where(ndbn: '01001').first
butter = UsdaFood.find_by(ndbn: '01001')
expect(butter).not_to be_nil expect(butter).not_to be_nil
expect(butter.usda_food_weights.count).to eq 4 expect(butter.usda_food_weights.count).to eq 4
clif_bar = UsdaFood.find_by(ndbn: '45042066') clif_bar = UsdaFood.where(ndbn: '45042066').first
expect(clif_bar).not_to be_nil expect(clif_bar).not_to be_nil
expect(clif_bar.usda_food_weights.count).to eq 1 expect(clif_bar.usda_food_weights.count).to eq 1
end
it 'imports SR28 nutrition fields correctly' do
import
butter = UsdaFood.find_by(ndbn: '01001')
expect(butter.kcal).to eq 717
expect(butter.protein).to eq 0.85
expect(butter.source).to eq 'sr'
end
it 'imports branded nutrition fields via map_function' do
import
clif_bar = UsdaFood.find_by(ndbn: '45042066')
expect(clif_bar.kcal).to eq 368 expect(clif_bar.kcal).to eq 368
expect(clif_bar.protein).to eq 13.24
expect(clif_bar.source).to eq 'bf'
end
it 'updates linked Food records with usda nutrition data' do
food = create(:food, ndbn: '01001')
import
food.reload
expect(food.kcal).to eq 717
end end
end end