├── src ├── templates │ ├── css-js │ │ ├── workspace │ │ │ ├── .keep │ │ │ └── store │ │ │ │ └── app │ │ │ │ ├── views │ │ │ │ └── products │ │ │ │ │ └── show.html.erb │ │ │ │ └── assets │ │ │ │ └── stylesheets │ │ │ │ └── application.css │ │ └── .tk-config.json │ ├── default │ │ ├── pgdata │ │ │ └── .keep │ │ ├── workspace │ │ │ └── .keep │ │ ├── lib │ │ │ ├── constants.js │ │ │ ├── patches │ │ │ │ └── authentication.rb │ │ │ ├── commands.js │ │ │ └── database.js │ │ ├── bin │ │ │ ├── console │ │ │ ├── rackup │ │ │ └── ruby │ │ ├── scripts │ │ │ └── createdb.js │ │ └── package.json │ ├── testing │ │ ├── workspace │ │ │ ├── .keep │ │ │ └── store │ │ │ │ └── test │ │ │ │ ├── fixtures │ │ │ │ ├── products.yml │ │ │ │ └── subscribers.yml │ │ │ │ ├── models │ │ │ │ └── product_test.rb │ │ │ │ └── mailers │ │ │ │ └── product_mailer_test.rb │ │ └── .tk-config.json │ ├── crud-products │ │ ├── workspace │ │ │ ├── .keep │ │ │ └── store │ │ │ │ ├── app │ │ │ │ ├── views │ │ │ │ │ └── products │ │ │ │ │ │ ├── edit.html.erb │ │ │ │ │ │ ├── new.html.erb │ │ │ │ │ │ ├── _form.html.erb │ │ │ │ │ │ ├── show.html.erb │ │ │ │ │ │ └── index.html.erb │ │ │ │ └── controllers │ │ │ │ │ └── products_controller.rb │ │ │ │ └── config │ │ │ │ └── routes.rb │ │ └── .tk-config.json │ ├── rails-new │ │ └── workspace │ │ │ └── store │ │ │ ├── log │ │ │ └── .keep │ │ │ ├── script │ │ │ └── .keep │ │ │ ├── storage │ │ │ └── .keep │ │ │ ├── tmp │ │ │ ├── .keep │ │ │ ├── pids │ │ │ │ └── .keep │ │ │ └── storage │ │ │ │ └── .keep │ │ │ ├── test │ │ │ ├── helpers │ │ │ │ └── .keep │ │ │ ├── controllers │ │ │ │ └── .keep │ │ │ ├── integration │ │ │ │ └── .keep │ │ │ └── test_helper.rb │ │ │ ├── app │ │ │ ├── assets │ │ │ │ ├── images │ │ │ │ │ └── .keep │ │ │ │ └── stylesheets │ │ │ │ │ └── application.css │ │ │ ├── models │ │ │ │ ├── concerns │ │ │ │ │ └── .keep │ │ │ │ └── application_record.rb │ │ │ ├── views │ │ │ │ ├── layouts │ │ │ │ │ ├── mailer.text.erb │ │ │ │ │ ├── mailer.html.erb │ │ │ │ │ └── application.html.erb │ │ │ │ └── pwa │ │ │ │ │ ├── manifest.json.erb │ │ │ │ │ └── service-worker.js │ │ │ ├── helpers │ │ │ │ └── application_helper.rb │ │ │ ├── mailers │ │ │ │ └── application_mailer.rb │ │ │ ├── javascript │ │ │ │ ├── application.js │ │ │ │ └── controllers │ │ │ │ │ ├── application.js │ │ │ │ │ └── index.js │ │ │ ├── controllers │ │ │ │ └── application_controller.rb │ │ │ └── jobs │ │ │ │ └── application_job.rb │ │ │ ├── .ruby-version │ │ │ ├── vendor │ │ │ └── javascripts │ │ │ │ └── .keep │ │ │ ├── config │ │ │ ├── master.key │ │ │ ├── routes.rb │ │ │ ├── boot.rb │ │ │ ├── environment.rb │ │ │ ├── cable.yml │ │ │ ├── importmap.rb │ │ │ ├── initializers │ │ │ │ ├── assets.rb │ │ │ │ ├── filter_parameter_logging.rb │ │ │ │ ├── inflections.rb │ │ │ │ └── content_security_policy.rb │ │ │ ├── credentials.yml.enc │ │ │ ├── database.yml │ │ │ ├── locales │ │ │ │ └── en.yml │ │ │ ├── application.rb │ │ │ └── storage.yml │ │ │ ├── bin │ │ │ ├── importmap │ │ │ └── rails │ │ │ ├── public │ │ │ ├── robots.txt │ │ │ ├── icon.png │ │ │ └── icon.svg │ │ │ ├── config.ru │ │ │ ├── Rakefile │ │ │ ├── README.md │ │ │ └── db │ │ │ └── seeds.rb │ ├── stock-notifications │ │ ├── workspace │ │ │ ├── .keep │ │ │ └── store │ │ │ │ ├── app │ │ │ │ ├── models │ │ │ │ │ ├── subscriber.rb │ │ │ │ │ ├── product.rb │ │ │ │ │ └── product │ │ │ │ │ │ └── notifications.rb │ │ │ │ ├── views │ │ │ │ │ ├── product_mailer │ │ │ │ │ │ ├── in_stock.text.erb │ │ │ │ │ │ └── in_stock.html.erb │ │ │ │ │ └── products │ │ │ │ │ │ ├── _inventory.html.erb │ │ │ │ │ │ ├── show.html.erb │ │ │ │ │ │ └── _form.html.erb │ │ │ │ ├── mailers │ │ │ │ │ └── product_mailer.rb │ │ │ │ └── controllers │ │ │ │ │ ├── unsubscribes_controller.rb │ │ │ │ │ ├── subscribers_controller.rb │ │ │ │ │ └── products_controller.rb │ │ │ │ ├── test │ │ │ │ ├── models │ │ │ │ │ └── subscriber_test.rb │ │ │ │ ├── fixtures │ │ │ │ │ └── subscribers.yml │ │ │ │ └── mailers │ │ │ │ │ └── product_mailer_test.rb │ │ │ │ ├── db │ │ │ │ ├── migrate │ │ │ │ │ ├── 20250620213158_add_inventory_count_to_products.rb │ │ │ │ │ └── 20250620214344_create_subscribers.rb │ │ │ │ └── seeds.rb │ │ │ │ └── config │ │ │ │ └── routes.rb │ │ └── .tk-config.json │ ├── authentication │ │ ├── workspace │ │ │ └── store │ │ │ │ ├── test │ │ │ │ ├── mailers │ │ │ │ │ ├── .keep │ │ │ │ │ └── previews │ │ │ │ │ │ └── passwords_mailer_preview.rb │ │ │ │ ├── models │ │ │ │ │ ├── .keep │ │ │ │ │ └── user_test.rb │ │ │ │ └── fixtures │ │ │ │ │ ├── files │ │ │ │ │ └── .keep │ │ │ │ │ └── users.yml │ │ │ │ ├── app │ │ │ │ ├── controllers │ │ │ │ │ ├── concerns │ │ │ │ │ │ └── .keep │ │ │ │ │ ├── application_controller.rb │ │ │ │ │ ├── sessions_controller.rb │ │ │ │ │ ├── products_controller.rb │ │ │ │ │ └── passwords_controller.rb │ │ │ │ ├── models │ │ │ │ │ ├── session.rb │ │ │ │ │ ├── current.rb │ │ │ │ │ └── user.rb │ │ │ │ ├── views │ │ │ │ │ ├── passwords_mailer │ │ │ │ │ │ ├── reset.text.erb │ │ │ │ │ │ └── reset.html.erb │ │ │ │ │ ├── products │ │ │ │ │ │ ├── show.html.erb │ │ │ │ │ │ └── index.html.erb │ │ │ │ │ ├── passwords │ │ │ │ │ │ ├── new.html.erb │ │ │ │ │ │ └── edit.html.erb │ │ │ │ │ └── sessions │ │ │ │ │ │ └── new.html.erb │ │ │ │ ├── mailers │ │ │ │ │ └── passwords_mailer.rb │ │ │ │ └── channels │ │ │ │ │ └── application_cable │ │ │ │ │ └── connection.rb │ │ │ │ ├── tmp │ │ │ │ └── authenticated-user.txt │ │ │ │ ├── config │ │ │ │ └── routes.rb │ │ │ │ └── db │ │ │ │ ├── migrate │ │ │ │ ├── 20250618233539_create_sessions.rb │ │ │ │ └── 20250618233538_create_users.rb │ │ │ │ └── seeds.rb │ │ └── .tk-config.json │ ├── active-storage │ │ ├── .tk-config.json │ │ └── workspace │ │ │ └── store │ │ │ ├── app │ │ │ ├── views │ │ │ │ ├── layouts │ │ │ │ │ └── action_text │ │ │ │ │ │ └── contents │ │ │ │ │ │ └── _content.html.erb │ │ │ │ ├── products │ │ │ │ │ ├── show.html.erb │ │ │ │ │ └── _form.html.erb │ │ │ │ └── active_storage │ │ │ │ │ └── blobs │ │ │ │ │ └── _blob.html.erb │ │ │ ├── assets │ │ │ │ └── images │ │ │ │ │ └── logo.png │ │ │ ├── models │ │ │ │ └── product.rb │ │ │ ├── javascript │ │ │ │ └── application.js │ │ │ └── controllers │ │ │ │ └── products_controller.rb │ │ │ ├── test │ │ │ └── fixtures │ │ │ │ └── action_text │ │ │ │ └── rich_texts.yml │ │ │ ├── config │ │ │ └── importmap.rb │ │ │ └── db │ │ │ ├── seeds.rb │ │ │ └── migrate │ │ │ └── 20250618002538_create_action_text_tables.action_text.rb │ ├── create-products │ │ ├── .tk-config.json │ │ └── workspace │ │ │ └── store │ │ │ ├── app │ │ │ └── models │ │ │ │ └── product.rb │ │ │ ├── db │ │ │ ├── seeds.rb │ │ │ ├── migrate │ │ │ │ └── 20250521010850_create_products.rb │ │ │ └── schema.rb │ │ │ └── test │ │ │ ├── models │ │ │ └── product_test.rb │ │ │ └── fixtures │ │ │ └── products.yml │ └── products-controller │ │ ├── .tk-config.json │ │ └── workspace │ │ └── store │ │ ├── app │ │ ├── helpers │ │ │ └── products_helper.rb │ │ ├── views │ │ │ └── products │ │ │ │ └── index.html.erb │ │ └── controllers │ │ │ └── products_controller.rb │ │ ├── config │ │ └── routes.rb │ │ └── test │ │ └── controllers │ │ └── products_controller_test.rb ├── content │ ├── tutorial │ │ ├── 15-css-and-js │ │ │ ├── 3-hotwire │ │ │ │ ├── _files │ │ │ │ │ ├── workspace │ │ │ │ │ │ ├── .keep │ │ │ │ │ │ └── store │ │ │ │ │ │ │ └── app │ │ │ │ │ │ │ ├── views │ │ │ │ │ │ │ └── products │ │ │ │ │ │ │ │ └── show.html.erb │ │ │ │ │ │ │ └── assets │ │ │ │ │ │ │ └── stylesheets │ │ │ │ │ │ │ └── application.css │ │ │ │ │ └── .tk-config.json │ │ │ │ └── content.md │ │ │ ├── 1-propshaft │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ └── .keep │ │ │ │ │ └── .tk-config.json │ │ │ ├── 2-importmap │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── store │ │ │ │ │ │ └── app │ │ │ │ │ │ ├── views │ │ │ │ │ │ └── products │ │ │ │ │ │ │ └── show.html.erb │ │ │ │ │ │ └── assets │ │ │ │ │ │ └── stylesheets │ │ │ │ │ │ └── application.css │ │ │ │ │ └── .tk-config.json │ │ │ └── meta.md │ │ ├── 16-testing │ │ │ ├── 1-fixtures │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ └── .keep │ │ │ │ │ └── .tk-config.json │ │ │ ├── 2-testing-emails │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── store │ │ │ │ │ │ └── test │ │ │ │ │ │ └── fixtures │ │ │ │ │ │ ├── products.yml │ │ │ │ │ │ └── subscribers.yml │ │ │ │ │ └── .tk-config.json │ │ │ └── meta.md │ │ ├── 6-active-record │ │ │ ├── 1-intro │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── app │ │ │ │ │ │ └── models │ │ │ │ │ │ └── product.rb │ │ │ │ │ └── .tk-config.json │ │ │ ├── 8-validations │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── app │ │ │ │ │ │ └── models │ │ │ │ │ │ └── product.rb │ │ │ │ │ └── .tk-config.json │ │ │ ├── 2-creating-records │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── app │ │ │ │ │ │ └── models │ │ │ │ │ │ └── product.rb │ │ │ │ │ └── .tk-config.json │ │ │ ├── 3-querying-records │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── app │ │ │ │ │ │ └── models │ │ │ │ │ │ └── product.rb │ │ │ │ │ └── .tk-config.json │ │ │ ├── 5-finding-records │ │ │ │ ├── _files │ │ │ │ │ ├── workspace │ │ │ │ │ │ ├── .keep │ │ │ │ │ │ └── app │ │ │ │ │ │ │ └── models │ │ │ │ │ │ │ └── product.rb │ │ │ │ │ └── .tk-config.json │ │ │ │ └── content.md │ │ │ ├── 6-updating-records │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── app │ │ │ │ │ │ └── models │ │ │ │ │ │ └── product.rb │ │ │ │ │ └── .tk-config.json │ │ │ ├── 7-deleting-records │ │ │ │ ├── _files │ │ │ │ │ ├── workspace │ │ │ │ │ │ ├── .keep │ │ │ │ │ │ └── app │ │ │ │ │ │ │ └── models │ │ │ │ │ │ │ └── product.rb │ │ │ │ │ └── .tk-config.json │ │ │ │ └── content.md │ │ │ ├── 4-filtering-records │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── app │ │ │ │ │ │ └── models │ │ │ │ │ │ └── product.rb │ │ │ │ │ └── .tk-config.json │ │ │ └── meta.md │ │ ├── 10-caching │ │ │ ├── 1-caching-products │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ └── .keep │ │ │ │ │ └── .tk-config.json │ │ │ └── meta.md │ │ ├── 11-rich-text │ │ │ ├── 1-action-text │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── store │ │ │ │ │ │ └── app │ │ │ │ │ │ └── views │ │ │ │ │ │ └── products │ │ │ │ │ │ └── show.html.erb │ │ │ │ │ └── .tk-config.json │ │ │ └── meta.md │ │ ├── 17-whats-next │ │ │ ├── 1-conclusion │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ └── .keep │ │ │ │ │ └── .tk-config.json │ │ │ └── meta.md │ │ ├── 3-hello-rails │ │ │ ├── 1-rails-server │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── config.ru │ │ │ │ │ └── .tk-config.json │ │ │ ├── meta.md │ │ │ └── 2-autoloading │ │ │ │ └── content.md │ │ ├── 4-database │ │ │ ├── 1-generate-model │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ └── .keep │ │ │ │ │ └── .tk-config.json │ │ │ ├── 2-database-migrations │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── store │ │ │ │ │ │ ├── app │ │ │ │ │ │ └── models │ │ │ │ │ │ │ └── product.rb │ │ │ │ │ │ ├── test │ │ │ │ │ │ ├── models │ │ │ │ │ │ │ └── product_test.rb │ │ │ │ │ │ └── fixtures │ │ │ │ │ │ │ └── products.yml │ │ │ │ │ │ └── db │ │ │ │ │ │ └── migrate │ │ │ │ │ │ └── 20250521010850_create_products.rb │ │ │ │ │ └── .tk-config.json │ │ │ ├── 3-running-migrations │ │ │ │ ├── _files │ │ │ │ │ ├── workspace │ │ │ │ │ │ └── .keep │ │ │ │ │ └── .tk-config.json │ │ │ │ └── content.md │ │ │ └── meta.md │ │ ├── 5-console │ │ │ ├── 1-rails-console │ │ │ │ ├── _files │ │ │ │ │ ├── workspace │ │ │ │ │ │ └── .keep │ │ │ │ │ └── .tk-config.json │ │ │ │ └── content.md │ │ │ └── meta.md │ │ ├── 8-controllers │ │ │ ├── 10-crud-destroy │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── store │ │ │ │ │ │ ├── app │ │ │ │ │ │ ├── views │ │ │ │ │ │ │ └── products │ │ │ │ │ │ │ │ ├── edit.html.erb │ │ │ │ │ │ │ │ ├── new.html.erb │ │ │ │ │ │ │ │ ├── show.html.erb │ │ │ │ │ │ │ │ ├── _form.html.erb │ │ │ │ │ │ │ │ └── index.html.erb │ │ │ │ │ │ └── controllers │ │ │ │ │ │ │ └── products_controller.rb │ │ │ │ │ │ └── config │ │ │ │ │ │ └── routes.rb │ │ │ │ │ └── .tk-config.json │ │ │ ├── 4-crud-show │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── store │ │ │ │ │ │ ├── app │ │ │ │ │ │ ├── controllers │ │ │ │ │ │ │ └── products_controller.rb │ │ │ │ │ │ └── views │ │ │ │ │ │ │ └── products │ │ │ │ │ │ │ └── index.html.erb │ │ │ │ │ │ └── config │ │ │ │ │ │ └── routes.rb │ │ │ │ │ └── .tk-config.json │ │ │ ├── 5-crud-create │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── store │ │ │ │ │ │ ├── app │ │ │ │ │ │ ├── views │ │ │ │ │ │ │ └── products │ │ │ │ │ │ │ │ ├── show.html.erb │ │ │ │ │ │ │ │ └── index.html.erb │ │ │ │ │ │ └── controllers │ │ │ │ │ │ │ └── products_controller.rb │ │ │ │ │ │ └── config │ │ │ │ │ │ └── routes.rb │ │ │ │ │ └── .tk-config.json │ │ │ ├── 7-crud-edit │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── store │ │ │ │ │ │ ├── app │ │ │ │ │ │ ├── views │ │ │ │ │ │ │ └── products │ │ │ │ │ │ │ │ ├── show.html.erb │ │ │ │ │ │ │ │ ├── new.html.erb │ │ │ │ │ │ │ │ └── index.html.erb │ │ │ │ │ │ └── controllers │ │ │ │ │ │ │ └── products_controller.rb │ │ │ │ │ │ └── config │ │ │ │ │ │ └── routes.rb │ │ │ │ │ └── .tk-config.json │ │ │ ├── 2-making-requests │ │ │ │ ├── _files │ │ │ │ │ ├── workspace │ │ │ │ │ │ └── .keep │ │ │ │ │ └── .tk-config.json │ │ │ │ └── content.md │ │ │ ├── 3-instance-variables │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── store │ │ │ │ │ │ └── config │ │ │ │ │ │ └── routes.rb │ │ │ │ │ └── .tk-config.json │ │ │ ├── 6-strong-parameters │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── store │ │ │ │ │ │ ├── app │ │ │ │ │ │ ├── views │ │ │ │ │ │ │ └── products │ │ │ │ │ │ │ │ ├── show.html.erb │ │ │ │ │ │ │ │ ├── new.html.erb │ │ │ │ │ │ │ │ └── index.html.erb │ │ │ │ │ │ └── controllers │ │ │ │ │ │ │ └── products_controller.rb │ │ │ │ │ │ └── config │ │ │ │ │ │ └── routes.rb │ │ │ │ │ └── .tk-config.json │ │ │ ├── 8-before-actions │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── store │ │ │ │ │ │ ├── config │ │ │ │ │ │ └── routes.rb │ │ │ │ │ │ └── app │ │ │ │ │ │ ├── views │ │ │ │ │ │ └── products │ │ │ │ │ │ │ ├── show.html.erb │ │ │ │ │ │ │ ├── new.html.erb │ │ │ │ │ │ │ └── index.html.erb │ │ │ │ │ │ └── controllers │ │ │ │ │ │ └── products_controller.rb │ │ │ │ │ └── .tk-config.json │ │ │ ├── 1-controllers-actions │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── store │ │ │ │ │ │ └── config │ │ │ │ │ │ └── routes.rb │ │ │ │ │ └── .tk-config.json │ │ │ ├── 9-extracting-partials │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── store │ │ │ │ │ │ ├── config │ │ │ │ │ │ └── routes.rb │ │ │ │ │ │ └── app │ │ │ │ │ │ ├── views │ │ │ │ │ │ └── products │ │ │ │ │ │ │ ├── show.html.erb │ │ │ │ │ │ │ ├── new.html.erb │ │ │ │ │ │ │ └── index.html.erb │ │ │ │ │ │ └── controllers │ │ │ │ │ │ └── products_controller.rb │ │ │ │ │ └── .tk-config.json │ │ │ └── meta.md │ │ ├── 12-file-uploads │ │ │ ├── 1-active-storage │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── store │ │ │ │ │ │ └── app │ │ │ │ │ │ ├── models │ │ │ │ │ │ └── product.rb │ │ │ │ │ │ ├── views │ │ │ │ │ │ └── products │ │ │ │ │ │ │ ├── _form.html.erb │ │ │ │ │ │ │ └── show.html.erb │ │ │ │ │ │ └── controllers │ │ │ │ │ │ └── products_controller.rb │ │ │ │ │ └── .tk-config.json │ │ │ └── meta.md │ │ ├── 13-internationalization │ │ │ ├── 1-i18n │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ └── .keep │ │ │ │ │ └── .tk-config.json │ │ │ └── meta.md │ │ ├── 2-rails-new │ │ │ ├── 3-directory-structure │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ └── .keep │ │ │ │ │ └── .tk-config.json │ │ │ ├── 2-creating-your-first-rails-app │ │ │ │ ├── _files │ │ │ │ │ └── workspace │ │ │ │ │ │ └── .keep │ │ │ │ └── content.md │ │ │ ├── meta.md │ │ │ ├── 4-mvc │ │ │ │ └── content.md │ │ │ └── 1-prerequisites │ │ │ │ └── content.md │ │ ├── 7-request-journey │ │ │ ├── 3-rails-routes │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ └── .keep │ │ │ │ │ └── .tk-config.json │ │ │ ├── 5-rails-routes-2 │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── store │ │ │ │ │ │ └── config │ │ │ │ │ │ └── routes.rb │ │ │ │ │ └── .tk-config.json │ │ │ ├── 6-crud-routes │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ └── .keep │ │ │ │ │ └── .tk-config.json │ │ │ ├── meta.md │ │ │ ├── 1-intro │ │ │ │ └── content.md │ │ │ └── 4-rails-routes-explained │ │ │ │ └── content.md │ │ ├── 9-authentication │ │ │ ├── 2-adding-log-out │ │ │ │ ├── _files │ │ │ │ │ ├── workspace │ │ │ │ │ │ ├── .keep │ │ │ │ │ │ └── store │ │ │ │ │ │ │ └── app │ │ │ │ │ │ │ ├── views │ │ │ │ │ │ │ ├── products │ │ │ │ │ │ │ │ ├── show.html.erb │ │ │ │ │ │ │ │ └── index.html.erb │ │ │ │ │ │ │ └── layouts │ │ │ │ │ │ │ │ └── application.html.erb │ │ │ │ │ │ │ └── controllers │ │ │ │ │ │ │ └── products_controller.rb │ │ │ │ │ └── .tk-config.json │ │ │ │ └── content.md │ │ │ ├── 4-authenticated-links │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── store │ │ │ │ │ │ └── app │ │ │ │ │ │ └── views │ │ │ │ │ │ ├── products │ │ │ │ │ │ ├── show.html.erb │ │ │ │ │ │ └── index.html.erb │ │ │ │ │ │ └── layouts │ │ │ │ │ │ └── application.html.erb │ │ │ │ │ └── .tk-config.json │ │ │ ├── 1-adding-authentication │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ └── .keep │ │ │ │ │ └── .tk-config.json │ │ │ ├── 3-unauthenticated-access │ │ │ │ ├── _files │ │ │ │ │ ├── workspace │ │ │ │ │ │ ├── .keep │ │ │ │ │ │ └── store │ │ │ │ │ │ │ └── app │ │ │ │ │ │ │ ├── views │ │ │ │ │ │ │ ├── products │ │ │ │ │ │ │ │ ├── show.html.erb │ │ │ │ │ │ │ │ └── index.html.erb │ │ │ │ │ │ │ └── layouts │ │ │ │ │ │ │ │ └── application.html.erb │ │ │ │ │ │ │ └── controllers │ │ │ │ │ │ │ └── products_controller.rb │ │ │ │ │ └── .tk-config.json │ │ │ │ └── content.md │ │ │ └── meta.md │ │ ├── 14-in-stock-notifications │ │ │ ├── 1-inventory-tracking │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ └── .keep │ │ │ │ │ └── .tk-config.json │ │ │ ├── 2-adding-subscribers │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── store │ │ │ │ │ │ ├── db │ │ │ │ │ │ ├── migrate │ │ │ │ │ │ │ └── 20250620213158_add_inventory_count_to_products.rb │ │ │ │ │ │ └── seeds.rb │ │ │ │ │ │ └── app │ │ │ │ │ │ ├── models │ │ │ │ │ │ └── product.rb │ │ │ │ │ │ ├── views │ │ │ │ │ │ └── products │ │ │ │ │ │ │ └── _form.html.erb │ │ │ │ │ │ └── controllers │ │ │ │ │ │ └── products_controller.rb │ │ │ │ │ └── .tk-config.json │ │ │ ├── 4-extracting-a-concern │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── store │ │ │ │ │ │ ├── app │ │ │ │ │ │ ├── models │ │ │ │ │ │ │ ├── subscriber.rb │ │ │ │ │ │ │ └── product.rb │ │ │ │ │ │ ├── views │ │ │ │ │ │ │ ├── product_mailer │ │ │ │ │ │ │ │ ├── in_stock.text.erb │ │ │ │ │ │ │ │ └── in_stock.html.erb │ │ │ │ │ │ │ └── products │ │ │ │ │ │ │ │ ├── _inventory.html.erb │ │ │ │ │ │ │ │ ├── show.html.erb │ │ │ │ │ │ │ │ └── _form.html.erb │ │ │ │ │ │ ├── mailers │ │ │ │ │ │ │ └── product_mailer.rb │ │ │ │ │ │ └── controllers │ │ │ │ │ │ │ ├── subscribers_controller.rb │ │ │ │ │ │ │ └── products_controller.rb │ │ │ │ │ │ ├── test │ │ │ │ │ │ ├── models │ │ │ │ │ │ │ └── subscriber_test.rb │ │ │ │ │ │ ├── fixtures │ │ │ │ │ │ │ └── subscribers.yml │ │ │ │ │ │ └── mailers │ │ │ │ │ │ │ └── product_mailer_test.rb │ │ │ │ │ │ ├── db │ │ │ │ │ │ ├── migrate │ │ │ │ │ │ │ ├── 20250620213158_add_inventory_count_to_products.rb │ │ │ │ │ │ │ └── 20250620214344_create_subscribers.rb │ │ │ │ │ │ └── seeds.rb │ │ │ │ │ │ └── config │ │ │ │ │ │ └── routes.rb │ │ │ │ │ └── .tk-config.json │ │ │ ├── 5-unsubscribe-links │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── store │ │ │ │ │ │ ├── app │ │ │ │ │ │ ├── models │ │ │ │ │ │ │ ├── subscriber.rb │ │ │ │ │ │ │ ├── product.rb │ │ │ │ │ │ │ └── product │ │ │ │ │ │ │ │ └── notifications.rb │ │ │ │ │ │ ├── views │ │ │ │ │ │ │ ├── product_mailer │ │ │ │ │ │ │ │ ├── in_stock.text.erb │ │ │ │ │ │ │ │ └── in_stock.html.erb │ │ │ │ │ │ │ └── products │ │ │ │ │ │ │ │ ├── _inventory.html.erb │ │ │ │ │ │ │ │ ├── show.html.erb │ │ │ │ │ │ │ │ └── _form.html.erb │ │ │ │ │ │ ├── mailers │ │ │ │ │ │ │ └── product_mailer.rb │ │ │ │ │ │ └── controllers │ │ │ │ │ │ │ ├── subscribers_controller.rb │ │ │ │ │ │ │ └── products_controller.rb │ │ │ │ │ │ ├── test │ │ │ │ │ │ ├── models │ │ │ │ │ │ │ └── subscriber_test.rb │ │ │ │ │ │ ├── fixtures │ │ │ │ │ │ │ └── subscribers.yml │ │ │ │ │ │ └── mailers │ │ │ │ │ │ │ └── product_mailer_test.rb │ │ │ │ │ │ ├── db │ │ │ │ │ │ ├── migrate │ │ │ │ │ │ │ ├── 20250620213158_add_inventory_count_to_products.rb │ │ │ │ │ │ │ └── 20250620214344_create_subscribers.rb │ │ │ │ │ │ └── seeds.rb │ │ │ │ │ │ └── config │ │ │ │ │ │ └── routes.rb │ │ │ │ │ └── .tk-config.json │ │ │ ├── 3-in-stock-email-notifications │ │ │ │ └── _files │ │ │ │ │ ├── workspace │ │ │ │ │ ├── .keep │ │ │ │ │ └── store │ │ │ │ │ │ ├── app │ │ │ │ │ │ ├── models │ │ │ │ │ │ │ ├── subscriber.rb │ │ │ │ │ │ │ └── product.rb │ │ │ │ │ │ ├── views │ │ │ │ │ │ │ └── products │ │ │ │ │ │ │ │ ├── _inventory.html.erb │ │ │ │ │ │ │ │ ├── show.html.erb │ │ │ │ │ │ │ │ └── _form.html.erb │ │ │ │ │ │ └── controllers │ │ │ │ │ │ │ ├── subscribers_controller.rb │ │ │ │ │ │ │ └── products_controller.rb │ │ │ │ │ │ ├── test │ │ │ │ │ │ ├── models │ │ │ │ │ │ │ └── subscriber_test.rb │ │ │ │ │ │ └── fixtures │ │ │ │ │ │ │ └── subscribers.yml │ │ │ │ │ │ ├── db │ │ │ │ │ │ ├── migrate │ │ │ │ │ │ │ ├── 20250620213158_add_inventory_count_to_products.rb │ │ │ │ │ │ │ └── 20250620214344_create_subscribers.rb │ │ │ │ │ │ └── seeds.rb │ │ │ │ │ │ └── config │ │ │ │ │ │ └── routes.rb │ │ │ │ │ └── .tk-config.json │ │ │ └── meta.md │ │ ├── 1-introduction │ │ │ └── meta.md │ │ └── meta.md │ └── config.ts ├── env.d.ts └── plugins │ └── remarkRailsPathLinks.ts ├── rails-wasm ├── .gitignore ├── .railsrc ├── Gemfile ├── bin │ └── pack ├── README.md ├── package.json ├── config │ └── wasmify.yml └── boot.rb ├── .dockerignore ├── public └── images │ └── getting_started │ ├── routing_dark.jpg │ ├── routing_light.jpg │ ├── mvc_architecture_dark.jpg │ └── mvc_architecture_light.jpg ├── vite.config.js ├── .vscode ├── extensions.json └── launch.json ├── .gitignore ├── uno.config.ts ├── icons └── languages │ ├── html.svg │ ├── markdown.svg │ ├── css.svg │ ├── ruby.svg │ ├── js.svg │ ├── ts.svg │ ├── json.svg │ ├── sass.svg │ └── erb.svg ├── tsconfig.json ├── fly.toml ├── .github └── workflows │ └── fly-deploy.yml ├── astro.config.ts ├── patches └── @tutorialkit+theme+1.5.0.patch └── package.json /src/templates/css-js/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/default/pgdata/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/default/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/testing/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/crud-products/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rails-wasm/.gitignore: -------------------------------------------------------------------------------- 1 | tmp/ 2 | build/ 3 | rubies/ 4 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/script/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/storage/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/tmp/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/stock-notifications/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/test/helpers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/tmp/pids/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/tmp/storage/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/test/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/test/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/test/controllers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/test/integration/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/15-css-and-js/3-hotwire/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/16-testing/1-fixtures/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/1-intro/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-3.3.6 2 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/vendor/javascripts/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/10-caching/1-caching-products/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/11-rich-text/1-action-text/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/15-css-and-js/1-propshaft/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/15-css-and-js/2-importmap/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/16-testing/2-testing-emails/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/17-whats-next/1-conclusion/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/3-hello-rails/1-rails-server/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/4-database/1-generate-model/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/5-console/1-rails-console/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/8-validations/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/10-crud-destroy/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/4-crud-show/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/5-crud-create/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/7-crud-edit/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/test/fixtures/files/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/12-file-uploads/1-active-storage/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/13-internationalization/1-i18n/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/2-rails-new/3-directory-structure/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/4-database/2-database-migrations/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/4-database/3-running-migrations/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/2-creating-records/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/3-querying-records/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/5-finding-records/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/6-updating-records/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/7-deleting-records/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/7-request-journey/3-rails-routes/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/7-request-journey/5-rails-routes-2/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/7-request-journey/6-crud-routes/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/2-making-requests/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/3-instance-variables/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/6-strong-parameters/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/8-before-actions/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/9-authentication/2-adding-log-out/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/testing/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../css-js" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/4-filtering-records/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/1-controllers-actions/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/9-extracting-partials/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/9-authentication/4-authenticated-links/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/2-rails-new/2-creating-your-first-rails-app/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/9-authentication/1-adding-authentication/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/9-authentication/3-unauthenticated-access/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/1-inventory-tracking/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/2-adding-subscribers/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/4-extracting-a-concern/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/5-unsubscribe-links/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/active-storage/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../authentication" 3 | } 4 | -------------------------------------------------------------------------------- /src/templates/authentication/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../crud-products" 3 | } 4 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/tmp/authenticated-user.txt: -------------------------------------------------------------------------------- 1 | you@example.org 2 | -------------------------------------------------------------------------------- /src/templates/create-products/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../rails-new" 3 | } 4 | -------------------------------------------------------------------------------- /src/templates/crud-products/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../create-products" 3 | } 4 | -------------------------------------------------------------------------------- /src/templates/css-js/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../stock-notifications" 3 | } 4 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/config/master.key: -------------------------------------------------------------------------------- 1 | 6cca3c78ba2c4c57b341dd7b4cb9b889 -------------------------------------------------------------------------------- /src/content/tutorial/3-hello-rails/meta.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: part 3 | title: Hello, Rails! 4 | --- 5 | -------------------------------------------------------------------------------- /src/templates/default/lib/constants.js: -------------------------------------------------------------------------------- 1 | export const RAILS_WASM_PACKAGE_VERSION = '8.0.2-rc.1'; 2 | -------------------------------------------------------------------------------- /src/templates/stock-notifications/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../active-storage" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/3-in-stock-email-notifications/_files/workspace/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/products-controller/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../create-products" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/2-rails-new/meta.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: part 3 | title: Creating a New Rails App 4 | --- 5 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /src/templates/products-controller/workspace/store/app/helpers/products_helper.rb: -------------------------------------------------------------------------------- 1 | module ProductsHelper 2 | end 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | /.git 2 | /node_modules 3 | .dockerignore 4 | .env 5 | Dockerfile 6 | fly.toml 7 | /dist 8 | /rails-wasm 9 | -------------------------------------------------------------------------------- /src/templates/testing/workspace/store/test/fixtures/products.yml: -------------------------------------------------------------------------------- 1 | tshirt: 2 | name: T-Shirt 3 | inventory_count: 15 4 | -------------------------------------------------------------------------------- /src/content/tutorial/16-testing/1-fixtures/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/css-js" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/1-intro/_files/workspace/app/models/product.rb: -------------------------------------------------------------------------------- 1 | class Product < ApplicationRecord 2 | end 3 | -------------------------------------------------------------------------------- /src/content/tutorial/1-introduction/meta.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: part 3 | title: Introduction 4 | editor: false 5 | terminal: false 6 | --- 7 | -------------------------------------------------------------------------------- /src/content/tutorial/16-testing/2-testing-emails/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/css-js" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/17-whats-next/1-conclusion/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/testing" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/3-hello-rails/1-rails-server/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/rails-new" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/4-database/1-generate-model/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/rails-new" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/8-validations/_files/workspace/app/models/product.rb: -------------------------------------------------------------------------------- 1 | class Product < ApplicationRecord 2 | end 3 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/app/models/session.rb: -------------------------------------------------------------------------------- 1 | class Session < ApplicationRecord 2 | belongs_to :user 3 | end 4 | -------------------------------------------------------------------------------- /src/content/tutorial/11-rich-text/1-action-text/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/active-storage" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/15-css-and-js/3-hotwire/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/stock-notifications" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/2-rails-new/3-directory-structure/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/rails-new" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/4-database/2-database-migrations/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/rails-new" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/4-database/3-running-migrations/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../2-database-migrations/_files" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/5-console/1-rails-console/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/create-products" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/1-intro/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/create-products" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/2-creating-records/_files/workspace/app/models/product.rb: -------------------------------------------------------------------------------- 1 | class Product < ApplicationRecord 2 | end 3 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/3-querying-records/_files/workspace/app/models/product.rb: -------------------------------------------------------------------------------- 1 | class Product < ApplicationRecord 2 | end 3 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/4-filtering-records/_files/workspace/app/models/product.rb: -------------------------------------------------------------------------------- 1 | class Product < ApplicationRecord 2 | end 3 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/5-finding-records/_files/workspace/app/models/product.rb: -------------------------------------------------------------------------------- 1 | class Product < ApplicationRecord 2 | end 3 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/6-updating-records/_files/workspace/app/models/product.rb: -------------------------------------------------------------------------------- 1 | class Product < ApplicationRecord 2 | end 3 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/7-deleting-records/_files/workspace/app/models/product.rb: -------------------------------------------------------------------------------- 1 | class Product < ApplicationRecord 2 | end 3 | -------------------------------------------------------------------------------- /src/content/tutorial/10-caching/1-caching-products/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/authentication" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/12-file-uploads/1-active-storage/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/active-storage" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/13-internationalization/1-i18n/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/active-storage" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/15-css-and-js/1-propshaft/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/stock-notifications" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/15-css-and-js/2-importmap/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/stock-notifications" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/4-database/2-database-migrations/_files/workspace/store/app/models/product.rb: -------------------------------------------------------------------------------- 1 | class Product < ApplicationRecord 2 | end 3 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/8-validations/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/create-products" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/7-request-journey/3-rails-routes/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/create-products" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/7-request-journey/6-crud-routes/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/create-products" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/4-crud-show/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/products-controller" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/5-crud-create/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/products-controller" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/7-crud-edit/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/products-controller" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/9-authentication/2-adding-log-out/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/authentication" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/2-creating-records/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/create-products" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/3-querying-records/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/create-products" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/4-filtering-records/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/create-products" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/5-finding-records/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/create-products" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/6-updating-records/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/create-products" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/7-deleting-records/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/create-products" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/7-request-journey/5-rails-routes-2/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/create-products" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/1-controllers-actions/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/create-products" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/10-crud-destroy/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/products-controller" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/2-making-requests/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/products-controller" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/6-strong-parameters/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/products-controller" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/8-before-actions/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/products-controller" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/9-authentication/4-authenticated-links/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/authentication" 3 | } 4 | -------------------------------------------------------------------------------- /src/templates/create-products/workspace/store/app/models/product.rb: -------------------------------------------------------------------------------- 1 | class Product < ApplicationRecord 2 | validates :name, presence: true 3 | end 4 | -------------------------------------------------------------------------------- /public/images/getting_started/routing_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilmartians/rails-tutorial/HEAD/public/images/getting_started/routing_dark.jpg -------------------------------------------------------------------------------- /public/images/getting_started/routing_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilmartians/rails-tutorial/HEAD/public/images/getting_started/routing_light.jpg -------------------------------------------------------------------------------- /src/content/tutorial/16-testing/2-testing-emails/_files/workspace/store/test/fixtures/products.yml: -------------------------------------------------------------------------------- 1 | tshirt: 2 | name: T-Shirt 3 | inventory_count: 15 4 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/3-instance-variables/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/products-controller" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/9-extracting-partials/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/products-controller" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/9-authentication/1-adding-authentication/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/crud-products" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/9-authentication/3-unauthenticated-access/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/authentication" 3 | } 4 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | primary_abstract_class 3 | end 4 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/1-inventory-tracking/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/active-storage" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/2-adding-subscribers/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/active-storage" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/4-extracting-a-concern/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/active-storage" 3 | } 4 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/5-unsubscribe-links/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/active-storage" 3 | } 4 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | -------------------------------------------------------------------------------- /src/templates/active-storage/workspace/store/app/views/layouts/action_text/contents/_content.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= yield -%> 3 |
4 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/bin/importmap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative "../config/application" 4 | require "importmap/commands" 5 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | export default defineConfig({ 4 | preview: { 5 | allowedHosts: ['astro.test'] 6 | } 7 | }); -------------------------------------------------------------------------------- /src/content/tutorial/3-hello-rails/1-rails-server/_files/workspace/config.ru: -------------------------------------------------------------------------------- 1 | run ->(env) { 2 | [200, {"Content-Type" => "text/plain"}, ["Hello from Rack!"]] 3 | } 4 | -------------------------------------------------------------------------------- /src/templates/products-controller/workspace/store/app/views/products/index.html.erb: -------------------------------------------------------------------------------- 1 |

