@tutorialkit-rb/cli 1.5.2-rb.0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +15 -0
- package/dist/index.js +1384 -0
- package/package.json +66 -0
- package/template/.gitignore +13 -0
- package/template/.vscode/extensions.json +4 -0
- package/template/.vscode/launch.json +11 -0
- package/template/README.md +179 -0
- package/template/astro.config.ts +21 -0
- package/template/bin/build-wasm +30 -0
- package/template/icons/languages/css.svg +1 -0
- package/template/icons/languages/html.svg +1 -0
- package/template/icons/languages/js.svg +1 -0
- package/template/icons/languages/json.svg +1 -0
- package/template/icons/languages/markdown.svg +1 -0
- package/template/icons/languages/ruby.svg +1 -0
- package/template/icons/languages/sass.svg +1 -0
- package/template/icons/languages/ts.svg +1 -0
- package/template/icons/phosphor/file-erb.svg +1 -0
- package/template/icons/phosphor/file-rb.svg +5 -0
- package/template/package.json +37 -0
- package/template/package.json.bak +37 -0
- package/template/public/favicon.svg +4 -0
- package/template/public/logo-dark.svg +4 -0
- package/template/public/logo.svg +4 -0
- package/template/ruby-wasm/.railsrc +12 -0
- package/template/ruby-wasm/Gemfile +22 -0
- package/template/ruby-wasm/Gemfile.lock +292 -0
- package/template/ruby-wasm/README.md +19 -0
- package/template/ruby-wasm/bin/pack +16 -0
- package/template/ruby-wasm/boot.rb +32 -0
- package/template/ruby-wasm/config/wasmify.yml +23 -0
- package/template/src/components/FileManager.tsx +116 -0
- package/template/src/components/GitHubLink.astro +17 -0
- package/template/src/components/HeadTags.astro +65 -0
- package/template/src/components/HelpDropdown.tsx +72 -0
- package/template/src/components/RailsPathLinkHandler.tsx +107 -0
- package/template/src/components/ShellConfigurator.tsx +95 -0
- package/template/src/components/TopBar.astro +48 -0
- package/template/src/content/config.ts +9 -0
- package/template/src/content/tutorial/1-getting-started/1-creating-your-first-rails-app/_files/workspace/.keep +0 -0
- package/template/src/content/tutorial/1-getting-started/1-creating-your-first-rails-app/content.md +34 -0
- package/template/src/content/tutorial/1-getting-started/2-rails-console/_files/.tk-config.json +3 -0
- package/template/src/content/tutorial/1-getting-started/2-rails-console/_files/workspace/.keep +0 -0
- package/template/src/content/tutorial/1-getting-started/2-rails-console/content.md +37 -0
- package/template/src/content/tutorial/1-getting-started/meta.md +4 -0
- package/template/src/content/tutorial/2-controllers/2-crud-operations/_files/.tk-config.json +3 -0
- package/template/src/content/tutorial/2-controllers/2-crud-operations/_files/workspace/.keep +0 -0
- package/template/src/content/tutorial/2-controllers/2-crud-operations/content.md +99 -0
- package/template/src/content/tutorial/2-controllers/meta.md +4 -0
- package/template/src/content/tutorial/meta.md +18 -0
- package/template/src/env.d.ts +3 -0
- package/template/src/plugins/remarkRailsPathLinks.ts +39 -0
- package/template/src/templates/crud-products/.tk-config.json +3 -0
- package/template/src/templates/crud-products/workspace/.keep +0 -0
- package/template/src/templates/crud-products/workspace/store/app/controllers/products_controller.rb +48 -0
- package/template/src/templates/crud-products/workspace/store/app/models/product.rb +3 -0
- package/template/src/templates/crud-products/workspace/store/app/views/products/_form.html.erb +10 -0
- package/template/src/templates/crud-products/workspace/store/app/views/products/edit.html.erb +4 -0
- package/template/src/templates/crud-products/workspace/store/app/views/products/index.html.erb +11 -0
- package/template/src/templates/crud-products/workspace/store/app/views/products/new.html.erb +4 -0
- package/template/src/templates/crud-products/workspace/store/app/views/products/show.html.erb +5 -0
- package/template/src/templates/crud-products/workspace/store/config/routes.rb +6 -0
- package/template/src/templates/crud-products/workspace/store/db/migrate/20250521010850_create_products.rb +9 -0
- package/template/src/templates/crud-products/workspace/store/db/schema.rb +22 -0
- package/template/src/templates/crud-products/workspace/store/db/seeds.rb +3 -0
- package/template/src/templates/crud-products/workspace/store/test/fixtures/products.yml +7 -0
- package/template/src/templates/crud-products/workspace/store/test/models/product_test.rb +7 -0
- package/template/src/templates/default/bin/console +9 -0
- package/template/src/templates/default/bin/rackup +11 -0
- package/template/src/templates/default/bin/rails +41 -0
- package/template/src/templates/default/bin/ruby +37 -0
- package/template/src/templates/default/lib/commands.js +39 -0
- package/template/src/templates/default/lib/database.js +46 -0
- package/template/src/templates/default/lib/irb.js +110 -0
- package/template/src/templates/default/lib/patches/app_generator.rb +43 -0
- package/template/src/templates/default/lib/patches/authentication.rb +24 -0
- package/template/src/templates/default/lib/rails.js +69 -0
- package/template/src/templates/default/lib/server/frame_location_middleware.js +77 -0
- package/template/src/templates/default/lib/server.js +307 -0
- package/template/src/templates/default/package-lock.json +1830 -0
- package/template/src/templates/default/package.json +23 -0
- package/template/src/templates/default/pgdata/.keep +0 -0
- package/template/src/templates/default/scripts/createdb.js +7 -0
- package/template/src/templates/default/scripts/rails.js +52 -0
- package/template/src/templates/default/scripts/wait-for-wasm.js +103 -0
- package/template/src/templates/default/workspace/.keep +0 -0
- package/template/src/templates/rails-app/workspace/store/.ruby-version +1 -0
- package/template/src/templates/rails-app/workspace/store/Gemfile +37 -0
- package/template/src/templates/rails-app/workspace/store/README.md +24 -0
- package/template/src/templates/rails-app/workspace/store/Rakefile +6 -0
- package/template/src/templates/rails-app/workspace/store/app/assets/images/.keep +0 -0
- package/template/src/templates/rails-app/workspace/store/app/assets/stylesheets/application.css +10 -0
- package/template/src/templates/rails-app/workspace/store/app/controllers/application_controller.rb +4 -0
- package/template/src/templates/rails-app/workspace/store/app/helpers/application_helper.rb +2 -0
- package/template/src/templates/rails-app/workspace/store/app/javascript/application.js +4 -0
- package/template/src/templates/rails-app/workspace/store/app/javascript/controllers/application.js +9 -0
- package/template/src/templates/rails-app/workspace/store/app/javascript/controllers/index.js +4 -0
- package/template/src/templates/rails-app/workspace/store/app/jobs/application_job.rb +7 -0
- package/template/src/templates/rails-app/workspace/store/app/mailers/application_mailer.rb +4 -0
- package/template/src/templates/rails-app/workspace/store/app/models/application_record.rb +3 -0
- package/template/src/templates/rails-app/workspace/store/app/models/concerns/.keep +0 -0
- package/template/src/templates/rails-app/workspace/store/app/views/layouts/application.html.erb +28 -0
- package/template/src/templates/rails-app/workspace/store/app/views/layouts/mailer.html.erb +13 -0
- package/template/src/templates/rails-app/workspace/store/app/views/layouts/mailer.text.erb +1 -0
- package/template/src/templates/rails-app/workspace/store/app/views/pwa/manifest.json.erb +22 -0
- package/template/src/templates/rails-app/workspace/store/app/views/pwa/service-worker.js +26 -0
- package/template/src/templates/rails-app/workspace/store/bin/importmap +4 -0
- package/template/src/templates/rails-app/workspace/store/bin/rails +4 -0
- package/template/src/templates/rails-app/workspace/store/config/application.rb +30 -0
- package/template/src/templates/rails-app/workspace/store/config/boot.rb +3 -0
- package/template/src/templates/rails-app/workspace/store/config/cable.yml +10 -0
- package/template/src/templates/rails-app/workspace/store/config/credentials.yml.enc +1 -0
- package/template/src/templates/rails-app/workspace/store/config/database.yml +32 -0
- package/template/src/templates/rails-app/workspace/store/config/environment.rb +5 -0
- package/template/src/templates/rails-app/workspace/store/config/environments/development.rb +69 -0
- package/template/src/templates/rails-app/workspace/store/config/environments/production.rb +89 -0
- package/template/src/templates/rails-app/workspace/store/config/environments/test.rb +53 -0
- package/template/src/templates/rails-app/workspace/store/config/importmap.rb +9 -0
- package/template/src/templates/rails-app/workspace/store/config/initializers/assets.rb +7 -0
- package/template/src/templates/rails-app/workspace/store/config/initializers/content_security_policy.rb +25 -0
- package/template/src/templates/rails-app/workspace/store/config/initializers/filter_parameter_logging.rb +8 -0
- package/template/src/templates/rails-app/workspace/store/config/initializers/inflections.rb +16 -0
- package/template/src/templates/rails-app/workspace/store/config/locales/en.yml +31 -0
- package/template/src/templates/rails-app/workspace/store/config/master.key +1 -0
- package/template/src/templates/rails-app/workspace/store/config/puma.rb +41 -0
- package/template/src/templates/rails-app/workspace/store/config/routes.rb +4 -0
- package/template/src/templates/rails-app/workspace/store/config/storage.yml +34 -0
- package/template/src/templates/rails-app/workspace/store/config.ru +6 -0
- package/template/src/templates/rails-app/workspace/store/db/seeds.rb +9 -0
- package/template/src/templates/rails-app/workspace/store/log/.keep +0 -0
- package/template/src/templates/rails-app/workspace/store/public/400.html +114 -0
- package/template/src/templates/rails-app/workspace/store/public/404.html +114 -0
- package/template/src/templates/rails-app/workspace/store/public/406-unsupported-browser.html +114 -0
- package/template/src/templates/rails-app/workspace/store/public/422.html +114 -0
- package/template/src/templates/rails-app/workspace/store/public/500.html +114 -0
- package/template/src/templates/rails-app/workspace/store/public/icon.png +0 -0
- package/template/src/templates/rails-app/workspace/store/public/icon.svg +3 -0
- package/template/src/templates/rails-app/workspace/store/public/robots.txt +1 -0
- package/template/src/templates/rails-app/workspace/store/script/.keep +0 -0
- package/template/src/templates/rails-app/workspace/store/storage/.keep +0 -0
- package/template/src/templates/rails-app/workspace/store/test/controllers/.keep +0 -0
- package/template/src/templates/rails-app/workspace/store/test/helpers/.keep +0 -0
- package/template/src/templates/rails-app/workspace/store/test/integration/.keep +0 -0
- package/template/src/templates/rails-app/workspace/store/test/test_helper.rb +15 -0
- package/template/src/templates/rails-app/workspace/store/tmp/.keep +0 -0
- package/template/src/templates/rails-app/workspace/store/tmp/pids/.keep +0 -0
- package/template/src/templates/rails-app/workspace/store/tmp/storage/.keep +0 -0
- package/template/src/templates/rails-app/workspace/store/vendor/javascripts/.keep +0 -0
- package/template/tsconfig.json +16 -0
- package/template/uno.config.ts +10 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { useStore } from '@nanostores/react';
|
|
2
|
+
import type { PreviewInfo } from '@tutorialkit-rb/runtime';
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
import tutorialStore from 'tutorialkit:store';
|
|
5
|
+
|
|
6
|
+
const ensureRailsServerStarted = async (preview: PreviewInfo) => {
|
|
7
|
+
if (preview.ready) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const terminalConfig = tutorialStore.terminalConfig.get();
|
|
12
|
+
const terminal = terminalConfig.panels.find((panel) => panel.type === 'terminal');
|
|
13
|
+
|
|
14
|
+
if (!terminal || !terminal.process) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
terminal.input(`bin/rails s\n`);
|
|
19
|
+
|
|
20
|
+
await new Promise<void>((resolve, reject) => {
|
|
21
|
+
const tid = setTimeout(() => {
|
|
22
|
+
clearInterval(tick);
|
|
23
|
+
reject();
|
|
24
|
+
}, 10000);
|
|
25
|
+
|
|
26
|
+
const tick = setInterval(() => {
|
|
27
|
+
if (preview.ready) {
|
|
28
|
+
clearInterval(tick);
|
|
29
|
+
clearTimeout(tid);
|
|
30
|
+
resolve();
|
|
31
|
+
}
|
|
32
|
+
}, 200);
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default function RailsPathLinkHandler() {
|
|
37
|
+
const previews = useStore(tutorialStore.previews);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
async function handleClick(event: MouseEvent) {
|
|
41
|
+
const target = event.target as HTMLElement;
|
|
42
|
+
const link = target.closest('.rails-path-link');
|
|
43
|
+
|
|
44
|
+
if (link) {
|
|
45
|
+
event.preventDefault();
|
|
46
|
+
|
|
47
|
+
const railsPath = link.getAttribute('data-rails-path');
|
|
48
|
+
|
|
49
|
+
if (railsPath) {
|
|
50
|
+
tutorialStore.setSelectedFile(`/workspace/store/${railsPath}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (target.tagName === 'A') {
|
|
57
|
+
const linkTarget = target as HTMLAnchorElement;
|
|
58
|
+
|
|
59
|
+
if (linkTarget.href.startsWith('http://localhost:3000')) {
|
|
60
|
+
event.preventDefault();
|
|
61
|
+
|
|
62
|
+
const railsPreview = previews.find((pr) => pr.port === 3000);
|
|
63
|
+
|
|
64
|
+
if (!railsPreview) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
await ensureRailsServerStarted(railsPreview);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('failed to start Rails server', e);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const input = document.querySelector(
|
|
76
|
+
'input[type="text"][name="tutorialkit-preview-navigation"]',
|
|
77
|
+
) as HTMLInputElement;
|
|
78
|
+
|
|
79
|
+
if (!input) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const newPath = linkTarget.href.replace('http://localhost:3000/', '');
|
|
84
|
+
|
|
85
|
+
input.value = newPath;
|
|
86
|
+
|
|
87
|
+
const ev = new KeyboardEvent('keydown', {
|
|
88
|
+
key: 'Enter',
|
|
89
|
+
code: 'Enter',
|
|
90
|
+
keyCode: 13,
|
|
91
|
+
bubbles: true,
|
|
92
|
+
cancelable: true,
|
|
93
|
+
});
|
|
94
|
+
input.dispatchEvent(ev);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
document.addEventListener('click', handleClick);
|
|
100
|
+
|
|
101
|
+
return () => {
|
|
102
|
+
document.removeEventListener('click', handleClick);
|
|
103
|
+
};
|
|
104
|
+
}, [previews]);
|
|
105
|
+
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { useStore } from '@nanostores/react';
|
|
2
|
+
import type { WebContainerProcess } from '@webcontainer/api';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import tutorialStore from 'tutorialkit:store';
|
|
5
|
+
|
|
6
|
+
type ShellConfig = Partial<{
|
|
7
|
+
workdir: string;
|
|
8
|
+
}>;
|
|
9
|
+
|
|
10
|
+
let observedProcess: WebContainerProcess | undefined = undefined;
|
|
11
|
+
let currWorkdir = '';
|
|
12
|
+
|
|
13
|
+
export const ShellConfigurator: React.FC = () => {
|
|
14
|
+
const boot = useStore(tutorialStore.bootStatus);
|
|
15
|
+
const storeRef = useStore(tutorialStore.ref);
|
|
16
|
+
const terminalConfig = useStore(tutorialStore.terminalConfig);
|
|
17
|
+
const lessonLoaded = useStore(tutorialStore.lessonFullyLoaded);
|
|
18
|
+
const [state, set] = useState(0);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const unlisten = tutorialStore.terminalConfig.listen(() => {
|
|
22
|
+
set(state + 1);
|
|
23
|
+
});
|
|
24
|
+
return unlisten;
|
|
25
|
+
}, [terminalConfig]);
|
|
26
|
+
|
|
27
|
+
const lesson = tutorialStore.lesson;
|
|
28
|
+
const terminal = terminalConfig.panels.find((panel) => panel.type === 'terminal');
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (boot !== 'booted') {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!lessonLoaded) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!lesson) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!terminal) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const conf = lesson?.data?.custom?.shell as ShellConfig;
|
|
48
|
+
|
|
49
|
+
if (!conf) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { workdir } = conf;
|
|
54
|
+
|
|
55
|
+
if (!workdir) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (currWorkdir === workdir) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
currWorkdir = workdir;
|
|
64
|
+
|
|
65
|
+
const checkProcess = () => {
|
|
66
|
+
if (terminal.process || observedProcess) {
|
|
67
|
+
if (!observedProcess) {
|
|
68
|
+
observedProcess = terminal.process;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
terminal.input(`cd /home/tutorial${workdir} && clear\n`);
|
|
72
|
+
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return false;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Check immediately
|
|
80
|
+
if (checkProcess()) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Set up interval to wait for process
|
|
85
|
+
const interval = setInterval(() => {
|
|
86
|
+
if (checkProcess()) {
|
|
87
|
+
clearInterval(interval);
|
|
88
|
+
}
|
|
89
|
+
}, 100);
|
|
90
|
+
|
|
91
|
+
return () => clearInterval(interval);
|
|
92
|
+
}, [boot, terminalConfig, storeRef, lessonLoaded]);
|
|
93
|
+
|
|
94
|
+
return null;
|
|
95
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import RailsPathLinkHandler from './RailsPathLinkHandler.tsx';
|
|
4
|
+
import { ShellConfigurator } from './ShellConfigurator';
|
|
5
|
+
import { FileManager } from './FileManager';
|
|
6
|
+
import { HelpDropdown } from './HelpDropdown';
|
|
7
|
+
import GitHubLink from './GitHubLink.astro';
|
|
8
|
+
|
|
9
|
+
const logo = path.join(import.meta.env.BASE_URL, 'logo.svg');
|
|
10
|
+
const logoDark = path.join(import.meta.env.BASE_URL, 'logo-dark.svg');
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<nav
|
|
14
|
+
class="bg-tk-elements-topBar-backgroundColor transition-theme border-b border-tk-elements-app-borderColor flex max-w-full items-center p-3 px-4 min-h-[56px]"
|
|
15
|
+
>
|
|
16
|
+
<div class="flex flex-1">
|
|
17
|
+
<a
|
|
18
|
+
href="/"
|
|
19
|
+
class="flex items-center text-tk-elements-topBar-logo-color hover:text-tk-elements-topBar-logo-colorHover"
|
|
20
|
+
>
|
|
21
|
+
{logo && <img class="h-5 w-auto dark:hidden" src={logo} />}
|
|
22
|
+
{logo && <img class="h-5 w-auto hidden dark:inline-block" src={logoDark} />}
|
|
23
|
+
</a>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="mr-2">
|
|
27
|
+
<HelpDropdown client:only />
|
|
28
|
+
</div>
|
|
29
|
+
<div class="mr-2">
|
|
30
|
+
<slot name="download-button" />
|
|
31
|
+
</div>
|
|
32
|
+
<div class="mr-2">
|
|
33
|
+
<slot name="open-in-stackblitz-link" />
|
|
34
|
+
</div>
|
|
35
|
+
<div>
|
|
36
|
+
<slot name="theme-switch" />
|
|
37
|
+
</div>
|
|
38
|
+
<div>
|
|
39
|
+
<slot name="login-button" />
|
|
40
|
+
</div>
|
|
41
|
+
<div class="mr-2">
|
|
42
|
+
<GitHubLink />
|
|
43
|
+
</div>
|
|
44
|
+
</nav>
|
|
45
|
+
|
|
46
|
+
<RailsPathLinkHandler client:only />
|
|
47
|
+
<ShellConfigurator client:only />
|
|
48
|
+
<FileManager client:only />
|
|
File without changes
|
package/template/src/content/tutorial/1-getting-started/1-creating-your-first-rails-app/content.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: lesson
|
|
3
|
+
title: Creating your first Rails app
|
|
4
|
+
editor: false
|
|
5
|
+
custom:
|
|
6
|
+
shell:
|
|
7
|
+
workdir: "/workspace/store"
|
|
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
|
+
```
|
package/template/src/content/tutorial/1-getting-started/2-rails-console/_files/workspace/.keep
ADDED
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
<!-- hit Enter -->
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
If the line "8.0.2" appears, it works!
|
|
File without changes
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: lesson
|
|
3
|
+
title: CRUD Operations
|
|
4
|
+
focus: /workspace/store/app/controllers/products_controller.rb
|
|
5
|
+
previews: [3000]
|
|
6
|
+
mainCommand: ['node scripts/rails.js server', 'Starting Rails server']
|
|
7
|
+
prepareCommands:
|
|
8
|
+
- ['npm install', 'Preparing Ruby runtime']
|
|
9
|
+
- ['node scripts/rails.js db:prepare', 'Prepare development database']
|
|
10
|
+
custom:
|
|
11
|
+
shell:
|
|
12
|
+
workdir: '/workspace/store'
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# CRUD Operations in Rails
|
|
16
|
+
|
|
17
|
+
**CRUD** stands for Create, Read, Update, and Delete - the four basic operations you can perform on data. Rails makes CRUD operations simple and intuitive.
|
|
18
|
+
|
|
19
|
+
## The Seven RESTful Actions
|
|
20
|
+
|
|
21
|
+
Rails controllers typically include these seven standard actions:
|
|
22
|
+
|
|
23
|
+
### Reading Data
|
|
24
|
+
- **`index`** - List all products
|
|
25
|
+
- **`show`** - Display a single product
|
|
26
|
+
|
|
27
|
+
### Creating Data
|
|
28
|
+
- **`new`** - Show form to create a product
|
|
29
|
+
- **`create`** - Process form submission and save product
|
|
30
|
+
|
|
31
|
+
### Updating Data
|
|
32
|
+
- **`edit`** - Show form to edit a product
|
|
33
|
+
- **`update`** - Process form submission and update product
|
|
34
|
+
|
|
35
|
+
### Deleting Data
|
|
36
|
+
- **`destroy`** - Delete a product
|
|
37
|
+
|
|
38
|
+
## Try the CRUD Operations
|
|
39
|
+
|
|
40
|
+
1. **View all products** - The home page shows the `index` action
|
|
41
|
+
2. **Create a new product** - Click "New Product"
|
|
42
|
+
3. **View a product** - Click on any product name
|
|
43
|
+
4. **Edit a product** - Click "Edit" on any product
|
|
44
|
+
5. **Delete a product** - Click "Delete" (with confirmation)
|
|
45
|
+
|
|
46
|
+
## Routes and Actions
|
|
47
|
+
|
|
48
|
+
Rails automatically creates RESTful routes for your resources:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
# In config/routes.rb
|
|
52
|
+
resources :products
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This generates these routes:
|
|
56
|
+
|
|
57
|
+
| HTTP Method | URL | Controller Action | Purpose |
|
|
58
|
+
|-------------|-----|-------------------|---------|
|
|
59
|
+
| GET | `/products` | `index` | List all products |
|
|
60
|
+
| GET | `/products/new` | `new` | Show new product form |
|
|
61
|
+
| POST | `/products` | `create` | Create a product |
|
|
62
|
+
| GET | `/products/1` | `show` | Show product #1 |
|
|
63
|
+
| GET | `/products/1/edit` | `edit` | Show edit form for product #1 |
|
|
64
|
+
| PATCH/PUT | `/products/1` | `update` | Update product #1 |
|
|
65
|
+
| DELETE | `/products/1` | `destroy` | Delete product #1 |
|
|
66
|
+
|
|
67
|
+
## Strong Parameters
|
|
68
|
+
|
|
69
|
+
Notice how the controller uses **strong parameters** for security:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
def product_params
|
|
73
|
+
params.expect(product: [ :name, :description, :price ])
|
|
74
|
+
end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
This prevents users from submitting malicious data by only allowing specified parameters.
|
|
78
|
+
|
|
79
|
+
## Forms and Validations
|
|
80
|
+
|
|
81
|
+
Rails forms automatically:
|
|
82
|
+
- Handle CSRF protection
|
|
83
|
+
- Display validation errors
|
|
84
|
+
- Maintain form state on errors
|
|
85
|
+
|
|
86
|
+
Try creating a product with invalid data to see validation in action!
|
|
87
|
+
|
|
88
|
+
:::tip
|
|
89
|
+
Rails follows RESTful conventions, making your applications predictable and maintainable. The seven standard actions cover most use cases for managing resources.
|
|
90
|
+
:::
|
|
91
|
+
|
|
92
|
+
## Experiment
|
|
93
|
+
|
|
94
|
+
Try modifying the controller:
|
|
95
|
+
1. Add a search feature to the `index` action
|
|
96
|
+
2. Add custom validations to the Product model
|
|
97
|
+
3. Customize the success messages after create/update/delete
|
|
98
|
+
|
|
99
|
+
CRUD operations are the foundation of most web applications!
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
---
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Root, InlineCode } from 'mdast';
|
|
2
|
+
import type { Plugin } from 'unified';
|
|
3
|
+
import { visit } from 'unist-util-visit';
|
|
4
|
+
|
|
5
|
+
// rails path patterns
|
|
6
|
+
const RAILS_PATH_PATTERN = /^(app|db|config|test)\/.+$/;
|
|
7
|
+
|
|
8
|
+
export interface RailsPathLinkOptions {
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const remarkRailsPathLinks: Plugin<[RailsPathLinkOptions?], Root> = (options = {}) => {
|
|
13
|
+
const className = options.className || 'rails-path-link';
|
|
14
|
+
|
|
15
|
+
return (tree, _) => {
|
|
16
|
+
visit(tree, 'inlineCode', (node: InlineCode, index, parent) => {
|
|
17
|
+
if (!parent || typeof index !== 'number') {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// check if the inline code content matches Rails path pattern
|
|
22
|
+
const content = node.value;
|
|
23
|
+
|
|
24
|
+
if (!RAILS_PATH_PATTERN.test(content)) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// replace the inline code node with an HTML node containing a link
|
|
29
|
+
const htmlNode = {
|
|
30
|
+
type: 'html',
|
|
31
|
+
value: `<button class="${className}" data-rails-path="${content}"><code>${content}</code></button>`,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
parent.children[index] = htmlNode as any;
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default remarkRailsPathLinks;
|
|
File without changes
|
package/template/src/templates/crud-products/workspace/store/app/controllers/products_controller.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
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
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# This file is auto-generated from the current state of the database. Instead
|
|
2
|
+
# of editing this file, please use the migrations feature of Active Record to
|
|
3
|
+
# incrementally modify your database, and then regenerate this schema definition.
|
|
4
|
+
#
|
|
5
|
+
# This file is the source Rails uses to define your schema when running `bin/rails
|
|
6
|
+
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
|
7
|
+
# be faster and is potentially less error prone than running all of your
|
|
8
|
+
# migrations from scratch. Old migrations may fail to apply correctly if those
|
|
9
|
+
# migrations use external dependencies or application code.
|
|
10
|
+
#
|
|
11
|
+
# It's strongly recommended that you check this file into your version control system.
|
|
12
|
+
|
|
13
|
+
ActiveRecord::Schema[8.0].define(version: 2025_05_21_010850) do
|
|
14
|
+
# These are extensions that must be enabled in order to support this database
|
|
15
|
+
enable_extension "pg_catalog.plpgsql"
|
|
16
|
+
|
|
17
|
+
create_table "products", force: :cascade do |t|
|
|
18
|
+
t.string "name"
|
|
19
|
+
t.datetime "created_at", precision: nil, null: false
|
|
20
|
+
t.datetime "updated_at", precision: nil, null: false
|
|
21
|
+
end
|
|
22
|
+
end
|