{"id":23244788,"url":"https://github.com/frodsan/recipes","last_synced_at":"2026-05-09T09:52:07.941Z","repository":{"id":145875113,"uuid":"477416656","full_name":"frodsan/recipes","owner":"frodsan","description":"Ruby on Rails code exercise","archived":false,"fork":false,"pushed_at":"2022-05-10T09:15:11.000Z","size":7805,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-12T06:24:31.761Z","etag":null,"topics":["ruby","rubyonrails","stimulusjs","tailwindcss"],"latest_commit_sha":null,"homepage":"https://savepenny.herokuapp.com/","language":"Ruby","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/frodsan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-03T17:35:28.000Z","updated_at":"2022-04-04T05:24:07.000Z","dependencies_parsed_at":null,"dependency_job_id":"71e34ea0-0256-4ca0-8eb9-79b80f1736f6","html_url":"https://github.com/frodsan/recipes","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frodsan%2Frecipes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frodsan%2Frecipes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frodsan%2Frecipes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frodsan%2Frecipes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/frodsan","download_url":"https://codeload.github.com/frodsan/recipes/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247415969,"owners_count":20935387,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ruby","rubyonrails","stimulusjs","tailwindcss"],"created_at":"2024-12-19T07:11:13.368Z","updated_at":"2026-05-09T09:52:02.911Z","avatar_url":"https://github.com/frodsan.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# recipes\n\nSave a penny and find recipes with ingredients you already have at home 🥘.\n\n![Website](docs/savepenny.gif)\n\n## Links:\n\n- Website: \u003chttps://savepenny.herokuapp.com/\u003e\n\n## Prerequisites\n\n- Git\n- Ruby 3.1\n- Postgres 13\n- Heroku CLI\n\n## Setup\n\n##### 1. Check out the repository\n\n```\n$ git clone git@github.com:frodsan/recipes.git\n```\n\n##### 2. Run setup script\n\nRun the following commands to install dependencies and setup the database.\n\n```\n$ bin/setup\n```\n\n##### 3. Start the development server\n\nYou can start the server using the command given below.\n\n```\n$ bin/dev\n```\n\nAnd now you can visit the site with the URL http://localhost:3000.\n\n## Description\n\nThe application help users to find recipes by the ingredients they have.\n\n![Website](docs/savepenny.png)\n\n#### Data Seeds\n\nThe dataset can be found in [db/seeds/recipes-en.json](db/seeds/recipes-en.json)\nand it has been populated into the database by `rails db:seed`. You can find the\ncode here: [db/seeds.rb](db/seeds.rb).\n\n#### Database Structure\n\nThe format of the JSON objects found in the dataset is:\n\n```json\n{\n  \"title\": \"Golden Sweet Cornbread\",\n  \"cook_time\": 25,\n  \"prep_time\": 10,\n  \"ingredients\": [\n    \"1 cup all-purpose flour\",\n    \"1 cup yellow cornmeal\",\n    \"⅔ cup white sugar\",\n    \"1 teaspoon salt\",\n    \"3 ½ teaspoons baking powder\",\n    \"1 egg\",\n    \"1 cup milk\",\n    \"⅓ cup vegetable oil\"\n  ],\n  \"ratings\": 4.74,\n  \"cuisine\": \"\",\n  \"category\": \"Cornbread\",\n  \"author\": \"bluegirl\",\n  \"image\": \"https://images.url/image.jpg\"\n}\n```\n\nI've created the `recipes` table with the same structure as the JSON object for simplicity.\nI've decided to use `jsonb` as the type for the `ingredients` field, so it's straightforward\nto read it from Rails, but also, there is no need to do any data transformation since I planned\nto use [Postgres' search features][pgsearch].\n\n#### Searching recipes by ingredients\n\nSince the ingredients field uses `jsonb`, it is possible to parse\neach string in the JSON array into a `tsvector`, a sorted list of\nnormalized lexemes:\n\n```sql\n# select to_tsvector('english', '[\n#     \"1 cup all-purpose flour\",\n#     \"1 cup yellow cornmeal\"\n# ]') as ts_vector;\n                                  ts_vector\n--------------------------------------------------------------------------------\n '1':1,7 'all-purpos':3 'cornmeal':10 'cup':2,8 'flour':6 'purpos':5 'yellow':9\n```\n\nThen, I decided to use the `websearch_to_tsquery` function, which provides\nsearch capabilities sort of like the ones used by search engines:\n\n```sql\n# -- Search by cornmeal and flour\n# select to_tsvector('english', '[\n#   \"1 cup all-purpose flour\",\n#   \"1 cup yellow cornmeal\"\n# ]') @@ websearch_to_tsquery('cornmeal flour') as must_be_true;\n must_be_true\n--------------\n t\n\n# -- Search by cornmeal but not flour\n# select to_tsvector('english', '[\n#   \"1 cup all-purpose flour\",\n#   \"1 cup yellow cornmeal\"\n# ]') @@ websearch_to_tsquery('cornmeal -flour') as must_be_false;\n must_be_false\n--------------\n f\n```\n\nThis is how is implemented in the model:\n\n```ruby\nclass Recipe \u003c ApplicationRecord\n  scope :by_ingredients, -\u003e (query) {\n    where(\"to_tsvector('english', ingredients) @@ websearch_to_tsquery(?)\", query)\n  }\nend\n```\n\nAlso, I've created an index on the function result to speed up the queries:\n\n```ruby\nadd_index :recipes, \"to_tsvector('english', ingredients)\", using: :gin\n```\n\nExplaining the query shows that the it uses the index:\n\n```\n\u003e Recipe.by_ingredients('pasta, eggplant').explain\nEXPLAIN for: SELECT \"recipes\".* FROM \"recipes\" WHERE (to_tsvector('english', ingredients) @@ websearch_to_tsquery('pasta, eggplant'))\n                                                    QUERY PLAN\n------------------------------------------------------------------------------------------------------------------\n Index Scan using index_recipes_on_to_tsvector_english_ingredients on recipes  (cost=0.11..6.11 rows=2 width=525)\n   Index Cond: (to_tsvector('english'::regconfig, ingredients) @@ websearch_to_tsquery('pasta, eggplant'::text))\n(2 rows)\n```\n\n#### Putting everything together\n\nThe rest is standard Rails code which can be found here:\n\n- [app/models/recipe.rb](app/models/recipe.rb)\n- [app/controllers/recipes_controller.rb](app/controllers/recipes_controller.rb)\n- [app/views/recipes/](app/views/recipes/)\n\n[pgsearch]: https://www.postgresql.org/docs/current/textsearch.html\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffrodsan%2Frecipes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffrodsan%2Frecipes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffrodsan%2Frecipes/lists"}