Products#index

2 |

Find me in app/views/products/index.html.erb

3 | -------------------------------------------------------------------------------- /public/images/getting_started/mvc_architecture_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilmartians/rails-tutorial/HEAD/public/images/getting_started/mvc_architecture_dark.jpg -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/3-in-stock-email-notifications/_files/.tk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../templates/active-storage" 3 | } 4 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | # Defines the root path route ("/") 3 | # root "posts#index" 4 | end 5 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilmartians/rails-tutorial/HEAD/src/templates/rails-new/workspace/store/public/icon.png -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode", "StackBlitz.tutorialkit", "unifiedjs.vscode-mdx"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /public/images/getting_started/mvc_architecture_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilmartians/rails-tutorial/HEAD/public/images/getting_started/mvc_architecture_light.jpg -------------------------------------------------------------------------------- /src/templates/products-controller/workspace/store/app/controllers/products_controller.rb: -------------------------------------------------------------------------------- 1 | class ProductsController < ApplicationController 2 | def index 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/7-crud-edit/_files/workspace/store/app/views/products/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= @product.name %>

2 | 3 | <%= link_to "Back", products_path %> 4 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/5-crud-create/_files/workspace/store/app/views/products/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= @product.name %>

2 | 3 | <%= link_to "Back", products_path %> 4 | -------------------------------------------------------------------------------- /src/templates/stock-notifications/workspace/store/app/models/subscriber.rb: -------------------------------------------------------------------------------- 1 | class Subscriber < ApplicationRecord 2 | belongs_to :product 3 | generates_token_for :unsubscribe 4 | end 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | .astro 3 | node_modules 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | .env 9 | .env.production 10 | .DS_Store 11 | .idea 12 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/4-extracting-a-concern/_files/workspace/store/app/models/subscriber.rb: -------------------------------------------------------------------------------- 1 | class Subscriber < ApplicationRecord 2 | belongs_to :product 3 | end 4 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/5-unsubscribe-links/_files/workspace/store/app/models/subscriber.rb: -------------------------------------------------------------------------------- 1 | class Subscriber < ApplicationRecord 2 | belongs_to :product 3 | end 4 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/6-strong-parameters/_files/workspace/store/app/views/products/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= @product.name %>

2 | 3 | <%= link_to "Back", products_path %> 4 | -------------------------------------------------------------------------------- /src/templates/crud-products/workspace/store/app/views/products/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Edit product

2 | 3 | <%= render "form", product: @product %> 4 | <%= link_to "Cancel", @product %> 5 | -------------------------------------------------------------------------------- /src/templates/crud-products/workspace/store/app/views/products/new.html.erb: -------------------------------------------------------------------------------- 1 |

New product

2 | 3 | <%= render "form", product: @product %> 4 | <%= link_to "Cancel", products_path %> 5 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: "from@example.com" 3 | layout "mailer" 4 | end 5 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/public/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /uno.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@tutorialkit/theme'; 2 | 3 | export default defineConfig({ 4 | // add your UnoCSS config here: https://unocss.dev/guide/config-file 5 | }); 6 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/3-in-stock-email-notifications/_files/workspace/store/app/models/subscriber.rb: -------------------------------------------------------------------------------- 1 | class Subscriber < ApplicationRecord 2 | belongs_to :product 3 | end 4 | -------------------------------------------------------------------------------- /src/templates/testing/workspace/store/test/fixtures/subscribers.yml: -------------------------------------------------------------------------------- 1 | david: 2 | product: tshirt 3 | email: david@example.org 4 | 5 | chris: 6 | product: tshirt 7 | email: chris@example.org 8 | -------------------------------------------------------------------------------- /icons/languages/html.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/templates/active-storage/workspace/store/app/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilmartians/rails-tutorial/HEAD/src/templates/active-storage/workspace/store/app/assets/images/logo.png -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/app/models/current.rb: -------------------------------------------------------------------------------- 1 | class Current < ActiveSupport::CurrentAttributes 2 | attribute :session 3 | delegate :user, to: :session, allow_nil: true 4 | end 5 | -------------------------------------------------------------------------------- /src/templates/crud-products/workspace/store/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :products 3 | 4 | # Defines the root path route ("/") 5 | root "products#index" 6 | end 7 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /src/templates/create-products/workspace/store/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # Create products that we should have created in the previous lessons 2 | Product.create(name: "T-Shirt").destroy 3 | Product.create(name: "Pants") 4 | -------------------------------------------------------------------------------- /src/templates/products-controller/workspace/store/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :products 3 | 4 | # Defines the root path route ("/") 5 | # root "posts#index" 6 | end 7 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /src/content/tutorial/12-file-uploads/1-active-storage/_files/workspace/store/app/models/product.rb: -------------------------------------------------------------------------------- 1 | class Product < ApplicationRecord 2 | has_rich_text :description 3 | validates :name, presence: true 4 | end 5 | -------------------------------------------------------------------------------- /src/templates/active-storage/workspace/store/test/fixtures/action_text/rich_texts.yml: -------------------------------------------------------------------------------- 1 | # one: 2 | # record: name_of_fixture (ClassOfFixture) 3 | # name: content 4 | # body:

In a million stars!

5 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/test/models/user_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class UserTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/10-crud-destroy/_files/workspace/store/app/views/products/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Edit product

2 | 3 | <%= render "form", product: @product %> 4 | <%= link_to "Cancel", @product %> 5 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/10-crud-destroy/_files/workspace/store/app/views/products/new.html.erb: -------------------------------------------------------------------------------- 1 |

New product

2 | 3 | <%= render "form", product: @product %> 4 | <%= link_to "Cancel", products_path %> 5 | -------------------------------------------------------------------------------- /src/templates/active-storage/workspace/store/app/models/product.rb: -------------------------------------------------------------------------------- 1 | class Product < ApplicationRecord 2 | has_one_attached :featured_image 3 | has_rich_text :description 4 | validates :name, presence: true 5 | end 6 | -------------------------------------------------------------------------------- /src/templates/create-products/workspace/store/test/models/product_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ProductTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/5-unsubscribe-links/_files/workspace/store/app/views/product_mailer/in_stock.text.erb: -------------------------------------------------------------------------------- 1 | Good news! 2 | 3 | <%= @product.name %> is back in stock. 4 | <%= product_url(@product) %> 5 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/4-crud-show/_files/workspace/store/app/controllers/products_controller.rb: -------------------------------------------------------------------------------- 1 | class ProductsController < ApplicationController 2 | def index 3 | @products = Product.all 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/4-extracting-a-concern/_files/workspace/store/app/views/product_mailer/in_stock.text.erb: -------------------------------------------------------------------------------- 1 | Good news! 2 | 3 | <%= @product.name %> is back in stock. 4 | <%= product_url(@product) %> 5 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/4-crud-show/_files/workspace/store/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :products 3 | 4 | # Defines the root path route ("/") 5 | root "products#index" 6 | end 7 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/7-crud-edit/_files/workspace/store/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :products 3 | 4 | # Defines the root path route ("/") 5 | root "products#index" 6 | end 7 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/app/views/passwords_mailer/reset.text.erb: -------------------------------------------------------------------------------- 1 | You can reset your password within the next 15 minutes on this password reset page: 2 | <%= edit_password_url(@user.password_reset_token) %> 3 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /src/templates/stock-notifications/workspace/store/test/models/subscriber_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class SubscriberTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /icons/languages/markdown.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/content/tutorial/10-caching/meta.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: part 3 | title: Caching 4 | prepareCommands: 5 | - ['npm install', 'Preparing Ruby runtime'] 6 | - ['node scripts/rails.js db:prepare', 'Prepare development database'] 7 | --- 8 | -------------------------------------------------------------------------------- /src/content/tutorial/16-testing/2-testing-emails/_files/workspace/store/test/fixtures/subscribers.yml: -------------------------------------------------------------------------------- 1 | david: 2 | product: tshirt 3 | email: david@example.org 4 | 5 | chris: 6 | product: tshirt 7 | email: chris@example.org 8 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/10-crud-destroy/_files/workspace/store/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :products 3 | 4 | # Defines the root path route ("/") 5 | root "products#index" 6 | end 7 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/5-crud-create/_files/workspace/store/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :products 3 | 4 | # Defines the root path route ("/") 5 | root "products#index" 6 | end 7 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/8-before-actions/_files/workspace/store/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :products 3 | 4 | # Defines the root path route ("/") 5 | root "products#index" 6 | end 7 | -------------------------------------------------------------------------------- /src/templates/create-products/workspace/store/test/fixtures/products.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | name: MyString 5 | 6 | two: 7 | name: MyString 8 | -------------------------------------------------------------------------------- /src/content/tutorial/11-rich-text/meta.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: part 3 | title: Rich Text 4 | prepareCommands: 5 | - ['npm install', 'Preparing Ruby runtime'] 6 | - ['node scripts/rails.js db:prepare', 'Prepare development database'] 7 | --- 8 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/5-unsubscribe-links/_files/workspace/store/app/views/product_mailer/in_stock.html.erb: -------------------------------------------------------------------------------- 1 |

Good news!

2 | 3 |

<%= link_to @product.name, product_url(@product) %> is back in stock.

4 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/1-controllers-actions/_files/workspace/store/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :products 3 | 4 | # Defines the root path route ("/") 5 | # root "posts#index" 6 | end 7 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/10-crud-destroy/_files/workspace/store/app/views/products/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= @product.name %>

2 | 3 | <%= link_to "Back", products_path %> 4 | <%= link_to "Edit", edit_product_path(@product) %> 5 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/3-instance-variables/_files/workspace/store/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :products 3 | 4 | # Defines the root path route ("/") 5 | root "products#index" 6 | end 7 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/6-strong-parameters/_files/workspace/store/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :products 3 | 4 | # Defines the root path route ("/") 5 | root "products#index" 6 | end 7 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/8-before-actions/_files/workspace/store/app/views/products/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= @product.name %>

2 | 3 | <%= link_to "Back", products_path %> 4 | <%= link_to "Edit", edit_product_path(@product) %> 5 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/9-extracting-partials/_files/workspace/store/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :products 3 | 4 | # Defines the root path route ("/") 5 | root "products#index" 6 | end 7 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/app/javascript/application.js: -------------------------------------------------------------------------------- 1 | // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails 2 | 3 | import "@hotwired/turbo-rails" 4 | import "controllers" 5 | -------------------------------------------------------------------------------- /src/content/tutorial/12-file-uploads/meta.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: part 3 | title: File Uploads 4 | prepareCommands: 5 | - ['npm install', 'Preparing Ruby runtime'] 6 | - ['node scripts/rails.js db:prepare', 'Prepare development database'] 7 | --- 8 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/4-extracting-a-concern/_files/workspace/store/app/views/product_mailer/in_stock.html.erb: -------------------------------------------------------------------------------- 1 |

Good news!

2 | 3 |

<%= link_to @product.name, product_url(@product) %> is back in stock.

4 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/9-extracting-partials/_files/workspace/store/app/views/products/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= @product.name %>

2 | 3 | <%= link_to "Back", products_path %> 4 | <%= link_to "Edit", edit_product_path(@product) %> 5 | -------------------------------------------------------------------------------- /src/content/tutorial/4-database/2-database-migrations/_files/workspace/store/test/models/product_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ProductTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/app/mailers/passwords_mailer.rb: -------------------------------------------------------------------------------- 1 | class PasswordsMailer < ApplicationMailer 2 | def reset(user) 3 | @user = user 4 | mail subject: "Reset your password", to: user.email_address 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | has_secure_password 3 | has_many :sessions, dependent: :destroy 4 | 5 | normalizes :email_address, with: ->(e) { e.strip.downcase } 6 | end 7 | -------------------------------------------------------------------------------- /src/content/tutorial/7-request-journey/5-rails-routes-2/_files/workspace/store/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | get "/products", to: "products#index" 3 | 4 | # Defines the root path route ("/") 5 | # root "posts#index" 6 | end 7 | -------------------------------------------------------------------------------- /src/content/tutorial/5-console/meta.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: part 3 | title: Rails Console 4 | editor: false 5 | prepareCommands: 6 | - ['npm install', 'Preparing Ruby runtime'] 7 | - ['node scripts/rails.js db:prepare', 'Prepare development database'] 8 | --- 9 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/meta.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: part 3 | title: Active Record Model Basics 4 | prepareCommands: 5 | - ['npm install', 'Preparing Ruby runtime'] 6 | - ['node scripts/rails.js db:prepare', 'Prepare development database'] 7 | --- 8 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/app/views/passwords_mailer/reset.html.erb: -------------------------------------------------------------------------------- 1 |

2 | You can reset your password within the next 15 minutes on 3 | <%= link_to "this password reset page", edit_password_url(@user.password_reset_token) %>. 4 |

5 | -------------------------------------------------------------------------------- /src/templates/products-controller/workspace/store/test/controllers/products_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ProductsControllerTest < ActionDispatch::IntegrationTest 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /src/content/tutorial/4-database/2-database-migrations/_files/workspace/store/test/fixtures/products.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | name: MyString 5 | 6 | two: 7 | name: MyString 8 | -------------------------------------------------------------------------------- /src/content/tutorial/4-database/meta.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: part 3 | title: Creating a Database Model 4 | prepareCommands: 5 | - ['npm install', 'Preparing Ruby runtime'] 6 | - ['node scripts/createdb.js store_development', 'Create development database'] 7 | --- 8 | -------------------------------------------------------------------------------- /rails-wasm/.railsrc: -------------------------------------------------------------------------------- 1 | --skip-bundle 2 | --skip-git 3 | --skip-decrypted-diffs 4 | --skip-bootsnap 5 | --skip-brakeman 6 | --skip-dev-gems 7 | --skip-kamal 8 | --skip-thruster 9 | --skip-docker 10 | --skip-system-test 11 | --skip-dev-gems 12 | --skip-rubocop 13 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/5-unsubscribe-links/_files/workspace/store/test/models/subscriber_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class SubscriberTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /src/content/tutorial/7-request-journey/meta.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: part 3 | title: A Request's Journey Through Rails 4 | prepareCommands: 5 | - ['npm install', 'Preparing Ruby runtime'] 6 | - ['node scripts/rails.js db:prepare', 'Prepare development database'] 7 | --- 8 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resource :session 3 | resources :passwords, param: :token 4 | resources :products 5 | 6 | # Defines the root path route ("/") 7 | root "products#index" 8 | end 9 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/4-extracting-a-concern/_files/workspace/store/test/models/subscriber_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class SubscriberTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development Server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/3-in-stock-email-notifications/_files/workspace/store/test/models/subscriber_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class SubscriberTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /src/templates/default/bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --disable-warning=ExperimentalWarning 2 | 3 | import initVM from "../lib/rails.js"; 4 | import IRBRepl from "../lib/irb.js"; 5 | 6 | const vm = await initVM(); 7 | const irb = new IRBRepl(vm); 8 | 9 | irb.start(); 10 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/4-crud-show/_files/workspace/store/app/views/products/index.html.erb: -------------------------------------------------------------------------------- 1 |

Products

2 | 3 |
4 | <% @products.each do |product| %> 5 |
6 | <%= product.name %> 7 |
8 | <% end %> 9 |
10 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: store_production 11 | -------------------------------------------------------------------------------- /src/templates/stock-notifications/workspace/store/test/fixtures/subscribers.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | product: one 5 | email: MyString 6 | 7 | two: 8 | product: two 9 | email: MyString 10 | -------------------------------------------------------------------------------- /src/content/config.ts: -------------------------------------------------------------------------------- 1 | import { contentSchema } from '@tutorialkit/types'; 2 | import { defineCollection } from 'astro:content'; 3 | 4 | const tutorial = defineCollection({ 5 | type: 'content', 6 | schema: contentSchema, 7 | }); 8 | 9 | export const collections = { tutorial }; 10 | -------------------------------------------------------------------------------- /src/templates/active-storage/workspace/store/app/javascript/application.js: -------------------------------------------------------------------------------- 1 | // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails 2 | 3 | import "@hotwired/turbo-rails" 4 | import "controllers" 5 | import "trix" 6 | import "@rails/actiontext" 7 | -------------------------------------------------------------------------------- /src/templates/create-products/workspace/store/db/migrate/20250521010850_create_products.rb: -------------------------------------------------------------------------------- 1 | class CreateProducts < ActiveRecord::Migration[8.0] 2 | def change 3 | create_table :products do |t| 4 | t.string :name 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /src/templates/default/scripts/createdb.js: -------------------------------------------------------------------------------- 1 | import { PGLite4Rails } from "../lib/database.js"; 2 | 3 | const pgDataDir = new URL("../pgdata", import.meta.url).pathname; 4 | const pglite = new PGLite4Rails(pgDataDir); 5 | 6 | const dbname = process.argv[2]; 7 | pglite.create_interface(dbname); 8 | -------------------------------------------------------------------------------- /src/templates/stock-notifications/workspace/store/app/views/product_mailer/in_stock.text.erb: -------------------------------------------------------------------------------- 1 | Good news! 2 | 3 | <%= @product.name %> is back in stock. 4 | <%= product_url(@product) %> 5 | 6 | Unsubscribe: <%= unsubscribe_url(token: params[:subscriber].generate_token_for(:unsubscribe)) %> 7 | -------------------------------------------------------------------------------- /src/templates/stock-notifications/workspace/store/db/migrate/20250620213158_add_inventory_count_to_products.rb: -------------------------------------------------------------------------------- 1 | class AddInventoryCountToProducts < ActiveRecord::Migration[8.0] 2 | def change 3 | add_column :products, :inventory_count, :integer, default: 0, null: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /icons/languages/css.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/templates/crud-products/workspace/store/app/views/products/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with model: product do |form| %> 2 |
3 | <%= form.label :name %> 4 | <%= form.text_field :name %> 5 |
6 | 7 |
8 | <%= form.submit %> 9 |
10 | <% end %> 11 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative "config/application" 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has. 3 | allow_browser versions: :modern 4 | end 5 | -------------------------------------------------------------------------------- /src/templates/crud-products/workspace/store/app/views/products/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= @product.name %>

2 | 3 | <%= link_to "Back", products_path %> 4 | <%= link_to "Edit", edit_product_path(@product) %> 5 | <%= button_to "Delete", @product, method: :delete, data: { turbo_confirm: "Are you sure?" } %> 6 | -------------------------------------------------------------------------------- /src/content/tutorial/16-testing/meta.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: part 3 | title: Testing 4 | editor: 5 | fileTree: 6 | allowEdits: "/workspace/store/**" 7 | prepareCommands: 8 | - ['npm install', 'Preparing Ruby runtime'] 9 | - ['node scripts/rails.js db:prepare', 'Prepare development database'] 10 | --- 11 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/5-crud-create/_files/workspace/store/app/controllers/products_controller.rb: -------------------------------------------------------------------------------- 1 | class ProductsController < ApplicationController 2 | def index 3 | @products = Product.all 4 | end 5 | 6 | def show 7 | @product = Product.find(params[:id]) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /src/templates/stock-notifications/workspace/store/app/views/product_mailer/in_stock.html.erb: -------------------------------------------------------------------------------- 1 |

Good news!

2 | 3 |

<%= link_to @product.name, product_url(@product) %> is back in stock.

4 | 5 | <%= link_to "Unsubscribe", unsubscribe_url(token: params[:subscriber].generate_token_for(:unsubscribe)) %> 6 | -------------------------------------------------------------------------------- /src/content/tutorial/17-whats-next/meta.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: part 3 | title: What's Next? 4 | editor: 5 | fileTree: 6 | allowEdits: "/workspace/store/**" 7 | prepareCommands: 8 | - ['npm install', 'Preparing Ruby runtime'] 9 | - ['node scripts/rails.js db:prepare', 'Prepare development database'] 10 | --- 11 | -------------------------------------------------------------------------------- /src/content/tutorial/4-database/2-database-migrations/_files/workspace/store/db/migrate/20250521010850_create_products.rb: -------------------------------------------------------------------------------- 1 | class CreateProducts < ActiveRecord::Migration[8.0] 2 | def change 3 | create_table :products do |t| 4 | t.string :name 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/5-crud-create/_files/workspace/store/app/views/products/index.html.erb: -------------------------------------------------------------------------------- 1 |

Products

2 | 3 |
4 | <% @products.each do |product| %> 5 |
6 | <%= link_to product.name, product_path(product.id) %> 7 |
8 | <% end %> 9 |
10 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/meta.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: part 3 | title: Controllers 4 | editor: 5 | fileTree: 6 | allowEdits: "/workspace/store/**" 7 | prepareCommands: 8 | - ['npm install', 'Preparing Ruby runtime'] 9 | - ['node scripts/rails.js db:prepare', 'Prepare development database'] 10 | --- 11 | -------------------------------------------------------------------------------- /icons/languages/ruby.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/5-unsubscribe-links/_files/workspace/store/test/fixtures/subscribers.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | product: one 5 | email: MyString 6 | 7 | two: 8 | product: two 9 | email: MyString 10 | -------------------------------------------------------------------------------- /src/content/tutorial/15-css-and-js/meta.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: part 3 | title: CSS & JavaScript 4 | editor: 5 | fileTree: 6 | allowEdits: "/workspace/store/**" 7 | prepareCommands: 8 | - ['npm install', 'Preparing Ruby runtime'] 9 | - ['node scripts/rails.js db:prepare', 'Prepare development database'] 10 | --- 11 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/10-crud-destroy/_files/workspace/store/app/views/products/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with model: product do |form| %> 2 |
3 | <%= form.label :name %> 4 | <%= form.text_field :name %> 5 |
6 | 7 |
8 | <%= form.submit %> 9 |
10 | <% end %> 11 | -------------------------------------------------------------------------------- /src/content/tutorial/9-authentication/meta.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: part 3 | title: Authentication 4 | editor: 5 | fileTree: 6 | allowEdits: "/workspace/store/**" 7 | prepareCommands: 8 | - ['npm install', 'Preparing Ruby runtime'] 9 | - ['node scripts/rails.js db:prepare', 'Prepare development database'] 10 | --- 11 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/2-adding-subscribers/_files/workspace/store/db/migrate/20250620213158_add_inventory_count_to_products.rb: -------------------------------------------------------------------------------- 1 | class AddInventoryCountToProducts < ActiveRecord::Migration[8.0] 2 | def change 3 | add_column :products, :inventory_count, :integer, default: 0, null: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/4-extracting-a-concern/_files/workspace/store/test/fixtures/subscribers.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | product: one 5 | email: MyString 6 | 7 | two: 8 | product: two 9 | email: MyString 10 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/5-unsubscribe-links/_files/workspace/store/db/migrate/20250620213158_add_inventory_count_to_products.rb: -------------------------------------------------------------------------------- 1 | class AddInventoryCountToProducts < ActiveRecord::Migration[8.0] 2 | def change 3 | add_column :products, :inventory_count, :integer, default: 0, null: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | include Authentication 3 | # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has. 4 | allow_browser versions: :modern 5 | end 6 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/test/fixtures/users.yml: -------------------------------------------------------------------------------- 1 | <% password_digest = BCrypt::Password.create("password") %> 2 | 3 | one: 4 | email_address: one@example.com 5 | password_digest: <%= password_digest %> 6 | 7 | two: 8 | email_address: two@example.com 9 | password_digest: <%= password_digest %> 10 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/4-extracting-a-concern/_files/workspace/store/db/migrate/20250620213158_add_inventory_count_to_products.rb: -------------------------------------------------------------------------------- 1 | class AddInventoryCountToProducts < ActiveRecord::Migration[8.0] 2 | def change 3 | add_column :products, :inventory_count, :integer, default: 0, null: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/content/tutorial/13-internationalization/meta.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: part 3 | title: Internationalization 4 | editor: 5 | fileTree: 6 | allowEdits: "/workspace/store/**" 7 | prepareCommands: 8 | - ['npm install', 'Preparing Ruby runtime'] 9 | - ['node scripts/rails.js db:prepare', 'Prepare development database'] 10 | --- 11 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/3-in-stock-email-notifications/_files/workspace/store/db/migrate/20250620213158_add_inventory_count_to_products.rb: -------------------------------------------------------------------------------- 1 | class AddInventoryCountToProducts < ActiveRecord::Migration[8.0] 2 | def change 3 | add_column :products, :inventory_count, :integer, default: 0, null: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/3-in-stock-email-notifications/_files/workspace/store/test/fixtures/subscribers.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | product: one 5 | email: MyString 6 | 7 | two: 8 | product: two 9 | email: MyString 10 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/meta.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: part 3 | title: In Stock Notifications 4 | editor: 5 | fileTree: 6 | allowEdits: "/workspace/store/**" 7 | prepareCommands: 8 | - ['npm install', 'Preparing Ruby runtime'] 9 | - ['node scripts/rails.js db:prepare', 'Prepare development database'] 10 | --- 11 | -------------------------------------------------------------------------------- /src/content/tutorial/9-authentication/2-adding-log-out/_files/workspace/store/app/views/products/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= @product.name %>

2 | 3 | <%= link_to "Back", products_path %> 4 | <%= link_to "Edit", edit_product_path(@product) %> 5 | <%= button_to "Delete", @product, method: :delete, data: { turbo_confirm: "Are you sure?" } %> 6 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/app/javascript/controllers/application.js: -------------------------------------------------------------------------------- 1 | import { Application } from "@hotwired/stimulus" 2 | 3 | const application = Application.start() 4 | 5 | // Configure Stimulus development experience 6 | application.debug = false 7 | window.Stimulus = application 8 | 9 | export { application } 10 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/2-adding-subscribers/_files/workspace/store/app/models/product.rb: -------------------------------------------------------------------------------- 1 | class Product < ApplicationRecord 2 | has_one_attached :featured_image 3 | has_rich_text :description 4 | 5 | validates :name, presence: true 6 | validates :inventory_count, numericality: { greater_than_or_equal_to: 0 } 7 | end 8 | -------------------------------------------------------------------------------- /src/content/tutorial/9-authentication/3-unauthenticated-access/_files/workspace/store/app/views/products/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= @product.name %>

2 | 3 | <%= link_to "Back", products_path %> 4 | <%= link_to "Edit", edit_product_path(@product) %> 5 | <%= button_to "Delete", @product, method: :delete, data: { turbo_confirm: "Are you sure?" } %> 6 | -------------------------------------------------------------------------------- /src/content/tutorial/9-authentication/4-authenticated-links/_files/workspace/store/app/views/products/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= @product.name %>

2 | 3 | <%= link_to "Back", products_path %> 4 | <%= link_to "Edit", edit_product_path(@product) %> 5 | <%= button_to "Delete", @product, method: :delete, data: { turbo_confirm: "Are you sure?" } %> 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "compilerOptions": { 4 | "jsx": "react-jsx", 5 | "baseUrl": "./", 6 | "jsxImportSource": "react", 7 | "paths": { 8 | "@*": [ 9 | "src/*" 10 | ] 11 | } 12 | }, 13 | "exclude": [ 14 | "dist", 15 | "vendor" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/7-crud-edit/_files/workspace/store/app/views/products/new.html.erb: -------------------------------------------------------------------------------- 1 |

New product

2 | 3 | <%= form_with model: @product do |form| %> 4 |
5 | <%= form.label :name %> 6 | <%= form.text_field :name %> 7 |
8 | 9 |
10 | <%= form.submit %> 11 |
12 | <% end %> 13 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/app/views/products/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= @product.name %>

2 | 3 | <%= link_to "Back", products_path %> 4 | <% if authenticated? %> 5 | <%= link_to "Edit", edit_product_path(@product) %> 6 | <%= button_to "Delete", @product, method: :delete, data: { turbo_confirm: "Are you sure?" } %> 7 | <% end %> 8 | -------------------------------------------------------------------------------- /src/templates/crud-products/workspace/store/app/views/products/index.html.erb: -------------------------------------------------------------------------------- 1 |

Products

2 | 3 | <%= link_to "New product", new_product_path %> 4 | 5 |
6 | <% @products.each do |product| %> 7 |
8 | <%= link_to product.name, product_path(product.id) %> 9 |
10 | <% end %> 11 |
12 | -------------------------------------------------------------------------------- /src/templates/stock-notifications/workspace/store/app/models/product.rb: -------------------------------------------------------------------------------- 1 | class Product < ApplicationRecord 2 | include Notifications 3 | 4 | has_one_attached :featured_image 5 | has_rich_text :description 6 | 7 | validates :name, presence: true 8 | validates :inventory_count, numericality: { greater_than_or_equal_to: 0 } 9 | end 10 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/8-before-actions/_files/workspace/store/app/views/products/new.html.erb: -------------------------------------------------------------------------------- 1 |

New product

2 | 3 | <%= form_with model: @product do |form| %> 4 |
5 | <%= form.label :name %> 6 | <%= form.text_field :name %> 7 |
8 | 9 |
10 | <%= form.submit %> 11 |
12 | <% end %> 13 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/app/javascript/controllers/index.js: -------------------------------------------------------------------------------- 1 | // Import and register all your controllers from the importmap via controllers/**/*_controller 2 | import { application } from "controllers/application" 3 | import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" 4 | eagerLoadControllersFrom("controllers", application) 5 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/6-strong-parameters/_files/workspace/store/app/views/products/new.html.erb: -------------------------------------------------------------------------------- 1 |

New product

2 | 3 | <%= form_with model: @product do |form| %> 4 |
5 | <%= form.label :name %> 6 | <%= form.text_field :name %> 7 |
8 | 9 |
10 | <%= form.submit %> 11 |
12 | <% end %> 13 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/9-extracting-partials/_files/workspace/store/app/views/products/new.html.erb: -------------------------------------------------------------------------------- 1 |

New product

2 | 3 | <%= form_with model: @product do |form| %> 4 |
5 | <%= form.label :name %> 6 | <%= form.text_field :name %> 7 |
8 | 9 |
10 | <%= form.submit %> 11 |
12 | <% end %> 13 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | # Automatically retry jobs that encountered a deadlock 3 | # retry_on ActiveRecord::Deadlocked 4 | 5 | # Most jobs are safe to ignore if the underlying records are no longer available 6 | # discard_on ActiveJob::DeserializationError 7 | end 8 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/templates/stock-notifications/workspace/store/db/migrate/20250620214344_create_subscribers.rb: -------------------------------------------------------------------------------- 1 | class CreateSubscribers < ActiveRecord::Migration[8.0] 2 | def change 3 | create_table :subscribers do |t| 4 | t.belongs_to :product, null: false, foreign_key: true 5 | t.string :email 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/app/views/products/index.html.erb: -------------------------------------------------------------------------------- 1 |

Products

2 | 3 | <%= link_to "New product", new_product_path if authenticated? %> 4 | 5 |
6 | <% @products.each do |product| %> 7 |
8 | <%= link_to product.name, product_path(product.id) %> 9 |
10 | <% end %> 11 |
12 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/7-crud-edit/_files/workspace/store/app/views/products/index.html.erb: -------------------------------------------------------------------------------- 1 |

Products

2 | 3 | <%= link_to "New product", new_product_path %> 4 | 5 |
6 | <% @products.each do |product| %> 7 |
8 | <%= link_to product.name, product_path(product.id) %> 9 |
10 | <% end %> 11 |
12 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/10-crud-destroy/_files/workspace/store/app/views/products/index.html.erb: -------------------------------------------------------------------------------- 1 |

Products

2 | 3 | <%= link_to "New product", new_product_path %> 4 | 5 |
6 | <% @products.each do |product| %> 7 |
8 | <%= link_to product.name, product_path(product.id) %> 9 |
10 | <% end %> 11 |
12 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/8-before-actions/_files/workspace/store/app/views/products/index.html.erb: -------------------------------------------------------------------------------- 1 |

Products

2 | 3 | <%= link_to "New product", new_product_path %> 4 | 5 |
6 | <% @products.each do |product| %> 7 |
8 | <%= link_to product.name, product_path(product.id) %> 9 |
10 | <% end %> 11 |
12 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/5-unsubscribe-links/_files/workspace/store/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resource :session 3 | resources :passwords, param: :token 4 | resources :products do 5 | resources :subscribers, only: [ :create ] 6 | end 7 | 8 | # Defines the root path route ("/") 9 | root "products#index" 10 | end 11 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/6-strong-parameters/_files/workspace/store/app/views/products/index.html.erb: -------------------------------------------------------------------------------- 1 |

Products

2 | 3 | <%= link_to "New product", new_product_path %> 4 | 5 |
6 | <% @products.each do |product| %> 7 |
8 | <%= link_to product.name, product_path(product.id) %> 9 |
10 | <% end %> 11 |
12 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/9-extracting-partials/_files/workspace/store/app/views/products/index.html.erb: -------------------------------------------------------------------------------- 1 |

Products

2 | 3 | <%= link_to "New product", new_product_path %> 4 | 5 |
6 | <% @products.each do |product| %> 7 |
8 | <%= link_to product.name, product_path(product.id) %> 9 |
10 | <% end %> 11 |
12 | -------------------------------------------------------------------------------- /src/content/tutorial/9-authentication/2-adding-log-out/_files/workspace/store/app/views/products/index.html.erb: -------------------------------------------------------------------------------- 1 |

Products

2 | 3 | <%= link_to "New product", new_product_path %> 4 | 5 |
6 | <% @products.each do |product| %> 7 |
8 | <%= link_to product.name, product_path(product.id) %> 9 |
10 | <% end %> 11 |
12 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/db/migrate/20250618233539_create_sessions.rb: -------------------------------------------------------------------------------- 1 | class CreateSessions < ActiveRecord::Migration[8.0] 2 | def change 3 | create_table :sessions do |t| 4 | t.references :user, null: false, foreign_key: true 5 | t.string :ip_address 6 | t.string :user_agent 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/config/importmap.rb: -------------------------------------------------------------------------------- 1 | # Pin npm packages by running ./bin/importmap 2 | 3 | pin "application" 4 | 5 | pin "@hotwired/turbo-rails", to: "turbo.min.js" 6 | pin "@hotwired/stimulus", to: "stimulus.min.js" 7 | pin "@hotwired/stimulus-loading", to: "stimulus-loading.js" 8 | 9 | pin_all_from "app/javascript/controllers", under: "controllers" 10 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = "1.0" 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/4-extracting-a-concern/_files/workspace/store/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resource :session 3 | resources :passwords, param: :token 4 | resources :products do 5 | resources :subscribers, only: [ :create ] 6 | end 7 | 8 | # Defines the root path route ("/") 9 | root "products#index" 10 | end 11 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/5-unsubscribe-links/_files/workspace/store/app/models/product.rb: -------------------------------------------------------------------------------- 1 | class Product < ApplicationRecord 2 | include Notifications 3 | 4 | has_one_attached :featured_image 5 | has_rich_text :description 6 | 7 | validates :name, presence: true 8 | validates :inventory_count, numericality: { greater_than_or_equal_to: 0 } 9 | end 10 | -------------------------------------------------------------------------------- /src/content/tutorial/9-authentication/3-unauthenticated-access/_files/workspace/store/app/views/products/index.html.erb: -------------------------------------------------------------------------------- 1 |

Products

2 | 3 | <%= link_to "New product", new_product_path %> 4 | 5 |
6 | <% @products.each do |product| %> 7 |
8 | <%= link_to product.name, product_path(product.id) %> 9 |
10 | <% end %> 11 |
12 | -------------------------------------------------------------------------------- /src/content/tutorial/9-authentication/4-authenticated-links/_files/workspace/store/app/views/products/index.html.erb: -------------------------------------------------------------------------------- 1 |

Products

2 | 3 | <%= link_to "New product", new_product_path %> 4 | 5 |
6 | <% @products.each do |product| %> 7 |
8 | <%= link_to product.name, product_path(product.id) %> 9 |
10 | <% end %> 11 |
12 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/test/mailers/previews/passwords_mailer_preview.rb: -------------------------------------------------------------------------------- 1 | # Preview all emails at http://localhost:3000/rails/mailers/passwords_mailer 2 | class PasswordsMailerPreview < ActionMailer::Preview 3 | # Preview this email at http://localhost:3000/rails/mailers/passwords_mailer/reset 4 | def reset 5 | PasswordsMailer.reset(User.take) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/templates/stock-notifications/workspace/store/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resource :session 3 | resources :passwords, param: :token 4 | resources :products do 5 | resources :subscribers, only: [ :create ] 6 | end 7 | resource :unsubscribe, only: [ :show ] 8 | 9 | # Defines the root path route ("/") 10 | root "products#index" 11 | end 12 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/3-in-stock-email-notifications/_files/workspace/store/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resource :session 3 | resources :passwords, param: :token 4 | resources :products do 5 | resources :subscribers, only: [ :create ] 6 | end 7 | 8 | # Defines the root path route ("/") 9 | root "products#index" 10 | end 11 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/3-in-stock-email-notifications/_files/workspace/store/app/models/product.rb: -------------------------------------------------------------------------------- 1 | class Product < ApplicationRecord 2 | has_many :subscribers, dependent: :destroy 3 | has_one_attached :featured_image 4 | has_rich_text :description 5 | 6 | validates :name, presence: true 7 | validates :inventory_count, numericality: { greater_than_or_equal_to: 0 } 8 | end 9 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/5-unsubscribe-links/_files/workspace/store/db/migrate/20250620214344_create_subscribers.rb: -------------------------------------------------------------------------------- 1 | class CreateSubscribers < ActiveRecord::Migration[8.0] 2 | def change 3 | create_table :subscribers do |t| 4 | t.belongs_to :product, null: false, foreign_key: true 5 | t.string :email 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/db/migrate/20250618233538_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration[8.0] 2 | def change 3 | create_table :users do |t| 4 | t.string :email_address, null: false 5 | t.string :password_digest, null: false 6 | 7 | t.timestamps 8 | end 9 | add_index :users, :email_address, unique: true 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/4-extracting-a-concern/_files/workspace/store/db/migrate/20250620214344_create_subscribers.rb: -------------------------------------------------------------------------------- 1 | class CreateSubscribers < ActiveRecord::Migration[8.0] 2 | def change 3 | create_table :subscribers do |t| 4 | t.belongs_to :product, null: false, foreign_key: true 5 | t.string :email 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /src/templates/default/bin/rackup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --disable-warning=ExperimentalWarning --watch --watch-path=./ 2 | 3 | import initVM from "../lib/rails.js"; 4 | import { createRackServer } from "../lib/server.js"; 5 | 6 | const vm = await initVM(); 7 | const server = await createRackServer(vm); 8 | 9 | server.listen(3000, () => { 10 | console.log("Server started on port 3000"); 11 | }); 12 | -------------------------------------------------------------------------------- /src/templates/stock-notifications/workspace/store/app/mailers/product_mailer.rb: -------------------------------------------------------------------------------- 1 | class ProductMailer < ApplicationMailer 2 | # Subject can be set in your I18n file at config/locales/en.yml 3 | # with the following lookup: 4 | # 5 | # en.product_mailer.in_stock.subject 6 | # 7 | def in_stock 8 | @product = params[:product] 9 | mail to: params[:subscriber].email 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/3-in-stock-email-notifications/_files/workspace/store/db/migrate/20250620214344_create_subscribers.rb: -------------------------------------------------------------------------------- 1 | class CreateSubscribers < ActiveRecord::Migration[8.0] 2 | def change 3 | create_table :subscribers do |t| 4 | t.belongs_to :product, null: false, foreign_key: true 5 | t.string :email 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /src/content/tutorial/11-rich-text/1-action-text/_files/workspace/store/app/views/products/show.html.erb: -------------------------------------------------------------------------------- 1 | <% cache @product do %> 2 |

<%= @product.name %>

3 | <% end %> 4 | 5 | <%= link_to "Back", products_path %> 6 | <% if authenticated? %> 7 | <%= link_to "Edit", edit_product_path(@product) %> 8 | <%= button_to "Delete", @product, method: :delete, data: { turbo_confirm: "Are you sure?" } %> 9 | <% end %> 10 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # Create products that we should have created in the previous lessons 2 | Product.create(name: "T-Shirt").destroy 3 | Product.create(name: "Pants") 4 | 5 | # For authentication 6 | User.create!( 7 | email_address: "you@example.org", 8 | password: "s3cr3t", 9 | password_confirmation: "s3cr3t" 10 | ) unless User.where(email_address: "you@example.org").exists? 11 | -------------------------------------------------------------------------------- /rails-wasm/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "rails", "~> 8.0.0" 4 | gem "wasmify-rails", "~> 0.4.0" 5 | 6 | # rails new gems 7 | gem "propshaft" 8 | gem "importmap-rails" 9 | gem "turbo-rails" 10 | gem "stimulus-rails" 11 | gem "jbuilder" 12 | 13 | gem "bcrypt", "~> 3.1.7" 14 | 15 | gem "solid_cache" 16 | gem "solid_queue" 17 | gem "solid_cable" 18 | 19 | gem "image_processing", "~> 1.2" 20 | 21 | gem "tzinfo-data" 22 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/5-unsubscribe-links/_files/workspace/store/app/mailers/product_mailer.rb: -------------------------------------------------------------------------------- 1 | class ProductMailer < ApplicationMailer 2 | # Subject can be set in your I18n file at config/locales/en.yml 3 | # with the following lookup: 4 | # 5 | # en.product_mailer.in_stock.subject 6 | # 7 | def in_stock 8 | @product = params[:product] 9 | mail to: params[:subscriber].email 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/4-extracting-a-concern/_files/workspace/store/app/mailers/product_mailer.rb: -------------------------------------------------------------------------------- 1 | class ProductMailer < ApplicationMailer 2 | # Subject can be set in your I18n file at config/locales/en.yml 3 | # with the following lookup: 4 | # 5 | # en.product_mailer.in_stock.subject 6 | # 7 | def in_stock 8 | @product = params[:product] 9 | mail to: params[:subscriber].email 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/templates/stock-notifications/workspace/store/test/mailers/product_mailer_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ProductMailerTest < ActionMailer::TestCase 4 | test "in_stock" do 5 | mail = ProductMailer.in_stock 6 | assert_equal "In stock", mail.subject 7 | assert_equal [ "to@example.org" ], mail.to 8 | assert_equal [ "from@example.com" ], mail.from 9 | assert_match "Hi", mail.body.encoded 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/content/tutorial/meta.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: tutorial 3 | openInStackBlitz: false 4 | prepareCommands: 5 | - ['npm install', 'Preparing Ruby runtime'] 6 | previews: false 7 | filesystem: 8 | watch: ['/*.json', '/workspace/**/*'] 9 | terminal: 10 | open: true 11 | activePanel: 0 12 | panels: 13 | - type: terminal 14 | id: 'cmds' 15 | title: 'Command Line' 16 | allowRedirects: true 17 | - ['output', 'Setup Logs'] 18 | --- 19 | -------------------------------------------------------------------------------- /src/templates/active-storage/workspace/store/config/importmap.rb: -------------------------------------------------------------------------------- 1 | # Pin npm packages by running ./bin/importmap 2 | 3 | pin "application" 4 | 5 | pin "@hotwired/turbo-rails", to: "turbo.min.js" 6 | pin "@hotwired/stimulus", to: "stimulus.min.js" 7 | pin "@hotwired/stimulus-loading", to: "stimulus-loading.js" 8 | 9 | pin_all_from "app/javascript/controllers", under: "controllers" 10 | 11 | pin "trix" 12 | pin "@rails/actiontext", to: "actiontext.esm.js" 13 | -------------------------------------------------------------------------------- /icons/languages/js.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/content/tutorial/12-file-uploads/1-active-storage/_files/workspace/store/app/views/products/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with model: product do |form| %> 2 |
3 | <%= form.label :name %> 4 | <%= form.text_field :name %> 5 |
6 | 7 |
8 | <%= form.label :description, style: "display: block" %> 9 | <%= form.rich_textarea :description %> 10 |
11 | 12 |
13 | <%= form.submit %> 14 |
15 | <% end %> 16 | -------------------------------------------------------------------------------- /src/content/tutorial/12-file-uploads/1-active-storage/_files/workspace/store/app/views/products/show.html.erb: -------------------------------------------------------------------------------- 1 | <% cache @product do %> 2 |

<%= @product.name %>

3 | <%= @product.description %> 4 | <% end %> 5 | 6 | <%= link_to "Back", products_path %> 7 | <% if authenticated? %> 8 | <%= link_to "Edit", edit_product_path(@product) %> 9 | <%= button_to "Delete", @product, method: :delete, data: { turbo_confirm: "Are you sure?" } %> 10 | <% end %> 11 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/app/views/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 |

Forgot your password?

2 | 3 | <%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %> 4 | 5 | <%= form_with url: passwords_path do |form| %> 6 | <%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email_address] %>
7 | <%= form.submit "Email reset instructions" %> 8 | <% end %> 9 | -------------------------------------------------------------------------------- /src/templates/testing/workspace/store/test/models/product_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ProductTest < ActiveSupport::TestCase 4 | include ActionMailer::TestHelper 5 | 6 | test "sends email notifications when back in stock" do 7 | product = products(:tshirt) 8 | 9 | # Set product out of stock 10 | product.update(inventory_count: 0) 11 | 12 | assert_emails 2 do 13 | product.update(inventory_count: 99) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /src/templates/stock-notifications/workspace/store/app/views/products/_inventory.html.erb: -------------------------------------------------------------------------------- 1 | <% if product.inventory_count? %> 2 |

<%= product.inventory_count %> in stock

3 | <% else %> 4 |

Out of stock

5 |

Email me when available.

6 | 7 | <%= form_with model: [product, Subscriber.new] do |form| %> 8 | <%= form.email_field :email, placeholder: "you@example.com", required: true %> 9 | <%= form.submit "Submit" %> 10 | <% end %> 11 | <% end %> 12 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/4-extracting-a-concern/_files/workspace/store/test/mailers/product_mailer_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ProductMailerTest < ActionMailer::TestCase 4 | test "in_stock" do 5 | mail = ProductMailer.in_stock 6 | assert_equal "In stock", mail.subject 7 | assert_equal [ "to@example.org" ], mail.to 8 | assert_equal [ "from@example.com" ], mail.from 9 | assert_match "Hi", mail.body.encoded 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/5-unsubscribe-links/_files/workspace/store/test/mailers/product_mailer_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ProductMailerTest < ActionMailer::TestCase 4 | test "in_stock" do 5 | mail = ProductMailer.in_stock 6 | assert_equal "In stock", mail.subject 7 | assert_equal [ "to@example.org" ], mail.to 8 | assert_equal [ "from@example.com" ], mail.from 9 | assert_match "Hi", mail.body.encoded 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/templates/stock-notifications/workspace/store/app/controllers/unsubscribes_controller.rb: -------------------------------------------------------------------------------- 1 | class UnsubscribesController < ApplicationController 2 | allow_unauthenticated_access 3 | before_action :set_subscriber 4 | 5 | def show 6 | @subscriber&.destroy 7 | redirect_to root_path, notice: "Unsubscribed successfully." 8 | end 9 | 10 | private 11 | 12 | def set_subscriber 13 | @subscriber = Subscriber.find_by_token_for(:unsubscribe, params[:token]) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /rails-wasm/bin/pack: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | 5 | require "fileutils" 6 | require "wasmify/rails/builder" 7 | 8 | builder = Wasmify::Rails::Builder.new 9 | builder.run(name: "ruby.wasm") 10 | 11 | require "wasmify/rails/packer" 12 | 13 | output_dir = ARGV[0] ? ARGV[0] : Wasmify::Rails.config.output_dir 14 | 15 | packer = Wasmify::Rails::Packer.new(output_dir:) 16 | packer.run(name: "rails.wasm", ruby_wasm_path: File.join(Wasmify::Rails.config.tmp_dir, "ruby.wasm")) 17 | -------------------------------------------------------------------------------- /src/templates/testing/workspace/store/test/mailers/product_mailer_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ProductMailerTest < ActionMailer::TestCase 4 | test "in_stock" do 5 | mail = ProductMailer.with(product: products(:tshirt), subscriber: subscribers(:david)).in_stock 6 | assert_equal "In stock", mail.subject 7 | assert_equal [ "david@example.org" ], mail.to 8 | assert_equal [ "from@example.com" ], mail.from 9 | assert_match "Good news!", mail.body.encoded 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/templates/active-storage/workspace/store/app/views/products/show.html.erb: -------------------------------------------------------------------------------- 1 | <% cache @product do %> 2 | <%= image_tag @product.featured_image if @product.featured_image.attached? %> 3 |

<%= @product.name %>

4 | <%= @product.description %> 5 | <% end %> 6 | 7 | <%= link_to "Back", products_path %> 8 | <% if authenticated? %> 9 | <%= link_to "Edit", edit_product_path(@product) %> 10 | <%= button_to "Delete", @product, method: :delete, data: { turbo_confirm: "Are you sure?" } %> 11 | <% end %> 12 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/4-extracting-a-concern/_files/workspace/store/app/views/products/_inventory.html.erb: -------------------------------------------------------------------------------- 1 | <% if product.inventory_count? %> 2 |

<%= product.inventory_count %> in stock

3 | <% else %> 4 |

Out of stock

5 |

Email me when available.

6 | 7 | <%= form_with model: [product, Subscriber.new] do |form| %> 8 | <%= form.email_field :email, placeholder: "you@example.com", required: true %> 9 | <%= form.submit "Submit" %> 10 | <% end %> 11 | <% end %> 12 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/5-unsubscribe-links/_files/workspace/store/app/views/products/_inventory.html.erb: -------------------------------------------------------------------------------- 1 | <% if product.inventory_count? %> 2 |

<%= product.inventory_count %> in stock

3 | <% else %> 4 |

Out of stock

5 |

Email me when available.

6 | 7 | <%= form_with model: [product, Subscriber.new] do |form| %> 8 | <%= form.email_field :email, placeholder: "you@example.com", required: true %> 9 | <%= form.submit "Submit" %> 10 | <% end %> 11 | <% end %> 12 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/3-in-stock-email-notifications/_files/workspace/store/app/views/products/_inventory.html.erb: -------------------------------------------------------------------------------- 1 | <% if product.inventory_count? %> 2 |

<%= product.inventory_count %> in stock

3 | <% else %> 4 |

Out of stock

5 |

Email me when available.

6 | 7 | <%= form_with model: [product, Subscriber.new] do |form| %> 8 | <%= form.email_field :email, placeholder: "you@example.com", required: true %> 9 | <%= form.submit "Submit" %> 10 | <% end %> 11 | <% end %> 12 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | identified_by :current_user 4 | 5 | def connect 6 | set_current_user || reject_unauthorized_connection 7 | end 8 | 9 | private 10 | def set_current_user 11 | if session = Session.find_by(id: cookies.signed[:session_id]) 12 | self.current_user = session.user 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | This README would normally document whatever steps are necessary to get the 4 | application up and running. 5 | 6 | Things you may want to cover: 7 | 8 | * Ruby version 9 | 10 | * System dependencies 11 | 12 | * Configuration 13 | 14 | * Database creation 15 | 16 | * Database initialization 17 | 18 | * How to run the test suite 19 | 20 | * Services (job queues, cache servers, search engines, etc.) 21 | 22 | * Deployment instructions 23 | 24 | * ... 25 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] ||= "test" 2 | require_relative "../config/environment" 3 | require "rails/test_help" 4 | 5 | module ActiveSupport 6 | class TestCase 7 | # Run tests in parallel with specified workers 8 | parallelize(workers: :number_of_processors) 9 | 10 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 11 | fixtures :all 12 | 13 | # Add more helper methods to be used by all tests here... 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /fly.toml: -------------------------------------------------------------------------------- 1 | # fly.toml app configuration file generated for tutorialkit-rb-playground on 2025-05-02T17:18:25-07:00 2 | # 3 | # See https://fly.io/docs/reference/configuration/ for information about how to use this file. 4 | # 5 | 6 | app = 'tutorialkit-rb-playground' 7 | primary_region = 'sea' 8 | 9 | [build] 10 | 11 | [http_service] 12 | internal_port = 80 13 | force_https = true 14 | auto_stop_machines = 'stop' 15 | auto_start_machines = true 16 | min_machines_running = 0 17 | processes = ['app'] 18 | 19 | [[vm]] 20 | size = 'shared-cpu-1x' 21 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/app/views/pwa/manifest.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Store", 3 | "icons": [ 4 | { 5 | "src": "/icon.png", 6 | "type": "image/png", 7 | "sizes": "512x512" 8 | }, 9 | { 10 | "src": "/icon.png", 11 | "type": "image/png", 12 | "sizes": "512x512", 13 | "purpose": "maskable" 14 | } 15 | ], 16 | "start_url": "/", 17 | "display": "standalone", 18 | "scope": "/", 19 | "description": "Store.", 20 | "theme_color": "red", 21 | "background_color": "red" 22 | } 23 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. 4 | # Use this to limit dissemination of sensitive information. 5 | # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. 6 | Rails.application.config.filter_parameters += [ 7 | :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc 8 | ] 9 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should ensure the existence of records required to run the application in every environment (production, 2 | # development, test). The code here should be idempotent so that it can be executed at any point in every environment. 3 | # The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). 4 | # 5 | # Example: 6 | # 7 | # ["Action", "Comedy", "Drama", "Horror"].each do |genre_name| 8 | # MovieGenre.find_or_create_by!(name: genre_name) 9 | # end 10 | -------------------------------------------------------------------------------- /src/templates/stock-notifications/workspace/store/app/views/products/show.html.erb: -------------------------------------------------------------------------------- 1 | <% cache @product do %> 2 | <%= image_tag @product.featured_image if @product.featured_image.attached? %> 3 |

<%= @product.name %>

4 | <%= @product.description %> 5 | <% end %> 6 | 7 | <%= render "inventory", product: @product %> 8 | 9 | <%= link_to "Back", products_path %> 10 | <% if authenticated? %> 11 | <%= link_to "Edit", edit_product_path(@product) %> 12 | <%= button_to "Delete", @product, method: :delete, data: { turbo_confirm: "Are you sure?" } %> 13 | <% end %> 14 | -------------------------------------------------------------------------------- /icons/languages/ts.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/templates/stock-notifications/workspace/store/app/controllers/subscribers_controller.rb: -------------------------------------------------------------------------------- 1 | class SubscribersController < ApplicationController 2 | allow_unauthenticated_access 3 | before_action :set_product 4 | 5 | def create 6 | @product.subscribers.where(subscriber_params).first_or_create 7 | redirect_to @product, notice: "You are now subscribed." 8 | end 9 | 10 | private 11 | 12 | def set_product 13 | @product = Product.find(params[:product_id]) 14 | end 15 | 16 | def subscriber_params 17 | params.expect(subscriber: [ :email ]) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /src/templates/active-storage/workspace/store/app/views/products/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with model: product do |form| %> 2 |
3 | <%= form.label :name %> 4 | <%= form.text_field :name %> 5 |
6 | 7 |
8 | <%= form.label :description, style: "display: block" %> 9 | <%= form.rich_textarea :description %> 10 |
11 | 12 |
13 | <%= form.label :featured_image, style: "display: block" %> 14 | <%= form.file_field :featured_image, accept: "image/*" %> 15 |
16 | 17 |
18 | <%= form.submit %> 19 |
20 | <% end %> 21 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | +opdmbbIhKjrVxzwTcULNjyc72D92lPMBrS3ITE0htdpvlwr4KZMIzfSPrPhcmpuHtKF3xNkhEW3/MUuQJb+BQ0++W/wmTzP/CJHaiYkidd0a6arzdXLqxKfkTkc3ew6OUCoNUAyAoVJ+ASr/k655xTm77rvSydr0f3syWdXpa/AOzopeSOVuWxr4k0QnSbeOd1/BGVY5p/FzpakfKGlc5TVXTpkvkWqC3z5w0pNJnfRQAxSp0i/0xIk0y9OxJRU3E4Yyu20+O5NQSW4O+kgI6LJEHu1LCrZCXzVO6kz8BktmGajU72pXAaM56oKMvaOWUf+XxJdKC2+6pLMlIw2apkeXA3Gxc0Afl+hSrx62JeqhExbSXccWk9KVgvTHDBnaCgJu/VMCukQELyMqb/Tw9TPjnyMr2WV8v66QPs2gQLg+kGMOZlW6R6ubu0i2BBV+rxQkn1vPOrsgitbK9B6PtPWEgmTdBiu3QcVDFUIomQLTrGqPkkgxReM--H5GpUcrVpHme6TEY--KUIefLaLDjHc8MAEP0yeng== -------------------------------------------------------------------------------- /.github/workflows/fly-deploy.yml: -------------------------------------------------------------------------------- 1 | # See https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/ 2 | 3 | name: Fly Deploy 4 | on: 5 | push: 6 | branches: 7 | - master 8 | jobs: 9 | deploy: 10 | name: Deploy app 11 | runs-on: ubuntu-latest 12 | concurrency: deploy-group # optional: ensure only one action runs at a time 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: superfly/flyctl-actions/setup-flyctl@master 16 | - run: flyctl deploy --remote-only 17 | env: 18 | FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} 19 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css. 3 | * 4 | * With Propshaft, assets are served efficiently without preprocessing steps. You can still include 5 | * application-wide styles in this file, but keep in mind that CSS precedence will follow the standard 6 | * cascading order, meaning styles declared later in the document or manifest will override earlier ones, 7 | * depending on specificity. 8 | * 9 | * Consider organizing styles into separate files for maintainability. 10 | */ 11 | -------------------------------------------------------------------------------- /astro.config.ts: -------------------------------------------------------------------------------- 1 | import tutorialkit from '@tutorialkit/astro'; 2 | import { defineConfig } from 'astro/config'; 3 | import remarkRailsPathLinks from './src/plugins/remarkRailsPathLinks'; 4 | 5 | export default defineConfig({ 6 | devToolbar: { 7 | enabled: false, 8 | }, 9 | integrations: [ 10 | tutorialkit({ 11 | components: { 12 | TopBar: "./src/components/TopBar.astro", 13 | HeadTags: "./src/components/HeadTags.astro" 14 | }, 15 | defaultRoutes: true 16 | }) 17 | ], 18 | markdown: { 19 | remarkPlugins: [remarkRailsPathLinks], 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/5-unsubscribe-links/_files/workspace/store/app/views/products/show.html.erb: -------------------------------------------------------------------------------- 1 | <% cache @product do %> 2 | <%= image_tag @product.featured_image if @product.featured_image.attached? %> 3 |

<%= @product.name %>

4 | <%= @product.description %> 5 | <% end %> 6 | 7 | <%= render "inventory", product: @product %> 8 | 9 | <%= link_to "Back", products_path %> 10 | <% if authenticated? %> 11 | <%= link_to "Edit", edit_product_path(@product) %> 12 | <%= button_to "Delete", @product, method: :delete, data: { turbo_confirm: "Are you sure?" } %> 13 | <% end %> 14 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/app/views/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Update your password

2 | 3 | <%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %> 4 | 5 | <%= form_with url: password_path(params[:token]), method: :put do |form| %> 6 | <%= form.password_field :password, required: true, autocomplete: "new-password", placeholder: "Enter new password", maxlength: 72 %>
7 | <%= form.password_field :password_confirmation, required: true, autocomplete: "new-password", placeholder: "Repeat new password", maxlength: 72 %>
8 | <%= form.submit "Save" %> 9 | <% end %> 10 | -------------------------------------------------------------------------------- /src/templates/stock-notifications/workspace/store/app/models/product/notifications.rb: -------------------------------------------------------------------------------- 1 | module Product::Notifications 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | has_many :subscribers, dependent: :destroy 6 | after_update_commit :notify_subscribers, if: :back_in_stock? 7 | end 8 | 9 | def back_in_stock? 10 | inventory_count_previously_was&.zero? && inventory_count > 0 11 | end 12 | 13 | def notify_subscribers 14 | subscribers.each do |subscriber| 15 | ProductMailer.with(product: self, subscriber: subscriber).in_stock.deliver_later 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/4-extracting-a-concern/_files/workspace/store/app/views/products/show.html.erb: -------------------------------------------------------------------------------- 1 | <% cache @product do %> 2 | <%= image_tag @product.featured_image if @product.featured_image.attached? %> 3 |

<%= @product.name %>

4 | <%= @product.description %> 5 | <% end %> 6 | 7 | <%= render "inventory", product: @product %> 8 | 9 | <%= link_to "Back", products_path %> 10 | <% if authenticated? %> 11 | <%= link_to "Edit", edit_product_path(@product) %> 12 | <%= button_to "Delete", @product, method: :delete, data: { turbo_confirm: "Are you sure?" } %> 13 | <% end %> 14 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/3-in-stock-email-notifications/_files/workspace/store/app/views/products/show.html.erb: -------------------------------------------------------------------------------- 1 | <% cache @product do %> 2 | <%= image_tag @product.featured_image if @product.featured_image.attached? %> 3 |

<%= @product.name %>

4 | <%= @product.description %> 5 | <% end %> 6 | 7 | <%= render "inventory", product: @product %> 8 | 9 | <%= link_to "Back", products_path %> 10 | <% if authenticated? %> 11 | <%= link_to "Edit", edit_product_path(@product) %> 12 | <%= button_to "Delete", @product, method: :delete, data: { turbo_confirm: "Are you sure?" } %> 13 | <% end %> 14 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/5-unsubscribe-links/_files/workspace/store/app/controllers/subscribers_controller.rb: -------------------------------------------------------------------------------- 1 | class SubscribersController < ApplicationController 2 | allow_unauthenticated_access 3 | before_action :set_product 4 | 5 | def create 6 | @product.subscribers.where(subscriber_params).first_or_create 7 | redirect_to @product, notice: "You are now subscribed." 8 | end 9 | 10 | private 11 | 12 | def set_product 13 | @product = Product.find(params[:product_id]) 14 | end 15 | 16 | def subscriber_params 17 | params.expect(subscriber: [ :email ]) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/4-extracting-a-concern/_files/workspace/store/app/controllers/subscribers_controller.rb: -------------------------------------------------------------------------------- 1 | class SubscribersController < ApplicationController 2 | allow_unauthenticated_access 3 | before_action :set_product 4 | 5 | def create 6 | @product.subscribers.where(subscriber_params).first_or_create 7 | redirect_to @product, notice: "You are now subscribed." 8 | end 9 | 10 | private 11 | 12 | def set_product 13 | @product = Product.find(params[:product_id]) 14 | end 15 | 16 | def subscriber_params 17 | params.expect(subscriber: [ :email ]) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /rails-wasm/README.md: -------------------------------------------------------------------------------- 1 | # @rails-tutorial-wasm NPM package 2 | 3 | To create a `rails.wasm` module with Ruby and Rails included, run the following commands: 4 | 5 | ```sh 6 | bundle install 7 | bin/pack 8 | ``` 9 | 10 | Then, you can publish the NPM package containing the `rails.wasm` file as follows: 11 | 12 | ```sh 13 | npm publish 14 | ``` 15 | 16 | ## Prerequisites 17 | 18 | To build and pack Ruby Wasm modules, you need the following: 19 | 20 | - Rust toolchain: 21 | 22 | ```sh 23 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 24 | ``` 25 | 26 | - [wasi-vfs](https://github.com/kateinoigakukun/wasi-vfs) 27 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/3-in-stock-email-notifications/_files/workspace/store/app/controllers/subscribers_controller.rb: -------------------------------------------------------------------------------- 1 | class SubscribersController < ApplicationController 2 | allow_unauthenticated_access 3 | before_action :set_product 4 | 5 | def create 6 | @product.subscribers.where(subscriber_params).first_or_create 7 | redirect_to @product, notice: "You are now subscribed." 8 | end 9 | 10 | private 11 | 12 | def set_product 13 | @product = Product.find(params[:product_id]) 14 | end 15 | 16 | def subscriber_params 17 | params.expect(subscriber: [ :email ]) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/5-unsubscribe-links/_files/workspace/store/app/models/product/notifications.rb: -------------------------------------------------------------------------------- 1 | module Product::Notifications 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | has_many :subscribers, dependent: :destroy 6 | after_update_commit :notify_subscribers, if: :back_in_stock? 7 | end 8 | 9 | def back_in_stock? 10 | inventory_count_previously_was&.zero? && inventory_count > 0 11 | end 12 | 13 | def notify_subscribers 14 | subscribers.each do |subscriber| 15 | ProductMailer.with(product: self, subscriber: subscriber).in_stock.deliver_later 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /rails-wasm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rails-tutorial/wasm", 3 | "version": "8.0.2-rc.1", 4 | "description": "Prebuilt Wasm module with Ruby 3.3, Rails and default Rails gems", 5 | "exports": { 6 | "./dist/rails.wasm": "./dist/rails.wasm" 7 | }, 8 | "files": [ 9 | "dist", 10 | "README.md" 11 | ], 12 | "repository": "https://github.com/palkan/rails-tutorial-on-wasm", 13 | "homepage": "https://github.com/palkan/rails-tutorial-on-wasm", 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "keywords": [ 18 | "wasm", 19 | "webassembly", 20 | "rails" 21 | ], 22 | "license": "MIT", 23 | "dependencies": {} 24 | } 25 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/7-crud-edit/_files/workspace/store/app/controllers/products_controller.rb: -------------------------------------------------------------------------------- 1 | class ProductsController < ApplicationController 2 | def index 3 | @products = Product.all 4 | end 5 | 6 | def show 7 | @product = Product.find(params[:id]) 8 | end 9 | 10 | def new 11 | @product = Product.new 12 | end 13 | 14 | def create 15 | @product = Product.new(product_params) 16 | if @product.save 17 | redirect_to @product 18 | else 19 | render :new, status: :unprocessable_entity 20 | end 21 | end 22 | 23 | private 24 | def product_params 25 | params.expect(product: [ :name ]) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/6-strong-parameters/_files/workspace/store/app/controllers/products_controller.rb: -------------------------------------------------------------------------------- 1 | class ProductsController < ApplicationController 2 | def index 3 | @products = Product.all 4 | end 5 | 6 | def show 7 | @product = Product.find(params[:id]) 8 | end 9 | 10 | def new 11 | @product = Product.new 12 | end 13 | 14 | def create 15 | @product = Product.new(product_params) 16 | if @product.save 17 | redirect_to @product 18 | else 19 | render :new, status: :unprocessable_entity 20 | end 21 | end 22 | 23 | private 24 | def product_params 25 | params.expect(product: [ :name ]) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /rails-wasm/config/wasmify.yml: -------------------------------------------------------------------------------- 1 | output_dir: "./dist" 2 | ruby_version: "3.3" 3 | 4 | # Specify the list of directories to be included into the final 5 | # app.wasm file. 6 | pack_directories: [] 7 | pack_root: "/rails-vm" 8 | additional_root_files: 9 | - boot.rb 10 | - .railsrc 11 | 12 | # Specify the list of gems to skip during the base module compiliation. 13 | # Usually, you want to specify the gems with native extensions that are 14 | # not currently Wasm-compatible. 15 | exclude_gems: 16 | - nio4r 17 | - io-console 18 | - psych 19 | 20 | # Skip building native extensions for the following gems (and keep their Ruby source) 21 | ignore_gem_extensions: 22 | - bigdecimal 23 | - date 24 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/app/views/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %> 2 | <%= tag.div(flash[:notice], style: "color:green") if flash[:notice] %> 3 | 4 | <%= form_with url: session_path do |form| %> 5 | <%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email_address] %>
6 | <%= form.password_field :password, required: true, autocomplete: "current-password", placeholder: "Enter your password", maxlength: 72 %>
7 | <%= form.submit "Sign in" %> 8 | <% end %> 9 |
10 | 11 | <%= link_to "Forgot password?", new_password_path %> 12 | -------------------------------------------------------------------------------- /src/templates/css-js/workspace/store/app/views/products/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= link_to "Back", products_path %>

2 | 3 |
4 | <%= image_tag @product.featured_image if @product.featured_image.attached? %> 5 | 6 |
7 | <% cache @product do %> 8 |

<%= @product.name %>

9 | <%= @product.description %> 10 | <% end %> 11 | 12 | <%= render "inventory", product: @product %> 13 | 14 | <% if authenticated? %> 15 | <%= link_to "Edit", edit_product_path(@product) %> 16 | <%= button_to "Delete", @product, method: :delete, data: { turbo_confirm: "Are you sure?" } %> 17 | <% end %> 18 |
19 |
20 | -------------------------------------------------------------------------------- /patches/@tutorialkit+theme+1.5.0.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/@tutorialkit/theme/dist/index.js b/node_modules/@tutorialkit/theme/dist/index.js 2 | index e6669fb..3939937 100644 3 | --- a/node_modules/@tutorialkit/theme/dist/index.js 4 | +++ b/node_modules/@tutorialkit/theme/dist/index.js 5 | @@ -52,7 +52,7 @@ export function getInlineContentForPackage({ name, pattern, root }) { 6 | } 7 | } 8 | function readCustomIcons() { 9 | - const iconPaths = globSync('./icons/languages/*.svg'); 10 | + const iconPaths = globSync('./icons/**/*.svg'); 11 | return iconPaths.reduce((acc, iconPath) => { 12 | const collectionName = basename(dirname(iconPath)); 13 | const [iconName] = basename(iconPath).split('.'); 14 | -------------------------------------------------------------------------------- /src/templates/css-js/workspace/store/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, Helvetica, sans-serif; 3 | padding: 1rem; 4 | } 5 | 6 | nav { 7 | justify-content: flex-end; 8 | display: flex; 9 | font-size: 0.875em; 10 | gap: 0.5rem; 11 | max-width: 1024px; 12 | margin: 0 auto; 13 | padding: 1rem; 14 | } 15 | 16 | nav a { 17 | display: inline-block; 18 | } 19 | 20 | main { 21 | max-width: 1024px; 22 | margin: 0 auto; 23 | } 24 | 25 | .notice { 26 | color: green; 27 | } 28 | 29 | section.product { 30 | display: flex; 31 | gap: 1rem; 32 | flex-direction: row; 33 | } 34 | 35 | section.product img { 36 | border-radius: 8px; 37 | flex-basis: 50%; 38 | max-width: 50%; 39 | } 40 | -------------------------------------------------------------------------------- /src/content/tutorial/15-css-and-js/3-hotwire/_files/workspace/store/app/views/products/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= link_to "Back", products_path %>

2 | 3 |
4 | <%= image_tag @product.featured_image if @product.featured_image.attached? %> 5 | 6 |
7 | <% cache @product do %> 8 |

<%= @product.name %>

9 | <%= @product.description %> 10 | <% end %> 11 | 12 | <%= render "inventory", product: @product %> 13 | 14 | <% if authenticated? %> 15 | <%= link_to "Edit", edit_product_path(@product) %> 16 | <%= button_to "Delete", @product, method: :delete, data: { turbo_confirm: "Are you sure?" } %> 17 | <% end %> 18 |
19 |
20 | -------------------------------------------------------------------------------- /src/templates/active-storage/workspace/store/app/views/active_storage/blobs/_blob.html.erb: -------------------------------------------------------------------------------- 1 |
attachment--<%= blob.filename.extension %>"> 2 | <% if blob.representable? %> 3 | <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %> 4 | <% end %> 5 | 6 |
7 | <% if caption = blob.try(:caption) %> 8 | <%= caption %> 9 | <% else %> 10 | <%= blob.filename %> 11 | <%= number_to_human_size blob.byte_size %> 12 | <% end %> 13 |
14 |
15 | -------------------------------------------------------------------------------- /src/content/tutorial/15-css-and-js/2-importmap/_files/workspace/store/app/views/products/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= link_to "Back", products_path %>

2 | 3 |
4 | <%= image_tag @product.featured_image if @product.featured_image.attached? %> 5 | 6 |
7 | <% cache @product do %> 8 |

<%= @product.name %>

9 | <%= @product.description %> 10 | <% end %> 11 | 12 | <%= render "inventory", product: @product %> 13 | 14 | <% if authenticated? %> 15 | <%= link_to "Edit", edit_product_path(@product) %> 16 | <%= button_to "Delete", @product, method: :delete, data: { turbo_confirm: "Are you sure?" } %> 17 | <% end %> 18 |
19 |
20 | -------------------------------------------------------------------------------- /src/templates/stock-notifications/workspace/store/app/views/products/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with model: product do |form| %> 2 |
3 | <%= form.label :name %> 4 | <%= form.text_field :name %> 5 |
6 | 7 |
8 | <%= form.label :description, style: "display: block" %> 9 | <%= form.rich_textarea :description %> 10 |
11 | 12 |
13 | <%= form.label :featured_image, style: "display: block" %> 14 | <%= form.file_field :featured_image, accept: "image/*" %> 15 |
16 | 17 |
18 | <%= form.label :inventory_count, style: "display: block" %> 19 | <%= form.number_field :inventory_count %> 20 |
21 | 22 |
23 | <%= form.submit %> 24 |
25 | <% end %> 26 | -------------------------------------------------------------------------------- /icons/languages/json.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/templates/default/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-project", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "test": "node --disable-warning=ExperimentalWarning ./scripts/test.js", 9 | "test:watch": "node --disable-warning=ExperimentalWarning --watch --watch-path=./workspace ./scripts/test.js", 10 | "postinstall": "chmod +x bin/*" 11 | }, 12 | "dependencies": { 13 | "@electric-sql/pglite": "^0.3.1", 14 | "@ruby/wasm-wasi": "^2.7.1", 15 | "@rails-tutorial/wasm": "^8.0.2-rc.1", 16 | "express": "^5.1.0", 17 | "multer": "^2.0.1", 18 | "set-cookie-parser": "^2.7.1" 19 | }, 20 | "devDependencies": { 21 | "vite": "^5.2.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class SessionsController < ApplicationController 2 | allow_unauthenticated_access only: %i[ new create ] 3 | rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_session_url, alert: "Try again later." } 4 | 5 | def new 6 | end 7 | 8 | def create 9 | if user = User.authenticate_by(params.permit(:email_address, :password)) 10 | start_new_session_for user 11 | redirect_to after_authentication_url 12 | else 13 | redirect_to new_session_path, alert: "Try another email address or password." 14 | end 15 | end 16 | 17 | def destroy 18 | terminate_session 19 | redirect_to new_session_path 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /src/content/tutorial/15-css-and-js/3-hotwire/_files/workspace/store/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, Helvetica, sans-serif; 3 | padding: 1rem; 4 | } 5 | 6 | nav { 7 | justify-content: flex-end; 8 | display: flex; 9 | font-size: 0.875em; 10 | gap: 0.5rem; 11 | max-width: 1024px; 12 | margin: 0 auto; 13 | padding: 1rem; 14 | } 15 | 16 | nav a { 17 | display: inline-block; 18 | } 19 | 20 | main { 21 | max-width: 1024px; 22 | margin: 0 auto; 23 | } 24 | 25 | .notice { 26 | color: green; 27 | } 28 | 29 | section.product { 30 | display: flex; 31 | gap: 1rem; 32 | flex-direction: row; 33 | } 34 | 35 | section.product img { 36 | border-radius: 8px; 37 | flex-basis: 50%; 38 | max-width: 50%; 39 | } 40 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/4-extracting-a-concern/_files/workspace/store/app/models/product.rb: -------------------------------------------------------------------------------- 1 | class Product < ApplicationRecord 2 | has_many :subscribers, dependent: :destroy 3 | has_one_attached :featured_image 4 | has_rich_text :description 5 | 6 | validates :name, presence: true 7 | validates :inventory_count, numericality: { greater_than_or_equal_to: 0 } 8 | 9 | after_update_commit :notify_subscribers, if: :back_in_stock? 10 | 11 | def back_in_stock? 12 | inventory_count_previously_was&.zero? && inventory_count > 0 13 | end 14 | 15 | def notify_subscribers 16 | subscribers.each do |subscriber| 17 | ProductMailer.with(product: self, subscriber: subscriber).in_stock.deliver_later 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /src/content/tutorial/15-css-and-js/2-importmap/_files/workspace/store/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, Helvetica, sans-serif; 3 | padding: 1rem; 4 | } 5 | 6 | nav { 7 | justify-content: flex-end; 8 | display: flex; 9 | font-size: 0.875em; 10 | gap: 0.5rem; 11 | max-width: 1024px; 12 | margin: 0 auto; 13 | padding: 1rem; 14 | } 15 | 16 | nav a { 17 | display: inline-block; 18 | } 19 | 20 | main { 21 | max-width: 1024px; 22 | margin: 0 auto; 23 | } 24 | 25 | .notice { 26 | color: green; 27 | } 28 | 29 | section.product { 30 | display: flex; 31 | gap: 1rem; 32 | flex-direction: row; 33 | } 34 | 35 | section.product img { 36 | border-radius: 8px; 37 | flex-basis: 50%; 38 | max-width: 50%; 39 | } 40 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, "\\1en" 8 | # inflect.singular /^(ox)en/i, "\\1" 9 | # inflect.irregular "person", "people" 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym "RESTful" 16 | # end 17 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/2-adding-subscribers/_files/workspace/store/app/views/products/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with model: product do |form| %> 2 |
3 | <%= form.label :name %> 4 | <%= form.text_field :name %> 5 |
6 | 7 |
8 | <%= form.label :description, style: "display: block" %> 9 | <%= form.rich_textarea :description %> 10 |
11 | 12 |
13 | <%= form.label :featured_image, style: "display: block" %> 14 | <%= form.file_field :featured_image, accept: "image/*" %> 15 |
16 | 17 |
18 | <%= form.label :inventory_count, style: "display: block" %> 19 | <%= form.number_field :inventory_count %> 20 |
21 | 22 |
23 | <%= form.submit %> 24 |
25 | <% end %> 26 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/5-unsubscribe-links/_files/workspace/store/app/views/products/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with model: product do |form| %> 2 |
3 | <%= form.label :name %> 4 | <%= form.text_field :name %> 5 |
6 | 7 |
8 | <%= form.label :description, style: "display: block" %> 9 | <%= form.rich_textarea :description %> 10 |
11 | 12 |
13 | <%= form.label :featured_image, style: "display: block" %> 14 | <%= form.file_field :featured_image, accept: "image/*" %> 15 |
16 | 17 |
18 | <%= form.label :inventory_count, style: "display: block" %> 19 | <%= form.number_field :inventory_count %> 20 |
21 | 22 |
23 | <%= form.submit %> 24 |
25 | <% end %> 26 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/4-extracting-a-concern/_files/workspace/store/app/views/products/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with model: product do |form| %> 2 |
3 | <%= form.label :name %> 4 | <%= form.text_field :name %> 5 |
6 | 7 |
8 | <%= form.label :description, style: "display: block" %> 9 | <%= form.rich_textarea :description %> 10 |
11 | 12 |
13 | <%= form.label :featured_image, style: "display: block" %> 14 | <%= form.file_field :featured_image, accept: "image/*" %> 15 |
16 | 17 |
18 | <%= form.label :inventory_count, style: "display: block" %> 19 | <%= form.number_field :inventory_count %> 20 |
21 | 22 |
23 | <%= form.submit %> 24 |
25 | <% end %> 26 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/3-in-stock-email-notifications/_files/workspace/store/app/views/products/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with model: product do |form| %> 2 |
3 | <%= form.label :name %> 4 | <%= form.text_field :name %> 5 |
6 | 7 |
8 | <%= form.label :description, style: "display: block" %> 9 | <%= form.rich_textarea :description %> 10 |
11 | 12 |
13 | <%= form.label :featured_image, style: "display: block" %> 14 | <%= form.file_field :featured_image, accept: "image/*" %> 15 |
16 | 17 |
18 | <%= form.label :inventory_count, style: "display: block" %> 19 | <%= form.number_field :inventory_count %> 20 |
21 | 22 |
23 | <%= form.submit %> 24 |
25 | <% end %> 26 | -------------------------------------------------------------------------------- /rails-wasm/boot.rb: -------------------------------------------------------------------------------- 1 | # Put all the code required to initialize the Rails Wasm environment 2 | 3 | # Common Rails shims 4 | require "wasmify/rails/shim" 5 | 6 | # Load Rails patches 7 | require "wasmify/rails/patches" 8 | 9 | # Setup external commands 10 | require "wasmify/external_commands" 11 | 12 | # Patch Bundler.require to only require precompiled deps 13 | # (We don't want to deal with group: :wasm here) 14 | def Bundler.require(*groups) 15 | %w[ 16 | rails 17 | wasmify-rails 18 | propshaft 19 | importmap-rails 20 | turbo-rails 21 | stimulus-rails 22 | jbuilder 23 | bcrypt 24 | solid_cache 25 | solid_queue 26 | solid_cable 27 | image_processing 28 | tzinfo/data 29 | ].each do |gem_name| 30 | Kernel.require gem_name 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /src/templates/default/lib/patches/authentication.rb: -------------------------------------------------------------------------------- 1 | # Automatically authenticate a user of first request if the corresponding file is present. 2 | # This way, we can skip the authentication in the second part of the tutorial 3 | Wasmify::Patcha.on_load("Authentication") do 4 | Authentication.prepend(Module.new do 5 | def find_session_by_cookie 6 | return super if $__pre_authenticated 7 | 8 | $__pre_authenticated = true 9 | 10 | session = super 11 | 12 | return session if session 13 | 14 | return unless Rails.root.join("tmp/authenticated-user.txt").exist? 15 | 16 | $_user_email = Rails.root.join("tmp/authenticated-user.txt").read.chomp 17 | 18 | user = User.find_by(email_address: $_user_email) 19 | return unless user 20 | 21 | start_new_session_for(user) 22 | end 23 | end) 24 | end 25 | -------------------------------------------------------------------------------- /src/templates/active-storage/workspace/store/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # Create products that we should have created in the previous lessons 2 | Product.create(name: "T-Shirt").destroy 3 | Product.create(name: "Pants") 4 | 5 | # For authentication 6 | User.create!( 7 | email_address: "you@example.org", 8 | password: "s3cr3t", 9 | password_confirmation: "s3cr3t" 10 | ) unless User.where(email_address: "you@example.org").exists? 11 | 12 | # Create a new product with description and logo attached 13 | logo = File.open(Rails.root.join("app/assets/images/logo.png")) 14 | 15 | product = Product.create(name: "Rails Tutorial", featured_image: logo) 16 | product.description = "

Rails Tutorial Shirt

A high-quality shirt featuring the official Rails logo.

  • 100% cotton
  • Available in multiple sizes
  • Machine washable
" 17 | product.save 18 | -------------------------------------------------------------------------------- /src/content/tutorial/2-rails-new/2-creating-your-first-rails-app/content.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: lesson 3 | title: Creating your first Rails app 4 | editor: false 5 | custom: 6 | shell: 7 | workdir: "/workspace" 8 | --- 9 | 10 | Creating Your First Rails App 11 | ----------------------------- 12 | 13 | Rails comes with several commands to make life easier. Run `rails --help` to see 14 | all of the commands. 15 | 16 | `rails new` generates the foundation of a fresh Rails application for you, so 17 | let's start there. 18 | 19 | To create our `store` application, run the following command in your terminal: 20 | 21 | ```bash 22 | $ rails new store 23 | ``` 24 | 25 | :::info 26 | You can customize the application Rails generates by using flags. To see 27 | these options, run `rails new --help`. 28 | ::: 29 | 30 | After your new application is created, switch to its directory: 31 | 32 | ```bash 33 | $ cd store 34 | ``` 35 | -------------------------------------------------------------------------------- /src/content/tutorial/9-authentication/3-unauthenticated-access/content.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: lesson 3 | title: Allowing Unauthenticated Access 4 | focus: /workspace/store/app/controllers/products_controller.rb 5 | previews: 6 | - 3000 7 | custom: 8 | shell: 9 | workdir: "/workspace/store" 10 | --- 11 | 12 | ### Allowing Unauthenticated Access 13 | 14 | However, our store's product index and show pages should be accessible to 15 | everyone. By default, the Rails authentication generator will restrict all pages 16 | to authenticated users only. 17 | 18 | To allow guests to view products, we can allow unauthenticated access in our 19 | controller. 20 | 21 | ```ruby ins={2} 22 | class ProductsController < ApplicationController 23 | allow_unauthenticated_access only: %i[ index show ] 24 | # ... 25 | end 26 | ``` 27 | 28 | Log out and visit the products index and show pages to see they're accessible 29 | without being authenticated. 30 | -------------------------------------------------------------------------------- /icons/languages/sass.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/templates/stock-notifications/workspace/store/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # Create products that we should have created in the previous lessons 2 | Product.create!(name: "T-Shirt", inventory_count: 2).destroy 3 | Product.create!(name: "Pants", inventory_count: 22) 4 | 5 | # For authentication 6 | User.create!( 7 | email_address: "you@example.org", 8 | password: "s3cr3t", 9 | password_confirmation: "s3cr3t" 10 | ) unless User.where(email_address: "you@example.org").exists? 11 | 12 | # Create a new product with description and logo attached 13 | logo = File.open(Rails.root.join("app/assets/images/logo.png")) 14 | 15 | product = Product.create!(name: "Rails Tutorial", featured_image: logo, inventory_count: 13) 16 | product.description = "

Rails Tutorial Shirt

A high-quality shirt featuring the official Rails logo.

  • 100% cotton
  • Available in multiple sizes
  • Machine washable
" 17 | product.save! 18 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/2-adding-subscribers/_files/workspace/store/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # Create products that we should have created in the previous lessons 2 | Product.create!(name: "T-Shirt", inventory_count: 2).destroy 3 | Product.create!(name: "Pants", inventory_count: 22) 4 | 5 | # For authentication 6 | User.create!( 7 | email_address: "you@example.org", 8 | password: "s3cr3t", 9 | password_confirmation: "s3cr3t" 10 | ) unless User.where(email_address: "you@example.org").exists? 11 | 12 | # Create a new product with description and logo attached 13 | logo = File.open(Rails.root.join("app/assets/images/logo.png")) 14 | 15 | product = Product.create!(name: "Rails Tutorial", featured_image: logo, inventory_count: 13) 16 | product.description = "

Rails Tutorial Shirt

A high-quality shirt featuring the official Rails logo.

  • 100% cotton
  • Available in multiple sizes
  • Machine washable
" 17 | product.save! 18 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/4-extracting-a-concern/_files/workspace/store/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # Create products that we should have created in the previous lessons 2 | Product.create!(name: "T-Shirt", inventory_count: 2).destroy 3 | Product.create!(name: "Pants", inventory_count: 22) 4 | 5 | # For authentication 6 | User.create!( 7 | email_address: "you@example.org", 8 | password: "s3cr3t", 9 | password_confirmation: "s3cr3t" 10 | ) unless User.where(email_address: "you@example.org").exists? 11 | 12 | # Create a new product with description and logo attached 13 | logo = File.open(Rails.root.join("app/assets/images/logo.png")) 14 | 15 | product = Product.create!(name: "Rails Tutorial", featured_image: logo, inventory_count: 13) 16 | product.description = "

Rails Tutorial Shirt

A high-quality shirt featuring the official Rails logo.

  • 100% cotton
  • Available in multiple sizes
  • Machine washable
" 17 | product.save! 18 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/5-unsubscribe-links/_files/workspace/store/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # Create products that we should have created in the previous lessons 2 | Product.create!(name: "T-Shirt", inventory_count: 2).destroy 3 | Product.create!(name: "Pants", inventory_count: 22) 4 | 5 | # For authentication 6 | User.create!( 7 | email_address: "you@example.org", 8 | password: "s3cr3t", 9 | password_confirmation: "s3cr3t" 10 | ) unless User.where(email_address: "you@example.org").exists? 11 | 12 | # Create a new product with description and logo attached 13 | logo = File.open(Rails.root.join("app/assets/images/logo.png")) 14 | 15 | product = Product.create!(name: "Rails Tutorial", featured_image: logo, inventory_count: 13) 16 | product.description = "

Rails Tutorial Shirt

A high-quality shirt featuring the official Rails logo.

  • 100% cotton
  • Available in multiple sizes
  • Machine washable
" 17 | product.save! 18 | -------------------------------------------------------------------------------- /src/content/tutorial/5-console/1-rails-console/content.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: lesson 3 | title: Rails Console 4 | custom: 5 | shell: 6 | workdir: "/workspace/store" 7 | --- 8 | 9 | Rails Console 10 | ------------------- 11 | 12 | Now that we have created our products table, we can interact with it in Rails. 13 | Let's try it out. 14 | 15 | For this, we're going to use a Rails feature called the *console*. The console 16 | is a helpful, interactive tool for testing our code in our Rails application. Run the following command in the terminal: 17 | 18 | ```bash 19 | $ bin/rails console 20 | ``` 21 | 22 | You should see a prompt like the following: 23 | 24 | ```irb 25 | Loading development environment (Rails 8.0.2) 26 | store(dev)> 27 | ``` 28 | 29 | Now we can type code that will be executed when we hit `Enter`. Try 30 | printing out the Rails version: 31 | 32 | ```irb 33 | store(dev)> Rails.version 34 | 35 | ``` 36 | 37 | If the line "8.0.2" appears, it works! 38 | -------------------------------------------------------------------------------- /src/content/tutorial/14-in-stock-notifications/3-in-stock-email-notifications/_files/workspace/store/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # Create products that we should have created in the previous lessons 2 | Product.create!(name: "T-Shirt", inventory_count: 2).destroy 3 | Product.create!(name: "Pants", inventory_count: 22) 4 | 5 | # For authentication 6 | User.create!( 7 | email_address: "you@example.org", 8 | password: "s3cr3t", 9 | password_confirmation: "s3cr3t" 10 | ) unless User.where(email_address: "you@example.org").exists? 11 | 12 | # Create a new product with description and logo attached 13 | logo = File.open(Rails.root.join("app/assets/images/logo.png")) 14 | 15 | product = Product.create!(name: "Rails Tutorial", featured_image: logo, inventory_count: 13) 16 | product.description = "

Rails Tutorial Shirt

A high-quality shirt featuring the official Rails logo.

  • 100% cotton
  • Available in multiple sizes
  • Machine washable
" 17 | product.save! 18 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/8-before-actions/_files/workspace/store/app/controllers/products_controller.rb: -------------------------------------------------------------------------------- 1 | class ProductsController < ApplicationController 2 | def index 3 | @products = Product.all 4 | end 5 | 6 | def show 7 | @product = Product.find(params[:id]) 8 | end 9 | 10 | def new 11 | @product = Product.new 12 | end 13 | 14 | def create 15 | @product = Product.new(product_params) 16 | if @product.save 17 | redirect_to @product 18 | else 19 | render :new, status: :unprocessable_entity 20 | end 21 | end 22 | 23 | def edit 24 | @product = Product.find(params[:id]) 25 | end 26 | 27 | def update 28 | @product = Product.find(params[:id]) 29 | if @product.update(product_params) 30 | redirect_to @product 31 | else 32 | render :edit, status: :unprocessable_entity 33 | end 34 | end 35 | 36 | private 37 | def product_params 38 | params.expect(product: [ :name ]) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /src/content/tutorial/2-rails-new/4-mvc/content.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: lesson 3 | title: MVC Basics 4 | editor: false 5 | custom: 6 | shell: 7 | workdir: "/workspace/store" 8 | --- 9 | 10 | Model-View-Controller Basics 11 | ---------------------------- 12 | 13 | Rails code is organized using the Model-View-Controller (MVC) architecture. With 14 | MVC, we have three main concepts where the majority of our code lives: 15 | 16 | * Model - Manages the data in your application. Typically, your database tables. 17 | * View - Handles rendering responses in different formats like HTML, JSON, XML, 18 | etc. 19 | * Controller - Handles user interactions and the logic for each request. 20 | 21 | 22 | 23 | 24 | 25 | 26 | Now that we've got a basic understanding of MVC, let's see how it's used in 27 | Rails. 28 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/10-crud-destroy/_files/workspace/store/app/controllers/products_controller.rb: -------------------------------------------------------------------------------- 1 | class ProductsController < ApplicationController 2 | before_action :set_product, only: %i[ show edit update ] 3 | 4 | def index 5 | @products = Product.all 6 | end 7 | 8 | def show 9 | end 10 | 11 | def new 12 | @product = Product.new 13 | end 14 | 15 | def create 16 | @product = Product.new(product_params) 17 | if @product.save 18 | redirect_to @product 19 | else 20 | render :new, status: :unprocessable_entity 21 | end 22 | end 23 | 24 | def edit 25 | end 26 | 27 | def update 28 | if @product.update(product_params) 29 | redirect_to @product 30 | else 31 | render :edit, status: :unprocessable_entity 32 | end 33 | end 34 | 35 | private 36 | def set_product 37 | @product = Product.find(params[:id]) 38 | end 39 | 40 | def product_params 41 | params.expect(product: [ :name ]) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /icons/languages/erb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/content/tutorial/8-controllers/9-extracting-partials/_files/workspace/store/app/controllers/products_controller.rb: -------------------------------------------------------------------------------- 1 | class ProductsController < ApplicationController 2 | before_action :set_product, only: %i[ show edit update ] 3 | 4 | def index 5 | @products = Product.all 6 | end 7 | 8 | def show 9 | end 10 | 11 | def new 12 | @product = Product.new 13 | end 14 | 15 | def create 16 | @product = Product.new(product_params) 17 | if @product.save 18 | redirect_to @product 19 | else 20 | render :new, status: :unprocessable_entity 21 | end 22 | end 23 | 24 | def edit 25 | end 26 | 27 | def update 28 | if @product.update(product_params) 29 | redirect_to @product 30 | else 31 | render :edit, status: :unprocessable_entity 32 | end 33 | end 34 | 35 | private 36 | def set_product 37 | @product = Product.find(params[:id]) 38 | end 39 | 40 | def product_params 41 | params.expect(product: [ :name ]) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /src/content/tutorial/7-request-journey/1-intro/content.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: lesson 3 | title: "HTTP Intro" 4 | editor: false 5 | custom: 6 | shell: 7 | workdir: "/workspace/store" 8 | --- 9 | 10 | A Request's Journey Through Rails 11 | --------------------------------- 12 | 13 | To get Rails saying "Hello", you need to create at minimum a _route_, a 14 | _controller_ with an _action_, and a _view_. A route maps a request to a 15 | controller action. A controller action performs the necessary work to handle the 16 | request, and prepares any data for the view. A view displays data in a desired 17 | format. 18 | 19 | In terms of implementation: Routes are rules written in a Ruby 20 | [DSL (Domain-Specific Language)](https://en.wikipedia.org/wiki/Domain-specific_language). 21 | Controllers are Ruby classes, and their public methods are actions. And views 22 | are templates, usually written in a mixture of HTML and Ruby. 23 | 24 | That's the short of it, but we’re going to walk through each of these steps in 25 | more detail next. 26 | -------------------------------------------------------------------------------- /src/content/tutorial/4-database/3-running-migrations/content.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: lesson 3 | title: Running Migrations 4 | focus: /workspace/store/db/migrate/20250521010850_create_products.rb 5 | custom: 6 | shell: 7 | workdir: "/workspace/store" 8 | fs: 9 | remove: 10 | - "/workspace/store" 11 | --- 12 | 13 | Running Migrations 14 | ------------------- 15 | 16 | Now that you have defined what changes to make to the database, use the 17 | following command to run the migrations: 18 | 19 | ```bash 20 | $ bin/rails db:migrate 21 | ``` 22 | 23 | This command checks for any new migrations and applies them to your database. 24 | Its output looks like this: 25 | 26 | ```bash 27 | == 20250521010850 CreateProducts: migrating =================================== 28 | -- create_table(:products) 29 | -> 0.0030s 30 | == 20250521010850 CreateProducts: migrated (0.0031s) ========================== 31 | ``` 32 | 33 | :::tip 34 | If you make a mistake, you can run `bin/rails db:rollback` to undo the last 35 | migration. 36 | ::: 37 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/app/views/pwa/service-worker.js: -------------------------------------------------------------------------------- 1 | // Add a service worker for processing Web Push notifications: 2 | // 3 | // self.addEventListener("push", async (event) => { 4 | // const { title, options } = await event.data.json() 5 | // event.waitUntil(self.registration.showNotification(title, options)) 6 | // }) 7 | // 8 | // self.addEventListener("notificationclick", function(event) { 9 | // event.notification.close() 10 | // event.waitUntil( 11 | // clients.matchAll({ type: "window" }).then((clientList) => { 12 | // for (let i = 0; i < clientList.length; i++) { 13 | // let client = clientList[i] 14 | // let clientPath = (new URL(client.url)).pathname 15 | // 16 | // if (clientPath == event.notification.data.path && "focus" in client) { 17 | // return client.focus() 18 | // } 19 | // } 20 | // 21 | // if (clients.openWindow) { 22 | // return clients.openWindow(event.notification.data.path) 23 | // } 24 | // }) 25 | // ) 26 | // }) 27 | -------------------------------------------------------------------------------- /src/content/tutorial/2-rails-new/1-prerequisites/content.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: lesson 3 | title: Prerequisites 4 | editor: false 5 | custom: 6 | shell: 7 | workdir: "/workspace" 8 | --- 9 | 10 | Creating a New Rails App 11 | ------------------------ 12 | 13 | We're going to build a project called `store` - a simple e-commerce app that 14 | demonstrates several of Rails' built-in features. 15 | 16 | :::tip 17 | Any commands prefaced with a dollar sign `$` should be run in the terminal. 18 | ::: 19 | 20 | ### Prerequisites 21 | 22 | For this project, you will need ~~Ruby 3.2 or newer~~, ~~Rails 8.0.0 or newer~~, ~~a code editor~~... nothing! Everything you need to build your first Rails application is already "installed" in your browser. 23 | 24 | Let's verify the correct version of Rails is installed. To display the current 25 | version, go to the terminal and run the following. You should see a version number 26 | printed out: 27 | 28 | ```bash 29 | $ rails --version 30 | Rails 8.0.2 31 | ``` 32 | 33 | The version shown should be Rails 8.0.0 or higher. 34 | -------------------------------------------------------------------------------- /src/content/tutorial/3-hello-rails/2-autoloading/content.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: lesson 3 | title: Autoloading in Development 4 | editor: false 5 | custom: 6 | shell: 7 | workdir: "/workspace/store" 8 | --- 9 | 10 | Autoloading in Development 11 | ---------------- 12 | 13 | Developer happiness is a cornerstone philosophy of Rails and one way of 14 | achieving this is with automatic code reloading in development. 15 | 16 | Once you start the Rails server, new files or changes to existing files are 17 | detected and automatically loaded or reloaded as necessary. This allows you to 18 | focus on building without having to restart your Rails server after every 19 | change. 20 | 21 | You may also notice that Rails applications rarely use `require` statements like 22 | you may have seen in other programming languages. Rails uses naming conventions 23 | to require files automatically so you can focus on writing your application 24 | code. 25 | 26 | See 27 | [Autoloading and Reloading Constants](https://guides.rubyonrails.org/autoloading_and_reloading_constants.html) 28 | for more details. 29 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/config/database.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | adapter: pglite 3 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 4 | timeout: 5000 5 | 6 | development: 7 | <<: *default 8 | database: store_development 9 | 10 | # Warning: The database defined as "test" will be erased and 11 | # re-generated from your development database when you run "rake". 12 | # Do not set this db to the same as development or production. 13 | test: 14 | <<: *default 15 | database: store_test 16 | 17 | production: 18 | primary: 19 | <<: *default 20 | # database: path/to/persistent/storage/production.sqlite3 21 | cache: 22 | <<: *default 23 | # database: path/to/persistent/storage/production_cache.sqlite3 24 | migrations_paths: db/cache_migrate 25 | queue: 26 | <<: *default 27 | # database: path/to/persistent/storage/production_queue.sqlite3 28 | migrations_paths: db/queue_migrate 29 | cable: 30 | <<: *default 31 | # database: path/to/persistent/storage/production_cable.sqlite3 32 | migrations_paths: db/cable_migrate 33 | -------------------------------------------------------------------------------- /src/templates/crud-products/workspace/store/app/controllers/products_controller.rb: -------------------------------------------------------------------------------- 1 | class ProductsController < ApplicationController 2 | before_action :set_product, only: %i[ show edit update destroy] 3 | 4 | def index 5 | @products = Product.all 6 | end 7 | 8 | def show 9 | end 10 | 11 | def new 12 | @product = Product.new 13 | end 14 | 15 | def create 16 | @product = Product.new(product_params) 17 | if @product.save 18 | redirect_to @product 19 | else 20 | render :new, status: :unprocessable_entity 21 | end 22 | end 23 | 24 | def edit 25 | end 26 | 27 | def update 28 | if @product.update(product_params) 29 | redirect_to @product 30 | else 31 | render :edit, status: :unprocessable_entity 32 | end 33 | end 34 | 35 | def destroy 36 | @product.destroy 37 | redirect_to products_path 38 | end 39 | 40 | private 41 | def set_product 42 | @product = Product.find(params[:id]) 43 | end 44 | 45 | def product_params 46 | params.expect(product: [ :name ]) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization and 2 | # are automatically loaded by Rails. If you want to use locales other than 3 | # English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t "hello" 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t("hello") %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more about the API, please read the Rails Internationalization guide 20 | # at https://guides.rubyonrails.org/i18n.html. 21 | # 22 | # Be aware that YAML interprets the following case-insensitive strings as 23 | # booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings 24 | # must be quoted to be interpreted as strings. For example: 25 | # 26 | # en: 27 | # "yes": yup 28 | # enabled: "ON" 29 | 30 | en: 31 | hello: "Hello world" 32 | -------------------------------------------------------------------------------- /src/content/tutorial/9-authentication/2-adding-log-out/_files/workspace/store/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= content_for(:title) || "Store" %> 5 | 6 | 7 | 8 | <%= csrf_meta_tags %> 9 | <%= csp_meta_tag %> 10 | 11 | <%= yield :head %> 12 | 13 | <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %> 14 | <%#= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %> 15 | 16 | 17 | 18 | 19 | 20 | <%# Includes all stylesheet files in app/assets/stylesheets %> 21 | <%= stylesheet_link_tag :app, "data-turbo-track": "reload" %> 22 | 23 | 24 | 25 | <%= yield %> 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/templates/rails-new/workspace/store/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= content_for(:title) || "Store" %> 5 | 6 | 7 | 8 | <%= csrf_meta_tags %> 9 | <%= csp_meta_tag %> 10 | 11 | <%= yield :head %> 12 | 13 | <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %> 14 | <%#= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %> 15 | 16 | 17 | 18 | 19 | 20 | <%# Includes all stylesheet files in app/assets/stylesheets %> 21 | <%= stylesheet_link_tag :app, "data-turbo-track": "reload" %> 22 | <%= javascript_importmap_tags %> 23 | 24 | 25 | 26 | <%= yield %> 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/content/tutorial/9-authentication/2-adding-log-out/_files/workspace/store/app/controllers/products_controller.rb: -------------------------------------------------------------------------------- 1 | class ProductsController < ApplicationController 2 | before_action :set_product, only: %i[ show edit update destroy] 3 | 4 | def index 5 | @products = Product.all 6 | end 7 | 8 | def show 9 | end 10 | 11 | def new 12 | @product = Product.new 13 | end 14 | 15 | def create 16 | @product = Product.new(product_params) 17 | if @product.save 18 | redirect_to @product 19 | else 20 | render :new, status: :unprocessable_entity 21 | end 22 | end 23 | 24 | def edit 25 | end 26 | 27 | def update 28 | if @product.update(product_params) 29 | redirect_to @product 30 | else 31 | render :edit, status: :unprocessable_entity 32 | end 33 | end 34 | 35 | def destroy 36 | @product.destroy 37 | redirect_to products_path 38 | end 39 | 40 | private 41 | def set_product 42 | @product = Product.find(params[:id]) 43 | end 44 | 45 | def product_params 46 | params.expect(product: [ :name ]) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rails-tutorial", 3 | "version": "0.0.1", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "astro": "astro", 8 | "build": "astro check && astro build", 9 | "dev": "astro dev", 10 | "preview": "astro preview", 11 | "start": "astro dev", 12 | "postinstall": "patch-package" 13 | }, 14 | "dependencies": { 15 | "@codemirror/lang-yaml": "^6.1.2", 16 | "@codemirror/legacy-modes": "^6.5.1", 17 | "@tutorialkit/react": "1.5.0", 18 | "react": "^18.3.1", 19 | "react-dom": "^18.3.1" 20 | }, 21 | "devDependencies": { 22 | "@astrojs/check": "^0.7.0", 23 | "@astrojs/react": "^3.6.0", 24 | "@flydotio/dockerfile": "^0.7.10", 25 | "@tutorialkit/astro": "1.5.0", 26 | "@tutorialkit/theme": "1.5.0", 27 | "@tutorialkit/types": "1.5.0", 28 | "@types/mdast": "^4.0.4", 29 | "@types/node": "^20.14.6", 30 | "@types/react": "^18.3.3", 31 | "astro": "^4.15.0", 32 | "patch-package": "^8.0.0", 33 | "prettier-plugin-astro": "^0.14.1", 34 | "typescript": "^5.4.5", 35 | "unist-util-visit": "^5.0.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/content/tutorial/9-authentication/3-unauthenticated-access/_files/workspace/store/app/controllers/products_controller.rb: -------------------------------------------------------------------------------- 1 | class ProductsController < ApplicationController 2 | before_action :set_product, only: %i[ show edit update destroy] 3 | 4 | def index 5 | @products = Product.all 6 | end 7 | 8 | def show 9 | end 10 | 11 | def new 12 | @product = Product.new 13 | end 14 | 15 | def create 16 | @product = Product.new(product_params) 17 | if @product.save 18 | redirect_to @product 19 | else 20 | render :new, status: :unprocessable_entity 21 | end 22 | end 23 | 24 | def edit 25 | end 26 | 27 | def update 28 | if @product.update(product_params) 29 | redirect_to @product 30 | else 31 | render :edit, status: :unprocessable_entity 32 | end 33 | end 34 | 35 | def destroy 36 | @product.destroy 37 | redirect_to products_path 38 | end 39 | 40 | private 41 | def set_product 42 | @product = Product.find(params[:id]) 43 | end 44 | 45 | def product_params 46 | params.expect(product: [ :name ]) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /src/templates/default/lib/commands.js: -------------------------------------------------------------------------------- 1 | import { createRackServer } from "./server.js"; 2 | import IRBRepl from "./irb.js"; 3 | 4 | export default class ExternalCommands { 5 | constructor() { 6 | this.command = undefined; 7 | } 8 | 9 | server(port) { 10 | this.command = async function(vm) { 11 | const server = await createRackServer(vm, {skipRackup: true}); 12 | 13 | server.listen(port, () => { 14 | console.log(`Express.js server started on port ${port}`); 15 | console.log(`Use Ctrl-C to stop`); 16 | }); 17 | 18 | // FIXME: doesn't work; do WebContainers/jsh support signals at all? 19 | process.on('exit', async () => { 20 | console.log('Express.js server is shutting down'); 21 | await vm.evalAsync(`execute_at_exit_hooks`) 22 | }); 23 | } 24 | } 25 | 26 | console() { 27 | this.command = async function(vm) { 28 | const irb = new IRBRepl(vm); 29 | return irb.start(); 30 | } 31 | } 32 | 33 | // Invokes a registered command if any 34 | invoke(vm) { 35 | if (!this.command) return; 36 | 37 | return this.command(vm); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/app/controllers/products_controller.rb: -------------------------------------------------------------------------------- 1 | class ProductsController < ApplicationController 2 | allow_unauthenticated_access only: %i[ index show ] 3 | before_action :set_product, only: %i[ show edit update destroy] 4 | 5 | def index 6 | @products = Product.all 7 | end 8 | 9 | def show 10 | end 11 | 12 | def new 13 | @product = Product.new 14 | end 15 | 16 | def create 17 | @product = Product.new(product_params) 18 | if @product.save 19 | redirect_to @product 20 | else 21 | render :new, status: :unprocessable_entity 22 | end 23 | end 24 | 25 | def edit 26 | end 27 | 28 | def update 29 | if @product.update(product_params) 30 | redirect_to @product 31 | else 32 | render :edit, status: :unprocessable_entity 33 | end 34 | end 35 | 36 | def destroy 37 | @product.destroy 38 | redirect_to products_path 39 | end 40 | 41 | private 42 | def set_product 43 | @product = Product.find(params[:id]) 44 | end 45 | 46 | def product_params 47 | params.expect(product: [ :name ]) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /src/content/tutorial/6-active-record/5-finding-records/content.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: lesson 3 | title: Finding Records 4 | focus: /workspace/store/app/models/product.rb 5 | custom: 6 | shell: 7 | workdir: "/workspace/store" 8 | --- 9 | 10 | ### Finding Records 11 | 12 | What if we want to find one specific record? 13 | 14 | We can do this by using the `find` class method to look up a single record by 15 | ID. Call the method and pass in the specific ID by using the following code: 16 | 17 | ```irb 18 | store(dev)> Product.find(1) 19 | Product Load (0.2ms) SELECT "products".* FROM "products" WHERE "products"."id" = 1 LIMIT 1 /*application='Store'*/ 20 | => # 21 | ``` 22 | 23 | This generates a `SELECT` query but specifies a `WHERE` for the `id` column 24 | matching the ID of `1` that was passed in. It also adds a `LIMIT` to only return 25 | a single record. 26 | 27 | This time, we get a `Product` instance instead of an `ActiveRecord::Relation` 28 | since we're only retrieving a single record from the database. 29 | -------------------------------------------------------------------------------- /src/templates/active-storage/workspace/store/app/controllers/products_controller.rb: -------------------------------------------------------------------------------- 1 | class ProductsController < ApplicationController 2 | allow_unauthenticated_access only: %i[ index show ] 3 | before_action :set_product, only: %i[ show edit update destroy] 4 | 5 | def index 6 | @products = Product.all 7 | end 8 | 9 | def show 10 | end 11 | 12 | def new 13 | @product = Product.new 14 | end 15 | 16 | def create 17 | @product = Product.new(product_params) 18 | if @product.save 19 | redirect_to @product 20 | else 21 | render :new, status: :unprocessable_entity 22 | end 23 | end 24 | 25 | def edit 26 | end 27 | 28 | def update 29 | if @product.update(product_params) 30 | redirect_to @product 31 | else 32 | render :edit, status: :unprocessable_entity 33 | end 34 | end 35 | 36 | def destroy 37 | @product.destroy 38 | redirect_to products_path 39 | end 40 | 41 | private 42 | def set_product 43 | @product = Product.find(params[:id]) 44 | end 45 | 46 | def product_params 47 | params.expect(product: [ :name, :description, :featured_image ]) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /src/templates/authentication/workspace/store/app/controllers/passwords_controller.rb: -------------------------------------------------------------------------------- 1 | class PasswordsController < ApplicationController 2 | allow_unauthenticated_access 3 | before_action :set_user_by_token, only: %i[ edit update ] 4 | 5 | def new 6 | end 7 | 8 | def create 9 | if user = User.find_by(email_address: params[:email_address]) 10 | PasswordsMailer.reset(user).deliver_later 11 | end 12 | 13 | redirect_to new_session_path, notice: "Password reset instructions sent (if user with that email address exists)." 14 | end 15 | 16 | def edit 17 | end 18 | 19 | def update 20 | if @user.update(params.permit(:password, :password_confirmation)) 21 | redirect_to new_session_path, notice: "Password has been reset." 22 | else 23 | redirect_to edit_password_path(params[:token]), alert: "Passwords did not match." 24 | end 25 | end 26 | 27 | private 28 | def set_user_by_token 29 | @user = User.find_by_password_reset_token!(params[:token]) 30 | rescue ActiveSupport::MessageVerifier::InvalidSignature 31 | redirect_to new_password_path, alert: "Password reset link is invalid or has expired." 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /src/templates/active-storage/workspace/store/db/migrate/20250618002538_create_action_text_tables.action_text.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from action_text (originally 20180528164100) 2 | class CreateActionTextTables < ActiveRecord::Migration[6.0] 3 | def change 4 | # Use Active Record's configured type for primary and foreign keys 5 | primary_key_type, foreign_key_type = primary_and_foreign_key_types 6 | 7 | create_table :action_text_rich_texts, id: primary_key_type do |t| 8 | t.string :name, null: false 9 | t.text :body, size: :long 10 | t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type 11 | 12 | t.timestamps 13 | 14 | t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true 15 | end 16 | end 17 | 18 | private 19 | def primary_and_foreign_key_types 20 | config = Rails.configuration.generators 21 | setting = config.options[config.orm][:primary_key_type] 22 | primary_key_type = setting || :primary_key 23 | foreign_key_type = setting || :bigint 24 | [ primary_key_type, foreign_key_type ] 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /src/content/tutorial/12-file-uploads/1-active-storage/_files/workspace/store/app/controllers/products_controller.rb: -------------------------------------------------------------------------------- 1 | class ProductsController < ApplicationController 2 | allow_unauthenticated_access only: %i[ index show ] 3 | before_action :set_product, only: %i[ show edit update destroy] 4 | 5 | def index 6 | @products = Product.all 7 | end 8 | 9 | def show 10 | end 11 | 12 | def new 13 | @product = Product.new 14 | end 15 | 16 | def create 17 | @product = Product.new(product_params) 18 | if @product.save 19 | redirect_to @product 20 | else 21 | render :new, status: :unprocessable_entity 22 | end 23 | end 24 | 25 | def edit 26 | end 27 | 28 | def update 29 | if @product.update(product_params) 30 | redirect_to @product 31 | else 32 | render :edit, status: :unprocessable_entity 33 | end 34 | end 35 | 36 | def destroy 37 | @product.destroy 38 | redirect_to products_path 39 | end 40 | 41 | private 42 | def set_product 43 | @product = Product.find(params[:id]) 44 | end 45 | 46 | def product_params 47 | params.expect(product: [ :name, :description ]) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /src/content/tutorial/9-authentication/2-adding-log-out/content.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: lesson 3 | title: Adding Log Out 4 | focus: /workspace/store/app/views/layouts/application.html.erb 5 | previews: 6 | - 3000 7 | custom: 8 | shell: 9 | workdir: "/workspace/store" 10 | --- 11 | 12 | ### Adding Log Out 13 | 14 | To log out of the application, we can add a button to the top of 15 | `app/views/layouts/application.html.erb`. This layout is where you put HTML that 16 | you want to include in every page like a header or footer. 17 | 18 | Add a small `