diff --git a/8_0/ch01/.devcontainer/devcontainer.json b/8_0/ch01/.devcontainer/devcontainer.json new file mode 100644 index 00000000..e7243217 --- /dev/null +++ b/8_0/ch01/.devcontainer/devcontainer.json @@ -0,0 +1,105 @@ +// Specifications: https://containers.dev/implementors/json_reference/ +// Format details: https://aka.ms/devcontainer.json +// Config options: https://github.com/microsoft/vscode-dev-containers/tree/main/containers/ruby +{ + "name": "railstutorial", + + // Universal is well-customized image for Codespaces: + // https://hub.docker.com/_/microsoft-devcontainers-universal + //"image": "mcr.microsoft.com/devcontainers/universal:2", + //"features": { + // "ghcr.io/devcontainers/features/ruby:1": {} + //}, + + // Use Ruby image if you want to pin Ruby version like '3.2' + // https://github.com/devcontainers/images/tree/main/src/ruby + "image": "mcr.microsoft.com/devcontainers/ruby:3.2", + //"workspaceFolder": "/railstutorial", => Container build failed + + // Enable learners to choose an affordable spec, starting at minimum one. + //"hostRequirements": { + // "cpus": 2, + // "memory": "4gb", + // "storage": "32gb" + //}, + + "waitFor": "onCreateCommand", + //"onCreateCommand": "", + "onCreateCommand": "gem install solargraph -v '0.58.2' -N", + //"onCreateCommand": "gem install solargraph -v '0.50.0' -N && gem install ruby-lsp -N", + //"onCreateCommand": "gem install ruby-lsp -N", + //# => Solargraph gem not found. Run `gem install solargraph` or update your Gemfile. + "updateContentCommand": "bundle install", + "postCreateCommand": "", + "postAttachCommand": { + "server": "rails server" + }, + "customizations": { + "codespaces": { + "openFiles": [ + "app/views/hello/index.html.erb" + ] + }, + "vscode": { + "extensions": [ + "GitHub.codespaces", + "Shopify.ruby-lsp", // https://github.com/Shopify/ruby-lsp + "castwide.solargraph" // https://github.com/castwide/vscode-solargraph + //"rebornix.Ruby", // https://github.com/rubyide/vscode-ruby (Deprecated) + + ], + "settings": { + // General settings for Codespaces (VS Code) + //"ruby.useLanguageServer": true , + //"ruby.format": "rubocop", + //"ruby.lint": { "rubocop": true }, + //"ruby.intellisense": "rubyLocate", + "editor.tabSize": 2, + "editor.formatOnSave": false, // Disable onSave to show diff edited by learners only + "editor.formatOnType": false, // Disable onType for the same reason above + "editor.insertSpaces": true, // Use spaces, not tabs, to avoid errors for learners + "editor.renderWhitespace": "none", + "[ruby]": { + "editor.defaultFormatter": "castwide.solargraph", + "editor.semanticHighlighting.enabled": true // Enable semantic highlighting + }, + "files.associations": { "*.erb": "erb" }, + "emmet.includeLanguages": { "erb": "html" }, + + // Settings for Solargraph + // https://github.com/castwide/solargraph + "solargraph.useBundler": false, + "solargraph.diagnostics": false, + "solargraph.formatting": true, // Use Ctrl+Shift+P->Format to format + "solargraph.autoformat": false, + "solargraph.definitions": true, + "solargraph.completion": true, + "solargraph.references": true, + "solargraph.symbols": true, + "solargraph.rename": true, + "solargraph.hover": true, + + // Settings for Ruby LSP + // https://github.com/Shopify/ruby-lsp + "rubyLsp.rubyVersionManager": { + "identifier": "none" + }, + "rubyLsp.formatter": "none", + "rubyLsp.enabledFeatures": { + "diagnostics": false, + "formatting": false + } + } + } + }, + "remoteEnv": { + "EDITOR": "code --wait" + }, + "portsAttributes": { + "3000": { + "label": "Application", + "onAutoForward": "openPreview" + } + }, + "forwardPorts": [3000] +} diff --git a/8_0/ch01/.devcontainer/icon.svg b/8_0/ch01/.devcontainer/icon.svg new file mode 100644 index 00000000..f8444a4a --- /dev/null +++ b/8_0/ch01/.devcontainer/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/8_0/ch01/.gitattributes b/8_0/ch01/.gitattributes new file mode 100644 index 00000000..31eeee0b --- /dev/null +++ b/8_0/ch01/.gitattributes @@ -0,0 +1,7 @@ +# See https://git-scm.com/docs/gitattributes for more about git attribute files. + +# Mark the database schema as having been generated. +db/schema.rb linguist-generated + +# Mark any vendored files as having been vendored. +vendor/* linguist-vendored diff --git a/8_0/ch01/.gitignore b/8_0/ch01/.gitignore new file mode 100644 index 00000000..e1ef53ef --- /dev/null +++ b/8_0/ch01/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-* + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore uploaded files in development. +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/ +!/tmp/storage/.keep + +/public/assets + +# Ignore master key for decrypting credentials and more. +/config/master.key + +# Ignore history file used by IRB for interactive Ruby. +.irb_history + +dump.rdb diff --git a/8_0/ch01/.irbrc b/8_0/ch01/.irbrc new file mode 100644 index 00000000..3737e036 --- /dev/null +++ b/8_0/ch01/.irbrc @@ -0,0 +1 @@ +IRB.conf[:COMPLETOR] = :type # default is :regexp diff --git a/8_0/ch01/.solargraph.yml b/8_0/ch01/.solargraph.yml new file mode 100644 index 00000000..76ac24a6 --- /dev/null +++ b/8_0/ch01/.solargraph.yml @@ -0,0 +1,32 @@ +--- +include: +- "**/*.rb" +exclude: +- spec/**/* +- test/**/* +- vendor/**/* +- ".bundle/**/*" +require: +- actioncable +- actionmailer +- actionpack +- actionview +- activejob +- activemodel +- activerecord +- activestorage +- activesupport +domains: [] +reporters: +- rubocop +- require_not_found +- typecheck +formatter: + rubocop: + cops: safe + except: [] + only: [] + extra_args: [] +require_paths: [] +plugins: [] +max_files: 5000 diff --git a/8_0/ch01/.vscode/settings.json b/8_0/ch01/.vscode/settings.json new file mode 100644 index 00000000..cf85f519 --- /dev/null +++ b/8_0/ch01/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.minimap.enabled": false +} diff --git a/8_0/ch01/Gemfile b/8_0/ch01/Gemfile new file mode 100644 index 00000000..46cefd03 --- /dev/null +++ b/8_0/ch01/Gemfile @@ -0,0 +1,42 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '3.2.10' + +gem 'bootsnap', '1.16.0', require: false +gem 'concurrent-ruby', '1.3.4' +gem 'importmap-rails', '1.1.5' +gem 'jbuilder', '2.14.1' +gem 'puma', '6.6.1' +gem 'rails', '8.0.2.1' +gem 'sassc-rails', '2.1.2' +gem 'sprockets-rails', '3.4.2' +gem 'sqlite3', '2.7.3' +gem 'stimulus-rails', '1.2.1' +gem 'turbo-rails', '1.4.0' + +group :development, :test do + gem 'debug', '1.7.1', platforms: %i[mri mingw x64_mingw] + gem 'reline', '0.5.10' +end + +group :development do + gem 'irb', '1.15.2' + gem 'repl_type_completor', '0.1.10' + gem 'solargraph', '0.58.2' + gem 'web-console', '4.2.0' +end + +group :test do + gem 'capybara', '3.38.0' + gem 'guard', '2.18.0' + gem 'guard-minitest', '2.4.6' + gem 'minitest', '5.18.0' + gem 'minitest-reporters', '1.6.0' + gem 'rails-controller-testing', '1.0.5' + gem 'selenium-webdriver', '4.8.3' + gem 'webdrivers', '5.2.0' +end + +# Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります +# gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] diff --git a/8_0/ch01/Gemfile.lock b/8_0/ch01/Gemfile.lock new file mode 100644 index 00000000..a3ee4202 --- /dev/null +++ b/8_0/ch01/Gemfile.lock @@ -0,0 +1,433 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + actionmailer (8.0.2.1) + actionpack (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.0.2.1) + actionview (= 8.0.2.1) + activesupport (= 8.0.2.1) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.0.2.1) + actionpack (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (8.0.2.1) + activesupport (= 8.0.2.1) + builder (~> 3.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.3.6) + activemodel (8.0.2.1) + activesupport (= 8.0.2.1) + activerecord (8.0.2.1) + activemodel (= 8.0.2.1) + activesupport (= 8.0.2.1) + timeout (>= 0.4.0) + activestorage (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activesupport (= 8.0.2.1) + marcel (~> 1.0) + activesupport (8.0.2.1) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + ansi (1.5.0) + ast (2.4.3) + backport (1.2.0) + base64 (0.3.0) + benchmark (0.4.1) + bigdecimal (3.2.3) + bindex (0.8.1) + bootsnap (1.16.0) + msgpack (~> 1.2) + builder (3.3.0) + capybara (3.38.0) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + coderay (1.1.3) + concurrent-ruby (1.3.4) + connection_pool (2.5.4) + crass (1.0.6) + date (3.4.1) + debug (1.7.1) + irb (>= 1.5.0) + reline (>= 0.3.1) + diff-lcs (1.6.2) + drb (2.2.3) + erb (5.0.2) + erubi (1.13.1) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) + formatador (1.2.0) + reline + globalid (1.2.1) + activesupport (>= 6.1) + guard (2.18.0) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.13.0) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-minitest (2.4.6) + guard-compat (~> 1.2) + minitest (>= 3.0) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + importmap-rails (1.1.5) + actionpack (>= 6.0.0) + railties (>= 6.0.0) + io-console (0.8.1) + irb (1.15.2) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jaro_winkler (1.6.1) + jbuilder (2.14.1) + actionview (>= 7.0.0) + activesupport (>= 7.0.0) + json (2.13.2) + kramdown (2.5.1) + rexml (>= 3.3.9) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) + loofah (2.24.1) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + lumberjack (1.4.1) + mail (2.8.1) + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.0.4) + matrix (0.4.3) + method_source (1.1.0) + mini_mime (1.1.5) + minitest (5.18.0) + minitest-reporters (1.6.0) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + msgpack (1.8.0) + nenv (0.3.0) + net-imap (0.5.10) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.4) + nokogiri (1.19.1-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-musl) + racc (~> 1.4) + notiffany (0.1.3) + nenv (~> 0.1) + shellany (~> 0.0) + observer (0.1.2) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.9.0) + ast (~> 2.4.1) + racc + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + prism (1.4.0) + pry (0.15.2) + coderay (~> 1.1) + method_source (~> 1.0) + psych (5.2.6) + date + stringio + public_suffix (6.0.2) + puma (6.6.1) + nio4r (~> 2.0) + racc (1.8.1) + rack (3.2.5) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.2.1) + rack (>= 3) + rails (8.0.2.1) + actioncable (= 8.0.2.1) + actionmailbox (= 8.0.2.1) + actionmailer (= 8.0.2.1) + actionpack (= 8.0.2.1) + actiontext (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activemodel (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + bundler (>= 1.15.0) + railties (= 8.0.2.1) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.0) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rbs (3.6.1) + logger + rdoc (6.14.2) + erb + psych (>= 4.0.0) + regexp_parser (2.11.2) + reline (0.5.10) + io-console (~> 0.5) + repl_type_completor (0.1.10) + prism (~> 1.0) + rbs (>= 2.7.0, < 4.0.0) + reverse_markdown (3.0.0) + nokogiri + rexml (3.4.2) + rubocop (1.80.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.46.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.46.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + ruby-progressbar (1.13.0) + rubyzip (2.4.1) + sassc (2.4.0) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt + securerandom (0.4.1) + selenium-webdriver (4.8.3) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) + shellany (0.0.1) + solargraph (0.58.2) + backport (~> 1.2) + benchmark (~> 0.4) + bundler (~> 2.0) + diff-lcs (~> 1.4) + jaro_winkler (~> 1.6, >= 1.6.1) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.1) + logger (~> 1.6) + observer (~> 0.1) + ostruct (~> 0.6) + parser (~> 3.0) + prism (~> 1.4) + rbs (~> 3.6.1) + reverse_markdown (~> 3.0) + rubocop (~> 1.38) + thor (~> 1.0) + tilt (~> 2.0) + yard (~> 0.9, >= 0.9.24) + yard-solargraph (~> 0.1) + sprockets (4.2.2) + concurrent-ruby (~> 1.0) + logger + rack (>= 2.2.4, < 4) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + sprockets (>= 3.0.0) + sqlite3 (2.7.3-aarch64-linux-gnu) + sqlite3 (2.7.3-aarch64-linux-musl) + sqlite3 (2.7.3-arm-linux-gnu) + sqlite3 (2.7.3-arm-linux-musl) + sqlite3 (2.7.3-arm64-darwin) + sqlite3 (2.7.3-x86_64-darwin) + sqlite3 (2.7.3-x86_64-linux-gnu) + sqlite3 (2.7.3-x86_64-linux-musl) + stimulus-rails (1.2.1) + railties (>= 6.0.0) + stringio (3.1.7) + thor (1.4.0) + tilt (2.6.1) + timeout (0.4.3) + turbo-rails (1.4.0) + actionpack (>= 6.0.0) + activejob (>= 6.0.0) + railties (>= 6.0.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.1.5) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + uri (1.0.4) + useragent (0.16.11) + web-console (4.2.0) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + webdrivers (5.2.0) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (~> 4.0) + websocket (1.2.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) + yard (0.9.37) + yard-solargraph (0.1.0) + yard (~> 0.9) + zeitwerk (2.7.3) + +PLATFORMS + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + bootsnap (= 1.16.0) + capybara (= 3.38.0) + concurrent-ruby (= 1.3.4) + debug (= 1.7.1) + guard (= 2.18.0) + guard-minitest (= 2.4.6) + importmap-rails (= 1.1.5) + irb (= 1.15.2) + jbuilder (= 2.14.1) + minitest (= 5.18.0) + minitest-reporters (= 1.6.0) + puma (= 6.6.1) + rails (= 8.0.2.1) + rails-controller-testing (= 1.0.5) + reline (= 0.5.10) + repl_type_completor (= 0.1.10) + sassc-rails (= 2.1.2) + selenium-webdriver (= 4.8.3) + solargraph (= 0.58.2) + sprockets-rails (= 3.4.2) + sqlite3 (= 2.7.3) + stimulus-rails (= 1.2.1) + turbo-rails (= 1.4.0) + web-console (= 4.2.0) + webdrivers (= 5.2.0) + +RUBY VERSION + ruby 3.2.10p266 + +BUNDLED WITH + 2.5.6 diff --git a/8_0/ch01/LICENSE b/8_0/ch01/LICENSE new file mode 100644 index 00000000..20622e0c --- /dev/null +++ b/8_0/ch01/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 GitHub & 2023 YassLab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/8_0/ch01/README.md b/8_0/ch01/README.md new file mode 100644 index 00000000..7d64284c --- /dev/null +++ b/8_0/ch01/README.md @@ -0,0 +1,7 @@ +# Ruby on Rails Tutorial + +## "hello, world!" + +This is the first application for the +[*Ruby on Rails Tutorial*](https://railstutorial.jp/) +by [Michael Hartl](https://www.michaelhartl.com/). Hello, world! diff --git a/8_0/ch01/Rakefile b/8_0/ch01/Rakefile new file mode 100644 index 00000000..9a5ea738 --- /dev/null +++ b/8_0/ch01/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/8_0/ch01/app/assets/config/manifest.js b/8_0/ch01/app/assets/config/manifest.js new file mode 100644 index 00000000..ddd546a0 --- /dev/null +++ b/8_0/ch01/app/assets/config/manifest.js @@ -0,0 +1,4 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js diff --git a/8_0/ch01/app/assets/images/.keep b/8_0/ch01/app/assets/images/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch01/app/assets/stylesheets/application.css b/8_0/ch01/app/assets/stylesheets/application.css new file mode 100644 index 00000000..288b9ab7 --- /dev/null +++ b/8_0/ch01/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/8_0/ch01/app/assets/stylesheets/hello.css b/8_0/ch01/app/assets/stylesheets/hello.css new file mode 100644 index 00000000..497881a4 --- /dev/null +++ b/8_0/ch01/app/assets/stylesheets/hello.css @@ -0,0 +1,40 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.codespaces { + text-align: center; +} +.codespaces code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} +.codespaces-logo { + height: 40vmin; + pointer-events: none; +} +.codespaces-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} +.codespaces-link { + color: #61dafb; +} + +.heart { + color: #ff0000; +} +.small { + font-size: 0.75rem; +} diff --git a/8_0/ch01/app/channels/application_cable/channel.rb b/8_0/ch01/app/channels/application_cable/channel.rb new file mode 100644 index 00000000..d6726972 --- /dev/null +++ b/8_0/ch01/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/8_0/ch01/app/channels/application_cable/connection.rb b/8_0/ch01/app/channels/application_cable/connection.rb new file mode 100644 index 00000000..0ff5442f --- /dev/null +++ b/8_0/ch01/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/8_0/ch01/app/controllers/application_controller.rb b/8_0/ch01/app/controllers/application_controller.rb new file mode 100644 index 00000000..37e51416 --- /dev/null +++ b/8_0/ch01/app/controllers/application_controller.rb @@ -0,0 +1,5 @@ +class ApplicationController < ActionController::Base + def hello + render html: 'hello, world!' + end +end diff --git a/8_0/ch01/app/controllers/concerns/.keep b/8_0/ch01/app/controllers/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch01/app/controllers/hello_controller.rb b/8_0/ch01/app/controllers/hello_controller.rb new file mode 100644 index 00000000..52037537 --- /dev/null +++ b/8_0/ch01/app/controllers/hello_controller.rb @@ -0,0 +1,4 @@ +class HelloController < ApplicationController + def index + end +end diff --git a/8_0/ch01/app/helpers/application_helper.rb b/8_0/ch01/app/helpers/application_helper.rb new file mode 100644 index 00000000..de6be794 --- /dev/null +++ b/8_0/ch01/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/8_0/ch01/app/helpers/hello_codespaces_helper.rb b/8_0/ch01/app/helpers/hello_codespaces_helper.rb new file mode 100644 index 00000000..874a2ca2 --- /dev/null +++ b/8_0/ch01/app/helpers/hello_codespaces_helper.rb @@ -0,0 +1,2 @@ +module HelloCodespacesHelper +end diff --git a/8_0/ch01/app/javascript/application.js b/8_0/ch01/app/javascript/application.js new file mode 100644 index 00000000..0d7b4940 --- /dev/null +++ b/8_0/ch01/app/javascript/application.js @@ -0,0 +1,3 @@ +// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails +import "@hotwired/turbo-rails" +import "controllers" diff --git a/8_0/ch01/app/javascript/controllers/application.js b/8_0/ch01/app/javascript/controllers/application.js new file mode 100644 index 00000000..1213e85c --- /dev/null +++ b/8_0/ch01/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/8_0/ch01/app/javascript/controllers/hello_controller.js b/8_0/ch01/app/javascript/controllers/hello_controller.js new file mode 100644 index 00000000..5975c078 --- /dev/null +++ b/8_0/ch01/app/javascript/controllers/hello_controller.js @@ -0,0 +1,7 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.element.textContent = "Hello World!" + } +} diff --git a/8_0/ch01/app/javascript/controllers/index.js b/8_0/ch01/app/javascript/controllers/index.js new file mode 100644 index 00000000..54ad4cad --- /dev/null +++ b/8_0/ch01/app/javascript/controllers/index.js @@ -0,0 +1,11 @@ +// Import and register all your controllers from the importmap under controllers/* + +import { application } from "controllers/application" + +// Eager load all controllers defined in the import map under controllers/**/*_controller +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) + +// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) +// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" +// lazyLoadControllersFrom("controllers", application) diff --git a/8_0/ch01/app/jobs/application_job.rb b/8_0/ch01/app/jobs/application_job.rb new file mode 100644 index 00000000..d394c3d1 --- /dev/null +++ b/8_0/ch01/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/8_0/ch01/app/mailers/application_mailer.rb b/8_0/ch01/app/mailers/application_mailer.rb new file mode 100644 index 00000000..3c34c814 --- /dev/null +++ b/8_0/ch01/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "from@example.com" + layout "mailer" +end diff --git a/8_0/ch01/app/models/application_record.rb b/8_0/ch01/app/models/application_record.rb new file mode 100644 index 00000000..b63caeb8 --- /dev/null +++ b/8_0/ch01/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/8_0/ch01/app/models/concerns/.keep b/8_0/ch01/app/models/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch01/app/views/hello/index.html.erb b/8_0/ch01/app/views/hello/index.html.erb new file mode 100644 index 00000000..71661105 --- /dev/null +++ b/8_0/ch01/app/views/hello/index.html.erb @@ -0,0 +1,16 @@ +
+
+ +

+ Codespaces + ♥️ + Railsチュートリアル +

+

+ 🎓✨
+ ロゴ画像が表示されたらセットアップ完了です! +

+
+
diff --git a/8_0/ch01/app/views/layouts/application.html.erb b/8_0/ch01/app/views/layouts/application.html.erb new file mode 100644 index 00000000..03843a74 --- /dev/null +++ b/8_0/ch01/app/views/layouts/application.html.erb @@ -0,0 +1,16 @@ + + + + Sample App + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + + + + <%= yield %> + + diff --git a/8_0/ch01/app/views/layouts/mailer.html.erb b/8_0/ch01/app/views/layouts/mailer.html.erb new file mode 100644 index 00000000..cbd34d2e --- /dev/null +++ b/8_0/ch01/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/8_0/ch01/app/views/layouts/mailer.text.erb b/8_0/ch01/app/views/layouts/mailer.text.erb new file mode 100644 index 00000000..37f0bddb --- /dev/null +++ b/8_0/ch01/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/8_0/ch01/bin/bundle b/8_0/ch01/bin/bundle new file mode 100755 index 00000000..981e650b --- /dev/null +++ b/8_0/ch01/bin/bundle @@ -0,0 +1,114 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + requirement = bundler_gem_version.approximate_recommendation + + return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0") + + requirement += ".a" if bundler_gem_version.prerelease? + + requirement + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/8_0/ch01/bin/dev b/8_0/ch01/bin/dev new file mode 100755 index 00000000..5f91c205 --- /dev/null +++ b/8_0/ch01/bin/dev @@ -0,0 +1,2 @@ +#!/usr/bin/env ruby +exec "./bin/rails", "server", *ARGV diff --git a/8_0/ch01/bin/importmap b/8_0/ch01/bin/importmap new file mode 100755 index 00000000..36502ab1 --- /dev/null +++ b/8_0/ch01/bin/importmap @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require_relative "../config/application" +require "importmap/commands" diff --git a/8_0/ch01/bin/rails b/8_0/ch01/bin/rails new file mode 100755 index 00000000..efc03774 --- /dev/null +++ b/8_0/ch01/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/8_0/ch01/bin/rake b/8_0/ch01/bin/rake new file mode 100755 index 00000000..4fbf10b9 --- /dev/null +++ b/8_0/ch01/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/8_0/ch01/bin/rubocop b/8_0/ch01/bin/rubocop new file mode 100755 index 00000000..40330c0f --- /dev/null +++ b/8_0/ch01/bin/rubocop @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +# explicit rubocop config increases performance slightly while avoiding config confusion. +ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) + +load Gem.bin_path("rubocop", "rubocop") diff --git a/8_0/ch01/bin/setup b/8_0/ch01/bin/setup new file mode 100755 index 00000000..be3db3c0 --- /dev/null +++ b/8_0/ch01/bin/setup @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +require "fileutils" + +APP_ROOT = File.expand_path("..", __dir__) + +def system!(*args) + system(*args, exception: true) +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts "== Installing dependencies ==" + system("bundle check") || system!("bundle install") + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + puts "\n== Removing old logs and tempfiles ==" + system! "bin/rails log:clear tmp:clear" + + unless ARGV.include?("--skip-server") + puts "\n== Starting development server ==" + STDOUT.flush # flush the output before exec(2) so that it displays + exec "bin/dev" + end +end diff --git a/8_0/ch01/config.ru b/8_0/ch01/config.ru new file mode 100644 index 00000000..4a3c09a6 --- /dev/null +++ b/8_0/ch01/config.ru @@ -0,0 +1,6 @@ +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/8_0/ch01/config/application.rb b/8_0/ch01/config/application.rb new file mode 100644 index 00000000..0461e72f --- /dev/null +++ b/8_0/ch01/config/application.rb @@ -0,0 +1,27 @@ +require_relative "boot" + +require "rails/all" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module SampleApp + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 8.0 + + # Please, add to the `ignore` list any other `lib` subdirectories that do + # not contain `.rb` files, or that should not be reloaded or eager loaded. + # Common ones are `templates`, `generators`, or `middleware`, for example. + config.autoload_lib(ignore: %w[assets tasks]) + + # Configuration for the application, engines, and railties goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + # + # config.time_zone = "Central Time (US & Canada)" + # config.eager_load_paths << Rails.root.join("extras") + end +end diff --git a/8_0/ch01/config/boot.rb b/8_0/ch01/config/boot.rb new file mode 100644 index 00000000..988a5ddc --- /dev/null +++ b/8_0/ch01/config/boot.rb @@ -0,0 +1,4 @@ +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "bundler/setup" # Set up gems listed in the Gemfile. +require "bootsnap/setup" # Speed up boot time by caching expensive operations. diff --git a/8_0/ch01/config/cable.yml b/8_0/ch01/config/cable.yml new file mode 100644 index 00000000..ae7a2256 --- /dev/null +++ b/8_0/ch01/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: codespaces_try_rails_production diff --git a/8_0/ch01/config/credentials.yml.enc b/8_0/ch01/config/credentials.yml.enc new file mode 100644 index 00000000..339d64d2 --- /dev/null +++ b/8_0/ch01/config/credentials.yml.enc @@ -0,0 +1 @@ +NCs9M8uakg1CDYJENuGaipzymY8uG6i5gzghsy4npa3gYmGfYKPHMnEcJHCVHlM3gxDNJBteUqkRCxHQ5WQId8hzgxoQCL5RX7rtQ8XUzkJwgSUjKvbCrG5atjKkN9XuL91is5vtEN5W7vEz3qxN7Rp33QbNFiDfLs71F3zHVDYEMi6Dy1QMhVCa1v/tyRjBu/5m37RuiYF5FzWJnh4LhexSVr7Agm4LRLLN6qLCpoAe6D3TPHHBdtu7Pvy8jlrEfnnkUJ61wrj8+kOL4uLtpFShdYKhDkoTjQk7TCOgWyifnHAXazkBZoJZ1QYv/tZ9/ZoHvMEQ2dtGHlyTX8fbKtI13d2CFjGaDNKvRuurxE8dbeyiTfbycvve46+cz9OTlmhh0SGt24R1WV6GBKoDUeoHrIiBvW5qmKka--dQ0rodxmfgFLg2y/--t2x0gQjh8FYlk8v79vLSNg== \ No newline at end of file diff --git a/8_0/ch01/config/database.yml b/8_0/ch01/config/database.yml new file mode 100644 index 00000000..fcba57f1 --- /dev/null +++ b/8_0/ch01/config/database.yml @@ -0,0 +1,25 @@ +# SQLite. Versions 3.8.0 and up are supported. +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem "sqlite3" +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/8_0/ch01/config/environment.rb b/8_0/ch01/config/environment.rb new file mode 100644 index 00000000..cac53157 --- /dev/null +++ b/8_0/ch01/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/8_0/ch01/config/environments/development.rb b/8_0/ch01/config/environments/development.rb new file mode 100644 index 00000000..910cef99 --- /dev/null +++ b/8_0/ch01/config/environments/development.rb @@ -0,0 +1,86 @@ +require 'active_support/core_ext/integer/time' + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Make code changes take effect immediately without server restart. + config.enable_reloading = true + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing. + config.server_timing = true + + # Add the following line to disable forgery_protection_origin_check + config.action_controller.forgery_protection_origin_check = false + + # Enable/disable Action Controller caching. By default Action Controller caching is disabled. + # Run rails dev:cache to toggle Action Controller caching. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + config.public_file_server.headers = { 'cache-control' => "public, max-age=#{2.days.to_i}" } + else + config.action_controller.perform_caching = false + end + + # Change to :null_store to avoid any caching. + config.cache_store = :memory_store + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Make template changes take effect immediately. + config.action_mailer.perform_caching = false + + # Set localhost to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Append comments with runtime information tags to SQL queries in logs. + config.active_record.query_log_tags_enabled = true + + # Highlight code that enqueued background job in logs. + config.active_job.verbose_enqueue_logs = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true + + # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. + # config.generators.apply_rubocop_autocorrect_after_generate! + + pf_domain = ENV['GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN'] + config.action_dispatch.default_headers = { + 'X-Frame-Options' => "ALLOW-FROM #{pf_domain}" + } + + # Allow requests from our preview domain. + pf_host = "#{ENV['CODESPACE_NAME']}-3000.#{pf_domain}" + config.hosts << pf_host + + config.action_cable.allowed_request_origins = ["https://#{pf_host}"] +end diff --git a/8_0/ch01/config/environments/production.rb b/8_0/ch01/config/environments/production.rb new file mode 100644 index 00000000..17496077 --- /dev/null +++ b/8_0/ch01/config/environments/production.rb @@ -0,0 +1,89 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.enable_reloading = false + + # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). + config.eager_load = true + + # Full error reports are disabled. + config.consider_all_requests_local = false + + # Turn on fragment caching in view templates. + config.action_controller.perform_caching = true + + # Cache assets for far-future expiry since they are all digest stamped. + config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" } + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = "http://assets.example.com" + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Assume all access to the app is happening through a SSL-terminating reverse proxy. + config.assume_ssl = true + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = true + + # Skip http-to-https redirect for the default health check endpoint. + # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } + + # Log to STDOUT with the current request id as a default log tag. + config.log_tags = [ :request_id ] + config.logger = ActiveSupport::TaggedLogging.logger(STDOUT) + + # Change to "debug" to log everything (including potentially personally-identifiable information!) + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + + # Prevent health checks from clogging up the logs. + config.silence_healthcheck_path = "/up" + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Replace the default in-process memory cache store with a durable alternative. + # config.cache_store = :mem_cache_store + + # Replace the default in-process and non-durable queuing backend for Active Job. + # config.active_job.queue_adapter = :resque + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. + # config.action_mailer.smtp_settings = { + # user_name: Rails.application.credentials.dig(:smtp, :user_name), + # password: Rails.application.credentials.dig(:smtp, :password), + # address: "smtp.example.com", + # port: 587, + # authentication: :plain + # } + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Only use :id for inspections in production. + config.active_record.attributes_for_inspect = [ :id ] + + # Enable DNS rebinding protection and other `Host` header attacks. + # config.hosts = [ + # "example.com", # Allow requests from example.com + # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` + # ] + # + # Skip DNS rebinding protection for the default health check endpoint. + # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } +end diff --git a/8_0/ch01/config/environments/test.rb b/8_0/ch01/config/environments/test.rb new file mode 100644 index 00000000..c2095b11 --- /dev/null +++ b/8_0/ch01/config/environments/test.rb @@ -0,0 +1,53 @@ +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # While tests run files are not watched, reloading is not necessary. + config.enable_reloading = false + + # Eager loading loads your entire application. When running a single test locally, + # this is usually not necessary, and can slow down your test suite. However, it's + # recommended that you enable it in continuous integration systems to ensure eager + # loading is working properly before deploying your code. + config.eager_load = ENV["CI"].present? + + # Configure public file server for tests with cache-control for performance. + config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } + + # Show full error reports. + config.consider_all_requests_local = true + config.cache_store = :null_store + + # Render exception templates for rescuable exceptions and raise for other exceptions. + config.action_dispatch.show_exceptions = :rescuable + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :test + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true +end diff --git a/8_0/ch01/config/importmap.rb b/8_0/ch01/config/importmap.rb new file mode 100644 index 00000000..8dce42d4 --- /dev/null +++ b/8_0/ch01/config/importmap.rb @@ -0,0 +1,7 @@ +# Pin npm packages by running ./bin/importmap + +pin "application", preload: true +pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true +pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true +pin_all_from "app/javascript/controllers", under: "controllers" diff --git a/8_0/ch01/config/initializers/assets.rb b/8_0/ch01/config/initializers/assets.rb new file mode 100644 index 00000000..48732442 --- /dev/null +++ b/8_0/ch01/config/initializers/assets.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = "1.0" + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path diff --git a/8_0/ch01/config/initializers/content_security_policy.rb b/8_0/ch01/config/initializers/content_security_policy.rb new file mode 100644 index 00000000..b3076b38 --- /dev/null +++ b/8_0/ch01/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header + +# Rails.application.configure do +# config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end +# +# # Generate session nonces for permitted importmap, inline scripts, and inline styles. +# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } +# config.content_security_policy_nonce_directives = %w(script-src style-src) +# +# # Report violations without enforcing the policy. +# # config.content_security_policy_report_only = true +# end diff --git a/8_0/ch01/config/initializers/filter_parameter_logging.rb b/8_0/ch01/config/initializers/filter_parameter_logging.rb new file mode 100644 index 00000000..c0b717f7 --- /dev/null +++ b/8_0/ch01/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. +# Use this to limit dissemination of sensitive information. +# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. +Rails.application.config.filter_parameters += [ + :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc +] diff --git a/8_0/ch01/config/initializers/inflections.rb b/8_0/ch01/config/initializers/inflections.rb new file mode 100644 index 00000000..3860f659 --- /dev/null +++ b/8_0/ch01/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym "RESTful" +# end diff --git a/8_0/ch01/config/initializers/new_framework_defaults_8_0.rb b/8_0/ch01/config/initializers/new_framework_defaults_8_0.rb new file mode 100644 index 00000000..92efa951 --- /dev/null +++ b/8_0/ch01/config/initializers/new_framework_defaults_8_0.rb @@ -0,0 +1,30 @@ +# Be sure to restart your server when you modify this file. +# +# This file eases your Rails 8.0 framework defaults upgrade. +# +# Uncomment each configuration one by one to switch to the new default. +# Once your application is ready to run with all new defaults, you can remove +# this file and set the `config.load_defaults` to `8.0`. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. +# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html + +### +# Specifies whether `to_time` methods preserve the UTC offset of their receivers or preserves the timezone. +# If set to `:zone`, `to_time` methods will use the timezone of their receivers. +# If set to `:offset`, `to_time` methods will use the UTC offset. +# If `false`, `to_time` methods will convert to the local system UTC offset instead. +#++ +# Rails.application.config.active_support.to_time_preserves_timezone = :zone + +### +# When both `If-Modified-Since` and `If-None-Match` are provided by the client +# only consider `If-None-Match` as specified by RFC 7232 Section 6. +# If set to `false` both conditions need to be satisfied. +#++ +# Rails.application.config.action_dispatch.strict_freshness = true + +### +# Set `Regexp.timeout` to `1`s by default to improve security over Regexp Denial-of-Service attacks. +#++ +# Regexp.timeout = 1 diff --git a/8_0/ch01/config/initializers/permissions_policy.rb b/8_0/ch01/config/initializers/permissions_policy.rb new file mode 100644 index 00000000..00f64d71 --- /dev/null +++ b/8_0/ch01/config/initializers/permissions_policy.rb @@ -0,0 +1,11 @@ +# Define an application-wide HTTP permissions policy. For further +# information see https://developers.google.com/web/updates/2018/06/feature-policy +# +# Rails.application.config.permissions_policy do |f| +# f.camera :none +# f.gyroscope :none +# f.microphone :none +# f.usb :none +# f.fullscreen :self +# f.payment :self, "https://secure.example.com" +# end diff --git a/8_0/ch01/config/locales/en.yml b/8_0/ch01/config/locales/en.yml new file mode 100644 index 00000000..8ca56fc7 --- /dev/null +++ b/8_0/ch01/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# "true": "foo" +# +# To learn more, please read the Rails Internationalization guide +# available at https://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/8_0/ch01/config/puma.rb b/8_0/ch01/config/puma.rb new file mode 100644 index 00000000..a248513b --- /dev/null +++ b/8_0/ch01/config/puma.rb @@ -0,0 +1,41 @@ +# This configuration file will be evaluated by Puma. The top-level methods that +# are invoked here are part of Puma's configuration DSL. For more information +# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. +# +# Puma starts a configurable number of processes (workers) and each process +# serves each request in a thread from an internal thread pool. +# +# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You +# should only set this value when you want to run 2 or more workers. The +# default is already 1. +# +# The ideal number of threads per worker depends both on how much time the +# application spends waiting for IO operations and on how much you wish to +# prioritize throughput over latency. +# +# As a rule of thumb, increasing the number of threads will increase how much +# traffic a given process can handle (throughput), but due to CRuby's +# Global VM Lock (GVL) it has diminishing returns and will degrade the +# response time (latency) of the application. +# +# The default is set to 3 threads as it's deemed a decent compromise between +# throughput and latency for the average Rails application. +# +# Any libraries that use a connection pool or another resource pool should +# be configured to provide at least as many connections as the number of +# threads. This includes Active Record's `pool` parameter in `database.yml`. +threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +port ENV.fetch("PORT", 3000) + +# Allow puma to be restarted by `bin/rails restart` command. +plugin :tmp_restart + +# Run the Solid Queue supervisor inside of Puma for single-server deployments +plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] + +# Specify the PID file. Defaults to tmp/pids/server.pid in development. +# In other environments, only set the PID file if requested. +pidfile ENV["PIDFILE"] if ENV["PIDFILE"] diff --git a/8_0/ch01/config/routes.rb b/8_0/ch01/config/routes.rb new file mode 100644 index 00000000..3f94e714 --- /dev/null +++ b/8_0/ch01/config/routes.rb @@ -0,0 +1,3 @@ +Rails.application.routes.draw do + root 'application#hello' +end diff --git a/8_0/ch01/config/storage.yml b/8_0/ch01/config/storage.yml new file mode 100644 index 00000000..4942ab66 --- /dev/null +++ b/8_0/ch01/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket-<%= Rails.env %> + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket-<%= Rails.env %> + +# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name-<%= Rails.env %> + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/8_0/ch01/db/seeds.rb b/8_0/ch01/db/seeds.rb new file mode 100644 index 00000000..bc25fce3 --- /dev/null +++ b/8_0/ch01/db/seeds.rb @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). +# +# Examples: +# +# movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }]) +# Character.create(name: "Luke", movie: movies.first) diff --git a/8_0/ch01/lib/assets/.keep b/8_0/ch01/lib/assets/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch01/lib/tasks/.keep b/8_0/ch01/lib/tasks/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch01/log/.keep b/8_0/ch01/log/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch01/public/400.html b/8_0/ch01/public/400.html new file mode 100644 index 00000000..282dbc8c --- /dev/null +++ b/8_0/ch01/public/400.html @@ -0,0 +1,114 @@ + + + + + + + The server cannot process the request due to a client error (400 Bad Request) + + + + + + + + + + + + + +
+
+ +
+
+

The server cannot process the request due to a client error. Please check the request and try again. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch01/public/404.html b/8_0/ch01/public/404.html new file mode 100644 index 00000000..c0670bc8 --- /dev/null +++ b/8_0/ch01/public/404.html @@ -0,0 +1,114 @@ + + + + + + + The page you were looking for doesn’t exist (404 Not found) + + + + + + + + + + + + + +
+
+ +
+
+

The page you were looking for doesn’t exist. You may have mistyped the address or the page may have moved. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch01/public/406-unsupported-browser.html b/8_0/ch01/public/406-unsupported-browser.html new file mode 100644 index 00000000..9532a9cc --- /dev/null +++ b/8_0/ch01/public/406-unsupported-browser.html @@ -0,0 +1,114 @@ + + + + + + + Your browser is not supported (406 Not Acceptable) + + + + + + + + + + + + + +
+
+ +
+
+

Your browser is not supported.
Please upgrade your browser to continue.

+
+
+ + + + diff --git a/8_0/ch01/public/422.html b/8_0/ch01/public/422.html new file mode 100644 index 00000000..8bcf0601 --- /dev/null +++ b/8_0/ch01/public/422.html @@ -0,0 +1,114 @@ + + + + + + + The change you wanted was rejected (422 Unprocessable Entity) + + + + + + + + + + + + + +
+
+ +
+
+

The change you wanted was rejected. Maybe you tried to change something you didn’t have access to. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch01/public/500.html b/8_0/ch01/public/500.html new file mode 100644 index 00000000..d77718c3 --- /dev/null +++ b/8_0/ch01/public/500.html @@ -0,0 +1,114 @@ + + + + + + + We’re sorry, but something went wrong (500 Internal Server Error) + + + + + + + + + + + + + +
+
+ +
+
+

We’re sorry, but something went wrong.
If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch01/public/apple-touch-icon-precomposed.png b/8_0/ch01/public/apple-touch-icon-precomposed.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch01/public/apple-touch-icon.png b/8_0/ch01/public/apple-touch-icon.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch01/public/favicon.ico b/8_0/ch01/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch01/public/icon.png b/8_0/ch01/public/icon.png new file mode 100644 index 00000000..c4c9dbfb Binary files /dev/null and b/8_0/ch01/public/icon.png differ diff --git a/8_0/ch01/public/icon.svg b/8_0/ch01/public/icon.svg new file mode 100644 index 00000000..04b34bf8 --- /dev/null +++ b/8_0/ch01/public/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/8_0/ch01/public/railstutorial.png b/8_0/ch01/public/railstutorial.png new file mode 100644 index 00000000..70835e6b Binary files /dev/null and b/8_0/ch01/public/railstutorial.png differ diff --git a/8_0/ch01/public/robots.txt b/8_0/ch01/public/robots.txt new file mode 100644 index 00000000..c19f78ab --- /dev/null +++ b/8_0/ch01/public/robots.txt @@ -0,0 +1 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/8_0/ch01/storage/.keep b/8_0/ch01/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch01/test/application_system_test_case.rb b/8_0/ch01/test/application_system_test_case.rb new file mode 100644 index 00000000..d19212ab --- /dev/null +++ b/8_0/ch01/test/application_system_test_case.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/8_0/ch01/test/channels/application_cable/connection_test.rb b/8_0/ch01/test/channels/application_cable/connection_test.rb new file mode 100644 index 00000000..800405f1 --- /dev/null +++ b/8_0/ch01/test/channels/application_cable/connection_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase + # test "connects with cookies" do + # cookies.signed[:user_id] = 42 + # + # connect + # + # assert_equal connection.user_id, "42" + # end +end diff --git a/8_0/ch01/test/controllers/.keep b/8_0/ch01/test/controllers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch01/test/controllers/hello_codespaces_controller_test.rb b/8_0/ch01/test/controllers/hello_codespaces_controller_test.rb new file mode 100644 index 00000000..54353ea6 --- /dev/null +++ b/8_0/ch01/test/controllers/hello_codespaces_controller_test.rb @@ -0,0 +1,8 @@ +require "test_helper" + +class HelloCodespacesControllerTest < ActionDispatch::IntegrationTest + # test "should get index" do + # get "/" + # assert_response :success + # end +end diff --git a/8_0/ch01/test/fixtures/files/.keep b/8_0/ch01/test/fixtures/files/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch01/test/helpers/.keep b/8_0/ch01/test/helpers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch01/test/integration/.keep b/8_0/ch01/test/integration/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch01/test/mailers/.keep b/8_0/ch01/test/mailers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch01/test/models/.keep b/8_0/ch01/test/models/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch01/test/system/.keep b/8_0/ch01/test/system/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch01/test/test_helper.rb b/8_0/ch01/test/test_helper.rb new file mode 100644 index 00000000..d713e377 --- /dev/null +++ b/8_0/ch01/test/test_helper.rb @@ -0,0 +1,13 @@ +ENV["RAILS_ENV"] ||= "test" +require_relative "../config/environment" +require "rails/test_help" + +class ActiveSupport::TestCase + # Run tests in parallel with specified workers + parallelize(workers: :number_of_processors) + + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. + fixtures :all + + # Add more helper methods to be used by all tests here... +end diff --git a/8_0/ch01/tmp/.keep b/8_0/ch01/tmp/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch01/tmp/pids/.keep b/8_0/ch01/tmp/pids/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch01/tmp/storage/.keep b/8_0/ch01/tmp/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch01/vendor/.keep b/8_0/ch01/vendor/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch01/vendor/javascript/.keep b/8_0/ch01/vendor/javascript/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/.devcontainer/devcontainer.json b/8_0/ch02/.devcontainer/devcontainer.json new file mode 100644 index 00000000..e7243217 --- /dev/null +++ b/8_0/ch02/.devcontainer/devcontainer.json @@ -0,0 +1,105 @@ +// Specifications: https://containers.dev/implementors/json_reference/ +// Format details: https://aka.ms/devcontainer.json +// Config options: https://github.com/microsoft/vscode-dev-containers/tree/main/containers/ruby +{ + "name": "railstutorial", + + // Universal is well-customized image for Codespaces: + // https://hub.docker.com/_/microsoft-devcontainers-universal + //"image": "mcr.microsoft.com/devcontainers/universal:2", + //"features": { + // "ghcr.io/devcontainers/features/ruby:1": {} + //}, + + // Use Ruby image if you want to pin Ruby version like '3.2' + // https://github.com/devcontainers/images/tree/main/src/ruby + "image": "mcr.microsoft.com/devcontainers/ruby:3.2", + //"workspaceFolder": "/railstutorial", => Container build failed + + // Enable learners to choose an affordable spec, starting at minimum one. + //"hostRequirements": { + // "cpus": 2, + // "memory": "4gb", + // "storage": "32gb" + //}, + + "waitFor": "onCreateCommand", + //"onCreateCommand": "", + "onCreateCommand": "gem install solargraph -v '0.58.2' -N", + //"onCreateCommand": "gem install solargraph -v '0.50.0' -N && gem install ruby-lsp -N", + //"onCreateCommand": "gem install ruby-lsp -N", + //# => Solargraph gem not found. Run `gem install solargraph` or update your Gemfile. + "updateContentCommand": "bundle install", + "postCreateCommand": "", + "postAttachCommand": { + "server": "rails server" + }, + "customizations": { + "codespaces": { + "openFiles": [ + "app/views/hello/index.html.erb" + ] + }, + "vscode": { + "extensions": [ + "GitHub.codespaces", + "Shopify.ruby-lsp", // https://github.com/Shopify/ruby-lsp + "castwide.solargraph" // https://github.com/castwide/vscode-solargraph + //"rebornix.Ruby", // https://github.com/rubyide/vscode-ruby (Deprecated) + + ], + "settings": { + // General settings for Codespaces (VS Code) + //"ruby.useLanguageServer": true , + //"ruby.format": "rubocop", + //"ruby.lint": { "rubocop": true }, + //"ruby.intellisense": "rubyLocate", + "editor.tabSize": 2, + "editor.formatOnSave": false, // Disable onSave to show diff edited by learners only + "editor.formatOnType": false, // Disable onType for the same reason above + "editor.insertSpaces": true, // Use spaces, not tabs, to avoid errors for learners + "editor.renderWhitespace": "none", + "[ruby]": { + "editor.defaultFormatter": "castwide.solargraph", + "editor.semanticHighlighting.enabled": true // Enable semantic highlighting + }, + "files.associations": { "*.erb": "erb" }, + "emmet.includeLanguages": { "erb": "html" }, + + // Settings for Solargraph + // https://github.com/castwide/solargraph + "solargraph.useBundler": false, + "solargraph.diagnostics": false, + "solargraph.formatting": true, // Use Ctrl+Shift+P->Format to format + "solargraph.autoformat": false, + "solargraph.definitions": true, + "solargraph.completion": true, + "solargraph.references": true, + "solargraph.symbols": true, + "solargraph.rename": true, + "solargraph.hover": true, + + // Settings for Ruby LSP + // https://github.com/Shopify/ruby-lsp + "rubyLsp.rubyVersionManager": { + "identifier": "none" + }, + "rubyLsp.formatter": "none", + "rubyLsp.enabledFeatures": { + "diagnostics": false, + "formatting": false + } + } + } + }, + "remoteEnv": { + "EDITOR": "code --wait" + }, + "portsAttributes": { + "3000": { + "label": "Application", + "onAutoForward": "openPreview" + } + }, + "forwardPorts": [3000] +} diff --git a/8_0/ch02/.devcontainer/icon.svg b/8_0/ch02/.devcontainer/icon.svg new file mode 100644 index 00000000..f8444a4a --- /dev/null +++ b/8_0/ch02/.devcontainer/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/8_0/ch02/.gitattributes b/8_0/ch02/.gitattributes new file mode 100644 index 00000000..31eeee0b --- /dev/null +++ b/8_0/ch02/.gitattributes @@ -0,0 +1,7 @@ +# See https://git-scm.com/docs/gitattributes for more about git attribute files. + +# Mark the database schema as having been generated. +db/schema.rb linguist-generated + +# Mark any vendored files as having been vendored. +vendor/* linguist-vendored diff --git a/8_0/ch02/.gitignore b/8_0/ch02/.gitignore new file mode 100644 index 00000000..e1ef53ef --- /dev/null +++ b/8_0/ch02/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-* + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore uploaded files in development. +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/ +!/tmp/storage/.keep + +/public/assets + +# Ignore master key for decrypting credentials and more. +/config/master.key + +# Ignore history file used by IRB for interactive Ruby. +.irb_history + +dump.rdb diff --git a/8_0/ch02/.irbrc b/8_0/ch02/.irbrc new file mode 100644 index 00000000..3737e036 --- /dev/null +++ b/8_0/ch02/.irbrc @@ -0,0 +1 @@ +IRB.conf[:COMPLETOR] = :type # default is :regexp diff --git a/8_0/ch02/.solargraph.yml b/8_0/ch02/.solargraph.yml new file mode 100644 index 00000000..76ac24a6 --- /dev/null +++ b/8_0/ch02/.solargraph.yml @@ -0,0 +1,32 @@ +--- +include: +- "**/*.rb" +exclude: +- spec/**/* +- test/**/* +- vendor/**/* +- ".bundle/**/*" +require: +- actioncable +- actionmailer +- actionpack +- actionview +- activejob +- activemodel +- activerecord +- activestorage +- activesupport +domains: [] +reporters: +- rubocop +- require_not_found +- typecheck +formatter: + rubocop: + cops: safe + except: [] + only: [] + extra_args: [] +require_paths: [] +plugins: [] +max_files: 5000 diff --git a/8_0/ch02/.vscode/settings.json b/8_0/ch02/.vscode/settings.json new file mode 100644 index 00000000..cf85f519 --- /dev/null +++ b/8_0/ch02/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.minimap.enabled": false +} diff --git a/8_0/ch02/Gemfile b/8_0/ch02/Gemfile new file mode 100644 index 00000000..46cefd03 --- /dev/null +++ b/8_0/ch02/Gemfile @@ -0,0 +1,42 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '3.2.10' + +gem 'bootsnap', '1.16.0', require: false +gem 'concurrent-ruby', '1.3.4' +gem 'importmap-rails', '1.1.5' +gem 'jbuilder', '2.14.1' +gem 'puma', '6.6.1' +gem 'rails', '8.0.2.1' +gem 'sassc-rails', '2.1.2' +gem 'sprockets-rails', '3.4.2' +gem 'sqlite3', '2.7.3' +gem 'stimulus-rails', '1.2.1' +gem 'turbo-rails', '1.4.0' + +group :development, :test do + gem 'debug', '1.7.1', platforms: %i[mri mingw x64_mingw] + gem 'reline', '0.5.10' +end + +group :development do + gem 'irb', '1.15.2' + gem 'repl_type_completor', '0.1.10' + gem 'solargraph', '0.58.2' + gem 'web-console', '4.2.0' +end + +group :test do + gem 'capybara', '3.38.0' + gem 'guard', '2.18.0' + gem 'guard-minitest', '2.4.6' + gem 'minitest', '5.18.0' + gem 'minitest-reporters', '1.6.0' + gem 'rails-controller-testing', '1.0.5' + gem 'selenium-webdriver', '4.8.3' + gem 'webdrivers', '5.2.0' +end + +# Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります +# gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] diff --git a/8_0/ch02/Gemfile.lock b/8_0/ch02/Gemfile.lock new file mode 100644 index 00000000..a3ee4202 --- /dev/null +++ b/8_0/ch02/Gemfile.lock @@ -0,0 +1,433 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + actionmailer (8.0.2.1) + actionpack (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.0.2.1) + actionview (= 8.0.2.1) + activesupport (= 8.0.2.1) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.0.2.1) + actionpack (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (8.0.2.1) + activesupport (= 8.0.2.1) + builder (~> 3.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.3.6) + activemodel (8.0.2.1) + activesupport (= 8.0.2.1) + activerecord (8.0.2.1) + activemodel (= 8.0.2.1) + activesupport (= 8.0.2.1) + timeout (>= 0.4.0) + activestorage (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activesupport (= 8.0.2.1) + marcel (~> 1.0) + activesupport (8.0.2.1) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + ansi (1.5.0) + ast (2.4.3) + backport (1.2.0) + base64 (0.3.0) + benchmark (0.4.1) + bigdecimal (3.2.3) + bindex (0.8.1) + bootsnap (1.16.0) + msgpack (~> 1.2) + builder (3.3.0) + capybara (3.38.0) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + coderay (1.1.3) + concurrent-ruby (1.3.4) + connection_pool (2.5.4) + crass (1.0.6) + date (3.4.1) + debug (1.7.1) + irb (>= 1.5.0) + reline (>= 0.3.1) + diff-lcs (1.6.2) + drb (2.2.3) + erb (5.0.2) + erubi (1.13.1) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) + formatador (1.2.0) + reline + globalid (1.2.1) + activesupport (>= 6.1) + guard (2.18.0) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.13.0) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-minitest (2.4.6) + guard-compat (~> 1.2) + minitest (>= 3.0) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + importmap-rails (1.1.5) + actionpack (>= 6.0.0) + railties (>= 6.0.0) + io-console (0.8.1) + irb (1.15.2) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jaro_winkler (1.6.1) + jbuilder (2.14.1) + actionview (>= 7.0.0) + activesupport (>= 7.0.0) + json (2.13.2) + kramdown (2.5.1) + rexml (>= 3.3.9) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) + loofah (2.24.1) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + lumberjack (1.4.1) + mail (2.8.1) + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.0.4) + matrix (0.4.3) + method_source (1.1.0) + mini_mime (1.1.5) + minitest (5.18.0) + minitest-reporters (1.6.0) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + msgpack (1.8.0) + nenv (0.3.0) + net-imap (0.5.10) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.4) + nokogiri (1.19.1-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-musl) + racc (~> 1.4) + notiffany (0.1.3) + nenv (~> 0.1) + shellany (~> 0.0) + observer (0.1.2) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.9.0) + ast (~> 2.4.1) + racc + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + prism (1.4.0) + pry (0.15.2) + coderay (~> 1.1) + method_source (~> 1.0) + psych (5.2.6) + date + stringio + public_suffix (6.0.2) + puma (6.6.1) + nio4r (~> 2.0) + racc (1.8.1) + rack (3.2.5) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.2.1) + rack (>= 3) + rails (8.0.2.1) + actioncable (= 8.0.2.1) + actionmailbox (= 8.0.2.1) + actionmailer (= 8.0.2.1) + actionpack (= 8.0.2.1) + actiontext (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activemodel (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + bundler (>= 1.15.0) + railties (= 8.0.2.1) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.0) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rbs (3.6.1) + logger + rdoc (6.14.2) + erb + psych (>= 4.0.0) + regexp_parser (2.11.2) + reline (0.5.10) + io-console (~> 0.5) + repl_type_completor (0.1.10) + prism (~> 1.0) + rbs (>= 2.7.0, < 4.0.0) + reverse_markdown (3.0.0) + nokogiri + rexml (3.4.2) + rubocop (1.80.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.46.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.46.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + ruby-progressbar (1.13.0) + rubyzip (2.4.1) + sassc (2.4.0) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt + securerandom (0.4.1) + selenium-webdriver (4.8.3) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) + shellany (0.0.1) + solargraph (0.58.2) + backport (~> 1.2) + benchmark (~> 0.4) + bundler (~> 2.0) + diff-lcs (~> 1.4) + jaro_winkler (~> 1.6, >= 1.6.1) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.1) + logger (~> 1.6) + observer (~> 0.1) + ostruct (~> 0.6) + parser (~> 3.0) + prism (~> 1.4) + rbs (~> 3.6.1) + reverse_markdown (~> 3.0) + rubocop (~> 1.38) + thor (~> 1.0) + tilt (~> 2.0) + yard (~> 0.9, >= 0.9.24) + yard-solargraph (~> 0.1) + sprockets (4.2.2) + concurrent-ruby (~> 1.0) + logger + rack (>= 2.2.4, < 4) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + sprockets (>= 3.0.0) + sqlite3 (2.7.3-aarch64-linux-gnu) + sqlite3 (2.7.3-aarch64-linux-musl) + sqlite3 (2.7.3-arm-linux-gnu) + sqlite3 (2.7.3-arm-linux-musl) + sqlite3 (2.7.3-arm64-darwin) + sqlite3 (2.7.3-x86_64-darwin) + sqlite3 (2.7.3-x86_64-linux-gnu) + sqlite3 (2.7.3-x86_64-linux-musl) + stimulus-rails (1.2.1) + railties (>= 6.0.0) + stringio (3.1.7) + thor (1.4.0) + tilt (2.6.1) + timeout (0.4.3) + turbo-rails (1.4.0) + actionpack (>= 6.0.0) + activejob (>= 6.0.0) + railties (>= 6.0.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.1.5) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + uri (1.0.4) + useragent (0.16.11) + web-console (4.2.0) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + webdrivers (5.2.0) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (~> 4.0) + websocket (1.2.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) + yard (0.9.37) + yard-solargraph (0.1.0) + yard (~> 0.9) + zeitwerk (2.7.3) + +PLATFORMS + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + bootsnap (= 1.16.0) + capybara (= 3.38.0) + concurrent-ruby (= 1.3.4) + debug (= 1.7.1) + guard (= 2.18.0) + guard-minitest (= 2.4.6) + importmap-rails (= 1.1.5) + irb (= 1.15.2) + jbuilder (= 2.14.1) + minitest (= 5.18.0) + minitest-reporters (= 1.6.0) + puma (= 6.6.1) + rails (= 8.0.2.1) + rails-controller-testing (= 1.0.5) + reline (= 0.5.10) + repl_type_completor (= 0.1.10) + sassc-rails (= 2.1.2) + selenium-webdriver (= 4.8.3) + solargraph (= 0.58.2) + sprockets-rails (= 3.4.2) + sqlite3 (= 2.7.3) + stimulus-rails (= 1.2.1) + turbo-rails (= 1.4.0) + web-console (= 4.2.0) + webdrivers (= 5.2.0) + +RUBY VERSION + ruby 3.2.10p266 + +BUNDLED WITH + 2.5.6 diff --git a/8_0/ch02/LICENSE b/8_0/ch02/LICENSE new file mode 100644 index 00000000..20622e0c --- /dev/null +++ b/8_0/ch02/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 GitHub & 2023 YassLab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/8_0/ch02/README.md b/8_0/ch02/README.md new file mode 100644 index 00000000..4d3cdc46 --- /dev/null +++ b/8_0/ch02/README.md @@ -0,0 +1,110 @@ +# Codespaces ♥️ Railsチュートリアル + +本リポジトリは[Railsチュートリアル](https://railstutorial.jp/)第8版に対応した [GitHub Codespaces](https://github.co.jp/features/codespaces) 用テンプレートです。2022年11月に公開された[GitHub公式のRailsテンプレート](https://github.com/github/codespaces-rails)を、[Railsチュートリアル](https://railstutorial.jp)用にカスタマイズしたものです。 + +- [:computer: GitHub Codespaces 対応!環境構築が不要に(解説動画付き) - note](https://note.com/yasslab/n/n427c56266295) +- [:newspaper: GitHub Codespaces が全ユーザーに無料提供へ、毎月60時間分 - Publickey](https://www.publickey1.jp/blog/22/github_codespaces60jetbrainsjupyterlabide.html) + +
+ +本テンプレートは、Railsチュートリアルの第1章・第2章・第3章の冒頭にある `rails new` および `Gemfile` の更新まで(難しいとされる「環境構築」まで)が完了している状態となっており、**rails server が立ち上げられる状態から学習をスタートできます** 📝✨ + +![Codespaces のサンプル画面](https://i.gyazo.com/b3af38fd1f8b2824791da9001a2bf6a0.png) + + + +
+ +## 必要なもの + +- [Chrome](https://www.google.com/intl/ja/chrome/browser/) などのブラウザ(Chrome だとより快適に動作します) +- [GitHub](https://github.co.jp/) のアカウント(もしまだであれば事前に作成しておきましょう) + [![GitHub Top](https://i.gyazo.com/b5bad7bc8318837b67def1643a52b955.png)](https://github.co.jp/) + +
+ +## Codespaces 使い方 + +以下の手順で、Codespaces を利用した環境構築が行えます 🛠 + +1. 当ページの上部にある `Use this template` から `Create a new repository` をクリックします。もし `Use this template` が表示されない場合は、ブラウザの横幅を広げてみましょう。 + ![本リポジトリからリポジトリを作成する場面](https://i.gyazo.com/a483f77e8299ea6b5dd75795c793fb8b.png) + +1. 移動したページで、`Repository name` に作成するアプリ名、`Description` にアプリの説明文を入力し、`Private` を選択してリポジトリを非公開に設定します。最後に `Create repository from template` をクリックすると、新しいリポジトリが作成されます。(以下は第1章の `hello_app` を作成する場合の例です) + ![テンプレートリポジトリの作成画面](https://i.gyazo.com/2e0188742504ec559109ba35a6b3714d.png) + +1. 作成したリポジトリに飛んだら、`Code` から `Codespaces` タブに移動し、`Create codespace on main` をクリックします。 + ![テンプレートリポジトリから Codespaces へ](https://i.gyazo.com/17c40d8c1453de7a5db9d7ed6b603db6.png) + +1. 環境構築が完了するのを待ちます(1〜2分ほど掛かります) + ![Codespaces の立ち上げ中の画面](https://i.gyazo.com/1dc81bccd2f416bc936cd60f348a6d7a.png) + +1. Railsチュートリアルのロゴ画像が表示されたら完成です! + ![Codespaces による環境構築の完了画面Top](https://i.gyazo.com/b3af38fd1f8b2824791da9001a2bf6a0.png) + +`rails new` や `Gemfile` の更新、`rails server` を立ち上がるところまで(難しいとされる「環境構築」が終わるところまで)が完了している状態なので、**第1章・第2章・第3章のコードを書くところから始められます!** 📝✨ + +例えば第1章の場合は「[1.3.2 `rails server`](https://railstutorial.jp/chapters/beginning#sec-rails_server)」の途中から、すなわち `rails server` を立ち上げたところからスタートできます。 + +> :memo: Codespaces によってココまで自動化されていますが、**1.3.2 以前の内容(何が自動化されたのか)を知ることも大事**です。このまま 1.3.2 以降に進めていただいてももちろん大丈夫ですが、どこかの段階で 1.3.2 以前の内容にも目を通しておくと、知識は広がります。 + +
+ +## インストール済みの拡張機能について +より良い学習体験に繋げるため、本テンプレートには以下の VS Code 拡張機能がデフォルトで入っています。 + +- [:octocat: Shopify/ruby-lsp](https://github.com/Shopify/ruby-lsp): + - Ruby コードを色分けして表示するハイライト機能や、コード補完機能などが使えます(以下は[公式のデモ動画](https://github.com/Shopify/ruby-lsp/tree/main/vscode#features)です)\ + ![Ruby LSP Official DEMO](https://i.gyazo.com/71a5c5114b7836d942a5145ca58eadb9.gif) \ + 参考記事: [Ruby LSPのコードナビゲーションで強化された主な機能 - TechRacho](https://techracho.bpsinc.jp/hachi8833/2024_07_29/143652) + +- [:octocat: castwide/vscode-solargraph](https://github.com/castwide/vscode-solargraph): + - Ruby コードの定義元が調べられるコードジャンプ機能や、ドキュメント表示機能などが使えます(以下は[公式のデモ動画](https://github.com/castwide/vscode-solargraph#readme)です) \ + ![Solargraph Official DEMO](https://i.gyazo.com/5fac6a81088d814a5b8354431239b03d.gif) + +RuboCop によるコード整形、Ruby 公式デバッガーなどの拡張機能はお好みで追加してください。本テンプレートでは必要最低限の拡張機能に留めています。 + +- [:octocat: misogi/vscode-ruby-rubocop](https://github.com/misogi/vscode-ruby-rubocop) +- [:octocat: ruby/vscode-rdbg](https://github.com/ruby/vscode-rdbg) +- [:octocat: ruby-debug/ruby-debug-ide](https://github.com/ruby-debug/ruby-debug-ide) +- [:octocat: Shopify/vscode-shopify-ruby](https://github.com/Shopify/vscode-shopify-ruby) +- [:octocat: primer/github-vscode-theme](https://github.com/primer/github-vscode-theme) + +
+ +## よくあるエラーと解決方法 +
+ ブラウザ別のエラー解決方法を見る(2023年3月時点) +

Google Chrome - Webビューの読み込みエラー

+ Chrome のエラー例1 +

Error: Could not register service workers: NotSupportedError ... などが表示され、「シンプルブラウザーは開いたけど何も表示されない」という場合があります。これは必要な Cookie が許可されていない場合に起こります。以下の例を参考に、サードパーティの Cookie を許可すると解決する場合が多いです。

+ Chrome のエラー例2 +

Cookie を許可しても解決しない場合は、シンプルブラウザーの右端にある「ブラウザーで開く」アイコンをクリックしてください。ブラウザの別タブで画面が表示され、こちらの画面でも現在の状態をご確認いただけます。

+ Chrome のエラー例3 +


+ +

Firefox - Webビューの読み込みエラー

+

上記の Chrome と同様に、シンプルブラウザーの画面が表示されない事があります。アドレスバーにある強化型トラッキング防止機能のアイコンをクリックし、「オフ」にすることでプレビューが表示されるようになります。

+ Firefox のエラー例1 +

上記の機能をオフにしても解決しない場合は、シンプルブラウザーではなく「新規ウィンドウでサイトを開く」をクリックしてください。ブラウザの別タブで画面が表示され、こちらの画面でも現在の状態をご確認いただけます。

+ Firefox のエラー例2 + Firefox のエラー例3 +


+ +

Safari - 入力の遅延・アイコンの一部非表示

+

Safari では問題なくことが多いです。ただし、文字入力をしてから、Codespaces 上の画面に表示されるまでが遅い場合があります。また一部のアイコンが表示されない現象も確認できています。開発する上で問題になるわけではないですが、もし気になる場合は Google Chrome など他のブラウザをお試しください。

+ Safari のエラー例1 +
+ +

+ +## 制作・ライセンス + +Copyright © [YassLab](http://yasslab.jp/) Inc.
+Railsチュートリアル運営チーム
+[https://railstutorial.jp/](https://railstutorial.jp/) + + + ソースコードのライセンスは LICENSE をご確認ください。
+ ロゴ画像やデモ動画などは各制作者の著作物となります。 +
diff --git a/8_0/ch02/Rakefile b/8_0/ch02/Rakefile new file mode 100644 index 00000000..9a5ea738 --- /dev/null +++ b/8_0/ch02/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/8_0/ch02/app/assets/config/manifest.js b/8_0/ch02/app/assets/config/manifest.js new file mode 100644 index 00000000..ddd546a0 --- /dev/null +++ b/8_0/ch02/app/assets/config/manifest.js @@ -0,0 +1,4 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js diff --git a/8_0/ch02/app/assets/images/.keep b/8_0/ch02/app/assets/images/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/app/assets/stylesheets/application.css b/8_0/ch02/app/assets/stylesheets/application.css new file mode 100644 index 00000000..288b9ab7 --- /dev/null +++ b/8_0/ch02/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/8_0/ch02/app/assets/stylesheets/hello.css b/8_0/ch02/app/assets/stylesheets/hello.css new file mode 100644 index 00000000..497881a4 --- /dev/null +++ b/8_0/ch02/app/assets/stylesheets/hello.css @@ -0,0 +1,40 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.codespaces { + text-align: center; +} +.codespaces code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} +.codespaces-logo { + height: 40vmin; + pointer-events: none; +} +.codespaces-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} +.codespaces-link { + color: #61dafb; +} + +.heart { + color: #ff0000; +} +.small { + font-size: 0.75rem; +} diff --git a/8_0/ch02/app/channels/application_cable/channel.rb b/8_0/ch02/app/channels/application_cable/channel.rb new file mode 100644 index 00000000..d6726972 --- /dev/null +++ b/8_0/ch02/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/8_0/ch02/app/channels/application_cable/connection.rb b/8_0/ch02/app/channels/application_cable/connection.rb new file mode 100644 index 00000000..0ff5442f --- /dev/null +++ b/8_0/ch02/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/8_0/ch02/app/controllers/application_controller.rb b/8_0/ch02/app/controllers/application_controller.rb new file mode 100644 index 00000000..09705d12 --- /dev/null +++ b/8_0/ch02/app/controllers/application_controller.rb @@ -0,0 +1,2 @@ +class ApplicationController < ActionController::Base +end diff --git a/8_0/ch02/app/controllers/concerns/.keep b/8_0/ch02/app/controllers/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/app/controllers/hello_controller.rb b/8_0/ch02/app/controllers/hello_controller.rb new file mode 100644 index 00000000..52037537 --- /dev/null +++ b/8_0/ch02/app/controllers/hello_controller.rb @@ -0,0 +1,4 @@ +class HelloController < ApplicationController + def index + end +end diff --git a/8_0/ch02/app/controllers/microposts_controller.rb b/8_0/ch02/app/controllers/microposts_controller.rb new file mode 100644 index 00000000..0945bfd9 --- /dev/null +++ b/8_0/ch02/app/controllers/microposts_controller.rb @@ -0,0 +1,70 @@ +class MicropostsController < ApplicationController + before_action :set_micropost, only: %i[ show edit update destroy ] + + # GET /microposts or /microposts.json + def index + @microposts = Micropost.all + end + + # GET /microposts/1 or /microposts/1.json + def show + end + + # GET /microposts/new + def new + @micropost = Micropost.new + end + + # GET /microposts/1/edit + def edit + end + + # POST /microposts or /microposts.json + def create + @micropost = Micropost.new(micropost_params) + + respond_to do |format| + if @micropost.save + format.html { redirect_to @micropost, notice: "Micropost was successfully created." } + format.json { render :show, status: :created, location: @micropost } + else + format.html { render :new, status: :unprocessable_entity } + format.json { render json: @micropost.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /microposts/1 or /microposts/1.json + def update + respond_to do |format| + if @micropost.update(micropost_params) + format.html { redirect_to @micropost, notice: "Micropost was successfully updated.", status: :see_other } + format.json { render :show, status: :ok, location: @micropost } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @micropost.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /microposts/1 or /microposts/1.json + def destroy + @micropost.destroy! + + respond_to do |format| + format.html { redirect_to microposts_path, notice: "Micropost was successfully destroyed.", status: :see_other } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_micropost + @micropost = Micropost.find(params.expect(:id)) + end + + # Only allow a list of trusted parameters through. + def micropost_params + params.expect(micropost: [ :content, :user_id ]) + end +end diff --git a/8_0/ch02/app/controllers/users_controller.rb b/8_0/ch02/app/controllers/users_controller.rb new file mode 100644 index 00000000..baa8f33d --- /dev/null +++ b/8_0/ch02/app/controllers/users_controller.rb @@ -0,0 +1,70 @@ +class UsersController < ApplicationController + before_action :set_user, only: %i[ show edit update destroy ] + + # GET /users or /users.json + def index + @users = User.all + end + + # GET /users/1 or /users/1.json + def show + end + + # GET /users/new + def new + @user = User.new + end + + # GET /users/1/edit + def edit + end + + # POST /users or /users.json + def create + @user = User.new(user_params) + + respond_to do |format| + if @user.save + format.html { redirect_to @user, notice: "User was successfully created." } + format.json { render :show, status: :created, location: @user } + else + format.html { render :new, status: :unprocessable_entity } + format.json { render json: @user.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /users/1 or /users/1.json + def update + respond_to do |format| + if @user.update(user_params) + format.html { redirect_to @user, notice: "User was successfully updated.", status: :see_other } + format.json { render :show, status: :ok, location: @user } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @user.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /users/1 or /users/1.json + def destroy + @user.destroy! + + respond_to do |format| + format.html { redirect_to users_path, notice: "User was successfully destroyed.", status: :see_other } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_user + @user = User.find(params.expect(:id)) + end + + # Only allow a list of trusted parameters through. + def user_params + params.expect(user: [ :name, :email ]) + end +end diff --git a/8_0/ch02/app/helpers/application_helper.rb b/8_0/ch02/app/helpers/application_helper.rb new file mode 100644 index 00000000..de6be794 --- /dev/null +++ b/8_0/ch02/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/8_0/ch02/app/helpers/hello_codespaces_helper.rb b/8_0/ch02/app/helpers/hello_codespaces_helper.rb new file mode 100644 index 00000000..874a2ca2 --- /dev/null +++ b/8_0/ch02/app/helpers/hello_codespaces_helper.rb @@ -0,0 +1,2 @@ +module HelloCodespacesHelper +end diff --git a/8_0/ch02/app/helpers/microposts_helper.rb b/8_0/ch02/app/helpers/microposts_helper.rb new file mode 100644 index 00000000..f08aad24 --- /dev/null +++ b/8_0/ch02/app/helpers/microposts_helper.rb @@ -0,0 +1,2 @@ +module MicropostsHelper +end diff --git a/8_0/ch02/app/helpers/users_helper.rb b/8_0/ch02/app/helpers/users_helper.rb new file mode 100644 index 00000000..2310a240 --- /dev/null +++ b/8_0/ch02/app/helpers/users_helper.rb @@ -0,0 +1,2 @@ +module UsersHelper +end diff --git a/8_0/ch02/app/javascript/application.js b/8_0/ch02/app/javascript/application.js new file mode 100644 index 00000000..0d7b4940 --- /dev/null +++ b/8_0/ch02/app/javascript/application.js @@ -0,0 +1,3 @@ +// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails +import "@hotwired/turbo-rails" +import "controllers" diff --git a/8_0/ch02/app/javascript/controllers/application.js b/8_0/ch02/app/javascript/controllers/application.js new file mode 100644 index 00000000..1213e85c --- /dev/null +++ b/8_0/ch02/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/8_0/ch02/app/javascript/controllers/hello_controller.js b/8_0/ch02/app/javascript/controllers/hello_controller.js new file mode 100644 index 00000000..5975c078 --- /dev/null +++ b/8_0/ch02/app/javascript/controllers/hello_controller.js @@ -0,0 +1,7 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.element.textContent = "Hello World!" + } +} diff --git a/8_0/ch02/app/javascript/controllers/index.js b/8_0/ch02/app/javascript/controllers/index.js new file mode 100644 index 00000000..54ad4cad --- /dev/null +++ b/8_0/ch02/app/javascript/controllers/index.js @@ -0,0 +1,11 @@ +// Import and register all your controllers from the importmap under controllers/* + +import { application } from "controllers/application" + +// Eager load all controllers defined in the import map under controllers/**/*_controller +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) + +// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) +// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" +// lazyLoadControllersFrom("controllers", application) diff --git a/8_0/ch02/app/jobs/application_job.rb b/8_0/ch02/app/jobs/application_job.rb new file mode 100644 index 00000000..d394c3d1 --- /dev/null +++ b/8_0/ch02/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/8_0/ch02/app/mailers/application_mailer.rb b/8_0/ch02/app/mailers/application_mailer.rb new file mode 100644 index 00000000..3c34c814 --- /dev/null +++ b/8_0/ch02/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "from@example.com" + layout "mailer" +end diff --git a/8_0/ch02/app/models/application_record.rb b/8_0/ch02/app/models/application_record.rb new file mode 100644 index 00000000..b63caeb8 --- /dev/null +++ b/8_0/ch02/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/8_0/ch02/app/models/concerns/.keep b/8_0/ch02/app/models/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/app/models/micropost.rb b/8_0/ch02/app/models/micropost.rb new file mode 100644 index 00000000..c4a69b32 --- /dev/null +++ b/8_0/ch02/app/models/micropost.rb @@ -0,0 +1,4 @@ +class Micropost < ApplicationRecord + belongs_to :user + validates :content, length: { maximum: 140 } +end diff --git a/8_0/ch02/app/models/user.rb b/8_0/ch02/app/models/user.rb new file mode 100644 index 00000000..6c05c076 --- /dev/null +++ b/8_0/ch02/app/models/user.rb @@ -0,0 +1,3 @@ +class User < ApplicationRecord + has_many :microposts +end diff --git a/8_0/ch02/app/views/hello/index.html.erb b/8_0/ch02/app/views/hello/index.html.erb new file mode 100644 index 00000000..71661105 --- /dev/null +++ b/8_0/ch02/app/views/hello/index.html.erb @@ -0,0 +1,16 @@ +
+
+ +

+ Codespaces + ♥️ + Railsチュートリアル +

+

+ 🎓✨
+ ロゴ画像が表示されたらセットアップ完了です! +

+
+
diff --git a/8_0/ch02/app/views/layouts/application.html.erb b/8_0/ch02/app/views/layouts/application.html.erb new file mode 100644 index 00000000..03843a74 --- /dev/null +++ b/8_0/ch02/app/views/layouts/application.html.erb @@ -0,0 +1,16 @@ + + + + Sample App + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + + + + <%= yield %> + + diff --git a/8_0/ch02/app/views/layouts/mailer.html.erb b/8_0/ch02/app/views/layouts/mailer.html.erb new file mode 100644 index 00000000..cbd34d2e --- /dev/null +++ b/8_0/ch02/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/8_0/ch02/app/views/layouts/mailer.text.erb b/8_0/ch02/app/views/layouts/mailer.text.erb new file mode 100644 index 00000000..37f0bddb --- /dev/null +++ b/8_0/ch02/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/8_0/ch02/app/views/microposts/_form.html.erb b/8_0/ch02/app/views/microposts/_form.html.erb new file mode 100644 index 00000000..979ab9dc --- /dev/null +++ b/8_0/ch02/app/views/microposts/_form.html.erb @@ -0,0 +1,27 @@ +<%= form_with(model: micropost) do |form| %> + <% if micropost.errors.any? %> +
+

<%= pluralize(micropost.errors.count, "error") %> prohibited this micropost from being saved:

+ + +
+ <% end %> + +
+ <%= form.label :content, style: "display: block" %> + <%= form.textarea :content %> +
+ +
+ <%= form.label :user_id, style: "display: block" %> + <%= form.number_field :user_id %> +
+ +
+ <%= form.submit %> +
+<% end %> diff --git a/8_0/ch02/app/views/microposts/_micropost.html.erb b/8_0/ch02/app/views/microposts/_micropost.html.erb new file mode 100644 index 00000000..48835018 --- /dev/null +++ b/8_0/ch02/app/views/microposts/_micropost.html.erb @@ -0,0 +1,12 @@ +
+

+ Content: + <%= micropost.content %> +

+ +

+ User: + <%= micropost.user_id %> +

+ +
diff --git a/8_0/ch02/app/views/microposts/_micropost.json.jbuilder b/8_0/ch02/app/views/microposts/_micropost.json.jbuilder new file mode 100644 index 00000000..087834b5 --- /dev/null +++ b/8_0/ch02/app/views/microposts/_micropost.json.jbuilder @@ -0,0 +1,2 @@ +json.extract! micropost, :id, :content, :user_id, :created_at, :updated_at +json.url micropost_url(micropost, format: :json) diff --git a/8_0/ch02/app/views/microposts/edit.html.erb b/8_0/ch02/app/views/microposts/edit.html.erb new file mode 100644 index 00000000..7acadd1b --- /dev/null +++ b/8_0/ch02/app/views/microposts/edit.html.erb @@ -0,0 +1,12 @@ +<% content_for :title, "Editing micropost" %> + +

Editing micropost

+ +<%= render "form", micropost: @micropost %> + +
+ +
+ <%= link_to "Show this micropost", @micropost %> | + <%= link_to "Back to microposts", microposts_path %> +
diff --git a/8_0/ch02/app/views/microposts/index.html.erb b/8_0/ch02/app/views/microposts/index.html.erb new file mode 100644 index 00000000..c34a676b --- /dev/null +++ b/8_0/ch02/app/views/microposts/index.html.erb @@ -0,0 +1,16 @@ +

<%= notice %>

+ +<% content_for :title, "Microposts" %> + +

Microposts

+ +
+ <% @microposts.each do |micropost| %> + <%= render micropost %> +

+ <%= link_to "Show this micropost", micropost %> +

+ <% end %> +
+ +<%= link_to "New micropost", new_micropost_path %> diff --git a/8_0/ch02/app/views/microposts/index.json.jbuilder b/8_0/ch02/app/views/microposts/index.json.jbuilder new file mode 100644 index 00000000..a5678f0a --- /dev/null +++ b/8_0/ch02/app/views/microposts/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @microposts, partial: "microposts/micropost", as: :micropost diff --git a/8_0/ch02/app/views/microposts/new.html.erb b/8_0/ch02/app/views/microposts/new.html.erb new file mode 100644 index 00000000..47b96bc5 --- /dev/null +++ b/8_0/ch02/app/views/microposts/new.html.erb @@ -0,0 +1,11 @@ +<% content_for :title, "New micropost" %> + +

New micropost

+ +<%= render "form", micropost: @micropost %> + +
+ +
+ <%= link_to "Back to microposts", microposts_path %> +
diff --git a/8_0/ch02/app/views/microposts/show.html.erb b/8_0/ch02/app/views/microposts/show.html.erb new file mode 100644 index 00000000..65125a7c --- /dev/null +++ b/8_0/ch02/app/views/microposts/show.html.erb @@ -0,0 +1,10 @@ +

<%= notice %>

+ +<%= render @micropost %> + +
+ <%= link_to "Edit this micropost", edit_micropost_path(@micropost) %> | + <%= link_to "Back to microposts", microposts_path %> + + <%= button_to "Destroy this micropost", @micropost, method: :delete %> +
diff --git a/8_0/ch02/app/views/microposts/show.json.jbuilder b/8_0/ch02/app/views/microposts/show.json.jbuilder new file mode 100644 index 00000000..921002a5 --- /dev/null +++ b/8_0/ch02/app/views/microposts/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! "microposts/micropost", micropost: @micropost diff --git a/8_0/ch02/app/views/users/_form.html.erb b/8_0/ch02/app/views/users/_form.html.erb new file mode 100644 index 00000000..b431c06e --- /dev/null +++ b/8_0/ch02/app/views/users/_form.html.erb @@ -0,0 +1,27 @@ +<%= form_with(model: user) do |form| %> + <% if user.errors.any? %> +
+

<%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:

+ + +
+ <% end %> + +
+ <%= form.label :name, style: "display: block" %> + <%= form.text_field :name %> +
+ +
+ <%= form.label :email, style: "display: block" %> + <%= form.text_field :email %> +
+ +
+ <%= form.submit %> +
+<% end %> diff --git a/8_0/ch02/app/views/users/_user.html.erb b/8_0/ch02/app/views/users/_user.html.erb new file mode 100644 index 00000000..e089c638 --- /dev/null +++ b/8_0/ch02/app/views/users/_user.html.erb @@ -0,0 +1,12 @@ +
+

+ Name: + <%= user.name %> +

+ +

+ Email: + <%= user.email %> +

+ +
diff --git a/8_0/ch02/app/views/users/_user.json.jbuilder b/8_0/ch02/app/views/users/_user.json.jbuilder new file mode 100644 index 00000000..bc210083 --- /dev/null +++ b/8_0/ch02/app/views/users/_user.json.jbuilder @@ -0,0 +1,2 @@ +json.extract! user, :id, :name, :email, :created_at, :updated_at +json.url user_url(user, format: :json) diff --git a/8_0/ch02/app/views/users/edit.html.erb b/8_0/ch02/app/views/users/edit.html.erb new file mode 100644 index 00000000..41461ee6 --- /dev/null +++ b/8_0/ch02/app/views/users/edit.html.erb @@ -0,0 +1,12 @@ +<% content_for :title, "Editing user" %> + +

Editing user

+ +<%= render "form", user: @user %> + +
+ +
+ <%= link_to "Show this user", @user %> | + <%= link_to "Back to users", users_path %> +
diff --git a/8_0/ch02/app/views/users/index.html.erb b/8_0/ch02/app/views/users/index.html.erb new file mode 100644 index 00000000..f5ac45c6 --- /dev/null +++ b/8_0/ch02/app/views/users/index.html.erb @@ -0,0 +1,16 @@ +

<%= notice %>

+ +<% content_for :title, "Users" %> + +

Users

+ +
+ <% @users.each do |user| %> + <%= render user %> +

+ <%= link_to "Show this user", user %> +

+ <% end %> +
+ +<%= link_to "New user", new_user_path %> diff --git a/8_0/ch02/app/views/users/index.json.jbuilder b/8_0/ch02/app/views/users/index.json.jbuilder new file mode 100644 index 00000000..98788dad --- /dev/null +++ b/8_0/ch02/app/views/users/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @users, partial: "users/user", as: :user diff --git a/8_0/ch02/app/views/users/new.html.erb b/8_0/ch02/app/views/users/new.html.erb new file mode 100644 index 00000000..0ded2b79 --- /dev/null +++ b/8_0/ch02/app/views/users/new.html.erb @@ -0,0 +1,11 @@ +<% content_for :title, "New user" %> + +

New user

+ +<%= render "form", user: @user %> + +
+ +
+ <%= link_to "Back to users", users_path %> +
diff --git a/8_0/ch02/app/views/users/show.html.erb b/8_0/ch02/app/views/users/show.html.erb new file mode 100644 index 00000000..673fae23 --- /dev/null +++ b/8_0/ch02/app/views/users/show.html.erb @@ -0,0 +1,10 @@ +

<%= notice %>

+ +<%= render @user %> + +
+ <%= link_to "Edit this user", edit_user_path(@user) %> | + <%= link_to "Back to users", users_path %> + + <%= button_to "Destroy this user", @user, method: :delete %> +
diff --git a/8_0/ch02/app/views/users/show.json.jbuilder b/8_0/ch02/app/views/users/show.json.jbuilder new file mode 100644 index 00000000..ff40bb96 --- /dev/null +++ b/8_0/ch02/app/views/users/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! "users/user", user: @user diff --git a/8_0/ch02/bin/bundle b/8_0/ch02/bin/bundle new file mode 100755 index 00000000..981e650b --- /dev/null +++ b/8_0/ch02/bin/bundle @@ -0,0 +1,114 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + requirement = bundler_gem_version.approximate_recommendation + + return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0") + + requirement += ".a" if bundler_gem_version.prerelease? + + requirement + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/8_0/ch02/bin/dev b/8_0/ch02/bin/dev new file mode 100755 index 00000000..5f91c205 --- /dev/null +++ b/8_0/ch02/bin/dev @@ -0,0 +1,2 @@ +#!/usr/bin/env ruby +exec "./bin/rails", "server", *ARGV diff --git a/8_0/ch02/bin/importmap b/8_0/ch02/bin/importmap new file mode 100755 index 00000000..36502ab1 --- /dev/null +++ b/8_0/ch02/bin/importmap @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require_relative "../config/application" +require "importmap/commands" diff --git a/8_0/ch02/bin/rails b/8_0/ch02/bin/rails new file mode 100755 index 00000000..efc03774 --- /dev/null +++ b/8_0/ch02/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/8_0/ch02/bin/rake b/8_0/ch02/bin/rake new file mode 100755 index 00000000..4fbf10b9 --- /dev/null +++ b/8_0/ch02/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/8_0/ch02/bin/rubocop b/8_0/ch02/bin/rubocop new file mode 100755 index 00000000..40330c0f --- /dev/null +++ b/8_0/ch02/bin/rubocop @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +# explicit rubocop config increases performance slightly while avoiding config confusion. +ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) + +load Gem.bin_path("rubocop", "rubocop") diff --git a/8_0/ch02/bin/setup b/8_0/ch02/bin/setup new file mode 100755 index 00000000..be3db3c0 --- /dev/null +++ b/8_0/ch02/bin/setup @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +require "fileutils" + +APP_ROOT = File.expand_path("..", __dir__) + +def system!(*args) + system(*args, exception: true) +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts "== Installing dependencies ==" + system("bundle check") || system!("bundle install") + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + puts "\n== Removing old logs and tempfiles ==" + system! "bin/rails log:clear tmp:clear" + + unless ARGV.include?("--skip-server") + puts "\n== Starting development server ==" + STDOUT.flush # flush the output before exec(2) so that it displays + exec "bin/dev" + end +end diff --git a/8_0/ch02/config.ru b/8_0/ch02/config.ru new file mode 100644 index 00000000..4a3c09a6 --- /dev/null +++ b/8_0/ch02/config.ru @@ -0,0 +1,6 @@ +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/8_0/ch02/config/application.rb b/8_0/ch02/config/application.rb new file mode 100644 index 00000000..0461e72f --- /dev/null +++ b/8_0/ch02/config/application.rb @@ -0,0 +1,27 @@ +require_relative "boot" + +require "rails/all" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module SampleApp + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 8.0 + + # Please, add to the `ignore` list any other `lib` subdirectories that do + # not contain `.rb` files, or that should not be reloaded or eager loaded. + # Common ones are `templates`, `generators`, or `middleware`, for example. + config.autoload_lib(ignore: %w[assets tasks]) + + # Configuration for the application, engines, and railties goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + # + # config.time_zone = "Central Time (US & Canada)" + # config.eager_load_paths << Rails.root.join("extras") + end +end diff --git a/8_0/ch02/config/boot.rb b/8_0/ch02/config/boot.rb new file mode 100644 index 00000000..988a5ddc --- /dev/null +++ b/8_0/ch02/config/boot.rb @@ -0,0 +1,4 @@ +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "bundler/setup" # Set up gems listed in the Gemfile. +require "bootsnap/setup" # Speed up boot time by caching expensive operations. diff --git a/8_0/ch02/config/cable.yml b/8_0/ch02/config/cable.yml new file mode 100644 index 00000000..ae7a2256 --- /dev/null +++ b/8_0/ch02/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: codespaces_try_rails_production diff --git a/8_0/ch02/config/credentials.yml.enc b/8_0/ch02/config/credentials.yml.enc new file mode 100644 index 00000000..339d64d2 --- /dev/null +++ b/8_0/ch02/config/credentials.yml.enc @@ -0,0 +1 @@ +NCs9M8uakg1CDYJENuGaipzymY8uG6i5gzghsy4npa3gYmGfYKPHMnEcJHCVHlM3gxDNJBteUqkRCxHQ5WQId8hzgxoQCL5RX7rtQ8XUzkJwgSUjKvbCrG5atjKkN9XuL91is5vtEN5W7vEz3qxN7Rp33QbNFiDfLs71F3zHVDYEMi6Dy1QMhVCa1v/tyRjBu/5m37RuiYF5FzWJnh4LhexSVr7Agm4LRLLN6qLCpoAe6D3TPHHBdtu7Pvy8jlrEfnnkUJ61wrj8+kOL4uLtpFShdYKhDkoTjQk7TCOgWyifnHAXazkBZoJZ1QYv/tZ9/ZoHvMEQ2dtGHlyTX8fbKtI13d2CFjGaDNKvRuurxE8dbeyiTfbycvve46+cz9OTlmhh0SGt24R1WV6GBKoDUeoHrIiBvW5qmKka--dQ0rodxmfgFLg2y/--t2x0gQjh8FYlk8v79vLSNg== \ No newline at end of file diff --git a/8_0/ch02/config/database.yml b/8_0/ch02/config/database.yml new file mode 100644 index 00000000..fcba57f1 --- /dev/null +++ b/8_0/ch02/config/database.yml @@ -0,0 +1,25 @@ +# SQLite. Versions 3.8.0 and up are supported. +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem "sqlite3" +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/8_0/ch02/config/environment.rb b/8_0/ch02/config/environment.rb new file mode 100644 index 00000000..cac53157 --- /dev/null +++ b/8_0/ch02/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/8_0/ch02/config/environments/development.rb b/8_0/ch02/config/environments/development.rb new file mode 100644 index 00000000..910cef99 --- /dev/null +++ b/8_0/ch02/config/environments/development.rb @@ -0,0 +1,86 @@ +require 'active_support/core_ext/integer/time' + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Make code changes take effect immediately without server restart. + config.enable_reloading = true + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing. + config.server_timing = true + + # Add the following line to disable forgery_protection_origin_check + config.action_controller.forgery_protection_origin_check = false + + # Enable/disable Action Controller caching. By default Action Controller caching is disabled. + # Run rails dev:cache to toggle Action Controller caching. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + config.public_file_server.headers = { 'cache-control' => "public, max-age=#{2.days.to_i}" } + else + config.action_controller.perform_caching = false + end + + # Change to :null_store to avoid any caching. + config.cache_store = :memory_store + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Make template changes take effect immediately. + config.action_mailer.perform_caching = false + + # Set localhost to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Append comments with runtime information tags to SQL queries in logs. + config.active_record.query_log_tags_enabled = true + + # Highlight code that enqueued background job in logs. + config.active_job.verbose_enqueue_logs = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true + + # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. + # config.generators.apply_rubocop_autocorrect_after_generate! + + pf_domain = ENV['GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN'] + config.action_dispatch.default_headers = { + 'X-Frame-Options' => "ALLOW-FROM #{pf_domain}" + } + + # Allow requests from our preview domain. + pf_host = "#{ENV['CODESPACE_NAME']}-3000.#{pf_domain}" + config.hosts << pf_host + + config.action_cable.allowed_request_origins = ["https://#{pf_host}"] +end diff --git a/8_0/ch02/config/environments/production.rb b/8_0/ch02/config/environments/production.rb new file mode 100644 index 00000000..17496077 --- /dev/null +++ b/8_0/ch02/config/environments/production.rb @@ -0,0 +1,89 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.enable_reloading = false + + # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). + config.eager_load = true + + # Full error reports are disabled. + config.consider_all_requests_local = false + + # Turn on fragment caching in view templates. + config.action_controller.perform_caching = true + + # Cache assets for far-future expiry since they are all digest stamped. + config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" } + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = "http://assets.example.com" + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Assume all access to the app is happening through a SSL-terminating reverse proxy. + config.assume_ssl = true + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = true + + # Skip http-to-https redirect for the default health check endpoint. + # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } + + # Log to STDOUT with the current request id as a default log tag. + config.log_tags = [ :request_id ] + config.logger = ActiveSupport::TaggedLogging.logger(STDOUT) + + # Change to "debug" to log everything (including potentially personally-identifiable information!) + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + + # Prevent health checks from clogging up the logs. + config.silence_healthcheck_path = "/up" + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Replace the default in-process memory cache store with a durable alternative. + # config.cache_store = :mem_cache_store + + # Replace the default in-process and non-durable queuing backend for Active Job. + # config.active_job.queue_adapter = :resque + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. + # config.action_mailer.smtp_settings = { + # user_name: Rails.application.credentials.dig(:smtp, :user_name), + # password: Rails.application.credentials.dig(:smtp, :password), + # address: "smtp.example.com", + # port: 587, + # authentication: :plain + # } + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Only use :id for inspections in production. + config.active_record.attributes_for_inspect = [ :id ] + + # Enable DNS rebinding protection and other `Host` header attacks. + # config.hosts = [ + # "example.com", # Allow requests from example.com + # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` + # ] + # + # Skip DNS rebinding protection for the default health check endpoint. + # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } +end diff --git a/8_0/ch02/config/environments/test.rb b/8_0/ch02/config/environments/test.rb new file mode 100644 index 00000000..c2095b11 --- /dev/null +++ b/8_0/ch02/config/environments/test.rb @@ -0,0 +1,53 @@ +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # While tests run files are not watched, reloading is not necessary. + config.enable_reloading = false + + # Eager loading loads your entire application. When running a single test locally, + # this is usually not necessary, and can slow down your test suite. However, it's + # recommended that you enable it in continuous integration systems to ensure eager + # loading is working properly before deploying your code. + config.eager_load = ENV["CI"].present? + + # Configure public file server for tests with cache-control for performance. + config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } + + # Show full error reports. + config.consider_all_requests_local = true + config.cache_store = :null_store + + # Render exception templates for rescuable exceptions and raise for other exceptions. + config.action_dispatch.show_exceptions = :rescuable + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :test + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true +end diff --git a/8_0/ch02/config/importmap.rb b/8_0/ch02/config/importmap.rb new file mode 100644 index 00000000..8dce42d4 --- /dev/null +++ b/8_0/ch02/config/importmap.rb @@ -0,0 +1,7 @@ +# Pin npm packages by running ./bin/importmap + +pin "application", preload: true +pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true +pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true +pin_all_from "app/javascript/controllers", under: "controllers" diff --git a/8_0/ch02/config/initializers/assets.rb b/8_0/ch02/config/initializers/assets.rb new file mode 100644 index 00000000..48732442 --- /dev/null +++ b/8_0/ch02/config/initializers/assets.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = "1.0" + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path diff --git a/8_0/ch02/config/initializers/content_security_policy.rb b/8_0/ch02/config/initializers/content_security_policy.rb new file mode 100644 index 00000000..b3076b38 --- /dev/null +++ b/8_0/ch02/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header + +# Rails.application.configure do +# config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end +# +# # Generate session nonces for permitted importmap, inline scripts, and inline styles. +# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } +# config.content_security_policy_nonce_directives = %w(script-src style-src) +# +# # Report violations without enforcing the policy. +# # config.content_security_policy_report_only = true +# end diff --git a/8_0/ch02/config/initializers/filter_parameter_logging.rb b/8_0/ch02/config/initializers/filter_parameter_logging.rb new file mode 100644 index 00000000..c0b717f7 --- /dev/null +++ b/8_0/ch02/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. +# Use this to limit dissemination of sensitive information. +# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. +Rails.application.config.filter_parameters += [ + :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc +] diff --git a/8_0/ch02/config/initializers/inflections.rb b/8_0/ch02/config/initializers/inflections.rb new file mode 100644 index 00000000..3860f659 --- /dev/null +++ b/8_0/ch02/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym "RESTful" +# end diff --git a/8_0/ch02/config/initializers/new_framework_defaults_8_0.rb b/8_0/ch02/config/initializers/new_framework_defaults_8_0.rb new file mode 100644 index 00000000..92efa951 --- /dev/null +++ b/8_0/ch02/config/initializers/new_framework_defaults_8_0.rb @@ -0,0 +1,30 @@ +# Be sure to restart your server when you modify this file. +# +# This file eases your Rails 8.0 framework defaults upgrade. +# +# Uncomment each configuration one by one to switch to the new default. +# Once your application is ready to run with all new defaults, you can remove +# this file and set the `config.load_defaults` to `8.0`. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. +# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html + +### +# Specifies whether `to_time` methods preserve the UTC offset of their receivers or preserves the timezone. +# If set to `:zone`, `to_time` methods will use the timezone of their receivers. +# If set to `:offset`, `to_time` methods will use the UTC offset. +# If `false`, `to_time` methods will convert to the local system UTC offset instead. +#++ +# Rails.application.config.active_support.to_time_preserves_timezone = :zone + +### +# When both `If-Modified-Since` and `If-None-Match` are provided by the client +# only consider `If-None-Match` as specified by RFC 7232 Section 6. +# If set to `false` both conditions need to be satisfied. +#++ +# Rails.application.config.action_dispatch.strict_freshness = true + +### +# Set `Regexp.timeout` to `1`s by default to improve security over Regexp Denial-of-Service attacks. +#++ +# Regexp.timeout = 1 diff --git a/8_0/ch02/config/initializers/permissions_policy.rb b/8_0/ch02/config/initializers/permissions_policy.rb new file mode 100644 index 00000000..00f64d71 --- /dev/null +++ b/8_0/ch02/config/initializers/permissions_policy.rb @@ -0,0 +1,11 @@ +# Define an application-wide HTTP permissions policy. For further +# information see https://developers.google.com/web/updates/2018/06/feature-policy +# +# Rails.application.config.permissions_policy do |f| +# f.camera :none +# f.gyroscope :none +# f.microphone :none +# f.usb :none +# f.fullscreen :self +# f.payment :self, "https://secure.example.com" +# end diff --git a/8_0/ch02/config/locales/en.yml b/8_0/ch02/config/locales/en.yml new file mode 100644 index 00000000..8ca56fc7 --- /dev/null +++ b/8_0/ch02/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# "true": "foo" +# +# To learn more, please read the Rails Internationalization guide +# available at https://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/8_0/ch02/config/puma.rb b/8_0/ch02/config/puma.rb new file mode 100644 index 00000000..a248513b --- /dev/null +++ b/8_0/ch02/config/puma.rb @@ -0,0 +1,41 @@ +# This configuration file will be evaluated by Puma. The top-level methods that +# are invoked here are part of Puma's configuration DSL. For more information +# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. +# +# Puma starts a configurable number of processes (workers) and each process +# serves each request in a thread from an internal thread pool. +# +# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You +# should only set this value when you want to run 2 or more workers. The +# default is already 1. +# +# The ideal number of threads per worker depends both on how much time the +# application spends waiting for IO operations and on how much you wish to +# prioritize throughput over latency. +# +# As a rule of thumb, increasing the number of threads will increase how much +# traffic a given process can handle (throughput), but due to CRuby's +# Global VM Lock (GVL) it has diminishing returns and will degrade the +# response time (latency) of the application. +# +# The default is set to 3 threads as it's deemed a decent compromise between +# throughput and latency for the average Rails application. +# +# Any libraries that use a connection pool or another resource pool should +# be configured to provide at least as many connections as the number of +# threads. This includes Active Record's `pool` parameter in `database.yml`. +threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +port ENV.fetch("PORT", 3000) + +# Allow puma to be restarted by `bin/rails restart` command. +plugin :tmp_restart + +# Run the Solid Queue supervisor inside of Puma for single-server deployments +plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] + +# Specify the PID file. Defaults to tmp/pids/server.pid in development. +# In other environments, only set the PID file if requested. +pidfile ENV["PIDFILE"] if ENV["PIDFILE"] diff --git a/8_0/ch02/config/routes.rb b/8_0/ch02/config/routes.rb new file mode 100644 index 00000000..59d82c41 --- /dev/null +++ b/8_0/ch02/config/routes.rb @@ -0,0 +1,5 @@ +Rails.application.routes.draw do + resources :microposts + resources :users + root 'users#index' +end diff --git a/8_0/ch02/config/storage.yml b/8_0/ch02/config/storage.yml new file mode 100644 index 00000000..4942ab66 --- /dev/null +++ b/8_0/ch02/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket-<%= Rails.env %> + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket-<%= Rails.env %> + +# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name-<%= Rails.env %> + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/8_0/ch02/db/migrate/20260501084327_create_users.rb b/8_0/ch02/db/migrate/20260501084327_create_users.rb new file mode 100644 index 00000000..3b80eb00 --- /dev/null +++ b/8_0/ch02/db/migrate/20260501084327_create_users.rb @@ -0,0 +1,10 @@ +class CreateUsers < ActiveRecord::Migration[8.0] + def change + create_table :users do |t| + t.string :name + t.string :email + + t.timestamps + end + end +end diff --git a/8_0/ch02/db/migrate/20260501084610_create_microposts.rb b/8_0/ch02/db/migrate/20260501084610_create_microposts.rb new file mode 100644 index 00000000..8c24d0a3 --- /dev/null +++ b/8_0/ch02/db/migrate/20260501084610_create_microposts.rb @@ -0,0 +1,10 @@ +class CreateMicroposts < ActiveRecord::Migration[8.0] + def change + create_table :microposts do |t| + t.text :content + t.integer :user_id + + t.timestamps + end + end +end diff --git a/8_0/ch02/db/schema.rb b/8_0/ch02/db/schema.rb new file mode 100644 index 00000000..77af234c --- /dev/null +++ b/8_0/ch02/db/schema.rb @@ -0,0 +1,27 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[8.0].define(version: 2026_05_01_084610) do + create_table "microposts", force: :cascade do |t| + t.text "content" + t.integer "user_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "users", force: :cascade do |t| + t.string "name" + t.string "email" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end +end diff --git a/8_0/ch02/db/seeds.rb b/8_0/ch02/db/seeds.rb new file mode 100644 index 00000000..bc25fce3 --- /dev/null +++ b/8_0/ch02/db/seeds.rb @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). +# +# Examples: +# +# movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }]) +# Character.create(name: "Luke", movie: movies.first) diff --git a/8_0/ch02/lib/assets/.keep b/8_0/ch02/lib/assets/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/lib/tasks/.keep b/8_0/ch02/lib/tasks/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/log/.keep b/8_0/ch02/log/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/public/400.html b/8_0/ch02/public/400.html new file mode 100644 index 00000000..282dbc8c --- /dev/null +++ b/8_0/ch02/public/400.html @@ -0,0 +1,114 @@ + + + + + + + The server cannot process the request due to a client error (400 Bad Request) + + + + + + + + + + + + + +
+
+ +
+
+

The server cannot process the request due to a client error. Please check the request and try again. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch02/public/404.html b/8_0/ch02/public/404.html new file mode 100644 index 00000000..c0670bc8 --- /dev/null +++ b/8_0/ch02/public/404.html @@ -0,0 +1,114 @@ + + + + + + + The page you were looking for doesn’t exist (404 Not found) + + + + + + + + + + + + + +
+
+ +
+
+

The page you were looking for doesn’t exist. You may have mistyped the address or the page may have moved. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch02/public/406-unsupported-browser.html b/8_0/ch02/public/406-unsupported-browser.html new file mode 100644 index 00000000..9532a9cc --- /dev/null +++ b/8_0/ch02/public/406-unsupported-browser.html @@ -0,0 +1,114 @@ + + + + + + + Your browser is not supported (406 Not Acceptable) + + + + + + + + + + + + + +
+
+ +
+
+

Your browser is not supported.
Please upgrade your browser to continue.

+
+
+ + + + diff --git a/8_0/ch02/public/422.html b/8_0/ch02/public/422.html new file mode 100644 index 00000000..8bcf0601 --- /dev/null +++ b/8_0/ch02/public/422.html @@ -0,0 +1,114 @@ + + + + + + + The change you wanted was rejected (422 Unprocessable Entity) + + + + + + + + + + + + + +
+
+ +
+
+

The change you wanted was rejected. Maybe you tried to change something you didn’t have access to. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch02/public/500.html b/8_0/ch02/public/500.html new file mode 100644 index 00000000..d77718c3 --- /dev/null +++ b/8_0/ch02/public/500.html @@ -0,0 +1,114 @@ + + + + + + + We’re sorry, but something went wrong (500 Internal Server Error) + + + + + + + + + + + + + +
+
+ +
+
+

We’re sorry, but something went wrong.
If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch02/public/apple-touch-icon-precomposed.png b/8_0/ch02/public/apple-touch-icon-precomposed.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/public/apple-touch-icon.png b/8_0/ch02/public/apple-touch-icon.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/public/favicon.ico b/8_0/ch02/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/public/icon.png b/8_0/ch02/public/icon.png new file mode 100644 index 00000000..c4c9dbfb Binary files /dev/null and b/8_0/ch02/public/icon.png differ diff --git a/8_0/ch02/public/icon.svg b/8_0/ch02/public/icon.svg new file mode 100644 index 00000000..04b34bf8 --- /dev/null +++ b/8_0/ch02/public/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/8_0/ch02/public/railstutorial.png b/8_0/ch02/public/railstutorial.png new file mode 100644 index 00000000..70835e6b Binary files /dev/null and b/8_0/ch02/public/railstutorial.png differ diff --git a/8_0/ch02/public/robots.txt b/8_0/ch02/public/robots.txt new file mode 100644 index 00000000..c19f78ab --- /dev/null +++ b/8_0/ch02/public/robots.txt @@ -0,0 +1 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/8_0/ch02/storage/.keep b/8_0/ch02/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/test/application_system_test_case.rb b/8_0/ch02/test/application_system_test_case.rb new file mode 100644 index 00000000..d19212ab --- /dev/null +++ b/8_0/ch02/test/application_system_test_case.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/8_0/ch02/test/channels/application_cable/connection_test.rb b/8_0/ch02/test/channels/application_cable/connection_test.rb new file mode 100644 index 00000000..800405f1 --- /dev/null +++ b/8_0/ch02/test/channels/application_cable/connection_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase + # test "connects with cookies" do + # cookies.signed[:user_id] = 42 + # + # connect + # + # assert_equal connection.user_id, "42" + # end +end diff --git a/8_0/ch02/test/controllers/.keep b/8_0/ch02/test/controllers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/test/controllers/hello_codespaces_controller_test.rb b/8_0/ch02/test/controllers/hello_codespaces_controller_test.rb new file mode 100644 index 00000000..54353ea6 --- /dev/null +++ b/8_0/ch02/test/controllers/hello_codespaces_controller_test.rb @@ -0,0 +1,8 @@ +require "test_helper" + +class HelloCodespacesControllerTest < ActionDispatch::IntegrationTest + # test "should get index" do + # get "/" + # assert_response :success + # end +end diff --git a/8_0/ch02/test/controllers/microposts_controller_test.rb b/8_0/ch02/test/controllers/microposts_controller_test.rb new file mode 100644 index 00000000..aeb980b2 --- /dev/null +++ b/8_0/ch02/test/controllers/microposts_controller_test.rb @@ -0,0 +1,48 @@ +require "test_helper" + +class MicropostsControllerTest < ActionDispatch::IntegrationTest + setup do + @micropost = microposts(:one) + end + + test "should get index" do + get microposts_url + assert_response :success + end + + test "should get new" do + get new_micropost_url + assert_response :success + end + + test "should create micropost" do + assert_difference("Micropost.count") do + post microposts_url, params: { micropost: { content: @micropost.content, user_id: @micropost.user_id } } + end + + assert_redirected_to micropost_url(Micropost.last) + end + + test "should show micropost" do + get micropost_url(@micropost) + assert_response :success + end + + test "should get edit" do + get edit_micropost_url(@micropost) + assert_response :success + end + + test "should update micropost" do + patch micropost_url(@micropost), params: { micropost: { content: @micropost.content, user_id: @micropost.user_id } } + assert_redirected_to micropost_url(@micropost) + end + + test "should destroy micropost" do + assert_difference("Micropost.count", -1) do + delete micropost_url(@micropost) + end + + assert_redirected_to microposts_url + end +end diff --git a/8_0/ch02/test/controllers/users_controller_test.rb b/8_0/ch02/test/controllers/users_controller_test.rb new file mode 100644 index 00000000..b548c579 --- /dev/null +++ b/8_0/ch02/test/controllers/users_controller_test.rb @@ -0,0 +1,48 @@ +require "test_helper" + +class UsersControllerTest < ActionDispatch::IntegrationTest + setup do + @user = users(:one) + end + + test "should get index" do + get users_url + assert_response :success + end + + test "should get new" do + get new_user_url + assert_response :success + end + + test "should create user" do + assert_difference("User.count") do + post users_url, params: { user: { email: @user.email, name: @user.name } } + end + + assert_redirected_to user_url(User.last) + end + + test "should show user" do + get user_url(@user) + assert_response :success + end + + test "should get edit" do + get edit_user_url(@user) + assert_response :success + end + + test "should update user" do + patch user_url(@user), params: { user: { email: @user.email, name: @user.name } } + assert_redirected_to user_url(@user) + end + + test "should destroy user" do + assert_difference("User.count", -1) do + delete user_url(@user) + end + + assert_redirected_to users_url + end +end diff --git a/8_0/ch02/test/fixtures/files/.keep b/8_0/ch02/test/fixtures/files/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/test/fixtures/microposts.yml b/8_0/ch02/test/fixtures/microposts.yml new file mode 100644 index 00000000..4040ba28 --- /dev/null +++ b/8_0/ch02/test/fixtures/microposts.yml @@ -0,0 +1,9 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + content: MyText + user_id: 1 + +two: + content: MyText + user_id: 1 diff --git a/8_0/ch02/test/fixtures/users.yml b/8_0/ch02/test/fixtures/users.yml new file mode 100644 index 00000000..11c0b4d5 --- /dev/null +++ b/8_0/ch02/test/fixtures/users.yml @@ -0,0 +1,9 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + name: MyString + email: MyString + +two: + name: MyString + email: MyString diff --git a/8_0/ch02/test/helpers/.keep b/8_0/ch02/test/helpers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/test/integration/.keep b/8_0/ch02/test/integration/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/test/mailers/.keep b/8_0/ch02/test/mailers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/test/models/.keep b/8_0/ch02/test/models/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/test/models/micropost_test.rb b/8_0/ch02/test/models/micropost_test.rb new file mode 100644 index 00000000..9a20fe61 --- /dev/null +++ b/8_0/ch02/test/models/micropost_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class MicropostTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/8_0/ch02/test/models/user_test.rb b/8_0/ch02/test/models/user_test.rb new file mode 100644 index 00000000..5c07f490 --- /dev/null +++ b/8_0/ch02/test/models/user_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class UserTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/8_0/ch02/test/system/.keep b/8_0/ch02/test/system/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/test/system/microposts_test.rb b/8_0/ch02/test/system/microposts_test.rb new file mode 100644 index 00000000..715225dd --- /dev/null +++ b/8_0/ch02/test/system/microposts_test.rb @@ -0,0 +1,43 @@ +require "application_system_test_case" + +class MicropostsTest < ApplicationSystemTestCase + setup do + @micropost = microposts(:one) + end + + test "visiting the index" do + visit microposts_url + assert_selector "h1", text: "Microposts" + end + + test "should create micropost" do + visit microposts_url + click_on "New micropost" + + fill_in "Content", with: @micropost.content + fill_in "User", with: @micropost.user_id + click_on "Create Micropost" + + assert_text "Micropost was successfully created" + click_on "Back" + end + + test "should update Micropost" do + visit micropost_url(@micropost) + click_on "Edit this micropost", match: :first + + fill_in "Content", with: @micropost.content + fill_in "User", with: @micropost.user_id + click_on "Update Micropost" + + assert_text "Micropost was successfully updated" + click_on "Back" + end + + test "should destroy Micropost" do + visit micropost_url(@micropost) + click_on "Destroy this micropost", match: :first + + assert_text "Micropost was successfully destroyed" + end +end diff --git a/8_0/ch02/test/system/users_test.rb b/8_0/ch02/test/system/users_test.rb new file mode 100644 index 00000000..c67556c0 --- /dev/null +++ b/8_0/ch02/test/system/users_test.rb @@ -0,0 +1,43 @@ +require "application_system_test_case" + +class UsersTest < ApplicationSystemTestCase + setup do + @user = users(:one) + end + + test "visiting the index" do + visit users_url + assert_selector "h1", text: "Users" + end + + test "should create user" do + visit users_url + click_on "New user" + + fill_in "Email", with: @user.email + fill_in "Name", with: @user.name + click_on "Create User" + + assert_text "User was successfully created" + click_on "Back" + end + + test "should update User" do + visit user_url(@user) + click_on "Edit this user", match: :first + + fill_in "Email", with: @user.email + fill_in "Name", with: @user.name + click_on "Update User" + + assert_text "User was successfully updated" + click_on "Back" + end + + test "should destroy User" do + visit user_url(@user) + click_on "Destroy this user", match: :first + + assert_text "User was successfully destroyed" + end +end diff --git a/8_0/ch02/test/test_helper.rb b/8_0/ch02/test/test_helper.rb new file mode 100644 index 00000000..d713e377 --- /dev/null +++ b/8_0/ch02/test/test_helper.rb @@ -0,0 +1,13 @@ +ENV["RAILS_ENV"] ||= "test" +require_relative "../config/environment" +require "rails/test_help" + +class ActiveSupport::TestCase + # Run tests in parallel with specified workers + parallelize(workers: :number_of_processors) + + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. + fixtures :all + + # Add more helper methods to be used by all tests here... +end diff --git a/8_0/ch02/tmp/.keep b/8_0/ch02/tmp/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/tmp/pids/.keep b/8_0/ch02/tmp/pids/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/tmp/storage/.keep b/8_0/ch02/tmp/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/vendor/.keep b/8_0/ch02/vendor/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch02/vendor/javascript/.keep b/8_0/ch02/vendor/javascript/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/.devcontainer/devcontainer.json b/8_0/ch03/.devcontainer/devcontainer.json new file mode 100644 index 00000000..e7243217 --- /dev/null +++ b/8_0/ch03/.devcontainer/devcontainer.json @@ -0,0 +1,105 @@ +// Specifications: https://containers.dev/implementors/json_reference/ +// Format details: https://aka.ms/devcontainer.json +// Config options: https://github.com/microsoft/vscode-dev-containers/tree/main/containers/ruby +{ + "name": "railstutorial", + + // Universal is well-customized image for Codespaces: + // https://hub.docker.com/_/microsoft-devcontainers-universal + //"image": "mcr.microsoft.com/devcontainers/universal:2", + //"features": { + // "ghcr.io/devcontainers/features/ruby:1": {} + //}, + + // Use Ruby image if you want to pin Ruby version like '3.2' + // https://github.com/devcontainers/images/tree/main/src/ruby + "image": "mcr.microsoft.com/devcontainers/ruby:3.2", + //"workspaceFolder": "/railstutorial", => Container build failed + + // Enable learners to choose an affordable spec, starting at minimum one. + //"hostRequirements": { + // "cpus": 2, + // "memory": "4gb", + // "storage": "32gb" + //}, + + "waitFor": "onCreateCommand", + //"onCreateCommand": "", + "onCreateCommand": "gem install solargraph -v '0.58.2' -N", + //"onCreateCommand": "gem install solargraph -v '0.50.0' -N && gem install ruby-lsp -N", + //"onCreateCommand": "gem install ruby-lsp -N", + //# => Solargraph gem not found. Run `gem install solargraph` or update your Gemfile. + "updateContentCommand": "bundle install", + "postCreateCommand": "", + "postAttachCommand": { + "server": "rails server" + }, + "customizations": { + "codespaces": { + "openFiles": [ + "app/views/hello/index.html.erb" + ] + }, + "vscode": { + "extensions": [ + "GitHub.codespaces", + "Shopify.ruby-lsp", // https://github.com/Shopify/ruby-lsp + "castwide.solargraph" // https://github.com/castwide/vscode-solargraph + //"rebornix.Ruby", // https://github.com/rubyide/vscode-ruby (Deprecated) + + ], + "settings": { + // General settings for Codespaces (VS Code) + //"ruby.useLanguageServer": true , + //"ruby.format": "rubocop", + //"ruby.lint": { "rubocop": true }, + //"ruby.intellisense": "rubyLocate", + "editor.tabSize": 2, + "editor.formatOnSave": false, // Disable onSave to show diff edited by learners only + "editor.formatOnType": false, // Disable onType for the same reason above + "editor.insertSpaces": true, // Use spaces, not tabs, to avoid errors for learners + "editor.renderWhitespace": "none", + "[ruby]": { + "editor.defaultFormatter": "castwide.solargraph", + "editor.semanticHighlighting.enabled": true // Enable semantic highlighting + }, + "files.associations": { "*.erb": "erb" }, + "emmet.includeLanguages": { "erb": "html" }, + + // Settings for Solargraph + // https://github.com/castwide/solargraph + "solargraph.useBundler": false, + "solargraph.diagnostics": false, + "solargraph.formatting": true, // Use Ctrl+Shift+P->Format to format + "solargraph.autoformat": false, + "solargraph.definitions": true, + "solargraph.completion": true, + "solargraph.references": true, + "solargraph.symbols": true, + "solargraph.rename": true, + "solargraph.hover": true, + + // Settings for Ruby LSP + // https://github.com/Shopify/ruby-lsp + "rubyLsp.rubyVersionManager": { + "identifier": "none" + }, + "rubyLsp.formatter": "none", + "rubyLsp.enabledFeatures": { + "diagnostics": false, + "formatting": false + } + } + } + }, + "remoteEnv": { + "EDITOR": "code --wait" + }, + "portsAttributes": { + "3000": { + "label": "Application", + "onAutoForward": "openPreview" + } + }, + "forwardPorts": [3000] +} diff --git a/8_0/ch03/.devcontainer/icon.svg b/8_0/ch03/.devcontainer/icon.svg new file mode 100644 index 00000000..f8444a4a --- /dev/null +++ b/8_0/ch03/.devcontainer/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/8_0/ch03/.gitattributes b/8_0/ch03/.gitattributes new file mode 100644 index 00000000..31eeee0b --- /dev/null +++ b/8_0/ch03/.gitattributes @@ -0,0 +1,7 @@ +# See https://git-scm.com/docs/gitattributes for more about git attribute files. + +# Mark the database schema as having been generated. +db/schema.rb linguist-generated + +# Mark any vendored files as having been vendored. +vendor/* linguist-vendored diff --git a/8_0/ch03/.gitignore b/8_0/ch03/.gitignore new file mode 100644 index 00000000..e1ef53ef --- /dev/null +++ b/8_0/ch03/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-* + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore uploaded files in development. +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/ +!/tmp/storage/.keep + +/public/assets + +# Ignore master key for decrypting credentials and more. +/config/master.key + +# Ignore history file used by IRB for interactive Ruby. +.irb_history + +dump.rdb diff --git a/8_0/ch03/.irbrc b/8_0/ch03/.irbrc new file mode 100644 index 00000000..3737e036 --- /dev/null +++ b/8_0/ch03/.irbrc @@ -0,0 +1 @@ +IRB.conf[:COMPLETOR] = :type # default is :regexp diff --git a/8_0/ch03/.solargraph.yml b/8_0/ch03/.solargraph.yml new file mode 100644 index 00000000..76ac24a6 --- /dev/null +++ b/8_0/ch03/.solargraph.yml @@ -0,0 +1,32 @@ +--- +include: +- "**/*.rb" +exclude: +- spec/**/* +- test/**/* +- vendor/**/* +- ".bundle/**/*" +require: +- actioncable +- actionmailer +- actionpack +- actionview +- activejob +- activemodel +- activerecord +- activestorage +- activesupport +domains: [] +reporters: +- rubocop +- require_not_found +- typecheck +formatter: + rubocop: + cops: safe + except: [] + only: [] + extra_args: [] +require_paths: [] +plugins: [] +max_files: 5000 diff --git a/8_0/ch03/.vscode/settings.json b/8_0/ch03/.vscode/settings.json new file mode 100644 index 00000000..cf85f519 --- /dev/null +++ b/8_0/ch03/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.minimap.enabled": false +} diff --git a/8_0/ch03/Gemfile b/8_0/ch03/Gemfile new file mode 100644 index 00000000..46cefd03 --- /dev/null +++ b/8_0/ch03/Gemfile @@ -0,0 +1,42 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '3.2.10' + +gem 'bootsnap', '1.16.0', require: false +gem 'concurrent-ruby', '1.3.4' +gem 'importmap-rails', '1.1.5' +gem 'jbuilder', '2.14.1' +gem 'puma', '6.6.1' +gem 'rails', '8.0.2.1' +gem 'sassc-rails', '2.1.2' +gem 'sprockets-rails', '3.4.2' +gem 'sqlite3', '2.7.3' +gem 'stimulus-rails', '1.2.1' +gem 'turbo-rails', '1.4.0' + +group :development, :test do + gem 'debug', '1.7.1', platforms: %i[mri mingw x64_mingw] + gem 'reline', '0.5.10' +end + +group :development do + gem 'irb', '1.15.2' + gem 'repl_type_completor', '0.1.10' + gem 'solargraph', '0.58.2' + gem 'web-console', '4.2.0' +end + +group :test do + gem 'capybara', '3.38.0' + gem 'guard', '2.18.0' + gem 'guard-minitest', '2.4.6' + gem 'minitest', '5.18.0' + gem 'minitest-reporters', '1.6.0' + gem 'rails-controller-testing', '1.0.5' + gem 'selenium-webdriver', '4.8.3' + gem 'webdrivers', '5.2.0' +end + +# Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります +# gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] diff --git a/8_0/ch03/Gemfile.lock b/8_0/ch03/Gemfile.lock new file mode 100644 index 00000000..a3ee4202 --- /dev/null +++ b/8_0/ch03/Gemfile.lock @@ -0,0 +1,433 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + actionmailer (8.0.2.1) + actionpack (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.0.2.1) + actionview (= 8.0.2.1) + activesupport (= 8.0.2.1) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.0.2.1) + actionpack (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (8.0.2.1) + activesupport (= 8.0.2.1) + builder (~> 3.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.3.6) + activemodel (8.0.2.1) + activesupport (= 8.0.2.1) + activerecord (8.0.2.1) + activemodel (= 8.0.2.1) + activesupport (= 8.0.2.1) + timeout (>= 0.4.0) + activestorage (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activesupport (= 8.0.2.1) + marcel (~> 1.0) + activesupport (8.0.2.1) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + ansi (1.5.0) + ast (2.4.3) + backport (1.2.0) + base64 (0.3.0) + benchmark (0.4.1) + bigdecimal (3.2.3) + bindex (0.8.1) + bootsnap (1.16.0) + msgpack (~> 1.2) + builder (3.3.0) + capybara (3.38.0) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + coderay (1.1.3) + concurrent-ruby (1.3.4) + connection_pool (2.5.4) + crass (1.0.6) + date (3.4.1) + debug (1.7.1) + irb (>= 1.5.0) + reline (>= 0.3.1) + diff-lcs (1.6.2) + drb (2.2.3) + erb (5.0.2) + erubi (1.13.1) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) + formatador (1.2.0) + reline + globalid (1.2.1) + activesupport (>= 6.1) + guard (2.18.0) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.13.0) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-minitest (2.4.6) + guard-compat (~> 1.2) + minitest (>= 3.0) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + importmap-rails (1.1.5) + actionpack (>= 6.0.0) + railties (>= 6.0.0) + io-console (0.8.1) + irb (1.15.2) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jaro_winkler (1.6.1) + jbuilder (2.14.1) + actionview (>= 7.0.0) + activesupport (>= 7.0.0) + json (2.13.2) + kramdown (2.5.1) + rexml (>= 3.3.9) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) + loofah (2.24.1) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + lumberjack (1.4.1) + mail (2.8.1) + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.0.4) + matrix (0.4.3) + method_source (1.1.0) + mini_mime (1.1.5) + minitest (5.18.0) + minitest-reporters (1.6.0) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + msgpack (1.8.0) + nenv (0.3.0) + net-imap (0.5.10) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.4) + nokogiri (1.19.1-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-musl) + racc (~> 1.4) + notiffany (0.1.3) + nenv (~> 0.1) + shellany (~> 0.0) + observer (0.1.2) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.9.0) + ast (~> 2.4.1) + racc + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + prism (1.4.0) + pry (0.15.2) + coderay (~> 1.1) + method_source (~> 1.0) + psych (5.2.6) + date + stringio + public_suffix (6.0.2) + puma (6.6.1) + nio4r (~> 2.0) + racc (1.8.1) + rack (3.2.5) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.2.1) + rack (>= 3) + rails (8.0.2.1) + actioncable (= 8.0.2.1) + actionmailbox (= 8.0.2.1) + actionmailer (= 8.0.2.1) + actionpack (= 8.0.2.1) + actiontext (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activemodel (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + bundler (>= 1.15.0) + railties (= 8.0.2.1) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.0) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rbs (3.6.1) + logger + rdoc (6.14.2) + erb + psych (>= 4.0.0) + regexp_parser (2.11.2) + reline (0.5.10) + io-console (~> 0.5) + repl_type_completor (0.1.10) + prism (~> 1.0) + rbs (>= 2.7.0, < 4.0.0) + reverse_markdown (3.0.0) + nokogiri + rexml (3.4.2) + rubocop (1.80.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.46.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.46.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + ruby-progressbar (1.13.0) + rubyzip (2.4.1) + sassc (2.4.0) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt + securerandom (0.4.1) + selenium-webdriver (4.8.3) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) + shellany (0.0.1) + solargraph (0.58.2) + backport (~> 1.2) + benchmark (~> 0.4) + bundler (~> 2.0) + diff-lcs (~> 1.4) + jaro_winkler (~> 1.6, >= 1.6.1) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.1) + logger (~> 1.6) + observer (~> 0.1) + ostruct (~> 0.6) + parser (~> 3.0) + prism (~> 1.4) + rbs (~> 3.6.1) + reverse_markdown (~> 3.0) + rubocop (~> 1.38) + thor (~> 1.0) + tilt (~> 2.0) + yard (~> 0.9, >= 0.9.24) + yard-solargraph (~> 0.1) + sprockets (4.2.2) + concurrent-ruby (~> 1.0) + logger + rack (>= 2.2.4, < 4) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + sprockets (>= 3.0.0) + sqlite3 (2.7.3-aarch64-linux-gnu) + sqlite3 (2.7.3-aarch64-linux-musl) + sqlite3 (2.7.3-arm-linux-gnu) + sqlite3 (2.7.3-arm-linux-musl) + sqlite3 (2.7.3-arm64-darwin) + sqlite3 (2.7.3-x86_64-darwin) + sqlite3 (2.7.3-x86_64-linux-gnu) + sqlite3 (2.7.3-x86_64-linux-musl) + stimulus-rails (1.2.1) + railties (>= 6.0.0) + stringio (3.1.7) + thor (1.4.0) + tilt (2.6.1) + timeout (0.4.3) + turbo-rails (1.4.0) + actionpack (>= 6.0.0) + activejob (>= 6.0.0) + railties (>= 6.0.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.1.5) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + uri (1.0.4) + useragent (0.16.11) + web-console (4.2.0) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + webdrivers (5.2.0) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (~> 4.0) + websocket (1.2.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) + yard (0.9.37) + yard-solargraph (0.1.0) + yard (~> 0.9) + zeitwerk (2.7.3) + +PLATFORMS + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + bootsnap (= 1.16.0) + capybara (= 3.38.0) + concurrent-ruby (= 1.3.4) + debug (= 1.7.1) + guard (= 2.18.0) + guard-minitest (= 2.4.6) + importmap-rails (= 1.1.5) + irb (= 1.15.2) + jbuilder (= 2.14.1) + minitest (= 5.18.0) + minitest-reporters (= 1.6.0) + puma (= 6.6.1) + rails (= 8.0.2.1) + rails-controller-testing (= 1.0.5) + reline (= 0.5.10) + repl_type_completor (= 0.1.10) + sassc-rails (= 2.1.2) + selenium-webdriver (= 4.8.3) + solargraph (= 0.58.2) + sprockets-rails (= 3.4.2) + sqlite3 (= 2.7.3) + stimulus-rails (= 1.2.1) + turbo-rails (= 1.4.0) + web-console (= 4.2.0) + webdrivers (= 5.2.0) + +RUBY VERSION + ruby 3.2.10p266 + +BUNDLED WITH + 2.5.6 diff --git a/8_0/ch03/Guardfile b/8_0/ch03/Guardfile new file mode 100644 index 00000000..5def34a0 --- /dev/null +++ b/8_0/ch03/Guardfile @@ -0,0 +1,76 @@ + require "active_support/inflector" + # Guardのマッチング規則を定義 + guard :minitest, all_on_start: false do + watch(%r{^test/(.*)/?(.*)_test\.rb$}) + watch('test/test_helper.rb') { 'test' } + watch('config/routes.rb') { interface_tests } + watch(%r{app/views/layouts/*}) { interface_tests } + watch(%r{^app/models/(.*?)\.rb$}) do |matches| + ["test/models/#{matches[1]}_test.rb", + "test/integration/microposts_interface_test.rb"] + end + watch(%r{^test/fixtures/(.*?)\.yml$}) do |matches| + "test/models/#{matches[1].singularize}_test.rb" + end + watch(%r{^app/mailers/(.*?)\.rb$}) do |matches| + "test/mailers/#{matches[1]}_test.rb" + end + watch(%r{^app/views/(.*)_mailer/.*$}) do |matches| + "test/mailers/#{matches[1]}_mailer_test.rb" + end + watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches| + resource_tests(matches[1]) + end + watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches| + ["test/controllers/#{matches[1]}_controller_test.rb"] + + integration_tests(matches[1]) + end + watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches| + integration_tests(matches[1]) + end + watch('app/views/layouts/application.html.erb') do + 'test/integration/site_layout_test.rb' + end + watch('app/helpers/sessions_helper.rb') do + integration_tests << 'test/helpers/sessions_helper_test.rb' + end + watch('app/controllers/sessions_controller.rb') do + ['test/controllers/sessions_controller_test.rb', + 'test/integration/users_login_test.rb'] + end + watch('app/controllers/account_activations_controller.rb') do + 'test/integration/users_signup_test.rb' + end + watch(%r{app/views/users/*}) do + resource_tests('users') + + ['test/integration/microposts_interface_test.rb'] + end + watch('app/controllers/relationships_controller.rb') do + ['test/controllers/relationships_controller_test.rb', + 'test/integration/following_test.rb'] + end + end + + # 指定のリソースに対応する統合テストを返す + def integration_tests(resource = :all) + if resource == :all + Dir["test/integration/*"] + else + Dir["test/integration/#{resource}_*.rb"] + end + end + + # インターフェースが該当するすべてのテストを返す + def interface_tests + integration_tests << "test/controllers" + end + + # 指定のリソースに対応するコントローラのテストを返す + def controller_test(resource) + "test/controllers/#{resource}_controller_test.rb" + end + + # 指定のリソースに対応するすべてのテストを返す + def resource_tests(resource) + integration_tests(resource) << controller_test(resource) + end diff --git a/8_0/ch03/LICENSE b/8_0/ch03/LICENSE new file mode 100644 index 00000000..20622e0c --- /dev/null +++ b/8_0/ch03/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 GitHub & 2023 YassLab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/8_0/ch03/README.md b/8_0/ch03/README.md new file mode 100644 index 00000000..17ca733d --- /dev/null +++ b/8_0/ch03/README.md @@ -0,0 +1,35 @@ +# Ruby on Rails チュートリアルのサンプルアプリケーション + +これは、次の教材で作られたサンプルアプリケーションです。 +[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +(第8版) +[Michael Hartl](https://www.michaelhartl.com/) 著 + +## ライセンス + +[Ruby on Rails チュートリアル](https://railstutorial.jp/)内にある +ソースコードはMITライセンスとBeerwareライセンスのもとで公開されています。 +詳細は [LICENSE.md](LICENSE.md) をご覧ください。 + +## 使い方 + +このアプリケーションを動かす場合は、まずはリポジトリをフォークしてください。 + +フォークしたリポジトリで、「Code」から「Codespaces」タブに移動し、 +「Create codespace on main」をクリックすると環境構築がスタートします。 +Railsサーバーが立ち上がり、シンプルブラウザが表示されるまでしばらくお待ちください。 + +次に、データベースへのマイグレーションを実行します。 + +``` +$ rails db:migrate +``` + +最後に、テストを実行してうまく動いているかどうか確認してください。 + +``` +$ rails test +``` + +詳しくは、[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +を参考にしてください。 diff --git a/8_0/ch03/Rakefile b/8_0/ch03/Rakefile new file mode 100644 index 00000000..9a5ea738 --- /dev/null +++ b/8_0/ch03/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/8_0/ch03/app/assets/config/manifest.js b/8_0/ch03/app/assets/config/manifest.js new file mode 100644 index 00000000..ddd546a0 --- /dev/null +++ b/8_0/ch03/app/assets/config/manifest.js @@ -0,0 +1,4 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js diff --git a/8_0/ch03/app/assets/images/.keep b/8_0/ch03/app/assets/images/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/app/assets/stylesheets/application.css b/8_0/ch03/app/assets/stylesheets/application.css new file mode 100644 index 00000000..288b9ab7 --- /dev/null +++ b/8_0/ch03/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/8_0/ch03/app/assets/stylesheets/hello.css b/8_0/ch03/app/assets/stylesheets/hello.css new file mode 100644 index 00000000..497881a4 --- /dev/null +++ b/8_0/ch03/app/assets/stylesheets/hello.css @@ -0,0 +1,40 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.codespaces { + text-align: center; +} +.codespaces code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} +.codespaces-logo { + height: 40vmin; + pointer-events: none; +} +.codespaces-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} +.codespaces-link { + color: #61dafb; +} + +.heart { + color: #ff0000; +} +.small { + font-size: 0.75rem; +} diff --git a/8_0/ch03/app/channels/application_cable/channel.rb b/8_0/ch03/app/channels/application_cable/channel.rb new file mode 100644 index 00000000..d6726972 --- /dev/null +++ b/8_0/ch03/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/8_0/ch03/app/channels/application_cable/connection.rb b/8_0/ch03/app/channels/application_cable/connection.rb new file mode 100644 index 00000000..0ff5442f --- /dev/null +++ b/8_0/ch03/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/8_0/ch03/app/controllers/application_controller.rb b/8_0/ch03/app/controllers/application_controller.rb new file mode 100644 index 00000000..09705d12 --- /dev/null +++ b/8_0/ch03/app/controllers/application_controller.rb @@ -0,0 +1,2 @@ +class ApplicationController < ActionController::Base +end diff --git a/8_0/ch03/app/controllers/concerns/.keep b/8_0/ch03/app/controllers/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/app/controllers/hello_controller.rb b/8_0/ch03/app/controllers/hello_controller.rb new file mode 100644 index 00000000..52037537 --- /dev/null +++ b/8_0/ch03/app/controllers/hello_controller.rb @@ -0,0 +1,4 @@ +class HelloController < ApplicationController + def index + end +end diff --git a/8_0/ch03/app/controllers/static_pages_controller.rb b/8_0/ch03/app/controllers/static_pages_controller.rb new file mode 100644 index 00000000..49ccd414 --- /dev/null +++ b/8_0/ch03/app/controllers/static_pages_controller.rb @@ -0,0 +1,11 @@ +class StaticPagesController < ApplicationController + + def home + end + + def help + end + + def about + end +end diff --git a/8_0/ch03/app/helpers/application_helper.rb b/8_0/ch03/app/helpers/application_helper.rb new file mode 100644 index 00000000..de6be794 --- /dev/null +++ b/8_0/ch03/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/8_0/ch03/app/helpers/hello_codespaces_helper.rb b/8_0/ch03/app/helpers/hello_codespaces_helper.rb new file mode 100644 index 00000000..874a2ca2 --- /dev/null +++ b/8_0/ch03/app/helpers/hello_codespaces_helper.rb @@ -0,0 +1,2 @@ +module HelloCodespacesHelper +end diff --git a/8_0/ch03/app/helpers/static_pages_helper.rb b/8_0/ch03/app/helpers/static_pages_helper.rb new file mode 100644 index 00000000..2d63e79e --- /dev/null +++ b/8_0/ch03/app/helpers/static_pages_helper.rb @@ -0,0 +1,2 @@ +module StaticPagesHelper +end diff --git a/8_0/ch03/app/javascript/application.js b/8_0/ch03/app/javascript/application.js new file mode 100644 index 00000000..0d7b4940 --- /dev/null +++ b/8_0/ch03/app/javascript/application.js @@ -0,0 +1,3 @@ +// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails +import "@hotwired/turbo-rails" +import "controllers" diff --git a/8_0/ch03/app/javascript/controllers/application.js b/8_0/ch03/app/javascript/controllers/application.js new file mode 100644 index 00000000..1213e85c --- /dev/null +++ b/8_0/ch03/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/8_0/ch03/app/javascript/controllers/hello_controller.js b/8_0/ch03/app/javascript/controllers/hello_controller.js new file mode 100644 index 00000000..5975c078 --- /dev/null +++ b/8_0/ch03/app/javascript/controllers/hello_controller.js @@ -0,0 +1,7 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.element.textContent = "Hello World!" + } +} diff --git a/8_0/ch03/app/javascript/controllers/index.js b/8_0/ch03/app/javascript/controllers/index.js new file mode 100644 index 00000000..54ad4cad --- /dev/null +++ b/8_0/ch03/app/javascript/controllers/index.js @@ -0,0 +1,11 @@ +// Import and register all your controllers from the importmap under controllers/* + +import { application } from "controllers/application" + +// Eager load all controllers defined in the import map under controllers/**/*_controller +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) + +// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) +// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" +// lazyLoadControllersFrom("controllers", application) diff --git a/8_0/ch03/app/jobs/application_job.rb b/8_0/ch03/app/jobs/application_job.rb new file mode 100644 index 00000000..d394c3d1 --- /dev/null +++ b/8_0/ch03/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/8_0/ch03/app/mailers/application_mailer.rb b/8_0/ch03/app/mailers/application_mailer.rb new file mode 100644 index 00000000..3c34c814 --- /dev/null +++ b/8_0/ch03/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "from@example.com" + layout "mailer" +end diff --git a/8_0/ch03/app/models/application_record.rb b/8_0/ch03/app/models/application_record.rb new file mode 100644 index 00000000..b63caeb8 --- /dev/null +++ b/8_0/ch03/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/8_0/ch03/app/models/concerns/.keep b/8_0/ch03/app/models/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/app/views/hello/index.html.erb b/8_0/ch03/app/views/hello/index.html.erb new file mode 100644 index 00000000..71661105 --- /dev/null +++ b/8_0/ch03/app/views/hello/index.html.erb @@ -0,0 +1,16 @@ +
+
+ +

+ Codespaces + ♥️ + Railsチュートリアル +

+

+ 🎓✨
+ ロゴ画像が表示されたらセットアップ完了です! +

+
+
diff --git a/8_0/ch03/app/views/layouts/application.html.erb b/8_0/ch03/app/views/layouts/application.html.erb new file mode 100644 index 00000000..4325e963 --- /dev/null +++ b/8_0/ch03/app/views/layouts/application.html.erb @@ -0,0 +1,17 @@ + + + + <%= yield(:title) %> | Ruby on Rails Tutorial Sample App + + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + + + + <%= yield %> + + diff --git a/8_0/ch03/app/views/layouts/mailer.html.erb b/8_0/ch03/app/views/layouts/mailer.html.erb new file mode 100644 index 00000000..cbd34d2e --- /dev/null +++ b/8_0/ch03/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/8_0/ch03/app/views/layouts/mailer.text.erb b/8_0/ch03/app/views/layouts/mailer.text.erb new file mode 100644 index 00000000..37f0bddb --- /dev/null +++ b/8_0/ch03/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/8_0/ch03/app/views/static_pages/about.html.erb b/8_0/ch03/app/views/static_pages/about.html.erb new file mode 100644 index 00000000..dce8fef2 --- /dev/null +++ b/8_0/ch03/app/views/static_pages/about.html.erb @@ -0,0 +1,10 @@ +<% provide(:title, "About") %> +

About

+

+ Ruby on Rails Tutorial + is a book and + screencast + to teach web development with + Ruby on Rails. + This is the sample application for the tutorial. +

diff --git a/8_0/ch03/app/views/static_pages/help.html.erb b/8_0/ch03/app/views/static_pages/help.html.erb new file mode 100644 index 00000000..4d06919d --- /dev/null +++ b/8_0/ch03/app/views/static_pages/help.html.erb @@ -0,0 +1,9 @@ +<% provide(:title, "Help") %> +

Help

+

+ Get help on the Ruby on Rails Tutorial at the + Rails Tutorial Help page. + To get help on this sample app, see the + Ruby on Rails Tutorial + book. +

diff --git a/8_0/ch03/app/views/static_pages/home.html.erb b/8_0/ch03/app/views/static_pages/home.html.erb new file mode 100644 index 00000000..4a131c90 --- /dev/null +++ b/8_0/ch03/app/views/static_pages/home.html.erb @@ -0,0 +1,7 @@ +<% provide(:title, "Home") %> +

Sample App

+

+ This is the home page for the + Ruby on Rails Tutorial + sample application. +

diff --git a/8_0/ch03/bin/bundle b/8_0/ch03/bin/bundle new file mode 100755 index 00000000..981e650b --- /dev/null +++ b/8_0/ch03/bin/bundle @@ -0,0 +1,114 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + requirement = bundler_gem_version.approximate_recommendation + + return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0") + + requirement += ".a" if bundler_gem_version.prerelease? + + requirement + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/8_0/ch03/bin/dev b/8_0/ch03/bin/dev new file mode 100755 index 00000000..5f91c205 --- /dev/null +++ b/8_0/ch03/bin/dev @@ -0,0 +1,2 @@ +#!/usr/bin/env ruby +exec "./bin/rails", "server", *ARGV diff --git a/8_0/ch03/bin/importmap b/8_0/ch03/bin/importmap new file mode 100755 index 00000000..36502ab1 --- /dev/null +++ b/8_0/ch03/bin/importmap @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require_relative "../config/application" +require "importmap/commands" diff --git a/8_0/ch03/bin/rails b/8_0/ch03/bin/rails new file mode 100755 index 00000000..efc03774 --- /dev/null +++ b/8_0/ch03/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/8_0/ch03/bin/rake b/8_0/ch03/bin/rake new file mode 100755 index 00000000..4fbf10b9 --- /dev/null +++ b/8_0/ch03/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/8_0/ch03/bin/rubocop b/8_0/ch03/bin/rubocop new file mode 100755 index 00000000..40330c0f --- /dev/null +++ b/8_0/ch03/bin/rubocop @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +# explicit rubocop config increases performance slightly while avoiding config confusion. +ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) + +load Gem.bin_path("rubocop", "rubocop") diff --git a/8_0/ch03/bin/setup b/8_0/ch03/bin/setup new file mode 100755 index 00000000..be3db3c0 --- /dev/null +++ b/8_0/ch03/bin/setup @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +require "fileutils" + +APP_ROOT = File.expand_path("..", __dir__) + +def system!(*args) + system(*args, exception: true) +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts "== Installing dependencies ==" + system("bundle check") || system!("bundle install") + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + puts "\n== Removing old logs and tempfiles ==" + system! "bin/rails log:clear tmp:clear" + + unless ARGV.include?("--skip-server") + puts "\n== Starting development server ==" + STDOUT.flush # flush the output before exec(2) so that it displays + exec "bin/dev" + end +end diff --git a/8_0/ch03/config.ru b/8_0/ch03/config.ru new file mode 100644 index 00000000..4a3c09a6 --- /dev/null +++ b/8_0/ch03/config.ru @@ -0,0 +1,6 @@ +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/8_0/ch03/config/application.rb b/8_0/ch03/config/application.rb new file mode 100644 index 00000000..0461e72f --- /dev/null +++ b/8_0/ch03/config/application.rb @@ -0,0 +1,27 @@ +require_relative "boot" + +require "rails/all" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module SampleApp + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 8.0 + + # Please, add to the `ignore` list any other `lib` subdirectories that do + # not contain `.rb` files, or that should not be reloaded or eager loaded. + # Common ones are `templates`, `generators`, or `middleware`, for example. + config.autoload_lib(ignore: %w[assets tasks]) + + # Configuration for the application, engines, and railties goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + # + # config.time_zone = "Central Time (US & Canada)" + # config.eager_load_paths << Rails.root.join("extras") + end +end diff --git a/8_0/ch03/config/boot.rb b/8_0/ch03/config/boot.rb new file mode 100644 index 00000000..988a5ddc --- /dev/null +++ b/8_0/ch03/config/boot.rb @@ -0,0 +1,4 @@ +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "bundler/setup" # Set up gems listed in the Gemfile. +require "bootsnap/setup" # Speed up boot time by caching expensive operations. diff --git a/8_0/ch03/config/cable.yml b/8_0/ch03/config/cable.yml new file mode 100644 index 00000000..ae7a2256 --- /dev/null +++ b/8_0/ch03/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: codespaces_try_rails_production diff --git a/8_0/ch03/config/credentials.yml.enc b/8_0/ch03/config/credentials.yml.enc new file mode 100644 index 00000000..339d64d2 --- /dev/null +++ b/8_0/ch03/config/credentials.yml.enc @@ -0,0 +1 @@ +NCs9M8uakg1CDYJENuGaipzymY8uG6i5gzghsy4npa3gYmGfYKPHMnEcJHCVHlM3gxDNJBteUqkRCxHQ5WQId8hzgxoQCL5RX7rtQ8XUzkJwgSUjKvbCrG5atjKkN9XuL91is5vtEN5W7vEz3qxN7Rp33QbNFiDfLs71F3zHVDYEMi6Dy1QMhVCa1v/tyRjBu/5m37RuiYF5FzWJnh4LhexSVr7Agm4LRLLN6qLCpoAe6D3TPHHBdtu7Pvy8jlrEfnnkUJ61wrj8+kOL4uLtpFShdYKhDkoTjQk7TCOgWyifnHAXazkBZoJZ1QYv/tZ9/ZoHvMEQ2dtGHlyTX8fbKtI13d2CFjGaDNKvRuurxE8dbeyiTfbycvve46+cz9OTlmhh0SGt24R1WV6GBKoDUeoHrIiBvW5qmKka--dQ0rodxmfgFLg2y/--t2x0gQjh8FYlk8v79vLSNg== \ No newline at end of file diff --git a/8_0/ch03/config/database.yml b/8_0/ch03/config/database.yml new file mode 100644 index 00000000..fcba57f1 --- /dev/null +++ b/8_0/ch03/config/database.yml @@ -0,0 +1,25 @@ +# SQLite. Versions 3.8.0 and up are supported. +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem "sqlite3" +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/8_0/ch03/config/environment.rb b/8_0/ch03/config/environment.rb new file mode 100644 index 00000000..cac53157 --- /dev/null +++ b/8_0/ch03/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/8_0/ch03/config/environments/development.rb b/8_0/ch03/config/environments/development.rb new file mode 100644 index 00000000..910cef99 --- /dev/null +++ b/8_0/ch03/config/environments/development.rb @@ -0,0 +1,86 @@ +require 'active_support/core_ext/integer/time' + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Make code changes take effect immediately without server restart. + config.enable_reloading = true + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing. + config.server_timing = true + + # Add the following line to disable forgery_protection_origin_check + config.action_controller.forgery_protection_origin_check = false + + # Enable/disable Action Controller caching. By default Action Controller caching is disabled. + # Run rails dev:cache to toggle Action Controller caching. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + config.public_file_server.headers = { 'cache-control' => "public, max-age=#{2.days.to_i}" } + else + config.action_controller.perform_caching = false + end + + # Change to :null_store to avoid any caching. + config.cache_store = :memory_store + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Make template changes take effect immediately. + config.action_mailer.perform_caching = false + + # Set localhost to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Append comments with runtime information tags to SQL queries in logs. + config.active_record.query_log_tags_enabled = true + + # Highlight code that enqueued background job in logs. + config.active_job.verbose_enqueue_logs = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true + + # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. + # config.generators.apply_rubocop_autocorrect_after_generate! + + pf_domain = ENV['GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN'] + config.action_dispatch.default_headers = { + 'X-Frame-Options' => "ALLOW-FROM #{pf_domain}" + } + + # Allow requests from our preview domain. + pf_host = "#{ENV['CODESPACE_NAME']}-3000.#{pf_domain}" + config.hosts << pf_host + + config.action_cable.allowed_request_origins = ["https://#{pf_host}"] +end diff --git a/8_0/ch03/config/environments/production.rb b/8_0/ch03/config/environments/production.rb new file mode 100644 index 00000000..17496077 --- /dev/null +++ b/8_0/ch03/config/environments/production.rb @@ -0,0 +1,89 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.enable_reloading = false + + # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). + config.eager_load = true + + # Full error reports are disabled. + config.consider_all_requests_local = false + + # Turn on fragment caching in view templates. + config.action_controller.perform_caching = true + + # Cache assets for far-future expiry since they are all digest stamped. + config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" } + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = "http://assets.example.com" + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Assume all access to the app is happening through a SSL-terminating reverse proxy. + config.assume_ssl = true + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = true + + # Skip http-to-https redirect for the default health check endpoint. + # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } + + # Log to STDOUT with the current request id as a default log tag. + config.log_tags = [ :request_id ] + config.logger = ActiveSupport::TaggedLogging.logger(STDOUT) + + # Change to "debug" to log everything (including potentially personally-identifiable information!) + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + + # Prevent health checks from clogging up the logs. + config.silence_healthcheck_path = "/up" + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Replace the default in-process memory cache store with a durable alternative. + # config.cache_store = :mem_cache_store + + # Replace the default in-process and non-durable queuing backend for Active Job. + # config.active_job.queue_adapter = :resque + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. + # config.action_mailer.smtp_settings = { + # user_name: Rails.application.credentials.dig(:smtp, :user_name), + # password: Rails.application.credentials.dig(:smtp, :password), + # address: "smtp.example.com", + # port: 587, + # authentication: :plain + # } + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Only use :id for inspections in production. + config.active_record.attributes_for_inspect = [ :id ] + + # Enable DNS rebinding protection and other `Host` header attacks. + # config.hosts = [ + # "example.com", # Allow requests from example.com + # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` + # ] + # + # Skip DNS rebinding protection for the default health check endpoint. + # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } +end diff --git a/8_0/ch03/config/environments/test.rb b/8_0/ch03/config/environments/test.rb new file mode 100644 index 00000000..0ebe3b4e --- /dev/null +++ b/8_0/ch03/config/environments/test.rb @@ -0,0 +1,53 @@ +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # While tests run files are not watched, reloading is not necessary. + config.enable_reloading = false + + # Eager loading loads your entire application. When running a single test locally, + # this is usually not necessary, and can slow down your test suite. However, it's + # recommended that you enable it in continuous integration systems to ensure eager + # loading is working properly before deploying your code. + config.eager_load = ENV["CI"].present? + + # Configure public file server for tests with cache-control for performance. + config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } + + # Show full error reports. + config.consider_all_requests_local = true + config.cache_store = :null_store + + # Render exception templates for rescuable exceptions and raise for other exceptions. + config.action_dispatch.show_exceptions = :none + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :test + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true +end diff --git a/8_0/ch03/config/importmap.rb b/8_0/ch03/config/importmap.rb new file mode 100644 index 00000000..8dce42d4 --- /dev/null +++ b/8_0/ch03/config/importmap.rb @@ -0,0 +1,7 @@ +# Pin npm packages by running ./bin/importmap + +pin "application", preload: true +pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true +pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true +pin_all_from "app/javascript/controllers", under: "controllers" diff --git a/8_0/ch03/config/initializers/assets.rb b/8_0/ch03/config/initializers/assets.rb new file mode 100644 index 00000000..48732442 --- /dev/null +++ b/8_0/ch03/config/initializers/assets.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = "1.0" + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path diff --git a/8_0/ch03/config/initializers/content_security_policy.rb b/8_0/ch03/config/initializers/content_security_policy.rb new file mode 100644 index 00000000..b3076b38 --- /dev/null +++ b/8_0/ch03/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header + +# Rails.application.configure do +# config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end +# +# # Generate session nonces for permitted importmap, inline scripts, and inline styles. +# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } +# config.content_security_policy_nonce_directives = %w(script-src style-src) +# +# # Report violations without enforcing the policy. +# # config.content_security_policy_report_only = true +# end diff --git a/8_0/ch03/config/initializers/filter_parameter_logging.rb b/8_0/ch03/config/initializers/filter_parameter_logging.rb new file mode 100644 index 00000000..c0b717f7 --- /dev/null +++ b/8_0/ch03/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. +# Use this to limit dissemination of sensitive information. +# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. +Rails.application.config.filter_parameters += [ + :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc +] diff --git a/8_0/ch03/config/initializers/inflections.rb b/8_0/ch03/config/initializers/inflections.rb new file mode 100644 index 00000000..3860f659 --- /dev/null +++ b/8_0/ch03/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym "RESTful" +# end diff --git a/8_0/ch03/config/initializers/new_framework_defaults_8_0.rb b/8_0/ch03/config/initializers/new_framework_defaults_8_0.rb new file mode 100644 index 00000000..92efa951 --- /dev/null +++ b/8_0/ch03/config/initializers/new_framework_defaults_8_0.rb @@ -0,0 +1,30 @@ +# Be sure to restart your server when you modify this file. +# +# This file eases your Rails 8.0 framework defaults upgrade. +# +# Uncomment each configuration one by one to switch to the new default. +# Once your application is ready to run with all new defaults, you can remove +# this file and set the `config.load_defaults` to `8.0`. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. +# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html + +### +# Specifies whether `to_time` methods preserve the UTC offset of their receivers or preserves the timezone. +# If set to `:zone`, `to_time` methods will use the timezone of their receivers. +# If set to `:offset`, `to_time` methods will use the UTC offset. +# If `false`, `to_time` methods will convert to the local system UTC offset instead. +#++ +# Rails.application.config.active_support.to_time_preserves_timezone = :zone + +### +# When both `If-Modified-Since` and `If-None-Match` are provided by the client +# only consider `If-None-Match` as specified by RFC 7232 Section 6. +# If set to `false` both conditions need to be satisfied. +#++ +# Rails.application.config.action_dispatch.strict_freshness = true + +### +# Set `Regexp.timeout` to `1`s by default to improve security over Regexp Denial-of-Service attacks. +#++ +# Regexp.timeout = 1 diff --git a/8_0/ch03/config/initializers/permissions_policy.rb b/8_0/ch03/config/initializers/permissions_policy.rb new file mode 100644 index 00000000..00f64d71 --- /dev/null +++ b/8_0/ch03/config/initializers/permissions_policy.rb @@ -0,0 +1,11 @@ +# Define an application-wide HTTP permissions policy. For further +# information see https://developers.google.com/web/updates/2018/06/feature-policy +# +# Rails.application.config.permissions_policy do |f| +# f.camera :none +# f.gyroscope :none +# f.microphone :none +# f.usb :none +# f.fullscreen :self +# f.payment :self, "https://secure.example.com" +# end diff --git a/8_0/ch03/config/locales/en.yml b/8_0/ch03/config/locales/en.yml new file mode 100644 index 00000000..8ca56fc7 --- /dev/null +++ b/8_0/ch03/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# "true": "foo" +# +# To learn more, please read the Rails Internationalization guide +# available at https://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/8_0/ch03/config/puma.rb b/8_0/ch03/config/puma.rb new file mode 100644 index 00000000..a248513b --- /dev/null +++ b/8_0/ch03/config/puma.rb @@ -0,0 +1,41 @@ +# This configuration file will be evaluated by Puma. The top-level methods that +# are invoked here are part of Puma's configuration DSL. For more information +# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. +# +# Puma starts a configurable number of processes (workers) and each process +# serves each request in a thread from an internal thread pool. +# +# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You +# should only set this value when you want to run 2 or more workers. The +# default is already 1. +# +# The ideal number of threads per worker depends both on how much time the +# application spends waiting for IO operations and on how much you wish to +# prioritize throughput over latency. +# +# As a rule of thumb, increasing the number of threads will increase how much +# traffic a given process can handle (throughput), but due to CRuby's +# Global VM Lock (GVL) it has diminishing returns and will degrade the +# response time (latency) of the application. +# +# The default is set to 3 threads as it's deemed a decent compromise between +# throughput and latency for the average Rails application. +# +# Any libraries that use a connection pool or another resource pool should +# be configured to provide at least as many connections as the number of +# threads. This includes Active Record's `pool` parameter in `database.yml`. +threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +port ENV.fetch("PORT", 3000) + +# Allow puma to be restarted by `bin/rails restart` command. +plugin :tmp_restart + +# Run the Solid Queue supervisor inside of Puma for single-server deployments +plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] + +# Specify the PID file. Defaults to tmp/pids/server.pid in development. +# In other environments, only set the PID file if requested. +pidfile ENV["PIDFILE"] if ENV["PIDFILE"] diff --git a/8_0/ch03/config/routes.rb b/8_0/ch03/config/routes.rb new file mode 100644 index 00000000..73181abd --- /dev/null +++ b/8_0/ch03/config/routes.rb @@ -0,0 +1,6 @@ +Rails.application.routes.draw do + root "static_pages#home" + get "static_pages/home" + get "static_pages/help" + get "static_pages/about" +end diff --git a/8_0/ch03/config/storage.yml b/8_0/ch03/config/storage.yml new file mode 100644 index 00000000..4942ab66 --- /dev/null +++ b/8_0/ch03/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket-<%= Rails.env %> + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket-<%= Rails.env %> + +# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name-<%= Rails.env %> + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/8_0/ch03/db/seeds.rb b/8_0/ch03/db/seeds.rb new file mode 100644 index 00000000..bc25fce3 --- /dev/null +++ b/8_0/ch03/db/seeds.rb @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). +# +# Examples: +# +# movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }]) +# Character.create(name: "Luke", movie: movies.first) diff --git a/8_0/ch03/lib/assets/.keep b/8_0/ch03/lib/assets/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/lib/tasks/.keep b/8_0/ch03/lib/tasks/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/log/.keep b/8_0/ch03/log/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/public/400.html b/8_0/ch03/public/400.html new file mode 100644 index 00000000..282dbc8c --- /dev/null +++ b/8_0/ch03/public/400.html @@ -0,0 +1,114 @@ + + + + + + + The server cannot process the request due to a client error (400 Bad Request) + + + + + + + + + + + + + +
+
+ +
+
+

The server cannot process the request due to a client error. Please check the request and try again. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch03/public/404.html b/8_0/ch03/public/404.html new file mode 100644 index 00000000..c0670bc8 --- /dev/null +++ b/8_0/ch03/public/404.html @@ -0,0 +1,114 @@ + + + + + + + The page you were looking for doesn’t exist (404 Not found) + + + + + + + + + + + + + +
+
+ +
+
+

The page you were looking for doesn’t exist. You may have mistyped the address or the page may have moved. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch03/public/406-unsupported-browser.html b/8_0/ch03/public/406-unsupported-browser.html new file mode 100644 index 00000000..9532a9cc --- /dev/null +++ b/8_0/ch03/public/406-unsupported-browser.html @@ -0,0 +1,114 @@ + + + + + + + Your browser is not supported (406 Not Acceptable) + + + + + + + + + + + + + +
+
+ +
+
+

Your browser is not supported.
Please upgrade your browser to continue.

+
+
+ + + + diff --git a/8_0/ch03/public/422.html b/8_0/ch03/public/422.html new file mode 100644 index 00000000..8bcf0601 --- /dev/null +++ b/8_0/ch03/public/422.html @@ -0,0 +1,114 @@ + + + + + + + The change you wanted was rejected (422 Unprocessable Entity) + + + + + + + + + + + + + +
+
+ +
+
+

The change you wanted was rejected. Maybe you tried to change something you didn’t have access to. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch03/public/500.html b/8_0/ch03/public/500.html new file mode 100644 index 00000000..d77718c3 --- /dev/null +++ b/8_0/ch03/public/500.html @@ -0,0 +1,114 @@ + + + + + + + We’re sorry, but something went wrong (500 Internal Server Error) + + + + + + + + + + + + + +
+
+ +
+
+

We’re sorry, but something went wrong.
If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch03/public/apple-touch-icon-precomposed.png b/8_0/ch03/public/apple-touch-icon-precomposed.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/public/apple-touch-icon.png b/8_0/ch03/public/apple-touch-icon.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/public/favicon.ico b/8_0/ch03/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/public/icon.png b/8_0/ch03/public/icon.png new file mode 100644 index 00000000..c4c9dbfb Binary files /dev/null and b/8_0/ch03/public/icon.png differ diff --git a/8_0/ch03/public/icon.svg b/8_0/ch03/public/icon.svg new file mode 100644 index 00000000..04b34bf8 --- /dev/null +++ b/8_0/ch03/public/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/8_0/ch03/public/railstutorial.png b/8_0/ch03/public/railstutorial.png new file mode 100644 index 00000000..70835e6b Binary files /dev/null and b/8_0/ch03/public/railstutorial.png differ diff --git a/8_0/ch03/public/robots.txt b/8_0/ch03/public/robots.txt new file mode 100644 index 00000000..c19f78ab --- /dev/null +++ b/8_0/ch03/public/robots.txt @@ -0,0 +1 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/8_0/ch03/storage/.keep b/8_0/ch03/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/test/application_system_test_case.rb b/8_0/ch03/test/application_system_test_case.rb new file mode 100644 index 00000000..d19212ab --- /dev/null +++ b/8_0/ch03/test/application_system_test_case.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/8_0/ch03/test/channels/application_cable/connection_test.rb b/8_0/ch03/test/channels/application_cable/connection_test.rb new file mode 100644 index 00000000..800405f1 --- /dev/null +++ b/8_0/ch03/test/channels/application_cable/connection_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase + # test "connects with cookies" do + # cookies.signed[:user_id] = 42 + # + # connect + # + # assert_equal connection.user_id, "42" + # end +end diff --git a/8_0/ch03/test/controllers/.keep b/8_0/ch03/test/controllers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/test/controllers/hello_codespaces_controller_test.rb b/8_0/ch03/test/controllers/hello_codespaces_controller_test.rb new file mode 100644 index 00000000..54353ea6 --- /dev/null +++ b/8_0/ch03/test/controllers/hello_codespaces_controller_test.rb @@ -0,0 +1,8 @@ +require "test_helper" + +class HelloCodespacesControllerTest < ActionDispatch::IntegrationTest + # test "should get index" do + # get "/" + # assert_response :success + # end +end diff --git a/8_0/ch03/test/controllers/static_pages_controller_test.rb b/8_0/ch03/test/controllers/static_pages_controller_test.rb new file mode 100644 index 00000000..f307e7cb --- /dev/null +++ b/8_0/ch03/test/controllers/static_pages_controller_test.rb @@ -0,0 +1,22 @@ +require "test_helper" + +class StaticPagesControllerTest < ActionDispatch::IntegrationTest + + test "should get home" do + get static_pages_home_url + assert_response :success + assert_select "title", "Home | Ruby on Rails Tutorial Sample App" + end + + test "should get help" do + get static_pages_help_url + assert_response :success + assert_select "title", "Help | Ruby on Rails Tutorial Sample App" + end + + test "should get about" do + get static_pages_about_url + assert_response :success + assert_select "title", "About | Ruby on Rails Tutorial Sample App" + end +end diff --git a/8_0/ch03/test/fixtures/files/.keep b/8_0/ch03/test/fixtures/files/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/test/helpers/.keep b/8_0/ch03/test/helpers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/test/integration/.keep b/8_0/ch03/test/integration/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/test/mailers/.keep b/8_0/ch03/test/mailers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/test/models/.keep b/8_0/ch03/test/models/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/test/system/.keep b/8_0/ch03/test/system/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/test/test_helper.rb b/8_0/ch03/test/test_helper.rb new file mode 100644 index 00000000..f566ca61 --- /dev/null +++ b/8_0/ch03/test/test_helper.rb @@ -0,0 +1,15 @@ +ENV["RAILS_ENV"] ||= "test" +require_relative "../config/environment" +require "rails/test_help" +require "minitest/reporters" +Minitest::Reporters.use! + +class ActiveSupport::TestCase + # 指定のワーカー数でテストを並列実行する + parallelize(workers: :number_of_processors) + + # test/fixtures/*.ymlにあるすべてのfixtureをセットアップする + fixtures :all + + # (すべてのテストで使うその他のヘルパーメソッドは省略) +end diff --git a/8_0/ch03/tmp/.keep b/8_0/ch03/tmp/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/tmp/pids/.keep b/8_0/ch03/tmp/pids/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/tmp/storage/.keep b/8_0/ch03/tmp/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/vendor/.keep b/8_0/ch03/vendor/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch03/vendor/javascript/.keep b/8_0/ch03/vendor/javascript/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/.devcontainer/devcontainer.json b/8_0/ch04/.devcontainer/devcontainer.json new file mode 100644 index 00000000..e7243217 --- /dev/null +++ b/8_0/ch04/.devcontainer/devcontainer.json @@ -0,0 +1,105 @@ +// Specifications: https://containers.dev/implementors/json_reference/ +// Format details: https://aka.ms/devcontainer.json +// Config options: https://github.com/microsoft/vscode-dev-containers/tree/main/containers/ruby +{ + "name": "railstutorial", + + // Universal is well-customized image for Codespaces: + // https://hub.docker.com/_/microsoft-devcontainers-universal + //"image": "mcr.microsoft.com/devcontainers/universal:2", + //"features": { + // "ghcr.io/devcontainers/features/ruby:1": {} + //}, + + // Use Ruby image if you want to pin Ruby version like '3.2' + // https://github.com/devcontainers/images/tree/main/src/ruby + "image": "mcr.microsoft.com/devcontainers/ruby:3.2", + //"workspaceFolder": "/railstutorial", => Container build failed + + // Enable learners to choose an affordable spec, starting at minimum one. + //"hostRequirements": { + // "cpus": 2, + // "memory": "4gb", + // "storage": "32gb" + //}, + + "waitFor": "onCreateCommand", + //"onCreateCommand": "", + "onCreateCommand": "gem install solargraph -v '0.58.2' -N", + //"onCreateCommand": "gem install solargraph -v '0.50.0' -N && gem install ruby-lsp -N", + //"onCreateCommand": "gem install ruby-lsp -N", + //# => Solargraph gem not found. Run `gem install solargraph` or update your Gemfile. + "updateContentCommand": "bundle install", + "postCreateCommand": "", + "postAttachCommand": { + "server": "rails server" + }, + "customizations": { + "codespaces": { + "openFiles": [ + "app/views/hello/index.html.erb" + ] + }, + "vscode": { + "extensions": [ + "GitHub.codespaces", + "Shopify.ruby-lsp", // https://github.com/Shopify/ruby-lsp + "castwide.solargraph" // https://github.com/castwide/vscode-solargraph + //"rebornix.Ruby", // https://github.com/rubyide/vscode-ruby (Deprecated) + + ], + "settings": { + // General settings for Codespaces (VS Code) + //"ruby.useLanguageServer": true , + //"ruby.format": "rubocop", + //"ruby.lint": { "rubocop": true }, + //"ruby.intellisense": "rubyLocate", + "editor.tabSize": 2, + "editor.formatOnSave": false, // Disable onSave to show diff edited by learners only + "editor.formatOnType": false, // Disable onType for the same reason above + "editor.insertSpaces": true, // Use spaces, not tabs, to avoid errors for learners + "editor.renderWhitespace": "none", + "[ruby]": { + "editor.defaultFormatter": "castwide.solargraph", + "editor.semanticHighlighting.enabled": true // Enable semantic highlighting + }, + "files.associations": { "*.erb": "erb" }, + "emmet.includeLanguages": { "erb": "html" }, + + // Settings for Solargraph + // https://github.com/castwide/solargraph + "solargraph.useBundler": false, + "solargraph.diagnostics": false, + "solargraph.formatting": true, // Use Ctrl+Shift+P->Format to format + "solargraph.autoformat": false, + "solargraph.definitions": true, + "solargraph.completion": true, + "solargraph.references": true, + "solargraph.symbols": true, + "solargraph.rename": true, + "solargraph.hover": true, + + // Settings for Ruby LSP + // https://github.com/Shopify/ruby-lsp + "rubyLsp.rubyVersionManager": { + "identifier": "none" + }, + "rubyLsp.formatter": "none", + "rubyLsp.enabledFeatures": { + "diagnostics": false, + "formatting": false + } + } + } + }, + "remoteEnv": { + "EDITOR": "code --wait" + }, + "portsAttributes": { + "3000": { + "label": "Application", + "onAutoForward": "openPreview" + } + }, + "forwardPorts": [3000] +} diff --git a/8_0/ch04/.devcontainer/icon.svg b/8_0/ch04/.devcontainer/icon.svg new file mode 100644 index 00000000..f8444a4a --- /dev/null +++ b/8_0/ch04/.devcontainer/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/8_0/ch04/.gitattributes b/8_0/ch04/.gitattributes new file mode 100644 index 00000000..31eeee0b --- /dev/null +++ b/8_0/ch04/.gitattributes @@ -0,0 +1,7 @@ +# See https://git-scm.com/docs/gitattributes for more about git attribute files. + +# Mark the database schema as having been generated. +db/schema.rb linguist-generated + +# Mark any vendored files as having been vendored. +vendor/* linguist-vendored diff --git a/8_0/ch04/.gitignore b/8_0/ch04/.gitignore new file mode 100644 index 00000000..e1ef53ef --- /dev/null +++ b/8_0/ch04/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-* + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore uploaded files in development. +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/ +!/tmp/storage/.keep + +/public/assets + +# Ignore master key for decrypting credentials and more. +/config/master.key + +# Ignore history file used by IRB for interactive Ruby. +.irb_history + +dump.rdb diff --git a/8_0/ch04/.irbrc b/8_0/ch04/.irbrc new file mode 100644 index 00000000..3737e036 --- /dev/null +++ b/8_0/ch04/.irbrc @@ -0,0 +1 @@ +IRB.conf[:COMPLETOR] = :type # default is :regexp diff --git a/8_0/ch04/.solargraph.yml b/8_0/ch04/.solargraph.yml new file mode 100644 index 00000000..76ac24a6 --- /dev/null +++ b/8_0/ch04/.solargraph.yml @@ -0,0 +1,32 @@ +--- +include: +- "**/*.rb" +exclude: +- spec/**/* +- test/**/* +- vendor/**/* +- ".bundle/**/*" +require: +- actioncable +- actionmailer +- actionpack +- actionview +- activejob +- activemodel +- activerecord +- activestorage +- activesupport +domains: [] +reporters: +- rubocop +- require_not_found +- typecheck +formatter: + rubocop: + cops: safe + except: [] + only: [] + extra_args: [] +require_paths: [] +plugins: [] +max_files: 5000 diff --git a/8_0/ch04/.vscode/settings.json b/8_0/ch04/.vscode/settings.json new file mode 100644 index 00000000..cf85f519 --- /dev/null +++ b/8_0/ch04/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.minimap.enabled": false +} diff --git a/8_0/ch04/Gemfile b/8_0/ch04/Gemfile new file mode 100644 index 00000000..46cefd03 --- /dev/null +++ b/8_0/ch04/Gemfile @@ -0,0 +1,42 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '3.2.10' + +gem 'bootsnap', '1.16.0', require: false +gem 'concurrent-ruby', '1.3.4' +gem 'importmap-rails', '1.1.5' +gem 'jbuilder', '2.14.1' +gem 'puma', '6.6.1' +gem 'rails', '8.0.2.1' +gem 'sassc-rails', '2.1.2' +gem 'sprockets-rails', '3.4.2' +gem 'sqlite3', '2.7.3' +gem 'stimulus-rails', '1.2.1' +gem 'turbo-rails', '1.4.0' + +group :development, :test do + gem 'debug', '1.7.1', platforms: %i[mri mingw x64_mingw] + gem 'reline', '0.5.10' +end + +group :development do + gem 'irb', '1.15.2' + gem 'repl_type_completor', '0.1.10' + gem 'solargraph', '0.58.2' + gem 'web-console', '4.2.0' +end + +group :test do + gem 'capybara', '3.38.0' + gem 'guard', '2.18.0' + gem 'guard-minitest', '2.4.6' + gem 'minitest', '5.18.0' + gem 'minitest-reporters', '1.6.0' + gem 'rails-controller-testing', '1.0.5' + gem 'selenium-webdriver', '4.8.3' + gem 'webdrivers', '5.2.0' +end + +# Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります +# gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] diff --git a/8_0/ch04/Gemfile.lock b/8_0/ch04/Gemfile.lock new file mode 100644 index 00000000..a3ee4202 --- /dev/null +++ b/8_0/ch04/Gemfile.lock @@ -0,0 +1,433 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + actionmailer (8.0.2.1) + actionpack (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.0.2.1) + actionview (= 8.0.2.1) + activesupport (= 8.0.2.1) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.0.2.1) + actionpack (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (8.0.2.1) + activesupport (= 8.0.2.1) + builder (~> 3.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.3.6) + activemodel (8.0.2.1) + activesupport (= 8.0.2.1) + activerecord (8.0.2.1) + activemodel (= 8.0.2.1) + activesupport (= 8.0.2.1) + timeout (>= 0.4.0) + activestorage (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activesupport (= 8.0.2.1) + marcel (~> 1.0) + activesupport (8.0.2.1) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + ansi (1.5.0) + ast (2.4.3) + backport (1.2.0) + base64 (0.3.0) + benchmark (0.4.1) + bigdecimal (3.2.3) + bindex (0.8.1) + bootsnap (1.16.0) + msgpack (~> 1.2) + builder (3.3.0) + capybara (3.38.0) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + coderay (1.1.3) + concurrent-ruby (1.3.4) + connection_pool (2.5.4) + crass (1.0.6) + date (3.4.1) + debug (1.7.1) + irb (>= 1.5.0) + reline (>= 0.3.1) + diff-lcs (1.6.2) + drb (2.2.3) + erb (5.0.2) + erubi (1.13.1) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) + formatador (1.2.0) + reline + globalid (1.2.1) + activesupport (>= 6.1) + guard (2.18.0) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.13.0) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-minitest (2.4.6) + guard-compat (~> 1.2) + minitest (>= 3.0) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + importmap-rails (1.1.5) + actionpack (>= 6.0.0) + railties (>= 6.0.0) + io-console (0.8.1) + irb (1.15.2) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jaro_winkler (1.6.1) + jbuilder (2.14.1) + actionview (>= 7.0.0) + activesupport (>= 7.0.0) + json (2.13.2) + kramdown (2.5.1) + rexml (>= 3.3.9) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) + loofah (2.24.1) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + lumberjack (1.4.1) + mail (2.8.1) + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.0.4) + matrix (0.4.3) + method_source (1.1.0) + mini_mime (1.1.5) + minitest (5.18.0) + minitest-reporters (1.6.0) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + msgpack (1.8.0) + nenv (0.3.0) + net-imap (0.5.10) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.4) + nokogiri (1.19.1-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-musl) + racc (~> 1.4) + notiffany (0.1.3) + nenv (~> 0.1) + shellany (~> 0.0) + observer (0.1.2) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.9.0) + ast (~> 2.4.1) + racc + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + prism (1.4.0) + pry (0.15.2) + coderay (~> 1.1) + method_source (~> 1.0) + psych (5.2.6) + date + stringio + public_suffix (6.0.2) + puma (6.6.1) + nio4r (~> 2.0) + racc (1.8.1) + rack (3.2.5) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.2.1) + rack (>= 3) + rails (8.0.2.1) + actioncable (= 8.0.2.1) + actionmailbox (= 8.0.2.1) + actionmailer (= 8.0.2.1) + actionpack (= 8.0.2.1) + actiontext (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activemodel (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + bundler (>= 1.15.0) + railties (= 8.0.2.1) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.0) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rbs (3.6.1) + logger + rdoc (6.14.2) + erb + psych (>= 4.0.0) + regexp_parser (2.11.2) + reline (0.5.10) + io-console (~> 0.5) + repl_type_completor (0.1.10) + prism (~> 1.0) + rbs (>= 2.7.0, < 4.0.0) + reverse_markdown (3.0.0) + nokogiri + rexml (3.4.2) + rubocop (1.80.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.46.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.46.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + ruby-progressbar (1.13.0) + rubyzip (2.4.1) + sassc (2.4.0) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt + securerandom (0.4.1) + selenium-webdriver (4.8.3) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) + shellany (0.0.1) + solargraph (0.58.2) + backport (~> 1.2) + benchmark (~> 0.4) + bundler (~> 2.0) + diff-lcs (~> 1.4) + jaro_winkler (~> 1.6, >= 1.6.1) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.1) + logger (~> 1.6) + observer (~> 0.1) + ostruct (~> 0.6) + parser (~> 3.0) + prism (~> 1.4) + rbs (~> 3.6.1) + reverse_markdown (~> 3.0) + rubocop (~> 1.38) + thor (~> 1.0) + tilt (~> 2.0) + yard (~> 0.9, >= 0.9.24) + yard-solargraph (~> 0.1) + sprockets (4.2.2) + concurrent-ruby (~> 1.0) + logger + rack (>= 2.2.4, < 4) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + sprockets (>= 3.0.0) + sqlite3 (2.7.3-aarch64-linux-gnu) + sqlite3 (2.7.3-aarch64-linux-musl) + sqlite3 (2.7.3-arm-linux-gnu) + sqlite3 (2.7.3-arm-linux-musl) + sqlite3 (2.7.3-arm64-darwin) + sqlite3 (2.7.3-x86_64-darwin) + sqlite3 (2.7.3-x86_64-linux-gnu) + sqlite3 (2.7.3-x86_64-linux-musl) + stimulus-rails (1.2.1) + railties (>= 6.0.0) + stringio (3.1.7) + thor (1.4.0) + tilt (2.6.1) + timeout (0.4.3) + turbo-rails (1.4.0) + actionpack (>= 6.0.0) + activejob (>= 6.0.0) + railties (>= 6.0.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.1.5) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + uri (1.0.4) + useragent (0.16.11) + web-console (4.2.0) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + webdrivers (5.2.0) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (~> 4.0) + websocket (1.2.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) + yard (0.9.37) + yard-solargraph (0.1.0) + yard (~> 0.9) + zeitwerk (2.7.3) + +PLATFORMS + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + bootsnap (= 1.16.0) + capybara (= 3.38.0) + concurrent-ruby (= 1.3.4) + debug (= 1.7.1) + guard (= 2.18.0) + guard-minitest (= 2.4.6) + importmap-rails (= 1.1.5) + irb (= 1.15.2) + jbuilder (= 2.14.1) + minitest (= 5.18.0) + minitest-reporters (= 1.6.0) + puma (= 6.6.1) + rails (= 8.0.2.1) + rails-controller-testing (= 1.0.5) + reline (= 0.5.10) + repl_type_completor (= 0.1.10) + sassc-rails (= 2.1.2) + selenium-webdriver (= 4.8.3) + solargraph (= 0.58.2) + sprockets-rails (= 3.4.2) + sqlite3 (= 2.7.3) + stimulus-rails (= 1.2.1) + turbo-rails (= 1.4.0) + web-console (= 4.2.0) + webdrivers (= 5.2.0) + +RUBY VERSION + ruby 3.2.10p266 + +BUNDLED WITH + 2.5.6 diff --git a/8_0/ch04/Guardfile b/8_0/ch04/Guardfile new file mode 100644 index 00000000..5def34a0 --- /dev/null +++ b/8_0/ch04/Guardfile @@ -0,0 +1,76 @@ + require "active_support/inflector" + # Guardのマッチング規則を定義 + guard :minitest, all_on_start: false do + watch(%r{^test/(.*)/?(.*)_test\.rb$}) + watch('test/test_helper.rb') { 'test' } + watch('config/routes.rb') { interface_tests } + watch(%r{app/views/layouts/*}) { interface_tests } + watch(%r{^app/models/(.*?)\.rb$}) do |matches| + ["test/models/#{matches[1]}_test.rb", + "test/integration/microposts_interface_test.rb"] + end + watch(%r{^test/fixtures/(.*?)\.yml$}) do |matches| + "test/models/#{matches[1].singularize}_test.rb" + end + watch(%r{^app/mailers/(.*?)\.rb$}) do |matches| + "test/mailers/#{matches[1]}_test.rb" + end + watch(%r{^app/views/(.*)_mailer/.*$}) do |matches| + "test/mailers/#{matches[1]}_mailer_test.rb" + end + watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches| + resource_tests(matches[1]) + end + watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches| + ["test/controllers/#{matches[1]}_controller_test.rb"] + + integration_tests(matches[1]) + end + watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches| + integration_tests(matches[1]) + end + watch('app/views/layouts/application.html.erb') do + 'test/integration/site_layout_test.rb' + end + watch('app/helpers/sessions_helper.rb') do + integration_tests << 'test/helpers/sessions_helper_test.rb' + end + watch('app/controllers/sessions_controller.rb') do + ['test/controllers/sessions_controller_test.rb', + 'test/integration/users_login_test.rb'] + end + watch('app/controllers/account_activations_controller.rb') do + 'test/integration/users_signup_test.rb' + end + watch(%r{app/views/users/*}) do + resource_tests('users') + + ['test/integration/microposts_interface_test.rb'] + end + watch('app/controllers/relationships_controller.rb') do + ['test/controllers/relationships_controller_test.rb', + 'test/integration/following_test.rb'] + end + end + + # 指定のリソースに対応する統合テストを返す + def integration_tests(resource = :all) + if resource == :all + Dir["test/integration/*"] + else + Dir["test/integration/#{resource}_*.rb"] + end + end + + # インターフェースが該当するすべてのテストを返す + def interface_tests + integration_tests << "test/controllers" + end + + # 指定のリソースに対応するコントローラのテストを返す + def controller_test(resource) + "test/controllers/#{resource}_controller_test.rb" + end + + # 指定のリソースに対応するすべてのテストを返す + def resource_tests(resource) + integration_tests(resource) << controller_test(resource) + end diff --git a/8_0/ch04/LICENSE b/8_0/ch04/LICENSE new file mode 100644 index 00000000..20622e0c --- /dev/null +++ b/8_0/ch04/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 GitHub & 2023 YassLab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/8_0/ch04/README.md b/8_0/ch04/README.md new file mode 100644 index 00000000..17ca733d --- /dev/null +++ b/8_0/ch04/README.md @@ -0,0 +1,35 @@ +# Ruby on Rails チュートリアルのサンプルアプリケーション + +これは、次の教材で作られたサンプルアプリケーションです。 +[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +(第8版) +[Michael Hartl](https://www.michaelhartl.com/) 著 + +## ライセンス + +[Ruby on Rails チュートリアル](https://railstutorial.jp/)内にある +ソースコードはMITライセンスとBeerwareライセンスのもとで公開されています。 +詳細は [LICENSE.md](LICENSE.md) をご覧ください。 + +## 使い方 + +このアプリケーションを動かす場合は、まずはリポジトリをフォークしてください。 + +フォークしたリポジトリで、「Code」から「Codespaces」タブに移動し、 +「Create codespace on main」をクリックすると環境構築がスタートします。 +Railsサーバーが立ち上がり、シンプルブラウザが表示されるまでしばらくお待ちください。 + +次に、データベースへのマイグレーションを実行します。 + +``` +$ rails db:migrate +``` + +最後に、テストを実行してうまく動いているかどうか確認してください。 + +``` +$ rails test +``` + +詳しくは、[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +を参考にしてください。 diff --git a/8_0/ch04/Rakefile b/8_0/ch04/Rakefile new file mode 100644 index 00000000..9a5ea738 --- /dev/null +++ b/8_0/ch04/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/8_0/ch04/app/assets/config/manifest.js b/8_0/ch04/app/assets/config/manifest.js new file mode 100644 index 00000000..ddd546a0 --- /dev/null +++ b/8_0/ch04/app/assets/config/manifest.js @@ -0,0 +1,4 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js diff --git a/8_0/ch04/app/assets/images/.keep b/8_0/ch04/app/assets/images/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/app/assets/stylesheets/application.css b/8_0/ch04/app/assets/stylesheets/application.css new file mode 100644 index 00000000..288b9ab7 --- /dev/null +++ b/8_0/ch04/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/8_0/ch04/app/assets/stylesheets/hello.css b/8_0/ch04/app/assets/stylesheets/hello.css new file mode 100644 index 00000000..497881a4 --- /dev/null +++ b/8_0/ch04/app/assets/stylesheets/hello.css @@ -0,0 +1,40 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.codespaces { + text-align: center; +} +.codespaces code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} +.codespaces-logo { + height: 40vmin; + pointer-events: none; +} +.codespaces-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} +.codespaces-link { + color: #61dafb; +} + +.heart { + color: #ff0000; +} +.small { + font-size: 0.75rem; +} diff --git a/8_0/ch04/app/channels/application_cable/channel.rb b/8_0/ch04/app/channels/application_cable/channel.rb new file mode 100644 index 00000000..d6726972 --- /dev/null +++ b/8_0/ch04/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/8_0/ch04/app/channels/application_cable/connection.rb b/8_0/ch04/app/channels/application_cable/connection.rb new file mode 100644 index 00000000..0ff5442f --- /dev/null +++ b/8_0/ch04/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/8_0/ch04/app/controllers/application_controller.rb b/8_0/ch04/app/controllers/application_controller.rb new file mode 100644 index 00000000..09705d12 --- /dev/null +++ b/8_0/ch04/app/controllers/application_controller.rb @@ -0,0 +1,2 @@ +class ApplicationController < ActionController::Base +end diff --git a/8_0/ch04/app/controllers/concerns/.keep b/8_0/ch04/app/controllers/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/app/controllers/hello_controller.rb b/8_0/ch04/app/controllers/hello_controller.rb new file mode 100644 index 00000000..52037537 --- /dev/null +++ b/8_0/ch04/app/controllers/hello_controller.rb @@ -0,0 +1,4 @@ +class HelloController < ApplicationController + def index + end +end diff --git a/8_0/ch04/app/controllers/static_pages_controller.rb b/8_0/ch04/app/controllers/static_pages_controller.rb new file mode 100644 index 00000000..49ccd414 --- /dev/null +++ b/8_0/ch04/app/controllers/static_pages_controller.rb @@ -0,0 +1,11 @@ +class StaticPagesController < ApplicationController + + def home + end + + def help + end + + def about + end +end diff --git a/8_0/ch04/app/helpers/application_helper.rb b/8_0/ch04/app/helpers/application_helper.rb new file mode 100644 index 00000000..708e4e48 --- /dev/null +++ b/8_0/ch04/app/helpers/application_helper.rb @@ -0,0 +1,12 @@ +module ApplicationHelper + + # ページごとの完全なタイトルを返します。 # コメント行 + def full_title(page_title = '') # メソッド定義とオプション引数 + base_title = "Ruby on Rails Tutorial Sample App" # 変数への代入 + if page_title.empty? # 論理値テスト + base_title # 暗黙の戻り値 + else + "#{page_title} | #{base_title}" # 文字列の結合 + end + end +end diff --git a/8_0/ch04/app/helpers/hello_codespaces_helper.rb b/8_0/ch04/app/helpers/hello_codespaces_helper.rb new file mode 100644 index 00000000..874a2ca2 --- /dev/null +++ b/8_0/ch04/app/helpers/hello_codespaces_helper.rb @@ -0,0 +1,2 @@ +module HelloCodespacesHelper +end diff --git a/8_0/ch04/app/helpers/static_pages_helper.rb b/8_0/ch04/app/helpers/static_pages_helper.rb new file mode 100644 index 00000000..2d63e79e --- /dev/null +++ b/8_0/ch04/app/helpers/static_pages_helper.rb @@ -0,0 +1,2 @@ +module StaticPagesHelper +end diff --git a/8_0/ch04/app/javascript/application.js b/8_0/ch04/app/javascript/application.js new file mode 100644 index 00000000..0d7b4940 --- /dev/null +++ b/8_0/ch04/app/javascript/application.js @@ -0,0 +1,3 @@ +// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails +import "@hotwired/turbo-rails" +import "controllers" diff --git a/8_0/ch04/app/javascript/controllers/application.js b/8_0/ch04/app/javascript/controllers/application.js new file mode 100644 index 00000000..1213e85c --- /dev/null +++ b/8_0/ch04/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/8_0/ch04/app/javascript/controllers/hello_controller.js b/8_0/ch04/app/javascript/controllers/hello_controller.js new file mode 100644 index 00000000..5975c078 --- /dev/null +++ b/8_0/ch04/app/javascript/controllers/hello_controller.js @@ -0,0 +1,7 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.element.textContent = "Hello World!" + } +} diff --git a/8_0/ch04/app/javascript/controllers/index.js b/8_0/ch04/app/javascript/controllers/index.js new file mode 100644 index 00000000..54ad4cad --- /dev/null +++ b/8_0/ch04/app/javascript/controllers/index.js @@ -0,0 +1,11 @@ +// Import and register all your controllers from the importmap under controllers/* + +import { application } from "controllers/application" + +// Eager load all controllers defined in the import map under controllers/**/*_controller +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) + +// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) +// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" +// lazyLoadControllersFrom("controllers", application) diff --git a/8_0/ch04/app/jobs/application_job.rb b/8_0/ch04/app/jobs/application_job.rb new file mode 100644 index 00000000..d394c3d1 --- /dev/null +++ b/8_0/ch04/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/8_0/ch04/app/mailers/application_mailer.rb b/8_0/ch04/app/mailers/application_mailer.rb new file mode 100644 index 00000000..3c34c814 --- /dev/null +++ b/8_0/ch04/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "from@example.com" + layout "mailer" +end diff --git a/8_0/ch04/app/models/application_record.rb b/8_0/ch04/app/models/application_record.rb new file mode 100644 index 00000000..b63caeb8 --- /dev/null +++ b/8_0/ch04/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/8_0/ch04/app/models/concerns/.keep b/8_0/ch04/app/models/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/app/views/hello/index.html.erb b/8_0/ch04/app/views/hello/index.html.erb new file mode 100644 index 00000000..71661105 --- /dev/null +++ b/8_0/ch04/app/views/hello/index.html.erb @@ -0,0 +1,16 @@ +
+
+ +

+ Codespaces + ♥️ + Railsチュートリアル +

+

+ 🎓✨
+ ロゴ画像が表示されたらセットアップ完了です! +

+
+
diff --git a/8_0/ch04/app/views/layouts/application.html.erb b/8_0/ch04/app/views/layouts/application.html.erb new file mode 100644 index 00000000..ab198d6d --- /dev/null +++ b/8_0/ch04/app/views/layouts/application.html.erb @@ -0,0 +1,17 @@ + + + + <%= full_title(yield(:title)) %> + + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + + + + <%= yield %> + + diff --git a/8_0/ch04/app/views/layouts/mailer.html.erb b/8_0/ch04/app/views/layouts/mailer.html.erb new file mode 100644 index 00000000..cbd34d2e --- /dev/null +++ b/8_0/ch04/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/8_0/ch04/app/views/layouts/mailer.text.erb b/8_0/ch04/app/views/layouts/mailer.text.erb new file mode 100644 index 00000000..37f0bddb --- /dev/null +++ b/8_0/ch04/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/8_0/ch04/app/views/static_pages/about.html.erb b/8_0/ch04/app/views/static_pages/about.html.erb new file mode 100644 index 00000000..dce8fef2 --- /dev/null +++ b/8_0/ch04/app/views/static_pages/about.html.erb @@ -0,0 +1,10 @@ +<% provide(:title, "About") %> +

About

+

+ Ruby on Rails Tutorial + is a book and + screencast + to teach web development with + Ruby on Rails. + This is the sample application for the tutorial. +

diff --git a/8_0/ch04/app/views/static_pages/help.html.erb b/8_0/ch04/app/views/static_pages/help.html.erb new file mode 100644 index 00000000..4d06919d --- /dev/null +++ b/8_0/ch04/app/views/static_pages/help.html.erb @@ -0,0 +1,9 @@ +<% provide(:title, "Help") %> +

Help

+

+ Get help on the Ruby on Rails Tutorial at the + Rails Tutorial Help page. + To get help on this sample app, see the + Ruby on Rails Tutorial + book. +

diff --git a/8_0/ch04/app/views/static_pages/home.html.erb b/8_0/ch04/app/views/static_pages/home.html.erb new file mode 100644 index 00000000..36aaa287 --- /dev/null +++ b/8_0/ch04/app/views/static_pages/home.html.erb @@ -0,0 +1,6 @@ +

Sample App

+

+ This is the home page for the + Ruby on Rails Tutorial + sample application. +

diff --git a/8_0/ch04/bin/bundle b/8_0/ch04/bin/bundle new file mode 100755 index 00000000..981e650b --- /dev/null +++ b/8_0/ch04/bin/bundle @@ -0,0 +1,114 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + requirement = bundler_gem_version.approximate_recommendation + + return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0") + + requirement += ".a" if bundler_gem_version.prerelease? + + requirement + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/8_0/ch04/bin/dev b/8_0/ch04/bin/dev new file mode 100755 index 00000000..5f91c205 --- /dev/null +++ b/8_0/ch04/bin/dev @@ -0,0 +1,2 @@ +#!/usr/bin/env ruby +exec "./bin/rails", "server", *ARGV diff --git a/8_0/ch04/bin/importmap b/8_0/ch04/bin/importmap new file mode 100755 index 00000000..36502ab1 --- /dev/null +++ b/8_0/ch04/bin/importmap @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require_relative "../config/application" +require "importmap/commands" diff --git a/8_0/ch04/bin/rails b/8_0/ch04/bin/rails new file mode 100755 index 00000000..efc03774 --- /dev/null +++ b/8_0/ch04/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/8_0/ch04/bin/rake b/8_0/ch04/bin/rake new file mode 100755 index 00000000..4fbf10b9 --- /dev/null +++ b/8_0/ch04/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/8_0/ch04/bin/rubocop b/8_0/ch04/bin/rubocop new file mode 100755 index 00000000..40330c0f --- /dev/null +++ b/8_0/ch04/bin/rubocop @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +# explicit rubocop config increases performance slightly while avoiding config confusion. +ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) + +load Gem.bin_path("rubocop", "rubocop") diff --git a/8_0/ch04/bin/setup b/8_0/ch04/bin/setup new file mode 100755 index 00000000..be3db3c0 --- /dev/null +++ b/8_0/ch04/bin/setup @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +require "fileutils" + +APP_ROOT = File.expand_path("..", __dir__) + +def system!(*args) + system(*args, exception: true) +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts "== Installing dependencies ==" + system("bundle check") || system!("bundle install") + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + puts "\n== Removing old logs and tempfiles ==" + system! "bin/rails log:clear tmp:clear" + + unless ARGV.include?("--skip-server") + puts "\n== Starting development server ==" + STDOUT.flush # flush the output before exec(2) so that it displays + exec "bin/dev" + end +end diff --git a/8_0/ch04/config.ru b/8_0/ch04/config.ru new file mode 100644 index 00000000..4a3c09a6 --- /dev/null +++ b/8_0/ch04/config.ru @@ -0,0 +1,6 @@ +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/8_0/ch04/config/application.rb b/8_0/ch04/config/application.rb new file mode 100644 index 00000000..0461e72f --- /dev/null +++ b/8_0/ch04/config/application.rb @@ -0,0 +1,27 @@ +require_relative "boot" + +require "rails/all" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module SampleApp + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 8.0 + + # Please, add to the `ignore` list any other `lib` subdirectories that do + # not contain `.rb` files, or that should not be reloaded or eager loaded. + # Common ones are `templates`, `generators`, or `middleware`, for example. + config.autoload_lib(ignore: %w[assets tasks]) + + # Configuration for the application, engines, and railties goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + # + # config.time_zone = "Central Time (US & Canada)" + # config.eager_load_paths << Rails.root.join("extras") + end +end diff --git a/8_0/ch04/config/boot.rb b/8_0/ch04/config/boot.rb new file mode 100644 index 00000000..988a5ddc --- /dev/null +++ b/8_0/ch04/config/boot.rb @@ -0,0 +1,4 @@ +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "bundler/setup" # Set up gems listed in the Gemfile. +require "bootsnap/setup" # Speed up boot time by caching expensive operations. diff --git a/8_0/ch04/config/cable.yml b/8_0/ch04/config/cable.yml new file mode 100644 index 00000000..ae7a2256 --- /dev/null +++ b/8_0/ch04/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: codespaces_try_rails_production diff --git a/8_0/ch04/config/credentials.yml.enc b/8_0/ch04/config/credentials.yml.enc new file mode 100644 index 00000000..339d64d2 --- /dev/null +++ b/8_0/ch04/config/credentials.yml.enc @@ -0,0 +1 @@ +NCs9M8uakg1CDYJENuGaipzymY8uG6i5gzghsy4npa3gYmGfYKPHMnEcJHCVHlM3gxDNJBteUqkRCxHQ5WQId8hzgxoQCL5RX7rtQ8XUzkJwgSUjKvbCrG5atjKkN9XuL91is5vtEN5W7vEz3qxN7Rp33QbNFiDfLs71F3zHVDYEMi6Dy1QMhVCa1v/tyRjBu/5m37RuiYF5FzWJnh4LhexSVr7Agm4LRLLN6qLCpoAe6D3TPHHBdtu7Pvy8jlrEfnnkUJ61wrj8+kOL4uLtpFShdYKhDkoTjQk7TCOgWyifnHAXazkBZoJZ1QYv/tZ9/ZoHvMEQ2dtGHlyTX8fbKtI13d2CFjGaDNKvRuurxE8dbeyiTfbycvve46+cz9OTlmhh0SGt24R1WV6GBKoDUeoHrIiBvW5qmKka--dQ0rodxmfgFLg2y/--t2x0gQjh8FYlk8v79vLSNg== \ No newline at end of file diff --git a/8_0/ch04/config/database.yml b/8_0/ch04/config/database.yml new file mode 100644 index 00000000..fcba57f1 --- /dev/null +++ b/8_0/ch04/config/database.yml @@ -0,0 +1,25 @@ +# SQLite. Versions 3.8.0 and up are supported. +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem "sqlite3" +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/8_0/ch04/config/environment.rb b/8_0/ch04/config/environment.rb new file mode 100644 index 00000000..cac53157 --- /dev/null +++ b/8_0/ch04/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/8_0/ch04/config/environments/development.rb b/8_0/ch04/config/environments/development.rb new file mode 100644 index 00000000..910cef99 --- /dev/null +++ b/8_0/ch04/config/environments/development.rb @@ -0,0 +1,86 @@ +require 'active_support/core_ext/integer/time' + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Make code changes take effect immediately without server restart. + config.enable_reloading = true + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing. + config.server_timing = true + + # Add the following line to disable forgery_protection_origin_check + config.action_controller.forgery_protection_origin_check = false + + # Enable/disable Action Controller caching. By default Action Controller caching is disabled. + # Run rails dev:cache to toggle Action Controller caching. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + config.public_file_server.headers = { 'cache-control' => "public, max-age=#{2.days.to_i}" } + else + config.action_controller.perform_caching = false + end + + # Change to :null_store to avoid any caching. + config.cache_store = :memory_store + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Make template changes take effect immediately. + config.action_mailer.perform_caching = false + + # Set localhost to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Append comments with runtime information tags to SQL queries in logs. + config.active_record.query_log_tags_enabled = true + + # Highlight code that enqueued background job in logs. + config.active_job.verbose_enqueue_logs = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true + + # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. + # config.generators.apply_rubocop_autocorrect_after_generate! + + pf_domain = ENV['GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN'] + config.action_dispatch.default_headers = { + 'X-Frame-Options' => "ALLOW-FROM #{pf_domain}" + } + + # Allow requests from our preview domain. + pf_host = "#{ENV['CODESPACE_NAME']}-3000.#{pf_domain}" + config.hosts << pf_host + + config.action_cable.allowed_request_origins = ["https://#{pf_host}"] +end diff --git a/8_0/ch04/config/environments/production.rb b/8_0/ch04/config/environments/production.rb new file mode 100644 index 00000000..17496077 --- /dev/null +++ b/8_0/ch04/config/environments/production.rb @@ -0,0 +1,89 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.enable_reloading = false + + # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). + config.eager_load = true + + # Full error reports are disabled. + config.consider_all_requests_local = false + + # Turn on fragment caching in view templates. + config.action_controller.perform_caching = true + + # Cache assets for far-future expiry since they are all digest stamped. + config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" } + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = "http://assets.example.com" + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Assume all access to the app is happening through a SSL-terminating reverse proxy. + config.assume_ssl = true + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = true + + # Skip http-to-https redirect for the default health check endpoint. + # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } + + # Log to STDOUT with the current request id as a default log tag. + config.log_tags = [ :request_id ] + config.logger = ActiveSupport::TaggedLogging.logger(STDOUT) + + # Change to "debug" to log everything (including potentially personally-identifiable information!) + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + + # Prevent health checks from clogging up the logs. + config.silence_healthcheck_path = "/up" + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Replace the default in-process memory cache store with a durable alternative. + # config.cache_store = :mem_cache_store + + # Replace the default in-process and non-durable queuing backend for Active Job. + # config.active_job.queue_adapter = :resque + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. + # config.action_mailer.smtp_settings = { + # user_name: Rails.application.credentials.dig(:smtp, :user_name), + # password: Rails.application.credentials.dig(:smtp, :password), + # address: "smtp.example.com", + # port: 587, + # authentication: :plain + # } + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Only use :id for inspections in production. + config.active_record.attributes_for_inspect = [ :id ] + + # Enable DNS rebinding protection and other `Host` header attacks. + # config.hosts = [ + # "example.com", # Allow requests from example.com + # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` + # ] + # + # Skip DNS rebinding protection for the default health check endpoint. + # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } +end diff --git a/8_0/ch04/config/environments/test.rb b/8_0/ch04/config/environments/test.rb new file mode 100644 index 00000000..0ebe3b4e --- /dev/null +++ b/8_0/ch04/config/environments/test.rb @@ -0,0 +1,53 @@ +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # While tests run files are not watched, reloading is not necessary. + config.enable_reloading = false + + # Eager loading loads your entire application. When running a single test locally, + # this is usually not necessary, and can slow down your test suite. However, it's + # recommended that you enable it in continuous integration systems to ensure eager + # loading is working properly before deploying your code. + config.eager_load = ENV["CI"].present? + + # Configure public file server for tests with cache-control for performance. + config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } + + # Show full error reports. + config.consider_all_requests_local = true + config.cache_store = :null_store + + # Render exception templates for rescuable exceptions and raise for other exceptions. + config.action_dispatch.show_exceptions = :none + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :test + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true +end diff --git a/8_0/ch04/config/importmap.rb b/8_0/ch04/config/importmap.rb new file mode 100644 index 00000000..8dce42d4 --- /dev/null +++ b/8_0/ch04/config/importmap.rb @@ -0,0 +1,7 @@ +# Pin npm packages by running ./bin/importmap + +pin "application", preload: true +pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true +pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true +pin_all_from "app/javascript/controllers", under: "controllers" diff --git a/8_0/ch04/config/initializers/assets.rb b/8_0/ch04/config/initializers/assets.rb new file mode 100644 index 00000000..48732442 --- /dev/null +++ b/8_0/ch04/config/initializers/assets.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = "1.0" + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path diff --git a/8_0/ch04/config/initializers/content_security_policy.rb b/8_0/ch04/config/initializers/content_security_policy.rb new file mode 100644 index 00000000..b3076b38 --- /dev/null +++ b/8_0/ch04/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header + +# Rails.application.configure do +# config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end +# +# # Generate session nonces for permitted importmap, inline scripts, and inline styles. +# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } +# config.content_security_policy_nonce_directives = %w(script-src style-src) +# +# # Report violations without enforcing the policy. +# # config.content_security_policy_report_only = true +# end diff --git a/8_0/ch04/config/initializers/filter_parameter_logging.rb b/8_0/ch04/config/initializers/filter_parameter_logging.rb new file mode 100644 index 00000000..c0b717f7 --- /dev/null +++ b/8_0/ch04/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. +# Use this to limit dissemination of sensitive information. +# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. +Rails.application.config.filter_parameters += [ + :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc +] diff --git a/8_0/ch04/config/initializers/inflections.rb b/8_0/ch04/config/initializers/inflections.rb new file mode 100644 index 00000000..3860f659 --- /dev/null +++ b/8_0/ch04/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym "RESTful" +# end diff --git a/8_0/ch04/config/initializers/new_framework_defaults_8_0.rb b/8_0/ch04/config/initializers/new_framework_defaults_8_0.rb new file mode 100644 index 00000000..92efa951 --- /dev/null +++ b/8_0/ch04/config/initializers/new_framework_defaults_8_0.rb @@ -0,0 +1,30 @@ +# Be sure to restart your server when you modify this file. +# +# This file eases your Rails 8.0 framework defaults upgrade. +# +# Uncomment each configuration one by one to switch to the new default. +# Once your application is ready to run with all new defaults, you can remove +# this file and set the `config.load_defaults` to `8.0`. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. +# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html + +### +# Specifies whether `to_time` methods preserve the UTC offset of their receivers or preserves the timezone. +# If set to `:zone`, `to_time` methods will use the timezone of their receivers. +# If set to `:offset`, `to_time` methods will use the UTC offset. +# If `false`, `to_time` methods will convert to the local system UTC offset instead. +#++ +# Rails.application.config.active_support.to_time_preserves_timezone = :zone + +### +# When both `If-Modified-Since` and `If-None-Match` are provided by the client +# only consider `If-None-Match` as specified by RFC 7232 Section 6. +# If set to `false` both conditions need to be satisfied. +#++ +# Rails.application.config.action_dispatch.strict_freshness = true + +### +# Set `Regexp.timeout` to `1`s by default to improve security over Regexp Denial-of-Service attacks. +#++ +# Regexp.timeout = 1 diff --git a/8_0/ch04/config/initializers/permissions_policy.rb b/8_0/ch04/config/initializers/permissions_policy.rb new file mode 100644 index 00000000..00f64d71 --- /dev/null +++ b/8_0/ch04/config/initializers/permissions_policy.rb @@ -0,0 +1,11 @@ +# Define an application-wide HTTP permissions policy. For further +# information see https://developers.google.com/web/updates/2018/06/feature-policy +# +# Rails.application.config.permissions_policy do |f| +# f.camera :none +# f.gyroscope :none +# f.microphone :none +# f.usb :none +# f.fullscreen :self +# f.payment :self, "https://secure.example.com" +# end diff --git a/8_0/ch04/config/locales/en.yml b/8_0/ch04/config/locales/en.yml new file mode 100644 index 00000000..8ca56fc7 --- /dev/null +++ b/8_0/ch04/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# "true": "foo" +# +# To learn more, please read the Rails Internationalization guide +# available at https://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/8_0/ch04/config/puma.rb b/8_0/ch04/config/puma.rb new file mode 100644 index 00000000..a248513b --- /dev/null +++ b/8_0/ch04/config/puma.rb @@ -0,0 +1,41 @@ +# This configuration file will be evaluated by Puma. The top-level methods that +# are invoked here are part of Puma's configuration DSL. For more information +# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. +# +# Puma starts a configurable number of processes (workers) and each process +# serves each request in a thread from an internal thread pool. +# +# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You +# should only set this value when you want to run 2 or more workers. The +# default is already 1. +# +# The ideal number of threads per worker depends both on how much time the +# application spends waiting for IO operations and on how much you wish to +# prioritize throughput over latency. +# +# As a rule of thumb, increasing the number of threads will increase how much +# traffic a given process can handle (throughput), but due to CRuby's +# Global VM Lock (GVL) it has diminishing returns and will degrade the +# response time (latency) of the application. +# +# The default is set to 3 threads as it's deemed a decent compromise between +# throughput and latency for the average Rails application. +# +# Any libraries that use a connection pool or another resource pool should +# be configured to provide at least as many connections as the number of +# threads. This includes Active Record's `pool` parameter in `database.yml`. +threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +port ENV.fetch("PORT", 3000) + +# Allow puma to be restarted by `bin/rails restart` command. +plugin :tmp_restart + +# Run the Solid Queue supervisor inside of Puma for single-server deployments +plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] + +# Specify the PID file. Defaults to tmp/pids/server.pid in development. +# In other environments, only set the PID file if requested. +pidfile ENV["PIDFILE"] if ENV["PIDFILE"] diff --git a/8_0/ch04/config/routes.rb b/8_0/ch04/config/routes.rb new file mode 100644 index 00000000..73181abd --- /dev/null +++ b/8_0/ch04/config/routes.rb @@ -0,0 +1,6 @@ +Rails.application.routes.draw do + root "static_pages#home" + get "static_pages/home" + get "static_pages/help" + get "static_pages/about" +end diff --git a/8_0/ch04/config/storage.yml b/8_0/ch04/config/storage.yml new file mode 100644 index 00000000..4942ab66 --- /dev/null +++ b/8_0/ch04/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket-<%= Rails.env %> + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket-<%= Rails.env %> + +# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name-<%= Rails.env %> + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/8_0/ch04/db/seeds.rb b/8_0/ch04/db/seeds.rb new file mode 100644 index 00000000..bc25fce3 --- /dev/null +++ b/8_0/ch04/db/seeds.rb @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). +# +# Examples: +# +# movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }]) +# Character.create(name: "Luke", movie: movies.first) diff --git a/8_0/ch04/lib/assets/.keep b/8_0/ch04/lib/assets/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/lib/tasks/.keep b/8_0/ch04/lib/tasks/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/log/.keep b/8_0/ch04/log/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/public/400.html b/8_0/ch04/public/400.html new file mode 100644 index 00000000..282dbc8c --- /dev/null +++ b/8_0/ch04/public/400.html @@ -0,0 +1,114 @@ + + + + + + + The server cannot process the request due to a client error (400 Bad Request) + + + + + + + + + + + + + +
+
+ +
+
+

The server cannot process the request due to a client error. Please check the request and try again. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch04/public/404.html b/8_0/ch04/public/404.html new file mode 100644 index 00000000..c0670bc8 --- /dev/null +++ b/8_0/ch04/public/404.html @@ -0,0 +1,114 @@ + + + + + + + The page you were looking for doesn’t exist (404 Not found) + + + + + + + + + + + + + +
+
+ +
+
+

The page you were looking for doesn’t exist. You may have mistyped the address or the page may have moved. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch04/public/406-unsupported-browser.html b/8_0/ch04/public/406-unsupported-browser.html new file mode 100644 index 00000000..9532a9cc --- /dev/null +++ b/8_0/ch04/public/406-unsupported-browser.html @@ -0,0 +1,114 @@ + + + + + + + Your browser is not supported (406 Not Acceptable) + + + + + + + + + + + + + +
+
+ +
+
+

Your browser is not supported.
Please upgrade your browser to continue.

+
+
+ + + + diff --git a/8_0/ch04/public/422.html b/8_0/ch04/public/422.html new file mode 100644 index 00000000..8bcf0601 --- /dev/null +++ b/8_0/ch04/public/422.html @@ -0,0 +1,114 @@ + + + + + + + The change you wanted was rejected (422 Unprocessable Entity) + + + + + + + + + + + + + +
+
+ +
+
+

The change you wanted was rejected. Maybe you tried to change something you didn’t have access to. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch04/public/500.html b/8_0/ch04/public/500.html new file mode 100644 index 00000000..d77718c3 --- /dev/null +++ b/8_0/ch04/public/500.html @@ -0,0 +1,114 @@ + + + + + + + We’re sorry, but something went wrong (500 Internal Server Error) + + + + + + + + + + + + + +
+
+ +
+
+

We’re sorry, but something went wrong.
If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch04/public/apple-touch-icon-precomposed.png b/8_0/ch04/public/apple-touch-icon-precomposed.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/public/apple-touch-icon.png b/8_0/ch04/public/apple-touch-icon.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/public/favicon.ico b/8_0/ch04/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/public/icon.png b/8_0/ch04/public/icon.png new file mode 100644 index 00000000..c4c9dbfb Binary files /dev/null and b/8_0/ch04/public/icon.png differ diff --git a/8_0/ch04/public/icon.svg b/8_0/ch04/public/icon.svg new file mode 100644 index 00000000..04b34bf8 --- /dev/null +++ b/8_0/ch04/public/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/8_0/ch04/public/railstutorial.png b/8_0/ch04/public/railstutorial.png new file mode 100644 index 00000000..70835e6b Binary files /dev/null and b/8_0/ch04/public/railstutorial.png differ diff --git a/8_0/ch04/public/robots.txt b/8_0/ch04/public/robots.txt new file mode 100644 index 00000000..c19f78ab --- /dev/null +++ b/8_0/ch04/public/robots.txt @@ -0,0 +1 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/8_0/ch04/storage/.keep b/8_0/ch04/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/test/application_system_test_case.rb b/8_0/ch04/test/application_system_test_case.rb new file mode 100644 index 00000000..d19212ab --- /dev/null +++ b/8_0/ch04/test/application_system_test_case.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/8_0/ch04/test/channels/application_cable/connection_test.rb b/8_0/ch04/test/channels/application_cable/connection_test.rb new file mode 100644 index 00000000..800405f1 --- /dev/null +++ b/8_0/ch04/test/channels/application_cable/connection_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase + # test "connects with cookies" do + # cookies.signed[:user_id] = 42 + # + # connect + # + # assert_equal connection.user_id, "42" + # end +end diff --git a/8_0/ch04/test/controllers/.keep b/8_0/ch04/test/controllers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/test/controllers/hello_codespaces_controller_test.rb b/8_0/ch04/test/controllers/hello_codespaces_controller_test.rb new file mode 100644 index 00000000..54353ea6 --- /dev/null +++ b/8_0/ch04/test/controllers/hello_codespaces_controller_test.rb @@ -0,0 +1,8 @@ +require "test_helper" + +class HelloCodespacesControllerTest < ActionDispatch::IntegrationTest + # test "should get index" do + # get "/" + # assert_response :success + # end +end diff --git a/8_0/ch04/test/controllers/static_pages_controller_test.rb b/8_0/ch04/test/controllers/static_pages_controller_test.rb new file mode 100644 index 00000000..80204ac7 --- /dev/null +++ b/8_0/ch04/test/controllers/static_pages_controller_test.rb @@ -0,0 +1,22 @@ +require "test_helper" + +class StaticPagesControllerTest < ActionDispatch::IntegrationTest + + test "should get home" do + get static_pages_home_url + assert_response :success + assert_select "title", "Ruby on Rails Tutorial Sample App" + end + + test "should get help" do + get static_pages_help_url + assert_response :success + assert_select "title", "Help | Ruby on Rails Tutorial Sample App" + end + + test "should get about" do + get static_pages_about_url + assert_response :success + assert_select "title", "About | Ruby on Rails Tutorial Sample App" + end +end diff --git a/8_0/ch04/test/fixtures/files/.keep b/8_0/ch04/test/fixtures/files/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/test/helpers/.keep b/8_0/ch04/test/helpers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/test/integration/.keep b/8_0/ch04/test/integration/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/test/mailers/.keep b/8_0/ch04/test/mailers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/test/models/.keep b/8_0/ch04/test/models/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/test/system/.keep b/8_0/ch04/test/system/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/test/test_helper.rb b/8_0/ch04/test/test_helper.rb new file mode 100644 index 00000000..f566ca61 --- /dev/null +++ b/8_0/ch04/test/test_helper.rb @@ -0,0 +1,15 @@ +ENV["RAILS_ENV"] ||= "test" +require_relative "../config/environment" +require "rails/test_help" +require "minitest/reporters" +Minitest::Reporters.use! + +class ActiveSupport::TestCase + # 指定のワーカー数でテストを並列実行する + parallelize(workers: :number_of_processors) + + # test/fixtures/*.ymlにあるすべてのfixtureをセットアップする + fixtures :all + + # (すべてのテストで使うその他のヘルパーメソッドは省略) +end diff --git a/8_0/ch04/tmp/.keep b/8_0/ch04/tmp/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/tmp/pids/.keep b/8_0/ch04/tmp/pids/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/tmp/storage/.keep b/8_0/ch04/tmp/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/vendor/.keep b/8_0/ch04/vendor/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch04/vendor/javascript/.keep b/8_0/ch04/vendor/javascript/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/.devcontainer/devcontainer.json b/8_0/ch05/.devcontainer/devcontainer.json new file mode 100644 index 00000000..e7243217 --- /dev/null +++ b/8_0/ch05/.devcontainer/devcontainer.json @@ -0,0 +1,105 @@ +// Specifications: https://containers.dev/implementors/json_reference/ +// Format details: https://aka.ms/devcontainer.json +// Config options: https://github.com/microsoft/vscode-dev-containers/tree/main/containers/ruby +{ + "name": "railstutorial", + + // Universal is well-customized image for Codespaces: + // https://hub.docker.com/_/microsoft-devcontainers-universal + //"image": "mcr.microsoft.com/devcontainers/universal:2", + //"features": { + // "ghcr.io/devcontainers/features/ruby:1": {} + //}, + + // Use Ruby image if you want to pin Ruby version like '3.2' + // https://github.com/devcontainers/images/tree/main/src/ruby + "image": "mcr.microsoft.com/devcontainers/ruby:3.2", + //"workspaceFolder": "/railstutorial", => Container build failed + + // Enable learners to choose an affordable spec, starting at minimum one. + //"hostRequirements": { + // "cpus": 2, + // "memory": "4gb", + // "storage": "32gb" + //}, + + "waitFor": "onCreateCommand", + //"onCreateCommand": "", + "onCreateCommand": "gem install solargraph -v '0.58.2' -N", + //"onCreateCommand": "gem install solargraph -v '0.50.0' -N && gem install ruby-lsp -N", + //"onCreateCommand": "gem install ruby-lsp -N", + //# => Solargraph gem not found. Run `gem install solargraph` or update your Gemfile. + "updateContentCommand": "bundle install", + "postCreateCommand": "", + "postAttachCommand": { + "server": "rails server" + }, + "customizations": { + "codespaces": { + "openFiles": [ + "app/views/hello/index.html.erb" + ] + }, + "vscode": { + "extensions": [ + "GitHub.codespaces", + "Shopify.ruby-lsp", // https://github.com/Shopify/ruby-lsp + "castwide.solargraph" // https://github.com/castwide/vscode-solargraph + //"rebornix.Ruby", // https://github.com/rubyide/vscode-ruby (Deprecated) + + ], + "settings": { + // General settings for Codespaces (VS Code) + //"ruby.useLanguageServer": true , + //"ruby.format": "rubocop", + //"ruby.lint": { "rubocop": true }, + //"ruby.intellisense": "rubyLocate", + "editor.tabSize": 2, + "editor.formatOnSave": false, // Disable onSave to show diff edited by learners only + "editor.formatOnType": false, // Disable onType for the same reason above + "editor.insertSpaces": true, // Use spaces, not tabs, to avoid errors for learners + "editor.renderWhitespace": "none", + "[ruby]": { + "editor.defaultFormatter": "castwide.solargraph", + "editor.semanticHighlighting.enabled": true // Enable semantic highlighting + }, + "files.associations": { "*.erb": "erb" }, + "emmet.includeLanguages": { "erb": "html" }, + + // Settings for Solargraph + // https://github.com/castwide/solargraph + "solargraph.useBundler": false, + "solargraph.diagnostics": false, + "solargraph.formatting": true, // Use Ctrl+Shift+P->Format to format + "solargraph.autoformat": false, + "solargraph.definitions": true, + "solargraph.completion": true, + "solargraph.references": true, + "solargraph.symbols": true, + "solargraph.rename": true, + "solargraph.hover": true, + + // Settings for Ruby LSP + // https://github.com/Shopify/ruby-lsp + "rubyLsp.rubyVersionManager": { + "identifier": "none" + }, + "rubyLsp.formatter": "none", + "rubyLsp.enabledFeatures": { + "diagnostics": false, + "formatting": false + } + } + } + }, + "remoteEnv": { + "EDITOR": "code --wait" + }, + "portsAttributes": { + "3000": { + "label": "Application", + "onAutoForward": "openPreview" + } + }, + "forwardPorts": [3000] +} diff --git a/8_0/ch05/.devcontainer/icon.svg b/8_0/ch05/.devcontainer/icon.svg new file mode 100644 index 00000000..f8444a4a --- /dev/null +++ b/8_0/ch05/.devcontainer/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/8_0/ch05/.gitattributes b/8_0/ch05/.gitattributes new file mode 100644 index 00000000..31eeee0b --- /dev/null +++ b/8_0/ch05/.gitattributes @@ -0,0 +1,7 @@ +# See https://git-scm.com/docs/gitattributes for more about git attribute files. + +# Mark the database schema as having been generated. +db/schema.rb linguist-generated + +# Mark any vendored files as having been vendored. +vendor/* linguist-vendored diff --git a/8_0/ch05/.gitignore b/8_0/ch05/.gitignore new file mode 100644 index 00000000..e1ef53ef --- /dev/null +++ b/8_0/ch05/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-* + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore uploaded files in development. +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/ +!/tmp/storage/.keep + +/public/assets + +# Ignore master key for decrypting credentials and more. +/config/master.key + +# Ignore history file used by IRB for interactive Ruby. +.irb_history + +dump.rdb diff --git a/8_0/ch05/.irbrc b/8_0/ch05/.irbrc new file mode 100644 index 00000000..3737e036 --- /dev/null +++ b/8_0/ch05/.irbrc @@ -0,0 +1 @@ +IRB.conf[:COMPLETOR] = :type # default is :regexp diff --git a/8_0/ch05/.solargraph.yml b/8_0/ch05/.solargraph.yml new file mode 100644 index 00000000..76ac24a6 --- /dev/null +++ b/8_0/ch05/.solargraph.yml @@ -0,0 +1,32 @@ +--- +include: +- "**/*.rb" +exclude: +- spec/**/* +- test/**/* +- vendor/**/* +- ".bundle/**/*" +require: +- actioncable +- actionmailer +- actionpack +- actionview +- activejob +- activemodel +- activerecord +- activestorage +- activesupport +domains: [] +reporters: +- rubocop +- require_not_found +- typecheck +formatter: + rubocop: + cops: safe + except: [] + only: [] + extra_args: [] +require_paths: [] +plugins: [] +max_files: 5000 diff --git a/8_0/ch05/.vscode/settings.json b/8_0/ch05/.vscode/settings.json new file mode 100644 index 00000000..cf85f519 --- /dev/null +++ b/8_0/ch05/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.minimap.enabled": false +} diff --git a/8_0/ch05/Gemfile b/8_0/ch05/Gemfile new file mode 100644 index 00000000..4f6c143b --- /dev/null +++ b/8_0/ch05/Gemfile @@ -0,0 +1,43 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '3.2.10' + +gem 'bootsnap', '1.16.0', require: false +gem 'bootstrap-sass', '3.4.1' +gem 'concurrent-ruby', '1.3.4' +gem 'importmap-rails', '1.1.5' +gem 'jbuilder', '2.14.1' +gem 'puma', '6.6.1' +gem 'rails', '8.0.2.1' +gem 'sassc-rails', '2.1.2' +gem 'sprockets-rails', '3.4.2' +gem 'sqlite3', '2.7.3' +gem 'stimulus-rails', '1.2.1' +gem 'turbo-rails', '1.4.0' + +group :development, :test do + gem 'debug', '1.7.1', platforms: %i[mri mingw x64_mingw] + gem 'reline', '0.5.10' +end + +group :development do + gem 'irb', '1.15.2' + gem 'repl_type_completor', '0.1.10' + gem 'solargraph', '0.58.2' + gem 'web-console', '4.2.0' +end + +group :test do + gem 'capybara', '3.38.0' + gem 'guard', '2.18.0' + gem 'guard-minitest', '2.4.6' + gem 'minitest', '5.18.0' + gem 'minitest-reporters', '1.6.0' + gem 'rails-controller-testing', '1.0.5' + gem 'selenium-webdriver', '4.8.3' + gem 'webdrivers', '5.2.0' +end + +# Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります +# gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] diff --git a/8_0/ch05/Gemfile.lock b/8_0/ch05/Gemfile.lock new file mode 100644 index 00000000..c1b9e93b --- /dev/null +++ b/8_0/ch05/Gemfile.lock @@ -0,0 +1,440 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + actionmailer (8.0.2.1) + actionpack (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.0.2.1) + actionview (= 8.0.2.1) + activesupport (= 8.0.2.1) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.0.2.1) + actionpack (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (8.0.2.1) + activesupport (= 8.0.2.1) + builder (~> 3.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.3.6) + activemodel (8.0.2.1) + activesupport (= 8.0.2.1) + activerecord (8.0.2.1) + activemodel (= 8.0.2.1) + activesupport (= 8.0.2.1) + timeout (>= 0.4.0) + activestorage (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activesupport (= 8.0.2.1) + marcel (~> 1.0) + activesupport (8.0.2.1) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + ansi (1.5.0) + ast (2.4.3) + autoprefixer-rails (10.4.21.0) + execjs (~> 2) + backport (1.2.0) + base64 (0.3.0) + benchmark (0.4.1) + bigdecimal (3.2.3) + bindex (0.8.1) + bootsnap (1.16.0) + msgpack (~> 1.2) + bootstrap-sass (3.4.1) + autoprefixer-rails (>= 5.2.1) + sassc (>= 2.0.0) + builder (3.3.0) + capybara (3.38.0) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + coderay (1.1.3) + concurrent-ruby (1.3.4) + connection_pool (2.5.4) + crass (1.0.6) + date (3.4.1) + debug (1.7.1) + irb (>= 1.5.0) + reline (>= 0.3.1) + diff-lcs (1.6.2) + drb (2.2.3) + erb (5.0.2) + erubi (1.13.1) + execjs (2.10.1) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) + formatador (1.2.0) + reline + globalid (1.2.1) + activesupport (>= 6.1) + guard (2.18.0) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.13.0) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-minitest (2.4.6) + guard-compat (~> 1.2) + minitest (>= 3.0) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + importmap-rails (1.1.5) + actionpack (>= 6.0.0) + railties (>= 6.0.0) + io-console (0.8.1) + irb (1.15.2) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jaro_winkler (1.6.1) + jbuilder (2.14.1) + actionview (>= 7.0.0) + activesupport (>= 7.0.0) + json (2.13.2) + kramdown (2.5.1) + rexml (>= 3.3.9) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) + loofah (2.24.1) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + lumberjack (1.4.1) + mail (2.8.1) + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.0.4) + matrix (0.4.3) + method_source (1.1.0) + mini_mime (1.1.5) + minitest (5.18.0) + minitest-reporters (1.6.0) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + msgpack (1.8.0) + nenv (0.3.0) + net-imap (0.5.10) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.4) + nokogiri (1.19.1-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-musl) + racc (~> 1.4) + notiffany (0.1.3) + nenv (~> 0.1) + shellany (~> 0.0) + observer (0.1.2) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.9.0) + ast (~> 2.4.1) + racc + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + prism (1.4.0) + pry (0.15.2) + coderay (~> 1.1) + method_source (~> 1.0) + psych (5.2.6) + date + stringio + public_suffix (6.0.2) + puma (6.6.1) + nio4r (~> 2.0) + racc (1.8.1) + rack (3.2.5) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.2.1) + rack (>= 3) + rails (8.0.2.1) + actioncable (= 8.0.2.1) + actionmailbox (= 8.0.2.1) + actionmailer (= 8.0.2.1) + actionpack (= 8.0.2.1) + actiontext (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activemodel (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + bundler (>= 1.15.0) + railties (= 8.0.2.1) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.0) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rbs (3.6.1) + logger + rdoc (6.14.2) + erb + psych (>= 4.0.0) + regexp_parser (2.11.2) + reline (0.5.10) + io-console (~> 0.5) + repl_type_completor (0.1.10) + prism (~> 1.0) + rbs (>= 2.7.0, < 4.0.0) + reverse_markdown (3.0.0) + nokogiri + rexml (3.4.2) + rubocop (1.80.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.46.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.46.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + ruby-progressbar (1.13.0) + rubyzip (2.4.1) + sassc (2.4.0) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt + securerandom (0.4.1) + selenium-webdriver (4.8.3) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) + shellany (0.0.1) + solargraph (0.58.2) + backport (~> 1.2) + benchmark (~> 0.4) + bundler (~> 2.0) + diff-lcs (~> 1.4) + jaro_winkler (~> 1.6, >= 1.6.1) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.1) + logger (~> 1.6) + observer (~> 0.1) + ostruct (~> 0.6) + parser (~> 3.0) + prism (~> 1.4) + rbs (~> 3.6.1) + reverse_markdown (~> 3.0) + rubocop (~> 1.38) + thor (~> 1.0) + tilt (~> 2.0) + yard (~> 0.9, >= 0.9.24) + yard-solargraph (~> 0.1) + sprockets (4.2.2) + concurrent-ruby (~> 1.0) + logger + rack (>= 2.2.4, < 4) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + sprockets (>= 3.0.0) + sqlite3 (2.7.3-aarch64-linux-gnu) + sqlite3 (2.7.3-aarch64-linux-musl) + sqlite3 (2.7.3-arm-linux-gnu) + sqlite3 (2.7.3-arm-linux-musl) + sqlite3 (2.7.3-arm64-darwin) + sqlite3 (2.7.3-x86_64-darwin) + sqlite3 (2.7.3-x86_64-linux-gnu) + sqlite3 (2.7.3-x86_64-linux-musl) + stimulus-rails (1.2.1) + railties (>= 6.0.0) + stringio (3.1.7) + thor (1.4.0) + tilt (2.6.1) + timeout (0.4.3) + turbo-rails (1.4.0) + actionpack (>= 6.0.0) + activejob (>= 6.0.0) + railties (>= 6.0.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.1.5) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + uri (1.0.4) + useragent (0.16.11) + web-console (4.2.0) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + webdrivers (5.2.0) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (~> 4.0) + websocket (1.2.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) + yard (0.9.37) + yard-solargraph (0.1.0) + yard (~> 0.9) + zeitwerk (2.7.3) + +PLATFORMS + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + bootsnap (= 1.16.0) + bootstrap-sass (= 3.4.1) + capybara (= 3.38.0) + concurrent-ruby (= 1.3.4) + debug (= 1.7.1) + guard (= 2.18.0) + guard-minitest (= 2.4.6) + importmap-rails (= 1.1.5) + irb (= 1.15.2) + jbuilder (= 2.14.1) + minitest (= 5.18.0) + minitest-reporters (= 1.6.0) + puma (= 6.6.1) + rails (= 8.0.2.1) + rails-controller-testing (= 1.0.5) + reline (= 0.5.10) + repl_type_completor (= 0.1.10) + sassc-rails (= 2.1.2) + selenium-webdriver (= 4.8.3) + solargraph (= 0.58.2) + sprockets-rails (= 3.4.2) + sqlite3 (= 2.7.3) + stimulus-rails (= 1.2.1) + turbo-rails (= 1.4.0) + web-console (= 4.2.0) + webdrivers (= 5.2.0) + +RUBY VERSION + ruby 3.2.10p266 + +BUNDLED WITH + 2.5.6 diff --git a/8_0/ch05/Guardfile b/8_0/ch05/Guardfile new file mode 100644 index 00000000..5def34a0 --- /dev/null +++ b/8_0/ch05/Guardfile @@ -0,0 +1,76 @@ + require "active_support/inflector" + # Guardのマッチング規則を定義 + guard :minitest, all_on_start: false do + watch(%r{^test/(.*)/?(.*)_test\.rb$}) + watch('test/test_helper.rb') { 'test' } + watch('config/routes.rb') { interface_tests } + watch(%r{app/views/layouts/*}) { interface_tests } + watch(%r{^app/models/(.*?)\.rb$}) do |matches| + ["test/models/#{matches[1]}_test.rb", + "test/integration/microposts_interface_test.rb"] + end + watch(%r{^test/fixtures/(.*?)\.yml$}) do |matches| + "test/models/#{matches[1].singularize}_test.rb" + end + watch(%r{^app/mailers/(.*?)\.rb$}) do |matches| + "test/mailers/#{matches[1]}_test.rb" + end + watch(%r{^app/views/(.*)_mailer/.*$}) do |matches| + "test/mailers/#{matches[1]}_mailer_test.rb" + end + watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches| + resource_tests(matches[1]) + end + watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches| + ["test/controllers/#{matches[1]}_controller_test.rb"] + + integration_tests(matches[1]) + end + watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches| + integration_tests(matches[1]) + end + watch('app/views/layouts/application.html.erb') do + 'test/integration/site_layout_test.rb' + end + watch('app/helpers/sessions_helper.rb') do + integration_tests << 'test/helpers/sessions_helper_test.rb' + end + watch('app/controllers/sessions_controller.rb') do + ['test/controllers/sessions_controller_test.rb', + 'test/integration/users_login_test.rb'] + end + watch('app/controllers/account_activations_controller.rb') do + 'test/integration/users_signup_test.rb' + end + watch(%r{app/views/users/*}) do + resource_tests('users') + + ['test/integration/microposts_interface_test.rb'] + end + watch('app/controllers/relationships_controller.rb') do + ['test/controllers/relationships_controller_test.rb', + 'test/integration/following_test.rb'] + end + end + + # 指定のリソースに対応する統合テストを返す + def integration_tests(resource = :all) + if resource == :all + Dir["test/integration/*"] + else + Dir["test/integration/#{resource}_*.rb"] + end + end + + # インターフェースが該当するすべてのテストを返す + def interface_tests + integration_tests << "test/controllers" + end + + # 指定のリソースに対応するコントローラのテストを返す + def controller_test(resource) + "test/controllers/#{resource}_controller_test.rb" + end + + # 指定のリソースに対応するすべてのテストを返す + def resource_tests(resource) + integration_tests(resource) << controller_test(resource) + end diff --git a/8_0/ch05/LICENSE b/8_0/ch05/LICENSE new file mode 100644 index 00000000..20622e0c --- /dev/null +++ b/8_0/ch05/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 GitHub & 2023 YassLab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/8_0/ch05/README.md b/8_0/ch05/README.md new file mode 100644 index 00000000..17ca733d --- /dev/null +++ b/8_0/ch05/README.md @@ -0,0 +1,35 @@ +# Ruby on Rails チュートリアルのサンプルアプリケーション + +これは、次の教材で作られたサンプルアプリケーションです。 +[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +(第8版) +[Michael Hartl](https://www.michaelhartl.com/) 著 + +## ライセンス + +[Ruby on Rails チュートリアル](https://railstutorial.jp/)内にある +ソースコードはMITライセンスとBeerwareライセンスのもとで公開されています。 +詳細は [LICENSE.md](LICENSE.md) をご覧ください。 + +## 使い方 + +このアプリケーションを動かす場合は、まずはリポジトリをフォークしてください。 + +フォークしたリポジトリで、「Code」から「Codespaces」タブに移動し、 +「Create codespace on main」をクリックすると環境構築がスタートします。 +Railsサーバーが立ち上がり、シンプルブラウザが表示されるまでしばらくお待ちください。 + +次に、データベースへのマイグレーションを実行します。 + +``` +$ rails db:migrate +``` + +最後に、テストを実行してうまく動いているかどうか確認してください。 + +``` +$ rails test +``` + +詳しくは、[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +を参考にしてください。 diff --git a/8_0/ch05/Rakefile b/8_0/ch05/Rakefile new file mode 100644 index 00000000..9a5ea738 --- /dev/null +++ b/8_0/ch05/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/8_0/ch05/app/assets/config/manifest.js b/8_0/ch05/app/assets/config/manifest.js new file mode 100644 index 00000000..ddd546a0 --- /dev/null +++ b/8_0/ch05/app/assets/config/manifest.js @@ -0,0 +1,4 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js diff --git a/8_0/ch05/app/assets/images/.keep b/8_0/ch05/app/assets/images/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/app/assets/images/rails.svg b/8_0/ch05/app/assets/images/rails.svg new file mode 100644 index 00000000..e1971850 --- /dev/null +++ b/8_0/ch05/app/assets/images/rails.svg @@ -0,0 +1,35 @@ + + + +rails-logo + + + + + + + + + + + + + + + + + + + + + diff --git a/8_0/ch05/app/assets/stylesheets/application.css b/8_0/ch05/app/assets/stylesheets/application.css new file mode 100644 index 00000000..288b9ab7 --- /dev/null +++ b/8_0/ch05/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/8_0/ch05/app/assets/stylesheets/custom.scss b/8_0/ch05/app/assets/stylesheets/custom.scss new file mode 100644 index 00000000..c60a3d48 --- /dev/null +++ b/8_0/ch05/app/assets/stylesheets/custom.scss @@ -0,0 +1,98 @@ +@import "bootstrap-sprockets"; +@import "bootstrap"; + +/* mixins, variables, etc. */ + +$gray-medium-light: #eaeaea; + +/* universal */ + +body { + padding-top: 60px; +} + +section { + overflow: auto; +} + +textarea { + resize: vertical; +} + +.center { + text-align: center; + h1 { + margin-bottom: 10px; + } +} + +/* typography */ + +h1, h2, h3, h4, h5, h6 { + line-height: 1; +} + +h1 { + font-size: 3em; + letter-spacing: -2px; + margin-bottom: 30px; + text-align: center; +} + +h2 { + font-size: 1.2em; + letter-spacing: -1px; + margin-bottom: 30px; + text-align: center; + font-weight: normal; + color: $gray-light; +} + +p { + font-size: 1.1em; + line-height: 1.7em; +} + + +/* header */ + +#logo { + float: left; + margin-right: 10px; + font-size: 1.7em; + color: white; + text-transform: uppercase; + letter-spacing: -1px; + padding-top: 9px; + font-weight: bold; + &:hover { + color: white; + text-decoration: none; + } +} + +/* footer */ + +footer { + margin-top: 45px; + padding-top: 5px; + border-top: 1px solid $gray-medium-light; + color: $gray-light; + a { + color: $gray; + &:hover { + color: $gray-darker; + } + } + small { + float: left; + } + ul { + float: right; + list-style: none; + li { + float: left; + margin-left: 15px; + } + } +} diff --git a/8_0/ch05/app/assets/stylesheets/hello.css b/8_0/ch05/app/assets/stylesheets/hello.css new file mode 100644 index 00000000..497881a4 --- /dev/null +++ b/8_0/ch05/app/assets/stylesheets/hello.css @@ -0,0 +1,40 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.codespaces { + text-align: center; +} +.codespaces code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} +.codespaces-logo { + height: 40vmin; + pointer-events: none; +} +.codespaces-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} +.codespaces-link { + color: #61dafb; +} + +.heart { + color: #ff0000; +} +.small { + font-size: 0.75rem; +} diff --git a/8_0/ch05/app/channels/application_cable/channel.rb b/8_0/ch05/app/channels/application_cable/channel.rb new file mode 100644 index 00000000..d6726972 --- /dev/null +++ b/8_0/ch05/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/8_0/ch05/app/channels/application_cable/connection.rb b/8_0/ch05/app/channels/application_cable/connection.rb new file mode 100644 index 00000000..0ff5442f --- /dev/null +++ b/8_0/ch05/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/8_0/ch05/app/controllers/application_controller.rb b/8_0/ch05/app/controllers/application_controller.rb new file mode 100644 index 00000000..09705d12 --- /dev/null +++ b/8_0/ch05/app/controllers/application_controller.rb @@ -0,0 +1,2 @@ +class ApplicationController < ActionController::Base +end diff --git a/8_0/ch05/app/controllers/concerns/.keep b/8_0/ch05/app/controllers/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/app/controllers/hello_controller.rb b/8_0/ch05/app/controllers/hello_controller.rb new file mode 100644 index 00000000..52037537 --- /dev/null +++ b/8_0/ch05/app/controllers/hello_controller.rb @@ -0,0 +1,4 @@ +class HelloController < ApplicationController + def index + end +end diff --git a/8_0/ch05/app/controllers/static_pages_controller.rb b/8_0/ch05/app/controllers/static_pages_controller.rb new file mode 100644 index 00000000..0673ad5e --- /dev/null +++ b/8_0/ch05/app/controllers/static_pages_controller.rb @@ -0,0 +1,14 @@ +class StaticPagesController < ApplicationController + + def home + end + + def help + end + + def about + end + + def contact + end +end diff --git a/8_0/ch05/app/controllers/users_controller.rb b/8_0/ch05/app/controllers/users_controller.rb new file mode 100644 index 00000000..2ec9ecca --- /dev/null +++ b/8_0/ch05/app/controllers/users_controller.rb @@ -0,0 +1,4 @@ +class UsersController < ApplicationController + def new + end +end diff --git a/8_0/ch05/app/helpers/application_helper.rb b/8_0/ch05/app/helpers/application_helper.rb new file mode 100644 index 00000000..708e4e48 --- /dev/null +++ b/8_0/ch05/app/helpers/application_helper.rb @@ -0,0 +1,12 @@ +module ApplicationHelper + + # ページごとの完全なタイトルを返します。 # コメント行 + def full_title(page_title = '') # メソッド定義とオプション引数 + base_title = "Ruby on Rails Tutorial Sample App" # 変数への代入 + if page_title.empty? # 論理値テスト + base_title # 暗黙の戻り値 + else + "#{page_title} | #{base_title}" # 文字列の結合 + end + end +end diff --git a/8_0/ch05/app/helpers/hello_codespaces_helper.rb b/8_0/ch05/app/helpers/hello_codespaces_helper.rb new file mode 100644 index 00000000..874a2ca2 --- /dev/null +++ b/8_0/ch05/app/helpers/hello_codespaces_helper.rb @@ -0,0 +1,2 @@ +module HelloCodespacesHelper +end diff --git a/8_0/ch05/app/helpers/static_pages_helper.rb b/8_0/ch05/app/helpers/static_pages_helper.rb new file mode 100644 index 00000000..2d63e79e --- /dev/null +++ b/8_0/ch05/app/helpers/static_pages_helper.rb @@ -0,0 +1,2 @@ +module StaticPagesHelper +end diff --git a/8_0/ch05/app/helpers/users_helper.rb b/8_0/ch05/app/helpers/users_helper.rb new file mode 100644 index 00000000..2310a240 --- /dev/null +++ b/8_0/ch05/app/helpers/users_helper.rb @@ -0,0 +1,2 @@ +module UsersHelper +end diff --git a/8_0/ch05/app/javascript/application.js b/8_0/ch05/app/javascript/application.js new file mode 100644 index 00000000..0d7b4940 --- /dev/null +++ b/8_0/ch05/app/javascript/application.js @@ -0,0 +1,3 @@ +// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails +import "@hotwired/turbo-rails" +import "controllers" diff --git a/8_0/ch05/app/javascript/controllers/application.js b/8_0/ch05/app/javascript/controllers/application.js new file mode 100644 index 00000000..1213e85c --- /dev/null +++ b/8_0/ch05/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/8_0/ch05/app/javascript/controllers/hello_controller.js b/8_0/ch05/app/javascript/controllers/hello_controller.js new file mode 100644 index 00000000..5975c078 --- /dev/null +++ b/8_0/ch05/app/javascript/controllers/hello_controller.js @@ -0,0 +1,7 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.element.textContent = "Hello World!" + } +} diff --git a/8_0/ch05/app/javascript/controllers/index.js b/8_0/ch05/app/javascript/controllers/index.js new file mode 100644 index 00000000..54ad4cad --- /dev/null +++ b/8_0/ch05/app/javascript/controllers/index.js @@ -0,0 +1,11 @@ +// Import and register all your controllers from the importmap under controllers/* + +import { application } from "controllers/application" + +// Eager load all controllers defined in the import map under controllers/**/*_controller +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) + +// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) +// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" +// lazyLoadControllersFrom("controllers", application) diff --git a/8_0/ch05/app/jobs/application_job.rb b/8_0/ch05/app/jobs/application_job.rb new file mode 100644 index 00000000..d394c3d1 --- /dev/null +++ b/8_0/ch05/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/8_0/ch05/app/mailers/application_mailer.rb b/8_0/ch05/app/mailers/application_mailer.rb new file mode 100644 index 00000000..3c34c814 --- /dev/null +++ b/8_0/ch05/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "from@example.com" + layout "mailer" +end diff --git a/8_0/ch05/app/models/application_record.rb b/8_0/ch05/app/models/application_record.rb new file mode 100644 index 00000000..b63caeb8 --- /dev/null +++ b/8_0/ch05/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/8_0/ch05/app/models/concerns/.keep b/8_0/ch05/app/models/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/app/views/hello/index.html.erb b/8_0/ch05/app/views/hello/index.html.erb new file mode 100644 index 00000000..71661105 --- /dev/null +++ b/8_0/ch05/app/views/hello/index.html.erb @@ -0,0 +1,16 @@ +
+
+ +

+ Codespaces + ♥️ + Railsチュートリアル +

+

+ 🎓✨
+ ロゴ画像が表示されたらセットアップ完了です! +

+
+
diff --git a/8_0/ch05/app/views/layouts/_footer.html.erb b/8_0/ch05/app/views/layouts/_footer.html.erb new file mode 100644 index 00000000..b6e31ff9 --- /dev/null +++ b/8_0/ch05/app/views/layouts/_footer.html.erb @@ -0,0 +1,13 @@ + diff --git a/8_0/ch05/app/views/layouts/_header.html.erb b/8_0/ch05/app/views/layouts/_header.html.erb new file mode 100644 index 00000000..1e481b02 --- /dev/null +++ b/8_0/ch05/app/views/layouts/_header.html.erb @@ -0,0 +1,12 @@ + diff --git a/8_0/ch05/app/views/layouts/application.html.erb b/8_0/ch05/app/views/layouts/application.html.erb new file mode 100644 index 00000000..e935ac3d --- /dev/null +++ b/8_0/ch05/app/views/layouts/application.html.erb @@ -0,0 +1,20 @@ + + + + <%= full_title(yield(:title)) %> + + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + + + <%= render 'layouts/header' %> +
+ <%= yield %> + <%= render 'layouts/footer' %> +
+ + diff --git a/8_0/ch05/app/views/layouts/mailer.html.erb b/8_0/ch05/app/views/layouts/mailer.html.erb new file mode 100644 index 00000000..cbd34d2e --- /dev/null +++ b/8_0/ch05/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/8_0/ch05/app/views/layouts/mailer.text.erb b/8_0/ch05/app/views/layouts/mailer.text.erb new file mode 100644 index 00000000..37f0bddb --- /dev/null +++ b/8_0/ch05/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/8_0/ch05/app/views/static_pages/about.html.erb b/8_0/ch05/app/views/static_pages/about.html.erb new file mode 100644 index 00000000..dce8fef2 --- /dev/null +++ b/8_0/ch05/app/views/static_pages/about.html.erb @@ -0,0 +1,10 @@ +<% provide(:title, "About") %> +

About

+

+ Ruby on Rails Tutorial + is a book and + screencast + to teach web development with + Ruby on Rails. + This is the sample application for the tutorial. +

diff --git a/8_0/ch05/app/views/static_pages/contact.html.erb b/8_0/ch05/app/views/static_pages/contact.html.erb new file mode 100644 index 00000000..4a5a35a6 --- /dev/null +++ b/8_0/ch05/app/views/static_pages/contact.html.erb @@ -0,0 +1,6 @@ +<% provide(:title, 'Contact') %> +

Contact

+

+ Contact the Ruby on Rails Tutorial about the sample app at the + contact page. +

diff --git a/8_0/ch05/app/views/static_pages/help.html.erb b/8_0/ch05/app/views/static_pages/help.html.erb new file mode 100644 index 00000000..4d06919d --- /dev/null +++ b/8_0/ch05/app/views/static_pages/help.html.erb @@ -0,0 +1,9 @@ +<% provide(:title, "Help") %> +

Help

+

+ Get help on the Ruby on Rails Tutorial at the + Rails Tutorial Help page. + To get help on this sample app, see the + Ruby on Rails Tutorial + book. +

diff --git a/8_0/ch05/app/views/static_pages/home.html.erb b/8_0/ch05/app/views/static_pages/home.html.erb new file mode 100644 index 00000000..f87607de --- /dev/null +++ b/8_0/ch05/app/views/static_pages/home.html.erb @@ -0,0 +1,14 @@ +
+

Welcome to the Sample App

+ +

+ This is the home page for the + Ruby on Rails Tutorial + sample application. +

+ + <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %> +
+ +<%= link_to image_tag("rails.svg", alt: "Rails logo", width: "200"), + "https://rubyonrails.org/" %> diff --git a/8_0/ch05/app/views/users/new.html.erb b/8_0/ch05/app/views/users/new.html.erb new file mode 100644 index 00000000..9c841e84 --- /dev/null +++ b/8_0/ch05/app/views/users/new.html.erb @@ -0,0 +1,3 @@ +<% provide(:title, 'Sign up') %> +

Sign up

+

This will be a signup page for new users.

diff --git a/8_0/ch05/bin/bundle b/8_0/ch05/bin/bundle new file mode 100755 index 00000000..981e650b --- /dev/null +++ b/8_0/ch05/bin/bundle @@ -0,0 +1,114 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + requirement = bundler_gem_version.approximate_recommendation + + return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0") + + requirement += ".a" if bundler_gem_version.prerelease? + + requirement + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/8_0/ch05/bin/dev b/8_0/ch05/bin/dev new file mode 100755 index 00000000..5f91c205 --- /dev/null +++ b/8_0/ch05/bin/dev @@ -0,0 +1,2 @@ +#!/usr/bin/env ruby +exec "./bin/rails", "server", *ARGV diff --git a/8_0/ch05/bin/importmap b/8_0/ch05/bin/importmap new file mode 100755 index 00000000..36502ab1 --- /dev/null +++ b/8_0/ch05/bin/importmap @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require_relative "../config/application" +require "importmap/commands" diff --git a/8_0/ch05/bin/rails b/8_0/ch05/bin/rails new file mode 100755 index 00000000..efc03774 --- /dev/null +++ b/8_0/ch05/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/8_0/ch05/bin/rake b/8_0/ch05/bin/rake new file mode 100755 index 00000000..4fbf10b9 --- /dev/null +++ b/8_0/ch05/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/8_0/ch05/bin/rubocop b/8_0/ch05/bin/rubocop new file mode 100755 index 00000000..40330c0f --- /dev/null +++ b/8_0/ch05/bin/rubocop @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +# explicit rubocop config increases performance slightly while avoiding config confusion. +ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) + +load Gem.bin_path("rubocop", "rubocop") diff --git a/8_0/ch05/bin/setup b/8_0/ch05/bin/setup new file mode 100755 index 00000000..be3db3c0 --- /dev/null +++ b/8_0/ch05/bin/setup @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +require "fileutils" + +APP_ROOT = File.expand_path("..", __dir__) + +def system!(*args) + system(*args, exception: true) +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts "== Installing dependencies ==" + system("bundle check") || system!("bundle install") + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + puts "\n== Removing old logs and tempfiles ==" + system! "bin/rails log:clear tmp:clear" + + unless ARGV.include?("--skip-server") + puts "\n== Starting development server ==" + STDOUT.flush # flush the output before exec(2) so that it displays + exec "bin/dev" + end +end diff --git a/8_0/ch05/config.ru b/8_0/ch05/config.ru new file mode 100644 index 00000000..4a3c09a6 --- /dev/null +++ b/8_0/ch05/config.ru @@ -0,0 +1,6 @@ +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/8_0/ch05/config/application.rb b/8_0/ch05/config/application.rb new file mode 100644 index 00000000..0461e72f --- /dev/null +++ b/8_0/ch05/config/application.rb @@ -0,0 +1,27 @@ +require_relative "boot" + +require "rails/all" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module SampleApp + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 8.0 + + # Please, add to the `ignore` list any other `lib` subdirectories that do + # not contain `.rb` files, or that should not be reloaded or eager loaded. + # Common ones are `templates`, `generators`, or `middleware`, for example. + config.autoload_lib(ignore: %w[assets tasks]) + + # Configuration for the application, engines, and railties goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + # + # config.time_zone = "Central Time (US & Canada)" + # config.eager_load_paths << Rails.root.join("extras") + end +end diff --git a/8_0/ch05/config/boot.rb b/8_0/ch05/config/boot.rb new file mode 100644 index 00000000..988a5ddc --- /dev/null +++ b/8_0/ch05/config/boot.rb @@ -0,0 +1,4 @@ +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "bundler/setup" # Set up gems listed in the Gemfile. +require "bootsnap/setup" # Speed up boot time by caching expensive operations. diff --git a/8_0/ch05/config/cable.yml b/8_0/ch05/config/cable.yml new file mode 100644 index 00000000..ae7a2256 --- /dev/null +++ b/8_0/ch05/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: codespaces_try_rails_production diff --git a/8_0/ch05/config/credentials.yml.enc b/8_0/ch05/config/credentials.yml.enc new file mode 100644 index 00000000..339d64d2 --- /dev/null +++ b/8_0/ch05/config/credentials.yml.enc @@ -0,0 +1 @@ +NCs9M8uakg1CDYJENuGaipzymY8uG6i5gzghsy4npa3gYmGfYKPHMnEcJHCVHlM3gxDNJBteUqkRCxHQ5WQId8hzgxoQCL5RX7rtQ8XUzkJwgSUjKvbCrG5atjKkN9XuL91is5vtEN5W7vEz3qxN7Rp33QbNFiDfLs71F3zHVDYEMi6Dy1QMhVCa1v/tyRjBu/5m37RuiYF5FzWJnh4LhexSVr7Agm4LRLLN6qLCpoAe6D3TPHHBdtu7Pvy8jlrEfnnkUJ61wrj8+kOL4uLtpFShdYKhDkoTjQk7TCOgWyifnHAXazkBZoJZ1QYv/tZ9/ZoHvMEQ2dtGHlyTX8fbKtI13d2CFjGaDNKvRuurxE8dbeyiTfbycvve46+cz9OTlmhh0SGt24R1WV6GBKoDUeoHrIiBvW5qmKka--dQ0rodxmfgFLg2y/--t2x0gQjh8FYlk8v79vLSNg== \ No newline at end of file diff --git a/8_0/ch05/config/database.yml b/8_0/ch05/config/database.yml new file mode 100644 index 00000000..fcba57f1 --- /dev/null +++ b/8_0/ch05/config/database.yml @@ -0,0 +1,25 @@ +# SQLite. Versions 3.8.0 and up are supported. +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem "sqlite3" +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/8_0/ch05/config/environment.rb b/8_0/ch05/config/environment.rb new file mode 100644 index 00000000..cac53157 --- /dev/null +++ b/8_0/ch05/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/8_0/ch05/config/environments/development.rb b/8_0/ch05/config/environments/development.rb new file mode 100644 index 00000000..910cef99 --- /dev/null +++ b/8_0/ch05/config/environments/development.rb @@ -0,0 +1,86 @@ +require 'active_support/core_ext/integer/time' + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Make code changes take effect immediately without server restart. + config.enable_reloading = true + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing. + config.server_timing = true + + # Add the following line to disable forgery_protection_origin_check + config.action_controller.forgery_protection_origin_check = false + + # Enable/disable Action Controller caching. By default Action Controller caching is disabled. + # Run rails dev:cache to toggle Action Controller caching. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + config.public_file_server.headers = { 'cache-control' => "public, max-age=#{2.days.to_i}" } + else + config.action_controller.perform_caching = false + end + + # Change to :null_store to avoid any caching. + config.cache_store = :memory_store + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Make template changes take effect immediately. + config.action_mailer.perform_caching = false + + # Set localhost to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Append comments with runtime information tags to SQL queries in logs. + config.active_record.query_log_tags_enabled = true + + # Highlight code that enqueued background job in logs. + config.active_job.verbose_enqueue_logs = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true + + # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. + # config.generators.apply_rubocop_autocorrect_after_generate! + + pf_domain = ENV['GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN'] + config.action_dispatch.default_headers = { + 'X-Frame-Options' => "ALLOW-FROM #{pf_domain}" + } + + # Allow requests from our preview domain. + pf_host = "#{ENV['CODESPACE_NAME']}-3000.#{pf_domain}" + config.hosts << pf_host + + config.action_cable.allowed_request_origins = ["https://#{pf_host}"] +end diff --git a/8_0/ch05/config/environments/production.rb b/8_0/ch05/config/environments/production.rb new file mode 100644 index 00000000..17496077 --- /dev/null +++ b/8_0/ch05/config/environments/production.rb @@ -0,0 +1,89 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.enable_reloading = false + + # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). + config.eager_load = true + + # Full error reports are disabled. + config.consider_all_requests_local = false + + # Turn on fragment caching in view templates. + config.action_controller.perform_caching = true + + # Cache assets for far-future expiry since they are all digest stamped. + config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" } + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = "http://assets.example.com" + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Assume all access to the app is happening through a SSL-terminating reverse proxy. + config.assume_ssl = true + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = true + + # Skip http-to-https redirect for the default health check endpoint. + # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } + + # Log to STDOUT with the current request id as a default log tag. + config.log_tags = [ :request_id ] + config.logger = ActiveSupport::TaggedLogging.logger(STDOUT) + + # Change to "debug" to log everything (including potentially personally-identifiable information!) + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + + # Prevent health checks from clogging up the logs. + config.silence_healthcheck_path = "/up" + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Replace the default in-process memory cache store with a durable alternative. + # config.cache_store = :mem_cache_store + + # Replace the default in-process and non-durable queuing backend for Active Job. + # config.active_job.queue_adapter = :resque + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. + # config.action_mailer.smtp_settings = { + # user_name: Rails.application.credentials.dig(:smtp, :user_name), + # password: Rails.application.credentials.dig(:smtp, :password), + # address: "smtp.example.com", + # port: 587, + # authentication: :plain + # } + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Only use :id for inspections in production. + config.active_record.attributes_for_inspect = [ :id ] + + # Enable DNS rebinding protection and other `Host` header attacks. + # config.hosts = [ + # "example.com", # Allow requests from example.com + # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` + # ] + # + # Skip DNS rebinding protection for the default health check endpoint. + # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } +end diff --git a/8_0/ch05/config/environments/test.rb b/8_0/ch05/config/environments/test.rb new file mode 100644 index 00000000..0ebe3b4e --- /dev/null +++ b/8_0/ch05/config/environments/test.rb @@ -0,0 +1,53 @@ +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # While tests run files are not watched, reloading is not necessary. + config.enable_reloading = false + + # Eager loading loads your entire application. When running a single test locally, + # this is usually not necessary, and can slow down your test suite. However, it's + # recommended that you enable it in continuous integration systems to ensure eager + # loading is working properly before deploying your code. + config.eager_load = ENV["CI"].present? + + # Configure public file server for tests with cache-control for performance. + config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } + + # Show full error reports. + config.consider_all_requests_local = true + config.cache_store = :null_store + + # Render exception templates for rescuable exceptions and raise for other exceptions. + config.action_dispatch.show_exceptions = :none + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :test + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true +end diff --git a/8_0/ch05/config/importmap.rb b/8_0/ch05/config/importmap.rb new file mode 100644 index 00000000..8dce42d4 --- /dev/null +++ b/8_0/ch05/config/importmap.rb @@ -0,0 +1,7 @@ +# Pin npm packages by running ./bin/importmap + +pin "application", preload: true +pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true +pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true +pin_all_from "app/javascript/controllers", under: "controllers" diff --git a/8_0/ch05/config/initializers/assets.rb b/8_0/ch05/config/initializers/assets.rb new file mode 100644 index 00000000..48732442 --- /dev/null +++ b/8_0/ch05/config/initializers/assets.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = "1.0" + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path diff --git a/8_0/ch05/config/initializers/content_security_policy.rb b/8_0/ch05/config/initializers/content_security_policy.rb new file mode 100644 index 00000000..b3076b38 --- /dev/null +++ b/8_0/ch05/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header + +# Rails.application.configure do +# config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end +# +# # Generate session nonces for permitted importmap, inline scripts, and inline styles. +# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } +# config.content_security_policy_nonce_directives = %w(script-src style-src) +# +# # Report violations without enforcing the policy. +# # config.content_security_policy_report_only = true +# end diff --git a/8_0/ch05/config/initializers/filter_parameter_logging.rb b/8_0/ch05/config/initializers/filter_parameter_logging.rb new file mode 100644 index 00000000..c0b717f7 --- /dev/null +++ b/8_0/ch05/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. +# Use this to limit dissemination of sensitive information. +# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. +Rails.application.config.filter_parameters += [ + :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc +] diff --git a/8_0/ch05/config/initializers/inflections.rb b/8_0/ch05/config/initializers/inflections.rb new file mode 100644 index 00000000..3860f659 --- /dev/null +++ b/8_0/ch05/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym "RESTful" +# end diff --git a/8_0/ch05/config/initializers/new_framework_defaults_8_0.rb b/8_0/ch05/config/initializers/new_framework_defaults_8_0.rb new file mode 100644 index 00000000..92efa951 --- /dev/null +++ b/8_0/ch05/config/initializers/new_framework_defaults_8_0.rb @@ -0,0 +1,30 @@ +# Be sure to restart your server when you modify this file. +# +# This file eases your Rails 8.0 framework defaults upgrade. +# +# Uncomment each configuration one by one to switch to the new default. +# Once your application is ready to run with all new defaults, you can remove +# this file and set the `config.load_defaults` to `8.0`. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. +# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html + +### +# Specifies whether `to_time` methods preserve the UTC offset of their receivers or preserves the timezone. +# If set to `:zone`, `to_time` methods will use the timezone of their receivers. +# If set to `:offset`, `to_time` methods will use the UTC offset. +# If `false`, `to_time` methods will convert to the local system UTC offset instead. +#++ +# Rails.application.config.active_support.to_time_preserves_timezone = :zone + +### +# When both `If-Modified-Since` and `If-None-Match` are provided by the client +# only consider `If-None-Match` as specified by RFC 7232 Section 6. +# If set to `false` both conditions need to be satisfied. +#++ +# Rails.application.config.action_dispatch.strict_freshness = true + +### +# Set `Regexp.timeout` to `1`s by default to improve security over Regexp Denial-of-Service attacks. +#++ +# Regexp.timeout = 1 diff --git a/8_0/ch05/config/initializers/permissions_policy.rb b/8_0/ch05/config/initializers/permissions_policy.rb new file mode 100644 index 00000000..00f64d71 --- /dev/null +++ b/8_0/ch05/config/initializers/permissions_policy.rb @@ -0,0 +1,11 @@ +# Define an application-wide HTTP permissions policy. For further +# information see https://developers.google.com/web/updates/2018/06/feature-policy +# +# Rails.application.config.permissions_policy do |f| +# f.camera :none +# f.gyroscope :none +# f.microphone :none +# f.usb :none +# f.fullscreen :self +# f.payment :self, "https://secure.example.com" +# end diff --git a/8_0/ch05/config/locales/en.yml b/8_0/ch05/config/locales/en.yml new file mode 100644 index 00000000..8ca56fc7 --- /dev/null +++ b/8_0/ch05/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# "true": "foo" +# +# To learn more, please read the Rails Internationalization guide +# available at https://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/8_0/ch05/config/puma.rb b/8_0/ch05/config/puma.rb new file mode 100644 index 00000000..a248513b --- /dev/null +++ b/8_0/ch05/config/puma.rb @@ -0,0 +1,41 @@ +# This configuration file will be evaluated by Puma. The top-level methods that +# are invoked here are part of Puma's configuration DSL. For more information +# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. +# +# Puma starts a configurable number of processes (workers) and each process +# serves each request in a thread from an internal thread pool. +# +# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You +# should only set this value when you want to run 2 or more workers. The +# default is already 1. +# +# The ideal number of threads per worker depends both on how much time the +# application spends waiting for IO operations and on how much you wish to +# prioritize throughput over latency. +# +# As a rule of thumb, increasing the number of threads will increase how much +# traffic a given process can handle (throughput), but due to CRuby's +# Global VM Lock (GVL) it has diminishing returns and will degrade the +# response time (latency) of the application. +# +# The default is set to 3 threads as it's deemed a decent compromise between +# throughput and latency for the average Rails application. +# +# Any libraries that use a connection pool or another resource pool should +# be configured to provide at least as many connections as the number of +# threads. This includes Active Record's `pool` parameter in `database.yml`. +threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +port ENV.fetch("PORT", 3000) + +# Allow puma to be restarted by `bin/rails restart` command. +plugin :tmp_restart + +# Run the Solid Queue supervisor inside of Puma for single-server deployments +plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] + +# Specify the PID file. Defaults to tmp/pids/server.pid in development. +# In other environments, only set the PID file if requested. +pidfile ENV["PIDFILE"] if ENV["PIDFILE"] diff --git a/8_0/ch05/config/routes.rb b/8_0/ch05/config/routes.rb new file mode 100644 index 00000000..c6255a01 --- /dev/null +++ b/8_0/ch05/config/routes.rb @@ -0,0 +1,7 @@ +Rails.application.routes.draw do + root "static_pages#home" + get "/help", to: "static_pages#help" + get "/about", to: "static_pages#about" + get "/contact", to: "static_pages#contact" + get "/signup", to: "users#new" +end diff --git a/8_0/ch05/config/storage.yml b/8_0/ch05/config/storage.yml new file mode 100644 index 00000000..4942ab66 --- /dev/null +++ b/8_0/ch05/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket-<%= Rails.env %> + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket-<%= Rails.env %> + +# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name-<%= Rails.env %> + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/8_0/ch05/db/seeds.rb b/8_0/ch05/db/seeds.rb new file mode 100644 index 00000000..bc25fce3 --- /dev/null +++ b/8_0/ch05/db/seeds.rb @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). +# +# Examples: +# +# movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }]) +# Character.create(name: "Luke", movie: movies.first) diff --git a/8_0/ch05/lib/assets/.keep b/8_0/ch05/lib/assets/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/lib/tasks/.keep b/8_0/ch05/lib/tasks/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/log/.keep b/8_0/ch05/log/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/public/400.html b/8_0/ch05/public/400.html new file mode 100644 index 00000000..282dbc8c --- /dev/null +++ b/8_0/ch05/public/400.html @@ -0,0 +1,114 @@ + + + + + + + The server cannot process the request due to a client error (400 Bad Request) + + + + + + + + + + + + + +
+
+ +
+
+

The server cannot process the request due to a client error. Please check the request and try again. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch05/public/404.html b/8_0/ch05/public/404.html new file mode 100644 index 00000000..c0670bc8 --- /dev/null +++ b/8_0/ch05/public/404.html @@ -0,0 +1,114 @@ + + + + + + + The page you were looking for doesn’t exist (404 Not found) + + + + + + + + + + + + + +
+
+ +
+
+

The page you were looking for doesn’t exist. You may have mistyped the address or the page may have moved. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch05/public/406-unsupported-browser.html b/8_0/ch05/public/406-unsupported-browser.html new file mode 100644 index 00000000..9532a9cc --- /dev/null +++ b/8_0/ch05/public/406-unsupported-browser.html @@ -0,0 +1,114 @@ + + + + + + + Your browser is not supported (406 Not Acceptable) + + + + + + + + + + + + + +
+
+ +
+
+

Your browser is not supported.
Please upgrade your browser to continue.

+
+
+ + + + diff --git a/8_0/ch05/public/422.html b/8_0/ch05/public/422.html new file mode 100644 index 00000000..8bcf0601 --- /dev/null +++ b/8_0/ch05/public/422.html @@ -0,0 +1,114 @@ + + + + + + + The change you wanted was rejected (422 Unprocessable Entity) + + + + + + + + + + + + + +
+
+ +
+
+

The change you wanted was rejected. Maybe you tried to change something you didn’t have access to. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch05/public/500.html b/8_0/ch05/public/500.html new file mode 100644 index 00000000..d77718c3 --- /dev/null +++ b/8_0/ch05/public/500.html @@ -0,0 +1,114 @@ + + + + + + + We’re sorry, but something went wrong (500 Internal Server Error) + + + + + + + + + + + + + +
+
+ +
+
+

We’re sorry, but something went wrong.
If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch05/public/apple-touch-icon-precomposed.png b/8_0/ch05/public/apple-touch-icon-precomposed.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/public/apple-touch-icon.png b/8_0/ch05/public/apple-touch-icon.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/public/favicon.ico b/8_0/ch05/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/public/icon.png b/8_0/ch05/public/icon.png new file mode 100644 index 00000000..c4c9dbfb Binary files /dev/null and b/8_0/ch05/public/icon.png differ diff --git a/8_0/ch05/public/icon.svg b/8_0/ch05/public/icon.svg new file mode 100644 index 00000000..04b34bf8 --- /dev/null +++ b/8_0/ch05/public/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/8_0/ch05/public/railstutorial.png b/8_0/ch05/public/railstutorial.png new file mode 100644 index 00000000..70835e6b Binary files /dev/null and b/8_0/ch05/public/railstutorial.png differ diff --git a/8_0/ch05/public/robots.txt b/8_0/ch05/public/robots.txt new file mode 100644 index 00000000..c19f78ab --- /dev/null +++ b/8_0/ch05/public/robots.txt @@ -0,0 +1 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/8_0/ch05/storage/.keep b/8_0/ch05/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/test/application_system_test_case.rb b/8_0/ch05/test/application_system_test_case.rb new file mode 100644 index 00000000..d19212ab --- /dev/null +++ b/8_0/ch05/test/application_system_test_case.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/8_0/ch05/test/channels/application_cable/connection_test.rb b/8_0/ch05/test/channels/application_cable/connection_test.rb new file mode 100644 index 00000000..800405f1 --- /dev/null +++ b/8_0/ch05/test/channels/application_cable/connection_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase + # test "connects with cookies" do + # cookies.signed[:user_id] = 42 + # + # connect + # + # assert_equal connection.user_id, "42" + # end +end diff --git a/8_0/ch05/test/controllers/.keep b/8_0/ch05/test/controllers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/test/controllers/hello_codespaces_controller_test.rb b/8_0/ch05/test/controllers/hello_codespaces_controller_test.rb new file mode 100644 index 00000000..54353ea6 --- /dev/null +++ b/8_0/ch05/test/controllers/hello_codespaces_controller_test.rb @@ -0,0 +1,8 @@ +require "test_helper" + +class HelloCodespacesControllerTest < ActionDispatch::IntegrationTest + # test "should get index" do + # get "/" + # assert_response :success + # end +end diff --git a/8_0/ch05/test/controllers/static_pages_controller_test.rb b/8_0/ch05/test/controllers/static_pages_controller_test.rb new file mode 100644 index 00000000..847856a3 --- /dev/null +++ b/8_0/ch05/test/controllers/static_pages_controller_test.rb @@ -0,0 +1,28 @@ +require "test_helper" + +class StaticPagesControllerTest < ActionDispatch::IntegrationTest + + test "should get home" do + get root_path + assert_response :success + assert_select "title", "Ruby on Rails Tutorial Sample App" + end + + test "should get help" do + get help_path + assert_response :success + assert_select "title", "Help | Ruby on Rails Tutorial Sample App" + end + + test "should get about" do + get about_path + assert_response :success + assert_select "title", "About | Ruby on Rails Tutorial Sample App" + end + + test "should get contact" do + get contact_path + assert_response :success + assert_select "title", "Contact | Ruby on Rails Tutorial Sample App" + end +end diff --git a/8_0/ch05/test/controllers/users_controller_test.rb b/8_0/ch05/test/controllers/users_controller_test.rb new file mode 100644 index 00000000..2b123196 --- /dev/null +++ b/8_0/ch05/test/controllers/users_controller_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +class UsersControllerTest < ActionDispatch::IntegrationTest + + test "should get new" do + get signup_path + assert_response :success + end +end diff --git a/8_0/ch05/test/fixtures/files/.keep b/8_0/ch05/test/fixtures/files/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/test/helpers/.keep b/8_0/ch05/test/helpers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/test/integration/.keep b/8_0/ch05/test/integration/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/test/integration/site_layout_test.rb b/8_0/ch05/test/integration/site_layout_test.rb new file mode 100644 index 00000000..0f7ca649 --- /dev/null +++ b/8_0/ch05/test/integration/site_layout_test.rb @@ -0,0 +1,13 @@ +require "test_helper" + +class SiteLayoutTest < ActionDispatch::IntegrationTest + + test "layout links" do + get root_path + assert_template 'static_pages/home' + assert_select "a[href=?]", root_path, count: 2 + assert_select "a[href=?]", help_path + assert_select "a[href=?]", about_path + assert_select "a[href=?]", contact_path + end +end diff --git a/8_0/ch05/test/mailers/.keep b/8_0/ch05/test/mailers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/test/models/.keep b/8_0/ch05/test/models/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/test/system/.keep b/8_0/ch05/test/system/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/test/test_helper.rb b/8_0/ch05/test/test_helper.rb new file mode 100644 index 00000000..f566ca61 --- /dev/null +++ b/8_0/ch05/test/test_helper.rb @@ -0,0 +1,15 @@ +ENV["RAILS_ENV"] ||= "test" +require_relative "../config/environment" +require "rails/test_help" +require "minitest/reporters" +Minitest::Reporters.use! + +class ActiveSupport::TestCase + # 指定のワーカー数でテストを並列実行する + parallelize(workers: :number_of_processors) + + # test/fixtures/*.ymlにあるすべてのfixtureをセットアップする + fixtures :all + + # (すべてのテストで使うその他のヘルパーメソッドは省略) +end diff --git a/8_0/ch05/tmp/.keep b/8_0/ch05/tmp/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/tmp/pids/.keep b/8_0/ch05/tmp/pids/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/tmp/storage/.keep b/8_0/ch05/tmp/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/vendor/.keep b/8_0/ch05/vendor/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch05/vendor/javascript/.keep b/8_0/ch05/vendor/javascript/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/.devcontainer/devcontainer.json b/8_0/ch06/.devcontainer/devcontainer.json new file mode 100644 index 00000000..e7243217 --- /dev/null +++ b/8_0/ch06/.devcontainer/devcontainer.json @@ -0,0 +1,105 @@ +// Specifications: https://containers.dev/implementors/json_reference/ +// Format details: https://aka.ms/devcontainer.json +// Config options: https://github.com/microsoft/vscode-dev-containers/tree/main/containers/ruby +{ + "name": "railstutorial", + + // Universal is well-customized image for Codespaces: + // https://hub.docker.com/_/microsoft-devcontainers-universal + //"image": "mcr.microsoft.com/devcontainers/universal:2", + //"features": { + // "ghcr.io/devcontainers/features/ruby:1": {} + //}, + + // Use Ruby image if you want to pin Ruby version like '3.2' + // https://github.com/devcontainers/images/tree/main/src/ruby + "image": "mcr.microsoft.com/devcontainers/ruby:3.2", + //"workspaceFolder": "/railstutorial", => Container build failed + + // Enable learners to choose an affordable spec, starting at minimum one. + //"hostRequirements": { + // "cpus": 2, + // "memory": "4gb", + // "storage": "32gb" + //}, + + "waitFor": "onCreateCommand", + //"onCreateCommand": "", + "onCreateCommand": "gem install solargraph -v '0.58.2' -N", + //"onCreateCommand": "gem install solargraph -v '0.50.0' -N && gem install ruby-lsp -N", + //"onCreateCommand": "gem install ruby-lsp -N", + //# => Solargraph gem not found. Run `gem install solargraph` or update your Gemfile. + "updateContentCommand": "bundle install", + "postCreateCommand": "", + "postAttachCommand": { + "server": "rails server" + }, + "customizations": { + "codespaces": { + "openFiles": [ + "app/views/hello/index.html.erb" + ] + }, + "vscode": { + "extensions": [ + "GitHub.codespaces", + "Shopify.ruby-lsp", // https://github.com/Shopify/ruby-lsp + "castwide.solargraph" // https://github.com/castwide/vscode-solargraph + //"rebornix.Ruby", // https://github.com/rubyide/vscode-ruby (Deprecated) + + ], + "settings": { + // General settings for Codespaces (VS Code) + //"ruby.useLanguageServer": true , + //"ruby.format": "rubocop", + //"ruby.lint": { "rubocop": true }, + //"ruby.intellisense": "rubyLocate", + "editor.tabSize": 2, + "editor.formatOnSave": false, // Disable onSave to show diff edited by learners only + "editor.formatOnType": false, // Disable onType for the same reason above + "editor.insertSpaces": true, // Use spaces, not tabs, to avoid errors for learners + "editor.renderWhitespace": "none", + "[ruby]": { + "editor.defaultFormatter": "castwide.solargraph", + "editor.semanticHighlighting.enabled": true // Enable semantic highlighting + }, + "files.associations": { "*.erb": "erb" }, + "emmet.includeLanguages": { "erb": "html" }, + + // Settings for Solargraph + // https://github.com/castwide/solargraph + "solargraph.useBundler": false, + "solargraph.diagnostics": false, + "solargraph.formatting": true, // Use Ctrl+Shift+P->Format to format + "solargraph.autoformat": false, + "solargraph.definitions": true, + "solargraph.completion": true, + "solargraph.references": true, + "solargraph.symbols": true, + "solargraph.rename": true, + "solargraph.hover": true, + + // Settings for Ruby LSP + // https://github.com/Shopify/ruby-lsp + "rubyLsp.rubyVersionManager": { + "identifier": "none" + }, + "rubyLsp.formatter": "none", + "rubyLsp.enabledFeatures": { + "diagnostics": false, + "formatting": false + } + } + } + }, + "remoteEnv": { + "EDITOR": "code --wait" + }, + "portsAttributes": { + "3000": { + "label": "Application", + "onAutoForward": "openPreview" + } + }, + "forwardPorts": [3000] +} diff --git a/8_0/ch06/.devcontainer/icon.svg b/8_0/ch06/.devcontainer/icon.svg new file mode 100644 index 00000000..f8444a4a --- /dev/null +++ b/8_0/ch06/.devcontainer/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/8_0/ch06/.gitattributes b/8_0/ch06/.gitattributes new file mode 100644 index 00000000..31eeee0b --- /dev/null +++ b/8_0/ch06/.gitattributes @@ -0,0 +1,7 @@ +# See https://git-scm.com/docs/gitattributes for more about git attribute files. + +# Mark the database schema as having been generated. +db/schema.rb linguist-generated + +# Mark any vendored files as having been vendored. +vendor/* linguist-vendored diff --git a/8_0/ch06/.gitignore b/8_0/ch06/.gitignore new file mode 100644 index 00000000..e1ef53ef --- /dev/null +++ b/8_0/ch06/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-* + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore uploaded files in development. +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/ +!/tmp/storage/.keep + +/public/assets + +# Ignore master key for decrypting credentials and more. +/config/master.key + +# Ignore history file used by IRB for interactive Ruby. +.irb_history + +dump.rdb diff --git a/8_0/ch06/.irbrc b/8_0/ch06/.irbrc new file mode 100644 index 00000000..3737e036 --- /dev/null +++ b/8_0/ch06/.irbrc @@ -0,0 +1 @@ +IRB.conf[:COMPLETOR] = :type # default is :regexp diff --git a/8_0/ch06/.solargraph.yml b/8_0/ch06/.solargraph.yml new file mode 100644 index 00000000..76ac24a6 --- /dev/null +++ b/8_0/ch06/.solargraph.yml @@ -0,0 +1,32 @@ +--- +include: +- "**/*.rb" +exclude: +- spec/**/* +- test/**/* +- vendor/**/* +- ".bundle/**/*" +require: +- actioncable +- actionmailer +- actionpack +- actionview +- activejob +- activemodel +- activerecord +- activestorage +- activesupport +domains: [] +reporters: +- rubocop +- require_not_found +- typecheck +formatter: + rubocop: + cops: safe + except: [] + only: [] + extra_args: [] +require_paths: [] +plugins: [] +max_files: 5000 diff --git a/8_0/ch06/.vscode/settings.json b/8_0/ch06/.vscode/settings.json new file mode 100644 index 00000000..cf85f519 --- /dev/null +++ b/8_0/ch06/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.minimap.enabled": false +} diff --git a/8_0/ch06/Gemfile b/8_0/ch06/Gemfile new file mode 100644 index 00000000..a015b466 --- /dev/null +++ b/8_0/ch06/Gemfile @@ -0,0 +1,44 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '3.2.10' + +gem 'bcrypt', '3.1.18' +gem 'bootsnap', '1.16.0', require: false +gem 'bootstrap-sass', '3.4.1' +gem 'concurrent-ruby', '1.3.4' +gem 'importmap-rails', '1.1.5' +gem 'jbuilder', '2.14.1' +gem 'puma', '6.6.1' +gem 'rails', '8.0.2.1' +gem 'sassc-rails', '2.1.2' +gem 'sprockets-rails', '3.4.2' +gem 'sqlite3', '2.7.3' +gem 'stimulus-rails', '1.2.1' +gem 'turbo-rails', '1.4.0' + +group :development, :test do + gem 'debug', '1.7.1', platforms: %i[mri mingw x64_mingw] + gem 'reline', '0.5.10' +end + +group :development do + gem 'irb', '1.15.2' + gem 'repl_type_completor', '0.1.10' + gem 'solargraph', '0.58.2' + gem 'web-console', '4.2.0' +end + +group :test do + gem 'capybara', '3.38.0' + gem 'guard', '2.18.0' + gem 'guard-minitest', '2.4.6' + gem 'minitest', '5.18.0' + gem 'minitest-reporters', '1.6.0' + gem 'rails-controller-testing', '1.0.5' + gem 'selenium-webdriver', '4.8.3' + gem 'webdrivers', '5.2.0' +end + +# Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります +# gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] diff --git a/8_0/ch06/Gemfile.lock b/8_0/ch06/Gemfile.lock new file mode 100644 index 00000000..119a783d --- /dev/null +++ b/8_0/ch06/Gemfile.lock @@ -0,0 +1,442 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + actionmailer (8.0.2.1) + actionpack (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.0.2.1) + actionview (= 8.0.2.1) + activesupport (= 8.0.2.1) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.0.2.1) + actionpack (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (8.0.2.1) + activesupport (= 8.0.2.1) + builder (~> 3.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.3.6) + activemodel (8.0.2.1) + activesupport (= 8.0.2.1) + activerecord (8.0.2.1) + activemodel (= 8.0.2.1) + activesupport (= 8.0.2.1) + timeout (>= 0.4.0) + activestorage (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activesupport (= 8.0.2.1) + marcel (~> 1.0) + activesupport (8.0.2.1) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + ansi (1.5.0) + ast (2.4.3) + autoprefixer-rails (10.4.21.0) + execjs (~> 2) + backport (1.2.0) + base64 (0.3.0) + bcrypt (3.1.18) + benchmark (0.4.1) + bigdecimal (3.2.3) + bindex (0.8.1) + bootsnap (1.16.0) + msgpack (~> 1.2) + bootstrap-sass (3.4.1) + autoprefixer-rails (>= 5.2.1) + sassc (>= 2.0.0) + builder (3.3.0) + capybara (3.38.0) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + coderay (1.1.3) + concurrent-ruby (1.3.4) + connection_pool (2.5.4) + crass (1.0.6) + date (3.4.1) + debug (1.7.1) + irb (>= 1.5.0) + reline (>= 0.3.1) + diff-lcs (1.6.2) + drb (2.2.3) + erb (5.0.2) + erubi (1.13.1) + execjs (2.10.1) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) + formatador (1.2.0) + reline + globalid (1.2.1) + activesupport (>= 6.1) + guard (2.18.0) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.13.0) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-minitest (2.4.6) + guard-compat (~> 1.2) + minitest (>= 3.0) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + importmap-rails (1.1.5) + actionpack (>= 6.0.0) + railties (>= 6.0.0) + io-console (0.8.1) + irb (1.15.2) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jaro_winkler (1.6.1) + jbuilder (2.14.1) + actionview (>= 7.0.0) + activesupport (>= 7.0.0) + json (2.13.2) + kramdown (2.5.1) + rexml (>= 3.3.9) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) + loofah (2.24.1) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + lumberjack (1.4.1) + mail (2.8.1) + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.0.4) + matrix (0.4.3) + method_source (1.1.0) + mini_mime (1.1.5) + minitest (5.18.0) + minitest-reporters (1.6.0) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + msgpack (1.8.0) + nenv (0.3.0) + net-imap (0.5.10) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.4) + nokogiri (1.19.1-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-musl) + racc (~> 1.4) + notiffany (0.1.3) + nenv (~> 0.1) + shellany (~> 0.0) + observer (0.1.2) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.9.0) + ast (~> 2.4.1) + racc + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + prism (1.4.0) + pry (0.15.2) + coderay (~> 1.1) + method_source (~> 1.0) + psych (5.2.6) + date + stringio + public_suffix (6.0.2) + puma (6.6.1) + nio4r (~> 2.0) + racc (1.8.1) + rack (3.2.5) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.2.1) + rack (>= 3) + rails (8.0.2.1) + actioncable (= 8.0.2.1) + actionmailbox (= 8.0.2.1) + actionmailer (= 8.0.2.1) + actionpack (= 8.0.2.1) + actiontext (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activemodel (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + bundler (>= 1.15.0) + railties (= 8.0.2.1) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.0) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rbs (3.6.1) + logger + rdoc (6.14.2) + erb + psych (>= 4.0.0) + regexp_parser (2.11.2) + reline (0.5.10) + io-console (~> 0.5) + repl_type_completor (0.1.10) + prism (~> 1.0) + rbs (>= 2.7.0, < 4.0.0) + reverse_markdown (3.0.0) + nokogiri + rexml (3.4.2) + rubocop (1.80.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.46.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.46.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + ruby-progressbar (1.13.0) + rubyzip (2.4.1) + sassc (2.4.0) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt + securerandom (0.4.1) + selenium-webdriver (4.8.3) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) + shellany (0.0.1) + solargraph (0.58.2) + backport (~> 1.2) + benchmark (~> 0.4) + bundler (~> 2.0) + diff-lcs (~> 1.4) + jaro_winkler (~> 1.6, >= 1.6.1) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.1) + logger (~> 1.6) + observer (~> 0.1) + ostruct (~> 0.6) + parser (~> 3.0) + prism (~> 1.4) + rbs (~> 3.6.1) + reverse_markdown (~> 3.0) + rubocop (~> 1.38) + thor (~> 1.0) + tilt (~> 2.0) + yard (~> 0.9, >= 0.9.24) + yard-solargraph (~> 0.1) + sprockets (4.2.2) + concurrent-ruby (~> 1.0) + logger + rack (>= 2.2.4, < 4) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + sprockets (>= 3.0.0) + sqlite3 (2.7.3-aarch64-linux-gnu) + sqlite3 (2.7.3-aarch64-linux-musl) + sqlite3 (2.7.3-arm-linux-gnu) + sqlite3 (2.7.3-arm-linux-musl) + sqlite3 (2.7.3-arm64-darwin) + sqlite3 (2.7.3-x86_64-darwin) + sqlite3 (2.7.3-x86_64-linux-gnu) + sqlite3 (2.7.3-x86_64-linux-musl) + stimulus-rails (1.2.1) + railties (>= 6.0.0) + stringio (3.1.7) + thor (1.4.0) + tilt (2.6.1) + timeout (0.4.3) + turbo-rails (1.4.0) + actionpack (>= 6.0.0) + activejob (>= 6.0.0) + railties (>= 6.0.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.1.5) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + uri (1.0.4) + useragent (0.16.11) + web-console (4.2.0) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + webdrivers (5.2.0) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (~> 4.0) + websocket (1.2.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) + yard (0.9.37) + yard-solargraph (0.1.0) + yard (~> 0.9) + zeitwerk (2.7.3) + +PLATFORMS + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + bcrypt (= 3.1.18) + bootsnap (= 1.16.0) + bootstrap-sass (= 3.4.1) + capybara (= 3.38.0) + concurrent-ruby (= 1.3.4) + debug (= 1.7.1) + guard (= 2.18.0) + guard-minitest (= 2.4.6) + importmap-rails (= 1.1.5) + irb (= 1.15.2) + jbuilder (= 2.14.1) + minitest (= 5.18.0) + minitest-reporters (= 1.6.0) + puma (= 6.6.1) + rails (= 8.0.2.1) + rails-controller-testing (= 1.0.5) + reline (= 0.5.10) + repl_type_completor (= 0.1.10) + sassc-rails (= 2.1.2) + selenium-webdriver (= 4.8.3) + solargraph (= 0.58.2) + sprockets-rails (= 3.4.2) + sqlite3 (= 2.7.3) + stimulus-rails (= 1.2.1) + turbo-rails (= 1.4.0) + web-console (= 4.2.0) + webdrivers (= 5.2.0) + +RUBY VERSION + ruby 3.2.10p266 + +BUNDLED WITH + 2.5.6 diff --git a/8_0/ch06/Guardfile b/8_0/ch06/Guardfile new file mode 100644 index 00000000..5def34a0 --- /dev/null +++ b/8_0/ch06/Guardfile @@ -0,0 +1,76 @@ + require "active_support/inflector" + # Guardのマッチング規則を定義 + guard :minitest, all_on_start: false do + watch(%r{^test/(.*)/?(.*)_test\.rb$}) + watch('test/test_helper.rb') { 'test' } + watch('config/routes.rb') { interface_tests } + watch(%r{app/views/layouts/*}) { interface_tests } + watch(%r{^app/models/(.*?)\.rb$}) do |matches| + ["test/models/#{matches[1]}_test.rb", + "test/integration/microposts_interface_test.rb"] + end + watch(%r{^test/fixtures/(.*?)\.yml$}) do |matches| + "test/models/#{matches[1].singularize}_test.rb" + end + watch(%r{^app/mailers/(.*?)\.rb$}) do |matches| + "test/mailers/#{matches[1]}_test.rb" + end + watch(%r{^app/views/(.*)_mailer/.*$}) do |matches| + "test/mailers/#{matches[1]}_mailer_test.rb" + end + watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches| + resource_tests(matches[1]) + end + watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches| + ["test/controllers/#{matches[1]}_controller_test.rb"] + + integration_tests(matches[1]) + end + watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches| + integration_tests(matches[1]) + end + watch('app/views/layouts/application.html.erb') do + 'test/integration/site_layout_test.rb' + end + watch('app/helpers/sessions_helper.rb') do + integration_tests << 'test/helpers/sessions_helper_test.rb' + end + watch('app/controllers/sessions_controller.rb') do + ['test/controllers/sessions_controller_test.rb', + 'test/integration/users_login_test.rb'] + end + watch('app/controllers/account_activations_controller.rb') do + 'test/integration/users_signup_test.rb' + end + watch(%r{app/views/users/*}) do + resource_tests('users') + + ['test/integration/microposts_interface_test.rb'] + end + watch('app/controllers/relationships_controller.rb') do + ['test/controllers/relationships_controller_test.rb', + 'test/integration/following_test.rb'] + end + end + + # 指定のリソースに対応する統合テストを返す + def integration_tests(resource = :all) + if resource == :all + Dir["test/integration/*"] + else + Dir["test/integration/#{resource}_*.rb"] + end + end + + # インターフェースが該当するすべてのテストを返す + def interface_tests + integration_tests << "test/controllers" + end + + # 指定のリソースに対応するコントローラのテストを返す + def controller_test(resource) + "test/controllers/#{resource}_controller_test.rb" + end + + # 指定のリソースに対応するすべてのテストを返す + def resource_tests(resource) + integration_tests(resource) << controller_test(resource) + end diff --git a/8_0/ch06/LICENSE b/8_0/ch06/LICENSE new file mode 100644 index 00000000..20622e0c --- /dev/null +++ b/8_0/ch06/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 GitHub & 2023 YassLab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/8_0/ch06/README.md b/8_0/ch06/README.md new file mode 100644 index 00000000..17ca733d --- /dev/null +++ b/8_0/ch06/README.md @@ -0,0 +1,35 @@ +# Ruby on Rails チュートリアルのサンプルアプリケーション + +これは、次の教材で作られたサンプルアプリケーションです。 +[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +(第8版) +[Michael Hartl](https://www.michaelhartl.com/) 著 + +## ライセンス + +[Ruby on Rails チュートリアル](https://railstutorial.jp/)内にある +ソースコードはMITライセンスとBeerwareライセンスのもとで公開されています。 +詳細は [LICENSE.md](LICENSE.md) をご覧ください。 + +## 使い方 + +このアプリケーションを動かす場合は、まずはリポジトリをフォークしてください。 + +フォークしたリポジトリで、「Code」から「Codespaces」タブに移動し、 +「Create codespace on main」をクリックすると環境構築がスタートします。 +Railsサーバーが立ち上がり、シンプルブラウザが表示されるまでしばらくお待ちください。 + +次に、データベースへのマイグレーションを実行します。 + +``` +$ rails db:migrate +``` + +最後に、テストを実行してうまく動いているかどうか確認してください。 + +``` +$ rails test +``` + +詳しくは、[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +を参考にしてください。 diff --git a/8_0/ch06/Rakefile b/8_0/ch06/Rakefile new file mode 100644 index 00000000..9a5ea738 --- /dev/null +++ b/8_0/ch06/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/8_0/ch06/app/assets/config/manifest.js b/8_0/ch06/app/assets/config/manifest.js new file mode 100644 index 00000000..ddd546a0 --- /dev/null +++ b/8_0/ch06/app/assets/config/manifest.js @@ -0,0 +1,4 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js diff --git a/8_0/ch06/app/assets/images/.keep b/8_0/ch06/app/assets/images/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/app/assets/images/rails.svg b/8_0/ch06/app/assets/images/rails.svg new file mode 100644 index 00000000..e1971850 --- /dev/null +++ b/8_0/ch06/app/assets/images/rails.svg @@ -0,0 +1,35 @@ + + + +rails-logo + + + + + + + + + + + + + + + + + + + + + diff --git a/8_0/ch06/app/assets/stylesheets/application.css b/8_0/ch06/app/assets/stylesheets/application.css new file mode 100644 index 00000000..288b9ab7 --- /dev/null +++ b/8_0/ch06/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/8_0/ch06/app/assets/stylesheets/custom.scss b/8_0/ch06/app/assets/stylesheets/custom.scss new file mode 100644 index 00000000..c60a3d48 --- /dev/null +++ b/8_0/ch06/app/assets/stylesheets/custom.scss @@ -0,0 +1,98 @@ +@import "bootstrap-sprockets"; +@import "bootstrap"; + +/* mixins, variables, etc. */ + +$gray-medium-light: #eaeaea; + +/* universal */ + +body { + padding-top: 60px; +} + +section { + overflow: auto; +} + +textarea { + resize: vertical; +} + +.center { + text-align: center; + h1 { + margin-bottom: 10px; + } +} + +/* typography */ + +h1, h2, h3, h4, h5, h6 { + line-height: 1; +} + +h1 { + font-size: 3em; + letter-spacing: -2px; + margin-bottom: 30px; + text-align: center; +} + +h2 { + font-size: 1.2em; + letter-spacing: -1px; + margin-bottom: 30px; + text-align: center; + font-weight: normal; + color: $gray-light; +} + +p { + font-size: 1.1em; + line-height: 1.7em; +} + + +/* header */ + +#logo { + float: left; + margin-right: 10px; + font-size: 1.7em; + color: white; + text-transform: uppercase; + letter-spacing: -1px; + padding-top: 9px; + font-weight: bold; + &:hover { + color: white; + text-decoration: none; + } +} + +/* footer */ + +footer { + margin-top: 45px; + padding-top: 5px; + border-top: 1px solid $gray-medium-light; + color: $gray-light; + a { + color: $gray; + &:hover { + color: $gray-darker; + } + } + small { + float: left; + } + ul { + float: right; + list-style: none; + li { + float: left; + margin-left: 15px; + } + } +} diff --git a/8_0/ch06/app/assets/stylesheets/hello.css b/8_0/ch06/app/assets/stylesheets/hello.css new file mode 100644 index 00000000..497881a4 --- /dev/null +++ b/8_0/ch06/app/assets/stylesheets/hello.css @@ -0,0 +1,40 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.codespaces { + text-align: center; +} +.codespaces code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} +.codespaces-logo { + height: 40vmin; + pointer-events: none; +} +.codespaces-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} +.codespaces-link { + color: #61dafb; +} + +.heart { + color: #ff0000; +} +.small { + font-size: 0.75rem; +} diff --git a/8_0/ch06/app/channels/application_cable/channel.rb b/8_0/ch06/app/channels/application_cable/channel.rb new file mode 100644 index 00000000..d6726972 --- /dev/null +++ b/8_0/ch06/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/8_0/ch06/app/channels/application_cable/connection.rb b/8_0/ch06/app/channels/application_cable/connection.rb new file mode 100644 index 00000000..0ff5442f --- /dev/null +++ b/8_0/ch06/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/8_0/ch06/app/controllers/application_controller.rb b/8_0/ch06/app/controllers/application_controller.rb new file mode 100644 index 00000000..09705d12 --- /dev/null +++ b/8_0/ch06/app/controllers/application_controller.rb @@ -0,0 +1,2 @@ +class ApplicationController < ActionController::Base +end diff --git a/8_0/ch06/app/controllers/concerns/.keep b/8_0/ch06/app/controllers/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/app/controllers/hello_controller.rb b/8_0/ch06/app/controllers/hello_controller.rb new file mode 100644 index 00000000..52037537 --- /dev/null +++ b/8_0/ch06/app/controllers/hello_controller.rb @@ -0,0 +1,4 @@ +class HelloController < ApplicationController + def index + end +end diff --git a/8_0/ch06/app/controllers/static_pages_controller.rb b/8_0/ch06/app/controllers/static_pages_controller.rb new file mode 100644 index 00000000..0673ad5e --- /dev/null +++ b/8_0/ch06/app/controllers/static_pages_controller.rb @@ -0,0 +1,14 @@ +class StaticPagesController < ApplicationController + + def home + end + + def help + end + + def about + end + + def contact + end +end diff --git a/8_0/ch06/app/controllers/users_controller.rb b/8_0/ch06/app/controllers/users_controller.rb new file mode 100644 index 00000000..2ec9ecca --- /dev/null +++ b/8_0/ch06/app/controllers/users_controller.rb @@ -0,0 +1,4 @@ +class UsersController < ApplicationController + def new + end +end diff --git a/8_0/ch06/app/helpers/application_helper.rb b/8_0/ch06/app/helpers/application_helper.rb new file mode 100644 index 00000000..708e4e48 --- /dev/null +++ b/8_0/ch06/app/helpers/application_helper.rb @@ -0,0 +1,12 @@ +module ApplicationHelper + + # ページごとの完全なタイトルを返します。 # コメント行 + def full_title(page_title = '') # メソッド定義とオプション引数 + base_title = "Ruby on Rails Tutorial Sample App" # 変数への代入 + if page_title.empty? # 論理値テスト + base_title # 暗黙の戻り値 + else + "#{page_title} | #{base_title}" # 文字列の結合 + end + end +end diff --git a/8_0/ch06/app/helpers/hello_codespaces_helper.rb b/8_0/ch06/app/helpers/hello_codespaces_helper.rb new file mode 100644 index 00000000..874a2ca2 --- /dev/null +++ b/8_0/ch06/app/helpers/hello_codespaces_helper.rb @@ -0,0 +1,2 @@ +module HelloCodespacesHelper +end diff --git a/8_0/ch06/app/helpers/static_pages_helper.rb b/8_0/ch06/app/helpers/static_pages_helper.rb new file mode 100644 index 00000000..2d63e79e --- /dev/null +++ b/8_0/ch06/app/helpers/static_pages_helper.rb @@ -0,0 +1,2 @@ +module StaticPagesHelper +end diff --git a/8_0/ch06/app/helpers/users_helper.rb b/8_0/ch06/app/helpers/users_helper.rb new file mode 100644 index 00000000..2310a240 --- /dev/null +++ b/8_0/ch06/app/helpers/users_helper.rb @@ -0,0 +1,2 @@ +module UsersHelper +end diff --git a/8_0/ch06/app/javascript/application.js b/8_0/ch06/app/javascript/application.js new file mode 100644 index 00000000..0d7b4940 --- /dev/null +++ b/8_0/ch06/app/javascript/application.js @@ -0,0 +1,3 @@ +// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails +import "@hotwired/turbo-rails" +import "controllers" diff --git a/8_0/ch06/app/javascript/controllers/application.js b/8_0/ch06/app/javascript/controllers/application.js new file mode 100644 index 00000000..1213e85c --- /dev/null +++ b/8_0/ch06/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/8_0/ch06/app/javascript/controllers/hello_controller.js b/8_0/ch06/app/javascript/controllers/hello_controller.js new file mode 100644 index 00000000..5975c078 --- /dev/null +++ b/8_0/ch06/app/javascript/controllers/hello_controller.js @@ -0,0 +1,7 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.element.textContent = "Hello World!" + } +} diff --git a/8_0/ch06/app/javascript/controllers/index.js b/8_0/ch06/app/javascript/controllers/index.js new file mode 100644 index 00000000..54ad4cad --- /dev/null +++ b/8_0/ch06/app/javascript/controllers/index.js @@ -0,0 +1,11 @@ +// Import and register all your controllers from the importmap under controllers/* + +import { application } from "controllers/application" + +// Eager load all controllers defined in the import map under controllers/**/*_controller +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) + +// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) +// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" +// lazyLoadControllersFrom("controllers", application) diff --git a/8_0/ch06/app/jobs/application_job.rb b/8_0/ch06/app/jobs/application_job.rb new file mode 100644 index 00000000..d394c3d1 --- /dev/null +++ b/8_0/ch06/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/8_0/ch06/app/mailers/application_mailer.rb b/8_0/ch06/app/mailers/application_mailer.rb new file mode 100644 index 00000000..3c34c814 --- /dev/null +++ b/8_0/ch06/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "from@example.com" + layout "mailer" +end diff --git a/8_0/ch06/app/models/application_record.rb b/8_0/ch06/app/models/application_record.rb new file mode 100644 index 00000000..b63caeb8 --- /dev/null +++ b/8_0/ch06/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/8_0/ch06/app/models/concerns/.keep b/8_0/ch06/app/models/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/app/models/user.rb b/8_0/ch06/app/models/user.rb new file mode 100644 index 00000000..6550627b --- /dev/null +++ b/8_0/ch06/app/models/user.rb @@ -0,0 +1,10 @@ +class User < ApplicationRecord + before_save { self.email = email.downcase } + validates :name, presence: true, length: { maximum: 50 } + VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i + validates :email, presence: true, length: { maximum: 255 }, + format: { with: VALID_EMAIL_REGEX }, + uniqueness: true + has_secure_password + validates :password, presence: true, length: { minimum: 6 } +end diff --git a/8_0/ch06/app/views/hello/index.html.erb b/8_0/ch06/app/views/hello/index.html.erb new file mode 100644 index 00000000..71661105 --- /dev/null +++ b/8_0/ch06/app/views/hello/index.html.erb @@ -0,0 +1,16 @@ +
+
+ +

+ Codespaces + ♥️ + Railsチュートリアル +

+

+ 🎓✨
+ ロゴ画像が表示されたらセットアップ完了です! +

+
+
diff --git a/8_0/ch06/app/views/layouts/_footer.html.erb b/8_0/ch06/app/views/layouts/_footer.html.erb new file mode 100644 index 00000000..b6e31ff9 --- /dev/null +++ b/8_0/ch06/app/views/layouts/_footer.html.erb @@ -0,0 +1,13 @@ + diff --git a/8_0/ch06/app/views/layouts/_header.html.erb b/8_0/ch06/app/views/layouts/_header.html.erb new file mode 100644 index 00000000..1e481b02 --- /dev/null +++ b/8_0/ch06/app/views/layouts/_header.html.erb @@ -0,0 +1,12 @@ + diff --git a/8_0/ch06/app/views/layouts/application.html.erb b/8_0/ch06/app/views/layouts/application.html.erb new file mode 100644 index 00000000..e935ac3d --- /dev/null +++ b/8_0/ch06/app/views/layouts/application.html.erb @@ -0,0 +1,20 @@ + + + + <%= full_title(yield(:title)) %> + + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + + + <%= render 'layouts/header' %> +
+ <%= yield %> + <%= render 'layouts/footer' %> +
+ + diff --git a/8_0/ch06/app/views/layouts/mailer.html.erb b/8_0/ch06/app/views/layouts/mailer.html.erb new file mode 100644 index 00000000..cbd34d2e --- /dev/null +++ b/8_0/ch06/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/8_0/ch06/app/views/layouts/mailer.text.erb b/8_0/ch06/app/views/layouts/mailer.text.erb new file mode 100644 index 00000000..37f0bddb --- /dev/null +++ b/8_0/ch06/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/8_0/ch06/app/views/static_pages/about.html.erb b/8_0/ch06/app/views/static_pages/about.html.erb new file mode 100644 index 00000000..dce8fef2 --- /dev/null +++ b/8_0/ch06/app/views/static_pages/about.html.erb @@ -0,0 +1,10 @@ +<% provide(:title, "About") %> +

About

+

+ Ruby on Rails Tutorial + is a book and + screencast + to teach web development with + Ruby on Rails. + This is the sample application for the tutorial. +

diff --git a/8_0/ch06/app/views/static_pages/contact.html.erb b/8_0/ch06/app/views/static_pages/contact.html.erb new file mode 100644 index 00000000..4a5a35a6 --- /dev/null +++ b/8_0/ch06/app/views/static_pages/contact.html.erb @@ -0,0 +1,6 @@ +<% provide(:title, 'Contact') %> +

Contact

+

+ Contact the Ruby on Rails Tutorial about the sample app at the + contact page. +

diff --git a/8_0/ch06/app/views/static_pages/help.html.erb b/8_0/ch06/app/views/static_pages/help.html.erb new file mode 100644 index 00000000..4d06919d --- /dev/null +++ b/8_0/ch06/app/views/static_pages/help.html.erb @@ -0,0 +1,9 @@ +<% provide(:title, "Help") %> +

Help

+

+ Get help on the Ruby on Rails Tutorial at the + Rails Tutorial Help page. + To get help on this sample app, see the + Ruby on Rails Tutorial + book. +

diff --git a/8_0/ch06/app/views/static_pages/home.html.erb b/8_0/ch06/app/views/static_pages/home.html.erb new file mode 100644 index 00000000..f87607de --- /dev/null +++ b/8_0/ch06/app/views/static_pages/home.html.erb @@ -0,0 +1,14 @@ +
+

Welcome to the Sample App

+ +

+ This is the home page for the + Ruby on Rails Tutorial + sample application. +

+ + <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %> +
+ +<%= link_to image_tag("rails.svg", alt: "Rails logo", width: "200"), + "https://rubyonrails.org/" %> diff --git a/8_0/ch06/app/views/users/new.html.erb b/8_0/ch06/app/views/users/new.html.erb new file mode 100644 index 00000000..9c841e84 --- /dev/null +++ b/8_0/ch06/app/views/users/new.html.erb @@ -0,0 +1,3 @@ +<% provide(:title, 'Sign up') %> +

Sign up

+

This will be a signup page for new users.

diff --git a/8_0/ch06/bin/bundle b/8_0/ch06/bin/bundle new file mode 100755 index 00000000..981e650b --- /dev/null +++ b/8_0/ch06/bin/bundle @@ -0,0 +1,114 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + requirement = bundler_gem_version.approximate_recommendation + + return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0") + + requirement += ".a" if bundler_gem_version.prerelease? + + requirement + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/8_0/ch06/bin/dev b/8_0/ch06/bin/dev new file mode 100755 index 00000000..5f91c205 --- /dev/null +++ b/8_0/ch06/bin/dev @@ -0,0 +1,2 @@ +#!/usr/bin/env ruby +exec "./bin/rails", "server", *ARGV diff --git a/8_0/ch06/bin/importmap b/8_0/ch06/bin/importmap new file mode 100755 index 00000000..36502ab1 --- /dev/null +++ b/8_0/ch06/bin/importmap @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require_relative "../config/application" +require "importmap/commands" diff --git a/8_0/ch06/bin/rails b/8_0/ch06/bin/rails new file mode 100755 index 00000000..efc03774 --- /dev/null +++ b/8_0/ch06/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/8_0/ch06/bin/rake b/8_0/ch06/bin/rake new file mode 100755 index 00000000..4fbf10b9 --- /dev/null +++ b/8_0/ch06/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/8_0/ch06/bin/render-build.sh b/8_0/ch06/bin/render-build.sh new file mode 100755 index 00000000..6a61cb7f --- /dev/null +++ b/8_0/ch06/bin/render-build.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# exit on error +set -o errexit +bundle install +bundle exec rails assets:precompile +bundle exec rails assets:clean +bundle exec rails db:migrate diff --git a/8_0/ch06/bin/rubocop b/8_0/ch06/bin/rubocop new file mode 100755 index 00000000..40330c0f --- /dev/null +++ b/8_0/ch06/bin/rubocop @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +# explicit rubocop config increases performance slightly while avoiding config confusion. +ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) + +load Gem.bin_path("rubocop", "rubocop") diff --git a/8_0/ch06/bin/setup b/8_0/ch06/bin/setup new file mode 100755 index 00000000..be3db3c0 --- /dev/null +++ b/8_0/ch06/bin/setup @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +require "fileutils" + +APP_ROOT = File.expand_path("..", __dir__) + +def system!(*args) + system(*args, exception: true) +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts "== Installing dependencies ==" + system("bundle check") || system!("bundle install") + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + puts "\n== Removing old logs and tempfiles ==" + system! "bin/rails log:clear tmp:clear" + + unless ARGV.include?("--skip-server") + puts "\n== Starting development server ==" + STDOUT.flush # flush the output before exec(2) so that it displays + exec "bin/dev" + end +end diff --git a/8_0/ch06/config.ru b/8_0/ch06/config.ru new file mode 100644 index 00000000..4a3c09a6 --- /dev/null +++ b/8_0/ch06/config.ru @@ -0,0 +1,6 @@ +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/8_0/ch06/config/application.rb b/8_0/ch06/config/application.rb new file mode 100644 index 00000000..0461e72f --- /dev/null +++ b/8_0/ch06/config/application.rb @@ -0,0 +1,27 @@ +require_relative "boot" + +require "rails/all" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module SampleApp + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 8.0 + + # Please, add to the `ignore` list any other `lib` subdirectories that do + # not contain `.rb` files, or that should not be reloaded or eager loaded. + # Common ones are `templates`, `generators`, or `middleware`, for example. + config.autoload_lib(ignore: %w[assets tasks]) + + # Configuration for the application, engines, and railties goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + # + # config.time_zone = "Central Time (US & Canada)" + # config.eager_load_paths << Rails.root.join("extras") + end +end diff --git a/8_0/ch06/config/boot.rb b/8_0/ch06/config/boot.rb new file mode 100644 index 00000000..988a5ddc --- /dev/null +++ b/8_0/ch06/config/boot.rb @@ -0,0 +1,4 @@ +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "bundler/setup" # Set up gems listed in the Gemfile. +require "bootsnap/setup" # Speed up boot time by caching expensive operations. diff --git a/8_0/ch06/config/cable.yml b/8_0/ch06/config/cable.yml new file mode 100644 index 00000000..ae7a2256 --- /dev/null +++ b/8_0/ch06/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: codespaces_try_rails_production diff --git a/8_0/ch06/config/credentials.yml.enc b/8_0/ch06/config/credentials.yml.enc new file mode 100644 index 00000000..339d64d2 --- /dev/null +++ b/8_0/ch06/config/credentials.yml.enc @@ -0,0 +1 @@ +NCs9M8uakg1CDYJENuGaipzymY8uG6i5gzghsy4npa3gYmGfYKPHMnEcJHCVHlM3gxDNJBteUqkRCxHQ5WQId8hzgxoQCL5RX7rtQ8XUzkJwgSUjKvbCrG5atjKkN9XuL91is5vtEN5W7vEz3qxN7Rp33QbNFiDfLs71F3zHVDYEMi6Dy1QMhVCa1v/tyRjBu/5m37RuiYF5FzWJnh4LhexSVr7Agm4LRLLN6qLCpoAe6D3TPHHBdtu7Pvy8jlrEfnnkUJ61wrj8+kOL4uLtpFShdYKhDkoTjQk7TCOgWyifnHAXazkBZoJZ1QYv/tZ9/ZoHvMEQ2dtGHlyTX8fbKtI13d2CFjGaDNKvRuurxE8dbeyiTfbycvve46+cz9OTlmhh0SGt24R1WV6GBKoDUeoHrIiBvW5qmKka--dQ0rodxmfgFLg2y/--t2x0gQjh8FYlk8v79vLSNg== \ No newline at end of file diff --git a/8_0/ch06/config/database.yml b/8_0/ch06/config/database.yml new file mode 100644 index 00000000..fcba57f1 --- /dev/null +++ b/8_0/ch06/config/database.yml @@ -0,0 +1,25 @@ +# SQLite. Versions 3.8.0 and up are supported. +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem "sqlite3" +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/8_0/ch06/config/environment.rb b/8_0/ch06/config/environment.rb new file mode 100644 index 00000000..cac53157 --- /dev/null +++ b/8_0/ch06/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/8_0/ch06/config/environments/development.rb b/8_0/ch06/config/environments/development.rb new file mode 100644 index 00000000..910cef99 --- /dev/null +++ b/8_0/ch06/config/environments/development.rb @@ -0,0 +1,86 @@ +require 'active_support/core_ext/integer/time' + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Make code changes take effect immediately without server restart. + config.enable_reloading = true + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing. + config.server_timing = true + + # Add the following line to disable forgery_protection_origin_check + config.action_controller.forgery_protection_origin_check = false + + # Enable/disable Action Controller caching. By default Action Controller caching is disabled. + # Run rails dev:cache to toggle Action Controller caching. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + config.public_file_server.headers = { 'cache-control' => "public, max-age=#{2.days.to_i}" } + else + config.action_controller.perform_caching = false + end + + # Change to :null_store to avoid any caching. + config.cache_store = :memory_store + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Make template changes take effect immediately. + config.action_mailer.perform_caching = false + + # Set localhost to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Append comments with runtime information tags to SQL queries in logs. + config.active_record.query_log_tags_enabled = true + + # Highlight code that enqueued background job in logs. + config.active_job.verbose_enqueue_logs = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true + + # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. + # config.generators.apply_rubocop_autocorrect_after_generate! + + pf_domain = ENV['GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN'] + config.action_dispatch.default_headers = { + 'X-Frame-Options' => "ALLOW-FROM #{pf_domain}" + } + + # Allow requests from our preview domain. + pf_host = "#{ENV['CODESPACE_NAME']}-3000.#{pf_domain}" + config.hosts << pf_host + + config.action_cable.allowed_request_origins = ["https://#{pf_host}"] +end diff --git a/8_0/ch06/config/environments/production.rb b/8_0/ch06/config/environments/production.rb new file mode 100644 index 00000000..17496077 --- /dev/null +++ b/8_0/ch06/config/environments/production.rb @@ -0,0 +1,89 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.enable_reloading = false + + # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). + config.eager_load = true + + # Full error reports are disabled. + config.consider_all_requests_local = false + + # Turn on fragment caching in view templates. + config.action_controller.perform_caching = true + + # Cache assets for far-future expiry since they are all digest stamped. + config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" } + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = "http://assets.example.com" + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Assume all access to the app is happening through a SSL-terminating reverse proxy. + config.assume_ssl = true + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = true + + # Skip http-to-https redirect for the default health check endpoint. + # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } + + # Log to STDOUT with the current request id as a default log tag. + config.log_tags = [ :request_id ] + config.logger = ActiveSupport::TaggedLogging.logger(STDOUT) + + # Change to "debug" to log everything (including potentially personally-identifiable information!) + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + + # Prevent health checks from clogging up the logs. + config.silence_healthcheck_path = "/up" + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Replace the default in-process memory cache store with a durable alternative. + # config.cache_store = :mem_cache_store + + # Replace the default in-process and non-durable queuing backend for Active Job. + # config.active_job.queue_adapter = :resque + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. + # config.action_mailer.smtp_settings = { + # user_name: Rails.application.credentials.dig(:smtp, :user_name), + # password: Rails.application.credentials.dig(:smtp, :password), + # address: "smtp.example.com", + # port: 587, + # authentication: :plain + # } + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Only use :id for inspections in production. + config.active_record.attributes_for_inspect = [ :id ] + + # Enable DNS rebinding protection and other `Host` header attacks. + # config.hosts = [ + # "example.com", # Allow requests from example.com + # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` + # ] + # + # Skip DNS rebinding protection for the default health check endpoint. + # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } +end diff --git a/8_0/ch06/config/environments/test.rb b/8_0/ch06/config/environments/test.rb new file mode 100644 index 00000000..0ebe3b4e --- /dev/null +++ b/8_0/ch06/config/environments/test.rb @@ -0,0 +1,53 @@ +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # While tests run files are not watched, reloading is not necessary. + config.enable_reloading = false + + # Eager loading loads your entire application. When running a single test locally, + # this is usually not necessary, and can slow down your test suite. However, it's + # recommended that you enable it in continuous integration systems to ensure eager + # loading is working properly before deploying your code. + config.eager_load = ENV["CI"].present? + + # Configure public file server for tests with cache-control for performance. + config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } + + # Show full error reports. + config.consider_all_requests_local = true + config.cache_store = :null_store + + # Render exception templates for rescuable exceptions and raise for other exceptions. + config.action_dispatch.show_exceptions = :none + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :test + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true +end diff --git a/8_0/ch06/config/importmap.rb b/8_0/ch06/config/importmap.rb new file mode 100644 index 00000000..8dce42d4 --- /dev/null +++ b/8_0/ch06/config/importmap.rb @@ -0,0 +1,7 @@ +# Pin npm packages by running ./bin/importmap + +pin "application", preload: true +pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true +pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true +pin_all_from "app/javascript/controllers", under: "controllers" diff --git a/8_0/ch06/config/initializers/assets.rb b/8_0/ch06/config/initializers/assets.rb new file mode 100644 index 00000000..48732442 --- /dev/null +++ b/8_0/ch06/config/initializers/assets.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = "1.0" + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path diff --git a/8_0/ch06/config/initializers/content_security_policy.rb b/8_0/ch06/config/initializers/content_security_policy.rb new file mode 100644 index 00000000..b3076b38 --- /dev/null +++ b/8_0/ch06/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header + +# Rails.application.configure do +# config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end +# +# # Generate session nonces for permitted importmap, inline scripts, and inline styles. +# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } +# config.content_security_policy_nonce_directives = %w(script-src style-src) +# +# # Report violations without enforcing the policy. +# # config.content_security_policy_report_only = true +# end diff --git a/8_0/ch06/config/initializers/filter_parameter_logging.rb b/8_0/ch06/config/initializers/filter_parameter_logging.rb new file mode 100644 index 00000000..c0b717f7 --- /dev/null +++ b/8_0/ch06/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. +# Use this to limit dissemination of sensitive information. +# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. +Rails.application.config.filter_parameters += [ + :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc +] diff --git a/8_0/ch06/config/initializers/inflections.rb b/8_0/ch06/config/initializers/inflections.rb new file mode 100644 index 00000000..3860f659 --- /dev/null +++ b/8_0/ch06/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym "RESTful" +# end diff --git a/8_0/ch06/config/initializers/new_framework_defaults_8_0.rb b/8_0/ch06/config/initializers/new_framework_defaults_8_0.rb new file mode 100644 index 00000000..92efa951 --- /dev/null +++ b/8_0/ch06/config/initializers/new_framework_defaults_8_0.rb @@ -0,0 +1,30 @@ +# Be sure to restart your server when you modify this file. +# +# This file eases your Rails 8.0 framework defaults upgrade. +# +# Uncomment each configuration one by one to switch to the new default. +# Once your application is ready to run with all new defaults, you can remove +# this file and set the `config.load_defaults` to `8.0`. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. +# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html + +### +# Specifies whether `to_time` methods preserve the UTC offset of their receivers or preserves the timezone. +# If set to `:zone`, `to_time` methods will use the timezone of their receivers. +# If set to `:offset`, `to_time` methods will use the UTC offset. +# If `false`, `to_time` methods will convert to the local system UTC offset instead. +#++ +# Rails.application.config.active_support.to_time_preserves_timezone = :zone + +### +# When both `If-Modified-Since` and `If-None-Match` are provided by the client +# only consider `If-None-Match` as specified by RFC 7232 Section 6. +# If set to `false` both conditions need to be satisfied. +#++ +# Rails.application.config.action_dispatch.strict_freshness = true + +### +# Set `Regexp.timeout` to `1`s by default to improve security over Regexp Denial-of-Service attacks. +#++ +# Regexp.timeout = 1 diff --git a/8_0/ch06/config/initializers/permissions_policy.rb b/8_0/ch06/config/initializers/permissions_policy.rb new file mode 100644 index 00000000..00f64d71 --- /dev/null +++ b/8_0/ch06/config/initializers/permissions_policy.rb @@ -0,0 +1,11 @@ +# Define an application-wide HTTP permissions policy. For further +# information see https://developers.google.com/web/updates/2018/06/feature-policy +# +# Rails.application.config.permissions_policy do |f| +# f.camera :none +# f.gyroscope :none +# f.microphone :none +# f.usb :none +# f.fullscreen :self +# f.payment :self, "https://secure.example.com" +# end diff --git a/8_0/ch06/config/locales/en.yml b/8_0/ch06/config/locales/en.yml new file mode 100644 index 00000000..8ca56fc7 --- /dev/null +++ b/8_0/ch06/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# "true": "foo" +# +# To learn more, please read the Rails Internationalization guide +# available at https://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/8_0/ch06/config/puma.rb b/8_0/ch06/config/puma.rb new file mode 100644 index 00000000..a248513b --- /dev/null +++ b/8_0/ch06/config/puma.rb @@ -0,0 +1,41 @@ +# This configuration file will be evaluated by Puma. The top-level methods that +# are invoked here are part of Puma's configuration DSL. For more information +# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. +# +# Puma starts a configurable number of processes (workers) and each process +# serves each request in a thread from an internal thread pool. +# +# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You +# should only set this value when you want to run 2 or more workers. The +# default is already 1. +# +# The ideal number of threads per worker depends both on how much time the +# application spends waiting for IO operations and on how much you wish to +# prioritize throughput over latency. +# +# As a rule of thumb, increasing the number of threads will increase how much +# traffic a given process can handle (throughput), but due to CRuby's +# Global VM Lock (GVL) it has diminishing returns and will degrade the +# response time (latency) of the application. +# +# The default is set to 3 threads as it's deemed a decent compromise between +# throughput and latency for the average Rails application. +# +# Any libraries that use a connection pool or another resource pool should +# be configured to provide at least as many connections as the number of +# threads. This includes Active Record's `pool` parameter in `database.yml`. +threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +port ENV.fetch("PORT", 3000) + +# Allow puma to be restarted by `bin/rails restart` command. +plugin :tmp_restart + +# Run the Solid Queue supervisor inside of Puma for single-server deployments +plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] + +# Specify the PID file. Defaults to tmp/pids/server.pid in development. +# In other environments, only set the PID file if requested. +pidfile ENV["PIDFILE"] if ENV["PIDFILE"] diff --git a/8_0/ch06/config/routes.rb b/8_0/ch06/config/routes.rb new file mode 100644 index 00000000..c6255a01 --- /dev/null +++ b/8_0/ch06/config/routes.rb @@ -0,0 +1,7 @@ +Rails.application.routes.draw do + root "static_pages#home" + get "/help", to: "static_pages#help" + get "/about", to: "static_pages#about" + get "/contact", to: "static_pages#contact" + get "/signup", to: "users#new" +end diff --git a/8_0/ch06/config/storage.yml b/8_0/ch06/config/storage.yml new file mode 100644 index 00000000..4942ab66 --- /dev/null +++ b/8_0/ch06/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket-<%= Rails.env %> + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket-<%= Rails.env %> + +# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name-<%= Rails.env %> + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/8_0/ch06/db/migrate/20260512080056_create_users.rb b/8_0/ch06/db/migrate/20260512080056_create_users.rb new file mode 100644 index 00000000..3b80eb00 --- /dev/null +++ b/8_0/ch06/db/migrate/20260512080056_create_users.rb @@ -0,0 +1,10 @@ +class CreateUsers < ActiveRecord::Migration[8.0] + def change + create_table :users do |t| + t.string :name + t.string :email + + t.timestamps + end + end +end diff --git a/8_0/ch06/db/migrate/20260512080840_add_index_to_users_email.rb b/8_0/ch06/db/migrate/20260512080840_add_index_to_users_email.rb new file mode 100644 index 00000000..a8e4ed8e --- /dev/null +++ b/8_0/ch06/db/migrate/20260512080840_add_index_to_users_email.rb @@ -0,0 +1,5 @@ +class AddIndexToUsersEmail < ActiveRecord::Migration[8.0] + def change + add_index :users, :email, unique: true + end +end diff --git a/8_0/ch06/db/migrate/20260512081511_add_password_digest_to_users.rb b/8_0/ch06/db/migrate/20260512081511_add_password_digest_to_users.rb new file mode 100644 index 00000000..81c7c9e0 --- /dev/null +++ b/8_0/ch06/db/migrate/20260512081511_add_password_digest_to_users.rb @@ -0,0 +1,5 @@ +class AddPasswordDigestToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :password_digest, :string + end +end diff --git a/8_0/ch06/db/schema.rb b/8_0/ch06/db/schema.rb new file mode 100644 index 00000000..e22275fc --- /dev/null +++ b/8_0/ch06/db/schema.rb @@ -0,0 +1,22 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[8.0].define(version: 2026_05_12_081511) do + create_table "users", force: :cascade do |t| + t.string "name" + t.string "email" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "password_digest" + t.index ["email"], name: "index_users_on_email", unique: true + end +end diff --git a/8_0/ch06/db/seeds.rb b/8_0/ch06/db/seeds.rb new file mode 100644 index 00000000..bc25fce3 --- /dev/null +++ b/8_0/ch06/db/seeds.rb @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). +# +# Examples: +# +# movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }]) +# Character.create(name: "Luke", movie: movies.first) diff --git a/8_0/ch06/lib/assets/.keep b/8_0/ch06/lib/assets/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/lib/tasks/.keep b/8_0/ch06/lib/tasks/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/log/.keep b/8_0/ch06/log/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/public/400.html b/8_0/ch06/public/400.html new file mode 100644 index 00000000..282dbc8c --- /dev/null +++ b/8_0/ch06/public/400.html @@ -0,0 +1,114 @@ + + + + + + + The server cannot process the request due to a client error (400 Bad Request) + + + + + + + + + + + + + +
+
+ +
+
+

The server cannot process the request due to a client error. Please check the request and try again. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch06/public/404.html b/8_0/ch06/public/404.html new file mode 100644 index 00000000..c0670bc8 --- /dev/null +++ b/8_0/ch06/public/404.html @@ -0,0 +1,114 @@ + + + + + + + The page you were looking for doesn’t exist (404 Not found) + + + + + + + + + + + + + +
+
+ +
+
+

The page you were looking for doesn’t exist. You may have mistyped the address or the page may have moved. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch06/public/406-unsupported-browser.html b/8_0/ch06/public/406-unsupported-browser.html new file mode 100644 index 00000000..9532a9cc --- /dev/null +++ b/8_0/ch06/public/406-unsupported-browser.html @@ -0,0 +1,114 @@ + + + + + + + Your browser is not supported (406 Not Acceptable) + + + + + + + + + + + + + +
+
+ +
+
+

Your browser is not supported.
Please upgrade your browser to continue.

+
+
+ + + + diff --git a/8_0/ch06/public/422.html b/8_0/ch06/public/422.html new file mode 100644 index 00000000..8bcf0601 --- /dev/null +++ b/8_0/ch06/public/422.html @@ -0,0 +1,114 @@ + + + + + + + The change you wanted was rejected (422 Unprocessable Entity) + + + + + + + + + + + + + +
+
+ +
+
+

The change you wanted was rejected. Maybe you tried to change something you didn’t have access to. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch06/public/500.html b/8_0/ch06/public/500.html new file mode 100644 index 00000000..d77718c3 --- /dev/null +++ b/8_0/ch06/public/500.html @@ -0,0 +1,114 @@ + + + + + + + We’re sorry, but something went wrong (500 Internal Server Error) + + + + + + + + + + + + + +
+
+ +
+
+

We’re sorry, but something went wrong.
If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch06/public/apple-touch-icon-precomposed.png b/8_0/ch06/public/apple-touch-icon-precomposed.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/public/apple-touch-icon.png b/8_0/ch06/public/apple-touch-icon.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/public/favicon.ico b/8_0/ch06/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/public/icon.png b/8_0/ch06/public/icon.png new file mode 100644 index 00000000..c4c9dbfb Binary files /dev/null and b/8_0/ch06/public/icon.png differ diff --git a/8_0/ch06/public/icon.svg b/8_0/ch06/public/icon.svg new file mode 100644 index 00000000..04b34bf8 --- /dev/null +++ b/8_0/ch06/public/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/8_0/ch06/public/railstutorial.png b/8_0/ch06/public/railstutorial.png new file mode 100644 index 00000000..70835e6b Binary files /dev/null and b/8_0/ch06/public/railstutorial.png differ diff --git a/8_0/ch06/public/robots.txt b/8_0/ch06/public/robots.txt new file mode 100644 index 00000000..c19f78ab --- /dev/null +++ b/8_0/ch06/public/robots.txt @@ -0,0 +1 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/8_0/ch06/storage/.keep b/8_0/ch06/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/test/application_system_test_case.rb b/8_0/ch06/test/application_system_test_case.rb new file mode 100644 index 00000000..d19212ab --- /dev/null +++ b/8_0/ch06/test/application_system_test_case.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/8_0/ch06/test/channels/application_cable/connection_test.rb b/8_0/ch06/test/channels/application_cable/connection_test.rb new file mode 100644 index 00000000..800405f1 --- /dev/null +++ b/8_0/ch06/test/channels/application_cable/connection_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase + # test "connects with cookies" do + # cookies.signed[:user_id] = 42 + # + # connect + # + # assert_equal connection.user_id, "42" + # end +end diff --git a/8_0/ch06/test/controllers/.keep b/8_0/ch06/test/controllers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/test/controllers/hello_codespaces_controller_test.rb b/8_0/ch06/test/controllers/hello_codespaces_controller_test.rb new file mode 100644 index 00000000..54353ea6 --- /dev/null +++ b/8_0/ch06/test/controllers/hello_codespaces_controller_test.rb @@ -0,0 +1,8 @@ +require "test_helper" + +class HelloCodespacesControllerTest < ActionDispatch::IntegrationTest + # test "should get index" do + # get "/" + # assert_response :success + # end +end diff --git a/8_0/ch06/test/controllers/static_pages_controller_test.rb b/8_0/ch06/test/controllers/static_pages_controller_test.rb new file mode 100644 index 00000000..847856a3 --- /dev/null +++ b/8_0/ch06/test/controllers/static_pages_controller_test.rb @@ -0,0 +1,28 @@ +require "test_helper" + +class StaticPagesControllerTest < ActionDispatch::IntegrationTest + + test "should get home" do + get root_path + assert_response :success + assert_select "title", "Ruby on Rails Tutorial Sample App" + end + + test "should get help" do + get help_path + assert_response :success + assert_select "title", "Help | Ruby on Rails Tutorial Sample App" + end + + test "should get about" do + get about_path + assert_response :success + assert_select "title", "About | Ruby on Rails Tutorial Sample App" + end + + test "should get contact" do + get contact_path + assert_response :success + assert_select "title", "Contact | Ruby on Rails Tutorial Sample App" + end +end diff --git a/8_0/ch06/test/controllers/users_controller_test.rb b/8_0/ch06/test/controllers/users_controller_test.rb new file mode 100644 index 00000000..2b123196 --- /dev/null +++ b/8_0/ch06/test/controllers/users_controller_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +class UsersControllerTest < ActionDispatch::IntegrationTest + + test "should get new" do + get signup_path + assert_response :success + end +end diff --git a/8_0/ch06/test/fixtures/files/.keep b/8_0/ch06/test/fixtures/files/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/test/fixtures/users.yml b/8_0/ch06/test/fixtures/users.yml new file mode 100644 index 00000000..e3b03cf0 --- /dev/null +++ b/8_0/ch06/test/fixtures/users.yml @@ -0,0 +1 @@ +# 空にする (既存のコードは削除する) diff --git a/8_0/ch06/test/helpers/.keep b/8_0/ch06/test/helpers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/test/integration/.keep b/8_0/ch06/test/integration/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/test/integration/site_layout_test.rb b/8_0/ch06/test/integration/site_layout_test.rb new file mode 100644 index 00000000..0f7ca649 --- /dev/null +++ b/8_0/ch06/test/integration/site_layout_test.rb @@ -0,0 +1,13 @@ +require "test_helper" + +class SiteLayoutTest < ActionDispatch::IntegrationTest + + test "layout links" do + get root_path + assert_template 'static_pages/home' + assert_select "a[href=?]", root_path, count: 2 + assert_select "a[href=?]", help_path + assert_select "a[href=?]", about_path + assert_select "a[href=?]", contact_path + end +end diff --git a/8_0/ch06/test/mailers/.keep b/8_0/ch06/test/mailers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/test/models/.keep b/8_0/ch06/test/models/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/test/models/user_test.rb b/8_0/ch06/test/models/user_test.rb new file mode 100644 index 00000000..a56aa365 --- /dev/null +++ b/8_0/ch06/test/models/user_test.rb @@ -0,0 +1,67 @@ +require "test_helper" + +class UserTest < ActiveSupport::TestCase + + def setup + @user = User.new(name: "Example User", email: "user@example.com", + password: "foobar", password_confirmation: "foobar") + end + + test "should be valid" do + assert @user.valid? + end + + test "name should be present" do + @user.name = " " + assert_not @user.valid? + end + + test "email should be present" do + @user.email = " " + assert_not @user.valid? + end + + test "name should not be too long" do + @user.name = "a" * 51 + assert_not @user.valid? + end + + test "email should not be too long" do + @user.email = "a" * 244 + "@example.com" + assert_not @user.valid? + end + + test "email validation should accept valid addresses" do + valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org + first.last@foo.jp alice+bob@baz.cn] + valid_addresses.each do |valid_address| + @user.email = valid_address + assert @user.valid?, "#{valid_address.inspect} should be valid" + end + end + + test "email validation should reject invalid addresses" do + invalid_addresses = %w[user@example,com user_at_foo.org user.name@example. + foo@bar_baz.com foo@bar+baz.com] + invalid_addresses.each do |invalid_address| + @user.email = invalid_address + assert_not @user.valid?, "#{invalid_address.inspect} should be invalid" + end + end + + test "email addresses should be unique" do + duplicate_user = @user.dup + @user.save + assert_not duplicate_user.valid? + end + + test "password should be present (nonblank)" do + @user.password = @user.password_confirmation = " " * 6 + assert_not @user.valid? + end + + test "password should have a minimum length" do + @user.password = @user.password_confirmation = "a" * 5 + assert_not @user.valid? + end +end diff --git a/8_0/ch06/test/system/.keep b/8_0/ch06/test/system/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/test/test_helper.rb b/8_0/ch06/test/test_helper.rb new file mode 100644 index 00000000..f566ca61 --- /dev/null +++ b/8_0/ch06/test/test_helper.rb @@ -0,0 +1,15 @@ +ENV["RAILS_ENV"] ||= "test" +require_relative "../config/environment" +require "rails/test_help" +require "minitest/reporters" +Minitest::Reporters.use! + +class ActiveSupport::TestCase + # 指定のワーカー数でテストを並列実行する + parallelize(workers: :number_of_processors) + + # test/fixtures/*.ymlにあるすべてのfixtureをセットアップする + fixtures :all + + # (すべてのテストで使うその他のヘルパーメソッドは省略) +end diff --git a/8_0/ch06/tmp/.keep b/8_0/ch06/tmp/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/tmp/pids/.keep b/8_0/ch06/tmp/pids/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/tmp/storage/.keep b/8_0/ch06/tmp/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/vendor/.keep b/8_0/ch06/vendor/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch06/vendor/javascript/.keep b/8_0/ch06/vendor/javascript/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/.devcontainer/devcontainer.json b/8_0/ch07/.devcontainer/devcontainer.json new file mode 100644 index 00000000..e7243217 --- /dev/null +++ b/8_0/ch07/.devcontainer/devcontainer.json @@ -0,0 +1,105 @@ +// Specifications: https://containers.dev/implementors/json_reference/ +// Format details: https://aka.ms/devcontainer.json +// Config options: https://github.com/microsoft/vscode-dev-containers/tree/main/containers/ruby +{ + "name": "railstutorial", + + // Universal is well-customized image for Codespaces: + // https://hub.docker.com/_/microsoft-devcontainers-universal + //"image": "mcr.microsoft.com/devcontainers/universal:2", + //"features": { + // "ghcr.io/devcontainers/features/ruby:1": {} + //}, + + // Use Ruby image if you want to pin Ruby version like '3.2' + // https://github.com/devcontainers/images/tree/main/src/ruby + "image": "mcr.microsoft.com/devcontainers/ruby:3.2", + //"workspaceFolder": "/railstutorial", => Container build failed + + // Enable learners to choose an affordable spec, starting at minimum one. + //"hostRequirements": { + // "cpus": 2, + // "memory": "4gb", + // "storage": "32gb" + //}, + + "waitFor": "onCreateCommand", + //"onCreateCommand": "", + "onCreateCommand": "gem install solargraph -v '0.58.2' -N", + //"onCreateCommand": "gem install solargraph -v '0.50.0' -N && gem install ruby-lsp -N", + //"onCreateCommand": "gem install ruby-lsp -N", + //# => Solargraph gem not found. Run `gem install solargraph` or update your Gemfile. + "updateContentCommand": "bundle install", + "postCreateCommand": "", + "postAttachCommand": { + "server": "rails server" + }, + "customizations": { + "codespaces": { + "openFiles": [ + "app/views/hello/index.html.erb" + ] + }, + "vscode": { + "extensions": [ + "GitHub.codespaces", + "Shopify.ruby-lsp", // https://github.com/Shopify/ruby-lsp + "castwide.solargraph" // https://github.com/castwide/vscode-solargraph + //"rebornix.Ruby", // https://github.com/rubyide/vscode-ruby (Deprecated) + + ], + "settings": { + // General settings for Codespaces (VS Code) + //"ruby.useLanguageServer": true , + //"ruby.format": "rubocop", + //"ruby.lint": { "rubocop": true }, + //"ruby.intellisense": "rubyLocate", + "editor.tabSize": 2, + "editor.formatOnSave": false, // Disable onSave to show diff edited by learners only + "editor.formatOnType": false, // Disable onType for the same reason above + "editor.insertSpaces": true, // Use spaces, not tabs, to avoid errors for learners + "editor.renderWhitespace": "none", + "[ruby]": { + "editor.defaultFormatter": "castwide.solargraph", + "editor.semanticHighlighting.enabled": true // Enable semantic highlighting + }, + "files.associations": { "*.erb": "erb" }, + "emmet.includeLanguages": { "erb": "html" }, + + // Settings for Solargraph + // https://github.com/castwide/solargraph + "solargraph.useBundler": false, + "solargraph.diagnostics": false, + "solargraph.formatting": true, // Use Ctrl+Shift+P->Format to format + "solargraph.autoformat": false, + "solargraph.definitions": true, + "solargraph.completion": true, + "solargraph.references": true, + "solargraph.symbols": true, + "solargraph.rename": true, + "solargraph.hover": true, + + // Settings for Ruby LSP + // https://github.com/Shopify/ruby-lsp + "rubyLsp.rubyVersionManager": { + "identifier": "none" + }, + "rubyLsp.formatter": "none", + "rubyLsp.enabledFeatures": { + "diagnostics": false, + "formatting": false + } + } + } + }, + "remoteEnv": { + "EDITOR": "code --wait" + }, + "portsAttributes": { + "3000": { + "label": "Application", + "onAutoForward": "openPreview" + } + }, + "forwardPorts": [3000] +} diff --git a/8_0/ch07/.devcontainer/icon.svg b/8_0/ch07/.devcontainer/icon.svg new file mode 100644 index 00000000..f8444a4a --- /dev/null +++ b/8_0/ch07/.devcontainer/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/8_0/ch07/.gitattributes b/8_0/ch07/.gitattributes new file mode 100644 index 00000000..31eeee0b --- /dev/null +++ b/8_0/ch07/.gitattributes @@ -0,0 +1,7 @@ +# See https://git-scm.com/docs/gitattributes for more about git attribute files. + +# Mark the database schema as having been generated. +db/schema.rb linguist-generated + +# Mark any vendored files as having been vendored. +vendor/* linguist-vendored diff --git a/8_0/ch07/.gitignore b/8_0/ch07/.gitignore new file mode 100644 index 00000000..e1ef53ef --- /dev/null +++ b/8_0/ch07/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-* + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore uploaded files in development. +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/ +!/tmp/storage/.keep + +/public/assets + +# Ignore master key for decrypting credentials and more. +/config/master.key + +# Ignore history file used by IRB for interactive Ruby. +.irb_history + +dump.rdb diff --git a/8_0/ch07/.irbrc b/8_0/ch07/.irbrc new file mode 100644 index 00000000..3737e036 --- /dev/null +++ b/8_0/ch07/.irbrc @@ -0,0 +1 @@ +IRB.conf[:COMPLETOR] = :type # default is :regexp diff --git a/8_0/ch07/.solargraph.yml b/8_0/ch07/.solargraph.yml new file mode 100644 index 00000000..76ac24a6 --- /dev/null +++ b/8_0/ch07/.solargraph.yml @@ -0,0 +1,32 @@ +--- +include: +- "**/*.rb" +exclude: +- spec/**/* +- test/**/* +- vendor/**/* +- ".bundle/**/*" +require: +- actioncable +- actionmailer +- actionpack +- actionview +- activejob +- activemodel +- activerecord +- activestorage +- activesupport +domains: [] +reporters: +- rubocop +- require_not_found +- typecheck +formatter: + rubocop: + cops: safe + except: [] + only: [] + extra_args: [] +require_paths: [] +plugins: [] +max_files: 5000 diff --git a/8_0/ch07/.vscode/settings.json b/8_0/ch07/.vscode/settings.json new file mode 100644 index 00000000..cf85f519 --- /dev/null +++ b/8_0/ch07/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.minimap.enabled": false +} diff --git a/8_0/ch07/Gemfile b/8_0/ch07/Gemfile new file mode 100644 index 00000000..a015b466 --- /dev/null +++ b/8_0/ch07/Gemfile @@ -0,0 +1,44 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '3.2.10' + +gem 'bcrypt', '3.1.18' +gem 'bootsnap', '1.16.0', require: false +gem 'bootstrap-sass', '3.4.1' +gem 'concurrent-ruby', '1.3.4' +gem 'importmap-rails', '1.1.5' +gem 'jbuilder', '2.14.1' +gem 'puma', '6.6.1' +gem 'rails', '8.0.2.1' +gem 'sassc-rails', '2.1.2' +gem 'sprockets-rails', '3.4.2' +gem 'sqlite3', '2.7.3' +gem 'stimulus-rails', '1.2.1' +gem 'turbo-rails', '1.4.0' + +group :development, :test do + gem 'debug', '1.7.1', platforms: %i[mri mingw x64_mingw] + gem 'reline', '0.5.10' +end + +group :development do + gem 'irb', '1.15.2' + gem 'repl_type_completor', '0.1.10' + gem 'solargraph', '0.58.2' + gem 'web-console', '4.2.0' +end + +group :test do + gem 'capybara', '3.38.0' + gem 'guard', '2.18.0' + gem 'guard-minitest', '2.4.6' + gem 'minitest', '5.18.0' + gem 'minitest-reporters', '1.6.0' + gem 'rails-controller-testing', '1.0.5' + gem 'selenium-webdriver', '4.8.3' + gem 'webdrivers', '5.2.0' +end + +# Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります +# gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] diff --git a/8_0/ch07/Gemfile.lock b/8_0/ch07/Gemfile.lock new file mode 100644 index 00000000..119a783d --- /dev/null +++ b/8_0/ch07/Gemfile.lock @@ -0,0 +1,442 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + actionmailer (8.0.2.1) + actionpack (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.0.2.1) + actionview (= 8.0.2.1) + activesupport (= 8.0.2.1) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.0.2.1) + actionpack (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (8.0.2.1) + activesupport (= 8.0.2.1) + builder (~> 3.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.3.6) + activemodel (8.0.2.1) + activesupport (= 8.0.2.1) + activerecord (8.0.2.1) + activemodel (= 8.0.2.1) + activesupport (= 8.0.2.1) + timeout (>= 0.4.0) + activestorage (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activesupport (= 8.0.2.1) + marcel (~> 1.0) + activesupport (8.0.2.1) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + ansi (1.5.0) + ast (2.4.3) + autoprefixer-rails (10.4.21.0) + execjs (~> 2) + backport (1.2.0) + base64 (0.3.0) + bcrypt (3.1.18) + benchmark (0.4.1) + bigdecimal (3.2.3) + bindex (0.8.1) + bootsnap (1.16.0) + msgpack (~> 1.2) + bootstrap-sass (3.4.1) + autoprefixer-rails (>= 5.2.1) + sassc (>= 2.0.0) + builder (3.3.0) + capybara (3.38.0) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + coderay (1.1.3) + concurrent-ruby (1.3.4) + connection_pool (2.5.4) + crass (1.0.6) + date (3.4.1) + debug (1.7.1) + irb (>= 1.5.0) + reline (>= 0.3.1) + diff-lcs (1.6.2) + drb (2.2.3) + erb (5.0.2) + erubi (1.13.1) + execjs (2.10.1) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) + formatador (1.2.0) + reline + globalid (1.2.1) + activesupport (>= 6.1) + guard (2.18.0) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.13.0) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-minitest (2.4.6) + guard-compat (~> 1.2) + minitest (>= 3.0) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + importmap-rails (1.1.5) + actionpack (>= 6.0.0) + railties (>= 6.0.0) + io-console (0.8.1) + irb (1.15.2) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jaro_winkler (1.6.1) + jbuilder (2.14.1) + actionview (>= 7.0.0) + activesupport (>= 7.0.0) + json (2.13.2) + kramdown (2.5.1) + rexml (>= 3.3.9) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) + loofah (2.24.1) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + lumberjack (1.4.1) + mail (2.8.1) + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.0.4) + matrix (0.4.3) + method_source (1.1.0) + mini_mime (1.1.5) + minitest (5.18.0) + minitest-reporters (1.6.0) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + msgpack (1.8.0) + nenv (0.3.0) + net-imap (0.5.10) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.4) + nokogiri (1.19.1-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-musl) + racc (~> 1.4) + notiffany (0.1.3) + nenv (~> 0.1) + shellany (~> 0.0) + observer (0.1.2) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.9.0) + ast (~> 2.4.1) + racc + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + prism (1.4.0) + pry (0.15.2) + coderay (~> 1.1) + method_source (~> 1.0) + psych (5.2.6) + date + stringio + public_suffix (6.0.2) + puma (6.6.1) + nio4r (~> 2.0) + racc (1.8.1) + rack (3.2.5) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.2.1) + rack (>= 3) + rails (8.0.2.1) + actioncable (= 8.0.2.1) + actionmailbox (= 8.0.2.1) + actionmailer (= 8.0.2.1) + actionpack (= 8.0.2.1) + actiontext (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activemodel (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + bundler (>= 1.15.0) + railties (= 8.0.2.1) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.0) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rbs (3.6.1) + logger + rdoc (6.14.2) + erb + psych (>= 4.0.0) + regexp_parser (2.11.2) + reline (0.5.10) + io-console (~> 0.5) + repl_type_completor (0.1.10) + prism (~> 1.0) + rbs (>= 2.7.0, < 4.0.0) + reverse_markdown (3.0.0) + nokogiri + rexml (3.4.2) + rubocop (1.80.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.46.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.46.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + ruby-progressbar (1.13.0) + rubyzip (2.4.1) + sassc (2.4.0) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt + securerandom (0.4.1) + selenium-webdriver (4.8.3) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) + shellany (0.0.1) + solargraph (0.58.2) + backport (~> 1.2) + benchmark (~> 0.4) + bundler (~> 2.0) + diff-lcs (~> 1.4) + jaro_winkler (~> 1.6, >= 1.6.1) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.1) + logger (~> 1.6) + observer (~> 0.1) + ostruct (~> 0.6) + parser (~> 3.0) + prism (~> 1.4) + rbs (~> 3.6.1) + reverse_markdown (~> 3.0) + rubocop (~> 1.38) + thor (~> 1.0) + tilt (~> 2.0) + yard (~> 0.9, >= 0.9.24) + yard-solargraph (~> 0.1) + sprockets (4.2.2) + concurrent-ruby (~> 1.0) + logger + rack (>= 2.2.4, < 4) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + sprockets (>= 3.0.0) + sqlite3 (2.7.3-aarch64-linux-gnu) + sqlite3 (2.7.3-aarch64-linux-musl) + sqlite3 (2.7.3-arm-linux-gnu) + sqlite3 (2.7.3-arm-linux-musl) + sqlite3 (2.7.3-arm64-darwin) + sqlite3 (2.7.3-x86_64-darwin) + sqlite3 (2.7.3-x86_64-linux-gnu) + sqlite3 (2.7.3-x86_64-linux-musl) + stimulus-rails (1.2.1) + railties (>= 6.0.0) + stringio (3.1.7) + thor (1.4.0) + tilt (2.6.1) + timeout (0.4.3) + turbo-rails (1.4.0) + actionpack (>= 6.0.0) + activejob (>= 6.0.0) + railties (>= 6.0.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.1.5) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + uri (1.0.4) + useragent (0.16.11) + web-console (4.2.0) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + webdrivers (5.2.0) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (~> 4.0) + websocket (1.2.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) + yard (0.9.37) + yard-solargraph (0.1.0) + yard (~> 0.9) + zeitwerk (2.7.3) + +PLATFORMS + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + bcrypt (= 3.1.18) + bootsnap (= 1.16.0) + bootstrap-sass (= 3.4.1) + capybara (= 3.38.0) + concurrent-ruby (= 1.3.4) + debug (= 1.7.1) + guard (= 2.18.0) + guard-minitest (= 2.4.6) + importmap-rails (= 1.1.5) + irb (= 1.15.2) + jbuilder (= 2.14.1) + minitest (= 5.18.0) + minitest-reporters (= 1.6.0) + puma (= 6.6.1) + rails (= 8.0.2.1) + rails-controller-testing (= 1.0.5) + reline (= 0.5.10) + repl_type_completor (= 0.1.10) + sassc-rails (= 2.1.2) + selenium-webdriver (= 4.8.3) + solargraph (= 0.58.2) + sprockets-rails (= 3.4.2) + sqlite3 (= 2.7.3) + stimulus-rails (= 1.2.1) + turbo-rails (= 1.4.0) + web-console (= 4.2.0) + webdrivers (= 5.2.0) + +RUBY VERSION + ruby 3.2.10p266 + +BUNDLED WITH + 2.5.6 diff --git a/8_0/ch07/Guardfile b/8_0/ch07/Guardfile new file mode 100644 index 00000000..5def34a0 --- /dev/null +++ b/8_0/ch07/Guardfile @@ -0,0 +1,76 @@ + require "active_support/inflector" + # Guardのマッチング規則を定義 + guard :minitest, all_on_start: false do + watch(%r{^test/(.*)/?(.*)_test\.rb$}) + watch('test/test_helper.rb') { 'test' } + watch('config/routes.rb') { interface_tests } + watch(%r{app/views/layouts/*}) { interface_tests } + watch(%r{^app/models/(.*?)\.rb$}) do |matches| + ["test/models/#{matches[1]}_test.rb", + "test/integration/microposts_interface_test.rb"] + end + watch(%r{^test/fixtures/(.*?)\.yml$}) do |matches| + "test/models/#{matches[1].singularize}_test.rb" + end + watch(%r{^app/mailers/(.*?)\.rb$}) do |matches| + "test/mailers/#{matches[1]}_test.rb" + end + watch(%r{^app/views/(.*)_mailer/.*$}) do |matches| + "test/mailers/#{matches[1]}_mailer_test.rb" + end + watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches| + resource_tests(matches[1]) + end + watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches| + ["test/controllers/#{matches[1]}_controller_test.rb"] + + integration_tests(matches[1]) + end + watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches| + integration_tests(matches[1]) + end + watch('app/views/layouts/application.html.erb') do + 'test/integration/site_layout_test.rb' + end + watch('app/helpers/sessions_helper.rb') do + integration_tests << 'test/helpers/sessions_helper_test.rb' + end + watch('app/controllers/sessions_controller.rb') do + ['test/controllers/sessions_controller_test.rb', + 'test/integration/users_login_test.rb'] + end + watch('app/controllers/account_activations_controller.rb') do + 'test/integration/users_signup_test.rb' + end + watch(%r{app/views/users/*}) do + resource_tests('users') + + ['test/integration/microposts_interface_test.rb'] + end + watch('app/controllers/relationships_controller.rb') do + ['test/controllers/relationships_controller_test.rb', + 'test/integration/following_test.rb'] + end + end + + # 指定のリソースに対応する統合テストを返す + def integration_tests(resource = :all) + if resource == :all + Dir["test/integration/*"] + else + Dir["test/integration/#{resource}_*.rb"] + end + end + + # インターフェースが該当するすべてのテストを返す + def interface_tests + integration_tests << "test/controllers" + end + + # 指定のリソースに対応するコントローラのテストを返す + def controller_test(resource) + "test/controllers/#{resource}_controller_test.rb" + end + + # 指定のリソースに対応するすべてのテストを返す + def resource_tests(resource) + integration_tests(resource) << controller_test(resource) + end diff --git a/8_0/ch07/LICENSE b/8_0/ch07/LICENSE new file mode 100644 index 00000000..20622e0c --- /dev/null +++ b/8_0/ch07/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 GitHub & 2023 YassLab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/8_0/ch07/README.md b/8_0/ch07/README.md new file mode 100644 index 00000000..17ca733d --- /dev/null +++ b/8_0/ch07/README.md @@ -0,0 +1,35 @@ +# Ruby on Rails チュートリアルのサンプルアプリケーション + +これは、次の教材で作られたサンプルアプリケーションです。 +[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +(第8版) +[Michael Hartl](https://www.michaelhartl.com/) 著 + +## ライセンス + +[Ruby on Rails チュートリアル](https://railstutorial.jp/)内にある +ソースコードはMITライセンスとBeerwareライセンスのもとで公開されています。 +詳細は [LICENSE.md](LICENSE.md) をご覧ください。 + +## 使い方 + +このアプリケーションを動かす場合は、まずはリポジトリをフォークしてください。 + +フォークしたリポジトリで、「Code」から「Codespaces」タブに移動し、 +「Create codespace on main」をクリックすると環境構築がスタートします。 +Railsサーバーが立ち上がり、シンプルブラウザが表示されるまでしばらくお待ちください。 + +次に、データベースへのマイグレーションを実行します。 + +``` +$ rails db:migrate +``` + +最後に、テストを実行してうまく動いているかどうか確認してください。 + +``` +$ rails test +``` + +詳しくは、[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +を参考にしてください。 diff --git a/8_0/ch07/Rakefile b/8_0/ch07/Rakefile new file mode 100644 index 00000000..9a5ea738 --- /dev/null +++ b/8_0/ch07/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/8_0/ch07/app/assets/config/manifest.js b/8_0/ch07/app/assets/config/manifest.js new file mode 100644 index 00000000..ddd546a0 --- /dev/null +++ b/8_0/ch07/app/assets/config/manifest.js @@ -0,0 +1,4 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js diff --git a/8_0/ch07/app/assets/images/.keep b/8_0/ch07/app/assets/images/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/app/assets/images/rails.svg b/8_0/ch07/app/assets/images/rails.svg new file mode 100644 index 00000000..e1971850 --- /dev/null +++ b/8_0/ch07/app/assets/images/rails.svg @@ -0,0 +1,35 @@ + + + +rails-logo + + + + + + + + + + + + + + + + + + + + + diff --git a/8_0/ch07/app/assets/stylesheets/application.css b/8_0/ch07/app/assets/stylesheets/application.css new file mode 100644 index 00000000..288b9ab7 --- /dev/null +++ b/8_0/ch07/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/8_0/ch07/app/assets/stylesheets/custom.scss b/8_0/ch07/app/assets/stylesheets/custom.scss new file mode 100644 index 00000000..8ee16b1d --- /dev/null +++ b/8_0/ch07/app/assets/stylesheets/custom.scss @@ -0,0 +1,178 @@ +@import "bootstrap-sprockets"; +@import "bootstrap"; + +/* mixins, variables, etc. */ + +$gray-medium-light: #eaeaea; + +@mixin box_sizing { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* universal */ + +body { + padding-top: 60px; +} + +section { + overflow: auto; +} + +textarea { + resize: vertical; +} + +.center { + text-align: center; + h1 { + margin-bottom: 10px; + } +} + +/* typography */ + +h1, h2, h3, h4, h5, h6 { + line-height: 1; +} + +h1 { + font-size: 3em; + letter-spacing: -2px; + margin-bottom: 30px; + text-align: center; +} + +h2 { + font-size: 1.2em; + letter-spacing: -1px; + margin-bottom: 30px; + text-align: center; + font-weight: normal; + color: $gray-light; +} + +p { + font-size: 1.1em; + line-height: 1.7em; +} + + +/* header */ + +#logo { + float: left; + margin-right: 10px; + font-size: 1.7em; + color: white; + text-transform: uppercase; + letter-spacing: -1px; + padding-top: 9px; + font-weight: bold; + &:hover { + color: white; + text-decoration: none; + } +} + +/* footer */ + +footer { + margin-top: 45px; + padding-top: 5px; + border-top: 1px solid $gray-medium-light; + color: $gray-light; + a { + color: $gray; + &:hover { + color: $gray-darker; + } + } + small { + float: left; + } + ul { + float: right; + list-style: none; + li { + float: left; + margin-left: 15px; + } + } +} + +/* miscellaneous */ + +.debug_dump { + clear: both; + float: left; + width: 100%; + margin-top: 45px; +} + +/* sidebar */ + +aside { + section.user_info { + margin-top: 20px; + } + section { + padding: 10px 0; + margin-top: 20px; + &:first-child { + border: 0; + padding-top: 0; + } + span { + display: block; + margin-bottom: 3px; + line-height: 1; + } + h1 { + font-size: 1.4em; + text-align: left; + letter-spacing: -1px; + margin-bottom: 3px; + margin-top: 0px; + } + } +} + +.gravatar { + float: left; + margin-right: 10px; +} + +.gravatar_edit { + margin-top: 15px; +} + +/* forms */ + +input, textarea { + border: 1px solid #bbb; + width: 100%; + margin-bottom: 15px; + @include box_sizing; +} + +input { + height: auto !important; +} + +#error_explanation { + color: red; + ul { + color: red; + margin: 0 0 30px 0; + } +} + +.field_with_errors { + @extend .has-error; + .form-control { + color: $state-danger-text; + } +} \ No newline at end of file diff --git a/8_0/ch07/app/assets/stylesheets/hello.css b/8_0/ch07/app/assets/stylesheets/hello.css new file mode 100644 index 00000000..497881a4 --- /dev/null +++ b/8_0/ch07/app/assets/stylesheets/hello.css @@ -0,0 +1,40 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.codespaces { + text-align: center; +} +.codespaces code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} +.codespaces-logo { + height: 40vmin; + pointer-events: none; +} +.codespaces-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} +.codespaces-link { + color: #61dafb; +} + +.heart { + color: #ff0000; +} +.small { + font-size: 0.75rem; +} diff --git a/8_0/ch07/app/channels/application_cable/channel.rb b/8_0/ch07/app/channels/application_cable/channel.rb new file mode 100644 index 00000000..d6726972 --- /dev/null +++ b/8_0/ch07/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/8_0/ch07/app/channels/application_cable/connection.rb b/8_0/ch07/app/channels/application_cable/connection.rb new file mode 100644 index 00000000..0ff5442f --- /dev/null +++ b/8_0/ch07/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/8_0/ch07/app/controllers/application_controller.rb b/8_0/ch07/app/controllers/application_controller.rb new file mode 100644 index 00000000..09705d12 --- /dev/null +++ b/8_0/ch07/app/controllers/application_controller.rb @@ -0,0 +1,2 @@ +class ApplicationController < ActionController::Base +end diff --git a/8_0/ch07/app/controllers/concerns/.keep b/8_0/ch07/app/controllers/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/app/controllers/hello_controller.rb b/8_0/ch07/app/controllers/hello_controller.rb new file mode 100644 index 00000000..52037537 --- /dev/null +++ b/8_0/ch07/app/controllers/hello_controller.rb @@ -0,0 +1,4 @@ +class HelloController < ApplicationController + def index + end +end diff --git a/8_0/ch07/app/controllers/static_pages_controller.rb b/8_0/ch07/app/controllers/static_pages_controller.rb new file mode 100644 index 00000000..0673ad5e --- /dev/null +++ b/8_0/ch07/app/controllers/static_pages_controller.rb @@ -0,0 +1,14 @@ +class StaticPagesController < ApplicationController + + def home + end + + def help + end + + def about + end + + def contact + end +end diff --git a/8_0/ch07/app/controllers/users_controller.rb b/8_0/ch07/app/controllers/users_controller.rb new file mode 100644 index 00000000..d3c4a07b --- /dev/null +++ b/8_0/ch07/app/controllers/users_controller.rb @@ -0,0 +1,27 @@ +class UsersController < ApplicationController + + def show + @user = User.find(params[:id]) + end + + def new + @user = User.new + end + + def create + @user = User.new(user_params) + if @user.save + flash[:success] = "Welcome to the Sample App!" + redirect_to @user + else + render 'new', status: :unprocessable_entity + end + end + + private + + def user_params + params.expect(user: [:name, :email, :password, + :password_confirmation]) + end +end diff --git a/8_0/ch07/app/helpers/application_helper.rb b/8_0/ch07/app/helpers/application_helper.rb new file mode 100644 index 00000000..708e4e48 --- /dev/null +++ b/8_0/ch07/app/helpers/application_helper.rb @@ -0,0 +1,12 @@ +module ApplicationHelper + + # ページごとの完全なタイトルを返します。 # コメント行 + def full_title(page_title = '') # メソッド定義とオプション引数 + base_title = "Ruby on Rails Tutorial Sample App" # 変数への代入 + if page_title.empty? # 論理値テスト + base_title # 暗黙の戻り値 + else + "#{page_title} | #{base_title}" # 文字列の結合 + end + end +end diff --git a/8_0/ch07/app/helpers/hello_codespaces_helper.rb b/8_0/ch07/app/helpers/hello_codespaces_helper.rb new file mode 100644 index 00000000..874a2ca2 --- /dev/null +++ b/8_0/ch07/app/helpers/hello_codespaces_helper.rb @@ -0,0 +1,2 @@ +module HelloCodespacesHelper +end diff --git a/8_0/ch07/app/helpers/static_pages_helper.rb b/8_0/ch07/app/helpers/static_pages_helper.rb new file mode 100644 index 00000000..2d63e79e --- /dev/null +++ b/8_0/ch07/app/helpers/static_pages_helper.rb @@ -0,0 +1,2 @@ +module StaticPagesHelper +end diff --git a/8_0/ch07/app/helpers/users_helper.rb b/8_0/ch07/app/helpers/users_helper.rb new file mode 100644 index 00000000..b8d0f8e5 --- /dev/null +++ b/8_0/ch07/app/helpers/users_helper.rb @@ -0,0 +1,9 @@ +module UsersHelper + + # 引数で与えられたユーザーのGravatar画像を返す + def gravatar_for(user) + gravatar_id = Digest::MD5::hexdigest(user.email.downcase) + gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}" + image_tag(gravatar_url, alt: user.name, class: "gravatar") + end +end diff --git a/8_0/ch07/app/javascript/application.js b/8_0/ch07/app/javascript/application.js new file mode 100644 index 00000000..0d7b4940 --- /dev/null +++ b/8_0/ch07/app/javascript/application.js @@ -0,0 +1,3 @@ +// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails +import "@hotwired/turbo-rails" +import "controllers" diff --git a/8_0/ch07/app/javascript/controllers/application.js b/8_0/ch07/app/javascript/controllers/application.js new file mode 100644 index 00000000..1213e85c --- /dev/null +++ b/8_0/ch07/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/8_0/ch07/app/javascript/controllers/hello_controller.js b/8_0/ch07/app/javascript/controllers/hello_controller.js new file mode 100644 index 00000000..5975c078 --- /dev/null +++ b/8_0/ch07/app/javascript/controllers/hello_controller.js @@ -0,0 +1,7 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.element.textContent = "Hello World!" + } +} diff --git a/8_0/ch07/app/javascript/controllers/index.js b/8_0/ch07/app/javascript/controllers/index.js new file mode 100644 index 00000000..54ad4cad --- /dev/null +++ b/8_0/ch07/app/javascript/controllers/index.js @@ -0,0 +1,11 @@ +// Import and register all your controllers from the importmap under controllers/* + +import { application } from "controllers/application" + +// Eager load all controllers defined in the import map under controllers/**/*_controller +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) + +// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) +// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" +// lazyLoadControllersFrom("controllers", application) diff --git a/8_0/ch07/app/jobs/application_job.rb b/8_0/ch07/app/jobs/application_job.rb new file mode 100644 index 00000000..d394c3d1 --- /dev/null +++ b/8_0/ch07/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/8_0/ch07/app/mailers/application_mailer.rb b/8_0/ch07/app/mailers/application_mailer.rb new file mode 100644 index 00000000..3c34c814 --- /dev/null +++ b/8_0/ch07/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "from@example.com" + layout "mailer" +end diff --git a/8_0/ch07/app/models/application_record.rb b/8_0/ch07/app/models/application_record.rb new file mode 100644 index 00000000..b63caeb8 --- /dev/null +++ b/8_0/ch07/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/8_0/ch07/app/models/concerns/.keep b/8_0/ch07/app/models/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/app/models/user.rb b/8_0/ch07/app/models/user.rb new file mode 100644 index 00000000..6550627b --- /dev/null +++ b/8_0/ch07/app/models/user.rb @@ -0,0 +1,10 @@ +class User < ApplicationRecord + before_save { self.email = email.downcase } + validates :name, presence: true, length: { maximum: 50 } + VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i + validates :email, presence: true, length: { maximum: 255 }, + format: { with: VALID_EMAIL_REGEX }, + uniqueness: true + has_secure_password + validates :password, presence: true, length: { minimum: 6 } +end diff --git a/8_0/ch07/app/views/hello/index.html.erb b/8_0/ch07/app/views/hello/index.html.erb new file mode 100644 index 00000000..71661105 --- /dev/null +++ b/8_0/ch07/app/views/hello/index.html.erb @@ -0,0 +1,16 @@ +
+
+ +

+ Codespaces + ♥️ + Railsチュートリアル +

+

+ 🎓✨
+ ロゴ画像が表示されたらセットアップ完了です! +

+
+
diff --git a/8_0/ch07/app/views/layouts/_footer.html.erb b/8_0/ch07/app/views/layouts/_footer.html.erb new file mode 100644 index 00000000..b6e31ff9 --- /dev/null +++ b/8_0/ch07/app/views/layouts/_footer.html.erb @@ -0,0 +1,13 @@ + diff --git a/8_0/ch07/app/views/layouts/_header.html.erb b/8_0/ch07/app/views/layouts/_header.html.erb new file mode 100644 index 00000000..1e481b02 --- /dev/null +++ b/8_0/ch07/app/views/layouts/_header.html.erb @@ -0,0 +1,12 @@ + diff --git a/8_0/ch07/app/views/layouts/application.html.erb b/8_0/ch07/app/views/layouts/application.html.erb new file mode 100644 index 00000000..eb23ef94 --- /dev/null +++ b/8_0/ch07/app/views/layouts/application.html.erb @@ -0,0 +1,24 @@ + + + + <%= full_title(yield(:title)) %> + + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + + + <%= render 'layouts/header' %> +
+ <% flash.each do |message_type, message| %> +
<%= message %>
+ <% end %> + <%= yield %> + <%= render 'layouts/footer' %> + <%= debug(params) if Rails.env.development? %> +
+ + diff --git a/8_0/ch07/app/views/layouts/mailer.html.erb b/8_0/ch07/app/views/layouts/mailer.html.erb new file mode 100644 index 00000000..cbd34d2e --- /dev/null +++ b/8_0/ch07/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/8_0/ch07/app/views/layouts/mailer.text.erb b/8_0/ch07/app/views/layouts/mailer.text.erb new file mode 100644 index 00000000..37f0bddb --- /dev/null +++ b/8_0/ch07/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/8_0/ch07/app/views/shared/_error_messages.html.erb b/8_0/ch07/app/views/shared/_error_messages.html.erb new file mode 100644 index 00000000..f80053e4 --- /dev/null +++ b/8_0/ch07/app/views/shared/_error_messages.html.erb @@ -0,0 +1,12 @@ +<% if @user.errors.any? %> +
+
+ The form contains <%= pluralize(@user.errors.count, "error") %>. +
+ +
+<% end %> diff --git a/8_0/ch07/app/views/static_pages/about.html.erb b/8_0/ch07/app/views/static_pages/about.html.erb new file mode 100644 index 00000000..dce8fef2 --- /dev/null +++ b/8_0/ch07/app/views/static_pages/about.html.erb @@ -0,0 +1,10 @@ +<% provide(:title, "About") %> +

About

+

+ Ruby on Rails Tutorial + is a book and + screencast + to teach web development with + Ruby on Rails. + This is the sample application for the tutorial. +

diff --git a/8_0/ch07/app/views/static_pages/contact.html.erb b/8_0/ch07/app/views/static_pages/contact.html.erb new file mode 100644 index 00000000..4a5a35a6 --- /dev/null +++ b/8_0/ch07/app/views/static_pages/contact.html.erb @@ -0,0 +1,6 @@ +<% provide(:title, 'Contact') %> +

Contact

+

+ Contact the Ruby on Rails Tutorial about the sample app at the + contact page. +

diff --git a/8_0/ch07/app/views/static_pages/help.html.erb b/8_0/ch07/app/views/static_pages/help.html.erb new file mode 100644 index 00000000..4d06919d --- /dev/null +++ b/8_0/ch07/app/views/static_pages/help.html.erb @@ -0,0 +1,9 @@ +<% provide(:title, "Help") %> +

Help

+

+ Get help on the Ruby on Rails Tutorial at the + Rails Tutorial Help page. + To get help on this sample app, see the + Ruby on Rails Tutorial + book. +

diff --git a/8_0/ch07/app/views/static_pages/home.html.erb b/8_0/ch07/app/views/static_pages/home.html.erb new file mode 100644 index 00000000..f87607de --- /dev/null +++ b/8_0/ch07/app/views/static_pages/home.html.erb @@ -0,0 +1,14 @@ +
+

Welcome to the Sample App

+ +

+ This is the home page for the + Ruby on Rails Tutorial + sample application. +

+ + <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %> +
+ +<%= link_to image_tag("rails.svg", alt: "Rails logo", width: "200"), + "https://rubyonrails.org/" %> diff --git a/8_0/ch07/app/views/users/new.html.erb b/8_0/ch07/app/views/users/new.html.erb new file mode 100644 index 00000000..79e39f80 --- /dev/null +++ b/8_0/ch07/app/views/users/new.html.erb @@ -0,0 +1,24 @@ +<% provide(:title, 'Sign up') %> +

Sign up

+ +
+
+ <%= form_with(model: @user) do |f| %> + <%= render 'shared/error_messages' %> + + <%= f.label :name %> + <%= f.text_field :name, class: 'form-control' %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :password_confirmation, "Confirmation" %> + <%= f.password_field :password_confirmation, class: 'form-control' %> + + <%= f.submit "Create my account", class: "btn btn-primary" %> + <% end %> +
+
diff --git a/8_0/ch07/app/views/users/show.html.erb b/8_0/ch07/app/views/users/show.html.erb new file mode 100644 index 00000000..4fda5f89 --- /dev/null +++ b/8_0/ch07/app/views/users/show.html.erb @@ -0,0 +1,11 @@ +<% provide(:title, @user.name) %> +
+ +
diff --git a/8_0/ch07/bin/bundle b/8_0/ch07/bin/bundle new file mode 100755 index 00000000..981e650b --- /dev/null +++ b/8_0/ch07/bin/bundle @@ -0,0 +1,114 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + requirement = bundler_gem_version.approximate_recommendation + + return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0") + + requirement += ".a" if bundler_gem_version.prerelease? + + requirement + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/8_0/ch07/bin/dev b/8_0/ch07/bin/dev new file mode 100755 index 00000000..5f91c205 --- /dev/null +++ b/8_0/ch07/bin/dev @@ -0,0 +1,2 @@ +#!/usr/bin/env ruby +exec "./bin/rails", "server", *ARGV diff --git a/8_0/ch07/bin/importmap b/8_0/ch07/bin/importmap new file mode 100755 index 00000000..36502ab1 --- /dev/null +++ b/8_0/ch07/bin/importmap @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require_relative "../config/application" +require "importmap/commands" diff --git a/8_0/ch07/bin/rails b/8_0/ch07/bin/rails new file mode 100755 index 00000000..efc03774 --- /dev/null +++ b/8_0/ch07/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/8_0/ch07/bin/rake b/8_0/ch07/bin/rake new file mode 100755 index 00000000..4fbf10b9 --- /dev/null +++ b/8_0/ch07/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/8_0/ch07/bin/render-build.sh b/8_0/ch07/bin/render-build.sh new file mode 100755 index 00000000..6a61cb7f --- /dev/null +++ b/8_0/ch07/bin/render-build.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# exit on error +set -o errexit +bundle install +bundle exec rails assets:precompile +bundle exec rails assets:clean +bundle exec rails db:migrate diff --git a/8_0/ch07/bin/rubocop b/8_0/ch07/bin/rubocop new file mode 100755 index 00000000..40330c0f --- /dev/null +++ b/8_0/ch07/bin/rubocop @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +# explicit rubocop config increases performance slightly while avoiding config confusion. +ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) + +load Gem.bin_path("rubocop", "rubocop") diff --git a/8_0/ch07/bin/setup b/8_0/ch07/bin/setup new file mode 100755 index 00000000..be3db3c0 --- /dev/null +++ b/8_0/ch07/bin/setup @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +require "fileutils" + +APP_ROOT = File.expand_path("..", __dir__) + +def system!(*args) + system(*args, exception: true) +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts "== Installing dependencies ==" + system("bundle check") || system!("bundle install") + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + puts "\n== Removing old logs and tempfiles ==" + system! "bin/rails log:clear tmp:clear" + + unless ARGV.include?("--skip-server") + puts "\n== Starting development server ==" + STDOUT.flush # flush the output before exec(2) so that it displays + exec "bin/dev" + end +end diff --git a/8_0/ch07/config.ru b/8_0/ch07/config.ru new file mode 100644 index 00000000..4a3c09a6 --- /dev/null +++ b/8_0/ch07/config.ru @@ -0,0 +1,6 @@ +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/8_0/ch07/config/application.rb b/8_0/ch07/config/application.rb new file mode 100644 index 00000000..0461e72f --- /dev/null +++ b/8_0/ch07/config/application.rb @@ -0,0 +1,27 @@ +require_relative "boot" + +require "rails/all" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module SampleApp + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 8.0 + + # Please, add to the `ignore` list any other `lib` subdirectories that do + # not contain `.rb` files, or that should not be reloaded or eager loaded. + # Common ones are `templates`, `generators`, or `middleware`, for example. + config.autoload_lib(ignore: %w[assets tasks]) + + # Configuration for the application, engines, and railties goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + # + # config.time_zone = "Central Time (US & Canada)" + # config.eager_load_paths << Rails.root.join("extras") + end +end diff --git a/8_0/ch07/config/boot.rb b/8_0/ch07/config/boot.rb new file mode 100644 index 00000000..988a5ddc --- /dev/null +++ b/8_0/ch07/config/boot.rb @@ -0,0 +1,4 @@ +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "bundler/setup" # Set up gems listed in the Gemfile. +require "bootsnap/setup" # Speed up boot time by caching expensive operations. diff --git a/8_0/ch07/config/cable.yml b/8_0/ch07/config/cable.yml new file mode 100644 index 00000000..ae7a2256 --- /dev/null +++ b/8_0/ch07/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: codespaces_try_rails_production diff --git a/8_0/ch07/config/credentials.yml.enc b/8_0/ch07/config/credentials.yml.enc new file mode 100644 index 00000000..339d64d2 --- /dev/null +++ b/8_0/ch07/config/credentials.yml.enc @@ -0,0 +1 @@ +NCs9M8uakg1CDYJENuGaipzymY8uG6i5gzghsy4npa3gYmGfYKPHMnEcJHCVHlM3gxDNJBteUqkRCxHQ5WQId8hzgxoQCL5RX7rtQ8XUzkJwgSUjKvbCrG5atjKkN9XuL91is5vtEN5W7vEz3qxN7Rp33QbNFiDfLs71F3zHVDYEMi6Dy1QMhVCa1v/tyRjBu/5m37RuiYF5FzWJnh4LhexSVr7Agm4LRLLN6qLCpoAe6D3TPHHBdtu7Pvy8jlrEfnnkUJ61wrj8+kOL4uLtpFShdYKhDkoTjQk7TCOgWyifnHAXazkBZoJZ1QYv/tZ9/ZoHvMEQ2dtGHlyTX8fbKtI13d2CFjGaDNKvRuurxE8dbeyiTfbycvve46+cz9OTlmhh0SGt24R1WV6GBKoDUeoHrIiBvW5qmKka--dQ0rodxmfgFLg2y/--t2x0gQjh8FYlk8v79vLSNg== \ No newline at end of file diff --git a/8_0/ch07/config/database.yml b/8_0/ch07/config/database.yml new file mode 100644 index 00000000..fcba57f1 --- /dev/null +++ b/8_0/ch07/config/database.yml @@ -0,0 +1,25 @@ +# SQLite. Versions 3.8.0 and up are supported. +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem "sqlite3" +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/8_0/ch07/config/environment.rb b/8_0/ch07/config/environment.rb new file mode 100644 index 00000000..cac53157 --- /dev/null +++ b/8_0/ch07/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/8_0/ch07/config/environments/development.rb b/8_0/ch07/config/environments/development.rb new file mode 100644 index 00000000..910cef99 --- /dev/null +++ b/8_0/ch07/config/environments/development.rb @@ -0,0 +1,86 @@ +require 'active_support/core_ext/integer/time' + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Make code changes take effect immediately without server restart. + config.enable_reloading = true + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing. + config.server_timing = true + + # Add the following line to disable forgery_protection_origin_check + config.action_controller.forgery_protection_origin_check = false + + # Enable/disable Action Controller caching. By default Action Controller caching is disabled. + # Run rails dev:cache to toggle Action Controller caching. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + config.public_file_server.headers = { 'cache-control' => "public, max-age=#{2.days.to_i}" } + else + config.action_controller.perform_caching = false + end + + # Change to :null_store to avoid any caching. + config.cache_store = :memory_store + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Make template changes take effect immediately. + config.action_mailer.perform_caching = false + + # Set localhost to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Append comments with runtime information tags to SQL queries in logs. + config.active_record.query_log_tags_enabled = true + + # Highlight code that enqueued background job in logs. + config.active_job.verbose_enqueue_logs = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true + + # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. + # config.generators.apply_rubocop_autocorrect_after_generate! + + pf_domain = ENV['GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN'] + config.action_dispatch.default_headers = { + 'X-Frame-Options' => "ALLOW-FROM #{pf_domain}" + } + + # Allow requests from our preview domain. + pf_host = "#{ENV['CODESPACE_NAME']}-3000.#{pf_domain}" + config.hosts << pf_host + + config.action_cable.allowed_request_origins = ["https://#{pf_host}"] +end diff --git a/8_0/ch07/config/environments/production.rb b/8_0/ch07/config/environments/production.rb new file mode 100644 index 00000000..17496077 --- /dev/null +++ b/8_0/ch07/config/environments/production.rb @@ -0,0 +1,89 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.enable_reloading = false + + # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). + config.eager_load = true + + # Full error reports are disabled. + config.consider_all_requests_local = false + + # Turn on fragment caching in view templates. + config.action_controller.perform_caching = true + + # Cache assets for far-future expiry since they are all digest stamped. + config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" } + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = "http://assets.example.com" + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Assume all access to the app is happening through a SSL-terminating reverse proxy. + config.assume_ssl = true + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = true + + # Skip http-to-https redirect for the default health check endpoint. + # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } + + # Log to STDOUT with the current request id as a default log tag. + config.log_tags = [ :request_id ] + config.logger = ActiveSupport::TaggedLogging.logger(STDOUT) + + # Change to "debug" to log everything (including potentially personally-identifiable information!) + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + + # Prevent health checks from clogging up the logs. + config.silence_healthcheck_path = "/up" + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Replace the default in-process memory cache store with a durable alternative. + # config.cache_store = :mem_cache_store + + # Replace the default in-process and non-durable queuing backend for Active Job. + # config.active_job.queue_adapter = :resque + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. + # config.action_mailer.smtp_settings = { + # user_name: Rails.application.credentials.dig(:smtp, :user_name), + # password: Rails.application.credentials.dig(:smtp, :password), + # address: "smtp.example.com", + # port: 587, + # authentication: :plain + # } + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Only use :id for inspections in production. + config.active_record.attributes_for_inspect = [ :id ] + + # Enable DNS rebinding protection and other `Host` header attacks. + # config.hosts = [ + # "example.com", # Allow requests from example.com + # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` + # ] + # + # Skip DNS rebinding protection for the default health check endpoint. + # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } +end diff --git a/8_0/ch07/config/environments/test.rb b/8_0/ch07/config/environments/test.rb new file mode 100644 index 00000000..0ebe3b4e --- /dev/null +++ b/8_0/ch07/config/environments/test.rb @@ -0,0 +1,53 @@ +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # While tests run files are not watched, reloading is not necessary. + config.enable_reloading = false + + # Eager loading loads your entire application. When running a single test locally, + # this is usually not necessary, and can slow down your test suite. However, it's + # recommended that you enable it in continuous integration systems to ensure eager + # loading is working properly before deploying your code. + config.eager_load = ENV["CI"].present? + + # Configure public file server for tests with cache-control for performance. + config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } + + # Show full error reports. + config.consider_all_requests_local = true + config.cache_store = :null_store + + # Render exception templates for rescuable exceptions and raise for other exceptions. + config.action_dispatch.show_exceptions = :none + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :test + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true +end diff --git a/8_0/ch07/config/importmap.rb b/8_0/ch07/config/importmap.rb new file mode 100644 index 00000000..8dce42d4 --- /dev/null +++ b/8_0/ch07/config/importmap.rb @@ -0,0 +1,7 @@ +# Pin npm packages by running ./bin/importmap + +pin "application", preload: true +pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true +pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true +pin_all_from "app/javascript/controllers", under: "controllers" diff --git a/8_0/ch07/config/initializers/assets.rb b/8_0/ch07/config/initializers/assets.rb new file mode 100644 index 00000000..48732442 --- /dev/null +++ b/8_0/ch07/config/initializers/assets.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = "1.0" + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path diff --git a/8_0/ch07/config/initializers/content_security_policy.rb b/8_0/ch07/config/initializers/content_security_policy.rb new file mode 100644 index 00000000..b3076b38 --- /dev/null +++ b/8_0/ch07/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header + +# Rails.application.configure do +# config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end +# +# # Generate session nonces for permitted importmap, inline scripts, and inline styles. +# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } +# config.content_security_policy_nonce_directives = %w(script-src style-src) +# +# # Report violations without enforcing the policy. +# # config.content_security_policy_report_only = true +# end diff --git a/8_0/ch07/config/initializers/filter_parameter_logging.rb b/8_0/ch07/config/initializers/filter_parameter_logging.rb new file mode 100644 index 00000000..c0b717f7 --- /dev/null +++ b/8_0/ch07/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. +# Use this to limit dissemination of sensitive information. +# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. +Rails.application.config.filter_parameters += [ + :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc +] diff --git a/8_0/ch07/config/initializers/inflections.rb b/8_0/ch07/config/initializers/inflections.rb new file mode 100644 index 00000000..3860f659 --- /dev/null +++ b/8_0/ch07/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym "RESTful" +# end diff --git a/8_0/ch07/config/initializers/new_framework_defaults_8_0.rb b/8_0/ch07/config/initializers/new_framework_defaults_8_0.rb new file mode 100644 index 00000000..92efa951 --- /dev/null +++ b/8_0/ch07/config/initializers/new_framework_defaults_8_0.rb @@ -0,0 +1,30 @@ +# Be sure to restart your server when you modify this file. +# +# This file eases your Rails 8.0 framework defaults upgrade. +# +# Uncomment each configuration one by one to switch to the new default. +# Once your application is ready to run with all new defaults, you can remove +# this file and set the `config.load_defaults` to `8.0`. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. +# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html + +### +# Specifies whether `to_time` methods preserve the UTC offset of their receivers or preserves the timezone. +# If set to `:zone`, `to_time` methods will use the timezone of their receivers. +# If set to `:offset`, `to_time` methods will use the UTC offset. +# If `false`, `to_time` methods will convert to the local system UTC offset instead. +#++ +# Rails.application.config.active_support.to_time_preserves_timezone = :zone + +### +# When both `If-Modified-Since` and `If-None-Match` are provided by the client +# only consider `If-None-Match` as specified by RFC 7232 Section 6. +# If set to `false` both conditions need to be satisfied. +#++ +# Rails.application.config.action_dispatch.strict_freshness = true + +### +# Set `Regexp.timeout` to `1`s by default to improve security over Regexp Denial-of-Service attacks. +#++ +# Regexp.timeout = 1 diff --git a/8_0/ch07/config/initializers/permissions_policy.rb b/8_0/ch07/config/initializers/permissions_policy.rb new file mode 100644 index 00000000..00f64d71 --- /dev/null +++ b/8_0/ch07/config/initializers/permissions_policy.rb @@ -0,0 +1,11 @@ +# Define an application-wide HTTP permissions policy. For further +# information see https://developers.google.com/web/updates/2018/06/feature-policy +# +# Rails.application.config.permissions_policy do |f| +# f.camera :none +# f.gyroscope :none +# f.microphone :none +# f.usb :none +# f.fullscreen :self +# f.payment :self, "https://secure.example.com" +# end diff --git a/8_0/ch07/config/locales/en.yml b/8_0/ch07/config/locales/en.yml new file mode 100644 index 00000000..8ca56fc7 --- /dev/null +++ b/8_0/ch07/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# "true": "foo" +# +# To learn more, please read the Rails Internationalization guide +# available at https://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/8_0/ch07/config/puma.rb b/8_0/ch07/config/puma.rb new file mode 100644 index 00000000..a248513b --- /dev/null +++ b/8_0/ch07/config/puma.rb @@ -0,0 +1,41 @@ +# This configuration file will be evaluated by Puma. The top-level methods that +# are invoked here are part of Puma's configuration DSL. For more information +# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. +# +# Puma starts a configurable number of processes (workers) and each process +# serves each request in a thread from an internal thread pool. +# +# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You +# should only set this value when you want to run 2 or more workers. The +# default is already 1. +# +# The ideal number of threads per worker depends both on how much time the +# application spends waiting for IO operations and on how much you wish to +# prioritize throughput over latency. +# +# As a rule of thumb, increasing the number of threads will increase how much +# traffic a given process can handle (throughput), but due to CRuby's +# Global VM Lock (GVL) it has diminishing returns and will degrade the +# response time (latency) of the application. +# +# The default is set to 3 threads as it's deemed a decent compromise between +# throughput and latency for the average Rails application. +# +# Any libraries that use a connection pool or another resource pool should +# be configured to provide at least as many connections as the number of +# threads. This includes Active Record's `pool` parameter in `database.yml`. +threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +port ENV.fetch("PORT", 3000) + +# Allow puma to be restarted by `bin/rails restart` command. +plugin :tmp_restart + +# Run the Solid Queue supervisor inside of Puma for single-server deployments +plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] + +# Specify the PID file. Defaults to tmp/pids/server.pid in development. +# In other environments, only set the PID file if requested. +pidfile ENV["PIDFILE"] if ENV["PIDFILE"] diff --git a/8_0/ch07/config/routes.rb b/8_0/ch07/config/routes.rb new file mode 100644 index 00000000..67975a8d --- /dev/null +++ b/8_0/ch07/config/routes.rb @@ -0,0 +1,8 @@ +Rails.application.routes.draw do + root "static_pages#home" + get "/help", to: "static_pages#help" + get "/about", to: "static_pages#about" + get "/contact", to: "static_pages#contact" + get "/signup", to: "users#new" + resources :users +end diff --git a/8_0/ch07/config/storage.yml b/8_0/ch07/config/storage.yml new file mode 100644 index 00000000..4942ab66 --- /dev/null +++ b/8_0/ch07/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket-<%= Rails.env %> + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket-<%= Rails.env %> + +# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name-<%= Rails.env %> + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/8_0/ch07/db/migrate/20260512080056_create_users.rb b/8_0/ch07/db/migrate/20260512080056_create_users.rb new file mode 100644 index 00000000..3b80eb00 --- /dev/null +++ b/8_0/ch07/db/migrate/20260512080056_create_users.rb @@ -0,0 +1,10 @@ +class CreateUsers < ActiveRecord::Migration[8.0] + def change + create_table :users do |t| + t.string :name + t.string :email + + t.timestamps + end + end +end diff --git a/8_0/ch07/db/migrate/20260512080840_add_index_to_users_email.rb b/8_0/ch07/db/migrate/20260512080840_add_index_to_users_email.rb new file mode 100644 index 00000000..a8e4ed8e --- /dev/null +++ b/8_0/ch07/db/migrate/20260512080840_add_index_to_users_email.rb @@ -0,0 +1,5 @@ +class AddIndexToUsersEmail < ActiveRecord::Migration[8.0] + def change + add_index :users, :email, unique: true + end +end diff --git a/8_0/ch07/db/migrate/20260512081511_add_password_digest_to_users.rb b/8_0/ch07/db/migrate/20260512081511_add_password_digest_to_users.rb new file mode 100644 index 00000000..81c7c9e0 --- /dev/null +++ b/8_0/ch07/db/migrate/20260512081511_add_password_digest_to_users.rb @@ -0,0 +1,5 @@ +class AddPasswordDigestToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :password_digest, :string + end +end diff --git a/8_0/ch07/db/schema.rb b/8_0/ch07/db/schema.rb new file mode 100644 index 00000000..e22275fc --- /dev/null +++ b/8_0/ch07/db/schema.rb @@ -0,0 +1,22 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[8.0].define(version: 2026_05_12_081511) do + create_table "users", force: :cascade do |t| + t.string "name" + t.string "email" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "password_digest" + t.index ["email"], name: "index_users_on_email", unique: true + end +end diff --git a/8_0/ch07/db/seeds.rb b/8_0/ch07/db/seeds.rb new file mode 100644 index 00000000..bc25fce3 --- /dev/null +++ b/8_0/ch07/db/seeds.rb @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). +# +# Examples: +# +# movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }]) +# Character.create(name: "Luke", movie: movies.first) diff --git a/8_0/ch07/lib/assets/.keep b/8_0/ch07/lib/assets/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/lib/tasks/.keep b/8_0/ch07/lib/tasks/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/log/.keep b/8_0/ch07/log/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/public/400.html b/8_0/ch07/public/400.html new file mode 100644 index 00000000..282dbc8c --- /dev/null +++ b/8_0/ch07/public/400.html @@ -0,0 +1,114 @@ + + + + + + + The server cannot process the request due to a client error (400 Bad Request) + + + + + + + + + + + + + +
+
+ +
+
+

The server cannot process the request due to a client error. Please check the request and try again. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch07/public/404.html b/8_0/ch07/public/404.html new file mode 100644 index 00000000..c0670bc8 --- /dev/null +++ b/8_0/ch07/public/404.html @@ -0,0 +1,114 @@ + + + + + + + The page you were looking for doesn’t exist (404 Not found) + + + + + + + + + + + + + +
+
+ +
+
+

The page you were looking for doesn’t exist. You may have mistyped the address or the page may have moved. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch07/public/406-unsupported-browser.html b/8_0/ch07/public/406-unsupported-browser.html new file mode 100644 index 00000000..9532a9cc --- /dev/null +++ b/8_0/ch07/public/406-unsupported-browser.html @@ -0,0 +1,114 @@ + + + + + + + Your browser is not supported (406 Not Acceptable) + + + + + + + + + + + + + +
+
+ +
+
+

Your browser is not supported.
Please upgrade your browser to continue.

+
+
+ + + + diff --git a/8_0/ch07/public/422.html b/8_0/ch07/public/422.html new file mode 100644 index 00000000..8bcf0601 --- /dev/null +++ b/8_0/ch07/public/422.html @@ -0,0 +1,114 @@ + + + + + + + The change you wanted was rejected (422 Unprocessable Entity) + + + + + + + + + + + + + +
+
+ +
+
+

The change you wanted was rejected. Maybe you tried to change something you didn’t have access to. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch07/public/500.html b/8_0/ch07/public/500.html new file mode 100644 index 00000000..d77718c3 --- /dev/null +++ b/8_0/ch07/public/500.html @@ -0,0 +1,114 @@ + + + + + + + We’re sorry, but something went wrong (500 Internal Server Error) + + + + + + + + + + + + + +
+
+ +
+
+

We’re sorry, but something went wrong.
If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch07/public/apple-touch-icon-precomposed.png b/8_0/ch07/public/apple-touch-icon-precomposed.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/public/apple-touch-icon.png b/8_0/ch07/public/apple-touch-icon.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/public/favicon.ico b/8_0/ch07/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/public/icon.png b/8_0/ch07/public/icon.png new file mode 100644 index 00000000..c4c9dbfb Binary files /dev/null and b/8_0/ch07/public/icon.png differ diff --git a/8_0/ch07/public/icon.svg b/8_0/ch07/public/icon.svg new file mode 100644 index 00000000..04b34bf8 --- /dev/null +++ b/8_0/ch07/public/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/8_0/ch07/public/railstutorial.png b/8_0/ch07/public/railstutorial.png new file mode 100644 index 00000000..70835e6b Binary files /dev/null and b/8_0/ch07/public/railstutorial.png differ diff --git a/8_0/ch07/public/robots.txt b/8_0/ch07/public/robots.txt new file mode 100644 index 00000000..c19f78ab --- /dev/null +++ b/8_0/ch07/public/robots.txt @@ -0,0 +1 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/8_0/ch07/storage/.keep b/8_0/ch07/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/test/application_system_test_case.rb b/8_0/ch07/test/application_system_test_case.rb new file mode 100644 index 00000000..d19212ab --- /dev/null +++ b/8_0/ch07/test/application_system_test_case.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/8_0/ch07/test/channels/application_cable/connection_test.rb b/8_0/ch07/test/channels/application_cable/connection_test.rb new file mode 100644 index 00000000..800405f1 --- /dev/null +++ b/8_0/ch07/test/channels/application_cable/connection_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase + # test "connects with cookies" do + # cookies.signed[:user_id] = 42 + # + # connect + # + # assert_equal connection.user_id, "42" + # end +end diff --git a/8_0/ch07/test/controllers/.keep b/8_0/ch07/test/controllers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/test/controllers/hello_codespaces_controller_test.rb b/8_0/ch07/test/controllers/hello_codespaces_controller_test.rb new file mode 100644 index 00000000..54353ea6 --- /dev/null +++ b/8_0/ch07/test/controllers/hello_codespaces_controller_test.rb @@ -0,0 +1,8 @@ +require "test_helper" + +class HelloCodespacesControllerTest < ActionDispatch::IntegrationTest + # test "should get index" do + # get "/" + # assert_response :success + # end +end diff --git a/8_0/ch07/test/controllers/static_pages_controller_test.rb b/8_0/ch07/test/controllers/static_pages_controller_test.rb new file mode 100644 index 00000000..847856a3 --- /dev/null +++ b/8_0/ch07/test/controllers/static_pages_controller_test.rb @@ -0,0 +1,28 @@ +require "test_helper" + +class StaticPagesControllerTest < ActionDispatch::IntegrationTest + + test "should get home" do + get root_path + assert_response :success + assert_select "title", "Ruby on Rails Tutorial Sample App" + end + + test "should get help" do + get help_path + assert_response :success + assert_select "title", "Help | Ruby on Rails Tutorial Sample App" + end + + test "should get about" do + get about_path + assert_response :success + assert_select "title", "About | Ruby on Rails Tutorial Sample App" + end + + test "should get contact" do + get contact_path + assert_response :success + assert_select "title", "Contact | Ruby on Rails Tutorial Sample App" + end +end diff --git a/8_0/ch07/test/controllers/users_controller_test.rb b/8_0/ch07/test/controllers/users_controller_test.rb new file mode 100644 index 00000000..2b123196 --- /dev/null +++ b/8_0/ch07/test/controllers/users_controller_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +class UsersControllerTest < ActionDispatch::IntegrationTest + + test "should get new" do + get signup_path + assert_response :success + end +end diff --git a/8_0/ch07/test/fixtures/files/.keep b/8_0/ch07/test/fixtures/files/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/test/fixtures/users.yml b/8_0/ch07/test/fixtures/users.yml new file mode 100644 index 00000000..e3b03cf0 --- /dev/null +++ b/8_0/ch07/test/fixtures/users.yml @@ -0,0 +1 @@ +# 空にする (既存のコードは削除する) diff --git a/8_0/ch07/test/helpers/.keep b/8_0/ch07/test/helpers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/test/integration/.keep b/8_0/ch07/test/integration/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/test/integration/site_layout_test.rb b/8_0/ch07/test/integration/site_layout_test.rb new file mode 100644 index 00000000..0f7ca649 --- /dev/null +++ b/8_0/ch07/test/integration/site_layout_test.rb @@ -0,0 +1,13 @@ +require "test_helper" + +class SiteLayoutTest < ActionDispatch::IntegrationTest + + test "layout links" do + get root_path + assert_template 'static_pages/home' + assert_select "a[href=?]", root_path, count: 2 + assert_select "a[href=?]", help_path + assert_select "a[href=?]", about_path + assert_select "a[href=?]", contact_path + end +end diff --git a/8_0/ch07/test/integration/users_signup_test.rb b/8_0/ch07/test/integration/users_signup_test.rb new file mode 100644 index 00000000..d08dc026 --- /dev/null +++ b/8_0/ch07/test/integration/users_signup_test.rb @@ -0,0 +1,27 @@ +require "test_helper" + +class UsersSignupTest < ActionDispatch::IntegrationTest + + test "invalid signup information" do + get signup_path + assert_no_difference 'User.count' do + post users_path, params: { user: { name: "", + email: "user@invalid", + password: "foo", + password_confirmation: "bar" } } + end + assert_response :unprocessable_entity + assert_template 'users/new' + end + + test "valid signup information" do + assert_difference 'User.count', 1 do + post users_path, params: { user: { name: "Example User", + email: "user@example.com", + password: "password", + password_confirmation: "password" } } + end + follow_redirect! + assert_template 'users/show' + end +end diff --git a/8_0/ch07/test/mailers/.keep b/8_0/ch07/test/mailers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/test/models/.keep b/8_0/ch07/test/models/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/test/models/user_test.rb b/8_0/ch07/test/models/user_test.rb new file mode 100644 index 00000000..a56aa365 --- /dev/null +++ b/8_0/ch07/test/models/user_test.rb @@ -0,0 +1,67 @@ +require "test_helper" + +class UserTest < ActiveSupport::TestCase + + def setup + @user = User.new(name: "Example User", email: "user@example.com", + password: "foobar", password_confirmation: "foobar") + end + + test "should be valid" do + assert @user.valid? + end + + test "name should be present" do + @user.name = " " + assert_not @user.valid? + end + + test "email should be present" do + @user.email = " " + assert_not @user.valid? + end + + test "name should not be too long" do + @user.name = "a" * 51 + assert_not @user.valid? + end + + test "email should not be too long" do + @user.email = "a" * 244 + "@example.com" + assert_not @user.valid? + end + + test "email validation should accept valid addresses" do + valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org + first.last@foo.jp alice+bob@baz.cn] + valid_addresses.each do |valid_address| + @user.email = valid_address + assert @user.valid?, "#{valid_address.inspect} should be valid" + end + end + + test "email validation should reject invalid addresses" do + invalid_addresses = %w[user@example,com user_at_foo.org user.name@example. + foo@bar_baz.com foo@bar+baz.com] + invalid_addresses.each do |invalid_address| + @user.email = invalid_address + assert_not @user.valid?, "#{invalid_address.inspect} should be invalid" + end + end + + test "email addresses should be unique" do + duplicate_user = @user.dup + @user.save + assert_not duplicate_user.valid? + end + + test "password should be present (nonblank)" do + @user.password = @user.password_confirmation = " " * 6 + assert_not @user.valid? + end + + test "password should have a minimum length" do + @user.password = @user.password_confirmation = "a" * 5 + assert_not @user.valid? + end +end diff --git a/8_0/ch07/test/system/.keep b/8_0/ch07/test/system/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/test/test_helper.rb b/8_0/ch07/test/test_helper.rb new file mode 100644 index 00000000..f566ca61 --- /dev/null +++ b/8_0/ch07/test/test_helper.rb @@ -0,0 +1,15 @@ +ENV["RAILS_ENV"] ||= "test" +require_relative "../config/environment" +require "rails/test_help" +require "minitest/reporters" +Minitest::Reporters.use! + +class ActiveSupport::TestCase + # 指定のワーカー数でテストを並列実行する + parallelize(workers: :number_of_processors) + + # test/fixtures/*.ymlにあるすべてのfixtureをセットアップする + fixtures :all + + # (すべてのテストで使うその他のヘルパーメソッドは省略) +end diff --git a/8_0/ch07/tmp/.keep b/8_0/ch07/tmp/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/tmp/pids/.keep b/8_0/ch07/tmp/pids/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/tmp/storage/.keep b/8_0/ch07/tmp/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/vendor/.keep b/8_0/ch07/vendor/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch07/vendor/javascript/.keep b/8_0/ch07/vendor/javascript/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/.devcontainer/devcontainer.json b/8_0/ch08/.devcontainer/devcontainer.json new file mode 100644 index 00000000..e7243217 --- /dev/null +++ b/8_0/ch08/.devcontainer/devcontainer.json @@ -0,0 +1,105 @@ +// Specifications: https://containers.dev/implementors/json_reference/ +// Format details: https://aka.ms/devcontainer.json +// Config options: https://github.com/microsoft/vscode-dev-containers/tree/main/containers/ruby +{ + "name": "railstutorial", + + // Universal is well-customized image for Codespaces: + // https://hub.docker.com/_/microsoft-devcontainers-universal + //"image": "mcr.microsoft.com/devcontainers/universal:2", + //"features": { + // "ghcr.io/devcontainers/features/ruby:1": {} + //}, + + // Use Ruby image if you want to pin Ruby version like '3.2' + // https://github.com/devcontainers/images/tree/main/src/ruby + "image": "mcr.microsoft.com/devcontainers/ruby:3.2", + //"workspaceFolder": "/railstutorial", => Container build failed + + // Enable learners to choose an affordable spec, starting at minimum one. + //"hostRequirements": { + // "cpus": 2, + // "memory": "4gb", + // "storage": "32gb" + //}, + + "waitFor": "onCreateCommand", + //"onCreateCommand": "", + "onCreateCommand": "gem install solargraph -v '0.58.2' -N", + //"onCreateCommand": "gem install solargraph -v '0.50.0' -N && gem install ruby-lsp -N", + //"onCreateCommand": "gem install ruby-lsp -N", + //# => Solargraph gem not found. Run `gem install solargraph` or update your Gemfile. + "updateContentCommand": "bundle install", + "postCreateCommand": "", + "postAttachCommand": { + "server": "rails server" + }, + "customizations": { + "codespaces": { + "openFiles": [ + "app/views/hello/index.html.erb" + ] + }, + "vscode": { + "extensions": [ + "GitHub.codespaces", + "Shopify.ruby-lsp", // https://github.com/Shopify/ruby-lsp + "castwide.solargraph" // https://github.com/castwide/vscode-solargraph + //"rebornix.Ruby", // https://github.com/rubyide/vscode-ruby (Deprecated) + + ], + "settings": { + // General settings for Codespaces (VS Code) + //"ruby.useLanguageServer": true , + //"ruby.format": "rubocop", + //"ruby.lint": { "rubocop": true }, + //"ruby.intellisense": "rubyLocate", + "editor.tabSize": 2, + "editor.formatOnSave": false, // Disable onSave to show diff edited by learners only + "editor.formatOnType": false, // Disable onType for the same reason above + "editor.insertSpaces": true, // Use spaces, not tabs, to avoid errors for learners + "editor.renderWhitespace": "none", + "[ruby]": { + "editor.defaultFormatter": "castwide.solargraph", + "editor.semanticHighlighting.enabled": true // Enable semantic highlighting + }, + "files.associations": { "*.erb": "erb" }, + "emmet.includeLanguages": { "erb": "html" }, + + // Settings for Solargraph + // https://github.com/castwide/solargraph + "solargraph.useBundler": false, + "solargraph.diagnostics": false, + "solargraph.formatting": true, // Use Ctrl+Shift+P->Format to format + "solargraph.autoformat": false, + "solargraph.definitions": true, + "solargraph.completion": true, + "solargraph.references": true, + "solargraph.symbols": true, + "solargraph.rename": true, + "solargraph.hover": true, + + // Settings for Ruby LSP + // https://github.com/Shopify/ruby-lsp + "rubyLsp.rubyVersionManager": { + "identifier": "none" + }, + "rubyLsp.formatter": "none", + "rubyLsp.enabledFeatures": { + "diagnostics": false, + "formatting": false + } + } + } + }, + "remoteEnv": { + "EDITOR": "code --wait" + }, + "portsAttributes": { + "3000": { + "label": "Application", + "onAutoForward": "openPreview" + } + }, + "forwardPorts": [3000] +} diff --git a/8_0/ch08/.devcontainer/icon.svg b/8_0/ch08/.devcontainer/icon.svg new file mode 100644 index 00000000..f8444a4a --- /dev/null +++ b/8_0/ch08/.devcontainer/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/8_0/ch08/.gitattributes b/8_0/ch08/.gitattributes new file mode 100644 index 00000000..31eeee0b --- /dev/null +++ b/8_0/ch08/.gitattributes @@ -0,0 +1,7 @@ +# See https://git-scm.com/docs/gitattributes for more about git attribute files. + +# Mark the database schema as having been generated. +db/schema.rb linguist-generated + +# Mark any vendored files as having been vendored. +vendor/* linguist-vendored diff --git a/8_0/ch08/.gitignore b/8_0/ch08/.gitignore new file mode 100644 index 00000000..e1ef53ef --- /dev/null +++ b/8_0/ch08/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-* + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore uploaded files in development. +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/ +!/tmp/storage/.keep + +/public/assets + +# Ignore master key for decrypting credentials and more. +/config/master.key + +# Ignore history file used by IRB for interactive Ruby. +.irb_history + +dump.rdb diff --git a/8_0/ch08/.irbrc b/8_0/ch08/.irbrc new file mode 100644 index 00000000..3737e036 --- /dev/null +++ b/8_0/ch08/.irbrc @@ -0,0 +1 @@ +IRB.conf[:COMPLETOR] = :type # default is :regexp diff --git a/8_0/ch08/.solargraph.yml b/8_0/ch08/.solargraph.yml new file mode 100644 index 00000000..76ac24a6 --- /dev/null +++ b/8_0/ch08/.solargraph.yml @@ -0,0 +1,32 @@ +--- +include: +- "**/*.rb" +exclude: +- spec/**/* +- test/**/* +- vendor/**/* +- ".bundle/**/*" +require: +- actioncable +- actionmailer +- actionpack +- actionview +- activejob +- activemodel +- activerecord +- activestorage +- activesupport +domains: [] +reporters: +- rubocop +- require_not_found +- typecheck +formatter: + rubocop: + cops: safe + except: [] + only: [] + extra_args: [] +require_paths: [] +plugins: [] +max_files: 5000 diff --git a/8_0/ch08/.vscode/settings.json b/8_0/ch08/.vscode/settings.json new file mode 100644 index 00000000..cf85f519 --- /dev/null +++ b/8_0/ch08/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.minimap.enabled": false +} diff --git a/8_0/ch08/Gemfile b/8_0/ch08/Gemfile new file mode 100644 index 00000000..a015b466 --- /dev/null +++ b/8_0/ch08/Gemfile @@ -0,0 +1,44 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '3.2.10' + +gem 'bcrypt', '3.1.18' +gem 'bootsnap', '1.16.0', require: false +gem 'bootstrap-sass', '3.4.1' +gem 'concurrent-ruby', '1.3.4' +gem 'importmap-rails', '1.1.5' +gem 'jbuilder', '2.14.1' +gem 'puma', '6.6.1' +gem 'rails', '8.0.2.1' +gem 'sassc-rails', '2.1.2' +gem 'sprockets-rails', '3.4.2' +gem 'sqlite3', '2.7.3' +gem 'stimulus-rails', '1.2.1' +gem 'turbo-rails', '1.4.0' + +group :development, :test do + gem 'debug', '1.7.1', platforms: %i[mri mingw x64_mingw] + gem 'reline', '0.5.10' +end + +group :development do + gem 'irb', '1.15.2' + gem 'repl_type_completor', '0.1.10' + gem 'solargraph', '0.58.2' + gem 'web-console', '4.2.0' +end + +group :test do + gem 'capybara', '3.38.0' + gem 'guard', '2.18.0' + gem 'guard-minitest', '2.4.6' + gem 'minitest', '5.18.0' + gem 'minitest-reporters', '1.6.0' + gem 'rails-controller-testing', '1.0.5' + gem 'selenium-webdriver', '4.8.3' + gem 'webdrivers', '5.2.0' +end + +# Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります +# gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] diff --git a/8_0/ch08/Gemfile.lock b/8_0/ch08/Gemfile.lock new file mode 100644 index 00000000..119a783d --- /dev/null +++ b/8_0/ch08/Gemfile.lock @@ -0,0 +1,442 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + actionmailer (8.0.2.1) + actionpack (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.0.2.1) + actionview (= 8.0.2.1) + activesupport (= 8.0.2.1) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.0.2.1) + actionpack (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (8.0.2.1) + activesupport (= 8.0.2.1) + builder (~> 3.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.3.6) + activemodel (8.0.2.1) + activesupport (= 8.0.2.1) + activerecord (8.0.2.1) + activemodel (= 8.0.2.1) + activesupport (= 8.0.2.1) + timeout (>= 0.4.0) + activestorage (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activesupport (= 8.0.2.1) + marcel (~> 1.0) + activesupport (8.0.2.1) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + ansi (1.5.0) + ast (2.4.3) + autoprefixer-rails (10.4.21.0) + execjs (~> 2) + backport (1.2.0) + base64 (0.3.0) + bcrypt (3.1.18) + benchmark (0.4.1) + bigdecimal (3.2.3) + bindex (0.8.1) + bootsnap (1.16.0) + msgpack (~> 1.2) + bootstrap-sass (3.4.1) + autoprefixer-rails (>= 5.2.1) + sassc (>= 2.0.0) + builder (3.3.0) + capybara (3.38.0) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + coderay (1.1.3) + concurrent-ruby (1.3.4) + connection_pool (2.5.4) + crass (1.0.6) + date (3.4.1) + debug (1.7.1) + irb (>= 1.5.0) + reline (>= 0.3.1) + diff-lcs (1.6.2) + drb (2.2.3) + erb (5.0.2) + erubi (1.13.1) + execjs (2.10.1) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) + formatador (1.2.0) + reline + globalid (1.2.1) + activesupport (>= 6.1) + guard (2.18.0) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.13.0) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-minitest (2.4.6) + guard-compat (~> 1.2) + minitest (>= 3.0) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + importmap-rails (1.1.5) + actionpack (>= 6.0.0) + railties (>= 6.0.0) + io-console (0.8.1) + irb (1.15.2) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jaro_winkler (1.6.1) + jbuilder (2.14.1) + actionview (>= 7.0.0) + activesupport (>= 7.0.0) + json (2.13.2) + kramdown (2.5.1) + rexml (>= 3.3.9) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) + loofah (2.24.1) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + lumberjack (1.4.1) + mail (2.8.1) + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.0.4) + matrix (0.4.3) + method_source (1.1.0) + mini_mime (1.1.5) + minitest (5.18.0) + minitest-reporters (1.6.0) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + msgpack (1.8.0) + nenv (0.3.0) + net-imap (0.5.10) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.4) + nokogiri (1.19.1-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-musl) + racc (~> 1.4) + notiffany (0.1.3) + nenv (~> 0.1) + shellany (~> 0.0) + observer (0.1.2) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.9.0) + ast (~> 2.4.1) + racc + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + prism (1.4.0) + pry (0.15.2) + coderay (~> 1.1) + method_source (~> 1.0) + psych (5.2.6) + date + stringio + public_suffix (6.0.2) + puma (6.6.1) + nio4r (~> 2.0) + racc (1.8.1) + rack (3.2.5) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.2.1) + rack (>= 3) + rails (8.0.2.1) + actioncable (= 8.0.2.1) + actionmailbox (= 8.0.2.1) + actionmailer (= 8.0.2.1) + actionpack (= 8.0.2.1) + actiontext (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activemodel (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + bundler (>= 1.15.0) + railties (= 8.0.2.1) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.0) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rbs (3.6.1) + logger + rdoc (6.14.2) + erb + psych (>= 4.0.0) + regexp_parser (2.11.2) + reline (0.5.10) + io-console (~> 0.5) + repl_type_completor (0.1.10) + prism (~> 1.0) + rbs (>= 2.7.0, < 4.0.0) + reverse_markdown (3.0.0) + nokogiri + rexml (3.4.2) + rubocop (1.80.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.46.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.46.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + ruby-progressbar (1.13.0) + rubyzip (2.4.1) + sassc (2.4.0) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt + securerandom (0.4.1) + selenium-webdriver (4.8.3) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) + shellany (0.0.1) + solargraph (0.58.2) + backport (~> 1.2) + benchmark (~> 0.4) + bundler (~> 2.0) + diff-lcs (~> 1.4) + jaro_winkler (~> 1.6, >= 1.6.1) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.1) + logger (~> 1.6) + observer (~> 0.1) + ostruct (~> 0.6) + parser (~> 3.0) + prism (~> 1.4) + rbs (~> 3.6.1) + reverse_markdown (~> 3.0) + rubocop (~> 1.38) + thor (~> 1.0) + tilt (~> 2.0) + yard (~> 0.9, >= 0.9.24) + yard-solargraph (~> 0.1) + sprockets (4.2.2) + concurrent-ruby (~> 1.0) + logger + rack (>= 2.2.4, < 4) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + sprockets (>= 3.0.0) + sqlite3 (2.7.3-aarch64-linux-gnu) + sqlite3 (2.7.3-aarch64-linux-musl) + sqlite3 (2.7.3-arm-linux-gnu) + sqlite3 (2.7.3-arm-linux-musl) + sqlite3 (2.7.3-arm64-darwin) + sqlite3 (2.7.3-x86_64-darwin) + sqlite3 (2.7.3-x86_64-linux-gnu) + sqlite3 (2.7.3-x86_64-linux-musl) + stimulus-rails (1.2.1) + railties (>= 6.0.0) + stringio (3.1.7) + thor (1.4.0) + tilt (2.6.1) + timeout (0.4.3) + turbo-rails (1.4.0) + actionpack (>= 6.0.0) + activejob (>= 6.0.0) + railties (>= 6.0.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.1.5) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + uri (1.0.4) + useragent (0.16.11) + web-console (4.2.0) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + webdrivers (5.2.0) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (~> 4.0) + websocket (1.2.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) + yard (0.9.37) + yard-solargraph (0.1.0) + yard (~> 0.9) + zeitwerk (2.7.3) + +PLATFORMS + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + bcrypt (= 3.1.18) + bootsnap (= 1.16.0) + bootstrap-sass (= 3.4.1) + capybara (= 3.38.0) + concurrent-ruby (= 1.3.4) + debug (= 1.7.1) + guard (= 2.18.0) + guard-minitest (= 2.4.6) + importmap-rails (= 1.1.5) + irb (= 1.15.2) + jbuilder (= 2.14.1) + minitest (= 5.18.0) + minitest-reporters (= 1.6.0) + puma (= 6.6.1) + rails (= 8.0.2.1) + rails-controller-testing (= 1.0.5) + reline (= 0.5.10) + repl_type_completor (= 0.1.10) + sassc-rails (= 2.1.2) + selenium-webdriver (= 4.8.3) + solargraph (= 0.58.2) + sprockets-rails (= 3.4.2) + sqlite3 (= 2.7.3) + stimulus-rails (= 1.2.1) + turbo-rails (= 1.4.0) + web-console (= 4.2.0) + webdrivers (= 5.2.0) + +RUBY VERSION + ruby 3.2.10p266 + +BUNDLED WITH + 2.5.6 diff --git a/8_0/ch08/Guardfile b/8_0/ch08/Guardfile new file mode 100644 index 00000000..5def34a0 --- /dev/null +++ b/8_0/ch08/Guardfile @@ -0,0 +1,76 @@ + require "active_support/inflector" + # Guardのマッチング規則を定義 + guard :minitest, all_on_start: false do + watch(%r{^test/(.*)/?(.*)_test\.rb$}) + watch('test/test_helper.rb') { 'test' } + watch('config/routes.rb') { interface_tests } + watch(%r{app/views/layouts/*}) { interface_tests } + watch(%r{^app/models/(.*?)\.rb$}) do |matches| + ["test/models/#{matches[1]}_test.rb", + "test/integration/microposts_interface_test.rb"] + end + watch(%r{^test/fixtures/(.*?)\.yml$}) do |matches| + "test/models/#{matches[1].singularize}_test.rb" + end + watch(%r{^app/mailers/(.*?)\.rb$}) do |matches| + "test/mailers/#{matches[1]}_test.rb" + end + watch(%r{^app/views/(.*)_mailer/.*$}) do |matches| + "test/mailers/#{matches[1]}_mailer_test.rb" + end + watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches| + resource_tests(matches[1]) + end + watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches| + ["test/controllers/#{matches[1]}_controller_test.rb"] + + integration_tests(matches[1]) + end + watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches| + integration_tests(matches[1]) + end + watch('app/views/layouts/application.html.erb') do + 'test/integration/site_layout_test.rb' + end + watch('app/helpers/sessions_helper.rb') do + integration_tests << 'test/helpers/sessions_helper_test.rb' + end + watch('app/controllers/sessions_controller.rb') do + ['test/controllers/sessions_controller_test.rb', + 'test/integration/users_login_test.rb'] + end + watch('app/controllers/account_activations_controller.rb') do + 'test/integration/users_signup_test.rb' + end + watch(%r{app/views/users/*}) do + resource_tests('users') + + ['test/integration/microposts_interface_test.rb'] + end + watch('app/controllers/relationships_controller.rb') do + ['test/controllers/relationships_controller_test.rb', + 'test/integration/following_test.rb'] + end + end + + # 指定のリソースに対応する統合テストを返す + def integration_tests(resource = :all) + if resource == :all + Dir["test/integration/*"] + else + Dir["test/integration/#{resource}_*.rb"] + end + end + + # インターフェースが該当するすべてのテストを返す + def interface_tests + integration_tests << "test/controllers" + end + + # 指定のリソースに対応するコントローラのテストを返す + def controller_test(resource) + "test/controllers/#{resource}_controller_test.rb" + end + + # 指定のリソースに対応するすべてのテストを返す + def resource_tests(resource) + integration_tests(resource) << controller_test(resource) + end diff --git a/8_0/ch08/LICENSE b/8_0/ch08/LICENSE new file mode 100644 index 00000000..20622e0c --- /dev/null +++ b/8_0/ch08/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 GitHub & 2023 YassLab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/8_0/ch08/README.md b/8_0/ch08/README.md new file mode 100644 index 00000000..17ca733d --- /dev/null +++ b/8_0/ch08/README.md @@ -0,0 +1,35 @@ +# Ruby on Rails チュートリアルのサンプルアプリケーション + +これは、次の教材で作られたサンプルアプリケーションです。 +[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +(第8版) +[Michael Hartl](https://www.michaelhartl.com/) 著 + +## ライセンス + +[Ruby on Rails チュートリアル](https://railstutorial.jp/)内にある +ソースコードはMITライセンスとBeerwareライセンスのもとで公開されています。 +詳細は [LICENSE.md](LICENSE.md) をご覧ください。 + +## 使い方 + +このアプリケーションを動かす場合は、まずはリポジトリをフォークしてください。 + +フォークしたリポジトリで、「Code」から「Codespaces」タブに移動し、 +「Create codespace on main」をクリックすると環境構築がスタートします。 +Railsサーバーが立ち上がり、シンプルブラウザが表示されるまでしばらくお待ちください。 + +次に、データベースへのマイグレーションを実行します。 + +``` +$ rails db:migrate +``` + +最後に、テストを実行してうまく動いているかどうか確認してください。 + +``` +$ rails test +``` + +詳しくは、[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +を参考にしてください。 diff --git a/8_0/ch08/Rakefile b/8_0/ch08/Rakefile new file mode 100644 index 00000000..9a5ea738 --- /dev/null +++ b/8_0/ch08/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/8_0/ch08/app/assets/config/manifest.js b/8_0/ch08/app/assets/config/manifest.js new file mode 100644 index 00000000..ddd546a0 --- /dev/null +++ b/8_0/ch08/app/assets/config/manifest.js @@ -0,0 +1,4 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js diff --git a/8_0/ch08/app/assets/images/.keep b/8_0/ch08/app/assets/images/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/app/assets/images/rails.svg b/8_0/ch08/app/assets/images/rails.svg new file mode 100644 index 00000000..e1971850 --- /dev/null +++ b/8_0/ch08/app/assets/images/rails.svg @@ -0,0 +1,35 @@ + + + +rails-logo + + + + + + + + + + + + + + + + + + + + + diff --git a/8_0/ch08/app/assets/stylesheets/application.css b/8_0/ch08/app/assets/stylesheets/application.css new file mode 100644 index 00000000..288b9ab7 --- /dev/null +++ b/8_0/ch08/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/8_0/ch08/app/assets/stylesheets/custom.scss b/8_0/ch08/app/assets/stylesheets/custom.scss new file mode 100644 index 00000000..010f1030 --- /dev/null +++ b/8_0/ch08/app/assets/stylesheets/custom.scss @@ -0,0 +1,201 @@ +@import "bootstrap-sprockets"; +@import "bootstrap"; + +/* mixins, variables, etc. */ + +$gray-medium-light: #eaeaea; + +@mixin box_sizing { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* universal */ + +body { + padding-top: 60px; +} + +section { + overflow: auto; +} + +textarea { + resize: vertical; +} + +.center { + text-align: center; + h1 { + margin-bottom: 10px; + } +} + +/* typography */ + +h1, h2, h3, h4, h5, h6 { + line-height: 1; +} + +h1 { + font-size: 3em; + letter-spacing: -2px; + margin-bottom: 30px; + text-align: center; +} + +h2 { + font-size: 1.2em; + letter-spacing: -1px; + margin-bottom: 30px; + text-align: center; + font-weight: normal; + color: $gray-light; +} + +p { + font-size: 1.1em; + line-height: 1.7em; +} + + +/* header */ + +#logo { + float: left; + margin-right: 10px; + font-size: 1.7em; + color: white; + text-transform: uppercase; + letter-spacing: -1px; + padding-top: 9px; + font-weight: bold; + &:hover { + color: white; + text-decoration: none; + } +} + +/* footer */ + +footer { + margin-top: 45px; + padding-top: 5px; + border-top: 1px solid $gray-medium-light; + color: $gray-light; + a { + color: $gray; + &:hover { + color: $gray-darker; + } + } + small { + float: left; + } + ul { + float: right; + list-style: none; + li { + float: left; + margin-left: 15px; + } + } +} +@media (max-width: 800px) { + footer { + small { + display: block; + float: none; + margin-bottom: 1em; + } + ul { + float: none; + padding: 0; + li { + float: none; + margin-left: 0; + } + } + } +} + +/* miscellaneous */ + +.debug_dump { + clear: both; + float: left; + width: 100%; + margin-top: 45px; +} + +/* sidebar */ + +aside { + section.user_info { + margin-top: 20px; + } + section { + padding: 10px 0; + margin-top: 20px; + &:first-child { + border: 0; + padding-top: 0; + } + span { + display: block; + margin-bottom: 3px; + line-height: 1; + } + h1 { + font-size: 1.4em; + text-align: left; + letter-spacing: -1px; + margin-bottom: 3px; + margin-top: 0px; + } + } +} + +.gravatar { + float: left; + margin-right: 10px; +} + +.gravatar_edit { + margin-top: 15px; +} + +/* forms */ + +input, textarea { + border: 1px solid #bbb; + width: 100%; + margin-bottom: 15px; + @include box_sizing; +} + +input { + height: auto !important; +} + +#error_explanation { + color: red; + ul { + color: red; + margin: 0 0 30px 0; + } +} + +.field_with_errors { + @extend .has-error; + .form-control { + color: $state-danger-text; + } +} + +/* Dropdown menu */ + +.dropdown-menu.active { + display: block; +} \ No newline at end of file diff --git a/8_0/ch08/app/assets/stylesheets/hello.css b/8_0/ch08/app/assets/stylesheets/hello.css new file mode 100644 index 00000000..497881a4 --- /dev/null +++ b/8_0/ch08/app/assets/stylesheets/hello.css @@ -0,0 +1,40 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.codespaces { + text-align: center; +} +.codespaces code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} +.codespaces-logo { + height: 40vmin; + pointer-events: none; +} +.codespaces-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} +.codespaces-link { + color: #61dafb; +} + +.heart { + color: #ff0000; +} +.small { + font-size: 0.75rem; +} diff --git a/8_0/ch08/app/channels/application_cable/channel.rb b/8_0/ch08/app/channels/application_cable/channel.rb new file mode 100644 index 00000000..d6726972 --- /dev/null +++ b/8_0/ch08/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/8_0/ch08/app/channels/application_cable/connection.rb b/8_0/ch08/app/channels/application_cable/connection.rb new file mode 100644 index 00000000..0ff5442f --- /dev/null +++ b/8_0/ch08/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/8_0/ch08/app/controllers/application_controller.rb b/8_0/ch08/app/controllers/application_controller.rb new file mode 100644 index 00000000..09faff3f --- /dev/null +++ b/8_0/ch08/app/controllers/application_controller.rb @@ -0,0 +1,3 @@ +class ApplicationController < ActionController::Base + include SessionsHelper +end diff --git a/8_0/ch08/app/controllers/concerns/.keep b/8_0/ch08/app/controllers/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/app/controllers/hello_controller.rb b/8_0/ch08/app/controllers/hello_controller.rb new file mode 100644 index 00000000..52037537 --- /dev/null +++ b/8_0/ch08/app/controllers/hello_controller.rb @@ -0,0 +1,4 @@ +class HelloController < ApplicationController + def index + end +end diff --git a/8_0/ch08/app/controllers/sessions_controller.rb b/8_0/ch08/app/controllers/sessions_controller.rb new file mode 100644 index 00000000..2e0d6f83 --- /dev/null +++ b/8_0/ch08/app/controllers/sessions_controller.rb @@ -0,0 +1,22 @@ +class SessionsController < ApplicationController + + def new + end + + def create + user = User.find_by(email: params[:session][:email].downcase) + if user && user.authenticate(params[:session][:password]) + reset_session + log_in user + redirect_to user + else + flash.now[:danger] = 'Invalid email/password combination' + render 'new', status: :unprocessable_entity + end + end + + def destroy + log_out + redirect_to root_url, status: :see_other + end +end diff --git a/8_0/ch08/app/controllers/static_pages_controller.rb b/8_0/ch08/app/controllers/static_pages_controller.rb new file mode 100644 index 00000000..0673ad5e --- /dev/null +++ b/8_0/ch08/app/controllers/static_pages_controller.rb @@ -0,0 +1,14 @@ +class StaticPagesController < ApplicationController + + def home + end + + def help + end + + def about + end + + def contact + end +end diff --git a/8_0/ch08/app/controllers/users_controller.rb b/8_0/ch08/app/controllers/users_controller.rb new file mode 100644 index 00000000..5a4f1f5e --- /dev/null +++ b/8_0/ch08/app/controllers/users_controller.rb @@ -0,0 +1,29 @@ +class UsersController < ApplicationController + + def show + @user = User.find(params[:id]) + end + + def new + @user = User.new + end + + def create + @user = User.new(user_params) + if @user.save + reset_session + log_in @user + flash[:success] = "Welcome to the Sample App!" + redirect_to @user + else + render 'new', status: :unprocessable_entity + end + end + + private + + def user_params + params.expect(user: [:name, :email, :password, + :password_confirmation]) + end +end diff --git a/8_0/ch08/app/helpers/application_helper.rb b/8_0/ch08/app/helpers/application_helper.rb new file mode 100644 index 00000000..708e4e48 --- /dev/null +++ b/8_0/ch08/app/helpers/application_helper.rb @@ -0,0 +1,12 @@ +module ApplicationHelper + + # ページごとの完全なタイトルを返します。 # コメント行 + def full_title(page_title = '') # メソッド定義とオプション引数 + base_title = "Ruby on Rails Tutorial Sample App" # 変数への代入 + if page_title.empty? # 論理値テスト + base_title # 暗黙の戻り値 + else + "#{page_title} | #{base_title}" # 文字列の結合 + end + end +end diff --git a/8_0/ch08/app/helpers/hello_codespaces_helper.rb b/8_0/ch08/app/helpers/hello_codespaces_helper.rb new file mode 100644 index 00000000..874a2ca2 --- /dev/null +++ b/8_0/ch08/app/helpers/hello_codespaces_helper.rb @@ -0,0 +1,2 @@ +module HelloCodespacesHelper +end diff --git a/8_0/ch08/app/helpers/sessions_helper.rb b/8_0/ch08/app/helpers/sessions_helper.rb new file mode 100644 index 00000000..3ccde613 --- /dev/null +++ b/8_0/ch08/app/helpers/sessions_helper.rb @@ -0,0 +1,25 @@ +module SessionsHelper + + # 渡されたユーザーでログインする + def log_in(user) + session[:user_id] = user.id + end + + # 現在ログイン中のユーザーを返す(いる場合) + def current_user + if session[:user_id] + @current_user ||= User.find_by(id: session[:user_id]) + end + end + + # ユーザーがログインしていればtrue、その他ならfalseを返す + def logged_in? + !current_user.nil? + end + + # 現在のユーザーをログアウトする + def log_out + reset_session + @current_user = nil # 安全のため + end +end diff --git a/8_0/ch08/app/helpers/static_pages_helper.rb b/8_0/ch08/app/helpers/static_pages_helper.rb new file mode 100644 index 00000000..2d63e79e --- /dev/null +++ b/8_0/ch08/app/helpers/static_pages_helper.rb @@ -0,0 +1,2 @@ +module StaticPagesHelper +end diff --git a/8_0/ch08/app/helpers/users_helper.rb b/8_0/ch08/app/helpers/users_helper.rb new file mode 100644 index 00000000..b8d0f8e5 --- /dev/null +++ b/8_0/ch08/app/helpers/users_helper.rb @@ -0,0 +1,9 @@ +module UsersHelper + + # 引数で与えられたユーザーのGravatar画像を返す + def gravatar_for(user) + gravatar_id = Digest::MD5::hexdigest(user.email.downcase) + gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}" + image_tag(gravatar_url, alt: user.name, class: "gravatar") + end +end diff --git a/8_0/ch08/app/javascript/application.js b/8_0/ch08/app/javascript/application.js new file mode 100644 index 00000000..46ebafe8 --- /dev/null +++ b/8_0/ch08/app/javascript/application.js @@ -0,0 +1,5 @@ +// Configure your import map in config/importmap.rb. +// Read more: https://github.com/rails/importmap-rails +import "@hotwired/turbo-rails" +import "controllers" +import "custom/menu" diff --git a/8_0/ch08/app/javascript/controllers/application.js b/8_0/ch08/app/javascript/controllers/application.js new file mode 100644 index 00000000..1213e85c --- /dev/null +++ b/8_0/ch08/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/8_0/ch08/app/javascript/controllers/hello_controller.js b/8_0/ch08/app/javascript/controllers/hello_controller.js new file mode 100644 index 00000000..5975c078 --- /dev/null +++ b/8_0/ch08/app/javascript/controllers/hello_controller.js @@ -0,0 +1,7 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.element.textContent = "Hello World!" + } +} diff --git a/8_0/ch08/app/javascript/controllers/index.js b/8_0/ch08/app/javascript/controllers/index.js new file mode 100644 index 00000000..54ad4cad --- /dev/null +++ b/8_0/ch08/app/javascript/controllers/index.js @@ -0,0 +1,11 @@ +// Import and register all your controllers from the importmap under controllers/* + +import { application } from "controllers/application" + +// Eager load all controllers defined in the import map under controllers/**/*_controller +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) + +// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) +// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" +// lazyLoadControllersFrom("controllers", application) diff --git a/8_0/ch08/app/javascript/custom/menu.js b/8_0/ch08/app/javascript/custom/menu.js new file mode 100644 index 00000000..92e4a8dd --- /dev/null +++ b/8_0/ch08/app/javascript/custom/menu.js @@ -0,0 +1,22 @@ +// メニュー操作 + +// トグルリスナーを追加してクリックをリッスンする +document.addEventListener("turbo:load", function() { + let hamburger = document.querySelector("#hamburger"); + if (hamburger){ + hamburger.addEventListener("click", function(event) { + event.preventDefault(); + let menu = document.querySelector("#navbar-menu"); + menu.classList.toggle("collapse"); + }); + } + + let account = document.querySelector("#account"); + if (account) { + account.addEventListener("click", function(event) { + event.preventDefault(); + let menu = document.querySelector("#dropdown-menu"); + menu.classList.toggle("active"); + }); + } +}); diff --git a/8_0/ch08/app/jobs/application_job.rb b/8_0/ch08/app/jobs/application_job.rb new file mode 100644 index 00000000..d394c3d1 --- /dev/null +++ b/8_0/ch08/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/8_0/ch08/app/mailers/application_mailer.rb b/8_0/ch08/app/mailers/application_mailer.rb new file mode 100644 index 00000000..3c34c814 --- /dev/null +++ b/8_0/ch08/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "from@example.com" + layout "mailer" +end diff --git a/8_0/ch08/app/models/application_record.rb b/8_0/ch08/app/models/application_record.rb new file mode 100644 index 00000000..b63caeb8 --- /dev/null +++ b/8_0/ch08/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/8_0/ch08/app/models/concerns/.keep b/8_0/ch08/app/models/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/app/models/user.rb b/8_0/ch08/app/models/user.rb new file mode 100644 index 00000000..c3885d7a --- /dev/null +++ b/8_0/ch08/app/models/user.rb @@ -0,0 +1,17 @@ +class User < ApplicationRecord + before_save { self.email = email.downcase } + validates :name, presence: true, length: { maximum: 50 } + VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i + validates :email, presence: true, length: { maximum: 255 }, + format: { with: VALID_EMAIL_REGEX }, + uniqueness: true + has_secure_password + validates :password, presence: true, length: { minimum: 6 } + + # 渡された文字列のハッシュ値を返す + def User.digest(string) + cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : + BCrypt::Engine.cost + BCrypt::Password.create(string, cost: cost) + end +end diff --git a/8_0/ch08/app/views/hello/index.html.erb b/8_0/ch08/app/views/hello/index.html.erb new file mode 100644 index 00000000..71661105 --- /dev/null +++ b/8_0/ch08/app/views/hello/index.html.erb @@ -0,0 +1,16 @@ +
+
+ +

+ Codespaces + ♥️ + Railsチュートリアル +

+

+ 🎓✨
+ ロゴ画像が表示されたらセットアップ完了です! +

+
+
diff --git a/8_0/ch08/app/views/layouts/_footer.html.erb b/8_0/ch08/app/views/layouts/_footer.html.erb new file mode 100644 index 00000000..b6e31ff9 --- /dev/null +++ b/8_0/ch08/app/views/layouts/_footer.html.erb @@ -0,0 +1,13 @@ + diff --git a/8_0/ch08/app/views/layouts/_header.html.erb b/8_0/ch08/app/views/layouts/_header.html.erb new file mode 100644 index 00000000..150d97be --- /dev/null +++ b/8_0/ch08/app/views/layouts/_header.html.erb @@ -0,0 +1,39 @@ + diff --git a/8_0/ch08/app/views/layouts/application.html.erb b/8_0/ch08/app/views/layouts/application.html.erb new file mode 100644 index 00000000..eb23ef94 --- /dev/null +++ b/8_0/ch08/app/views/layouts/application.html.erb @@ -0,0 +1,24 @@ + + + + <%= full_title(yield(:title)) %> + + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + + + <%= render 'layouts/header' %> +
+ <% flash.each do |message_type, message| %> +
<%= message %>
+ <% end %> + <%= yield %> + <%= render 'layouts/footer' %> + <%= debug(params) if Rails.env.development? %> +
+ + diff --git a/8_0/ch08/app/views/layouts/mailer.html.erb b/8_0/ch08/app/views/layouts/mailer.html.erb new file mode 100644 index 00000000..cbd34d2e --- /dev/null +++ b/8_0/ch08/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/8_0/ch08/app/views/layouts/mailer.text.erb b/8_0/ch08/app/views/layouts/mailer.text.erb new file mode 100644 index 00000000..37f0bddb --- /dev/null +++ b/8_0/ch08/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/8_0/ch08/app/views/sessions/new.html.erb b/8_0/ch08/app/views/sessions/new.html.erb new file mode 100644 index 00000000..ac87a945 --- /dev/null +++ b/8_0/ch08/app/views/sessions/new.html.erb @@ -0,0 +1,19 @@ +<% provide(:title, "Log in") %> +

Log in

+ +
+
+ <%= form_with(url: login_path, scope: :session) do |f| %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.submit "Log in", class: "btn btn-primary" %> + <% end %> + +

New user? <%= link_to "Sign up now!", signup_path %>

+
+
diff --git a/8_0/ch08/app/views/shared/_error_messages.html.erb b/8_0/ch08/app/views/shared/_error_messages.html.erb new file mode 100644 index 00000000..f80053e4 --- /dev/null +++ b/8_0/ch08/app/views/shared/_error_messages.html.erb @@ -0,0 +1,12 @@ +<% if @user.errors.any? %> +
+
+ The form contains <%= pluralize(@user.errors.count, "error") %>. +
+ +
+<% end %> diff --git a/8_0/ch08/app/views/static_pages/about.html.erb b/8_0/ch08/app/views/static_pages/about.html.erb new file mode 100644 index 00000000..dce8fef2 --- /dev/null +++ b/8_0/ch08/app/views/static_pages/about.html.erb @@ -0,0 +1,10 @@ +<% provide(:title, "About") %> +

About

+

+ Ruby on Rails Tutorial + is a book and + screencast + to teach web development with + Ruby on Rails. + This is the sample application for the tutorial. +

diff --git a/8_0/ch08/app/views/static_pages/contact.html.erb b/8_0/ch08/app/views/static_pages/contact.html.erb new file mode 100644 index 00000000..4a5a35a6 --- /dev/null +++ b/8_0/ch08/app/views/static_pages/contact.html.erb @@ -0,0 +1,6 @@ +<% provide(:title, 'Contact') %> +

Contact

+

+ Contact the Ruby on Rails Tutorial about the sample app at the + contact page. +

diff --git a/8_0/ch08/app/views/static_pages/help.html.erb b/8_0/ch08/app/views/static_pages/help.html.erb new file mode 100644 index 00000000..4d06919d --- /dev/null +++ b/8_0/ch08/app/views/static_pages/help.html.erb @@ -0,0 +1,9 @@ +<% provide(:title, "Help") %> +

Help

+

+ Get help on the Ruby on Rails Tutorial at the + Rails Tutorial Help page. + To get help on this sample app, see the + Ruby on Rails Tutorial + book. +

diff --git a/8_0/ch08/app/views/static_pages/home.html.erb b/8_0/ch08/app/views/static_pages/home.html.erb new file mode 100644 index 00000000..f87607de --- /dev/null +++ b/8_0/ch08/app/views/static_pages/home.html.erb @@ -0,0 +1,14 @@ +
+

Welcome to the Sample App

+ +

+ This is the home page for the + Ruby on Rails Tutorial + sample application. +

+ + <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %> +
+ +<%= link_to image_tag("rails.svg", alt: "Rails logo", width: "200"), + "https://rubyonrails.org/" %> diff --git a/8_0/ch08/app/views/users/new.html.erb b/8_0/ch08/app/views/users/new.html.erb new file mode 100644 index 00000000..79e39f80 --- /dev/null +++ b/8_0/ch08/app/views/users/new.html.erb @@ -0,0 +1,24 @@ +<% provide(:title, 'Sign up') %> +

Sign up

+ +
+
+ <%= form_with(model: @user) do |f| %> + <%= render 'shared/error_messages' %> + + <%= f.label :name %> + <%= f.text_field :name, class: 'form-control' %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :password_confirmation, "Confirmation" %> + <%= f.password_field :password_confirmation, class: 'form-control' %> + + <%= f.submit "Create my account", class: "btn btn-primary" %> + <% end %> +
+
diff --git a/8_0/ch08/app/views/users/show.html.erb b/8_0/ch08/app/views/users/show.html.erb new file mode 100644 index 00000000..4fda5f89 --- /dev/null +++ b/8_0/ch08/app/views/users/show.html.erb @@ -0,0 +1,11 @@ +<% provide(:title, @user.name) %> +
+ +
diff --git a/8_0/ch08/bin/bundle b/8_0/ch08/bin/bundle new file mode 100755 index 00000000..981e650b --- /dev/null +++ b/8_0/ch08/bin/bundle @@ -0,0 +1,114 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + requirement = bundler_gem_version.approximate_recommendation + + return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0") + + requirement += ".a" if bundler_gem_version.prerelease? + + requirement + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/8_0/ch08/bin/dev b/8_0/ch08/bin/dev new file mode 100755 index 00000000..5f91c205 --- /dev/null +++ b/8_0/ch08/bin/dev @@ -0,0 +1,2 @@ +#!/usr/bin/env ruby +exec "./bin/rails", "server", *ARGV diff --git a/8_0/ch08/bin/importmap b/8_0/ch08/bin/importmap new file mode 100755 index 00000000..36502ab1 --- /dev/null +++ b/8_0/ch08/bin/importmap @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require_relative "../config/application" +require "importmap/commands" diff --git a/8_0/ch08/bin/rails b/8_0/ch08/bin/rails new file mode 100755 index 00000000..efc03774 --- /dev/null +++ b/8_0/ch08/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/8_0/ch08/bin/rake b/8_0/ch08/bin/rake new file mode 100755 index 00000000..4fbf10b9 --- /dev/null +++ b/8_0/ch08/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/8_0/ch08/bin/render-build.sh b/8_0/ch08/bin/render-build.sh new file mode 100755 index 00000000..6a61cb7f --- /dev/null +++ b/8_0/ch08/bin/render-build.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# exit on error +set -o errexit +bundle install +bundle exec rails assets:precompile +bundle exec rails assets:clean +bundle exec rails db:migrate diff --git a/8_0/ch08/bin/rubocop b/8_0/ch08/bin/rubocop new file mode 100755 index 00000000..40330c0f --- /dev/null +++ b/8_0/ch08/bin/rubocop @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +# explicit rubocop config increases performance slightly while avoiding config confusion. +ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) + +load Gem.bin_path("rubocop", "rubocop") diff --git a/8_0/ch08/bin/setup b/8_0/ch08/bin/setup new file mode 100755 index 00000000..be3db3c0 --- /dev/null +++ b/8_0/ch08/bin/setup @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +require "fileutils" + +APP_ROOT = File.expand_path("..", __dir__) + +def system!(*args) + system(*args, exception: true) +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts "== Installing dependencies ==" + system("bundle check") || system!("bundle install") + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + puts "\n== Removing old logs and tempfiles ==" + system! "bin/rails log:clear tmp:clear" + + unless ARGV.include?("--skip-server") + puts "\n== Starting development server ==" + STDOUT.flush # flush the output before exec(2) so that it displays + exec "bin/dev" + end +end diff --git a/8_0/ch08/config.ru b/8_0/ch08/config.ru new file mode 100644 index 00000000..4a3c09a6 --- /dev/null +++ b/8_0/ch08/config.ru @@ -0,0 +1,6 @@ +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/8_0/ch08/config/application.rb b/8_0/ch08/config/application.rb new file mode 100644 index 00000000..0461e72f --- /dev/null +++ b/8_0/ch08/config/application.rb @@ -0,0 +1,27 @@ +require_relative "boot" + +require "rails/all" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module SampleApp + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 8.0 + + # Please, add to the `ignore` list any other `lib` subdirectories that do + # not contain `.rb` files, or that should not be reloaded or eager loaded. + # Common ones are `templates`, `generators`, or `middleware`, for example. + config.autoload_lib(ignore: %w[assets tasks]) + + # Configuration for the application, engines, and railties goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + # + # config.time_zone = "Central Time (US & Canada)" + # config.eager_load_paths << Rails.root.join("extras") + end +end diff --git a/8_0/ch08/config/boot.rb b/8_0/ch08/config/boot.rb new file mode 100644 index 00000000..988a5ddc --- /dev/null +++ b/8_0/ch08/config/boot.rb @@ -0,0 +1,4 @@ +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "bundler/setup" # Set up gems listed in the Gemfile. +require "bootsnap/setup" # Speed up boot time by caching expensive operations. diff --git a/8_0/ch08/config/cable.yml b/8_0/ch08/config/cable.yml new file mode 100644 index 00000000..ae7a2256 --- /dev/null +++ b/8_0/ch08/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: codespaces_try_rails_production diff --git a/8_0/ch08/config/credentials.yml.enc b/8_0/ch08/config/credentials.yml.enc new file mode 100644 index 00000000..339d64d2 --- /dev/null +++ b/8_0/ch08/config/credentials.yml.enc @@ -0,0 +1 @@ +NCs9M8uakg1CDYJENuGaipzymY8uG6i5gzghsy4npa3gYmGfYKPHMnEcJHCVHlM3gxDNJBteUqkRCxHQ5WQId8hzgxoQCL5RX7rtQ8XUzkJwgSUjKvbCrG5atjKkN9XuL91is5vtEN5W7vEz3qxN7Rp33QbNFiDfLs71F3zHVDYEMi6Dy1QMhVCa1v/tyRjBu/5m37RuiYF5FzWJnh4LhexSVr7Agm4LRLLN6qLCpoAe6D3TPHHBdtu7Pvy8jlrEfnnkUJ61wrj8+kOL4uLtpFShdYKhDkoTjQk7TCOgWyifnHAXazkBZoJZ1QYv/tZ9/ZoHvMEQ2dtGHlyTX8fbKtI13d2CFjGaDNKvRuurxE8dbeyiTfbycvve46+cz9OTlmhh0SGt24R1WV6GBKoDUeoHrIiBvW5qmKka--dQ0rodxmfgFLg2y/--t2x0gQjh8FYlk8v79vLSNg== \ No newline at end of file diff --git a/8_0/ch08/config/database.yml b/8_0/ch08/config/database.yml new file mode 100644 index 00000000..fcba57f1 --- /dev/null +++ b/8_0/ch08/config/database.yml @@ -0,0 +1,25 @@ +# SQLite. Versions 3.8.0 and up are supported. +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem "sqlite3" +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/8_0/ch08/config/environment.rb b/8_0/ch08/config/environment.rb new file mode 100644 index 00000000..cac53157 --- /dev/null +++ b/8_0/ch08/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/8_0/ch08/config/environments/development.rb b/8_0/ch08/config/environments/development.rb new file mode 100644 index 00000000..910cef99 --- /dev/null +++ b/8_0/ch08/config/environments/development.rb @@ -0,0 +1,86 @@ +require 'active_support/core_ext/integer/time' + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Make code changes take effect immediately without server restart. + config.enable_reloading = true + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing. + config.server_timing = true + + # Add the following line to disable forgery_protection_origin_check + config.action_controller.forgery_protection_origin_check = false + + # Enable/disable Action Controller caching. By default Action Controller caching is disabled. + # Run rails dev:cache to toggle Action Controller caching. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + config.public_file_server.headers = { 'cache-control' => "public, max-age=#{2.days.to_i}" } + else + config.action_controller.perform_caching = false + end + + # Change to :null_store to avoid any caching. + config.cache_store = :memory_store + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Make template changes take effect immediately. + config.action_mailer.perform_caching = false + + # Set localhost to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Append comments with runtime information tags to SQL queries in logs. + config.active_record.query_log_tags_enabled = true + + # Highlight code that enqueued background job in logs. + config.active_job.verbose_enqueue_logs = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true + + # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. + # config.generators.apply_rubocop_autocorrect_after_generate! + + pf_domain = ENV['GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN'] + config.action_dispatch.default_headers = { + 'X-Frame-Options' => "ALLOW-FROM #{pf_domain}" + } + + # Allow requests from our preview domain. + pf_host = "#{ENV['CODESPACE_NAME']}-3000.#{pf_domain}" + config.hosts << pf_host + + config.action_cable.allowed_request_origins = ["https://#{pf_host}"] +end diff --git a/8_0/ch08/config/environments/production.rb b/8_0/ch08/config/environments/production.rb new file mode 100644 index 00000000..17496077 --- /dev/null +++ b/8_0/ch08/config/environments/production.rb @@ -0,0 +1,89 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.enable_reloading = false + + # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). + config.eager_load = true + + # Full error reports are disabled. + config.consider_all_requests_local = false + + # Turn on fragment caching in view templates. + config.action_controller.perform_caching = true + + # Cache assets for far-future expiry since they are all digest stamped. + config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" } + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = "http://assets.example.com" + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Assume all access to the app is happening through a SSL-terminating reverse proxy. + config.assume_ssl = true + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = true + + # Skip http-to-https redirect for the default health check endpoint. + # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } + + # Log to STDOUT with the current request id as a default log tag. + config.log_tags = [ :request_id ] + config.logger = ActiveSupport::TaggedLogging.logger(STDOUT) + + # Change to "debug" to log everything (including potentially personally-identifiable information!) + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + + # Prevent health checks from clogging up the logs. + config.silence_healthcheck_path = "/up" + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Replace the default in-process memory cache store with a durable alternative. + # config.cache_store = :mem_cache_store + + # Replace the default in-process and non-durable queuing backend for Active Job. + # config.active_job.queue_adapter = :resque + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. + # config.action_mailer.smtp_settings = { + # user_name: Rails.application.credentials.dig(:smtp, :user_name), + # password: Rails.application.credentials.dig(:smtp, :password), + # address: "smtp.example.com", + # port: 587, + # authentication: :plain + # } + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Only use :id for inspections in production. + config.active_record.attributes_for_inspect = [ :id ] + + # Enable DNS rebinding protection and other `Host` header attacks. + # config.hosts = [ + # "example.com", # Allow requests from example.com + # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` + # ] + # + # Skip DNS rebinding protection for the default health check endpoint. + # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } +end diff --git a/8_0/ch08/config/environments/test.rb b/8_0/ch08/config/environments/test.rb new file mode 100644 index 00000000..0ebe3b4e --- /dev/null +++ b/8_0/ch08/config/environments/test.rb @@ -0,0 +1,53 @@ +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # While tests run files are not watched, reloading is not necessary. + config.enable_reloading = false + + # Eager loading loads your entire application. When running a single test locally, + # this is usually not necessary, and can slow down your test suite. However, it's + # recommended that you enable it in continuous integration systems to ensure eager + # loading is working properly before deploying your code. + config.eager_load = ENV["CI"].present? + + # Configure public file server for tests with cache-control for performance. + config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } + + # Show full error reports. + config.consider_all_requests_local = true + config.cache_store = :null_store + + # Render exception templates for rescuable exceptions and raise for other exceptions. + config.action_dispatch.show_exceptions = :none + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :test + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true +end diff --git a/8_0/ch08/config/importmap.rb b/8_0/ch08/config/importmap.rb new file mode 100644 index 00000000..caf3ed06 --- /dev/null +++ b/8_0/ch08/config/importmap.rb @@ -0,0 +1,8 @@ +# Pin npm packages by running ./bin/importmap + +pin "application", preload: true +pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true +pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true +pin_all_from "app/javascript/controllers", under: "controllers" +pin_all_from "app/javascript/custom", under: "custom" diff --git a/8_0/ch08/config/initializers/assets.rb b/8_0/ch08/config/initializers/assets.rb new file mode 100644 index 00000000..48732442 --- /dev/null +++ b/8_0/ch08/config/initializers/assets.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = "1.0" + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path diff --git a/8_0/ch08/config/initializers/content_security_policy.rb b/8_0/ch08/config/initializers/content_security_policy.rb new file mode 100644 index 00000000..b3076b38 --- /dev/null +++ b/8_0/ch08/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header + +# Rails.application.configure do +# config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end +# +# # Generate session nonces for permitted importmap, inline scripts, and inline styles. +# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } +# config.content_security_policy_nonce_directives = %w(script-src style-src) +# +# # Report violations without enforcing the policy. +# # config.content_security_policy_report_only = true +# end diff --git a/8_0/ch08/config/initializers/filter_parameter_logging.rb b/8_0/ch08/config/initializers/filter_parameter_logging.rb new file mode 100644 index 00000000..c0b717f7 --- /dev/null +++ b/8_0/ch08/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. +# Use this to limit dissemination of sensitive information. +# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. +Rails.application.config.filter_parameters += [ + :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc +] diff --git a/8_0/ch08/config/initializers/inflections.rb b/8_0/ch08/config/initializers/inflections.rb new file mode 100644 index 00000000..3860f659 --- /dev/null +++ b/8_0/ch08/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym "RESTful" +# end diff --git a/8_0/ch08/config/initializers/new_framework_defaults_8_0.rb b/8_0/ch08/config/initializers/new_framework_defaults_8_0.rb new file mode 100644 index 00000000..92efa951 --- /dev/null +++ b/8_0/ch08/config/initializers/new_framework_defaults_8_0.rb @@ -0,0 +1,30 @@ +# Be sure to restart your server when you modify this file. +# +# This file eases your Rails 8.0 framework defaults upgrade. +# +# Uncomment each configuration one by one to switch to the new default. +# Once your application is ready to run with all new defaults, you can remove +# this file and set the `config.load_defaults` to `8.0`. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. +# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html + +### +# Specifies whether `to_time` methods preserve the UTC offset of their receivers or preserves the timezone. +# If set to `:zone`, `to_time` methods will use the timezone of their receivers. +# If set to `:offset`, `to_time` methods will use the UTC offset. +# If `false`, `to_time` methods will convert to the local system UTC offset instead. +#++ +# Rails.application.config.active_support.to_time_preserves_timezone = :zone + +### +# When both `If-Modified-Since` and `If-None-Match` are provided by the client +# only consider `If-None-Match` as specified by RFC 7232 Section 6. +# If set to `false` both conditions need to be satisfied. +#++ +# Rails.application.config.action_dispatch.strict_freshness = true + +### +# Set `Regexp.timeout` to `1`s by default to improve security over Regexp Denial-of-Service attacks. +#++ +# Regexp.timeout = 1 diff --git a/8_0/ch08/config/initializers/permissions_policy.rb b/8_0/ch08/config/initializers/permissions_policy.rb new file mode 100644 index 00000000..00f64d71 --- /dev/null +++ b/8_0/ch08/config/initializers/permissions_policy.rb @@ -0,0 +1,11 @@ +# Define an application-wide HTTP permissions policy. For further +# information see https://developers.google.com/web/updates/2018/06/feature-policy +# +# Rails.application.config.permissions_policy do |f| +# f.camera :none +# f.gyroscope :none +# f.microphone :none +# f.usb :none +# f.fullscreen :self +# f.payment :self, "https://secure.example.com" +# end diff --git a/8_0/ch08/config/locales/en.yml b/8_0/ch08/config/locales/en.yml new file mode 100644 index 00000000..8ca56fc7 --- /dev/null +++ b/8_0/ch08/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# "true": "foo" +# +# To learn more, please read the Rails Internationalization guide +# available at https://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/8_0/ch08/config/puma.rb b/8_0/ch08/config/puma.rb new file mode 100644 index 00000000..a248513b --- /dev/null +++ b/8_0/ch08/config/puma.rb @@ -0,0 +1,41 @@ +# This configuration file will be evaluated by Puma. The top-level methods that +# are invoked here are part of Puma's configuration DSL. For more information +# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. +# +# Puma starts a configurable number of processes (workers) and each process +# serves each request in a thread from an internal thread pool. +# +# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You +# should only set this value when you want to run 2 or more workers. The +# default is already 1. +# +# The ideal number of threads per worker depends both on how much time the +# application spends waiting for IO operations and on how much you wish to +# prioritize throughput over latency. +# +# As a rule of thumb, increasing the number of threads will increase how much +# traffic a given process can handle (throughput), but due to CRuby's +# Global VM Lock (GVL) it has diminishing returns and will degrade the +# response time (latency) of the application. +# +# The default is set to 3 threads as it's deemed a decent compromise between +# throughput and latency for the average Rails application. +# +# Any libraries that use a connection pool or another resource pool should +# be configured to provide at least as many connections as the number of +# threads. This includes Active Record's `pool` parameter in `database.yml`. +threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +port ENV.fetch("PORT", 3000) + +# Allow puma to be restarted by `bin/rails restart` command. +plugin :tmp_restart + +# Run the Solid Queue supervisor inside of Puma for single-server deployments +plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] + +# Specify the PID file. Defaults to tmp/pids/server.pid in development. +# In other environments, only set the PID file if requested. +pidfile ENV["PIDFILE"] if ENV["PIDFILE"] diff --git a/8_0/ch08/config/routes.rb b/8_0/ch08/config/routes.rb new file mode 100644 index 00000000..197ff607 --- /dev/null +++ b/8_0/ch08/config/routes.rb @@ -0,0 +1,11 @@ +Rails.application.routes.draw do + root "static_pages#home" + get "/help", to: "static_pages#help" + get "/about", to: "static_pages#about" + get "/contact", to: "static_pages#contact" + get "/signup", to: "users#new" + get "/login", to: "sessions#new" + post "/login", to: "sessions#create" + delete "/logout", to: "sessions#destroy" + resources :users +end diff --git a/8_0/ch08/config/storage.yml b/8_0/ch08/config/storage.yml new file mode 100644 index 00000000..4942ab66 --- /dev/null +++ b/8_0/ch08/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket-<%= Rails.env %> + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket-<%= Rails.env %> + +# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name-<%= Rails.env %> + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/8_0/ch08/db/migrate/20260512080056_create_users.rb b/8_0/ch08/db/migrate/20260512080056_create_users.rb new file mode 100644 index 00000000..3b80eb00 --- /dev/null +++ b/8_0/ch08/db/migrate/20260512080056_create_users.rb @@ -0,0 +1,10 @@ +class CreateUsers < ActiveRecord::Migration[8.0] + def change + create_table :users do |t| + t.string :name + t.string :email + + t.timestamps + end + end +end diff --git a/8_0/ch08/db/migrate/20260512080840_add_index_to_users_email.rb b/8_0/ch08/db/migrate/20260512080840_add_index_to_users_email.rb new file mode 100644 index 00000000..a8e4ed8e --- /dev/null +++ b/8_0/ch08/db/migrate/20260512080840_add_index_to_users_email.rb @@ -0,0 +1,5 @@ +class AddIndexToUsersEmail < ActiveRecord::Migration[8.0] + def change + add_index :users, :email, unique: true + end +end diff --git a/8_0/ch08/db/migrate/20260512081511_add_password_digest_to_users.rb b/8_0/ch08/db/migrate/20260512081511_add_password_digest_to_users.rb new file mode 100644 index 00000000..81c7c9e0 --- /dev/null +++ b/8_0/ch08/db/migrate/20260512081511_add_password_digest_to_users.rb @@ -0,0 +1,5 @@ +class AddPasswordDigestToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :password_digest, :string + end +end diff --git a/8_0/ch08/db/schema.rb b/8_0/ch08/db/schema.rb new file mode 100644 index 00000000..e22275fc --- /dev/null +++ b/8_0/ch08/db/schema.rb @@ -0,0 +1,22 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[8.0].define(version: 2026_05_12_081511) do + create_table "users", force: :cascade do |t| + t.string "name" + t.string "email" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "password_digest" + t.index ["email"], name: "index_users_on_email", unique: true + end +end diff --git a/8_0/ch08/db/seeds.rb b/8_0/ch08/db/seeds.rb new file mode 100644 index 00000000..bc25fce3 --- /dev/null +++ b/8_0/ch08/db/seeds.rb @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). +# +# Examples: +# +# movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }]) +# Character.create(name: "Luke", movie: movies.first) diff --git a/8_0/ch08/lib/assets/.keep b/8_0/ch08/lib/assets/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/lib/tasks/.keep b/8_0/ch08/lib/tasks/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/log/.keep b/8_0/ch08/log/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/public/400.html b/8_0/ch08/public/400.html new file mode 100644 index 00000000..282dbc8c --- /dev/null +++ b/8_0/ch08/public/400.html @@ -0,0 +1,114 @@ + + + + + + + The server cannot process the request due to a client error (400 Bad Request) + + + + + + + + + + + + + +
+
+ +
+
+

The server cannot process the request due to a client error. Please check the request and try again. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch08/public/404.html b/8_0/ch08/public/404.html new file mode 100644 index 00000000..c0670bc8 --- /dev/null +++ b/8_0/ch08/public/404.html @@ -0,0 +1,114 @@ + + + + + + + The page you were looking for doesn’t exist (404 Not found) + + + + + + + + + + + + + +
+
+ +
+
+

The page you were looking for doesn’t exist. You may have mistyped the address or the page may have moved. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch08/public/406-unsupported-browser.html b/8_0/ch08/public/406-unsupported-browser.html new file mode 100644 index 00000000..9532a9cc --- /dev/null +++ b/8_0/ch08/public/406-unsupported-browser.html @@ -0,0 +1,114 @@ + + + + + + + Your browser is not supported (406 Not Acceptable) + + + + + + + + + + + + + +
+
+ +
+
+

Your browser is not supported.
Please upgrade your browser to continue.

+
+
+ + + + diff --git a/8_0/ch08/public/422.html b/8_0/ch08/public/422.html new file mode 100644 index 00000000..8bcf0601 --- /dev/null +++ b/8_0/ch08/public/422.html @@ -0,0 +1,114 @@ + + + + + + + The change you wanted was rejected (422 Unprocessable Entity) + + + + + + + + + + + + + +
+
+ +
+
+

The change you wanted was rejected. Maybe you tried to change something you didn’t have access to. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch08/public/500.html b/8_0/ch08/public/500.html new file mode 100644 index 00000000..d77718c3 --- /dev/null +++ b/8_0/ch08/public/500.html @@ -0,0 +1,114 @@ + + + + + + + We’re sorry, but something went wrong (500 Internal Server Error) + + + + + + + + + + + + + +
+
+ +
+
+

We’re sorry, but something went wrong.
If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch08/public/apple-touch-icon-precomposed.png b/8_0/ch08/public/apple-touch-icon-precomposed.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/public/apple-touch-icon.png b/8_0/ch08/public/apple-touch-icon.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/public/favicon.ico b/8_0/ch08/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/public/icon.png b/8_0/ch08/public/icon.png new file mode 100644 index 00000000..c4c9dbfb Binary files /dev/null and b/8_0/ch08/public/icon.png differ diff --git a/8_0/ch08/public/icon.svg b/8_0/ch08/public/icon.svg new file mode 100644 index 00000000..04b34bf8 --- /dev/null +++ b/8_0/ch08/public/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/8_0/ch08/public/railstutorial.png b/8_0/ch08/public/railstutorial.png new file mode 100644 index 00000000..70835e6b Binary files /dev/null and b/8_0/ch08/public/railstutorial.png differ diff --git a/8_0/ch08/public/robots.txt b/8_0/ch08/public/robots.txt new file mode 100644 index 00000000..c19f78ab --- /dev/null +++ b/8_0/ch08/public/robots.txt @@ -0,0 +1 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/8_0/ch08/storage/.keep b/8_0/ch08/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/test/application_system_test_case.rb b/8_0/ch08/test/application_system_test_case.rb new file mode 100644 index 00000000..d19212ab --- /dev/null +++ b/8_0/ch08/test/application_system_test_case.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/8_0/ch08/test/channels/application_cable/connection_test.rb b/8_0/ch08/test/channels/application_cable/connection_test.rb new file mode 100644 index 00000000..800405f1 --- /dev/null +++ b/8_0/ch08/test/channels/application_cable/connection_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase + # test "connects with cookies" do + # cookies.signed[:user_id] = 42 + # + # connect + # + # assert_equal connection.user_id, "42" + # end +end diff --git a/8_0/ch08/test/controllers/.keep b/8_0/ch08/test/controllers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/test/controllers/hello_codespaces_controller_test.rb b/8_0/ch08/test/controllers/hello_codespaces_controller_test.rb new file mode 100644 index 00000000..54353ea6 --- /dev/null +++ b/8_0/ch08/test/controllers/hello_codespaces_controller_test.rb @@ -0,0 +1,8 @@ +require "test_helper" + +class HelloCodespacesControllerTest < ActionDispatch::IntegrationTest + # test "should get index" do + # get "/" + # assert_response :success + # end +end diff --git a/8_0/ch08/test/controllers/sessions_controller_test.rb b/8_0/ch08/test/controllers/sessions_controller_test.rb new file mode 100644 index 00000000..8cc3f29e --- /dev/null +++ b/8_0/ch08/test/controllers/sessions_controller_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +class SessionsControllerTest < ActionDispatch::IntegrationTest + + test "should get new" do + get login_path + assert_response :success + end +end diff --git a/8_0/ch08/test/controllers/static_pages_controller_test.rb b/8_0/ch08/test/controllers/static_pages_controller_test.rb new file mode 100644 index 00000000..847856a3 --- /dev/null +++ b/8_0/ch08/test/controllers/static_pages_controller_test.rb @@ -0,0 +1,28 @@ +require "test_helper" + +class StaticPagesControllerTest < ActionDispatch::IntegrationTest + + test "should get home" do + get root_path + assert_response :success + assert_select "title", "Ruby on Rails Tutorial Sample App" + end + + test "should get help" do + get help_path + assert_response :success + assert_select "title", "Help | Ruby on Rails Tutorial Sample App" + end + + test "should get about" do + get about_path + assert_response :success + assert_select "title", "About | Ruby on Rails Tutorial Sample App" + end + + test "should get contact" do + get contact_path + assert_response :success + assert_select "title", "Contact | Ruby on Rails Tutorial Sample App" + end +end diff --git a/8_0/ch08/test/controllers/users_controller_test.rb b/8_0/ch08/test/controllers/users_controller_test.rb new file mode 100644 index 00000000..2b123196 --- /dev/null +++ b/8_0/ch08/test/controllers/users_controller_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +class UsersControllerTest < ActionDispatch::IntegrationTest + + test "should get new" do + get signup_path + assert_response :success + end +end diff --git a/8_0/ch08/test/fixtures/files/.keep b/8_0/ch08/test/fixtures/files/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/test/fixtures/users.yml b/8_0/ch08/test/fixtures/users.yml new file mode 100644 index 00000000..15c78353 --- /dev/null +++ b/8_0/ch08/test/fixtures/users.yml @@ -0,0 +1,4 @@ +michael: + name: Michael Example + email: michael@example.com + password_digest: <%= User.digest('password') %> diff --git a/8_0/ch08/test/helpers/.keep b/8_0/ch08/test/helpers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/test/integration/.keep b/8_0/ch08/test/integration/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/test/integration/site_layout_test.rb b/8_0/ch08/test/integration/site_layout_test.rb new file mode 100644 index 00000000..0f7ca649 --- /dev/null +++ b/8_0/ch08/test/integration/site_layout_test.rb @@ -0,0 +1,13 @@ +require "test_helper" + +class SiteLayoutTest < ActionDispatch::IntegrationTest + + test "layout links" do + get root_path + assert_template 'static_pages/home' + assert_select "a[href=?]", root_path, count: 2 + assert_select "a[href=?]", help_path + assert_select "a[href=?]", about_path + assert_select "a[href=?]", contact_path + end +end diff --git a/8_0/ch08/test/integration/users_login_test.rb b/8_0/ch08/test/integration/users_login_test.rb new file mode 100644 index 00000000..905fc7e3 --- /dev/null +++ b/8_0/ch08/test/integration/users_login_test.rb @@ -0,0 +1,41 @@ +require "test_helper" + +class UsersLoginTest < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + end + + test "login with valid email/invalid password" do + get login_path + assert_template 'sessions/new' + post login_path, params: { session: { email: @user.email, + password: "invalid" } } + assert_not is_logged_in? + assert_response :unprocessable_entity + assert_template 'sessions/new' + assert_not flash.empty? + get root_path + assert flash.empty? + end + + test "login with valid information followed by logout" do + post login_path, params: { session: { email: @user.email, + password: 'password' } } + assert is_logged_in? + assert_redirected_to @user + follow_redirect! + assert_template 'users/show' + assert_select "a[href=?]", login_path, count: 0 + assert_select "a[href=?]", logout_path + assert_select "a[href=?]", user_path(@user) + delete logout_path + assert_not is_logged_in? + assert_response :see_other + assert_redirected_to root_url + follow_redirect! + assert_select "a[href=?]", login_path + assert_select "a[href=?]", logout_path, count: 0 + assert_select "a[href=?]", user_path(@user), count: 0 + end +end diff --git a/8_0/ch08/test/integration/users_signup_test.rb b/8_0/ch08/test/integration/users_signup_test.rb new file mode 100644 index 00000000..cbd8ff99 --- /dev/null +++ b/8_0/ch08/test/integration/users_signup_test.rb @@ -0,0 +1,28 @@ +require "test_helper" + +class UsersSignupTest < ActionDispatch::IntegrationTest + + test "invalid signup information" do + get signup_path + assert_no_difference 'User.count' do + post users_path, params: { user: { name: "", + email: "user@invalid", + password: "foo", + password_confirmation: "bar" } } + end + assert_response :unprocessable_entity + assert_template 'users/new' + end + + test "valid signup information" do + assert_difference 'User.count', 1 do + post users_path, params: { user: { name: "Example User", + email: "user@example.com", + password: "password", + password_confirmation: "password" } } + end + follow_redirect! + assert_template 'users/show' + assert is_logged_in? + end +end diff --git a/8_0/ch08/test/mailers/.keep b/8_0/ch08/test/mailers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/test/models/.keep b/8_0/ch08/test/models/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/test/models/user_test.rb b/8_0/ch08/test/models/user_test.rb new file mode 100644 index 00000000..a56aa365 --- /dev/null +++ b/8_0/ch08/test/models/user_test.rb @@ -0,0 +1,67 @@ +require "test_helper" + +class UserTest < ActiveSupport::TestCase + + def setup + @user = User.new(name: "Example User", email: "user@example.com", + password: "foobar", password_confirmation: "foobar") + end + + test "should be valid" do + assert @user.valid? + end + + test "name should be present" do + @user.name = " " + assert_not @user.valid? + end + + test "email should be present" do + @user.email = " " + assert_not @user.valid? + end + + test "name should not be too long" do + @user.name = "a" * 51 + assert_not @user.valid? + end + + test "email should not be too long" do + @user.email = "a" * 244 + "@example.com" + assert_not @user.valid? + end + + test "email validation should accept valid addresses" do + valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org + first.last@foo.jp alice+bob@baz.cn] + valid_addresses.each do |valid_address| + @user.email = valid_address + assert @user.valid?, "#{valid_address.inspect} should be valid" + end + end + + test "email validation should reject invalid addresses" do + invalid_addresses = %w[user@example,com user_at_foo.org user.name@example. + foo@bar_baz.com foo@bar+baz.com] + invalid_addresses.each do |invalid_address| + @user.email = invalid_address + assert_not @user.valid?, "#{invalid_address.inspect} should be invalid" + end + end + + test "email addresses should be unique" do + duplicate_user = @user.dup + @user.save + assert_not duplicate_user.valid? + end + + test "password should be present (nonblank)" do + @user.password = @user.password_confirmation = " " * 6 + assert_not @user.valid? + end + + test "password should have a minimum length" do + @user.password = @user.password_confirmation = "a" * 5 + assert_not @user.valid? + end +end diff --git a/8_0/ch08/test/system/.keep b/8_0/ch08/test/system/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/test/test_helper.rb b/8_0/ch08/test/test_helper.rb new file mode 100644 index 00000000..3f416069 --- /dev/null +++ b/8_0/ch08/test/test_helper.rb @@ -0,0 +1,18 @@ +ENV["RAILS_ENV"] ||= "test" +require_relative "../config/environment" +require "rails/test_help" +require "minitest/reporters" +Minitest::Reporters.use! + +class ActiveSupport::TestCase + # 指定のワーカー数でテストを並列実行する + parallelize(workers: :number_of_processors) + + # test/fixtures/*.ymlにあるすべてのfixtureをセットアップする + fixtures :all + + # テストユーザーがログイン中の場合にtrueを返す + def is_logged_in? + !session[:user_id].nil? + end +end diff --git a/8_0/ch08/tmp/.keep b/8_0/ch08/tmp/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/tmp/pids/.keep b/8_0/ch08/tmp/pids/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/tmp/storage/.keep b/8_0/ch08/tmp/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/vendor/.keep b/8_0/ch08/vendor/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch08/vendor/javascript/.keep b/8_0/ch08/vendor/javascript/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/.devcontainer/devcontainer.json b/8_0/ch09/.devcontainer/devcontainer.json new file mode 100644 index 00000000..e7243217 --- /dev/null +++ b/8_0/ch09/.devcontainer/devcontainer.json @@ -0,0 +1,105 @@ +// Specifications: https://containers.dev/implementors/json_reference/ +// Format details: https://aka.ms/devcontainer.json +// Config options: https://github.com/microsoft/vscode-dev-containers/tree/main/containers/ruby +{ + "name": "railstutorial", + + // Universal is well-customized image for Codespaces: + // https://hub.docker.com/_/microsoft-devcontainers-universal + //"image": "mcr.microsoft.com/devcontainers/universal:2", + //"features": { + // "ghcr.io/devcontainers/features/ruby:1": {} + //}, + + // Use Ruby image if you want to pin Ruby version like '3.2' + // https://github.com/devcontainers/images/tree/main/src/ruby + "image": "mcr.microsoft.com/devcontainers/ruby:3.2", + //"workspaceFolder": "/railstutorial", => Container build failed + + // Enable learners to choose an affordable spec, starting at minimum one. + //"hostRequirements": { + // "cpus": 2, + // "memory": "4gb", + // "storage": "32gb" + //}, + + "waitFor": "onCreateCommand", + //"onCreateCommand": "", + "onCreateCommand": "gem install solargraph -v '0.58.2' -N", + //"onCreateCommand": "gem install solargraph -v '0.50.0' -N && gem install ruby-lsp -N", + //"onCreateCommand": "gem install ruby-lsp -N", + //# => Solargraph gem not found. Run `gem install solargraph` or update your Gemfile. + "updateContentCommand": "bundle install", + "postCreateCommand": "", + "postAttachCommand": { + "server": "rails server" + }, + "customizations": { + "codespaces": { + "openFiles": [ + "app/views/hello/index.html.erb" + ] + }, + "vscode": { + "extensions": [ + "GitHub.codespaces", + "Shopify.ruby-lsp", // https://github.com/Shopify/ruby-lsp + "castwide.solargraph" // https://github.com/castwide/vscode-solargraph + //"rebornix.Ruby", // https://github.com/rubyide/vscode-ruby (Deprecated) + + ], + "settings": { + // General settings for Codespaces (VS Code) + //"ruby.useLanguageServer": true , + //"ruby.format": "rubocop", + //"ruby.lint": { "rubocop": true }, + //"ruby.intellisense": "rubyLocate", + "editor.tabSize": 2, + "editor.formatOnSave": false, // Disable onSave to show diff edited by learners only + "editor.formatOnType": false, // Disable onType for the same reason above + "editor.insertSpaces": true, // Use spaces, not tabs, to avoid errors for learners + "editor.renderWhitespace": "none", + "[ruby]": { + "editor.defaultFormatter": "castwide.solargraph", + "editor.semanticHighlighting.enabled": true // Enable semantic highlighting + }, + "files.associations": { "*.erb": "erb" }, + "emmet.includeLanguages": { "erb": "html" }, + + // Settings for Solargraph + // https://github.com/castwide/solargraph + "solargraph.useBundler": false, + "solargraph.diagnostics": false, + "solargraph.formatting": true, // Use Ctrl+Shift+P->Format to format + "solargraph.autoformat": false, + "solargraph.definitions": true, + "solargraph.completion": true, + "solargraph.references": true, + "solargraph.symbols": true, + "solargraph.rename": true, + "solargraph.hover": true, + + // Settings for Ruby LSP + // https://github.com/Shopify/ruby-lsp + "rubyLsp.rubyVersionManager": { + "identifier": "none" + }, + "rubyLsp.formatter": "none", + "rubyLsp.enabledFeatures": { + "diagnostics": false, + "formatting": false + } + } + } + }, + "remoteEnv": { + "EDITOR": "code --wait" + }, + "portsAttributes": { + "3000": { + "label": "Application", + "onAutoForward": "openPreview" + } + }, + "forwardPorts": [3000] +} diff --git a/8_0/ch09/.devcontainer/icon.svg b/8_0/ch09/.devcontainer/icon.svg new file mode 100644 index 00000000..f8444a4a --- /dev/null +++ b/8_0/ch09/.devcontainer/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/8_0/ch09/.gitattributes b/8_0/ch09/.gitattributes new file mode 100644 index 00000000..31eeee0b --- /dev/null +++ b/8_0/ch09/.gitattributes @@ -0,0 +1,7 @@ +# See https://git-scm.com/docs/gitattributes for more about git attribute files. + +# Mark the database schema as having been generated. +db/schema.rb linguist-generated + +# Mark any vendored files as having been vendored. +vendor/* linguist-vendored diff --git a/8_0/ch09/.gitignore b/8_0/ch09/.gitignore new file mode 100644 index 00000000..e1ef53ef --- /dev/null +++ b/8_0/ch09/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-* + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore uploaded files in development. +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/ +!/tmp/storage/.keep + +/public/assets + +# Ignore master key for decrypting credentials and more. +/config/master.key + +# Ignore history file used by IRB for interactive Ruby. +.irb_history + +dump.rdb diff --git a/8_0/ch09/.irbrc b/8_0/ch09/.irbrc new file mode 100644 index 00000000..3737e036 --- /dev/null +++ b/8_0/ch09/.irbrc @@ -0,0 +1 @@ +IRB.conf[:COMPLETOR] = :type # default is :regexp diff --git a/8_0/ch09/.solargraph.yml b/8_0/ch09/.solargraph.yml new file mode 100644 index 00000000..76ac24a6 --- /dev/null +++ b/8_0/ch09/.solargraph.yml @@ -0,0 +1,32 @@ +--- +include: +- "**/*.rb" +exclude: +- spec/**/* +- test/**/* +- vendor/**/* +- ".bundle/**/*" +require: +- actioncable +- actionmailer +- actionpack +- actionview +- activejob +- activemodel +- activerecord +- activestorage +- activesupport +domains: [] +reporters: +- rubocop +- require_not_found +- typecheck +formatter: + rubocop: + cops: safe + except: [] + only: [] + extra_args: [] +require_paths: [] +plugins: [] +max_files: 5000 diff --git a/8_0/ch09/.vscode/settings.json b/8_0/ch09/.vscode/settings.json new file mode 100644 index 00000000..cf85f519 --- /dev/null +++ b/8_0/ch09/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.minimap.enabled": false +} diff --git a/8_0/ch09/Gemfile b/8_0/ch09/Gemfile new file mode 100644 index 00000000..a015b466 --- /dev/null +++ b/8_0/ch09/Gemfile @@ -0,0 +1,44 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '3.2.10' + +gem 'bcrypt', '3.1.18' +gem 'bootsnap', '1.16.0', require: false +gem 'bootstrap-sass', '3.4.1' +gem 'concurrent-ruby', '1.3.4' +gem 'importmap-rails', '1.1.5' +gem 'jbuilder', '2.14.1' +gem 'puma', '6.6.1' +gem 'rails', '8.0.2.1' +gem 'sassc-rails', '2.1.2' +gem 'sprockets-rails', '3.4.2' +gem 'sqlite3', '2.7.3' +gem 'stimulus-rails', '1.2.1' +gem 'turbo-rails', '1.4.0' + +group :development, :test do + gem 'debug', '1.7.1', platforms: %i[mri mingw x64_mingw] + gem 'reline', '0.5.10' +end + +group :development do + gem 'irb', '1.15.2' + gem 'repl_type_completor', '0.1.10' + gem 'solargraph', '0.58.2' + gem 'web-console', '4.2.0' +end + +group :test do + gem 'capybara', '3.38.0' + gem 'guard', '2.18.0' + gem 'guard-minitest', '2.4.6' + gem 'minitest', '5.18.0' + gem 'minitest-reporters', '1.6.0' + gem 'rails-controller-testing', '1.0.5' + gem 'selenium-webdriver', '4.8.3' + gem 'webdrivers', '5.2.0' +end + +# Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります +# gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] diff --git a/8_0/ch09/Gemfile.lock b/8_0/ch09/Gemfile.lock new file mode 100644 index 00000000..119a783d --- /dev/null +++ b/8_0/ch09/Gemfile.lock @@ -0,0 +1,442 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + actionmailer (8.0.2.1) + actionpack (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.0.2.1) + actionview (= 8.0.2.1) + activesupport (= 8.0.2.1) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.0.2.1) + actionpack (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (8.0.2.1) + activesupport (= 8.0.2.1) + builder (~> 3.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.3.6) + activemodel (8.0.2.1) + activesupport (= 8.0.2.1) + activerecord (8.0.2.1) + activemodel (= 8.0.2.1) + activesupport (= 8.0.2.1) + timeout (>= 0.4.0) + activestorage (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activesupport (= 8.0.2.1) + marcel (~> 1.0) + activesupport (8.0.2.1) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + ansi (1.5.0) + ast (2.4.3) + autoprefixer-rails (10.4.21.0) + execjs (~> 2) + backport (1.2.0) + base64 (0.3.0) + bcrypt (3.1.18) + benchmark (0.4.1) + bigdecimal (3.2.3) + bindex (0.8.1) + bootsnap (1.16.0) + msgpack (~> 1.2) + bootstrap-sass (3.4.1) + autoprefixer-rails (>= 5.2.1) + sassc (>= 2.0.0) + builder (3.3.0) + capybara (3.38.0) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + coderay (1.1.3) + concurrent-ruby (1.3.4) + connection_pool (2.5.4) + crass (1.0.6) + date (3.4.1) + debug (1.7.1) + irb (>= 1.5.0) + reline (>= 0.3.1) + diff-lcs (1.6.2) + drb (2.2.3) + erb (5.0.2) + erubi (1.13.1) + execjs (2.10.1) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) + formatador (1.2.0) + reline + globalid (1.2.1) + activesupport (>= 6.1) + guard (2.18.0) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.13.0) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-minitest (2.4.6) + guard-compat (~> 1.2) + minitest (>= 3.0) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + importmap-rails (1.1.5) + actionpack (>= 6.0.0) + railties (>= 6.0.0) + io-console (0.8.1) + irb (1.15.2) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jaro_winkler (1.6.1) + jbuilder (2.14.1) + actionview (>= 7.0.0) + activesupport (>= 7.0.0) + json (2.13.2) + kramdown (2.5.1) + rexml (>= 3.3.9) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) + loofah (2.24.1) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + lumberjack (1.4.1) + mail (2.8.1) + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.0.4) + matrix (0.4.3) + method_source (1.1.0) + mini_mime (1.1.5) + minitest (5.18.0) + minitest-reporters (1.6.0) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + msgpack (1.8.0) + nenv (0.3.0) + net-imap (0.5.10) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.4) + nokogiri (1.19.1-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-musl) + racc (~> 1.4) + notiffany (0.1.3) + nenv (~> 0.1) + shellany (~> 0.0) + observer (0.1.2) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.9.0) + ast (~> 2.4.1) + racc + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + prism (1.4.0) + pry (0.15.2) + coderay (~> 1.1) + method_source (~> 1.0) + psych (5.2.6) + date + stringio + public_suffix (6.0.2) + puma (6.6.1) + nio4r (~> 2.0) + racc (1.8.1) + rack (3.2.5) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.2.1) + rack (>= 3) + rails (8.0.2.1) + actioncable (= 8.0.2.1) + actionmailbox (= 8.0.2.1) + actionmailer (= 8.0.2.1) + actionpack (= 8.0.2.1) + actiontext (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activemodel (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + bundler (>= 1.15.0) + railties (= 8.0.2.1) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.0) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rbs (3.6.1) + logger + rdoc (6.14.2) + erb + psych (>= 4.0.0) + regexp_parser (2.11.2) + reline (0.5.10) + io-console (~> 0.5) + repl_type_completor (0.1.10) + prism (~> 1.0) + rbs (>= 2.7.0, < 4.0.0) + reverse_markdown (3.0.0) + nokogiri + rexml (3.4.2) + rubocop (1.80.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.46.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.46.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + ruby-progressbar (1.13.0) + rubyzip (2.4.1) + sassc (2.4.0) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt + securerandom (0.4.1) + selenium-webdriver (4.8.3) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) + shellany (0.0.1) + solargraph (0.58.2) + backport (~> 1.2) + benchmark (~> 0.4) + bundler (~> 2.0) + diff-lcs (~> 1.4) + jaro_winkler (~> 1.6, >= 1.6.1) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.1) + logger (~> 1.6) + observer (~> 0.1) + ostruct (~> 0.6) + parser (~> 3.0) + prism (~> 1.4) + rbs (~> 3.6.1) + reverse_markdown (~> 3.0) + rubocop (~> 1.38) + thor (~> 1.0) + tilt (~> 2.0) + yard (~> 0.9, >= 0.9.24) + yard-solargraph (~> 0.1) + sprockets (4.2.2) + concurrent-ruby (~> 1.0) + logger + rack (>= 2.2.4, < 4) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + sprockets (>= 3.0.0) + sqlite3 (2.7.3-aarch64-linux-gnu) + sqlite3 (2.7.3-aarch64-linux-musl) + sqlite3 (2.7.3-arm-linux-gnu) + sqlite3 (2.7.3-arm-linux-musl) + sqlite3 (2.7.3-arm64-darwin) + sqlite3 (2.7.3-x86_64-darwin) + sqlite3 (2.7.3-x86_64-linux-gnu) + sqlite3 (2.7.3-x86_64-linux-musl) + stimulus-rails (1.2.1) + railties (>= 6.0.0) + stringio (3.1.7) + thor (1.4.0) + tilt (2.6.1) + timeout (0.4.3) + turbo-rails (1.4.0) + actionpack (>= 6.0.0) + activejob (>= 6.0.0) + railties (>= 6.0.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.1.5) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + uri (1.0.4) + useragent (0.16.11) + web-console (4.2.0) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + webdrivers (5.2.0) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (~> 4.0) + websocket (1.2.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) + yard (0.9.37) + yard-solargraph (0.1.0) + yard (~> 0.9) + zeitwerk (2.7.3) + +PLATFORMS + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + bcrypt (= 3.1.18) + bootsnap (= 1.16.0) + bootstrap-sass (= 3.4.1) + capybara (= 3.38.0) + concurrent-ruby (= 1.3.4) + debug (= 1.7.1) + guard (= 2.18.0) + guard-minitest (= 2.4.6) + importmap-rails (= 1.1.5) + irb (= 1.15.2) + jbuilder (= 2.14.1) + minitest (= 5.18.0) + minitest-reporters (= 1.6.0) + puma (= 6.6.1) + rails (= 8.0.2.1) + rails-controller-testing (= 1.0.5) + reline (= 0.5.10) + repl_type_completor (= 0.1.10) + sassc-rails (= 2.1.2) + selenium-webdriver (= 4.8.3) + solargraph (= 0.58.2) + sprockets-rails (= 3.4.2) + sqlite3 (= 2.7.3) + stimulus-rails (= 1.2.1) + turbo-rails (= 1.4.0) + web-console (= 4.2.0) + webdrivers (= 5.2.0) + +RUBY VERSION + ruby 3.2.10p266 + +BUNDLED WITH + 2.5.6 diff --git a/8_0/ch09/Guardfile b/8_0/ch09/Guardfile new file mode 100644 index 00000000..5def34a0 --- /dev/null +++ b/8_0/ch09/Guardfile @@ -0,0 +1,76 @@ + require "active_support/inflector" + # Guardのマッチング規則を定義 + guard :minitest, all_on_start: false do + watch(%r{^test/(.*)/?(.*)_test\.rb$}) + watch('test/test_helper.rb') { 'test' } + watch('config/routes.rb') { interface_tests } + watch(%r{app/views/layouts/*}) { interface_tests } + watch(%r{^app/models/(.*?)\.rb$}) do |matches| + ["test/models/#{matches[1]}_test.rb", + "test/integration/microposts_interface_test.rb"] + end + watch(%r{^test/fixtures/(.*?)\.yml$}) do |matches| + "test/models/#{matches[1].singularize}_test.rb" + end + watch(%r{^app/mailers/(.*?)\.rb$}) do |matches| + "test/mailers/#{matches[1]}_test.rb" + end + watch(%r{^app/views/(.*)_mailer/.*$}) do |matches| + "test/mailers/#{matches[1]}_mailer_test.rb" + end + watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches| + resource_tests(matches[1]) + end + watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches| + ["test/controllers/#{matches[1]}_controller_test.rb"] + + integration_tests(matches[1]) + end + watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches| + integration_tests(matches[1]) + end + watch('app/views/layouts/application.html.erb') do + 'test/integration/site_layout_test.rb' + end + watch('app/helpers/sessions_helper.rb') do + integration_tests << 'test/helpers/sessions_helper_test.rb' + end + watch('app/controllers/sessions_controller.rb') do + ['test/controllers/sessions_controller_test.rb', + 'test/integration/users_login_test.rb'] + end + watch('app/controllers/account_activations_controller.rb') do + 'test/integration/users_signup_test.rb' + end + watch(%r{app/views/users/*}) do + resource_tests('users') + + ['test/integration/microposts_interface_test.rb'] + end + watch('app/controllers/relationships_controller.rb') do + ['test/controllers/relationships_controller_test.rb', + 'test/integration/following_test.rb'] + end + end + + # 指定のリソースに対応する統合テストを返す + def integration_tests(resource = :all) + if resource == :all + Dir["test/integration/*"] + else + Dir["test/integration/#{resource}_*.rb"] + end + end + + # インターフェースが該当するすべてのテストを返す + def interface_tests + integration_tests << "test/controllers" + end + + # 指定のリソースに対応するコントローラのテストを返す + def controller_test(resource) + "test/controllers/#{resource}_controller_test.rb" + end + + # 指定のリソースに対応するすべてのテストを返す + def resource_tests(resource) + integration_tests(resource) << controller_test(resource) + end diff --git a/8_0/ch09/LICENSE b/8_0/ch09/LICENSE new file mode 100644 index 00000000..20622e0c --- /dev/null +++ b/8_0/ch09/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 GitHub & 2023 YassLab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/8_0/ch09/README.md b/8_0/ch09/README.md new file mode 100644 index 00000000..17ca733d --- /dev/null +++ b/8_0/ch09/README.md @@ -0,0 +1,35 @@ +# Ruby on Rails チュートリアルのサンプルアプリケーション + +これは、次の教材で作られたサンプルアプリケーションです。 +[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +(第8版) +[Michael Hartl](https://www.michaelhartl.com/) 著 + +## ライセンス + +[Ruby on Rails チュートリアル](https://railstutorial.jp/)内にある +ソースコードはMITライセンスとBeerwareライセンスのもとで公開されています。 +詳細は [LICENSE.md](LICENSE.md) をご覧ください。 + +## 使い方 + +このアプリケーションを動かす場合は、まずはリポジトリをフォークしてください。 + +フォークしたリポジトリで、「Code」から「Codespaces」タブに移動し、 +「Create codespace on main」をクリックすると環境構築がスタートします。 +Railsサーバーが立ち上がり、シンプルブラウザが表示されるまでしばらくお待ちください。 + +次に、データベースへのマイグレーションを実行します。 + +``` +$ rails db:migrate +``` + +最後に、テストを実行してうまく動いているかどうか確認してください。 + +``` +$ rails test +``` + +詳しくは、[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +を参考にしてください。 diff --git a/8_0/ch09/Rakefile b/8_0/ch09/Rakefile new file mode 100644 index 00000000..9a5ea738 --- /dev/null +++ b/8_0/ch09/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/8_0/ch09/app/assets/config/manifest.js b/8_0/ch09/app/assets/config/manifest.js new file mode 100644 index 00000000..ddd546a0 --- /dev/null +++ b/8_0/ch09/app/assets/config/manifest.js @@ -0,0 +1,4 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js diff --git a/8_0/ch09/app/assets/images/.keep b/8_0/ch09/app/assets/images/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/app/assets/images/rails.svg b/8_0/ch09/app/assets/images/rails.svg new file mode 100644 index 00000000..e1971850 --- /dev/null +++ b/8_0/ch09/app/assets/images/rails.svg @@ -0,0 +1,35 @@ + + + +rails-logo + + + + + + + + + + + + + + + + + + + + + diff --git a/8_0/ch09/app/assets/stylesheets/application.css b/8_0/ch09/app/assets/stylesheets/application.css new file mode 100644 index 00000000..288b9ab7 --- /dev/null +++ b/8_0/ch09/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/8_0/ch09/app/assets/stylesheets/custom.scss b/8_0/ch09/app/assets/stylesheets/custom.scss new file mode 100644 index 00000000..3e507724 --- /dev/null +++ b/8_0/ch09/app/assets/stylesheets/custom.scss @@ -0,0 +1,215 @@ +@import "bootstrap-sprockets"; +@import "bootstrap"; + +/* mixins, variables, etc. */ + +$gray-medium-light: #eaeaea; + +@mixin box_sizing { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* universal */ + +body { + padding-top: 60px; +} + +section { + overflow: auto; +} + +textarea { + resize: vertical; +} + +.center { + text-align: center; + h1 { + margin-bottom: 10px; + } +} + +/* typography */ + +h1, h2, h3, h4, h5, h6 { + line-height: 1; +} + +h1 { + font-size: 3em; + letter-spacing: -2px; + margin-bottom: 30px; + text-align: center; +} + +h2 { + font-size: 1.2em; + letter-spacing: -1px; + margin-bottom: 30px; + text-align: center; + font-weight: normal; + color: $gray-light; +} + +p { + font-size: 1.1em; + line-height: 1.7em; +} + + +/* header */ + +#logo { + float: left; + margin-right: 10px; + font-size: 1.7em; + color: white; + text-transform: uppercase; + letter-spacing: -1px; + padding-top: 9px; + font-weight: bold; + &:hover { + color: white; + text-decoration: none; + } +} + +/* footer */ + +footer { + margin-top: 45px; + padding-top: 5px; + border-top: 1px solid $gray-medium-light; + color: $gray-light; + a { + color: $gray; + &:hover { + color: $gray-darker; + } + } + small { + float: left; + } + ul { + float: right; + list-style: none; + li { + float: left; + margin-left: 15px; + } + } +} +@media (max-width: 800px) { + footer { + small { + display: block; + float: none; + margin-bottom: 1em; + } + ul { + float: none; + padding: 0; + li { + float: none; + margin-left: 0; + } + } + } +} + +/* miscellaneous */ + +.debug_dump { + clear: both; + float: left; + width: 100%; + margin-top: 45px; +} + +/* sidebar */ + +aside { + section.user_info { + margin-top: 20px; + } + section { + padding: 10px 0; + margin-top: 20px; + &:first-child { + border: 0; + padding-top: 0; + } + span { + display: block; + margin-bottom: 3px; + line-height: 1; + } + h1 { + font-size: 1.4em; + text-align: left; + letter-spacing: -1px; + margin-bottom: 3px; + margin-top: 0px; + } + } +} + +.gravatar { + float: left; + margin-right: 10px; +} + +.gravatar_edit { + margin-top: 15px; +} + +/* forms */ + +input, textarea { + border: 1px solid #bbb; + width: 100%; + margin-bottom: 15px; + @include box_sizing; +} + +input { + height: auto !important; +} + +#error_explanation { + color: red; + ul { + color: red; + margin: 0 0 30px 0; + } +} + +.field_with_errors { + @extend .has-error; + .form-control { + color: $state-danger-text; + } +} + +.checkbox { + margin-top: -10px; + margin-bottom: 10px; + span { + margin-left: 20px; + font-weight: normal; + } +} + +#session_remember_me { + width: auto; + margin-left: 0; +} + +/* Dropdown menu */ + +.dropdown-menu.active { + display: block; +} \ No newline at end of file diff --git a/8_0/ch09/app/assets/stylesheets/hello.css b/8_0/ch09/app/assets/stylesheets/hello.css new file mode 100644 index 00000000..497881a4 --- /dev/null +++ b/8_0/ch09/app/assets/stylesheets/hello.css @@ -0,0 +1,40 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.codespaces { + text-align: center; +} +.codespaces code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} +.codespaces-logo { + height: 40vmin; + pointer-events: none; +} +.codespaces-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} +.codespaces-link { + color: #61dafb; +} + +.heart { + color: #ff0000; +} +.small { + font-size: 0.75rem; +} diff --git a/8_0/ch09/app/channels/application_cable/channel.rb b/8_0/ch09/app/channels/application_cable/channel.rb new file mode 100644 index 00000000..d6726972 --- /dev/null +++ b/8_0/ch09/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/8_0/ch09/app/channels/application_cable/connection.rb b/8_0/ch09/app/channels/application_cable/connection.rb new file mode 100644 index 00000000..0ff5442f --- /dev/null +++ b/8_0/ch09/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/8_0/ch09/app/controllers/application_controller.rb b/8_0/ch09/app/controllers/application_controller.rb new file mode 100644 index 00000000..09faff3f --- /dev/null +++ b/8_0/ch09/app/controllers/application_controller.rb @@ -0,0 +1,3 @@ +class ApplicationController < ActionController::Base + include SessionsHelper +end diff --git a/8_0/ch09/app/controllers/concerns/.keep b/8_0/ch09/app/controllers/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/app/controllers/hello_controller.rb b/8_0/ch09/app/controllers/hello_controller.rb new file mode 100644 index 00000000..52037537 --- /dev/null +++ b/8_0/ch09/app/controllers/hello_controller.rb @@ -0,0 +1,4 @@ +class HelloController < ApplicationController + def index + end +end diff --git a/8_0/ch09/app/controllers/sessions_controller.rb b/8_0/ch09/app/controllers/sessions_controller.rb new file mode 100644 index 00000000..17dcd4c3 --- /dev/null +++ b/8_0/ch09/app/controllers/sessions_controller.rb @@ -0,0 +1,23 @@ +class SessionsController < ApplicationController + + def new + end + + def create + user = User.find_by(email: params[:session][:email].downcase) + if user && user.authenticate(params[:session][:password]) + reset_session + params[:session][:remember_me] == '1' ? remember(user) : forget(user) + log_in user + redirect_to user + else + flash.now[:danger] = 'Invalid email/password combination' + render 'new', status: :unprocessable_entity + end + end + + def destroy + log_out if logged_in? + redirect_to root_url, status: :see_other + end +end diff --git a/8_0/ch09/app/controllers/static_pages_controller.rb b/8_0/ch09/app/controllers/static_pages_controller.rb new file mode 100644 index 00000000..0673ad5e --- /dev/null +++ b/8_0/ch09/app/controllers/static_pages_controller.rb @@ -0,0 +1,14 @@ +class StaticPagesController < ApplicationController + + def home + end + + def help + end + + def about + end + + def contact + end +end diff --git a/8_0/ch09/app/controllers/users_controller.rb b/8_0/ch09/app/controllers/users_controller.rb new file mode 100644 index 00000000..5a4f1f5e --- /dev/null +++ b/8_0/ch09/app/controllers/users_controller.rb @@ -0,0 +1,29 @@ +class UsersController < ApplicationController + + def show + @user = User.find(params[:id]) + end + + def new + @user = User.new + end + + def create + @user = User.new(user_params) + if @user.save + reset_session + log_in @user + flash[:success] = "Welcome to the Sample App!" + redirect_to @user + else + render 'new', status: :unprocessable_entity + end + end + + private + + def user_params + params.expect(user: [:name, :email, :password, + :password_confirmation]) + end +end diff --git a/8_0/ch09/app/helpers/application_helper.rb b/8_0/ch09/app/helpers/application_helper.rb new file mode 100644 index 00000000..708e4e48 --- /dev/null +++ b/8_0/ch09/app/helpers/application_helper.rb @@ -0,0 +1,12 @@ +module ApplicationHelper + + # ページごとの完全なタイトルを返します。 # コメント行 + def full_title(page_title = '') # メソッド定義とオプション引数 + base_title = "Ruby on Rails Tutorial Sample App" # 変数への代入 + if page_title.empty? # 論理値テスト + base_title # 暗黙の戻り値 + else + "#{page_title} | #{base_title}" # 文字列の結合 + end + end +end diff --git a/8_0/ch09/app/helpers/hello_codespaces_helper.rb b/8_0/ch09/app/helpers/hello_codespaces_helper.rb new file mode 100644 index 00000000..874a2ca2 --- /dev/null +++ b/8_0/ch09/app/helpers/hello_codespaces_helper.rb @@ -0,0 +1,2 @@ +module HelloCodespacesHelper +end diff --git a/8_0/ch09/app/helpers/sessions_helper.rb b/8_0/ch09/app/helpers/sessions_helper.rb new file mode 100644 index 00000000..0f2dc836 --- /dev/null +++ b/8_0/ch09/app/helpers/sessions_helper.rb @@ -0,0 +1,45 @@ +module SessionsHelper + # 渡されたユーザーでログインする + def log_in(user) + session[:user_id] = user.id + end + + # 永続的セッションのためにユーザーをデータベースに記憶する + def remember(user) + user.remember + cookies.permanent.encrypted[:user_id] = user.id + cookies.permanent[:remember_token] = user.remember_token + end + + # 記憶トークンcookieに対応するユーザーを返す + def current_user + if (user_id = session[:user_id]) + @current_user ||= User.find_by(id: user_id) + elsif (user_id = cookies.encrypted[:user_id]) + user = User.find_by(id: user_id) + if user && user.authenticated?(cookies[:remember_token]) + log_in user + @current_user = user + end + end + end + + # ユーザーがログインしていればtrue、その他ならfalseを返す + def logged_in? + !current_user.nil? + end + + # 永続的セッションを破棄する + def forget(user) + user.forget + cookies.delete(:user_id) + cookies.delete(:remember_token) + end + + # 現在のユーザーをログアウトする + def log_out + forget(current_user) + reset_session + @current_user = nil + end +end diff --git a/8_0/ch09/app/helpers/static_pages_helper.rb b/8_0/ch09/app/helpers/static_pages_helper.rb new file mode 100644 index 00000000..2d63e79e --- /dev/null +++ b/8_0/ch09/app/helpers/static_pages_helper.rb @@ -0,0 +1,2 @@ +module StaticPagesHelper +end diff --git a/8_0/ch09/app/helpers/users_helper.rb b/8_0/ch09/app/helpers/users_helper.rb new file mode 100644 index 00000000..b8d0f8e5 --- /dev/null +++ b/8_0/ch09/app/helpers/users_helper.rb @@ -0,0 +1,9 @@ +module UsersHelper + + # 引数で与えられたユーザーのGravatar画像を返す + def gravatar_for(user) + gravatar_id = Digest::MD5::hexdigest(user.email.downcase) + gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}" + image_tag(gravatar_url, alt: user.name, class: "gravatar") + end +end diff --git a/8_0/ch09/app/javascript/application.js b/8_0/ch09/app/javascript/application.js new file mode 100644 index 00000000..46ebafe8 --- /dev/null +++ b/8_0/ch09/app/javascript/application.js @@ -0,0 +1,5 @@ +// Configure your import map in config/importmap.rb. +// Read more: https://github.com/rails/importmap-rails +import "@hotwired/turbo-rails" +import "controllers" +import "custom/menu" diff --git a/8_0/ch09/app/javascript/controllers/application.js b/8_0/ch09/app/javascript/controllers/application.js new file mode 100644 index 00000000..1213e85c --- /dev/null +++ b/8_0/ch09/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/8_0/ch09/app/javascript/controllers/hello_controller.js b/8_0/ch09/app/javascript/controllers/hello_controller.js new file mode 100644 index 00000000..5975c078 --- /dev/null +++ b/8_0/ch09/app/javascript/controllers/hello_controller.js @@ -0,0 +1,7 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.element.textContent = "Hello World!" + } +} diff --git a/8_0/ch09/app/javascript/controllers/index.js b/8_0/ch09/app/javascript/controllers/index.js new file mode 100644 index 00000000..54ad4cad --- /dev/null +++ b/8_0/ch09/app/javascript/controllers/index.js @@ -0,0 +1,11 @@ +// Import and register all your controllers from the importmap under controllers/* + +import { application } from "controllers/application" + +// Eager load all controllers defined in the import map under controllers/**/*_controller +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) + +// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) +// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" +// lazyLoadControllersFrom("controllers", application) diff --git a/8_0/ch09/app/javascript/custom/menu.js b/8_0/ch09/app/javascript/custom/menu.js new file mode 100644 index 00000000..92e4a8dd --- /dev/null +++ b/8_0/ch09/app/javascript/custom/menu.js @@ -0,0 +1,22 @@ +// メニュー操作 + +// トグルリスナーを追加してクリックをリッスンする +document.addEventListener("turbo:load", function() { + let hamburger = document.querySelector("#hamburger"); + if (hamburger){ + hamburger.addEventListener("click", function(event) { + event.preventDefault(); + let menu = document.querySelector("#navbar-menu"); + menu.classList.toggle("collapse"); + }); + } + + let account = document.querySelector("#account"); + if (account) { + account.addEventListener("click", function(event) { + event.preventDefault(); + let menu = document.querySelector("#dropdown-menu"); + menu.classList.toggle("active"); + }); + } +}); diff --git a/8_0/ch09/app/jobs/application_job.rb b/8_0/ch09/app/jobs/application_job.rb new file mode 100644 index 00000000..d394c3d1 --- /dev/null +++ b/8_0/ch09/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/8_0/ch09/app/mailers/application_mailer.rb b/8_0/ch09/app/mailers/application_mailer.rb new file mode 100644 index 00000000..3c34c814 --- /dev/null +++ b/8_0/ch09/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "from@example.com" + layout "mailer" +end diff --git a/8_0/ch09/app/models/application_record.rb b/8_0/ch09/app/models/application_record.rb new file mode 100644 index 00000000..b63caeb8 --- /dev/null +++ b/8_0/ch09/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/8_0/ch09/app/models/concerns/.keep b/8_0/ch09/app/models/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/app/models/user.rb b/8_0/ch09/app/models/user.rb new file mode 100644 index 00000000..e4d2b8b5 --- /dev/null +++ b/8_0/ch09/app/models/user.rb @@ -0,0 +1,40 @@ +class User < ApplicationRecord + attr_accessor :remember_token + before_save { self.email = email.downcase } + validates :name, presence: true, length: { maximum: 50 } + VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i + validates :email, presence: true, length: { maximum: 255 }, + format: { with: VALID_EMAIL_REGEX }, + uniqueness: true + has_secure_password + validates :password, presence: true, length: { minimum: 6 } + + # 渡された文字列のハッシュ値を返す + def User.digest(string) + cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : + BCrypt::Engine.cost + BCrypt::Password.create(string, cost: cost) + end + + # ランダムなトークンを返す + def User.new_token + SecureRandom.urlsafe_base64 + end + + # 永続的セッションのためにユーザーをデータベースに記憶する + def remember + self.remember_token = User.new_token + update_attribute(:remember_digest, User.digest(remember_token)) + end + + # 渡されたトークンがダイジェストと一致したらtrueを返す + def authenticated?(remember_token) + return false if remember_digest.nil? + BCrypt::Password.new(remember_digest).is_password?(remember_token) + end + + # ユーザーのログイン情報を破棄する + def forget + update_attribute(:remember_digest, nil) + end +end diff --git a/8_0/ch09/app/views/hello/index.html.erb b/8_0/ch09/app/views/hello/index.html.erb new file mode 100644 index 00000000..71661105 --- /dev/null +++ b/8_0/ch09/app/views/hello/index.html.erb @@ -0,0 +1,16 @@ +
+
+ +

+ Codespaces + ♥️ + Railsチュートリアル +

+

+ 🎓✨
+ ロゴ画像が表示されたらセットアップ完了です! +

+
+
diff --git a/8_0/ch09/app/views/layouts/_footer.html.erb b/8_0/ch09/app/views/layouts/_footer.html.erb new file mode 100644 index 00000000..b6e31ff9 --- /dev/null +++ b/8_0/ch09/app/views/layouts/_footer.html.erb @@ -0,0 +1,13 @@ + diff --git a/8_0/ch09/app/views/layouts/_header.html.erb b/8_0/ch09/app/views/layouts/_header.html.erb new file mode 100644 index 00000000..150d97be --- /dev/null +++ b/8_0/ch09/app/views/layouts/_header.html.erb @@ -0,0 +1,39 @@ + diff --git a/8_0/ch09/app/views/layouts/application.html.erb b/8_0/ch09/app/views/layouts/application.html.erb new file mode 100644 index 00000000..eb23ef94 --- /dev/null +++ b/8_0/ch09/app/views/layouts/application.html.erb @@ -0,0 +1,24 @@ + + + + <%= full_title(yield(:title)) %> + + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + + + <%= render 'layouts/header' %> +
+ <% flash.each do |message_type, message| %> +
<%= message %>
+ <% end %> + <%= yield %> + <%= render 'layouts/footer' %> + <%= debug(params) if Rails.env.development? %> +
+ + diff --git a/8_0/ch09/app/views/layouts/mailer.html.erb b/8_0/ch09/app/views/layouts/mailer.html.erb new file mode 100644 index 00000000..cbd34d2e --- /dev/null +++ b/8_0/ch09/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/8_0/ch09/app/views/layouts/mailer.text.erb b/8_0/ch09/app/views/layouts/mailer.text.erb new file mode 100644 index 00000000..37f0bddb --- /dev/null +++ b/8_0/ch09/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/8_0/ch09/app/views/sessions/new.html.erb b/8_0/ch09/app/views/sessions/new.html.erb new file mode 100644 index 00000000..4eafa29b --- /dev/null +++ b/8_0/ch09/app/views/sessions/new.html.erb @@ -0,0 +1,24 @@ +<% provide(:title, "Log in") %> +

Log in

+ +
+
+ <%= form_with(url: login_path, scope: :session) do |f| %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :remember_me, class: "checkbox inline" do %> + <%= f.check_box :remember_me %> + Remember me on this computer + <% end %> + + <%= f.submit "Log in", class: "btn btn-primary" %> + <% end %> + +

New user? <%= link_to "Sign up now!", signup_path %>

+
+
diff --git a/8_0/ch09/app/views/shared/_error_messages.html.erb b/8_0/ch09/app/views/shared/_error_messages.html.erb new file mode 100644 index 00000000..f80053e4 --- /dev/null +++ b/8_0/ch09/app/views/shared/_error_messages.html.erb @@ -0,0 +1,12 @@ +<% if @user.errors.any? %> +
+
+ The form contains <%= pluralize(@user.errors.count, "error") %>. +
+ +
+<% end %> diff --git a/8_0/ch09/app/views/static_pages/about.html.erb b/8_0/ch09/app/views/static_pages/about.html.erb new file mode 100644 index 00000000..dce8fef2 --- /dev/null +++ b/8_0/ch09/app/views/static_pages/about.html.erb @@ -0,0 +1,10 @@ +<% provide(:title, "About") %> +

About

+

+ Ruby on Rails Tutorial + is a book and + screencast + to teach web development with + Ruby on Rails. + This is the sample application for the tutorial. +

diff --git a/8_0/ch09/app/views/static_pages/contact.html.erb b/8_0/ch09/app/views/static_pages/contact.html.erb new file mode 100644 index 00000000..4a5a35a6 --- /dev/null +++ b/8_0/ch09/app/views/static_pages/contact.html.erb @@ -0,0 +1,6 @@ +<% provide(:title, 'Contact') %> +

Contact

+

+ Contact the Ruby on Rails Tutorial about the sample app at the + contact page. +

diff --git a/8_0/ch09/app/views/static_pages/help.html.erb b/8_0/ch09/app/views/static_pages/help.html.erb new file mode 100644 index 00000000..4d06919d --- /dev/null +++ b/8_0/ch09/app/views/static_pages/help.html.erb @@ -0,0 +1,9 @@ +<% provide(:title, "Help") %> +

Help

+

+ Get help on the Ruby on Rails Tutorial at the + Rails Tutorial Help page. + To get help on this sample app, see the + Ruby on Rails Tutorial + book. +

diff --git a/8_0/ch09/app/views/static_pages/home.html.erb b/8_0/ch09/app/views/static_pages/home.html.erb new file mode 100644 index 00000000..f87607de --- /dev/null +++ b/8_0/ch09/app/views/static_pages/home.html.erb @@ -0,0 +1,14 @@ +
+

Welcome to the Sample App

+ +

+ This is the home page for the + Ruby on Rails Tutorial + sample application. +

+ + <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %> +
+ +<%= link_to image_tag("rails.svg", alt: "Rails logo", width: "200"), + "https://rubyonrails.org/" %> diff --git a/8_0/ch09/app/views/users/new.html.erb b/8_0/ch09/app/views/users/new.html.erb new file mode 100644 index 00000000..79e39f80 --- /dev/null +++ b/8_0/ch09/app/views/users/new.html.erb @@ -0,0 +1,24 @@ +<% provide(:title, 'Sign up') %> +

Sign up

+ +
+
+ <%= form_with(model: @user) do |f| %> + <%= render 'shared/error_messages' %> + + <%= f.label :name %> + <%= f.text_field :name, class: 'form-control' %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :password_confirmation, "Confirmation" %> + <%= f.password_field :password_confirmation, class: 'form-control' %> + + <%= f.submit "Create my account", class: "btn btn-primary" %> + <% end %> +
+
diff --git a/8_0/ch09/app/views/users/show.html.erb b/8_0/ch09/app/views/users/show.html.erb new file mode 100644 index 00000000..4fda5f89 --- /dev/null +++ b/8_0/ch09/app/views/users/show.html.erb @@ -0,0 +1,11 @@ +<% provide(:title, @user.name) %> +
+ +
diff --git a/8_0/ch09/bin/bundle b/8_0/ch09/bin/bundle new file mode 100755 index 00000000..981e650b --- /dev/null +++ b/8_0/ch09/bin/bundle @@ -0,0 +1,114 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + requirement = bundler_gem_version.approximate_recommendation + + return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0") + + requirement += ".a" if bundler_gem_version.prerelease? + + requirement + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/8_0/ch09/bin/dev b/8_0/ch09/bin/dev new file mode 100755 index 00000000..5f91c205 --- /dev/null +++ b/8_0/ch09/bin/dev @@ -0,0 +1,2 @@ +#!/usr/bin/env ruby +exec "./bin/rails", "server", *ARGV diff --git a/8_0/ch09/bin/importmap b/8_0/ch09/bin/importmap new file mode 100755 index 00000000..36502ab1 --- /dev/null +++ b/8_0/ch09/bin/importmap @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require_relative "../config/application" +require "importmap/commands" diff --git a/8_0/ch09/bin/rails b/8_0/ch09/bin/rails new file mode 100755 index 00000000..efc03774 --- /dev/null +++ b/8_0/ch09/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/8_0/ch09/bin/rake b/8_0/ch09/bin/rake new file mode 100755 index 00000000..4fbf10b9 --- /dev/null +++ b/8_0/ch09/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/8_0/ch09/bin/render-build.sh b/8_0/ch09/bin/render-build.sh new file mode 100755 index 00000000..6a61cb7f --- /dev/null +++ b/8_0/ch09/bin/render-build.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# exit on error +set -o errexit +bundle install +bundle exec rails assets:precompile +bundle exec rails assets:clean +bundle exec rails db:migrate diff --git a/8_0/ch09/bin/rubocop b/8_0/ch09/bin/rubocop new file mode 100755 index 00000000..40330c0f --- /dev/null +++ b/8_0/ch09/bin/rubocop @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +# explicit rubocop config increases performance slightly while avoiding config confusion. +ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) + +load Gem.bin_path("rubocop", "rubocop") diff --git a/8_0/ch09/bin/setup b/8_0/ch09/bin/setup new file mode 100755 index 00000000..be3db3c0 --- /dev/null +++ b/8_0/ch09/bin/setup @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +require "fileutils" + +APP_ROOT = File.expand_path("..", __dir__) + +def system!(*args) + system(*args, exception: true) +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts "== Installing dependencies ==" + system("bundle check") || system!("bundle install") + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + puts "\n== Removing old logs and tempfiles ==" + system! "bin/rails log:clear tmp:clear" + + unless ARGV.include?("--skip-server") + puts "\n== Starting development server ==" + STDOUT.flush # flush the output before exec(2) so that it displays + exec "bin/dev" + end +end diff --git a/8_0/ch09/config.ru b/8_0/ch09/config.ru new file mode 100644 index 00000000..4a3c09a6 --- /dev/null +++ b/8_0/ch09/config.ru @@ -0,0 +1,6 @@ +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/8_0/ch09/config/application.rb b/8_0/ch09/config/application.rb new file mode 100644 index 00000000..0461e72f --- /dev/null +++ b/8_0/ch09/config/application.rb @@ -0,0 +1,27 @@ +require_relative "boot" + +require "rails/all" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module SampleApp + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 8.0 + + # Please, add to the `ignore` list any other `lib` subdirectories that do + # not contain `.rb` files, or that should not be reloaded or eager loaded. + # Common ones are `templates`, `generators`, or `middleware`, for example. + config.autoload_lib(ignore: %w[assets tasks]) + + # Configuration for the application, engines, and railties goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + # + # config.time_zone = "Central Time (US & Canada)" + # config.eager_load_paths << Rails.root.join("extras") + end +end diff --git a/8_0/ch09/config/boot.rb b/8_0/ch09/config/boot.rb new file mode 100644 index 00000000..988a5ddc --- /dev/null +++ b/8_0/ch09/config/boot.rb @@ -0,0 +1,4 @@ +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "bundler/setup" # Set up gems listed in the Gemfile. +require "bootsnap/setup" # Speed up boot time by caching expensive operations. diff --git a/8_0/ch09/config/cable.yml b/8_0/ch09/config/cable.yml new file mode 100644 index 00000000..ae7a2256 --- /dev/null +++ b/8_0/ch09/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: codespaces_try_rails_production diff --git a/8_0/ch09/config/credentials.yml.enc b/8_0/ch09/config/credentials.yml.enc new file mode 100644 index 00000000..339d64d2 --- /dev/null +++ b/8_0/ch09/config/credentials.yml.enc @@ -0,0 +1 @@ +NCs9M8uakg1CDYJENuGaipzymY8uG6i5gzghsy4npa3gYmGfYKPHMnEcJHCVHlM3gxDNJBteUqkRCxHQ5WQId8hzgxoQCL5RX7rtQ8XUzkJwgSUjKvbCrG5atjKkN9XuL91is5vtEN5W7vEz3qxN7Rp33QbNFiDfLs71F3zHVDYEMi6Dy1QMhVCa1v/tyRjBu/5m37RuiYF5FzWJnh4LhexSVr7Agm4LRLLN6qLCpoAe6D3TPHHBdtu7Pvy8jlrEfnnkUJ61wrj8+kOL4uLtpFShdYKhDkoTjQk7TCOgWyifnHAXazkBZoJZ1QYv/tZ9/ZoHvMEQ2dtGHlyTX8fbKtI13d2CFjGaDNKvRuurxE8dbeyiTfbycvve46+cz9OTlmhh0SGt24R1WV6GBKoDUeoHrIiBvW5qmKka--dQ0rodxmfgFLg2y/--t2x0gQjh8FYlk8v79vLSNg== \ No newline at end of file diff --git a/8_0/ch09/config/database.yml b/8_0/ch09/config/database.yml new file mode 100644 index 00000000..fcba57f1 --- /dev/null +++ b/8_0/ch09/config/database.yml @@ -0,0 +1,25 @@ +# SQLite. Versions 3.8.0 and up are supported. +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem "sqlite3" +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/8_0/ch09/config/environment.rb b/8_0/ch09/config/environment.rb new file mode 100644 index 00000000..cac53157 --- /dev/null +++ b/8_0/ch09/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/8_0/ch09/config/environments/development.rb b/8_0/ch09/config/environments/development.rb new file mode 100644 index 00000000..910cef99 --- /dev/null +++ b/8_0/ch09/config/environments/development.rb @@ -0,0 +1,86 @@ +require 'active_support/core_ext/integer/time' + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Make code changes take effect immediately without server restart. + config.enable_reloading = true + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing. + config.server_timing = true + + # Add the following line to disable forgery_protection_origin_check + config.action_controller.forgery_protection_origin_check = false + + # Enable/disable Action Controller caching. By default Action Controller caching is disabled. + # Run rails dev:cache to toggle Action Controller caching. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + config.public_file_server.headers = { 'cache-control' => "public, max-age=#{2.days.to_i}" } + else + config.action_controller.perform_caching = false + end + + # Change to :null_store to avoid any caching. + config.cache_store = :memory_store + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Make template changes take effect immediately. + config.action_mailer.perform_caching = false + + # Set localhost to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Append comments with runtime information tags to SQL queries in logs. + config.active_record.query_log_tags_enabled = true + + # Highlight code that enqueued background job in logs. + config.active_job.verbose_enqueue_logs = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true + + # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. + # config.generators.apply_rubocop_autocorrect_after_generate! + + pf_domain = ENV['GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN'] + config.action_dispatch.default_headers = { + 'X-Frame-Options' => "ALLOW-FROM #{pf_domain}" + } + + # Allow requests from our preview domain. + pf_host = "#{ENV['CODESPACE_NAME']}-3000.#{pf_domain}" + config.hosts << pf_host + + config.action_cable.allowed_request_origins = ["https://#{pf_host}"] +end diff --git a/8_0/ch09/config/environments/production.rb b/8_0/ch09/config/environments/production.rb new file mode 100644 index 00000000..17496077 --- /dev/null +++ b/8_0/ch09/config/environments/production.rb @@ -0,0 +1,89 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.enable_reloading = false + + # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). + config.eager_load = true + + # Full error reports are disabled. + config.consider_all_requests_local = false + + # Turn on fragment caching in view templates. + config.action_controller.perform_caching = true + + # Cache assets for far-future expiry since they are all digest stamped. + config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" } + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = "http://assets.example.com" + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Assume all access to the app is happening through a SSL-terminating reverse proxy. + config.assume_ssl = true + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = true + + # Skip http-to-https redirect for the default health check endpoint. + # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } + + # Log to STDOUT with the current request id as a default log tag. + config.log_tags = [ :request_id ] + config.logger = ActiveSupport::TaggedLogging.logger(STDOUT) + + # Change to "debug" to log everything (including potentially personally-identifiable information!) + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + + # Prevent health checks from clogging up the logs. + config.silence_healthcheck_path = "/up" + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Replace the default in-process memory cache store with a durable alternative. + # config.cache_store = :mem_cache_store + + # Replace the default in-process and non-durable queuing backend for Active Job. + # config.active_job.queue_adapter = :resque + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. + # config.action_mailer.smtp_settings = { + # user_name: Rails.application.credentials.dig(:smtp, :user_name), + # password: Rails.application.credentials.dig(:smtp, :password), + # address: "smtp.example.com", + # port: 587, + # authentication: :plain + # } + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Only use :id for inspections in production. + config.active_record.attributes_for_inspect = [ :id ] + + # Enable DNS rebinding protection and other `Host` header attacks. + # config.hosts = [ + # "example.com", # Allow requests from example.com + # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` + # ] + # + # Skip DNS rebinding protection for the default health check endpoint. + # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } +end diff --git a/8_0/ch09/config/environments/test.rb b/8_0/ch09/config/environments/test.rb new file mode 100644 index 00000000..0ebe3b4e --- /dev/null +++ b/8_0/ch09/config/environments/test.rb @@ -0,0 +1,53 @@ +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # While tests run files are not watched, reloading is not necessary. + config.enable_reloading = false + + # Eager loading loads your entire application. When running a single test locally, + # this is usually not necessary, and can slow down your test suite. However, it's + # recommended that you enable it in continuous integration systems to ensure eager + # loading is working properly before deploying your code. + config.eager_load = ENV["CI"].present? + + # Configure public file server for tests with cache-control for performance. + config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } + + # Show full error reports. + config.consider_all_requests_local = true + config.cache_store = :null_store + + # Render exception templates for rescuable exceptions and raise for other exceptions. + config.action_dispatch.show_exceptions = :none + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :test + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true +end diff --git a/8_0/ch09/config/importmap.rb b/8_0/ch09/config/importmap.rb new file mode 100644 index 00000000..caf3ed06 --- /dev/null +++ b/8_0/ch09/config/importmap.rb @@ -0,0 +1,8 @@ +# Pin npm packages by running ./bin/importmap + +pin "application", preload: true +pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true +pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true +pin_all_from "app/javascript/controllers", under: "controllers" +pin_all_from "app/javascript/custom", under: "custom" diff --git a/8_0/ch09/config/initializers/assets.rb b/8_0/ch09/config/initializers/assets.rb new file mode 100644 index 00000000..48732442 --- /dev/null +++ b/8_0/ch09/config/initializers/assets.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = "1.0" + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path diff --git a/8_0/ch09/config/initializers/content_security_policy.rb b/8_0/ch09/config/initializers/content_security_policy.rb new file mode 100644 index 00000000..b3076b38 --- /dev/null +++ b/8_0/ch09/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header + +# Rails.application.configure do +# config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end +# +# # Generate session nonces for permitted importmap, inline scripts, and inline styles. +# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } +# config.content_security_policy_nonce_directives = %w(script-src style-src) +# +# # Report violations without enforcing the policy. +# # config.content_security_policy_report_only = true +# end diff --git a/8_0/ch09/config/initializers/filter_parameter_logging.rb b/8_0/ch09/config/initializers/filter_parameter_logging.rb new file mode 100644 index 00000000..c0b717f7 --- /dev/null +++ b/8_0/ch09/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. +# Use this to limit dissemination of sensitive information. +# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. +Rails.application.config.filter_parameters += [ + :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc +] diff --git a/8_0/ch09/config/initializers/inflections.rb b/8_0/ch09/config/initializers/inflections.rb new file mode 100644 index 00000000..3860f659 --- /dev/null +++ b/8_0/ch09/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym "RESTful" +# end diff --git a/8_0/ch09/config/initializers/new_framework_defaults_8_0.rb b/8_0/ch09/config/initializers/new_framework_defaults_8_0.rb new file mode 100644 index 00000000..92efa951 --- /dev/null +++ b/8_0/ch09/config/initializers/new_framework_defaults_8_0.rb @@ -0,0 +1,30 @@ +# Be sure to restart your server when you modify this file. +# +# This file eases your Rails 8.0 framework defaults upgrade. +# +# Uncomment each configuration one by one to switch to the new default. +# Once your application is ready to run with all new defaults, you can remove +# this file and set the `config.load_defaults` to `8.0`. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. +# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html + +### +# Specifies whether `to_time` methods preserve the UTC offset of their receivers or preserves the timezone. +# If set to `:zone`, `to_time` methods will use the timezone of their receivers. +# If set to `:offset`, `to_time` methods will use the UTC offset. +# If `false`, `to_time` methods will convert to the local system UTC offset instead. +#++ +# Rails.application.config.active_support.to_time_preserves_timezone = :zone + +### +# When both `If-Modified-Since` and `If-None-Match` are provided by the client +# only consider `If-None-Match` as specified by RFC 7232 Section 6. +# If set to `false` both conditions need to be satisfied. +#++ +# Rails.application.config.action_dispatch.strict_freshness = true + +### +# Set `Regexp.timeout` to `1`s by default to improve security over Regexp Denial-of-Service attacks. +#++ +# Regexp.timeout = 1 diff --git a/8_0/ch09/config/initializers/permissions_policy.rb b/8_0/ch09/config/initializers/permissions_policy.rb new file mode 100644 index 00000000..00f64d71 --- /dev/null +++ b/8_0/ch09/config/initializers/permissions_policy.rb @@ -0,0 +1,11 @@ +# Define an application-wide HTTP permissions policy. For further +# information see https://developers.google.com/web/updates/2018/06/feature-policy +# +# Rails.application.config.permissions_policy do |f| +# f.camera :none +# f.gyroscope :none +# f.microphone :none +# f.usb :none +# f.fullscreen :self +# f.payment :self, "https://secure.example.com" +# end diff --git a/8_0/ch09/config/locales/en.yml b/8_0/ch09/config/locales/en.yml new file mode 100644 index 00000000..8ca56fc7 --- /dev/null +++ b/8_0/ch09/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# "true": "foo" +# +# To learn more, please read the Rails Internationalization guide +# available at https://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/8_0/ch09/config/puma.rb b/8_0/ch09/config/puma.rb new file mode 100644 index 00000000..a248513b --- /dev/null +++ b/8_0/ch09/config/puma.rb @@ -0,0 +1,41 @@ +# This configuration file will be evaluated by Puma. The top-level methods that +# are invoked here are part of Puma's configuration DSL. For more information +# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. +# +# Puma starts a configurable number of processes (workers) and each process +# serves each request in a thread from an internal thread pool. +# +# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You +# should only set this value when you want to run 2 or more workers. The +# default is already 1. +# +# The ideal number of threads per worker depends both on how much time the +# application spends waiting for IO operations and on how much you wish to +# prioritize throughput over latency. +# +# As a rule of thumb, increasing the number of threads will increase how much +# traffic a given process can handle (throughput), but due to CRuby's +# Global VM Lock (GVL) it has diminishing returns and will degrade the +# response time (latency) of the application. +# +# The default is set to 3 threads as it's deemed a decent compromise between +# throughput and latency for the average Rails application. +# +# Any libraries that use a connection pool or another resource pool should +# be configured to provide at least as many connections as the number of +# threads. This includes Active Record's `pool` parameter in `database.yml`. +threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +port ENV.fetch("PORT", 3000) + +# Allow puma to be restarted by `bin/rails restart` command. +plugin :tmp_restart + +# Run the Solid Queue supervisor inside of Puma for single-server deployments +plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] + +# Specify the PID file. Defaults to tmp/pids/server.pid in development. +# In other environments, only set the PID file if requested. +pidfile ENV["PIDFILE"] if ENV["PIDFILE"] diff --git a/8_0/ch09/config/routes.rb b/8_0/ch09/config/routes.rb new file mode 100644 index 00000000..197ff607 --- /dev/null +++ b/8_0/ch09/config/routes.rb @@ -0,0 +1,11 @@ +Rails.application.routes.draw do + root "static_pages#home" + get "/help", to: "static_pages#help" + get "/about", to: "static_pages#about" + get "/contact", to: "static_pages#contact" + get "/signup", to: "users#new" + get "/login", to: "sessions#new" + post "/login", to: "sessions#create" + delete "/logout", to: "sessions#destroy" + resources :users +end diff --git a/8_0/ch09/config/storage.yml b/8_0/ch09/config/storage.yml new file mode 100644 index 00000000..4942ab66 --- /dev/null +++ b/8_0/ch09/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket-<%= Rails.env %> + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket-<%= Rails.env %> + +# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name-<%= Rails.env %> + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/8_0/ch09/db/migrate/20260512080056_create_users.rb b/8_0/ch09/db/migrate/20260512080056_create_users.rb new file mode 100644 index 00000000..3b80eb00 --- /dev/null +++ b/8_0/ch09/db/migrate/20260512080056_create_users.rb @@ -0,0 +1,10 @@ +class CreateUsers < ActiveRecord::Migration[8.0] + def change + create_table :users do |t| + t.string :name + t.string :email + + t.timestamps + end + end +end diff --git a/8_0/ch09/db/migrate/20260512080840_add_index_to_users_email.rb b/8_0/ch09/db/migrate/20260512080840_add_index_to_users_email.rb new file mode 100644 index 00000000..a8e4ed8e --- /dev/null +++ b/8_0/ch09/db/migrate/20260512080840_add_index_to_users_email.rb @@ -0,0 +1,5 @@ +class AddIndexToUsersEmail < ActiveRecord::Migration[8.0] + def change + add_index :users, :email, unique: true + end +end diff --git a/8_0/ch09/db/migrate/20260512081511_add_password_digest_to_users.rb b/8_0/ch09/db/migrate/20260512081511_add_password_digest_to_users.rb new file mode 100644 index 00000000..81c7c9e0 --- /dev/null +++ b/8_0/ch09/db/migrate/20260512081511_add_password_digest_to_users.rb @@ -0,0 +1,5 @@ +class AddPasswordDigestToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :password_digest, :string + end +end diff --git a/8_0/ch09/db/migrate/20260626064202_add_remember_digest_to_users.rb b/8_0/ch09/db/migrate/20260626064202_add_remember_digest_to_users.rb new file mode 100644 index 00000000..7a464448 --- /dev/null +++ b/8_0/ch09/db/migrate/20260626064202_add_remember_digest_to_users.rb @@ -0,0 +1,5 @@ +class AddRememberDigestToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :remember_digest, :string + end +end diff --git a/8_0/ch09/db/schema.rb b/8_0/ch09/db/schema.rb new file mode 100644 index 00000000..887b636c --- /dev/null +++ b/8_0/ch09/db/schema.rb @@ -0,0 +1,23 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[8.0].define(version: 2026_06_26_064202) do + create_table "users", force: :cascade do |t| + t.string "name" + t.string "email" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "password_digest" + t.string "remember_digest" + t.index ["email"], name: "index_users_on_email", unique: true + end +end diff --git a/8_0/ch09/db/seeds.rb b/8_0/ch09/db/seeds.rb new file mode 100644 index 00000000..bc25fce3 --- /dev/null +++ b/8_0/ch09/db/seeds.rb @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). +# +# Examples: +# +# movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }]) +# Character.create(name: "Luke", movie: movies.first) diff --git a/8_0/ch09/lib/assets/.keep b/8_0/ch09/lib/assets/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/lib/tasks/.keep b/8_0/ch09/lib/tasks/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/log/.keep b/8_0/ch09/log/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/public/400.html b/8_0/ch09/public/400.html new file mode 100644 index 00000000..282dbc8c --- /dev/null +++ b/8_0/ch09/public/400.html @@ -0,0 +1,114 @@ + + + + + + + The server cannot process the request due to a client error (400 Bad Request) + + + + + + + + + + + + + +
+
+ +
+
+

The server cannot process the request due to a client error. Please check the request and try again. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch09/public/404.html b/8_0/ch09/public/404.html new file mode 100644 index 00000000..c0670bc8 --- /dev/null +++ b/8_0/ch09/public/404.html @@ -0,0 +1,114 @@ + + + + + + + The page you were looking for doesn’t exist (404 Not found) + + + + + + + + + + + + + +
+
+ +
+
+

The page you were looking for doesn’t exist. You may have mistyped the address or the page may have moved. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch09/public/406-unsupported-browser.html b/8_0/ch09/public/406-unsupported-browser.html new file mode 100644 index 00000000..9532a9cc --- /dev/null +++ b/8_0/ch09/public/406-unsupported-browser.html @@ -0,0 +1,114 @@ + + + + + + + Your browser is not supported (406 Not Acceptable) + + + + + + + + + + + + + +
+
+ +
+
+

Your browser is not supported.
Please upgrade your browser to continue.

+
+
+ + + + diff --git a/8_0/ch09/public/422.html b/8_0/ch09/public/422.html new file mode 100644 index 00000000..8bcf0601 --- /dev/null +++ b/8_0/ch09/public/422.html @@ -0,0 +1,114 @@ + + + + + + + The change you wanted was rejected (422 Unprocessable Entity) + + + + + + + + + + + + + +
+
+ +
+
+

The change you wanted was rejected. Maybe you tried to change something you didn’t have access to. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch09/public/500.html b/8_0/ch09/public/500.html new file mode 100644 index 00000000..d77718c3 --- /dev/null +++ b/8_0/ch09/public/500.html @@ -0,0 +1,114 @@ + + + + + + + We’re sorry, but something went wrong (500 Internal Server Error) + + + + + + + + + + + + + +
+
+ +
+
+

We’re sorry, but something went wrong.
If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/8_0/ch09/public/apple-touch-icon-precomposed.png b/8_0/ch09/public/apple-touch-icon-precomposed.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/public/apple-touch-icon.png b/8_0/ch09/public/apple-touch-icon.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/public/favicon.ico b/8_0/ch09/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/public/icon.png b/8_0/ch09/public/icon.png new file mode 100644 index 00000000..c4c9dbfb Binary files /dev/null and b/8_0/ch09/public/icon.png differ diff --git a/8_0/ch09/public/icon.svg b/8_0/ch09/public/icon.svg new file mode 100644 index 00000000..04b34bf8 --- /dev/null +++ b/8_0/ch09/public/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/8_0/ch09/public/railstutorial.png b/8_0/ch09/public/railstutorial.png new file mode 100644 index 00000000..70835e6b Binary files /dev/null and b/8_0/ch09/public/railstutorial.png differ diff --git a/8_0/ch09/public/robots.txt b/8_0/ch09/public/robots.txt new file mode 100644 index 00000000..c19f78ab --- /dev/null +++ b/8_0/ch09/public/robots.txt @@ -0,0 +1 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/8_0/ch09/storage/.keep b/8_0/ch09/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/test/application_system_test_case.rb b/8_0/ch09/test/application_system_test_case.rb new file mode 100644 index 00000000..d19212ab --- /dev/null +++ b/8_0/ch09/test/application_system_test_case.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/8_0/ch09/test/channels/application_cable/connection_test.rb b/8_0/ch09/test/channels/application_cable/connection_test.rb new file mode 100644 index 00000000..800405f1 --- /dev/null +++ b/8_0/ch09/test/channels/application_cable/connection_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase + # test "connects with cookies" do + # cookies.signed[:user_id] = 42 + # + # connect + # + # assert_equal connection.user_id, "42" + # end +end diff --git a/8_0/ch09/test/controllers/.keep b/8_0/ch09/test/controllers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/test/controllers/hello_codespaces_controller_test.rb b/8_0/ch09/test/controllers/hello_codespaces_controller_test.rb new file mode 100644 index 00000000..54353ea6 --- /dev/null +++ b/8_0/ch09/test/controllers/hello_codespaces_controller_test.rb @@ -0,0 +1,8 @@ +require "test_helper" + +class HelloCodespacesControllerTest < ActionDispatch::IntegrationTest + # test "should get index" do + # get "/" + # assert_response :success + # end +end diff --git a/8_0/ch09/test/controllers/sessions_controller_test.rb b/8_0/ch09/test/controllers/sessions_controller_test.rb new file mode 100644 index 00000000..8cc3f29e --- /dev/null +++ b/8_0/ch09/test/controllers/sessions_controller_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +class SessionsControllerTest < ActionDispatch::IntegrationTest + + test "should get new" do + get login_path + assert_response :success + end +end diff --git a/8_0/ch09/test/controllers/static_pages_controller_test.rb b/8_0/ch09/test/controllers/static_pages_controller_test.rb new file mode 100644 index 00000000..847856a3 --- /dev/null +++ b/8_0/ch09/test/controllers/static_pages_controller_test.rb @@ -0,0 +1,28 @@ +require "test_helper" + +class StaticPagesControllerTest < ActionDispatch::IntegrationTest + + test "should get home" do + get root_path + assert_response :success + assert_select "title", "Ruby on Rails Tutorial Sample App" + end + + test "should get help" do + get help_path + assert_response :success + assert_select "title", "Help | Ruby on Rails Tutorial Sample App" + end + + test "should get about" do + get about_path + assert_response :success + assert_select "title", "About | Ruby on Rails Tutorial Sample App" + end + + test "should get contact" do + get contact_path + assert_response :success + assert_select "title", "Contact | Ruby on Rails Tutorial Sample App" + end +end diff --git a/8_0/ch09/test/controllers/users_controller_test.rb b/8_0/ch09/test/controllers/users_controller_test.rb new file mode 100644 index 00000000..2b123196 --- /dev/null +++ b/8_0/ch09/test/controllers/users_controller_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +class UsersControllerTest < ActionDispatch::IntegrationTest + + test "should get new" do + get signup_path + assert_response :success + end +end diff --git a/8_0/ch09/test/fixtures/files/.keep b/8_0/ch09/test/fixtures/files/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/test/fixtures/users.yml b/8_0/ch09/test/fixtures/users.yml new file mode 100644 index 00000000..15c78353 --- /dev/null +++ b/8_0/ch09/test/fixtures/users.yml @@ -0,0 +1,4 @@ +michael: + name: Michael Example + email: michael@example.com + password_digest: <%= User.digest('password') %> diff --git a/8_0/ch09/test/helpers/.keep b/8_0/ch09/test/helpers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/test/helpers/sessions_helper_test.rb b/8_0/ch09/test/helpers/sessions_helper_test.rb new file mode 100644 index 00000000..735ff17a --- /dev/null +++ b/8_0/ch09/test/helpers/sessions_helper_test.rb @@ -0,0 +1,19 @@ +require "test_helper" + +class SessionsHelperTest < ActionView::TestCase + + def setup + @user = users(:michael) + remember(@user) + end + + test "current_user returns right user when session is nil" do + assert_equal @user, current_user + assert is_logged_in? + end + + test "current_user returns nil when remember digest is wrong" do + @user.update_attribute(:remember_digest, User.digest(User.new_token)) + assert_nil current_user + end +end diff --git a/8_0/ch09/test/integration/.keep b/8_0/ch09/test/integration/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/test/integration/site_layout_test.rb b/8_0/ch09/test/integration/site_layout_test.rb new file mode 100644 index 00000000..0f7ca649 --- /dev/null +++ b/8_0/ch09/test/integration/site_layout_test.rb @@ -0,0 +1,13 @@ +require "test_helper" + +class SiteLayoutTest < ActionDispatch::IntegrationTest + + test "layout links" do + get root_path + assert_template 'static_pages/home' + assert_select "a[href=?]", root_path, count: 2 + assert_select "a[href=?]", help_path + assert_select "a[href=?]", about_path + assert_select "a[href=?]", contact_path + end +end diff --git a/8_0/ch09/test/integration/users_login_test.rb b/8_0/ch09/test/integration/users_login_test.rb new file mode 100644 index 00000000..5f3d7555 --- /dev/null +++ b/8_0/ch09/test/integration/users_login_test.rb @@ -0,0 +1,56 @@ +require "test_helper" + +class UsersLoginTest < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + end + + test "login with valid email/invalid password" do + get login_path + assert_template 'sessions/new' + post login_path, params: { session: { email: @user.email, + password: "invalid" } } + assert_not is_logged_in? + assert_response :unprocessable_entity + assert_template 'sessions/new' + assert_not flash.empty? + get root_path + assert flash.empty? + end + + test "login with valid information followed by logout" do + post login_path, params: { session: { email: @user.email, + password: 'password' } } + assert is_logged_in? + assert_redirected_to @user + follow_redirect! + assert_template 'users/show' + assert_select "a[href=?]", login_path, count: 0 + assert_select "a[href=?]", logout_path + assert_select "a[href=?]", user_path(@user) + delete logout_path + assert_not is_logged_in? + assert_response :see_other + assert_redirected_to root_url + # 2番目のウィンドウでログアウトをクリックするユーザーをシミュレートする + delete logout_path + follow_redirect! + assert_select "a[href=?]", login_path + assert_select "a[href=?]", logout_path, count: 0 + assert_select "a[href=?]", user_path(@user), count: 0 + end + + test "login with remembering" do + log_in_as(@user, remember_me: '1') + assert_not cookies[:remember_token].blank? + end + + test "login without remembering" do + # Cookieを保存してログイン + log_in_as(@user, remember_me: '1') + # Cookieが削除されていることを検証してからログイン + log_in_as(@user, remember_me: '0') + assert cookies[:remember_token].blank? + end +end diff --git a/8_0/ch09/test/integration/users_signup_test.rb b/8_0/ch09/test/integration/users_signup_test.rb new file mode 100644 index 00000000..cbd8ff99 --- /dev/null +++ b/8_0/ch09/test/integration/users_signup_test.rb @@ -0,0 +1,28 @@ +require "test_helper" + +class UsersSignupTest < ActionDispatch::IntegrationTest + + test "invalid signup information" do + get signup_path + assert_no_difference 'User.count' do + post users_path, params: { user: { name: "", + email: "user@invalid", + password: "foo", + password_confirmation: "bar" } } + end + assert_response :unprocessable_entity + assert_template 'users/new' + end + + test "valid signup information" do + assert_difference 'User.count', 1 do + post users_path, params: { user: { name: "Example User", + email: "user@example.com", + password: "password", + password_confirmation: "password" } } + end + follow_redirect! + assert_template 'users/show' + assert is_logged_in? + end +end diff --git a/8_0/ch09/test/mailers/.keep b/8_0/ch09/test/mailers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/test/models/.keep b/8_0/ch09/test/models/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/test/models/user_test.rb b/8_0/ch09/test/models/user_test.rb new file mode 100644 index 00000000..cbd59e80 --- /dev/null +++ b/8_0/ch09/test/models/user_test.rb @@ -0,0 +1,71 @@ +require "test_helper" + +class UserTest < ActiveSupport::TestCase + + def setup + @user = User.new(name: "Example User", email: "user@example.com", + password: "foobar", password_confirmation: "foobar") + end + + test "should be valid" do + assert @user.valid? + end + + test "name should be present" do + @user.name = " " + assert_not @user.valid? + end + + test "email should be present" do + @user.email = " " + assert_not @user.valid? + end + + test "name should not be too long" do + @user.name = "a" * 51 + assert_not @user.valid? + end + + test "email should not be too long" do + @user.email = "a" * 244 + "@example.com" + assert_not @user.valid? + end + + test "email validation should accept valid addresses" do + valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org + first.last@foo.jp alice+bob@baz.cn] + valid_addresses.each do |valid_address| + @user.email = valid_address + assert @user.valid?, "#{valid_address.inspect} should be valid" + end + end + + test "email validation should reject invalid addresses" do + invalid_addresses = %w[user@example,com user_at_foo.org user.name@example. + foo@bar_baz.com foo@bar+baz.com] + invalid_addresses.each do |invalid_address| + @user.email = invalid_address + assert_not @user.valid?, "#{invalid_address.inspect} should be invalid" + end + end + + test "email addresses should be unique" do + duplicate_user = @user.dup + @user.save + assert_not duplicate_user.valid? + end + + test "password should be present (nonblank)" do + @user.password = @user.password_confirmation = " " * 6 + assert_not @user.valid? + end + + test "password should have a minimum length" do + @user.password = @user.password_confirmation = "a" * 5 + assert_not @user.valid? + end + + test "authenticated? should return false for a user with nil digest" do + assert_not @user.authenticated?('') + end +end diff --git a/8_0/ch09/test/system/.keep b/8_0/ch09/test/system/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/test/test_helper.rb b/8_0/ch09/test/test_helper.rb new file mode 100644 index 00000000..5fbcafd0 --- /dev/null +++ b/8_0/ch09/test/test_helper.rb @@ -0,0 +1,33 @@ +ENV["RAILS_ENV"] ||= "test" +require_relative "../config/environment" +require "rails/test_help" +require "minitest/reporters" +Minitest::Reporters.use! + +class ActiveSupport::TestCase + # 指定のワーカー数でテストを並列実行する + parallelize(workers: :number_of_processors) + + # test/fixtures/*.ymlにあるすべてのfixtureをセットアップする + fixtures :all + + # テストユーザーがログイン中の場合にtrueを返す + def is_logged_in? + !session[:user_id].nil? + end + + # テストユーザーとしてログインする + def log_in_as(user) + session[:user_id] = user.id + end +end + +class ActionDispatch::IntegrationTest + + # テストユーザーとしてログインする + def log_in_as(user, password: 'password', remember_me: '1') + post login_path, params: { session: { email: user.email, + password: password, + remember_me: remember_me } } + end +end \ No newline at end of file diff --git a/8_0/ch09/tmp/.keep b/8_0/ch09/tmp/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/tmp/pids/.keep b/8_0/ch09/tmp/pids/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/tmp/storage/.keep b/8_0/ch09/tmp/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/vendor/.keep b/8_0/ch09/vendor/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch09/vendor/javascript/.keep b/8_0/ch09/vendor/javascript/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/.devcontainer/devcontainer.json b/8_0/ch10/.devcontainer/devcontainer.json new file mode 100644 index 00000000..e7243217 --- /dev/null +++ b/8_0/ch10/.devcontainer/devcontainer.json @@ -0,0 +1,105 @@ +// Specifications: https://containers.dev/implementors/json_reference/ +// Format details: https://aka.ms/devcontainer.json +// Config options: https://github.com/microsoft/vscode-dev-containers/tree/main/containers/ruby +{ + "name": "railstutorial", + + // Universal is well-customized image for Codespaces: + // https://hub.docker.com/_/microsoft-devcontainers-universal + //"image": "mcr.microsoft.com/devcontainers/universal:2", + //"features": { + // "ghcr.io/devcontainers/features/ruby:1": {} + //}, + + // Use Ruby image if you want to pin Ruby version like '3.2' + // https://github.com/devcontainers/images/tree/main/src/ruby + "image": "mcr.microsoft.com/devcontainers/ruby:3.2", + //"workspaceFolder": "/railstutorial", => Container build failed + + // Enable learners to choose an affordable spec, starting at minimum one. + //"hostRequirements": { + // "cpus": 2, + // "memory": "4gb", + // "storage": "32gb" + //}, + + "waitFor": "onCreateCommand", + //"onCreateCommand": "", + "onCreateCommand": "gem install solargraph -v '0.58.2' -N", + //"onCreateCommand": "gem install solargraph -v '0.50.0' -N && gem install ruby-lsp -N", + //"onCreateCommand": "gem install ruby-lsp -N", + //# => Solargraph gem not found. Run `gem install solargraph` or update your Gemfile. + "updateContentCommand": "bundle install", + "postCreateCommand": "", + "postAttachCommand": { + "server": "rails server" + }, + "customizations": { + "codespaces": { + "openFiles": [ + "app/views/hello/index.html.erb" + ] + }, + "vscode": { + "extensions": [ + "GitHub.codespaces", + "Shopify.ruby-lsp", // https://github.com/Shopify/ruby-lsp + "castwide.solargraph" // https://github.com/castwide/vscode-solargraph + //"rebornix.Ruby", // https://github.com/rubyide/vscode-ruby (Deprecated) + + ], + "settings": { + // General settings for Codespaces (VS Code) + //"ruby.useLanguageServer": true , + //"ruby.format": "rubocop", + //"ruby.lint": { "rubocop": true }, + //"ruby.intellisense": "rubyLocate", + "editor.tabSize": 2, + "editor.formatOnSave": false, // Disable onSave to show diff edited by learners only + "editor.formatOnType": false, // Disable onType for the same reason above + "editor.insertSpaces": true, // Use spaces, not tabs, to avoid errors for learners + "editor.renderWhitespace": "none", + "[ruby]": { + "editor.defaultFormatter": "castwide.solargraph", + "editor.semanticHighlighting.enabled": true // Enable semantic highlighting + }, + "files.associations": { "*.erb": "erb" }, + "emmet.includeLanguages": { "erb": "html" }, + + // Settings for Solargraph + // https://github.com/castwide/solargraph + "solargraph.useBundler": false, + "solargraph.diagnostics": false, + "solargraph.formatting": true, // Use Ctrl+Shift+P->Format to format + "solargraph.autoformat": false, + "solargraph.definitions": true, + "solargraph.completion": true, + "solargraph.references": true, + "solargraph.symbols": true, + "solargraph.rename": true, + "solargraph.hover": true, + + // Settings for Ruby LSP + // https://github.com/Shopify/ruby-lsp + "rubyLsp.rubyVersionManager": { + "identifier": "none" + }, + "rubyLsp.formatter": "none", + "rubyLsp.enabledFeatures": { + "diagnostics": false, + "formatting": false + } + } + } + }, + "remoteEnv": { + "EDITOR": "code --wait" + }, + "portsAttributes": { + "3000": { + "label": "Application", + "onAutoForward": "openPreview" + } + }, + "forwardPorts": [3000] +} diff --git a/8_0/ch10/.devcontainer/icon.svg b/8_0/ch10/.devcontainer/icon.svg new file mode 100644 index 00000000..f8444a4a --- /dev/null +++ b/8_0/ch10/.devcontainer/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/8_0/ch10/.gitattributes b/8_0/ch10/.gitattributes new file mode 100644 index 00000000..31eeee0b --- /dev/null +++ b/8_0/ch10/.gitattributes @@ -0,0 +1,7 @@ +# See https://git-scm.com/docs/gitattributes for more about git attribute files. + +# Mark the database schema as having been generated. +db/schema.rb linguist-generated + +# Mark any vendored files as having been vendored. +vendor/* linguist-vendored diff --git a/8_0/ch10/.gitignore b/8_0/ch10/.gitignore new file mode 100644 index 00000000..e1ef53ef --- /dev/null +++ b/8_0/ch10/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-* + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore uploaded files in development. +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/ +!/tmp/storage/.keep + +/public/assets + +# Ignore master key for decrypting credentials and more. +/config/master.key + +# Ignore history file used by IRB for interactive Ruby. +.irb_history + +dump.rdb diff --git a/8_0/ch10/.irbrc b/8_0/ch10/.irbrc new file mode 100644 index 00000000..3737e036 --- /dev/null +++ b/8_0/ch10/.irbrc @@ -0,0 +1 @@ +IRB.conf[:COMPLETOR] = :type # default is :regexp diff --git a/8_0/ch10/.solargraph.yml b/8_0/ch10/.solargraph.yml new file mode 100644 index 00000000..76ac24a6 --- /dev/null +++ b/8_0/ch10/.solargraph.yml @@ -0,0 +1,32 @@ +--- +include: +- "**/*.rb" +exclude: +- spec/**/* +- test/**/* +- vendor/**/* +- ".bundle/**/*" +require: +- actioncable +- actionmailer +- actionpack +- actionview +- activejob +- activemodel +- activerecord +- activestorage +- activesupport +domains: [] +reporters: +- rubocop +- require_not_found +- typecheck +formatter: + rubocop: + cops: safe + except: [] + only: [] + extra_args: [] +require_paths: [] +plugins: [] +max_files: 5000 diff --git a/8_0/ch10/.vscode/settings.json b/8_0/ch10/.vscode/settings.json new file mode 100644 index 00000000..cf85f519 --- /dev/null +++ b/8_0/ch10/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.minimap.enabled": false +} diff --git a/8_0/ch10/Gemfile b/8_0/ch10/Gemfile new file mode 100644 index 00000000..a4765cd8 --- /dev/null +++ b/8_0/ch10/Gemfile @@ -0,0 +1,47 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '3.2.10' + +gem 'bcrypt', '3.1.18' +gem 'bootsnap', '1.16.0', require: false +gem 'bootstrap-sass', '3.4.1' +gem 'bootstrap-will_paginate', '1.0.0' +gem 'concurrent-ruby', '1.3.4' +gem 'faker', '2.21.0' +gem 'importmap-rails', '1.1.5' +gem 'jbuilder', '2.14.1' +gem 'puma', '6.6.1' +gem 'rails', '8.0.2.1' +gem 'sassc-rails', '2.1.2' +gem 'sprockets-rails', '3.4.2' +gem 'sqlite3', '2.7.3' +gem 'stimulus-rails', '1.2.1' +gem 'turbo-rails', '1.4.0' +gem 'will_paginate', '3.3.1' + +group :development, :test do + gem 'debug', '1.7.1', platforms: %i[mri mingw x64_mingw] + gem 'reline', '0.5.10' +end + +group :development do + gem 'irb', '1.15.2' + gem 'repl_type_completor', '0.1.10' + gem 'solargraph', '0.58.2' + gem 'web-console', '4.2.0' +end + +group :test do + gem 'capybara', '3.38.0' + gem 'guard', '2.18.0' + gem 'guard-minitest', '2.4.6' + gem 'minitest', '5.18.0' + gem 'minitest-reporters', '1.6.0' + gem 'rails-controller-testing', '1.0.5' + gem 'selenium-webdriver', '4.8.3' + gem 'webdrivers', '5.2.0' +end + +# Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります +# gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] diff --git a/8_0/ch10/Gemfile.lock b/8_0/ch10/Gemfile.lock new file mode 100644 index 00000000..3e0c288a --- /dev/null +++ b/8_0/ch10/Gemfile.lock @@ -0,0 +1,450 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + actionmailer (8.0.2.1) + actionpack (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.0.2.1) + actionview (= 8.0.2.1) + activesupport (= 8.0.2.1) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.0.2.1) + actionpack (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (8.0.2.1) + activesupport (= 8.0.2.1) + builder (~> 3.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.3.6) + activemodel (8.0.2.1) + activesupport (= 8.0.2.1) + activerecord (8.0.2.1) + activemodel (= 8.0.2.1) + activesupport (= 8.0.2.1) + timeout (>= 0.4.0) + activestorage (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activesupport (= 8.0.2.1) + marcel (~> 1.0) + activesupport (8.0.2.1) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + ansi (1.5.0) + ast (2.4.3) + autoprefixer-rails (10.4.21.0) + execjs (~> 2) + backport (1.2.0) + base64 (0.3.0) + bcrypt (3.1.18) + benchmark (0.4.1) + bigdecimal (3.2.3) + bindex (0.8.1) + bootsnap (1.16.0) + msgpack (~> 1.2) + bootstrap-sass (3.4.1) + autoprefixer-rails (>= 5.2.1) + sassc (>= 2.0.0) + bootstrap-will_paginate (1.0.0) + will_paginate + builder (3.3.0) + capybara (3.38.0) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + coderay (1.1.3) + concurrent-ruby (1.3.4) + connection_pool (2.5.4) + crass (1.0.6) + date (3.4.1) + debug (1.7.1) + irb (>= 1.5.0) + reline (>= 0.3.1) + diff-lcs (1.6.2) + drb (2.2.3) + erb (5.0.2) + erubi (1.13.1) + execjs (2.10.1) + faker (2.21.0) + i18n (>= 1.8.11, < 2) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) + formatador (1.2.0) + reline + globalid (1.2.1) + activesupport (>= 6.1) + guard (2.18.0) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.13.0) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-minitest (2.4.6) + guard-compat (~> 1.2) + minitest (>= 3.0) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + importmap-rails (1.1.5) + actionpack (>= 6.0.0) + railties (>= 6.0.0) + io-console (0.8.1) + irb (1.15.2) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jaro_winkler (1.6.1) + jbuilder (2.14.1) + actionview (>= 7.0.0) + activesupport (>= 7.0.0) + json (2.13.2) + kramdown (2.5.1) + rexml (>= 3.3.9) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) + loofah (2.24.1) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + lumberjack (1.4.1) + mail (2.8.1) + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.0.4) + matrix (0.4.3) + method_source (1.1.0) + mini_mime (1.1.5) + minitest (5.18.0) + minitest-reporters (1.6.0) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + msgpack (1.8.0) + nenv (0.3.0) + net-imap (0.5.10) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.4) + nokogiri (1.19.1-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-musl) + racc (~> 1.4) + notiffany (0.1.3) + nenv (~> 0.1) + shellany (~> 0.0) + observer (0.1.2) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.9.0) + ast (~> 2.4.1) + racc + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + prism (1.4.0) + pry (0.15.2) + coderay (~> 1.1) + method_source (~> 1.0) + psych (5.2.6) + date + stringio + public_suffix (6.0.2) + puma (6.6.1) + nio4r (~> 2.0) + racc (1.8.1) + rack (3.2.5) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.2.1) + rack (>= 3) + rails (8.0.2.1) + actioncable (= 8.0.2.1) + actionmailbox (= 8.0.2.1) + actionmailer (= 8.0.2.1) + actionpack (= 8.0.2.1) + actiontext (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activemodel (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + bundler (>= 1.15.0) + railties (= 8.0.2.1) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.0) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rbs (3.6.1) + logger + rdoc (6.14.2) + erb + psych (>= 4.0.0) + regexp_parser (2.11.2) + reline (0.5.10) + io-console (~> 0.5) + repl_type_completor (0.1.10) + prism (~> 1.0) + rbs (>= 2.7.0, < 4.0.0) + reverse_markdown (3.0.0) + nokogiri + rexml (3.4.2) + rubocop (1.80.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.46.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.46.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + ruby-progressbar (1.13.0) + rubyzip (2.4.1) + sassc (2.4.0) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt + securerandom (0.4.1) + selenium-webdriver (4.8.3) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) + shellany (0.0.1) + solargraph (0.58.2) + backport (~> 1.2) + benchmark (~> 0.4) + bundler (~> 2.0) + diff-lcs (~> 1.4) + jaro_winkler (~> 1.6, >= 1.6.1) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.1) + logger (~> 1.6) + observer (~> 0.1) + ostruct (~> 0.6) + parser (~> 3.0) + prism (~> 1.4) + rbs (~> 3.6.1) + reverse_markdown (~> 3.0) + rubocop (~> 1.38) + thor (~> 1.0) + tilt (~> 2.0) + yard (~> 0.9, >= 0.9.24) + yard-solargraph (~> 0.1) + sprockets (4.2.2) + concurrent-ruby (~> 1.0) + logger + rack (>= 2.2.4, < 4) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + sprockets (>= 3.0.0) + sqlite3 (2.7.3-aarch64-linux-gnu) + sqlite3 (2.7.3-aarch64-linux-musl) + sqlite3 (2.7.3-arm-linux-gnu) + sqlite3 (2.7.3-arm-linux-musl) + sqlite3 (2.7.3-arm64-darwin) + sqlite3 (2.7.3-x86_64-darwin) + sqlite3 (2.7.3-x86_64-linux-gnu) + sqlite3 (2.7.3-x86_64-linux-musl) + stimulus-rails (1.2.1) + railties (>= 6.0.0) + stringio (3.1.7) + thor (1.4.0) + tilt (2.6.1) + timeout (0.4.3) + turbo-rails (1.4.0) + actionpack (>= 6.0.0) + activejob (>= 6.0.0) + railties (>= 6.0.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.1.5) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + uri (1.0.4) + useragent (0.16.11) + web-console (4.2.0) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + webdrivers (5.2.0) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (~> 4.0) + websocket (1.2.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + will_paginate (3.3.1) + xpath (3.2.0) + nokogiri (~> 1.8) + yard (0.9.37) + yard-solargraph (0.1.0) + yard (~> 0.9) + zeitwerk (2.7.3) + +PLATFORMS + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + bcrypt (= 3.1.18) + bootsnap (= 1.16.0) + bootstrap-sass (= 3.4.1) + bootstrap-will_paginate (= 1.0.0) + capybara (= 3.38.0) + concurrent-ruby (= 1.3.4) + debug (= 1.7.1) + faker (= 2.21.0) + guard (= 2.18.0) + guard-minitest (= 2.4.6) + importmap-rails (= 1.1.5) + irb (= 1.15.2) + jbuilder (= 2.14.1) + minitest (= 5.18.0) + minitest-reporters (= 1.6.0) + puma (= 6.6.1) + rails (= 8.0.2.1) + rails-controller-testing (= 1.0.5) + reline (= 0.5.10) + repl_type_completor (= 0.1.10) + sassc-rails (= 2.1.2) + selenium-webdriver (= 4.8.3) + solargraph (= 0.58.2) + sprockets-rails (= 3.4.2) + sqlite3 (= 2.7.3) + stimulus-rails (= 1.2.1) + turbo-rails (= 1.4.0) + web-console (= 4.2.0) + webdrivers (= 5.2.0) + will_paginate (= 3.3.1) + +RUBY VERSION + ruby 3.2.10p266 + +BUNDLED WITH + 2.5.6 diff --git a/8_0/ch10/Guardfile b/8_0/ch10/Guardfile new file mode 100644 index 00000000..5def34a0 --- /dev/null +++ b/8_0/ch10/Guardfile @@ -0,0 +1,76 @@ + require "active_support/inflector" + # Guardのマッチング規則を定義 + guard :minitest, all_on_start: false do + watch(%r{^test/(.*)/?(.*)_test\.rb$}) + watch('test/test_helper.rb') { 'test' } + watch('config/routes.rb') { interface_tests } + watch(%r{app/views/layouts/*}) { interface_tests } + watch(%r{^app/models/(.*?)\.rb$}) do |matches| + ["test/models/#{matches[1]}_test.rb", + "test/integration/microposts_interface_test.rb"] + end + watch(%r{^test/fixtures/(.*?)\.yml$}) do |matches| + "test/models/#{matches[1].singularize}_test.rb" + end + watch(%r{^app/mailers/(.*?)\.rb$}) do |matches| + "test/mailers/#{matches[1]}_test.rb" + end + watch(%r{^app/views/(.*)_mailer/.*$}) do |matches| + "test/mailers/#{matches[1]}_mailer_test.rb" + end + watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches| + resource_tests(matches[1]) + end + watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches| + ["test/controllers/#{matches[1]}_controller_test.rb"] + + integration_tests(matches[1]) + end + watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches| + integration_tests(matches[1]) + end + watch('app/views/layouts/application.html.erb') do + 'test/integration/site_layout_test.rb' + end + watch('app/helpers/sessions_helper.rb') do + integration_tests << 'test/helpers/sessions_helper_test.rb' + end + watch('app/controllers/sessions_controller.rb') do + ['test/controllers/sessions_controller_test.rb', + 'test/integration/users_login_test.rb'] + end + watch('app/controllers/account_activations_controller.rb') do + 'test/integration/users_signup_test.rb' + end + watch(%r{app/views/users/*}) do + resource_tests('users') + + ['test/integration/microposts_interface_test.rb'] + end + watch('app/controllers/relationships_controller.rb') do + ['test/controllers/relationships_controller_test.rb', + 'test/integration/following_test.rb'] + end + end + + # 指定のリソースに対応する統合テストを返す + def integration_tests(resource = :all) + if resource == :all + Dir["test/integration/*"] + else + Dir["test/integration/#{resource}_*.rb"] + end + end + + # インターフェースが該当するすべてのテストを返す + def interface_tests + integration_tests << "test/controllers" + end + + # 指定のリソースに対応するコントローラのテストを返す + def controller_test(resource) + "test/controllers/#{resource}_controller_test.rb" + end + + # 指定のリソースに対応するすべてのテストを返す + def resource_tests(resource) + integration_tests(resource) << controller_test(resource) + end diff --git a/8_0/ch10/LICENSE b/8_0/ch10/LICENSE new file mode 100644 index 00000000..20622e0c --- /dev/null +++ b/8_0/ch10/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 GitHub & 2023 YassLab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/8_0/ch10/README.md b/8_0/ch10/README.md new file mode 100644 index 00000000..17ca733d --- /dev/null +++ b/8_0/ch10/README.md @@ -0,0 +1,35 @@ +# Ruby on Rails チュートリアルのサンプルアプリケーション + +これは、次の教材で作られたサンプルアプリケーションです。 +[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +(第8版) +[Michael Hartl](https://www.michaelhartl.com/) 著 + +## ライセンス + +[Ruby on Rails チュートリアル](https://railstutorial.jp/)内にある +ソースコードはMITライセンスとBeerwareライセンスのもとで公開されています。 +詳細は [LICENSE.md](LICENSE.md) をご覧ください。 + +## 使い方 + +このアプリケーションを動かす場合は、まずはリポジトリをフォークしてください。 + +フォークしたリポジトリで、「Code」から「Codespaces」タブに移動し、 +「Create codespace on main」をクリックすると環境構築がスタートします。 +Railsサーバーが立ち上がり、シンプルブラウザが表示されるまでしばらくお待ちください。 + +次に、データベースへのマイグレーションを実行します。 + +``` +$ rails db:migrate +``` + +最後に、テストを実行してうまく動いているかどうか確認してください。 + +``` +$ rails test +``` + +詳しくは、[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +を参考にしてください。 diff --git a/8_0/ch10/Rakefile b/8_0/ch10/Rakefile new file mode 100644 index 00000000..9a5ea738 --- /dev/null +++ b/8_0/ch10/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/8_0/ch10/app/assets/config/manifest.js b/8_0/ch10/app/assets/config/manifest.js new file mode 100644 index 00000000..ddd546a0 --- /dev/null +++ b/8_0/ch10/app/assets/config/manifest.js @@ -0,0 +1,4 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js diff --git a/8_0/ch10/app/assets/images/.keep b/8_0/ch10/app/assets/images/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/app/assets/images/rails.svg b/8_0/ch10/app/assets/images/rails.svg new file mode 100644 index 00000000..e1971850 --- /dev/null +++ b/8_0/ch10/app/assets/images/rails.svg @@ -0,0 +1,35 @@ + + + +rails-logo + + + + + + + + + + + + + + + + + + + + + diff --git a/8_0/ch10/app/assets/stylesheets/application.css b/8_0/ch10/app/assets/stylesheets/application.css new file mode 100644 index 00000000..288b9ab7 --- /dev/null +++ b/8_0/ch10/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/8_0/ch10/app/assets/stylesheets/custom.scss b/8_0/ch10/app/assets/stylesheets/custom.scss new file mode 100644 index 00000000..4fd609ce --- /dev/null +++ b/8_0/ch10/app/assets/stylesheets/custom.scss @@ -0,0 +1,227 @@ +@import "bootstrap-sprockets"; +@import "bootstrap"; + +/* mixins, variables, etc. */ + +$gray-medium-light: #eaeaea; + +@mixin box_sizing { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* universal */ + +body { + padding-top: 60px; +} + +section { + overflow: auto; +} + +textarea { + resize: vertical; +} + +.center { + text-align: center; + h1 { + margin-bottom: 10px; + } +} + +/* typography */ + +h1, h2, h3, h4, h5, h6 { + line-height: 1; +} + +h1 { + font-size: 3em; + letter-spacing: -2px; + margin-bottom: 30px; + text-align: center; +} + +h2 { + font-size: 1.2em; + letter-spacing: -1px; + margin-bottom: 30px; + text-align: center; + font-weight: normal; + color: $gray-light; +} + +p { + font-size: 1.1em; + line-height: 1.7em; +} + + +/* header */ + +#logo { + float: left; + margin-right: 10px; + font-size: 1.7em; + color: white; + text-transform: uppercase; + letter-spacing: -1px; + padding-top: 9px; + font-weight: bold; + &:hover { + color: white; + text-decoration: none; + } +} + +/* footer */ + +footer { + margin-top: 45px; + padding-top: 5px; + border-top: 1px solid $gray-medium-light; + color: $gray-light; + a { + color: $gray; + &:hover { + color: $gray-darker; + } + } + small { + float: left; + } + ul { + float: right; + list-style: none; + li { + float: left; + margin-left: 15px; + } + } +} +@media (max-width: 800px) { + footer { + small { + display: block; + float: none; + margin-bottom: 1em; + } + ul { + float: none; + padding: 0; + li { + float: none; + margin-left: 0; + } + } + } +} + +/* miscellaneous */ + +.debug_dump { + clear: both; + float: left; + width: 100%; + margin-top: 45px; +} + +/* sidebar */ + +aside { + section.user_info { + margin-top: 20px; + } + section { + padding: 10px 0; + margin-top: 20px; + &:first-child { + border: 0; + padding-top: 0; + } + span { + display: block; + margin-bottom: 3px; + line-height: 1; + } + h1 { + font-size: 1.4em; + text-align: left; + letter-spacing: -1px; + margin-bottom: 3px; + margin-top: 0px; + } + } +} + +.gravatar { + float: left; + margin-right: 10px; +} + +.gravatar_edit { + margin-top: 15px; +} + +/* forms */ + +input, textarea { + border: 1px solid #bbb; + width: 100%; + margin-bottom: 15px; + @include box_sizing; +} + +input { + height: auto !important; +} + +#error_explanation { + color: red; + ul { + color: red; + margin: 0 0 30px 0; + } +} + +.field_with_errors { + @extend .has-error; + .form-control { + color: $state-danger-text; + } +} + +.checkbox { + margin-top: -10px; + margin-bottom: 10px; + span { + margin-left: 20px; + font-weight: normal; + } +} + +#session_remember_me { + width: auto; + margin-left: 0; +} + +/* Dropdown menu */ + +.dropdown-menu.active { + display: block; +} + +/* Users index */ + +.users { + list-style: none; + margin: 0; + li { + overflow: auto; + padding: 10px 0; + border-bottom: 1px solid $gray-lighter; + } +} \ No newline at end of file diff --git a/8_0/ch10/app/assets/stylesheets/hello.css b/8_0/ch10/app/assets/stylesheets/hello.css new file mode 100644 index 00000000..497881a4 --- /dev/null +++ b/8_0/ch10/app/assets/stylesheets/hello.css @@ -0,0 +1,40 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.codespaces { + text-align: center; +} +.codespaces code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} +.codespaces-logo { + height: 40vmin; + pointer-events: none; +} +.codespaces-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} +.codespaces-link { + color: #61dafb; +} + +.heart { + color: #ff0000; +} +.small { + font-size: 0.75rem; +} diff --git a/8_0/ch10/app/channels/application_cable/channel.rb b/8_0/ch10/app/channels/application_cable/channel.rb new file mode 100644 index 00000000..d6726972 --- /dev/null +++ b/8_0/ch10/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/8_0/ch10/app/channels/application_cable/connection.rb b/8_0/ch10/app/channels/application_cable/connection.rb new file mode 100644 index 00000000..0ff5442f --- /dev/null +++ b/8_0/ch10/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/8_0/ch10/app/controllers/application_controller.rb b/8_0/ch10/app/controllers/application_controller.rb new file mode 100644 index 00000000..09faff3f --- /dev/null +++ b/8_0/ch10/app/controllers/application_controller.rb @@ -0,0 +1,3 @@ +class ApplicationController < ActionController::Base + include SessionsHelper +end diff --git a/8_0/ch10/app/controllers/concerns/.keep b/8_0/ch10/app/controllers/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/app/controllers/hello_controller.rb b/8_0/ch10/app/controllers/hello_controller.rb new file mode 100644 index 00000000..52037537 --- /dev/null +++ b/8_0/ch10/app/controllers/hello_controller.rb @@ -0,0 +1,4 @@ +class HelloController < ApplicationController + def index + end +end diff --git a/8_0/ch10/app/controllers/sessions_controller.rb b/8_0/ch10/app/controllers/sessions_controller.rb new file mode 100644 index 00000000..4a3591d1 --- /dev/null +++ b/8_0/ch10/app/controllers/sessions_controller.rb @@ -0,0 +1,24 @@ +class SessionsController < ApplicationController + + def new + end + + def create + user = User.find_by(email: params[:session][:email].downcase) + if user && user.authenticate(params[:session][:password]) + forwarding_url = session[:forwarding_url] + reset_session + params[:session][:remember_me] == '1' ? remember(user) : forget(user) + log_in user + redirect_to forwarding_url || user + else + flash.now[:danger] = 'Invalid email/password combination' + render 'new', status: :unprocessable_entity + end + end + + def destroy + log_out if logged_in? + redirect_to root_url, status: :see_other + end +end diff --git a/8_0/ch10/app/controllers/static_pages_controller.rb b/8_0/ch10/app/controllers/static_pages_controller.rb new file mode 100644 index 00000000..0673ad5e --- /dev/null +++ b/8_0/ch10/app/controllers/static_pages_controller.rb @@ -0,0 +1,14 @@ +class StaticPagesController < ApplicationController + + def home + end + + def help + end + + def about + end + + def contact + end +end diff --git a/8_0/ch10/app/controllers/users_controller.rb b/8_0/ch10/app/controllers/users_controller.rb new file mode 100644 index 00000000..c71d2dcc --- /dev/null +++ b/8_0/ch10/app/controllers/users_controller.rb @@ -0,0 +1,76 @@ +class UsersController < ApplicationController + before_action :logged_in_user, only: [:index, :edit, :update, :destroy] + before_action :correct_user, only: [:edit, :update] + before_action :admin_user, only: :destroy + + def index + @users = User.paginate(page: params[:page]) + end + + def show + @user = User.find(params[:id]) + end + + def new + @user = User.new + end + + def create + @user = User.new(user_params) + if @user.save + reset_session + log_in @user + flash[:success] = "Welcome to the Sample App!" + redirect_to @user + else + render 'new', status: :unprocessable_entity + end + end + + def edit + end + + def update + if @user.update(user_params) + flash[:success] = "Profile updated" + redirect_to @user + else + render 'edit', status: :unprocessable_entity + end + end + + def destroy + User.find(params[:id]).destroy + flash[:success] = "User deleted" + redirect_to users_url, status: :see_other + end + + private + + def user_params + params.expect(user: [:name, :email, :password, + :password_confirmation]) + end + + # beforeフィルタ + + # ログイン済みユーザーかどうか確認 + def logged_in_user + unless logged_in? + store_location + flash[:danger] = "Please log in." + redirect_to login_url, status: :see_other + end + end + + # 正しいユーザーかどうか確認 + def correct_user + @user = User.find(params[:id]) + redirect_to(root_url, status: :see_other) unless current_user?(@user) + end + + # 管理者かどうか確認 + def admin_user + redirect_to(root_url, status: :see_other) unless current_user.admin? + end +end diff --git a/8_0/ch10/app/helpers/application_helper.rb b/8_0/ch10/app/helpers/application_helper.rb new file mode 100644 index 00000000..708e4e48 --- /dev/null +++ b/8_0/ch10/app/helpers/application_helper.rb @@ -0,0 +1,12 @@ +module ApplicationHelper + + # ページごとの完全なタイトルを返します。 # コメント行 + def full_title(page_title = '') # メソッド定義とオプション引数 + base_title = "Ruby on Rails Tutorial Sample App" # 変数への代入 + if page_title.empty? # 論理値テスト + base_title # 暗黙の戻り値 + else + "#{page_title} | #{base_title}" # 文字列の結合 + end + end +end diff --git a/8_0/ch10/app/helpers/hello_codespaces_helper.rb b/8_0/ch10/app/helpers/hello_codespaces_helper.rb new file mode 100644 index 00000000..874a2ca2 --- /dev/null +++ b/8_0/ch10/app/helpers/hello_codespaces_helper.rb @@ -0,0 +1,2 @@ +module HelloCodespacesHelper +end diff --git a/8_0/ch10/app/helpers/sessions_helper.rb b/8_0/ch10/app/helpers/sessions_helper.rb new file mode 100644 index 00000000..ff5bcf0a --- /dev/null +++ b/8_0/ch10/app/helpers/sessions_helper.rb @@ -0,0 +1,62 @@ +module SessionsHelper + + # 渡されたユーザーでログインする + def log_in(user) + session[:user_id] = user.id + # セッションリプレイ攻撃から保護する + # 詳しくは https://techracho.bpsinc.jp/hachi8833/2023_06_02/130443 を参照 + session[:session_token] = user.session_token + end + + # ユーザーを永続的セッションに保存する + def remember(user) + user.remember + cookies.permanent.encrypted[:user_id] = user.id + cookies.permanent[:remember_token] = user.remember_token + end + + # 記憶トークンのcookieに対応するユーザーを返す + def current_user + if (user_id = session[:user_id]) + user = User.find_by(id: user_id) + if user && session[:session_token] == user.session_token + @current_user = user + end + elsif (user_id = cookies.encrypted[:user_id]) + user = User.find_by(id: user_id) + if user && user.authenticated?(cookies[:remember_token]) + log_in user + @current_user = user + end + end + end + + # 渡されたユーザーがカレントユーザーであればtrueを返す + def current_user?(user) + user && user == current_user + end + + # ユーザーがログインしていればtrue、その他ならfalseを返す + def logged_in? + !current_user.nil? + end + + # 永続的セッションを破棄する + def forget(user) + user.forget + cookies.delete(:user_id) + cookies.delete(:remember_token) + end + + # 現在のユーザーをログアウトする + def log_out + forget(current_user) + reset_session + @current_user = nil + end + + # アクセスしようとしたURLを保存する + def store_location + session[:forwarding_url] = request.original_url if request.get? + end +end diff --git a/8_0/ch10/app/helpers/static_pages_helper.rb b/8_0/ch10/app/helpers/static_pages_helper.rb new file mode 100644 index 00000000..2d63e79e --- /dev/null +++ b/8_0/ch10/app/helpers/static_pages_helper.rb @@ -0,0 +1,2 @@ +module StaticPagesHelper +end diff --git a/8_0/ch10/app/helpers/users_helper.rb b/8_0/ch10/app/helpers/users_helper.rb new file mode 100644 index 00000000..53790499 --- /dev/null +++ b/8_0/ch10/app/helpers/users_helper.rb @@ -0,0 +1,10 @@ +module UsersHelper + + # 引数で与えられたユーザーのGravatar画像を返す + def gravatar_for(user, options = { size: 80 }) + size = options[:size] + gravatar_id = Digest::MD5::hexdigest(user.email.downcase) + gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}" + image_tag(gravatar_url, alt: user.name, class: "gravatar") + end +end diff --git a/8_0/ch10/app/javascript/application.js b/8_0/ch10/app/javascript/application.js new file mode 100644 index 00000000..46ebafe8 --- /dev/null +++ b/8_0/ch10/app/javascript/application.js @@ -0,0 +1,5 @@ +// Configure your import map in config/importmap.rb. +// Read more: https://github.com/rails/importmap-rails +import "@hotwired/turbo-rails" +import "controllers" +import "custom/menu" diff --git a/8_0/ch10/app/javascript/controllers/application.js b/8_0/ch10/app/javascript/controllers/application.js new file mode 100644 index 00000000..1213e85c --- /dev/null +++ b/8_0/ch10/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/8_0/ch10/app/javascript/controllers/hello_controller.js b/8_0/ch10/app/javascript/controllers/hello_controller.js new file mode 100644 index 00000000..5975c078 --- /dev/null +++ b/8_0/ch10/app/javascript/controllers/hello_controller.js @@ -0,0 +1,7 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.element.textContent = "Hello World!" + } +} diff --git a/8_0/ch10/app/javascript/controllers/index.js b/8_0/ch10/app/javascript/controllers/index.js new file mode 100644 index 00000000..54ad4cad --- /dev/null +++ b/8_0/ch10/app/javascript/controllers/index.js @@ -0,0 +1,11 @@ +// Import and register all your controllers from the importmap under controllers/* + +import { application } from "controllers/application" + +// Eager load all controllers defined in the import map under controllers/**/*_controller +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) + +// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) +// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" +// lazyLoadControllersFrom("controllers", application) diff --git a/8_0/ch10/app/javascript/custom/menu.js b/8_0/ch10/app/javascript/custom/menu.js new file mode 100644 index 00000000..92e4a8dd --- /dev/null +++ b/8_0/ch10/app/javascript/custom/menu.js @@ -0,0 +1,22 @@ +// メニュー操作 + +// トグルリスナーを追加してクリックをリッスンする +document.addEventListener("turbo:load", function() { + let hamburger = document.querySelector("#hamburger"); + if (hamburger){ + hamburger.addEventListener("click", function(event) { + event.preventDefault(); + let menu = document.querySelector("#navbar-menu"); + menu.classList.toggle("collapse"); + }); + } + + let account = document.querySelector("#account"); + if (account) { + account.addEventListener("click", function(event) { + event.preventDefault(); + let menu = document.querySelector("#dropdown-menu"); + menu.classList.toggle("active"); + }); + } +}); diff --git a/8_0/ch10/app/jobs/application_job.rb b/8_0/ch10/app/jobs/application_job.rb new file mode 100644 index 00000000..d394c3d1 --- /dev/null +++ b/8_0/ch10/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/8_0/ch10/app/mailers/application_mailer.rb b/8_0/ch10/app/mailers/application_mailer.rb new file mode 100644 index 00000000..3c34c814 --- /dev/null +++ b/8_0/ch10/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "from@example.com" + layout "mailer" +end diff --git a/8_0/ch10/app/models/application_record.rb b/8_0/ch10/app/models/application_record.rb new file mode 100644 index 00000000..b63caeb8 --- /dev/null +++ b/8_0/ch10/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/8_0/ch10/app/models/concerns/.keep b/8_0/ch10/app/models/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/app/models/user.rb b/8_0/ch10/app/models/user.rb new file mode 100644 index 00000000..8a3e3561 --- /dev/null +++ b/8_0/ch10/app/models/user.rb @@ -0,0 +1,47 @@ +class User < ApplicationRecord + attr_accessor :remember_token + before_save { self.email = email.downcase } + validates :name, presence: true, length: { maximum: 50 } + VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i + validates :email, presence: true, length: { maximum: 255 }, + format: { with: VALID_EMAIL_REGEX }, + uniqueness: true + has_secure_password + validates :password, presence: true, length: { minimum: 6 }, allow_nil: true + + # 渡された文字列のハッシュ値を返す + def User.digest(string) + cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : + BCrypt::Engine.cost + BCrypt::Password.create(string, cost: cost) + end + + # ランダムなトークンを返す + def User.new_token + SecureRandom.urlsafe_base64 + end + + # 永続的セッションのためにユーザーをデータベースに記憶する + def remember + self.remember_token = User.new_token + update_attribute(:remember_digest, User.digest(remember_token)) + remember_digest + end + + # セッションハイジャック防止のためにセッショントークンを返す + # この記憶ダイジェストを再利用しているのは単に利便性のため + def session_token + remember_digest || remember + end + + # 渡されたトークンがダイジェストと一致したらtrueを返す + def authenticated?(remember_token) + return false if remember_digest.nil? + BCrypt::Password.new(remember_digest).is_password?(remember_token) + end + + # ユーザーのログイン情報を破棄する + def forget + update_attribute(:remember_digest, nil) + end +end diff --git a/8_0/ch10/app/views/hello/index.html.erb b/8_0/ch10/app/views/hello/index.html.erb new file mode 100644 index 00000000..71661105 --- /dev/null +++ b/8_0/ch10/app/views/hello/index.html.erb @@ -0,0 +1,16 @@ +
+
+ +

+ Codespaces + ♥️ + Railsチュートリアル +

+

+ 🎓✨
+ ロゴ画像が表示されたらセットアップ完了です! +

+
+
diff --git a/8_0/ch10/app/views/layouts/_footer.html.erb b/8_0/ch10/app/views/layouts/_footer.html.erb new file mode 100644 index 00000000..b6e31ff9 --- /dev/null +++ b/8_0/ch10/app/views/layouts/_footer.html.erb @@ -0,0 +1,13 @@ + diff --git a/8_0/ch10/app/views/layouts/_header.html.erb b/8_0/ch10/app/views/layouts/_header.html.erb new file mode 100644 index 00000000..8b07e211 --- /dev/null +++ b/8_0/ch10/app/views/layouts/_header.html.erb @@ -0,0 +1,39 @@ + diff --git a/8_0/ch10/app/views/layouts/application.html.erb b/8_0/ch10/app/views/layouts/application.html.erb new file mode 100644 index 00000000..eb23ef94 --- /dev/null +++ b/8_0/ch10/app/views/layouts/application.html.erb @@ -0,0 +1,24 @@ + + + + <%= full_title(yield(:title)) %> + + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + + + <%= render 'layouts/header' %> +
+ <% flash.each do |message_type, message| %> +
<%= message %>
+ <% end %> + <%= yield %> + <%= render 'layouts/footer' %> + <%= debug(params) if Rails.env.development? %> +
+ + diff --git a/8_0/ch10/app/views/layouts/mailer.html.erb b/8_0/ch10/app/views/layouts/mailer.html.erb new file mode 100644 index 00000000..cbd34d2e --- /dev/null +++ b/8_0/ch10/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/8_0/ch10/app/views/layouts/mailer.text.erb b/8_0/ch10/app/views/layouts/mailer.text.erb new file mode 100644 index 00000000..37f0bddb --- /dev/null +++ b/8_0/ch10/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/8_0/ch10/app/views/sessions/new.html.erb b/8_0/ch10/app/views/sessions/new.html.erb new file mode 100644 index 00000000..4eafa29b --- /dev/null +++ b/8_0/ch10/app/views/sessions/new.html.erb @@ -0,0 +1,24 @@ +<% provide(:title, "Log in") %> +

Log in

+ +
+
+ <%= form_with(url: login_path, scope: :session) do |f| %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :remember_me, class: "checkbox inline" do %> + <%= f.check_box :remember_me %> + Remember me on this computer + <% end %> + + <%= f.submit "Log in", class: "btn btn-primary" %> + <% end %> + +

New user? <%= link_to "Sign up now!", signup_path %>

+
+
diff --git a/8_0/ch10/app/views/shared/_error_messages.html.erb b/8_0/ch10/app/views/shared/_error_messages.html.erb new file mode 100644 index 00000000..f80053e4 --- /dev/null +++ b/8_0/ch10/app/views/shared/_error_messages.html.erb @@ -0,0 +1,12 @@ +<% if @user.errors.any? %> +
+
+ The form contains <%= pluralize(@user.errors.count, "error") %>. +
+ +
+<% end %> diff --git a/8_0/ch10/app/views/static_pages/about.html.erb b/8_0/ch10/app/views/static_pages/about.html.erb new file mode 100644 index 00000000..dce8fef2 --- /dev/null +++ b/8_0/ch10/app/views/static_pages/about.html.erb @@ -0,0 +1,10 @@ +<% provide(:title, "About") %> +

About

+

+ Ruby on Rails Tutorial + is a book and + screencast + to teach web development with + Ruby on Rails. + This is the sample application for the tutorial. +

diff --git a/8_0/ch10/app/views/static_pages/contact.html.erb b/8_0/ch10/app/views/static_pages/contact.html.erb new file mode 100644 index 00000000..4a5a35a6 --- /dev/null +++ b/8_0/ch10/app/views/static_pages/contact.html.erb @@ -0,0 +1,6 @@ +<% provide(:title, 'Contact') %> +

Contact

+

+ Contact the Ruby on Rails Tutorial about the sample app at the + contact page. +

diff --git a/8_0/ch10/app/views/static_pages/help.html.erb b/8_0/ch10/app/views/static_pages/help.html.erb new file mode 100644 index 00000000..4d06919d --- /dev/null +++ b/8_0/ch10/app/views/static_pages/help.html.erb @@ -0,0 +1,9 @@ +<% provide(:title, "Help") %> +

Help

+

+ Get help on the Ruby on Rails Tutorial at the + Rails Tutorial Help page. + To get help on this sample app, see the + Ruby on Rails Tutorial + book. +

diff --git a/8_0/ch10/app/views/static_pages/home.html.erb b/8_0/ch10/app/views/static_pages/home.html.erb new file mode 100644 index 00000000..f87607de --- /dev/null +++ b/8_0/ch10/app/views/static_pages/home.html.erb @@ -0,0 +1,14 @@ +
+

Welcome to the Sample App

+ +

+ This is the home page for the + Ruby on Rails Tutorial + sample application. +

+ + <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %> +
+ +<%= link_to image_tag("rails.svg", alt: "Rails logo", width: "200"), + "https://rubyonrails.org/" %> diff --git a/8_0/ch10/app/views/users/_user.html.erb b/8_0/ch10/app/views/users/_user.html.erb new file mode 100644 index 00000000..cf66c339 --- /dev/null +++ b/8_0/ch10/app/views/users/_user.html.erb @@ -0,0 +1,8 @@ +
  • + <%= gravatar_for user, size: 50 %> + <%= link_to user.name, user %> + <% if current_user.admin? && !current_user?(user) %> + | <%= link_to "delete", user, data: { "turbo-method": :delete, + turbo_confirm: "You sure?" } %> + <% end %> +
  • diff --git a/8_0/ch10/app/views/users/edit.html.erb b/8_0/ch10/app/views/users/edit.html.erb new file mode 100644 index 00000000..ae672af0 --- /dev/null +++ b/8_0/ch10/app/views/users/edit.html.erb @@ -0,0 +1,29 @@ +<% provide(:title, "Edit user") %> +

    Update your profile

    + +
    +
    + <%= form_with(model: @user) do |f| %> + <%= render 'shared/error_messages' %> + + <%= f.label :name %> + <%= f.text_field :name, class: 'form-control' %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :password_confirmation, "Confirmation" %> + <%= f.password_field :password_confirmation, class: 'form-control' %> + + <%= f.submit "Save changes", class: "btn btn-primary" %> + <% end %> + +
    + <%= gravatar_for @user %> + change +
    +
    +
    diff --git a/8_0/ch10/app/views/users/index.html.erb b/8_0/ch10/app/views/users/index.html.erb new file mode 100644 index 00000000..5b6dbaeb --- /dev/null +++ b/8_0/ch10/app/views/users/index.html.erb @@ -0,0 +1,10 @@ +<% provide(:title, 'All users') %> +

    All users

    + +<%= will_paginate %> + + + +<%= will_paginate %> diff --git a/8_0/ch10/app/views/users/new.html.erb b/8_0/ch10/app/views/users/new.html.erb new file mode 100644 index 00000000..79e39f80 --- /dev/null +++ b/8_0/ch10/app/views/users/new.html.erb @@ -0,0 +1,24 @@ +<% provide(:title, 'Sign up') %> +

    Sign up

    + +
    +
    + <%= form_with(model: @user) do |f| %> + <%= render 'shared/error_messages' %> + + <%= f.label :name %> + <%= f.text_field :name, class: 'form-control' %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :password_confirmation, "Confirmation" %> + <%= f.password_field :password_confirmation, class: 'form-control' %> + + <%= f.submit "Create my account", class: "btn btn-primary" %> + <% end %> +
    +
    diff --git a/8_0/ch10/app/views/users/show.html.erb b/8_0/ch10/app/views/users/show.html.erb new file mode 100644 index 00000000..4fda5f89 --- /dev/null +++ b/8_0/ch10/app/views/users/show.html.erb @@ -0,0 +1,11 @@ +<% provide(:title, @user.name) %> +
    + +
    diff --git a/8_0/ch10/bin/bundle b/8_0/ch10/bin/bundle new file mode 100755 index 00000000..981e650b --- /dev/null +++ b/8_0/ch10/bin/bundle @@ -0,0 +1,114 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + requirement = bundler_gem_version.approximate_recommendation + + return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0") + + requirement += ".a" if bundler_gem_version.prerelease? + + requirement + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/8_0/ch10/bin/dev b/8_0/ch10/bin/dev new file mode 100755 index 00000000..5f91c205 --- /dev/null +++ b/8_0/ch10/bin/dev @@ -0,0 +1,2 @@ +#!/usr/bin/env ruby +exec "./bin/rails", "server", *ARGV diff --git a/8_0/ch10/bin/importmap b/8_0/ch10/bin/importmap new file mode 100755 index 00000000..36502ab1 --- /dev/null +++ b/8_0/ch10/bin/importmap @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require_relative "../config/application" +require "importmap/commands" diff --git a/8_0/ch10/bin/rails b/8_0/ch10/bin/rails new file mode 100755 index 00000000..efc03774 --- /dev/null +++ b/8_0/ch10/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/8_0/ch10/bin/rake b/8_0/ch10/bin/rake new file mode 100755 index 00000000..4fbf10b9 --- /dev/null +++ b/8_0/ch10/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/8_0/ch10/bin/render-build.sh b/8_0/ch10/bin/render-build.sh new file mode 100644 index 00000000..b0156aa6 --- /dev/null +++ b/8_0/ch10/bin/render-build.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# exit on error +set -o errexit +bundle install +bundle exec rails assets:precompile +bundle exec rails assets:clean +DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rails db:migrate:reset +bundle exec rails db:seed diff --git a/8_0/ch10/bin/rubocop b/8_0/ch10/bin/rubocop new file mode 100755 index 00000000..40330c0f --- /dev/null +++ b/8_0/ch10/bin/rubocop @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +# explicit rubocop config increases performance slightly while avoiding config confusion. +ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) + +load Gem.bin_path("rubocop", "rubocop") diff --git a/8_0/ch10/bin/setup b/8_0/ch10/bin/setup new file mode 100755 index 00000000..be3db3c0 --- /dev/null +++ b/8_0/ch10/bin/setup @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +require "fileutils" + +APP_ROOT = File.expand_path("..", __dir__) + +def system!(*args) + system(*args, exception: true) +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts "== Installing dependencies ==" + system("bundle check") || system!("bundle install") + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + puts "\n== Removing old logs and tempfiles ==" + system! "bin/rails log:clear tmp:clear" + + unless ARGV.include?("--skip-server") + puts "\n== Starting development server ==" + STDOUT.flush # flush the output before exec(2) so that it displays + exec "bin/dev" + end +end diff --git a/8_0/ch10/config.ru b/8_0/ch10/config.ru new file mode 100644 index 00000000..4a3c09a6 --- /dev/null +++ b/8_0/ch10/config.ru @@ -0,0 +1,6 @@ +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/8_0/ch10/config/application.rb b/8_0/ch10/config/application.rb new file mode 100644 index 00000000..0461e72f --- /dev/null +++ b/8_0/ch10/config/application.rb @@ -0,0 +1,27 @@ +require_relative "boot" + +require "rails/all" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module SampleApp + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 8.0 + + # Please, add to the `ignore` list any other `lib` subdirectories that do + # not contain `.rb` files, or that should not be reloaded or eager loaded. + # Common ones are `templates`, `generators`, or `middleware`, for example. + config.autoload_lib(ignore: %w[assets tasks]) + + # Configuration for the application, engines, and railties goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + # + # config.time_zone = "Central Time (US & Canada)" + # config.eager_load_paths << Rails.root.join("extras") + end +end diff --git a/8_0/ch10/config/boot.rb b/8_0/ch10/config/boot.rb new file mode 100644 index 00000000..988a5ddc --- /dev/null +++ b/8_0/ch10/config/boot.rb @@ -0,0 +1,4 @@ +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "bundler/setup" # Set up gems listed in the Gemfile. +require "bootsnap/setup" # Speed up boot time by caching expensive operations. diff --git a/8_0/ch10/config/cable.yml b/8_0/ch10/config/cable.yml new file mode 100644 index 00000000..ae7a2256 --- /dev/null +++ b/8_0/ch10/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: codespaces_try_rails_production diff --git a/8_0/ch10/config/credentials.yml.enc b/8_0/ch10/config/credentials.yml.enc new file mode 100644 index 00000000..339d64d2 --- /dev/null +++ b/8_0/ch10/config/credentials.yml.enc @@ -0,0 +1 @@ +NCs9M8uakg1CDYJENuGaipzymY8uG6i5gzghsy4npa3gYmGfYKPHMnEcJHCVHlM3gxDNJBteUqkRCxHQ5WQId8hzgxoQCL5RX7rtQ8XUzkJwgSUjKvbCrG5atjKkN9XuL91is5vtEN5W7vEz3qxN7Rp33QbNFiDfLs71F3zHVDYEMi6Dy1QMhVCa1v/tyRjBu/5m37RuiYF5FzWJnh4LhexSVr7Agm4LRLLN6qLCpoAe6D3TPHHBdtu7Pvy8jlrEfnnkUJ61wrj8+kOL4uLtpFShdYKhDkoTjQk7TCOgWyifnHAXazkBZoJZ1QYv/tZ9/ZoHvMEQ2dtGHlyTX8fbKtI13d2CFjGaDNKvRuurxE8dbeyiTfbycvve46+cz9OTlmhh0SGt24R1WV6GBKoDUeoHrIiBvW5qmKka--dQ0rodxmfgFLg2y/--t2x0gQjh8FYlk8v79vLSNg== \ No newline at end of file diff --git a/8_0/ch10/config/database.yml b/8_0/ch10/config/database.yml new file mode 100644 index 00000000..fcba57f1 --- /dev/null +++ b/8_0/ch10/config/database.yml @@ -0,0 +1,25 @@ +# SQLite. Versions 3.8.0 and up are supported. +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem "sqlite3" +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/8_0/ch10/config/environment.rb b/8_0/ch10/config/environment.rb new file mode 100644 index 00000000..cac53157 --- /dev/null +++ b/8_0/ch10/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/8_0/ch10/config/environments/development.rb b/8_0/ch10/config/environments/development.rb new file mode 100644 index 00000000..910cef99 --- /dev/null +++ b/8_0/ch10/config/environments/development.rb @@ -0,0 +1,86 @@ +require 'active_support/core_ext/integer/time' + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Make code changes take effect immediately without server restart. + config.enable_reloading = true + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing. + config.server_timing = true + + # Add the following line to disable forgery_protection_origin_check + config.action_controller.forgery_protection_origin_check = false + + # Enable/disable Action Controller caching. By default Action Controller caching is disabled. + # Run rails dev:cache to toggle Action Controller caching. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + config.public_file_server.headers = { 'cache-control' => "public, max-age=#{2.days.to_i}" } + else + config.action_controller.perform_caching = false + end + + # Change to :null_store to avoid any caching. + config.cache_store = :memory_store + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Make template changes take effect immediately. + config.action_mailer.perform_caching = false + + # Set localhost to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Append comments with runtime information tags to SQL queries in logs. + config.active_record.query_log_tags_enabled = true + + # Highlight code that enqueued background job in logs. + config.active_job.verbose_enqueue_logs = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true + + # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. + # config.generators.apply_rubocop_autocorrect_after_generate! + + pf_domain = ENV['GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN'] + config.action_dispatch.default_headers = { + 'X-Frame-Options' => "ALLOW-FROM #{pf_domain}" + } + + # Allow requests from our preview domain. + pf_host = "#{ENV['CODESPACE_NAME']}-3000.#{pf_domain}" + config.hosts << pf_host + + config.action_cable.allowed_request_origins = ["https://#{pf_host}"] +end diff --git a/8_0/ch10/config/environments/production.rb b/8_0/ch10/config/environments/production.rb new file mode 100644 index 00000000..17496077 --- /dev/null +++ b/8_0/ch10/config/environments/production.rb @@ -0,0 +1,89 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.enable_reloading = false + + # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). + config.eager_load = true + + # Full error reports are disabled. + config.consider_all_requests_local = false + + # Turn on fragment caching in view templates. + config.action_controller.perform_caching = true + + # Cache assets for far-future expiry since they are all digest stamped. + config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" } + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = "http://assets.example.com" + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Assume all access to the app is happening through a SSL-terminating reverse proxy. + config.assume_ssl = true + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = true + + # Skip http-to-https redirect for the default health check endpoint. + # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } + + # Log to STDOUT with the current request id as a default log tag. + config.log_tags = [ :request_id ] + config.logger = ActiveSupport::TaggedLogging.logger(STDOUT) + + # Change to "debug" to log everything (including potentially personally-identifiable information!) + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + + # Prevent health checks from clogging up the logs. + config.silence_healthcheck_path = "/up" + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Replace the default in-process memory cache store with a durable alternative. + # config.cache_store = :mem_cache_store + + # Replace the default in-process and non-durable queuing backend for Active Job. + # config.active_job.queue_adapter = :resque + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. + # config.action_mailer.smtp_settings = { + # user_name: Rails.application.credentials.dig(:smtp, :user_name), + # password: Rails.application.credentials.dig(:smtp, :password), + # address: "smtp.example.com", + # port: 587, + # authentication: :plain + # } + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Only use :id for inspections in production. + config.active_record.attributes_for_inspect = [ :id ] + + # Enable DNS rebinding protection and other `Host` header attacks. + # config.hosts = [ + # "example.com", # Allow requests from example.com + # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` + # ] + # + # Skip DNS rebinding protection for the default health check endpoint. + # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } +end diff --git a/8_0/ch10/config/environments/test.rb b/8_0/ch10/config/environments/test.rb new file mode 100644 index 00000000..0ebe3b4e --- /dev/null +++ b/8_0/ch10/config/environments/test.rb @@ -0,0 +1,53 @@ +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # While tests run files are not watched, reloading is not necessary. + config.enable_reloading = false + + # Eager loading loads your entire application. When running a single test locally, + # this is usually not necessary, and can slow down your test suite. However, it's + # recommended that you enable it in continuous integration systems to ensure eager + # loading is working properly before deploying your code. + config.eager_load = ENV["CI"].present? + + # Configure public file server for tests with cache-control for performance. + config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } + + # Show full error reports. + config.consider_all_requests_local = true + config.cache_store = :null_store + + # Render exception templates for rescuable exceptions and raise for other exceptions. + config.action_dispatch.show_exceptions = :none + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :test + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true +end diff --git a/8_0/ch10/config/importmap.rb b/8_0/ch10/config/importmap.rb new file mode 100644 index 00000000..caf3ed06 --- /dev/null +++ b/8_0/ch10/config/importmap.rb @@ -0,0 +1,8 @@ +# Pin npm packages by running ./bin/importmap + +pin "application", preload: true +pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true +pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true +pin_all_from "app/javascript/controllers", under: "controllers" +pin_all_from "app/javascript/custom", under: "custom" diff --git a/8_0/ch10/config/initializers/assets.rb b/8_0/ch10/config/initializers/assets.rb new file mode 100644 index 00000000..48732442 --- /dev/null +++ b/8_0/ch10/config/initializers/assets.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = "1.0" + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path diff --git a/8_0/ch10/config/initializers/content_security_policy.rb b/8_0/ch10/config/initializers/content_security_policy.rb new file mode 100644 index 00000000..b3076b38 --- /dev/null +++ b/8_0/ch10/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header + +# Rails.application.configure do +# config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end +# +# # Generate session nonces for permitted importmap, inline scripts, and inline styles. +# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } +# config.content_security_policy_nonce_directives = %w(script-src style-src) +# +# # Report violations without enforcing the policy. +# # config.content_security_policy_report_only = true +# end diff --git a/8_0/ch10/config/initializers/filter_parameter_logging.rb b/8_0/ch10/config/initializers/filter_parameter_logging.rb new file mode 100644 index 00000000..c0b717f7 --- /dev/null +++ b/8_0/ch10/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. +# Use this to limit dissemination of sensitive information. +# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. +Rails.application.config.filter_parameters += [ + :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc +] diff --git a/8_0/ch10/config/initializers/inflections.rb b/8_0/ch10/config/initializers/inflections.rb new file mode 100644 index 00000000..3860f659 --- /dev/null +++ b/8_0/ch10/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym "RESTful" +# end diff --git a/8_0/ch10/config/initializers/new_framework_defaults_8_0.rb b/8_0/ch10/config/initializers/new_framework_defaults_8_0.rb new file mode 100644 index 00000000..92efa951 --- /dev/null +++ b/8_0/ch10/config/initializers/new_framework_defaults_8_0.rb @@ -0,0 +1,30 @@ +# Be sure to restart your server when you modify this file. +# +# This file eases your Rails 8.0 framework defaults upgrade. +# +# Uncomment each configuration one by one to switch to the new default. +# Once your application is ready to run with all new defaults, you can remove +# this file and set the `config.load_defaults` to `8.0`. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. +# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html + +### +# Specifies whether `to_time` methods preserve the UTC offset of their receivers or preserves the timezone. +# If set to `:zone`, `to_time` methods will use the timezone of their receivers. +# If set to `:offset`, `to_time` methods will use the UTC offset. +# If `false`, `to_time` methods will convert to the local system UTC offset instead. +#++ +# Rails.application.config.active_support.to_time_preserves_timezone = :zone + +### +# When both `If-Modified-Since` and `If-None-Match` are provided by the client +# only consider `If-None-Match` as specified by RFC 7232 Section 6. +# If set to `false` both conditions need to be satisfied. +#++ +# Rails.application.config.action_dispatch.strict_freshness = true + +### +# Set `Regexp.timeout` to `1`s by default to improve security over Regexp Denial-of-Service attacks. +#++ +# Regexp.timeout = 1 diff --git a/8_0/ch10/config/initializers/permissions_policy.rb b/8_0/ch10/config/initializers/permissions_policy.rb new file mode 100644 index 00000000..00f64d71 --- /dev/null +++ b/8_0/ch10/config/initializers/permissions_policy.rb @@ -0,0 +1,11 @@ +# Define an application-wide HTTP permissions policy. For further +# information see https://developers.google.com/web/updates/2018/06/feature-policy +# +# Rails.application.config.permissions_policy do |f| +# f.camera :none +# f.gyroscope :none +# f.microphone :none +# f.usb :none +# f.fullscreen :self +# f.payment :self, "https://secure.example.com" +# end diff --git a/8_0/ch10/config/locales/en.yml b/8_0/ch10/config/locales/en.yml new file mode 100644 index 00000000..8ca56fc7 --- /dev/null +++ b/8_0/ch10/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# "true": "foo" +# +# To learn more, please read the Rails Internationalization guide +# available at https://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/8_0/ch10/config/puma.rb b/8_0/ch10/config/puma.rb new file mode 100644 index 00000000..a248513b --- /dev/null +++ b/8_0/ch10/config/puma.rb @@ -0,0 +1,41 @@ +# This configuration file will be evaluated by Puma. The top-level methods that +# are invoked here are part of Puma's configuration DSL. For more information +# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. +# +# Puma starts a configurable number of processes (workers) and each process +# serves each request in a thread from an internal thread pool. +# +# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You +# should only set this value when you want to run 2 or more workers. The +# default is already 1. +# +# The ideal number of threads per worker depends both on how much time the +# application spends waiting for IO operations and on how much you wish to +# prioritize throughput over latency. +# +# As a rule of thumb, increasing the number of threads will increase how much +# traffic a given process can handle (throughput), but due to CRuby's +# Global VM Lock (GVL) it has diminishing returns and will degrade the +# response time (latency) of the application. +# +# The default is set to 3 threads as it's deemed a decent compromise between +# throughput and latency for the average Rails application. +# +# Any libraries that use a connection pool or another resource pool should +# be configured to provide at least as many connections as the number of +# threads. This includes Active Record's `pool` parameter in `database.yml`. +threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +port ENV.fetch("PORT", 3000) + +# Allow puma to be restarted by `bin/rails restart` command. +plugin :tmp_restart + +# Run the Solid Queue supervisor inside of Puma for single-server deployments +plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] + +# Specify the PID file. Defaults to tmp/pids/server.pid in development. +# In other environments, only set the PID file if requested. +pidfile ENV["PIDFILE"] if ENV["PIDFILE"] diff --git a/8_0/ch10/config/routes.rb b/8_0/ch10/config/routes.rb new file mode 100644 index 00000000..197ff607 --- /dev/null +++ b/8_0/ch10/config/routes.rb @@ -0,0 +1,11 @@ +Rails.application.routes.draw do + root "static_pages#home" + get "/help", to: "static_pages#help" + get "/about", to: "static_pages#about" + get "/contact", to: "static_pages#contact" + get "/signup", to: "users#new" + get "/login", to: "sessions#new" + post "/login", to: "sessions#create" + delete "/logout", to: "sessions#destroy" + resources :users +end diff --git a/8_0/ch10/config/storage.yml b/8_0/ch10/config/storage.yml new file mode 100644 index 00000000..4942ab66 --- /dev/null +++ b/8_0/ch10/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket-<%= Rails.env %> + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket-<%= Rails.env %> + +# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name-<%= Rails.env %> + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/8_0/ch10/db/migrate/20260512080056_create_users.rb b/8_0/ch10/db/migrate/20260512080056_create_users.rb new file mode 100644 index 00000000..3b80eb00 --- /dev/null +++ b/8_0/ch10/db/migrate/20260512080056_create_users.rb @@ -0,0 +1,10 @@ +class CreateUsers < ActiveRecord::Migration[8.0] + def change + create_table :users do |t| + t.string :name + t.string :email + + t.timestamps + end + end +end diff --git a/8_0/ch10/db/migrate/20260512080840_add_index_to_users_email.rb b/8_0/ch10/db/migrate/20260512080840_add_index_to_users_email.rb new file mode 100644 index 00000000..a8e4ed8e --- /dev/null +++ b/8_0/ch10/db/migrate/20260512080840_add_index_to_users_email.rb @@ -0,0 +1,5 @@ +class AddIndexToUsersEmail < ActiveRecord::Migration[8.0] + def change + add_index :users, :email, unique: true + end +end diff --git a/8_0/ch10/db/migrate/20260512081511_add_password_digest_to_users.rb b/8_0/ch10/db/migrate/20260512081511_add_password_digest_to_users.rb new file mode 100644 index 00000000..81c7c9e0 --- /dev/null +++ b/8_0/ch10/db/migrate/20260512081511_add_password_digest_to_users.rb @@ -0,0 +1,5 @@ +class AddPasswordDigestToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :password_digest, :string + end +end diff --git a/8_0/ch10/db/migrate/20260626064202_add_remember_digest_to_users.rb b/8_0/ch10/db/migrate/20260626064202_add_remember_digest_to_users.rb new file mode 100644 index 00000000..7a464448 --- /dev/null +++ b/8_0/ch10/db/migrate/20260626064202_add_remember_digest_to_users.rb @@ -0,0 +1,5 @@ +class AddRememberDigestToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :remember_digest, :string + end +end diff --git a/8_0/ch10/db/migrate/20260626075635_add_admin_to_users.rb b/8_0/ch10/db/migrate/20260626075635_add_admin_to_users.rb new file mode 100644 index 00000000..c1f08cf5 --- /dev/null +++ b/8_0/ch10/db/migrate/20260626075635_add_admin_to_users.rb @@ -0,0 +1,5 @@ +class AddAdminToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :admin, :boolean, default: false + end +end diff --git a/8_0/ch10/db/schema.rb b/8_0/ch10/db/schema.rb new file mode 100644 index 00000000..4d5cab97 --- /dev/null +++ b/8_0/ch10/db/schema.rb @@ -0,0 +1,24 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[8.0].define(version: 2026_06_26_075635) do + create_table "users", force: :cascade do |t| + t.string "name" + t.string "email" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "password_digest" + t.string "remember_digest" + t.boolean "admin", default: false + t.index ["email"], name: "index_users_on_email", unique: true + end +end diff --git a/8_0/ch10/db/seeds.rb b/8_0/ch10/db/seeds.rb new file mode 100644 index 00000000..a644b6e0 --- /dev/null +++ b/8_0/ch10/db/seeds.rb @@ -0,0 +1,17 @@ +# メインのサンプルユーザーを1人作成する +User.create!(name: "Example User", + email: "example@railstutorial.org", + password: "foobar", + password_confirmation: "foobar", + admin: true) + +# 追加のユーザーをまとめて生成する +99.times do |n| + name = Faker::Name.name + email = "example-#{n+1}@railstutorial.org" + password = "password" + User.create!(name: name, + email: email, + password: password, + password_confirmation: password) +end diff --git a/8_0/ch10/lib/assets/.keep b/8_0/ch10/lib/assets/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/lib/tasks/.keep b/8_0/ch10/lib/tasks/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/log/.keep b/8_0/ch10/log/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/public/400.html b/8_0/ch10/public/400.html new file mode 100644 index 00000000..282dbc8c --- /dev/null +++ b/8_0/ch10/public/400.html @@ -0,0 +1,114 @@ + + + + + + + The server cannot process the request due to a client error (400 Bad Request) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    The server cannot process the request due to a client error. Please check the request and try again. If you’re the application owner check the logs for more information.

    +
    +
    + + + + diff --git a/8_0/ch10/public/404.html b/8_0/ch10/public/404.html new file mode 100644 index 00000000..c0670bc8 --- /dev/null +++ b/8_0/ch10/public/404.html @@ -0,0 +1,114 @@ + + + + + + + The page you were looking for doesn’t exist (404 Not found) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    The page you were looking for doesn’t exist. You may have mistyped the address or the page may have moved. If you’re the application owner check the logs for more information.

    +
    +
    + + + + diff --git a/8_0/ch10/public/406-unsupported-browser.html b/8_0/ch10/public/406-unsupported-browser.html new file mode 100644 index 00000000..9532a9cc --- /dev/null +++ b/8_0/ch10/public/406-unsupported-browser.html @@ -0,0 +1,114 @@ + + + + + + + Your browser is not supported (406 Not Acceptable) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    Your browser is not supported.
    Please upgrade your browser to continue.

    +
    +
    + + + + diff --git a/8_0/ch10/public/422.html b/8_0/ch10/public/422.html new file mode 100644 index 00000000..8bcf0601 --- /dev/null +++ b/8_0/ch10/public/422.html @@ -0,0 +1,114 @@ + + + + + + + The change you wanted was rejected (422 Unprocessable Entity) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    The change you wanted was rejected. Maybe you tried to change something you didn’t have access to. If you’re the application owner check the logs for more information.

    +
    +
    + + + + diff --git a/8_0/ch10/public/500.html b/8_0/ch10/public/500.html new file mode 100644 index 00000000..d77718c3 --- /dev/null +++ b/8_0/ch10/public/500.html @@ -0,0 +1,114 @@ + + + + + + + We’re sorry, but something went wrong (500 Internal Server Error) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    We’re sorry, but something went wrong.
    If you’re the application owner check the logs for more information.

    +
    +
    + + + + diff --git a/8_0/ch10/public/apple-touch-icon-precomposed.png b/8_0/ch10/public/apple-touch-icon-precomposed.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/public/apple-touch-icon.png b/8_0/ch10/public/apple-touch-icon.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/public/favicon.ico b/8_0/ch10/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/public/icon.png b/8_0/ch10/public/icon.png new file mode 100644 index 00000000..c4c9dbfb Binary files /dev/null and b/8_0/ch10/public/icon.png differ diff --git a/8_0/ch10/public/icon.svg b/8_0/ch10/public/icon.svg new file mode 100644 index 00000000..04b34bf8 --- /dev/null +++ b/8_0/ch10/public/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/8_0/ch10/public/railstutorial.png b/8_0/ch10/public/railstutorial.png new file mode 100644 index 00000000..70835e6b Binary files /dev/null and b/8_0/ch10/public/railstutorial.png differ diff --git a/8_0/ch10/public/robots.txt b/8_0/ch10/public/robots.txt new file mode 100644 index 00000000..c19f78ab --- /dev/null +++ b/8_0/ch10/public/robots.txt @@ -0,0 +1 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/8_0/ch10/storage/.keep b/8_0/ch10/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/test/application_system_test_case.rb b/8_0/ch10/test/application_system_test_case.rb new file mode 100644 index 00000000..d19212ab --- /dev/null +++ b/8_0/ch10/test/application_system_test_case.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/8_0/ch10/test/channels/application_cable/connection_test.rb b/8_0/ch10/test/channels/application_cable/connection_test.rb new file mode 100644 index 00000000..800405f1 --- /dev/null +++ b/8_0/ch10/test/channels/application_cable/connection_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase + # test "connects with cookies" do + # cookies.signed[:user_id] = 42 + # + # connect + # + # assert_equal connection.user_id, "42" + # end +end diff --git a/8_0/ch10/test/controllers/.keep b/8_0/ch10/test/controllers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/test/controllers/hello_codespaces_controller_test.rb b/8_0/ch10/test/controllers/hello_codespaces_controller_test.rb new file mode 100644 index 00000000..54353ea6 --- /dev/null +++ b/8_0/ch10/test/controllers/hello_codespaces_controller_test.rb @@ -0,0 +1,8 @@ +require "test_helper" + +class HelloCodespacesControllerTest < ActionDispatch::IntegrationTest + # test "should get index" do + # get "/" + # assert_response :success + # end +end diff --git a/8_0/ch10/test/controllers/sessions_controller_test.rb b/8_0/ch10/test/controllers/sessions_controller_test.rb new file mode 100644 index 00000000..8cc3f29e --- /dev/null +++ b/8_0/ch10/test/controllers/sessions_controller_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +class SessionsControllerTest < ActionDispatch::IntegrationTest + + test "should get new" do + get login_path + assert_response :success + end +end diff --git a/8_0/ch10/test/controllers/static_pages_controller_test.rb b/8_0/ch10/test/controllers/static_pages_controller_test.rb new file mode 100644 index 00000000..847856a3 --- /dev/null +++ b/8_0/ch10/test/controllers/static_pages_controller_test.rb @@ -0,0 +1,28 @@ +require "test_helper" + +class StaticPagesControllerTest < ActionDispatch::IntegrationTest + + test "should get home" do + get root_path + assert_response :success + assert_select "title", "Ruby on Rails Tutorial Sample App" + end + + test "should get help" do + get help_path + assert_response :success + assert_select "title", "Help | Ruby on Rails Tutorial Sample App" + end + + test "should get about" do + get about_path + assert_response :success + assert_select "title", "About | Ruby on Rails Tutorial Sample App" + end + + test "should get contact" do + get contact_path + assert_response :success + assert_select "title", "Contact | Ruby on Rails Tutorial Sample App" + end +end diff --git a/8_0/ch10/test/controllers/users_controller_test.rb b/8_0/ch10/test/controllers/users_controller_test.rb new file mode 100644 index 00000000..ffd2ec99 --- /dev/null +++ b/8_0/ch10/test/controllers/users_controller_test.rb @@ -0,0 +1,64 @@ +require "test_helper" + +class UsersControllerTest < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + @other_user = users(:archer) + end + + test "should get new" do + get signup_path + assert_response :success + end + + test "should redirect index when not logged in" do + get users_path + assert_redirected_to login_url + end + + test "should redirect edit when not logged in" do + get edit_user_path(@user) + assert_not flash.empty? + assert_redirected_to login_url + end + + test "should redirect update when not logged in" do + patch user_path(@user), params: { user: { name: @user.name, + email: @user.email } } + assert_not flash.empty? + assert_redirected_to login_url + end + + test "should redirect edit when logged in as wrong user" do + log_in_as(@other_user) + get edit_user_path(@user) + assert flash.empty? + assert_redirected_to root_url + end + + test "should redirect update when logged in as wrong user" do + log_in_as(@other_user) + patch user_path(@user), params: { user: { name: @user.name, + email: @user.email } } + assert flash.empty? + assert_redirected_to root_url + end + + test "should redirect destroy when not logged in" do + assert_no_difference 'User.count' do + delete user_path(@user) + end + assert_response :see_other + assert_redirected_to login_url + end + + test "should redirect destroy when logged in as a non-admin" do + log_in_as(@other_user) + assert_no_difference 'User.count' do + delete user_path(@user) + end + assert_response :see_other + assert_redirected_to root_url + end +end diff --git a/8_0/ch10/test/fixtures/files/.keep b/8_0/ch10/test/fixtures/files/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/test/fixtures/users.yml b/8_0/ch10/test/fixtures/users.yml new file mode 100644 index 00000000..099a1b06 --- /dev/null +++ b/8_0/ch10/test/fixtures/users.yml @@ -0,0 +1,27 @@ +michael: + name: Michael Example + email: michael@example.com + password_digest: <%= User.digest('password') %> + admin: true + +archer: + name: Sterling Archer + email: duchess@example.gov + password_digest: <%= User.digest('password') %> + +lana: + name: Lana Kane + email: hands@example.gov + password_digest: <%= User.digest('password') %> + +malory: + name: Malory Archer + email: boss@example.gov + password_digest: <%= User.digest('password') %> + +<% 30.times do |n| %> +user_<%= n %>: + name: <%= "User #{n}" %> + email: <%= "user-#{n}@example.com" %> + password_digest: <%= User.digest('password') %> +<% end %> diff --git a/8_0/ch10/test/helpers/.keep b/8_0/ch10/test/helpers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/test/helpers/sessions_helper_test.rb b/8_0/ch10/test/helpers/sessions_helper_test.rb new file mode 100644 index 00000000..735ff17a --- /dev/null +++ b/8_0/ch10/test/helpers/sessions_helper_test.rb @@ -0,0 +1,19 @@ +require "test_helper" + +class SessionsHelperTest < ActionView::TestCase + + def setup + @user = users(:michael) + remember(@user) + end + + test "current_user returns right user when session is nil" do + assert_equal @user, current_user + assert is_logged_in? + end + + test "current_user returns nil when remember digest is wrong" do + @user.update_attribute(:remember_digest, User.digest(User.new_token)) + assert_nil current_user + end +end diff --git a/8_0/ch10/test/integration/.keep b/8_0/ch10/test/integration/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/test/integration/site_layout_test.rb b/8_0/ch10/test/integration/site_layout_test.rb new file mode 100644 index 00000000..0f7ca649 --- /dev/null +++ b/8_0/ch10/test/integration/site_layout_test.rb @@ -0,0 +1,13 @@ +require "test_helper" + +class SiteLayoutTest < ActionDispatch::IntegrationTest + + test "layout links" do + get root_path + assert_template 'static_pages/home' + assert_select "a[href=?]", root_path, count: 2 + assert_select "a[href=?]", help_path + assert_select "a[href=?]", about_path + assert_select "a[href=?]", contact_path + end +end diff --git a/8_0/ch10/test/integration/users_edit_test.rb b/8_0/ch10/test/integration/users_edit_test.rb new file mode 100644 index 00000000..23ca6b68 --- /dev/null +++ b/8_0/ch10/test/integration/users_edit_test.rb @@ -0,0 +1,37 @@ +require "test_helper" + +class UsersEditTest < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + end + + test "unsuccessful edit" do + log_in_as(@user) + get edit_user_path(@user) + assert_template 'users/edit' + patch user_path(@user), params: { user: { name: "", + email: "foo@invalid", + password: "foo", + password_confirmation: "bar" } } + + assert_template 'users/edit' + end + + test "successful edit with friendly forwarding" do + get edit_user_path(@user) + log_in_as(@user) + assert_redirected_to edit_user_url(@user) + name = "Foo Bar" + email = "foo@bar.com" + patch user_path(@user), params: { user: { name: name, + email: email, + password: "", + password_confirmation: "" } } + assert_not flash.empty? + assert_redirected_to @user + @user.reload + assert_equal name, @user.name + assert_equal email, @user.email + end +end diff --git a/8_0/ch10/test/integration/users_index_test.rb b/8_0/ch10/test/integration/users_index_test.rb new file mode 100644 index 00000000..63bb8d9e --- /dev/null +++ b/8_0/ch10/test/integration/users_index_test.rb @@ -0,0 +1,34 @@ +require "test_helper" + +class UsersIndexTest < ActionDispatch::IntegrationTest + + def setup + @admin = users(:michael) + @non_admin = users(:archer) + end + + test "index as admin including pagination and delete links" do + log_in_as(@admin) + get users_path + assert_template 'users/index' + assert_select 'div.pagination' + first_page_of_users = User.paginate(page: 1) + first_page_of_users.each do |user| + assert_select 'a[href=?]', user_path(user), text: user.name + unless user == @admin + assert_select 'a[href=?]', user_path(user), text: 'delete' + end + end + assert_difference 'User.count', -1 do + delete user_path(@non_admin) + assert_response :see_other + assert_redirected_to users_url + end + end + + test "index as non-admin" do + log_in_as(@non_admin) + get users_path + assert_select 'a', text: 'delete', count: 0 + end +end diff --git a/8_0/ch10/test/integration/users_login_test.rb b/8_0/ch10/test/integration/users_login_test.rb new file mode 100644 index 00000000..5f3d7555 --- /dev/null +++ b/8_0/ch10/test/integration/users_login_test.rb @@ -0,0 +1,56 @@ +require "test_helper" + +class UsersLoginTest < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + end + + test "login with valid email/invalid password" do + get login_path + assert_template 'sessions/new' + post login_path, params: { session: { email: @user.email, + password: "invalid" } } + assert_not is_logged_in? + assert_response :unprocessable_entity + assert_template 'sessions/new' + assert_not flash.empty? + get root_path + assert flash.empty? + end + + test "login with valid information followed by logout" do + post login_path, params: { session: { email: @user.email, + password: 'password' } } + assert is_logged_in? + assert_redirected_to @user + follow_redirect! + assert_template 'users/show' + assert_select "a[href=?]", login_path, count: 0 + assert_select "a[href=?]", logout_path + assert_select "a[href=?]", user_path(@user) + delete logout_path + assert_not is_logged_in? + assert_response :see_other + assert_redirected_to root_url + # 2番目のウィンドウでログアウトをクリックするユーザーをシミュレートする + delete logout_path + follow_redirect! + assert_select "a[href=?]", login_path + assert_select "a[href=?]", logout_path, count: 0 + assert_select "a[href=?]", user_path(@user), count: 0 + end + + test "login with remembering" do + log_in_as(@user, remember_me: '1') + assert_not cookies[:remember_token].blank? + end + + test "login without remembering" do + # Cookieを保存してログイン + log_in_as(@user, remember_me: '1') + # Cookieが削除されていることを検証してからログイン + log_in_as(@user, remember_me: '0') + assert cookies[:remember_token].blank? + end +end diff --git a/8_0/ch10/test/integration/users_signup_test.rb b/8_0/ch10/test/integration/users_signup_test.rb new file mode 100644 index 00000000..cbd8ff99 --- /dev/null +++ b/8_0/ch10/test/integration/users_signup_test.rb @@ -0,0 +1,28 @@ +require "test_helper" + +class UsersSignupTest < ActionDispatch::IntegrationTest + + test "invalid signup information" do + get signup_path + assert_no_difference 'User.count' do + post users_path, params: { user: { name: "", + email: "user@invalid", + password: "foo", + password_confirmation: "bar" } } + end + assert_response :unprocessable_entity + assert_template 'users/new' + end + + test "valid signup information" do + assert_difference 'User.count', 1 do + post users_path, params: { user: { name: "Example User", + email: "user@example.com", + password: "password", + password_confirmation: "password" } } + end + follow_redirect! + assert_template 'users/show' + assert is_logged_in? + end +end diff --git a/8_0/ch10/test/mailers/.keep b/8_0/ch10/test/mailers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/test/models/.keep b/8_0/ch10/test/models/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/test/models/user_test.rb b/8_0/ch10/test/models/user_test.rb new file mode 100644 index 00000000..cbd59e80 --- /dev/null +++ b/8_0/ch10/test/models/user_test.rb @@ -0,0 +1,71 @@ +require "test_helper" + +class UserTest < ActiveSupport::TestCase + + def setup + @user = User.new(name: "Example User", email: "user@example.com", + password: "foobar", password_confirmation: "foobar") + end + + test "should be valid" do + assert @user.valid? + end + + test "name should be present" do + @user.name = " " + assert_not @user.valid? + end + + test "email should be present" do + @user.email = " " + assert_not @user.valid? + end + + test "name should not be too long" do + @user.name = "a" * 51 + assert_not @user.valid? + end + + test "email should not be too long" do + @user.email = "a" * 244 + "@example.com" + assert_not @user.valid? + end + + test "email validation should accept valid addresses" do + valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org + first.last@foo.jp alice+bob@baz.cn] + valid_addresses.each do |valid_address| + @user.email = valid_address + assert @user.valid?, "#{valid_address.inspect} should be valid" + end + end + + test "email validation should reject invalid addresses" do + invalid_addresses = %w[user@example,com user_at_foo.org user.name@example. + foo@bar_baz.com foo@bar+baz.com] + invalid_addresses.each do |invalid_address| + @user.email = invalid_address + assert_not @user.valid?, "#{invalid_address.inspect} should be invalid" + end + end + + test "email addresses should be unique" do + duplicate_user = @user.dup + @user.save + assert_not duplicate_user.valid? + end + + test "password should be present (nonblank)" do + @user.password = @user.password_confirmation = " " * 6 + assert_not @user.valid? + end + + test "password should have a minimum length" do + @user.password = @user.password_confirmation = "a" * 5 + assert_not @user.valid? + end + + test "authenticated? should return false for a user with nil digest" do + assert_not @user.authenticated?('') + end +end diff --git a/8_0/ch10/test/system/.keep b/8_0/ch10/test/system/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/test/test_helper.rb b/8_0/ch10/test/test_helper.rb new file mode 100644 index 00000000..5fbcafd0 --- /dev/null +++ b/8_0/ch10/test/test_helper.rb @@ -0,0 +1,33 @@ +ENV["RAILS_ENV"] ||= "test" +require_relative "../config/environment" +require "rails/test_help" +require "minitest/reporters" +Minitest::Reporters.use! + +class ActiveSupport::TestCase + # 指定のワーカー数でテストを並列実行する + parallelize(workers: :number_of_processors) + + # test/fixtures/*.ymlにあるすべてのfixtureをセットアップする + fixtures :all + + # テストユーザーがログイン中の場合にtrueを返す + def is_logged_in? + !session[:user_id].nil? + end + + # テストユーザーとしてログインする + def log_in_as(user) + session[:user_id] = user.id + end +end + +class ActionDispatch::IntegrationTest + + # テストユーザーとしてログインする + def log_in_as(user, password: 'password', remember_me: '1') + post login_path, params: { session: { email: user.email, + password: password, + remember_me: remember_me } } + end +end \ No newline at end of file diff --git a/8_0/ch10/tmp/.keep b/8_0/ch10/tmp/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/tmp/pids/.keep b/8_0/ch10/tmp/pids/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/tmp/storage/.keep b/8_0/ch10/tmp/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/vendor/.keep b/8_0/ch10/vendor/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch10/vendor/javascript/.keep b/8_0/ch10/vendor/javascript/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/.devcontainer/devcontainer.json b/8_0/ch11/.devcontainer/devcontainer.json new file mode 100644 index 00000000..e7243217 --- /dev/null +++ b/8_0/ch11/.devcontainer/devcontainer.json @@ -0,0 +1,105 @@ +// Specifications: https://containers.dev/implementors/json_reference/ +// Format details: https://aka.ms/devcontainer.json +// Config options: https://github.com/microsoft/vscode-dev-containers/tree/main/containers/ruby +{ + "name": "railstutorial", + + // Universal is well-customized image for Codespaces: + // https://hub.docker.com/_/microsoft-devcontainers-universal + //"image": "mcr.microsoft.com/devcontainers/universal:2", + //"features": { + // "ghcr.io/devcontainers/features/ruby:1": {} + //}, + + // Use Ruby image if you want to pin Ruby version like '3.2' + // https://github.com/devcontainers/images/tree/main/src/ruby + "image": "mcr.microsoft.com/devcontainers/ruby:3.2", + //"workspaceFolder": "/railstutorial", => Container build failed + + // Enable learners to choose an affordable spec, starting at minimum one. + //"hostRequirements": { + // "cpus": 2, + // "memory": "4gb", + // "storage": "32gb" + //}, + + "waitFor": "onCreateCommand", + //"onCreateCommand": "", + "onCreateCommand": "gem install solargraph -v '0.58.2' -N", + //"onCreateCommand": "gem install solargraph -v '0.50.0' -N && gem install ruby-lsp -N", + //"onCreateCommand": "gem install ruby-lsp -N", + //# => Solargraph gem not found. Run `gem install solargraph` or update your Gemfile. + "updateContentCommand": "bundle install", + "postCreateCommand": "", + "postAttachCommand": { + "server": "rails server" + }, + "customizations": { + "codespaces": { + "openFiles": [ + "app/views/hello/index.html.erb" + ] + }, + "vscode": { + "extensions": [ + "GitHub.codespaces", + "Shopify.ruby-lsp", // https://github.com/Shopify/ruby-lsp + "castwide.solargraph" // https://github.com/castwide/vscode-solargraph + //"rebornix.Ruby", // https://github.com/rubyide/vscode-ruby (Deprecated) + + ], + "settings": { + // General settings for Codespaces (VS Code) + //"ruby.useLanguageServer": true , + //"ruby.format": "rubocop", + //"ruby.lint": { "rubocop": true }, + //"ruby.intellisense": "rubyLocate", + "editor.tabSize": 2, + "editor.formatOnSave": false, // Disable onSave to show diff edited by learners only + "editor.formatOnType": false, // Disable onType for the same reason above + "editor.insertSpaces": true, // Use spaces, not tabs, to avoid errors for learners + "editor.renderWhitespace": "none", + "[ruby]": { + "editor.defaultFormatter": "castwide.solargraph", + "editor.semanticHighlighting.enabled": true // Enable semantic highlighting + }, + "files.associations": { "*.erb": "erb" }, + "emmet.includeLanguages": { "erb": "html" }, + + // Settings for Solargraph + // https://github.com/castwide/solargraph + "solargraph.useBundler": false, + "solargraph.diagnostics": false, + "solargraph.formatting": true, // Use Ctrl+Shift+P->Format to format + "solargraph.autoformat": false, + "solargraph.definitions": true, + "solargraph.completion": true, + "solargraph.references": true, + "solargraph.symbols": true, + "solargraph.rename": true, + "solargraph.hover": true, + + // Settings for Ruby LSP + // https://github.com/Shopify/ruby-lsp + "rubyLsp.rubyVersionManager": { + "identifier": "none" + }, + "rubyLsp.formatter": "none", + "rubyLsp.enabledFeatures": { + "diagnostics": false, + "formatting": false + } + } + } + }, + "remoteEnv": { + "EDITOR": "code --wait" + }, + "portsAttributes": { + "3000": { + "label": "Application", + "onAutoForward": "openPreview" + } + }, + "forwardPorts": [3000] +} diff --git a/8_0/ch11/.devcontainer/icon.svg b/8_0/ch11/.devcontainer/icon.svg new file mode 100644 index 00000000..f8444a4a --- /dev/null +++ b/8_0/ch11/.devcontainer/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/8_0/ch11/.gitattributes b/8_0/ch11/.gitattributes new file mode 100644 index 00000000..31eeee0b --- /dev/null +++ b/8_0/ch11/.gitattributes @@ -0,0 +1,7 @@ +# See https://git-scm.com/docs/gitattributes for more about git attribute files. + +# Mark the database schema as having been generated. +db/schema.rb linguist-generated + +# Mark any vendored files as having been vendored. +vendor/* linguist-vendored diff --git a/8_0/ch11/.gitignore b/8_0/ch11/.gitignore new file mode 100644 index 00000000..e1ef53ef --- /dev/null +++ b/8_0/ch11/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-* + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore uploaded files in development. +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/ +!/tmp/storage/.keep + +/public/assets + +# Ignore master key for decrypting credentials and more. +/config/master.key + +# Ignore history file used by IRB for interactive Ruby. +.irb_history + +dump.rdb diff --git a/8_0/ch11/.irbrc b/8_0/ch11/.irbrc new file mode 100644 index 00000000..3737e036 --- /dev/null +++ b/8_0/ch11/.irbrc @@ -0,0 +1 @@ +IRB.conf[:COMPLETOR] = :type # default is :regexp diff --git a/8_0/ch11/.solargraph.yml b/8_0/ch11/.solargraph.yml new file mode 100644 index 00000000..76ac24a6 --- /dev/null +++ b/8_0/ch11/.solargraph.yml @@ -0,0 +1,32 @@ +--- +include: +- "**/*.rb" +exclude: +- spec/**/* +- test/**/* +- vendor/**/* +- ".bundle/**/*" +require: +- actioncable +- actionmailer +- actionpack +- actionview +- activejob +- activemodel +- activerecord +- activestorage +- activesupport +domains: [] +reporters: +- rubocop +- require_not_found +- typecheck +formatter: + rubocop: + cops: safe + except: [] + only: [] + extra_args: [] +require_paths: [] +plugins: [] +max_files: 5000 diff --git a/8_0/ch11/.vscode/settings.json b/8_0/ch11/.vscode/settings.json new file mode 100644 index 00000000..cf85f519 --- /dev/null +++ b/8_0/ch11/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.minimap.enabled": false +} diff --git a/8_0/ch11/Gemfile b/8_0/ch11/Gemfile new file mode 100644 index 00000000..0d40ba8c --- /dev/null +++ b/8_0/ch11/Gemfile @@ -0,0 +1,51 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '3.2.10' + +gem 'bcrypt', '3.1.18' +gem 'bootsnap', '1.16.0', require: false +gem 'bootstrap-sass', '3.4.1' +gem 'bootstrap-will_paginate', '1.0.0' +gem 'concurrent-ruby', '1.3.4' +gem 'faker', '2.21.0' +gem 'importmap-rails', '1.1.5' +gem 'jbuilder', '2.14.1' +gem 'puma', '6.6.1' +gem 'rails', '8.0.2.1' +gem 'sassc-rails', '2.1.2' +gem 'sprockets-rails', '3.4.2' +gem 'sqlite3', '2.7.3' +gem 'stimulus-rails', '1.2.1' +gem 'turbo-rails', '1.4.0' +gem 'will_paginate', '3.3.1' + +group :development, :test do + gem 'debug', '1.7.1', platforms: %i[mri mingw x64_mingw] + gem 'reline', '0.5.10' +end + +group :development do + gem 'irb', '1.15.2' + gem 'repl_type_completor', '0.1.10' + gem 'solargraph', '0.58.2' + gem 'web-console', '4.2.0' +end + +group :test do + gem 'capybara', '3.38.0' + gem 'guard', '2.18.0' + gem 'guard-minitest', '2.4.6' + gem 'minitest', '5.18.0' + gem 'minitest-reporters', '1.6.0' + gem 'rails-controller-testing', '1.0.5' + gem 'selenium-webdriver', '4.8.3' + gem 'webdrivers', '5.2.0' +end + +group :production do + gem 'mailgun-ruby', "1.3.10" +end + +# Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります +# gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] diff --git a/8_0/ch11/Gemfile.lock b/8_0/ch11/Gemfile.lock new file mode 100644 index 00000000..b08d1f3c --- /dev/null +++ b/8_0/ch11/Gemfile.lock @@ -0,0 +1,466 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + actionmailer (8.0.2.1) + actionpack (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.0.2.1) + actionview (= 8.0.2.1) + activesupport (= 8.0.2.1) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.0.2.1) + actionpack (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (8.0.2.1) + activesupport (= 8.0.2.1) + builder (~> 3.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.3.6) + activemodel (8.0.2.1) + activesupport (= 8.0.2.1) + activerecord (8.0.2.1) + activemodel (= 8.0.2.1) + activesupport (= 8.0.2.1) + timeout (>= 0.4.0) + activestorage (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activesupport (= 8.0.2.1) + marcel (~> 1.0) + activesupport (8.0.2.1) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + ansi (1.5.0) + ast (2.4.3) + autoprefixer-rails (10.4.21.0) + execjs (~> 2) + backport (1.2.0) + base64 (0.3.0) + bcrypt (3.1.18) + benchmark (0.4.1) + bigdecimal (3.2.3) + bindex (0.8.1) + bootsnap (1.16.0) + msgpack (~> 1.2) + bootstrap-sass (3.4.1) + autoprefixer-rails (>= 5.2.1) + sassc (>= 2.0.0) + bootstrap-will_paginate (1.0.0) + will_paginate + builder (3.3.0) + capybara (3.38.0) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + coderay (1.1.3) + concurrent-ruby (1.3.4) + connection_pool (2.5.4) + crass (1.0.6) + date (3.4.1) + debug (1.7.1) + irb (>= 1.5.0) + reline (>= 0.3.1) + diff-lcs (1.6.2) + drb (2.2.3) + erb (5.0.2) + erubi (1.13.1) + execjs (2.10.1) + faker (2.21.0) + i18n (>= 1.8.11, < 2) + faraday (2.14.3) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-multipart (1.1.1) + multipart-post (~> 2.0) + faraday-net_http (3.4.4) + net-http (~> 0.5) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) + formatador (1.2.0) + reline + globalid (1.2.1) + activesupport (>= 6.1) + guard (2.18.0) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.13.0) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-minitest (2.4.6) + guard-compat (~> 1.2) + minitest (>= 3.0) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + importmap-rails (1.1.5) + actionpack (>= 6.0.0) + railties (>= 6.0.0) + io-console (0.8.1) + irb (1.15.2) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jaro_winkler (1.6.1) + jbuilder (2.14.1) + actionview (>= 7.0.0) + activesupport (>= 7.0.0) + json (2.13.2) + kramdown (2.5.1) + rexml (>= 3.3.9) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) + loofah (2.24.1) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + lumberjack (1.4.1) + mail (2.8.1) + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + mailgun-ruby (1.3.10) + faraday (~> 2.1) + faraday-multipart (~> 1.1.0) + mini_mime + marcel (1.0.4) + matrix (0.4.3) + method_source (1.1.0) + mini_mime (1.1.5) + minitest (5.18.0) + minitest-reporters (1.6.0) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + msgpack (1.8.0) + multipart-post (2.4.1) + nenv (0.3.0) + net-http (0.9.1) + uri (>= 0.11.1) + net-imap (0.5.10) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.4) + nokogiri (1.19.1-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-musl) + racc (~> 1.4) + notiffany (0.1.3) + nenv (~> 0.1) + shellany (~> 0.0) + observer (0.1.2) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.9.0) + ast (~> 2.4.1) + racc + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + prism (1.4.0) + pry (0.15.2) + coderay (~> 1.1) + method_source (~> 1.0) + psych (5.2.6) + date + stringio + public_suffix (6.0.2) + puma (6.6.1) + nio4r (~> 2.0) + racc (1.8.1) + rack (3.2.5) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.2.1) + rack (>= 3) + rails (8.0.2.1) + actioncable (= 8.0.2.1) + actionmailbox (= 8.0.2.1) + actionmailer (= 8.0.2.1) + actionpack (= 8.0.2.1) + actiontext (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activemodel (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + bundler (>= 1.15.0) + railties (= 8.0.2.1) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.0) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rbs (3.6.1) + logger + rdoc (6.14.2) + erb + psych (>= 4.0.0) + regexp_parser (2.11.2) + reline (0.5.10) + io-console (~> 0.5) + repl_type_completor (0.1.10) + prism (~> 1.0) + rbs (>= 2.7.0, < 4.0.0) + reverse_markdown (3.0.0) + nokogiri + rexml (3.4.2) + rubocop (1.80.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.46.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.46.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + ruby-progressbar (1.13.0) + rubyzip (2.4.1) + sassc (2.4.0) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt + securerandom (0.4.1) + selenium-webdriver (4.8.3) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) + shellany (0.0.1) + solargraph (0.58.2) + backport (~> 1.2) + benchmark (~> 0.4) + bundler (~> 2.0) + diff-lcs (~> 1.4) + jaro_winkler (~> 1.6, >= 1.6.1) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.1) + logger (~> 1.6) + observer (~> 0.1) + ostruct (~> 0.6) + parser (~> 3.0) + prism (~> 1.4) + rbs (~> 3.6.1) + reverse_markdown (~> 3.0) + rubocop (~> 1.38) + thor (~> 1.0) + tilt (~> 2.0) + yard (~> 0.9, >= 0.9.24) + yard-solargraph (~> 0.1) + sprockets (4.2.2) + concurrent-ruby (~> 1.0) + logger + rack (>= 2.2.4, < 4) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + sprockets (>= 3.0.0) + sqlite3 (2.7.3-aarch64-linux-gnu) + sqlite3 (2.7.3-aarch64-linux-musl) + sqlite3 (2.7.3-arm-linux-gnu) + sqlite3 (2.7.3-arm-linux-musl) + sqlite3 (2.7.3-arm64-darwin) + sqlite3 (2.7.3-x86_64-darwin) + sqlite3 (2.7.3-x86_64-linux-gnu) + sqlite3 (2.7.3-x86_64-linux-musl) + stimulus-rails (1.2.1) + railties (>= 6.0.0) + stringio (3.1.7) + thor (1.4.0) + tilt (2.6.1) + timeout (0.4.3) + turbo-rails (1.4.0) + actionpack (>= 6.0.0) + activejob (>= 6.0.0) + railties (>= 6.0.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.1.5) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + uri (1.0.4) + useragent (0.16.11) + web-console (4.2.0) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + webdrivers (5.2.0) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (~> 4.0) + websocket (1.2.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + will_paginate (3.3.1) + xpath (3.2.0) + nokogiri (~> 1.8) + yard (0.9.37) + yard-solargraph (0.1.0) + yard (~> 0.9) + zeitwerk (2.7.3) + +PLATFORMS + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + bcrypt (= 3.1.18) + bootsnap (= 1.16.0) + bootstrap-sass (= 3.4.1) + bootstrap-will_paginate (= 1.0.0) + capybara (= 3.38.0) + concurrent-ruby (= 1.3.4) + debug (= 1.7.1) + faker (= 2.21.0) + guard (= 2.18.0) + guard-minitest (= 2.4.6) + importmap-rails (= 1.1.5) + irb (= 1.15.2) + jbuilder (= 2.14.1) + mailgun-ruby (= 1.3.10) + minitest (= 5.18.0) + minitest-reporters (= 1.6.0) + puma (= 6.6.1) + rails (= 8.0.2.1) + rails-controller-testing (= 1.0.5) + reline (= 0.5.10) + repl_type_completor (= 0.1.10) + sassc-rails (= 2.1.2) + selenium-webdriver (= 4.8.3) + solargraph (= 0.58.2) + sprockets-rails (= 3.4.2) + sqlite3 (= 2.7.3) + stimulus-rails (= 1.2.1) + turbo-rails (= 1.4.0) + web-console (= 4.2.0) + webdrivers (= 5.2.0) + will_paginate (= 3.3.1) + +RUBY VERSION + ruby 3.2.10p266 + +BUNDLED WITH + 2.5.6 diff --git a/8_0/ch11/Guardfile b/8_0/ch11/Guardfile new file mode 100644 index 00000000..5def34a0 --- /dev/null +++ b/8_0/ch11/Guardfile @@ -0,0 +1,76 @@ + require "active_support/inflector" + # Guardのマッチング規則を定義 + guard :minitest, all_on_start: false do + watch(%r{^test/(.*)/?(.*)_test\.rb$}) + watch('test/test_helper.rb') { 'test' } + watch('config/routes.rb') { interface_tests } + watch(%r{app/views/layouts/*}) { interface_tests } + watch(%r{^app/models/(.*?)\.rb$}) do |matches| + ["test/models/#{matches[1]}_test.rb", + "test/integration/microposts_interface_test.rb"] + end + watch(%r{^test/fixtures/(.*?)\.yml$}) do |matches| + "test/models/#{matches[1].singularize}_test.rb" + end + watch(%r{^app/mailers/(.*?)\.rb$}) do |matches| + "test/mailers/#{matches[1]}_test.rb" + end + watch(%r{^app/views/(.*)_mailer/.*$}) do |matches| + "test/mailers/#{matches[1]}_mailer_test.rb" + end + watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches| + resource_tests(matches[1]) + end + watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches| + ["test/controllers/#{matches[1]}_controller_test.rb"] + + integration_tests(matches[1]) + end + watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches| + integration_tests(matches[1]) + end + watch('app/views/layouts/application.html.erb') do + 'test/integration/site_layout_test.rb' + end + watch('app/helpers/sessions_helper.rb') do + integration_tests << 'test/helpers/sessions_helper_test.rb' + end + watch('app/controllers/sessions_controller.rb') do + ['test/controllers/sessions_controller_test.rb', + 'test/integration/users_login_test.rb'] + end + watch('app/controllers/account_activations_controller.rb') do + 'test/integration/users_signup_test.rb' + end + watch(%r{app/views/users/*}) do + resource_tests('users') + + ['test/integration/microposts_interface_test.rb'] + end + watch('app/controllers/relationships_controller.rb') do + ['test/controllers/relationships_controller_test.rb', + 'test/integration/following_test.rb'] + end + end + + # 指定のリソースに対応する統合テストを返す + def integration_tests(resource = :all) + if resource == :all + Dir["test/integration/*"] + else + Dir["test/integration/#{resource}_*.rb"] + end + end + + # インターフェースが該当するすべてのテストを返す + def interface_tests + integration_tests << "test/controllers" + end + + # 指定のリソースに対応するコントローラのテストを返す + def controller_test(resource) + "test/controllers/#{resource}_controller_test.rb" + end + + # 指定のリソースに対応するすべてのテストを返す + def resource_tests(resource) + integration_tests(resource) << controller_test(resource) + end diff --git a/8_0/ch11/LICENSE b/8_0/ch11/LICENSE new file mode 100644 index 00000000..20622e0c --- /dev/null +++ b/8_0/ch11/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 GitHub & 2023 YassLab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/8_0/ch11/README.md b/8_0/ch11/README.md new file mode 100644 index 00000000..17ca733d --- /dev/null +++ b/8_0/ch11/README.md @@ -0,0 +1,35 @@ +# Ruby on Rails チュートリアルのサンプルアプリケーション + +これは、次の教材で作られたサンプルアプリケーションです。 +[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +(第8版) +[Michael Hartl](https://www.michaelhartl.com/) 著 + +## ライセンス + +[Ruby on Rails チュートリアル](https://railstutorial.jp/)内にある +ソースコードはMITライセンスとBeerwareライセンスのもとで公開されています。 +詳細は [LICENSE.md](LICENSE.md) をご覧ください。 + +## 使い方 + +このアプリケーションを動かす場合は、まずはリポジトリをフォークしてください。 + +フォークしたリポジトリで、「Code」から「Codespaces」タブに移動し、 +「Create codespace on main」をクリックすると環境構築がスタートします。 +Railsサーバーが立ち上がり、シンプルブラウザが表示されるまでしばらくお待ちください。 + +次に、データベースへのマイグレーションを実行します。 + +``` +$ rails db:migrate +``` + +最後に、テストを実行してうまく動いているかどうか確認してください。 + +``` +$ rails test +``` + +詳しくは、[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +を参考にしてください。 diff --git a/8_0/ch11/Rakefile b/8_0/ch11/Rakefile new file mode 100644 index 00000000..9a5ea738 --- /dev/null +++ b/8_0/ch11/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/8_0/ch11/app/assets/config/manifest.js b/8_0/ch11/app/assets/config/manifest.js new file mode 100644 index 00000000..ddd546a0 --- /dev/null +++ b/8_0/ch11/app/assets/config/manifest.js @@ -0,0 +1,4 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js diff --git a/8_0/ch11/app/assets/images/.keep b/8_0/ch11/app/assets/images/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/app/assets/images/rails.svg b/8_0/ch11/app/assets/images/rails.svg new file mode 100644 index 00000000..e1971850 --- /dev/null +++ b/8_0/ch11/app/assets/images/rails.svg @@ -0,0 +1,35 @@ + + + +rails-logo + + + + + + + + + + + + + + + + + + + + + diff --git a/8_0/ch11/app/assets/stylesheets/application.css b/8_0/ch11/app/assets/stylesheets/application.css new file mode 100644 index 00000000..288b9ab7 --- /dev/null +++ b/8_0/ch11/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/8_0/ch11/app/assets/stylesheets/custom.scss b/8_0/ch11/app/assets/stylesheets/custom.scss new file mode 100644 index 00000000..4fd609ce --- /dev/null +++ b/8_0/ch11/app/assets/stylesheets/custom.scss @@ -0,0 +1,227 @@ +@import "bootstrap-sprockets"; +@import "bootstrap"; + +/* mixins, variables, etc. */ + +$gray-medium-light: #eaeaea; + +@mixin box_sizing { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* universal */ + +body { + padding-top: 60px; +} + +section { + overflow: auto; +} + +textarea { + resize: vertical; +} + +.center { + text-align: center; + h1 { + margin-bottom: 10px; + } +} + +/* typography */ + +h1, h2, h3, h4, h5, h6 { + line-height: 1; +} + +h1 { + font-size: 3em; + letter-spacing: -2px; + margin-bottom: 30px; + text-align: center; +} + +h2 { + font-size: 1.2em; + letter-spacing: -1px; + margin-bottom: 30px; + text-align: center; + font-weight: normal; + color: $gray-light; +} + +p { + font-size: 1.1em; + line-height: 1.7em; +} + + +/* header */ + +#logo { + float: left; + margin-right: 10px; + font-size: 1.7em; + color: white; + text-transform: uppercase; + letter-spacing: -1px; + padding-top: 9px; + font-weight: bold; + &:hover { + color: white; + text-decoration: none; + } +} + +/* footer */ + +footer { + margin-top: 45px; + padding-top: 5px; + border-top: 1px solid $gray-medium-light; + color: $gray-light; + a { + color: $gray; + &:hover { + color: $gray-darker; + } + } + small { + float: left; + } + ul { + float: right; + list-style: none; + li { + float: left; + margin-left: 15px; + } + } +} +@media (max-width: 800px) { + footer { + small { + display: block; + float: none; + margin-bottom: 1em; + } + ul { + float: none; + padding: 0; + li { + float: none; + margin-left: 0; + } + } + } +} + +/* miscellaneous */ + +.debug_dump { + clear: both; + float: left; + width: 100%; + margin-top: 45px; +} + +/* sidebar */ + +aside { + section.user_info { + margin-top: 20px; + } + section { + padding: 10px 0; + margin-top: 20px; + &:first-child { + border: 0; + padding-top: 0; + } + span { + display: block; + margin-bottom: 3px; + line-height: 1; + } + h1 { + font-size: 1.4em; + text-align: left; + letter-spacing: -1px; + margin-bottom: 3px; + margin-top: 0px; + } + } +} + +.gravatar { + float: left; + margin-right: 10px; +} + +.gravatar_edit { + margin-top: 15px; +} + +/* forms */ + +input, textarea { + border: 1px solid #bbb; + width: 100%; + margin-bottom: 15px; + @include box_sizing; +} + +input { + height: auto !important; +} + +#error_explanation { + color: red; + ul { + color: red; + margin: 0 0 30px 0; + } +} + +.field_with_errors { + @extend .has-error; + .form-control { + color: $state-danger-text; + } +} + +.checkbox { + margin-top: -10px; + margin-bottom: 10px; + span { + margin-left: 20px; + font-weight: normal; + } +} + +#session_remember_me { + width: auto; + margin-left: 0; +} + +/* Dropdown menu */ + +.dropdown-menu.active { + display: block; +} + +/* Users index */ + +.users { + list-style: none; + margin: 0; + li { + overflow: auto; + padding: 10px 0; + border-bottom: 1px solid $gray-lighter; + } +} \ No newline at end of file diff --git a/8_0/ch11/app/assets/stylesheets/hello.css b/8_0/ch11/app/assets/stylesheets/hello.css new file mode 100644 index 00000000..497881a4 --- /dev/null +++ b/8_0/ch11/app/assets/stylesheets/hello.css @@ -0,0 +1,40 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.codespaces { + text-align: center; +} +.codespaces code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} +.codespaces-logo { + height: 40vmin; + pointer-events: none; +} +.codespaces-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} +.codespaces-link { + color: #61dafb; +} + +.heart { + color: #ff0000; +} +.small { + font-size: 0.75rem; +} diff --git a/8_0/ch11/app/channels/application_cable/channel.rb b/8_0/ch11/app/channels/application_cable/channel.rb new file mode 100644 index 00000000..d6726972 --- /dev/null +++ b/8_0/ch11/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/8_0/ch11/app/channels/application_cable/connection.rb b/8_0/ch11/app/channels/application_cable/connection.rb new file mode 100644 index 00000000..0ff5442f --- /dev/null +++ b/8_0/ch11/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/8_0/ch11/app/controllers/account_activations_controller.rb b/8_0/ch11/app/controllers/account_activations_controller.rb new file mode 100644 index 00000000..4985c73e --- /dev/null +++ b/8_0/ch11/app/controllers/account_activations_controller.rb @@ -0,0 +1,15 @@ +class AccountActivationsController < ApplicationController + + def edit + user = User.find_by(email: params[:email]) + if user && !user.activated? && user.authenticated?(:activation, params[:id]) + user.activate + log_in user + flash[:success] = "Account activated!" + redirect_to user + else + flash[:danger] = "Invalid activation link" + redirect_to root_url + end + end +end diff --git a/8_0/ch11/app/controllers/application_controller.rb b/8_0/ch11/app/controllers/application_controller.rb new file mode 100644 index 00000000..09faff3f --- /dev/null +++ b/8_0/ch11/app/controllers/application_controller.rb @@ -0,0 +1,3 @@ +class ApplicationController < ActionController::Base + include SessionsHelper +end diff --git a/8_0/ch11/app/controllers/concerns/.keep b/8_0/ch11/app/controllers/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/app/controllers/hello_controller.rb b/8_0/ch11/app/controllers/hello_controller.rb new file mode 100644 index 00000000..52037537 --- /dev/null +++ b/8_0/ch11/app/controllers/hello_controller.rb @@ -0,0 +1,4 @@ +class HelloController < ApplicationController + def index + end +end diff --git a/8_0/ch11/app/controllers/sessions_controller.rb b/8_0/ch11/app/controllers/sessions_controller.rb new file mode 100644 index 00000000..4fa0043a --- /dev/null +++ b/8_0/ch11/app/controllers/sessions_controller.rb @@ -0,0 +1,31 @@ +class SessionsController < ApplicationController + + def new + end + + def create + user = User.find_by(email: params[:session][:email].downcase) + if user && user.authenticate(params[:session][:password]) + if user.activated? + forwarding_url = session[:forwarding_url] + reset_session + params[:session][:remember_me] == '1' ? remember(user) : forget(user) + log_in user + redirect_to forwarding_url || user + else + message = "Account not activated. " + message += "Check your email for the activation link." + flash[:warning] = message + redirect_to root_url + end + else + flash.now[:danger] = 'Invalid email/password combination' + render 'new', status: :unprocessable_entity + end + end + + def destroy + log_out if logged_in? + redirect_to root_url, status: :see_other + end +end diff --git a/8_0/ch11/app/controllers/static_pages_controller.rb b/8_0/ch11/app/controllers/static_pages_controller.rb new file mode 100644 index 00000000..0673ad5e --- /dev/null +++ b/8_0/ch11/app/controllers/static_pages_controller.rb @@ -0,0 +1,14 @@ +class StaticPagesController < ApplicationController + + def home + end + + def help + end + + def about + end + + def contact + end +end diff --git a/8_0/ch11/app/controllers/users_controller.rb b/8_0/ch11/app/controllers/users_controller.rb new file mode 100644 index 00000000..05d245b3 --- /dev/null +++ b/8_0/ch11/app/controllers/users_controller.rb @@ -0,0 +1,75 @@ +class UsersController < ApplicationController + before_action :logged_in_user, only: [:index, :edit, :update, :destroy] + before_action :correct_user, only: [:edit, :update] + before_action :admin_user, only: :destroy + + def index + @users = User.paginate(page: params[:page]) + end + + def show + @user = User.find(params[:id]) + end + + def new + @user = User.new + end + + def create + @user = User.new(user_params) + if @user.save + @user.send_activation_email + flash[:info] = "Please check your email to activate your account." + redirect_to root_url + else + render 'new', status: :unprocessable_entity + end + end + + def edit + end + + def update + if @user.update(user_params) + flash[:success] = "Profile updated" + redirect_to @user + else + render 'edit', status: :unprocessable_entity + end + end + + def destroy + User.find(params[:id]).destroy + flash[:success] = "User deleted" + redirect_to users_url, status: :see_other + end + + private + + def user_params + params.expect(user: [:name, :email, :password, + :password_confirmation]) + end + + # beforeフィルタ + + # ログイン済みユーザーかどうか確認 + def logged_in_user + unless logged_in? + store_location + flash[:danger] = "Please log in." + redirect_to login_url, status: :see_other + end + end + + # 正しいユーザーかどうか確認 + def correct_user + @user = User.find(params[:id]) + redirect_to(root_url, status: :see_other) unless current_user?(@user) + end + + # 管理者かどうか確認 + def admin_user + redirect_to(root_url, status: :see_other) unless current_user.admin? + end +end diff --git a/8_0/ch11/app/helpers/account_activations_helper.rb b/8_0/ch11/app/helpers/account_activations_helper.rb new file mode 100644 index 00000000..c4d5ac72 --- /dev/null +++ b/8_0/ch11/app/helpers/account_activations_helper.rb @@ -0,0 +1,2 @@ +module AccountActivationsHelper +end diff --git a/8_0/ch11/app/helpers/application_helper.rb b/8_0/ch11/app/helpers/application_helper.rb new file mode 100644 index 00000000..708e4e48 --- /dev/null +++ b/8_0/ch11/app/helpers/application_helper.rb @@ -0,0 +1,12 @@ +module ApplicationHelper + + # ページごとの完全なタイトルを返します。 # コメント行 + def full_title(page_title = '') # メソッド定義とオプション引数 + base_title = "Ruby on Rails Tutorial Sample App" # 変数への代入 + if page_title.empty? # 論理値テスト + base_title # 暗黙の戻り値 + else + "#{page_title} | #{base_title}" # 文字列の結合 + end + end +end diff --git a/8_0/ch11/app/helpers/hello_codespaces_helper.rb b/8_0/ch11/app/helpers/hello_codespaces_helper.rb new file mode 100644 index 00000000..874a2ca2 --- /dev/null +++ b/8_0/ch11/app/helpers/hello_codespaces_helper.rb @@ -0,0 +1,2 @@ +module HelloCodespacesHelper +end diff --git a/8_0/ch11/app/helpers/sessions_helper.rb b/8_0/ch11/app/helpers/sessions_helper.rb new file mode 100644 index 00000000..a38a35bd --- /dev/null +++ b/8_0/ch11/app/helpers/sessions_helper.rb @@ -0,0 +1,60 @@ +module SessionsHelper + + # 渡されたユーザーでログインする + def log_in(user) + session[:user_id] = user.id + # セッションリプレイ攻撃から保護する + # 詳しくは https://techracho.bpsinc.jp/hachi8833/2023_06_02/130443 を参照 + session[:session_token] = user.session_token + end + + # ユーザーを永続的セッションに保存する + def remember(user) + user.remember + cookies.permanent.encrypted[:user_id] = user.id + cookies.permanent[:remember_token] = user.remember_token + end + + # 現在ログイン中のユーザーを返す(いる場合) + def current_user + if (user_id = session[:user_id]) + user = User.find_by(id: user_id) + @current_user ||= user if session[:session_token] == user.session_token + elsif (user_id = cookies.encrypted[:user_id]) + user = User.find_by(id: user_id) + if user && user.authenticated?(:remember, cookies[:remember_token]) + log_in user + @current_user = user + end + end + end + + # 渡されたユーザーがカレントユーザーであればtrueを返す + def current_user?(user) + user && user == current_user + end + + # ユーザーがログインしていればtrue、その他ならfalseを返す + def logged_in? + !current_user.nil? + end + + # 永続的セッションを破棄する + def forget(user) + user.forget + cookies.delete(:user_id) + cookies.delete(:remember_token) + end + + # 現在のユーザーをログアウトする + def log_out + forget(current_user) + reset_session + @current_user = nil + end + + # アクセスしようとしたURLを保存する + def store_location + session[:forwarding_url] = request.original_url if request.get? + end +end diff --git a/8_0/ch11/app/helpers/static_pages_helper.rb b/8_0/ch11/app/helpers/static_pages_helper.rb new file mode 100644 index 00000000..2d63e79e --- /dev/null +++ b/8_0/ch11/app/helpers/static_pages_helper.rb @@ -0,0 +1,2 @@ +module StaticPagesHelper +end diff --git a/8_0/ch11/app/helpers/users_helper.rb b/8_0/ch11/app/helpers/users_helper.rb new file mode 100644 index 00000000..53790499 --- /dev/null +++ b/8_0/ch11/app/helpers/users_helper.rb @@ -0,0 +1,10 @@ +module UsersHelper + + # 引数で与えられたユーザーのGravatar画像を返す + def gravatar_for(user, options = { size: 80 }) + size = options[:size] + gravatar_id = Digest::MD5::hexdigest(user.email.downcase) + gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}" + image_tag(gravatar_url, alt: user.name, class: "gravatar") + end +end diff --git a/8_0/ch11/app/javascript/application.js b/8_0/ch11/app/javascript/application.js new file mode 100644 index 00000000..46ebafe8 --- /dev/null +++ b/8_0/ch11/app/javascript/application.js @@ -0,0 +1,5 @@ +// Configure your import map in config/importmap.rb. +// Read more: https://github.com/rails/importmap-rails +import "@hotwired/turbo-rails" +import "controllers" +import "custom/menu" diff --git a/8_0/ch11/app/javascript/controllers/application.js b/8_0/ch11/app/javascript/controllers/application.js new file mode 100644 index 00000000..1213e85c --- /dev/null +++ b/8_0/ch11/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/8_0/ch11/app/javascript/controllers/hello_controller.js b/8_0/ch11/app/javascript/controllers/hello_controller.js new file mode 100644 index 00000000..5975c078 --- /dev/null +++ b/8_0/ch11/app/javascript/controllers/hello_controller.js @@ -0,0 +1,7 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.element.textContent = "Hello World!" + } +} diff --git a/8_0/ch11/app/javascript/controllers/index.js b/8_0/ch11/app/javascript/controllers/index.js new file mode 100644 index 00000000..54ad4cad --- /dev/null +++ b/8_0/ch11/app/javascript/controllers/index.js @@ -0,0 +1,11 @@ +// Import and register all your controllers from the importmap under controllers/* + +import { application } from "controllers/application" + +// Eager load all controllers defined in the import map under controllers/**/*_controller +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) + +// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) +// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" +// lazyLoadControllersFrom("controllers", application) diff --git a/8_0/ch11/app/javascript/custom/menu.js b/8_0/ch11/app/javascript/custom/menu.js new file mode 100644 index 00000000..92e4a8dd --- /dev/null +++ b/8_0/ch11/app/javascript/custom/menu.js @@ -0,0 +1,22 @@ +// メニュー操作 + +// トグルリスナーを追加してクリックをリッスンする +document.addEventListener("turbo:load", function() { + let hamburger = document.querySelector("#hamburger"); + if (hamburger){ + hamburger.addEventListener("click", function(event) { + event.preventDefault(); + let menu = document.querySelector("#navbar-menu"); + menu.classList.toggle("collapse"); + }); + } + + let account = document.querySelector("#account"); + if (account) { + account.addEventListener("click", function(event) { + event.preventDefault(); + let menu = document.querySelector("#dropdown-menu"); + menu.classList.toggle("active"); + }); + } +}); diff --git a/8_0/ch11/app/jobs/application_job.rb b/8_0/ch11/app/jobs/application_job.rb new file mode 100644 index 00000000..d394c3d1 --- /dev/null +++ b/8_0/ch11/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/8_0/ch11/app/mailers/application_mailer.rb b/8_0/ch11/app/mailers/application_mailer.rb new file mode 100644 index 00000000..b166e6e2 --- /dev/null +++ b/8_0/ch11/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "Mailgunに登録したメールアドレス" + layout "mailer" +end diff --git a/8_0/ch11/app/mailers/user_mailer.rb b/8_0/ch11/app/mailers/user_mailer.rb new file mode 100644 index 00000000..0452efdb --- /dev/null +++ b/8_0/ch11/app/mailers/user_mailer.rb @@ -0,0 +1,13 @@ +class UserMailer < ApplicationMailer + + def account_activation(user) + @user = user + mail to: user.email, subject: "Account activation" + end + + def password_reset + @greeting = "Hi" + + mail to: "to@example.org" + end +end diff --git a/8_0/ch11/app/models/application_record.rb b/8_0/ch11/app/models/application_record.rb new file mode 100644 index 00000000..b63caeb8 --- /dev/null +++ b/8_0/ch11/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/8_0/ch11/app/models/concerns/.keep b/8_0/ch11/app/models/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/app/models/user.rb b/8_0/ch11/app/models/user.rb new file mode 100644 index 00000000..b9ab9c67 --- /dev/null +++ b/8_0/ch11/app/models/user.rb @@ -0,0 +1,73 @@ +class User < ApplicationRecord + attr_accessor :remember_token, :activation_token + before_save :downcase_email + before_create :create_activation_digest + validates :name, presence: true, length: { maximum: 50 } + VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i + validates :email, presence: true, length: { maximum: 255 }, + format: { with: VALID_EMAIL_REGEX }, + uniqueness: true + has_secure_password + validates :password, presence: true, length: { minimum: 6 }, allow_nil: true + + # 渡された文字列のハッシュ値を返す + def User.digest(string) + cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : + BCrypt::Engine.cost + BCrypt::Password.create(string, cost: cost) + end + + # ランダムなトークンを返す + def User.new_token + SecureRandom.urlsafe_base64 + end + + # 永続的セッションのためにユーザーをデータベースに記憶する + def remember + self.remember_token = User.new_token + update_attribute(:remember_digest, User.digest(remember_token)) + remember_digest + end + + # セッションハイジャック防止のためにセッショントークンを返す + # この記憶ダイジェストを再利用しているのは単に利便性のため + def session_token + remember_digest || remember + end + + # 渡されたトークンがダイジェストと一致したらtrueを返す + def authenticated?(attribute, token) + digest = send("#{attribute}_digest") + return false if digest.nil? + BCrypt::Password.new(digest).is_password?(token) + end + + # ユーザーのログイン情報を破棄する + def forget + update_attribute(:remember_digest, nil) + end + + # アカウントを有効にする + def activate + update_attribute(:activated, true) + update_attribute(:activated_at, Time.zone.now) + end + + # 有効化用のメールを送信する + def send_activation_email + UserMailer.account_activation(self).deliver_now + end + + private + + # メールアドレスをすべて小文字にする + def downcase_email + self.email = email.downcase + end + + # 有効化トークンとダイジェストを作成および代入する + def create_activation_digest + self.activation_token = User.new_token + self.activation_digest = User.digest(activation_token) + end +end diff --git a/8_0/ch11/app/views/hello/index.html.erb b/8_0/ch11/app/views/hello/index.html.erb new file mode 100644 index 00000000..71661105 --- /dev/null +++ b/8_0/ch11/app/views/hello/index.html.erb @@ -0,0 +1,16 @@ +
    +
    + +

    + Codespaces + ♥️ + Railsチュートリアル +

    +

    + 🎓✨
    + ロゴ画像が表示されたらセットアップ完了です! +

    +
    +
    diff --git a/8_0/ch11/app/views/layouts/_footer.html.erb b/8_0/ch11/app/views/layouts/_footer.html.erb new file mode 100644 index 00000000..b6e31ff9 --- /dev/null +++ b/8_0/ch11/app/views/layouts/_footer.html.erb @@ -0,0 +1,13 @@ + diff --git a/8_0/ch11/app/views/layouts/_header.html.erb b/8_0/ch11/app/views/layouts/_header.html.erb new file mode 100644 index 00000000..8b07e211 --- /dev/null +++ b/8_0/ch11/app/views/layouts/_header.html.erb @@ -0,0 +1,39 @@ + diff --git a/8_0/ch11/app/views/layouts/application.html.erb b/8_0/ch11/app/views/layouts/application.html.erb new file mode 100644 index 00000000..eb23ef94 --- /dev/null +++ b/8_0/ch11/app/views/layouts/application.html.erb @@ -0,0 +1,24 @@ + + + + <%= full_title(yield(:title)) %> + + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + + + <%= render 'layouts/header' %> +
    + <% flash.each do |message_type, message| %> +
    <%= message %>
    + <% end %> + <%= yield %> + <%= render 'layouts/footer' %> + <%= debug(params) if Rails.env.development? %> +
    + + diff --git a/8_0/ch11/app/views/layouts/mailer.html.erb b/8_0/ch11/app/views/layouts/mailer.html.erb new file mode 100644 index 00000000..cbd34d2e --- /dev/null +++ b/8_0/ch11/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/8_0/ch11/app/views/layouts/mailer.text.erb b/8_0/ch11/app/views/layouts/mailer.text.erb new file mode 100644 index 00000000..37f0bddb --- /dev/null +++ b/8_0/ch11/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/8_0/ch11/app/views/sessions/new.html.erb b/8_0/ch11/app/views/sessions/new.html.erb new file mode 100644 index 00000000..4eafa29b --- /dev/null +++ b/8_0/ch11/app/views/sessions/new.html.erb @@ -0,0 +1,24 @@ +<% provide(:title, "Log in") %> +

    Log in

    + +
    +
    + <%= form_with(url: login_path, scope: :session) do |f| %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :remember_me, class: "checkbox inline" do %> + <%= f.check_box :remember_me %> + Remember me on this computer + <% end %> + + <%= f.submit "Log in", class: "btn btn-primary" %> + <% end %> + +

    New user? <%= link_to "Sign up now!", signup_path %>

    +
    +
    diff --git a/8_0/ch11/app/views/shared/_error_messages.html.erb b/8_0/ch11/app/views/shared/_error_messages.html.erb new file mode 100644 index 00000000..f80053e4 --- /dev/null +++ b/8_0/ch11/app/views/shared/_error_messages.html.erb @@ -0,0 +1,12 @@ +<% if @user.errors.any? %> +
    +
    + The form contains <%= pluralize(@user.errors.count, "error") %>. +
    + +
    +<% end %> diff --git a/8_0/ch11/app/views/static_pages/about.html.erb b/8_0/ch11/app/views/static_pages/about.html.erb new file mode 100644 index 00000000..dce8fef2 --- /dev/null +++ b/8_0/ch11/app/views/static_pages/about.html.erb @@ -0,0 +1,10 @@ +<% provide(:title, "About") %> +

    About

    +

    + Ruby on Rails Tutorial + is a book and + screencast + to teach web development with + Ruby on Rails. + This is the sample application for the tutorial. +

    diff --git a/8_0/ch11/app/views/static_pages/contact.html.erb b/8_0/ch11/app/views/static_pages/contact.html.erb new file mode 100644 index 00000000..4a5a35a6 --- /dev/null +++ b/8_0/ch11/app/views/static_pages/contact.html.erb @@ -0,0 +1,6 @@ +<% provide(:title, 'Contact') %> +

    Contact

    +

    + Contact the Ruby on Rails Tutorial about the sample app at the + contact page. +

    diff --git a/8_0/ch11/app/views/static_pages/help.html.erb b/8_0/ch11/app/views/static_pages/help.html.erb new file mode 100644 index 00000000..4d06919d --- /dev/null +++ b/8_0/ch11/app/views/static_pages/help.html.erb @@ -0,0 +1,9 @@ +<% provide(:title, "Help") %> +

    Help

    +

    + Get help on the Ruby on Rails Tutorial at the + Rails Tutorial Help page. + To get help on this sample app, see the + Ruby on Rails Tutorial + book. +

    diff --git a/8_0/ch11/app/views/static_pages/home.html.erb b/8_0/ch11/app/views/static_pages/home.html.erb new file mode 100644 index 00000000..f87607de --- /dev/null +++ b/8_0/ch11/app/views/static_pages/home.html.erb @@ -0,0 +1,14 @@ +
    +

    Welcome to the Sample App

    + +

    + This is the home page for the + Ruby on Rails Tutorial + sample application. +

    + + <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %> +
    + +<%= link_to image_tag("rails.svg", alt: "Rails logo", width: "200"), + "https://rubyonrails.org/" %> diff --git a/8_0/ch11/app/views/user_mailer/account_activation.html.erb b/8_0/ch11/app/views/user_mailer/account_activation.html.erb new file mode 100644 index 00000000..c95644ae --- /dev/null +++ b/8_0/ch11/app/views/user_mailer/account_activation.html.erb @@ -0,0 +1,10 @@ +

    Sample App

    + +

    Hi <%= @user.name %>,

    + +

    +Welcome to the Sample App! Click on the link below to activate your account: +

    + +<%= link_to "Activate", edit_account_activation_url(@user.activation_token, + email: @user.email) %> diff --git a/8_0/ch11/app/views/user_mailer/account_activation.text.erb b/8_0/ch11/app/views/user_mailer/account_activation.text.erb new file mode 100644 index 00000000..fecf2be3 --- /dev/null +++ b/8_0/ch11/app/views/user_mailer/account_activation.text.erb @@ -0,0 +1,5 @@ +Hi <%= @user.name %>, + +Welcome to the Sample App! Click on the link below to activate your account: + +<%= edit_account_activation_url(@user.activation_token, email: @user.email) %> diff --git a/8_0/ch11/app/views/user_mailer/password_reset.html.erb b/8_0/ch11/app/views/user_mailer/password_reset.html.erb new file mode 100644 index 00000000..6dec9843 --- /dev/null +++ b/8_0/ch11/app/views/user_mailer/password_reset.html.erb @@ -0,0 +1,5 @@ +

    User#password_reset

    + +

    + <%= @greeting %>, find me in app/views/user_mailer/password_reset.html.erb +

    diff --git a/8_0/ch11/app/views/user_mailer/password_reset.text.erb b/8_0/ch11/app/views/user_mailer/password_reset.text.erb new file mode 100644 index 00000000..5ba80cc5 --- /dev/null +++ b/8_0/ch11/app/views/user_mailer/password_reset.text.erb @@ -0,0 +1,3 @@ +User#password_reset + +<%= @greeting %>, find me in app/views/user_mailer/password_reset.text.erb diff --git a/8_0/ch11/app/views/users/_user.html.erb b/8_0/ch11/app/views/users/_user.html.erb new file mode 100644 index 00000000..cf66c339 --- /dev/null +++ b/8_0/ch11/app/views/users/_user.html.erb @@ -0,0 +1,8 @@ +
  • + <%= gravatar_for user, size: 50 %> + <%= link_to user.name, user %> + <% if current_user.admin? && !current_user?(user) %> + | <%= link_to "delete", user, data: { "turbo-method": :delete, + turbo_confirm: "You sure?" } %> + <% end %> +
  • diff --git a/8_0/ch11/app/views/users/edit.html.erb b/8_0/ch11/app/views/users/edit.html.erb new file mode 100644 index 00000000..ae672af0 --- /dev/null +++ b/8_0/ch11/app/views/users/edit.html.erb @@ -0,0 +1,29 @@ +<% provide(:title, "Edit user") %> +

    Update your profile

    + +
    +
    + <%= form_with(model: @user) do |f| %> + <%= render 'shared/error_messages' %> + + <%= f.label :name %> + <%= f.text_field :name, class: 'form-control' %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :password_confirmation, "Confirmation" %> + <%= f.password_field :password_confirmation, class: 'form-control' %> + + <%= f.submit "Save changes", class: "btn btn-primary" %> + <% end %> + +
    + <%= gravatar_for @user %> + change +
    +
    +
    diff --git a/8_0/ch11/app/views/users/index.html.erb b/8_0/ch11/app/views/users/index.html.erb new file mode 100644 index 00000000..5b6dbaeb --- /dev/null +++ b/8_0/ch11/app/views/users/index.html.erb @@ -0,0 +1,10 @@ +<% provide(:title, 'All users') %> +

    All users

    + +<%= will_paginate %> + + + +<%= will_paginate %> diff --git a/8_0/ch11/app/views/users/new.html.erb b/8_0/ch11/app/views/users/new.html.erb new file mode 100644 index 00000000..79e39f80 --- /dev/null +++ b/8_0/ch11/app/views/users/new.html.erb @@ -0,0 +1,24 @@ +<% provide(:title, 'Sign up') %> +

    Sign up

    + +
    +
    + <%= form_with(model: @user) do |f| %> + <%= render 'shared/error_messages' %> + + <%= f.label :name %> + <%= f.text_field :name, class: 'form-control' %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :password_confirmation, "Confirmation" %> + <%= f.password_field :password_confirmation, class: 'form-control' %> + + <%= f.submit "Create my account", class: "btn btn-primary" %> + <% end %> +
    +
    diff --git a/8_0/ch11/app/views/users/show.html.erb b/8_0/ch11/app/views/users/show.html.erb new file mode 100644 index 00000000..4fda5f89 --- /dev/null +++ b/8_0/ch11/app/views/users/show.html.erb @@ -0,0 +1,11 @@ +<% provide(:title, @user.name) %> +
    + +
    diff --git a/8_0/ch11/bin/bundle b/8_0/ch11/bin/bundle new file mode 100755 index 00000000..981e650b --- /dev/null +++ b/8_0/ch11/bin/bundle @@ -0,0 +1,114 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + requirement = bundler_gem_version.approximate_recommendation + + return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0") + + requirement += ".a" if bundler_gem_version.prerelease? + + requirement + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/8_0/ch11/bin/dev b/8_0/ch11/bin/dev new file mode 100755 index 00000000..5f91c205 --- /dev/null +++ b/8_0/ch11/bin/dev @@ -0,0 +1,2 @@ +#!/usr/bin/env ruby +exec "./bin/rails", "server", *ARGV diff --git a/8_0/ch11/bin/importmap b/8_0/ch11/bin/importmap new file mode 100755 index 00000000..36502ab1 --- /dev/null +++ b/8_0/ch11/bin/importmap @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require_relative "../config/application" +require "importmap/commands" diff --git a/8_0/ch11/bin/rails b/8_0/ch11/bin/rails new file mode 100755 index 00000000..efc03774 --- /dev/null +++ b/8_0/ch11/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/8_0/ch11/bin/rake b/8_0/ch11/bin/rake new file mode 100755 index 00000000..4fbf10b9 --- /dev/null +++ b/8_0/ch11/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/8_0/ch11/bin/render-build.sh b/8_0/ch11/bin/render-build.sh new file mode 100644 index 00000000..b0156aa6 --- /dev/null +++ b/8_0/ch11/bin/render-build.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# exit on error +set -o errexit +bundle install +bundle exec rails assets:precompile +bundle exec rails assets:clean +DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rails db:migrate:reset +bundle exec rails db:seed diff --git a/8_0/ch11/bin/rubocop b/8_0/ch11/bin/rubocop new file mode 100755 index 00000000..40330c0f --- /dev/null +++ b/8_0/ch11/bin/rubocop @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +# explicit rubocop config increases performance slightly while avoiding config confusion. +ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) + +load Gem.bin_path("rubocop", "rubocop") diff --git a/8_0/ch11/bin/setup b/8_0/ch11/bin/setup new file mode 100755 index 00000000..be3db3c0 --- /dev/null +++ b/8_0/ch11/bin/setup @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +require "fileutils" + +APP_ROOT = File.expand_path("..", __dir__) + +def system!(*args) + system(*args, exception: true) +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts "== Installing dependencies ==" + system("bundle check") || system!("bundle install") + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + puts "\n== Removing old logs and tempfiles ==" + system! "bin/rails log:clear tmp:clear" + + unless ARGV.include?("--skip-server") + puts "\n== Starting development server ==" + STDOUT.flush # flush the output before exec(2) so that it displays + exec "bin/dev" + end +end diff --git a/8_0/ch11/config.ru b/8_0/ch11/config.ru new file mode 100644 index 00000000..4a3c09a6 --- /dev/null +++ b/8_0/ch11/config.ru @@ -0,0 +1,6 @@ +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/8_0/ch11/config/application.rb b/8_0/ch11/config/application.rb new file mode 100644 index 00000000..0461e72f --- /dev/null +++ b/8_0/ch11/config/application.rb @@ -0,0 +1,27 @@ +require_relative "boot" + +require "rails/all" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module SampleApp + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 8.0 + + # Please, add to the `ignore` list any other `lib` subdirectories that do + # not contain `.rb` files, or that should not be reloaded or eager loaded. + # Common ones are `templates`, `generators`, or `middleware`, for example. + config.autoload_lib(ignore: %w[assets tasks]) + + # Configuration for the application, engines, and railties goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + # + # config.time_zone = "Central Time (US & Canada)" + # config.eager_load_paths << Rails.root.join("extras") + end +end diff --git a/8_0/ch11/config/boot.rb b/8_0/ch11/config/boot.rb new file mode 100644 index 00000000..988a5ddc --- /dev/null +++ b/8_0/ch11/config/boot.rb @@ -0,0 +1,4 @@ +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "bundler/setup" # Set up gems listed in the Gemfile. +require "bootsnap/setup" # Speed up boot time by caching expensive operations. diff --git a/8_0/ch11/config/cable.yml b/8_0/ch11/config/cable.yml new file mode 100644 index 00000000..ae7a2256 --- /dev/null +++ b/8_0/ch11/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: codespaces_try_rails_production diff --git a/8_0/ch11/config/credentials.yml.enc b/8_0/ch11/config/credentials.yml.enc new file mode 100644 index 00000000..339d64d2 --- /dev/null +++ b/8_0/ch11/config/credentials.yml.enc @@ -0,0 +1 @@ +NCs9M8uakg1CDYJENuGaipzymY8uG6i5gzghsy4npa3gYmGfYKPHMnEcJHCVHlM3gxDNJBteUqkRCxHQ5WQId8hzgxoQCL5RX7rtQ8XUzkJwgSUjKvbCrG5atjKkN9XuL91is5vtEN5W7vEz3qxN7Rp33QbNFiDfLs71F3zHVDYEMi6Dy1QMhVCa1v/tyRjBu/5m37RuiYF5FzWJnh4LhexSVr7Agm4LRLLN6qLCpoAe6D3TPHHBdtu7Pvy8jlrEfnnkUJ61wrj8+kOL4uLtpFShdYKhDkoTjQk7TCOgWyifnHAXazkBZoJZ1QYv/tZ9/ZoHvMEQ2dtGHlyTX8fbKtI13d2CFjGaDNKvRuurxE8dbeyiTfbycvve46+cz9OTlmhh0SGt24R1WV6GBKoDUeoHrIiBvW5qmKka--dQ0rodxmfgFLg2y/--t2x0gQjh8FYlk8v79vLSNg== \ No newline at end of file diff --git a/8_0/ch11/config/database.yml b/8_0/ch11/config/database.yml new file mode 100644 index 00000000..fcba57f1 --- /dev/null +++ b/8_0/ch11/config/database.yml @@ -0,0 +1,25 @@ +# SQLite. Versions 3.8.0 and up are supported. +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem "sqlite3" +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/8_0/ch11/config/environment.rb b/8_0/ch11/config/environment.rb new file mode 100644 index 00000000..cac53157 --- /dev/null +++ b/8_0/ch11/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/8_0/ch11/config/environments/development.rb b/8_0/ch11/config/environments/development.rb new file mode 100644 index 00000000..6e0241dd --- /dev/null +++ b/8_0/ch11/config/environments/development.rb @@ -0,0 +1,90 @@ +require 'active_support/core_ext/integer/time' + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Make code changes take effect immediately without server restart. + config.enable_reloading = true + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing. + config.server_timing = true + + # Add the following line to disable forgery_protection_origin_check + config.action_controller.forgery_protection_origin_check = false + + # Enable/disable Action Controller caching. By default Action Controller caching is disabled. + # Run rails dev:cache to toggle Action Controller caching. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + config.public_file_server.headers = { 'cache-control' => "public, max-age=#{2.days.to_i}" } + else + config.action_controller.perform_caching = false + end + + # Change to :null_store to avoid any caching. + config.cache_store = :memory_store + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Make template changes take effect immediately. + config.action_mailer.perform_caching = false + + # Set localhost to be used by links generated in mailer templates. + host = 'example.com' # ここをコピペすると失敗します。自分の環境のホストに変えてください。 + # クラウドIDEの場合は以下をお使いください + config.action_mailer.default_url_options = { host: host, protocol: 'https' } + # localhostで開発している場合は以下をお使いください + # config.action_mailer.default_url_options = { host: host, protocol: 'http' } + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Append comments with runtime information tags to SQL queries in logs. + config.active_record.query_log_tags_enabled = true + + # Highlight code that enqueued background job in logs. + config.active_job.verbose_enqueue_logs = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true + + # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. + # config.generators.apply_rubocop_autocorrect_after_generate! + + pf_domain = ENV['GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN'] + config.action_dispatch.default_headers = { + 'X-Frame-Options' => "ALLOW-FROM #{pf_domain}" + } + + # Allow requests from our preview domain. + pf_host = "#{ENV['CODESPACE_NAME']}-3000.#{pf_domain}" + config.hosts << pf_host + + config.action_cable.allowed_request_origins = ["https://#{pf_host}"] +end diff --git a/8_0/ch11/config/environments/production.rb b/8_0/ch11/config/environments/production.rb new file mode 100644 index 00000000..6ed5c3fa --- /dev/null +++ b/8_0/ch11/config/environments/production.rb @@ -0,0 +1,94 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.enable_reloading = false + + # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). + config.eager_load = true + + # Full error reports are disabled. + config.consider_all_requests_local = false + + # Turn on fragment caching in view templates. + config.action_controller.perform_caching = true + + # Cache assets for far-future expiry since they are all digest stamped. + config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" } + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = "http://assets.example.com" + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Assume all access to the app is happening through a SSL-terminating reverse proxy. + config.assume_ssl = true + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = true + + # Skip http-to-https redirect for the default health check endpoint. + # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } + + # Log to STDOUT with the current request id as a default log tag. + config.log_tags = [ :request_id ] + config.logger = ActiveSupport::TaggedLogging.logger(STDOUT) + + # Change to "debug" to log everything (including potentially personally-identifiable information!) + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + + # Prevent health checks from clogging up the logs. + config.silence_healthcheck_path = "/up" + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Replace the default in-process memory cache store with a durable alternative. + # config.cache_store = :mem_cache_store + + # Replace the default in-process and non-durable queuing backend for Active Job. + # config.active_job.queue_adapter = :resque + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + config.action_mailer.raise_delivery_errors = true + host = 'https://<あなたのRenderアプリ名>.onrender.com' + config.action_mailer.default_url_options = { host: host } + # Mailgun API設定 + config.action_mailer.delivery_method = :mailgun + config.action_mailer.mailgun_settings = { + api_key: ENV['MAILGUN_API_KEY'], + domain: ENV['MAILGUN_DOMAIN'] + } + + # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. + # config.action_mailer.smtp_settings = { + # user_name: Rails.application.credentials.dig(:smtp, :user_name), + # password: Rails.application.credentials.dig(:smtp, :password), + # address: "smtp.example.com", + # port: 587, + # authentication: :plain + # } + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Only use :id for inspections in production. + config.active_record.attributes_for_inspect = [ :id ] + + # Enable DNS rebinding protection and other `Host` header attacks. + # config.hosts = [ + # "example.com", # Allow requests from example.com + # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` + # ] + # + # Skip DNS rebinding protection for the default health check endpoint. + # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } +end diff --git a/8_0/ch11/config/environments/test.rb b/8_0/ch11/config/environments/test.rb new file mode 100644 index 00000000..0ebe3b4e --- /dev/null +++ b/8_0/ch11/config/environments/test.rb @@ -0,0 +1,53 @@ +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # While tests run files are not watched, reloading is not necessary. + config.enable_reloading = false + + # Eager loading loads your entire application. When running a single test locally, + # this is usually not necessary, and can slow down your test suite. However, it's + # recommended that you enable it in continuous integration systems to ensure eager + # loading is working properly before deploying your code. + config.eager_load = ENV["CI"].present? + + # Configure public file server for tests with cache-control for performance. + config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } + + # Show full error reports. + config.consider_all_requests_local = true + config.cache_store = :null_store + + # Render exception templates for rescuable exceptions and raise for other exceptions. + config.action_dispatch.show_exceptions = :none + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :test + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true +end diff --git a/8_0/ch11/config/importmap.rb b/8_0/ch11/config/importmap.rb new file mode 100644 index 00000000..caf3ed06 --- /dev/null +++ b/8_0/ch11/config/importmap.rb @@ -0,0 +1,8 @@ +# Pin npm packages by running ./bin/importmap + +pin "application", preload: true +pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true +pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true +pin_all_from "app/javascript/controllers", under: "controllers" +pin_all_from "app/javascript/custom", under: "custom" diff --git a/8_0/ch11/config/initializers/assets.rb b/8_0/ch11/config/initializers/assets.rb new file mode 100644 index 00000000..48732442 --- /dev/null +++ b/8_0/ch11/config/initializers/assets.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = "1.0" + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path diff --git a/8_0/ch11/config/initializers/content_security_policy.rb b/8_0/ch11/config/initializers/content_security_policy.rb new file mode 100644 index 00000000..b3076b38 --- /dev/null +++ b/8_0/ch11/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header + +# Rails.application.configure do +# config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end +# +# # Generate session nonces for permitted importmap, inline scripts, and inline styles. +# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } +# config.content_security_policy_nonce_directives = %w(script-src style-src) +# +# # Report violations without enforcing the policy. +# # config.content_security_policy_report_only = true +# end diff --git a/8_0/ch11/config/initializers/filter_parameter_logging.rb b/8_0/ch11/config/initializers/filter_parameter_logging.rb new file mode 100644 index 00000000..c0b717f7 --- /dev/null +++ b/8_0/ch11/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. +# Use this to limit dissemination of sensitive information. +# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. +Rails.application.config.filter_parameters += [ + :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc +] diff --git a/8_0/ch11/config/initializers/inflections.rb b/8_0/ch11/config/initializers/inflections.rb new file mode 100644 index 00000000..3860f659 --- /dev/null +++ b/8_0/ch11/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym "RESTful" +# end diff --git a/8_0/ch11/config/initializers/new_framework_defaults_8_0.rb b/8_0/ch11/config/initializers/new_framework_defaults_8_0.rb new file mode 100644 index 00000000..92efa951 --- /dev/null +++ b/8_0/ch11/config/initializers/new_framework_defaults_8_0.rb @@ -0,0 +1,30 @@ +# Be sure to restart your server when you modify this file. +# +# This file eases your Rails 8.0 framework defaults upgrade. +# +# Uncomment each configuration one by one to switch to the new default. +# Once your application is ready to run with all new defaults, you can remove +# this file and set the `config.load_defaults` to `8.0`. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. +# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html + +### +# Specifies whether `to_time` methods preserve the UTC offset of their receivers or preserves the timezone. +# If set to `:zone`, `to_time` methods will use the timezone of their receivers. +# If set to `:offset`, `to_time` methods will use the UTC offset. +# If `false`, `to_time` methods will convert to the local system UTC offset instead. +#++ +# Rails.application.config.active_support.to_time_preserves_timezone = :zone + +### +# When both `If-Modified-Since` and `If-None-Match` are provided by the client +# only consider `If-None-Match` as specified by RFC 7232 Section 6. +# If set to `false` both conditions need to be satisfied. +#++ +# Rails.application.config.action_dispatch.strict_freshness = true + +### +# Set `Regexp.timeout` to `1`s by default to improve security over Regexp Denial-of-Service attacks. +#++ +# Regexp.timeout = 1 diff --git a/8_0/ch11/config/initializers/permissions_policy.rb b/8_0/ch11/config/initializers/permissions_policy.rb new file mode 100644 index 00000000..00f64d71 --- /dev/null +++ b/8_0/ch11/config/initializers/permissions_policy.rb @@ -0,0 +1,11 @@ +# Define an application-wide HTTP permissions policy. For further +# information see https://developers.google.com/web/updates/2018/06/feature-policy +# +# Rails.application.config.permissions_policy do |f| +# f.camera :none +# f.gyroscope :none +# f.microphone :none +# f.usb :none +# f.fullscreen :self +# f.payment :self, "https://secure.example.com" +# end diff --git a/8_0/ch11/config/locales/en.yml b/8_0/ch11/config/locales/en.yml new file mode 100644 index 00000000..8ca56fc7 --- /dev/null +++ b/8_0/ch11/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# "true": "foo" +# +# To learn more, please read the Rails Internationalization guide +# available at https://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/8_0/ch11/config/puma.rb b/8_0/ch11/config/puma.rb new file mode 100644 index 00000000..a248513b --- /dev/null +++ b/8_0/ch11/config/puma.rb @@ -0,0 +1,41 @@ +# This configuration file will be evaluated by Puma. The top-level methods that +# are invoked here are part of Puma's configuration DSL. For more information +# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. +# +# Puma starts a configurable number of processes (workers) and each process +# serves each request in a thread from an internal thread pool. +# +# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You +# should only set this value when you want to run 2 or more workers. The +# default is already 1. +# +# The ideal number of threads per worker depends both on how much time the +# application spends waiting for IO operations and on how much you wish to +# prioritize throughput over latency. +# +# As a rule of thumb, increasing the number of threads will increase how much +# traffic a given process can handle (throughput), but due to CRuby's +# Global VM Lock (GVL) it has diminishing returns and will degrade the +# response time (latency) of the application. +# +# The default is set to 3 threads as it's deemed a decent compromise between +# throughput and latency for the average Rails application. +# +# Any libraries that use a connection pool or another resource pool should +# be configured to provide at least as many connections as the number of +# threads. This includes Active Record's `pool` parameter in `database.yml`. +threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +port ENV.fetch("PORT", 3000) + +# Allow puma to be restarted by `bin/rails restart` command. +plugin :tmp_restart + +# Run the Solid Queue supervisor inside of Puma for single-server deployments +plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] + +# Specify the PID file. Defaults to tmp/pids/server.pid in development. +# In other environments, only set the PID file if requested. +pidfile ENV["PIDFILE"] if ENV["PIDFILE"] diff --git a/8_0/ch11/config/routes.rb b/8_0/ch11/config/routes.rb new file mode 100644 index 00000000..a883aa4c --- /dev/null +++ b/8_0/ch11/config/routes.rb @@ -0,0 +1,12 @@ +Rails.application.routes.draw do + root "static_pages#home" + get "/help", to: "static_pages#help" + get "/about", to: "static_pages#about" + get "/contact", to: "static_pages#contact" + get "/signup", to: "users#new" + get "/login", to: "sessions#new" + post "/login", to: "sessions#create" + delete "/logout", to: "sessions#destroy" + resources :users + resources :account_activations, only: [:edit] +end diff --git a/8_0/ch11/config/storage.yml b/8_0/ch11/config/storage.yml new file mode 100644 index 00000000..4942ab66 --- /dev/null +++ b/8_0/ch11/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket-<%= Rails.env %> + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket-<%= Rails.env %> + +# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name-<%= Rails.env %> + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/8_0/ch11/db/migrate/20260512080056_create_users.rb b/8_0/ch11/db/migrate/20260512080056_create_users.rb new file mode 100644 index 00000000..3b80eb00 --- /dev/null +++ b/8_0/ch11/db/migrate/20260512080056_create_users.rb @@ -0,0 +1,10 @@ +class CreateUsers < ActiveRecord::Migration[8.0] + def change + create_table :users do |t| + t.string :name + t.string :email + + t.timestamps + end + end +end diff --git a/8_0/ch11/db/migrate/20260512080840_add_index_to_users_email.rb b/8_0/ch11/db/migrate/20260512080840_add_index_to_users_email.rb new file mode 100644 index 00000000..a8e4ed8e --- /dev/null +++ b/8_0/ch11/db/migrate/20260512080840_add_index_to_users_email.rb @@ -0,0 +1,5 @@ +class AddIndexToUsersEmail < ActiveRecord::Migration[8.0] + def change + add_index :users, :email, unique: true + end +end diff --git a/8_0/ch11/db/migrate/20260512081511_add_password_digest_to_users.rb b/8_0/ch11/db/migrate/20260512081511_add_password_digest_to_users.rb new file mode 100644 index 00000000..81c7c9e0 --- /dev/null +++ b/8_0/ch11/db/migrate/20260512081511_add_password_digest_to_users.rb @@ -0,0 +1,5 @@ +class AddPasswordDigestToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :password_digest, :string + end +end diff --git a/8_0/ch11/db/migrate/20260626064202_add_remember_digest_to_users.rb b/8_0/ch11/db/migrate/20260626064202_add_remember_digest_to_users.rb new file mode 100644 index 00000000..7a464448 --- /dev/null +++ b/8_0/ch11/db/migrate/20260626064202_add_remember_digest_to_users.rb @@ -0,0 +1,5 @@ +class AddRememberDigestToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :remember_digest, :string + end +end diff --git a/8_0/ch11/db/migrate/20260626075635_add_admin_to_users.rb b/8_0/ch11/db/migrate/20260626075635_add_admin_to_users.rb new file mode 100644 index 00000000..c1f08cf5 --- /dev/null +++ b/8_0/ch11/db/migrate/20260626075635_add_admin_to_users.rb @@ -0,0 +1,5 @@ +class AddAdminToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :admin, :boolean, default: false + end +end diff --git a/8_0/ch11/db/migrate/20260630012638_add_activation_to_users.rb b/8_0/ch11/db/migrate/20260630012638_add_activation_to_users.rb new file mode 100644 index 00000000..be38e387 --- /dev/null +++ b/8_0/ch11/db/migrate/20260630012638_add_activation_to_users.rb @@ -0,0 +1,7 @@ +class AddActivationToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :activation_digest, :string + add_column :users, :activated, :boolean, default: false + add_column :users, :activated_at, :datetime + end +end diff --git a/8_0/ch11/db/schema.rb b/8_0/ch11/db/schema.rb new file mode 100644 index 00000000..c26a9fd5 --- /dev/null +++ b/8_0/ch11/db/schema.rb @@ -0,0 +1,27 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[8.0].define(version: 2026_06_30_012638) do + create_table "users", force: :cascade do |t| + t.string "name" + t.string "email" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "password_digest" + t.string "remember_digest" + t.boolean "admin", default: false + t.string "activation_digest" + t.boolean "activated", default: false + t.datetime "activated_at" + t.index ["email"], name: "index_users_on_email", unique: true + end +end diff --git a/8_0/ch11/db/seeds.rb b/8_0/ch11/db/seeds.rb new file mode 100644 index 00000000..28f895b6 --- /dev/null +++ b/8_0/ch11/db/seeds.rb @@ -0,0 +1,21 @@ +# メインのサンプルユーザーを1人作成する +User.create!(name: "Example User", + email: "example@railstutorial.org", + password: "foobar", + password_confirmation: "foobar", + admin: true, + activated: true, + activated_at: Time.zone.now) + +# 追加のユーザーをまとめて生成する +99.times do |n| + name = Faker::Name.name + email = "example-#{n+1}@railstutorial.org" + password = "password" + User.create!(name: name, + email: email, + password: password, + password_confirmation: password, + activated: true, + activated_at: Time.zone.now) +end diff --git a/8_0/ch11/lib/assets/.keep b/8_0/ch11/lib/assets/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/lib/tasks/.keep b/8_0/ch11/lib/tasks/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/log/.keep b/8_0/ch11/log/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/public/400.html b/8_0/ch11/public/400.html new file mode 100644 index 00000000..282dbc8c --- /dev/null +++ b/8_0/ch11/public/400.html @@ -0,0 +1,114 @@ + + + + + + + The server cannot process the request due to a client error (400 Bad Request) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    The server cannot process the request due to a client error. Please check the request and try again. If you’re the application owner check the logs for more information.

    +
    +
    + + + + diff --git a/8_0/ch11/public/404.html b/8_0/ch11/public/404.html new file mode 100644 index 00000000..c0670bc8 --- /dev/null +++ b/8_0/ch11/public/404.html @@ -0,0 +1,114 @@ + + + + + + + The page you were looking for doesn’t exist (404 Not found) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    The page you were looking for doesn’t exist. You may have mistyped the address or the page may have moved. If you’re the application owner check the logs for more information.

    +
    +
    + + + + diff --git a/8_0/ch11/public/406-unsupported-browser.html b/8_0/ch11/public/406-unsupported-browser.html new file mode 100644 index 00000000..9532a9cc --- /dev/null +++ b/8_0/ch11/public/406-unsupported-browser.html @@ -0,0 +1,114 @@ + + + + + + + Your browser is not supported (406 Not Acceptable) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    Your browser is not supported.
    Please upgrade your browser to continue.

    +
    +
    + + + + diff --git a/8_0/ch11/public/422.html b/8_0/ch11/public/422.html new file mode 100644 index 00000000..8bcf0601 --- /dev/null +++ b/8_0/ch11/public/422.html @@ -0,0 +1,114 @@ + + + + + + + The change you wanted was rejected (422 Unprocessable Entity) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    The change you wanted was rejected. Maybe you tried to change something you didn’t have access to. If you’re the application owner check the logs for more information.

    +
    +
    + + + + diff --git a/8_0/ch11/public/500.html b/8_0/ch11/public/500.html new file mode 100644 index 00000000..d77718c3 --- /dev/null +++ b/8_0/ch11/public/500.html @@ -0,0 +1,114 @@ + + + + + + + We’re sorry, but something went wrong (500 Internal Server Error) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    We’re sorry, but something went wrong.
    If you’re the application owner check the logs for more information.

    +
    +
    + + + + diff --git a/8_0/ch11/public/apple-touch-icon-precomposed.png b/8_0/ch11/public/apple-touch-icon-precomposed.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/public/apple-touch-icon.png b/8_0/ch11/public/apple-touch-icon.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/public/favicon.ico b/8_0/ch11/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/public/icon.png b/8_0/ch11/public/icon.png new file mode 100644 index 00000000..c4c9dbfb Binary files /dev/null and b/8_0/ch11/public/icon.png differ diff --git a/8_0/ch11/public/icon.svg b/8_0/ch11/public/icon.svg new file mode 100644 index 00000000..04b34bf8 --- /dev/null +++ b/8_0/ch11/public/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/8_0/ch11/public/railstutorial.png b/8_0/ch11/public/railstutorial.png new file mode 100644 index 00000000..70835e6b Binary files /dev/null and b/8_0/ch11/public/railstutorial.png differ diff --git a/8_0/ch11/public/robots.txt b/8_0/ch11/public/robots.txt new file mode 100644 index 00000000..c19f78ab --- /dev/null +++ b/8_0/ch11/public/robots.txt @@ -0,0 +1 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/8_0/ch11/storage/.keep b/8_0/ch11/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/test/application_system_test_case.rb b/8_0/ch11/test/application_system_test_case.rb new file mode 100644 index 00000000..d19212ab --- /dev/null +++ b/8_0/ch11/test/application_system_test_case.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/8_0/ch11/test/channels/application_cable/connection_test.rb b/8_0/ch11/test/channels/application_cable/connection_test.rb new file mode 100644 index 00000000..800405f1 --- /dev/null +++ b/8_0/ch11/test/channels/application_cable/connection_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase + # test "connects with cookies" do + # cookies.signed[:user_id] = 42 + # + # connect + # + # assert_equal connection.user_id, "42" + # end +end diff --git a/8_0/ch11/test/controllers/.keep b/8_0/ch11/test/controllers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/test/controllers/account_activations_controller_test.rb b/8_0/ch11/test/controllers/account_activations_controller_test.rb new file mode 100644 index 00000000..bcd21995 --- /dev/null +++ b/8_0/ch11/test/controllers/account_activations_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class AccountActivationsControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/8_0/ch11/test/controllers/hello_codespaces_controller_test.rb b/8_0/ch11/test/controllers/hello_codespaces_controller_test.rb new file mode 100644 index 00000000..54353ea6 --- /dev/null +++ b/8_0/ch11/test/controllers/hello_codespaces_controller_test.rb @@ -0,0 +1,8 @@ +require "test_helper" + +class HelloCodespacesControllerTest < ActionDispatch::IntegrationTest + # test "should get index" do + # get "/" + # assert_response :success + # end +end diff --git a/8_0/ch11/test/controllers/sessions_controller_test.rb b/8_0/ch11/test/controllers/sessions_controller_test.rb new file mode 100644 index 00000000..8cc3f29e --- /dev/null +++ b/8_0/ch11/test/controllers/sessions_controller_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +class SessionsControllerTest < ActionDispatch::IntegrationTest + + test "should get new" do + get login_path + assert_response :success + end +end diff --git a/8_0/ch11/test/controllers/static_pages_controller_test.rb b/8_0/ch11/test/controllers/static_pages_controller_test.rb new file mode 100644 index 00000000..847856a3 --- /dev/null +++ b/8_0/ch11/test/controllers/static_pages_controller_test.rb @@ -0,0 +1,28 @@ +require "test_helper" + +class StaticPagesControllerTest < ActionDispatch::IntegrationTest + + test "should get home" do + get root_path + assert_response :success + assert_select "title", "Ruby on Rails Tutorial Sample App" + end + + test "should get help" do + get help_path + assert_response :success + assert_select "title", "Help | Ruby on Rails Tutorial Sample App" + end + + test "should get about" do + get about_path + assert_response :success + assert_select "title", "About | Ruby on Rails Tutorial Sample App" + end + + test "should get contact" do + get contact_path + assert_response :success + assert_select "title", "Contact | Ruby on Rails Tutorial Sample App" + end +end diff --git a/8_0/ch11/test/controllers/users_controller_test.rb b/8_0/ch11/test/controllers/users_controller_test.rb new file mode 100644 index 00000000..ffd2ec99 --- /dev/null +++ b/8_0/ch11/test/controllers/users_controller_test.rb @@ -0,0 +1,64 @@ +require "test_helper" + +class UsersControllerTest < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + @other_user = users(:archer) + end + + test "should get new" do + get signup_path + assert_response :success + end + + test "should redirect index when not logged in" do + get users_path + assert_redirected_to login_url + end + + test "should redirect edit when not logged in" do + get edit_user_path(@user) + assert_not flash.empty? + assert_redirected_to login_url + end + + test "should redirect update when not logged in" do + patch user_path(@user), params: { user: { name: @user.name, + email: @user.email } } + assert_not flash.empty? + assert_redirected_to login_url + end + + test "should redirect edit when logged in as wrong user" do + log_in_as(@other_user) + get edit_user_path(@user) + assert flash.empty? + assert_redirected_to root_url + end + + test "should redirect update when logged in as wrong user" do + log_in_as(@other_user) + patch user_path(@user), params: { user: { name: @user.name, + email: @user.email } } + assert flash.empty? + assert_redirected_to root_url + end + + test "should redirect destroy when not logged in" do + assert_no_difference 'User.count' do + delete user_path(@user) + end + assert_response :see_other + assert_redirected_to login_url + end + + test "should redirect destroy when logged in as a non-admin" do + log_in_as(@other_user) + assert_no_difference 'User.count' do + delete user_path(@user) + end + assert_response :see_other + assert_redirected_to root_url + end +end diff --git a/8_0/ch11/test/fixtures/files/.keep b/8_0/ch11/test/fixtures/files/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/test/fixtures/users.yml b/8_0/ch11/test/fixtures/users.yml new file mode 100644 index 00000000..02f3d49a --- /dev/null +++ b/8_0/ch11/test/fixtures/users.yml @@ -0,0 +1,37 @@ +michael: + name: Michael Example + email: michael@example.com + password_digest: <%= User.digest('password') %> + admin: true + activated: true + activated_at: <%= Time.zone.now %> + +archer: + name: Sterling Archer + email: duchess@example.gov + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> + +lana: + name: Lana Kane + email: hands@example.gov + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> + +malory: + name: Malory Archer + email: boss@example.gov + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> + +<% 30.times do |n| %> +user_<%= n %>: + name: <%= "User #{n}" %> + email: <%= "user-#{n}@example.com" %> + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> +<% end %> diff --git a/8_0/ch11/test/helpers/.keep b/8_0/ch11/test/helpers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/test/helpers/sessions_helper_test.rb b/8_0/ch11/test/helpers/sessions_helper_test.rb new file mode 100644 index 00000000..735ff17a --- /dev/null +++ b/8_0/ch11/test/helpers/sessions_helper_test.rb @@ -0,0 +1,19 @@ +require "test_helper" + +class SessionsHelperTest < ActionView::TestCase + + def setup + @user = users(:michael) + remember(@user) + end + + test "current_user returns right user when session is nil" do + assert_equal @user, current_user + assert is_logged_in? + end + + test "current_user returns nil when remember digest is wrong" do + @user.update_attribute(:remember_digest, User.digest(User.new_token)) + assert_nil current_user + end +end diff --git a/8_0/ch11/test/integration/.keep b/8_0/ch11/test/integration/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/test/integration/site_layout_test.rb b/8_0/ch11/test/integration/site_layout_test.rb new file mode 100644 index 00000000..0f7ca649 --- /dev/null +++ b/8_0/ch11/test/integration/site_layout_test.rb @@ -0,0 +1,13 @@ +require "test_helper" + +class SiteLayoutTest < ActionDispatch::IntegrationTest + + test "layout links" do + get root_path + assert_template 'static_pages/home' + assert_select "a[href=?]", root_path, count: 2 + assert_select "a[href=?]", help_path + assert_select "a[href=?]", about_path + assert_select "a[href=?]", contact_path + end +end diff --git a/8_0/ch11/test/integration/users_edit_test.rb b/8_0/ch11/test/integration/users_edit_test.rb new file mode 100644 index 00000000..23ca6b68 --- /dev/null +++ b/8_0/ch11/test/integration/users_edit_test.rb @@ -0,0 +1,37 @@ +require "test_helper" + +class UsersEditTest < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + end + + test "unsuccessful edit" do + log_in_as(@user) + get edit_user_path(@user) + assert_template 'users/edit' + patch user_path(@user), params: { user: { name: "", + email: "foo@invalid", + password: "foo", + password_confirmation: "bar" } } + + assert_template 'users/edit' + end + + test "successful edit with friendly forwarding" do + get edit_user_path(@user) + log_in_as(@user) + assert_redirected_to edit_user_url(@user) + name = "Foo Bar" + email = "foo@bar.com" + patch user_path(@user), params: { user: { name: name, + email: email, + password: "", + password_confirmation: "" } } + assert_not flash.empty? + assert_redirected_to @user + @user.reload + assert_equal name, @user.name + assert_equal email, @user.email + end +end diff --git a/8_0/ch11/test/integration/users_index_test.rb b/8_0/ch11/test/integration/users_index_test.rb new file mode 100644 index 00000000..63bb8d9e --- /dev/null +++ b/8_0/ch11/test/integration/users_index_test.rb @@ -0,0 +1,34 @@ +require "test_helper" + +class UsersIndexTest < ActionDispatch::IntegrationTest + + def setup + @admin = users(:michael) + @non_admin = users(:archer) + end + + test "index as admin including pagination and delete links" do + log_in_as(@admin) + get users_path + assert_template 'users/index' + assert_select 'div.pagination' + first_page_of_users = User.paginate(page: 1) + first_page_of_users.each do |user| + assert_select 'a[href=?]', user_path(user), text: user.name + unless user == @admin + assert_select 'a[href=?]', user_path(user), text: 'delete' + end + end + assert_difference 'User.count', -1 do + delete user_path(@non_admin) + assert_response :see_other + assert_redirected_to users_url + end + end + + test "index as non-admin" do + log_in_as(@non_admin) + get users_path + assert_select 'a', text: 'delete', count: 0 + end +end diff --git a/8_0/ch11/test/integration/users_login_test.rb b/8_0/ch11/test/integration/users_login_test.rb new file mode 100644 index 00000000..a9e70eaa --- /dev/null +++ b/8_0/ch11/test/integration/users_login_test.rb @@ -0,0 +1,96 @@ +require "test_helper" + +class UsersLogin < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + end +end + +class InvalidPasswordTest < UsersLogin + + test "login path" do + get login_path + assert_template 'sessions/new' + end + + test "login with valid email/invalid password" do + post login_path, params: { session: { email: @user.email, + password: "invalid" } } + assert_not is_logged_in? + assert_template 'sessions/new' + assert_not flash.empty? + get root_path + assert flash.empty? + end +end + +class ValidLogin < UsersLogin + + def setup + super + post login_path, params: { session: { email: @user.email, + password: 'password' } } + end +end + +class ValidLoginTest < ValidLogin + + test "valid login" do + assert is_logged_in? + assert_redirected_to @user + end + + test "redirect after login" do + follow_redirect! + assert_template 'users/show' + assert_select "a[href=?]", login_path, count: 0 + assert_select "a[href=?]", logout_path + assert_select "a[href=?]", user_path(@user) + end +end + +class Logout < ValidLogin + + def setup + super + delete logout_path + end +end + +class LogoutTest < Logout + + test "successful logout" do + assert_not is_logged_in? + assert_response :see_other + assert_redirected_to root_url + end + + test "redirect after logout" do + follow_redirect! + assert_select "a[href=?]", login_path + assert_select "a[href=?]", logout_path, count: 0 + assert_select "a[href=?]", user_path(@user), count: 0 + end + + test "should still work after logout in second window" do + delete logout_path + assert_redirected_to root_url + end +end + +class RememberingTest < UsersLogin + + test "login with remembering" do + log_in_as(@user, remember_me: '1') + assert_not cookies[:remember_token].blank? + end + + test "login without remembering" do + # Cookieを保存してログイン + log_in_as(@user, remember_me: '1') + # Cookieが削除されていることを検証してからログイン + log_in_as(@user, remember_me: '0') + assert cookies[:remember_token].blank? + end +end diff --git a/8_0/ch11/test/integration/users_signup_test.rb b/8_0/ch11/test/integration/users_signup_test.rb new file mode 100644 index 00000000..92172d43 --- /dev/null +++ b/8_0/ch11/test/integration/users_signup_test.rb @@ -0,0 +1,73 @@ +require "test_helper" + +class UsersSignup < ActionDispatch::IntegrationTest + + def setup + ActionMailer::Base.deliveries.clear + end +end + +class UsersSignupTest < UsersSignup + + test "invalid signup information" do + assert_no_difference 'User.count' do + post users_path, params: { user: { name: "", + email: "user@invalid", + password: "foo", + password_confirmation: "bar" } } + end + assert_response :unprocessable_entity + assert_template 'users/new' + assert_select 'div#error_explanation' + assert_select 'div.field_with_errors' + end + + test "valid signup information with account activation" do + assert_difference 'User.count', 1 do + post users_path, params: { user: { name: "Example User", + email: "user@example.com", + password: "password", + password_confirmation: "password" } } + end + assert_equal 1, ActionMailer::Base.deliveries.size + end +end + +class AccountActivationTest < UsersSignup + + def setup + super + post users_path, params: { user: { name: "Example User", + email: "user@example.com", + password: "password", + password_confirmation: "password" } } + @user = assigns(:user) + end + + test "should not be activated" do + assert_not @user.activated? + end + + test "should not be able to log in before account activation" do + log_in_as(@user) + assert_not is_logged_in? + end + + test "should not be able to log in with invalid activation token" do + get edit_account_activation_path("invalid token", email: @user.email) + assert_not is_logged_in? + end + + test "should not be able to log in with invalid email" do + get edit_account_activation_path(@user.activation_token, email: 'wrong') + assert_not is_logged_in? + end + + test "should log in successfully with valid activation token and email" do + get edit_account_activation_path(@user.activation_token, email: @user.email) + assert @user.reload.activated? + follow_redirect! + assert_template 'users/show' + assert is_logged_in? + end +end diff --git a/8_0/ch11/test/mailers/.keep b/8_0/ch11/test/mailers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/test/mailers/previews/user_mailer_preview.rb b/8_0/ch11/test/mailers/previews/user_mailer_preview.rb new file mode 100644 index 00000000..dd4acc7d --- /dev/null +++ b/8_0/ch11/test/mailers/previews/user_mailer_preview.rb @@ -0,0 +1,17 @@ +# Preview all emails at http://localhost:3000/rails/mailers/user_mailer +class UserMailerPreview < ActionMailer::Preview + + # Preview this email at + # http://localhost:3000/rails/mailers/user_mailer/account_activation + def account_activation + user = User.first + user.activation_token = User.new_token + UserMailer.account_activation(user) + end + + # Preview this email at + # http://localhost:3000/rails/mailers/user_mailer/password_reset + def password_reset + UserMailer.password_reset + end +end diff --git a/8_0/ch11/test/mailers/user_mailer_test.rb b/8_0/ch11/test/mailers/user_mailer_test.rb new file mode 100644 index 00000000..12299198 --- /dev/null +++ b/8_0/ch11/test/mailers/user_mailer_test.rb @@ -0,0 +1,16 @@ +require "test_helper" + +class UserMailerTest < ActionMailer::TestCase + + test "account_activation" do + user = users(:michael) + user.activation_token = User.new_token + mail = UserMailer.account_activation(user) + assert_equal "Account activation", mail.subject + assert_equal [user.email], mail.to + assert_equal ["Mailgunに登録したメールアドレス"], mail.from + assert_match user.name, mail.body.encoded + assert_match user.activation_token, mail.body.encoded + assert_match CGI.escape(user.email), mail.body.encoded + end +end diff --git a/8_0/ch11/test/models/.keep b/8_0/ch11/test/models/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/test/models/user_test.rb b/8_0/ch11/test/models/user_test.rb new file mode 100644 index 00000000..f9730c50 --- /dev/null +++ b/8_0/ch11/test/models/user_test.rb @@ -0,0 +1,71 @@ +require "test_helper" + +class UserTest < ActiveSupport::TestCase + + def setup + @user = User.new(name: "Example User", email: "user@example.com", + password: "foobar", password_confirmation: "foobar") + end + + test "should be valid" do + assert @user.valid? + end + + test "name should be present" do + @user.name = " " + assert_not @user.valid? + end + + test "email should be present" do + @user.email = " " + assert_not @user.valid? + end + + test "name should not be too long" do + @user.name = "a" * 51 + assert_not @user.valid? + end + + test "email should not be too long" do + @user.email = "a" * 244 + "@example.com" + assert_not @user.valid? + end + + test "email validation should accept valid addresses" do + valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org + first.last@foo.jp alice+bob@baz.cn] + valid_addresses.each do |valid_address| + @user.email = valid_address + assert @user.valid?, "#{valid_address.inspect} should be valid" + end + end + + test "email validation should reject invalid addresses" do + invalid_addresses = %w[user@example,com user_at_foo.org user.name@example. + foo@bar_baz.com foo@bar+baz.com] + invalid_addresses.each do |invalid_address| + @user.email = invalid_address + assert_not @user.valid?, "#{invalid_address.inspect} should be invalid" + end + end + + test "email addresses should be unique" do + duplicate_user = @user.dup + @user.save + assert_not duplicate_user.valid? + end + + test "password should be present (nonblank)" do + @user.password = @user.password_confirmation = " " * 6 + assert_not @user.valid? + end + + test "password should have a minimum length" do + @user.password = @user.password_confirmation = "a" * 5 + assert_not @user.valid? + end + + test "authenticated? should return false for a user with nil digest" do + assert_not @user.authenticated?(:remember, '') + end +end diff --git a/8_0/ch11/test/system/.keep b/8_0/ch11/test/system/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/test/test_helper.rb b/8_0/ch11/test/test_helper.rb new file mode 100644 index 00000000..5fbcafd0 --- /dev/null +++ b/8_0/ch11/test/test_helper.rb @@ -0,0 +1,33 @@ +ENV["RAILS_ENV"] ||= "test" +require_relative "../config/environment" +require "rails/test_help" +require "minitest/reporters" +Minitest::Reporters.use! + +class ActiveSupport::TestCase + # 指定のワーカー数でテストを並列実行する + parallelize(workers: :number_of_processors) + + # test/fixtures/*.ymlにあるすべてのfixtureをセットアップする + fixtures :all + + # テストユーザーがログイン中の場合にtrueを返す + def is_logged_in? + !session[:user_id].nil? + end + + # テストユーザーとしてログインする + def log_in_as(user) + session[:user_id] = user.id + end +end + +class ActionDispatch::IntegrationTest + + # テストユーザーとしてログインする + def log_in_as(user, password: 'password', remember_me: '1') + post login_path, params: { session: { email: user.email, + password: password, + remember_me: remember_me } } + end +end \ No newline at end of file diff --git a/8_0/ch11/tmp/.keep b/8_0/ch11/tmp/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/tmp/pids/.keep b/8_0/ch11/tmp/pids/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/tmp/storage/.keep b/8_0/ch11/tmp/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/vendor/.keep b/8_0/ch11/vendor/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch11/vendor/javascript/.keep b/8_0/ch11/vendor/javascript/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/.devcontainer/devcontainer.json b/8_0/ch12/.devcontainer/devcontainer.json new file mode 100644 index 00000000..e7243217 --- /dev/null +++ b/8_0/ch12/.devcontainer/devcontainer.json @@ -0,0 +1,105 @@ +// Specifications: https://containers.dev/implementors/json_reference/ +// Format details: https://aka.ms/devcontainer.json +// Config options: https://github.com/microsoft/vscode-dev-containers/tree/main/containers/ruby +{ + "name": "railstutorial", + + // Universal is well-customized image for Codespaces: + // https://hub.docker.com/_/microsoft-devcontainers-universal + //"image": "mcr.microsoft.com/devcontainers/universal:2", + //"features": { + // "ghcr.io/devcontainers/features/ruby:1": {} + //}, + + // Use Ruby image if you want to pin Ruby version like '3.2' + // https://github.com/devcontainers/images/tree/main/src/ruby + "image": "mcr.microsoft.com/devcontainers/ruby:3.2", + //"workspaceFolder": "/railstutorial", => Container build failed + + // Enable learners to choose an affordable spec, starting at minimum one. + //"hostRequirements": { + // "cpus": 2, + // "memory": "4gb", + // "storage": "32gb" + //}, + + "waitFor": "onCreateCommand", + //"onCreateCommand": "", + "onCreateCommand": "gem install solargraph -v '0.58.2' -N", + //"onCreateCommand": "gem install solargraph -v '0.50.0' -N && gem install ruby-lsp -N", + //"onCreateCommand": "gem install ruby-lsp -N", + //# => Solargraph gem not found. Run `gem install solargraph` or update your Gemfile. + "updateContentCommand": "bundle install", + "postCreateCommand": "", + "postAttachCommand": { + "server": "rails server" + }, + "customizations": { + "codespaces": { + "openFiles": [ + "app/views/hello/index.html.erb" + ] + }, + "vscode": { + "extensions": [ + "GitHub.codespaces", + "Shopify.ruby-lsp", // https://github.com/Shopify/ruby-lsp + "castwide.solargraph" // https://github.com/castwide/vscode-solargraph + //"rebornix.Ruby", // https://github.com/rubyide/vscode-ruby (Deprecated) + + ], + "settings": { + // General settings for Codespaces (VS Code) + //"ruby.useLanguageServer": true , + //"ruby.format": "rubocop", + //"ruby.lint": { "rubocop": true }, + //"ruby.intellisense": "rubyLocate", + "editor.tabSize": 2, + "editor.formatOnSave": false, // Disable onSave to show diff edited by learners only + "editor.formatOnType": false, // Disable onType for the same reason above + "editor.insertSpaces": true, // Use spaces, not tabs, to avoid errors for learners + "editor.renderWhitespace": "none", + "[ruby]": { + "editor.defaultFormatter": "castwide.solargraph", + "editor.semanticHighlighting.enabled": true // Enable semantic highlighting + }, + "files.associations": { "*.erb": "erb" }, + "emmet.includeLanguages": { "erb": "html" }, + + // Settings for Solargraph + // https://github.com/castwide/solargraph + "solargraph.useBundler": false, + "solargraph.diagnostics": false, + "solargraph.formatting": true, // Use Ctrl+Shift+P->Format to format + "solargraph.autoformat": false, + "solargraph.definitions": true, + "solargraph.completion": true, + "solargraph.references": true, + "solargraph.symbols": true, + "solargraph.rename": true, + "solargraph.hover": true, + + // Settings for Ruby LSP + // https://github.com/Shopify/ruby-lsp + "rubyLsp.rubyVersionManager": { + "identifier": "none" + }, + "rubyLsp.formatter": "none", + "rubyLsp.enabledFeatures": { + "diagnostics": false, + "formatting": false + } + } + } + }, + "remoteEnv": { + "EDITOR": "code --wait" + }, + "portsAttributes": { + "3000": { + "label": "Application", + "onAutoForward": "openPreview" + } + }, + "forwardPorts": [3000] +} diff --git a/8_0/ch12/.devcontainer/icon.svg b/8_0/ch12/.devcontainer/icon.svg new file mode 100644 index 00000000..f8444a4a --- /dev/null +++ b/8_0/ch12/.devcontainer/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/8_0/ch12/.gitattributes b/8_0/ch12/.gitattributes new file mode 100644 index 00000000..31eeee0b --- /dev/null +++ b/8_0/ch12/.gitattributes @@ -0,0 +1,7 @@ +# See https://git-scm.com/docs/gitattributes for more about git attribute files. + +# Mark the database schema as having been generated. +db/schema.rb linguist-generated + +# Mark any vendored files as having been vendored. +vendor/* linguist-vendored diff --git a/8_0/ch12/.gitignore b/8_0/ch12/.gitignore new file mode 100644 index 00000000..e1ef53ef --- /dev/null +++ b/8_0/ch12/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-* + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore uploaded files in development. +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/ +!/tmp/storage/.keep + +/public/assets + +# Ignore master key for decrypting credentials and more. +/config/master.key + +# Ignore history file used by IRB for interactive Ruby. +.irb_history + +dump.rdb diff --git a/8_0/ch12/.irbrc b/8_0/ch12/.irbrc new file mode 100644 index 00000000..3737e036 --- /dev/null +++ b/8_0/ch12/.irbrc @@ -0,0 +1 @@ +IRB.conf[:COMPLETOR] = :type # default is :regexp diff --git a/8_0/ch12/.solargraph.yml b/8_0/ch12/.solargraph.yml new file mode 100644 index 00000000..76ac24a6 --- /dev/null +++ b/8_0/ch12/.solargraph.yml @@ -0,0 +1,32 @@ +--- +include: +- "**/*.rb" +exclude: +- spec/**/* +- test/**/* +- vendor/**/* +- ".bundle/**/*" +require: +- actioncable +- actionmailer +- actionpack +- actionview +- activejob +- activemodel +- activerecord +- activestorage +- activesupport +domains: [] +reporters: +- rubocop +- require_not_found +- typecheck +formatter: + rubocop: + cops: safe + except: [] + only: [] + extra_args: [] +require_paths: [] +plugins: [] +max_files: 5000 diff --git a/8_0/ch12/.vscode/settings.json b/8_0/ch12/.vscode/settings.json new file mode 100644 index 00000000..cf85f519 --- /dev/null +++ b/8_0/ch12/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.minimap.enabled": false +} diff --git a/8_0/ch12/Gemfile b/8_0/ch12/Gemfile new file mode 100644 index 00000000..0d40ba8c --- /dev/null +++ b/8_0/ch12/Gemfile @@ -0,0 +1,51 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '3.2.10' + +gem 'bcrypt', '3.1.18' +gem 'bootsnap', '1.16.0', require: false +gem 'bootstrap-sass', '3.4.1' +gem 'bootstrap-will_paginate', '1.0.0' +gem 'concurrent-ruby', '1.3.4' +gem 'faker', '2.21.0' +gem 'importmap-rails', '1.1.5' +gem 'jbuilder', '2.14.1' +gem 'puma', '6.6.1' +gem 'rails', '8.0.2.1' +gem 'sassc-rails', '2.1.2' +gem 'sprockets-rails', '3.4.2' +gem 'sqlite3', '2.7.3' +gem 'stimulus-rails', '1.2.1' +gem 'turbo-rails', '1.4.0' +gem 'will_paginate', '3.3.1' + +group :development, :test do + gem 'debug', '1.7.1', platforms: %i[mri mingw x64_mingw] + gem 'reline', '0.5.10' +end + +group :development do + gem 'irb', '1.15.2' + gem 'repl_type_completor', '0.1.10' + gem 'solargraph', '0.58.2' + gem 'web-console', '4.2.0' +end + +group :test do + gem 'capybara', '3.38.0' + gem 'guard', '2.18.0' + gem 'guard-minitest', '2.4.6' + gem 'minitest', '5.18.0' + gem 'minitest-reporters', '1.6.0' + gem 'rails-controller-testing', '1.0.5' + gem 'selenium-webdriver', '4.8.3' + gem 'webdrivers', '5.2.0' +end + +group :production do + gem 'mailgun-ruby', "1.3.10" +end + +# Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります +# gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] diff --git a/8_0/ch12/Gemfile.lock b/8_0/ch12/Gemfile.lock new file mode 100644 index 00000000..b08d1f3c --- /dev/null +++ b/8_0/ch12/Gemfile.lock @@ -0,0 +1,466 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + actionmailer (8.0.2.1) + actionpack (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.0.2.1) + actionview (= 8.0.2.1) + activesupport (= 8.0.2.1) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.0.2.1) + actionpack (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (8.0.2.1) + activesupport (= 8.0.2.1) + builder (~> 3.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.3.6) + activemodel (8.0.2.1) + activesupport (= 8.0.2.1) + activerecord (8.0.2.1) + activemodel (= 8.0.2.1) + activesupport (= 8.0.2.1) + timeout (>= 0.4.0) + activestorage (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activesupport (= 8.0.2.1) + marcel (~> 1.0) + activesupport (8.0.2.1) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + ansi (1.5.0) + ast (2.4.3) + autoprefixer-rails (10.4.21.0) + execjs (~> 2) + backport (1.2.0) + base64 (0.3.0) + bcrypt (3.1.18) + benchmark (0.4.1) + bigdecimal (3.2.3) + bindex (0.8.1) + bootsnap (1.16.0) + msgpack (~> 1.2) + bootstrap-sass (3.4.1) + autoprefixer-rails (>= 5.2.1) + sassc (>= 2.0.0) + bootstrap-will_paginate (1.0.0) + will_paginate + builder (3.3.0) + capybara (3.38.0) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + coderay (1.1.3) + concurrent-ruby (1.3.4) + connection_pool (2.5.4) + crass (1.0.6) + date (3.4.1) + debug (1.7.1) + irb (>= 1.5.0) + reline (>= 0.3.1) + diff-lcs (1.6.2) + drb (2.2.3) + erb (5.0.2) + erubi (1.13.1) + execjs (2.10.1) + faker (2.21.0) + i18n (>= 1.8.11, < 2) + faraday (2.14.3) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-multipart (1.1.1) + multipart-post (~> 2.0) + faraday-net_http (3.4.4) + net-http (~> 0.5) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) + formatador (1.2.0) + reline + globalid (1.2.1) + activesupport (>= 6.1) + guard (2.18.0) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.13.0) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-minitest (2.4.6) + guard-compat (~> 1.2) + minitest (>= 3.0) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + importmap-rails (1.1.5) + actionpack (>= 6.0.0) + railties (>= 6.0.0) + io-console (0.8.1) + irb (1.15.2) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jaro_winkler (1.6.1) + jbuilder (2.14.1) + actionview (>= 7.0.0) + activesupport (>= 7.0.0) + json (2.13.2) + kramdown (2.5.1) + rexml (>= 3.3.9) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) + loofah (2.24.1) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + lumberjack (1.4.1) + mail (2.8.1) + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + mailgun-ruby (1.3.10) + faraday (~> 2.1) + faraday-multipart (~> 1.1.0) + mini_mime + marcel (1.0.4) + matrix (0.4.3) + method_source (1.1.0) + mini_mime (1.1.5) + minitest (5.18.0) + minitest-reporters (1.6.0) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + msgpack (1.8.0) + multipart-post (2.4.1) + nenv (0.3.0) + net-http (0.9.1) + uri (>= 0.11.1) + net-imap (0.5.10) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.4) + nokogiri (1.19.1-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-musl) + racc (~> 1.4) + notiffany (0.1.3) + nenv (~> 0.1) + shellany (~> 0.0) + observer (0.1.2) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.9.0) + ast (~> 2.4.1) + racc + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + prism (1.4.0) + pry (0.15.2) + coderay (~> 1.1) + method_source (~> 1.0) + psych (5.2.6) + date + stringio + public_suffix (6.0.2) + puma (6.6.1) + nio4r (~> 2.0) + racc (1.8.1) + rack (3.2.5) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.2.1) + rack (>= 3) + rails (8.0.2.1) + actioncable (= 8.0.2.1) + actionmailbox (= 8.0.2.1) + actionmailer (= 8.0.2.1) + actionpack (= 8.0.2.1) + actiontext (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activemodel (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + bundler (>= 1.15.0) + railties (= 8.0.2.1) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.0) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rbs (3.6.1) + logger + rdoc (6.14.2) + erb + psych (>= 4.0.0) + regexp_parser (2.11.2) + reline (0.5.10) + io-console (~> 0.5) + repl_type_completor (0.1.10) + prism (~> 1.0) + rbs (>= 2.7.0, < 4.0.0) + reverse_markdown (3.0.0) + nokogiri + rexml (3.4.2) + rubocop (1.80.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.46.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.46.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + ruby-progressbar (1.13.0) + rubyzip (2.4.1) + sassc (2.4.0) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt + securerandom (0.4.1) + selenium-webdriver (4.8.3) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) + shellany (0.0.1) + solargraph (0.58.2) + backport (~> 1.2) + benchmark (~> 0.4) + bundler (~> 2.0) + diff-lcs (~> 1.4) + jaro_winkler (~> 1.6, >= 1.6.1) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.1) + logger (~> 1.6) + observer (~> 0.1) + ostruct (~> 0.6) + parser (~> 3.0) + prism (~> 1.4) + rbs (~> 3.6.1) + reverse_markdown (~> 3.0) + rubocop (~> 1.38) + thor (~> 1.0) + tilt (~> 2.0) + yard (~> 0.9, >= 0.9.24) + yard-solargraph (~> 0.1) + sprockets (4.2.2) + concurrent-ruby (~> 1.0) + logger + rack (>= 2.2.4, < 4) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + sprockets (>= 3.0.0) + sqlite3 (2.7.3-aarch64-linux-gnu) + sqlite3 (2.7.3-aarch64-linux-musl) + sqlite3 (2.7.3-arm-linux-gnu) + sqlite3 (2.7.3-arm-linux-musl) + sqlite3 (2.7.3-arm64-darwin) + sqlite3 (2.7.3-x86_64-darwin) + sqlite3 (2.7.3-x86_64-linux-gnu) + sqlite3 (2.7.3-x86_64-linux-musl) + stimulus-rails (1.2.1) + railties (>= 6.0.0) + stringio (3.1.7) + thor (1.4.0) + tilt (2.6.1) + timeout (0.4.3) + turbo-rails (1.4.0) + actionpack (>= 6.0.0) + activejob (>= 6.0.0) + railties (>= 6.0.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.1.5) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + uri (1.0.4) + useragent (0.16.11) + web-console (4.2.0) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + webdrivers (5.2.0) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (~> 4.0) + websocket (1.2.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + will_paginate (3.3.1) + xpath (3.2.0) + nokogiri (~> 1.8) + yard (0.9.37) + yard-solargraph (0.1.0) + yard (~> 0.9) + zeitwerk (2.7.3) + +PLATFORMS + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + bcrypt (= 3.1.18) + bootsnap (= 1.16.0) + bootstrap-sass (= 3.4.1) + bootstrap-will_paginate (= 1.0.0) + capybara (= 3.38.0) + concurrent-ruby (= 1.3.4) + debug (= 1.7.1) + faker (= 2.21.0) + guard (= 2.18.0) + guard-minitest (= 2.4.6) + importmap-rails (= 1.1.5) + irb (= 1.15.2) + jbuilder (= 2.14.1) + mailgun-ruby (= 1.3.10) + minitest (= 5.18.0) + minitest-reporters (= 1.6.0) + puma (= 6.6.1) + rails (= 8.0.2.1) + rails-controller-testing (= 1.0.5) + reline (= 0.5.10) + repl_type_completor (= 0.1.10) + sassc-rails (= 2.1.2) + selenium-webdriver (= 4.8.3) + solargraph (= 0.58.2) + sprockets-rails (= 3.4.2) + sqlite3 (= 2.7.3) + stimulus-rails (= 1.2.1) + turbo-rails (= 1.4.0) + web-console (= 4.2.0) + webdrivers (= 5.2.0) + will_paginate (= 3.3.1) + +RUBY VERSION + ruby 3.2.10p266 + +BUNDLED WITH + 2.5.6 diff --git a/8_0/ch12/Guardfile b/8_0/ch12/Guardfile new file mode 100644 index 00000000..5def34a0 --- /dev/null +++ b/8_0/ch12/Guardfile @@ -0,0 +1,76 @@ + require "active_support/inflector" + # Guardのマッチング規則を定義 + guard :minitest, all_on_start: false do + watch(%r{^test/(.*)/?(.*)_test\.rb$}) + watch('test/test_helper.rb') { 'test' } + watch('config/routes.rb') { interface_tests } + watch(%r{app/views/layouts/*}) { interface_tests } + watch(%r{^app/models/(.*?)\.rb$}) do |matches| + ["test/models/#{matches[1]}_test.rb", + "test/integration/microposts_interface_test.rb"] + end + watch(%r{^test/fixtures/(.*?)\.yml$}) do |matches| + "test/models/#{matches[1].singularize}_test.rb" + end + watch(%r{^app/mailers/(.*?)\.rb$}) do |matches| + "test/mailers/#{matches[1]}_test.rb" + end + watch(%r{^app/views/(.*)_mailer/.*$}) do |matches| + "test/mailers/#{matches[1]}_mailer_test.rb" + end + watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches| + resource_tests(matches[1]) + end + watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches| + ["test/controllers/#{matches[1]}_controller_test.rb"] + + integration_tests(matches[1]) + end + watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches| + integration_tests(matches[1]) + end + watch('app/views/layouts/application.html.erb') do + 'test/integration/site_layout_test.rb' + end + watch('app/helpers/sessions_helper.rb') do + integration_tests << 'test/helpers/sessions_helper_test.rb' + end + watch('app/controllers/sessions_controller.rb') do + ['test/controllers/sessions_controller_test.rb', + 'test/integration/users_login_test.rb'] + end + watch('app/controllers/account_activations_controller.rb') do + 'test/integration/users_signup_test.rb' + end + watch(%r{app/views/users/*}) do + resource_tests('users') + + ['test/integration/microposts_interface_test.rb'] + end + watch('app/controllers/relationships_controller.rb') do + ['test/controllers/relationships_controller_test.rb', + 'test/integration/following_test.rb'] + end + end + + # 指定のリソースに対応する統合テストを返す + def integration_tests(resource = :all) + if resource == :all + Dir["test/integration/*"] + else + Dir["test/integration/#{resource}_*.rb"] + end + end + + # インターフェースが該当するすべてのテストを返す + def interface_tests + integration_tests << "test/controllers" + end + + # 指定のリソースに対応するコントローラのテストを返す + def controller_test(resource) + "test/controllers/#{resource}_controller_test.rb" + end + + # 指定のリソースに対応するすべてのテストを返す + def resource_tests(resource) + integration_tests(resource) << controller_test(resource) + end diff --git a/8_0/ch12/LICENSE b/8_0/ch12/LICENSE new file mode 100644 index 00000000..20622e0c --- /dev/null +++ b/8_0/ch12/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 GitHub & 2023 YassLab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/8_0/ch12/README.md b/8_0/ch12/README.md new file mode 100644 index 00000000..17ca733d --- /dev/null +++ b/8_0/ch12/README.md @@ -0,0 +1,35 @@ +# Ruby on Rails チュートリアルのサンプルアプリケーション + +これは、次の教材で作られたサンプルアプリケーションです。 +[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +(第8版) +[Michael Hartl](https://www.michaelhartl.com/) 著 + +## ライセンス + +[Ruby on Rails チュートリアル](https://railstutorial.jp/)内にある +ソースコードはMITライセンスとBeerwareライセンスのもとで公開されています。 +詳細は [LICENSE.md](LICENSE.md) をご覧ください。 + +## 使い方 + +このアプリケーションを動かす場合は、まずはリポジトリをフォークしてください。 + +フォークしたリポジトリで、「Code」から「Codespaces」タブに移動し、 +「Create codespace on main」をクリックすると環境構築がスタートします。 +Railsサーバーが立ち上がり、シンプルブラウザが表示されるまでしばらくお待ちください。 + +次に、データベースへのマイグレーションを実行します。 + +``` +$ rails db:migrate +``` + +最後に、テストを実行してうまく動いているかどうか確認してください。 + +``` +$ rails test +``` + +詳しくは、[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +を参考にしてください。 diff --git a/8_0/ch12/Rakefile b/8_0/ch12/Rakefile new file mode 100644 index 00000000..9a5ea738 --- /dev/null +++ b/8_0/ch12/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/8_0/ch12/app/assets/config/manifest.js b/8_0/ch12/app/assets/config/manifest.js new file mode 100644 index 00000000..ddd546a0 --- /dev/null +++ b/8_0/ch12/app/assets/config/manifest.js @@ -0,0 +1,4 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js diff --git a/8_0/ch12/app/assets/images/.keep b/8_0/ch12/app/assets/images/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/app/assets/images/rails.svg b/8_0/ch12/app/assets/images/rails.svg new file mode 100644 index 00000000..e1971850 --- /dev/null +++ b/8_0/ch12/app/assets/images/rails.svg @@ -0,0 +1,35 @@ + + + +rails-logo + + + + + + + + + + + + + + + + + + + + + diff --git a/8_0/ch12/app/assets/stylesheets/application.css b/8_0/ch12/app/assets/stylesheets/application.css new file mode 100644 index 00000000..288b9ab7 --- /dev/null +++ b/8_0/ch12/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/8_0/ch12/app/assets/stylesheets/custom.scss b/8_0/ch12/app/assets/stylesheets/custom.scss new file mode 100644 index 00000000..4fd609ce --- /dev/null +++ b/8_0/ch12/app/assets/stylesheets/custom.scss @@ -0,0 +1,227 @@ +@import "bootstrap-sprockets"; +@import "bootstrap"; + +/* mixins, variables, etc. */ + +$gray-medium-light: #eaeaea; + +@mixin box_sizing { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* universal */ + +body { + padding-top: 60px; +} + +section { + overflow: auto; +} + +textarea { + resize: vertical; +} + +.center { + text-align: center; + h1 { + margin-bottom: 10px; + } +} + +/* typography */ + +h1, h2, h3, h4, h5, h6 { + line-height: 1; +} + +h1 { + font-size: 3em; + letter-spacing: -2px; + margin-bottom: 30px; + text-align: center; +} + +h2 { + font-size: 1.2em; + letter-spacing: -1px; + margin-bottom: 30px; + text-align: center; + font-weight: normal; + color: $gray-light; +} + +p { + font-size: 1.1em; + line-height: 1.7em; +} + + +/* header */ + +#logo { + float: left; + margin-right: 10px; + font-size: 1.7em; + color: white; + text-transform: uppercase; + letter-spacing: -1px; + padding-top: 9px; + font-weight: bold; + &:hover { + color: white; + text-decoration: none; + } +} + +/* footer */ + +footer { + margin-top: 45px; + padding-top: 5px; + border-top: 1px solid $gray-medium-light; + color: $gray-light; + a { + color: $gray; + &:hover { + color: $gray-darker; + } + } + small { + float: left; + } + ul { + float: right; + list-style: none; + li { + float: left; + margin-left: 15px; + } + } +} +@media (max-width: 800px) { + footer { + small { + display: block; + float: none; + margin-bottom: 1em; + } + ul { + float: none; + padding: 0; + li { + float: none; + margin-left: 0; + } + } + } +} + +/* miscellaneous */ + +.debug_dump { + clear: both; + float: left; + width: 100%; + margin-top: 45px; +} + +/* sidebar */ + +aside { + section.user_info { + margin-top: 20px; + } + section { + padding: 10px 0; + margin-top: 20px; + &:first-child { + border: 0; + padding-top: 0; + } + span { + display: block; + margin-bottom: 3px; + line-height: 1; + } + h1 { + font-size: 1.4em; + text-align: left; + letter-spacing: -1px; + margin-bottom: 3px; + margin-top: 0px; + } + } +} + +.gravatar { + float: left; + margin-right: 10px; +} + +.gravatar_edit { + margin-top: 15px; +} + +/* forms */ + +input, textarea { + border: 1px solid #bbb; + width: 100%; + margin-bottom: 15px; + @include box_sizing; +} + +input { + height: auto !important; +} + +#error_explanation { + color: red; + ul { + color: red; + margin: 0 0 30px 0; + } +} + +.field_with_errors { + @extend .has-error; + .form-control { + color: $state-danger-text; + } +} + +.checkbox { + margin-top: -10px; + margin-bottom: 10px; + span { + margin-left: 20px; + font-weight: normal; + } +} + +#session_remember_me { + width: auto; + margin-left: 0; +} + +/* Dropdown menu */ + +.dropdown-menu.active { + display: block; +} + +/* Users index */ + +.users { + list-style: none; + margin: 0; + li { + overflow: auto; + padding: 10px 0; + border-bottom: 1px solid $gray-lighter; + } +} \ No newline at end of file diff --git a/8_0/ch12/app/assets/stylesheets/hello.css b/8_0/ch12/app/assets/stylesheets/hello.css new file mode 100644 index 00000000..497881a4 --- /dev/null +++ b/8_0/ch12/app/assets/stylesheets/hello.css @@ -0,0 +1,40 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.codespaces { + text-align: center; +} +.codespaces code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} +.codespaces-logo { + height: 40vmin; + pointer-events: none; +} +.codespaces-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} +.codespaces-link { + color: #61dafb; +} + +.heart { + color: #ff0000; +} +.small { + font-size: 0.75rem; +} diff --git a/8_0/ch12/app/channels/application_cable/channel.rb b/8_0/ch12/app/channels/application_cable/channel.rb new file mode 100644 index 00000000..d6726972 --- /dev/null +++ b/8_0/ch12/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/8_0/ch12/app/channels/application_cable/connection.rb b/8_0/ch12/app/channels/application_cable/connection.rb new file mode 100644 index 00000000..0ff5442f --- /dev/null +++ b/8_0/ch12/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/8_0/ch12/app/controllers/account_activations_controller.rb b/8_0/ch12/app/controllers/account_activations_controller.rb new file mode 100644 index 00000000..4985c73e --- /dev/null +++ b/8_0/ch12/app/controllers/account_activations_controller.rb @@ -0,0 +1,15 @@ +class AccountActivationsController < ApplicationController + + def edit + user = User.find_by(email: params[:email]) + if user && !user.activated? && user.authenticated?(:activation, params[:id]) + user.activate + log_in user + flash[:success] = "Account activated!" + redirect_to user + else + flash[:danger] = "Invalid activation link" + redirect_to root_url + end + end +end diff --git a/8_0/ch12/app/controllers/application_controller.rb b/8_0/ch12/app/controllers/application_controller.rb new file mode 100644 index 00000000..09faff3f --- /dev/null +++ b/8_0/ch12/app/controllers/application_controller.rb @@ -0,0 +1,3 @@ +class ApplicationController < ActionController::Base + include SessionsHelper +end diff --git a/8_0/ch12/app/controllers/concerns/.keep b/8_0/ch12/app/controllers/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/app/controllers/hello_controller.rb b/8_0/ch12/app/controllers/hello_controller.rb new file mode 100644 index 00000000..52037537 --- /dev/null +++ b/8_0/ch12/app/controllers/hello_controller.rb @@ -0,0 +1,4 @@ +class HelloController < ApplicationController + def index + end +end diff --git a/8_0/ch12/app/controllers/password_resets_controller.rb b/8_0/ch12/app/controllers/password_resets_controller.rb new file mode 100644 index 00000000..260165c3 --- /dev/null +++ b/8_0/ch12/app/controllers/password_resets_controller.rb @@ -0,0 +1,66 @@ +class PasswordResetsController < ApplicationController + before_action :get_user, only: [:edit, :update] + before_action :valid_user, only: [:edit, :update] + before_action :check_expiration, only: [:edit, :update] # (1)への対応 + + def new + end + + def create + @user = User.find_by(email: params[:password_reset][:email].downcase) + if @user + @user.create_reset_digest + @user.send_password_reset_email + flash[:info] = "Email sent with password reset instructions" + redirect_to root_url + else + flash.now[:danger] = "Email address not found" + render 'new', status: :unprocessable_entity + end + end + + def edit + end + + def update + if params[:user][:password].empty? # (3)への対応 + @user.errors.add(:password, "can't be empty") + render 'edit', status: :unprocessable_entity + elsif @user.update(user_params) # (4)への対応 + reset_session + log_in @user + flash[:success] = "Password has been reset." + redirect_to @user + else + render 'edit', status: :unprocessable_entity # (2)への対応 + end + end + + private + + def user_params + params.expect(user: [:password, :password_confirmation]) + end + + # beforeフィルタ + + def get_user + @user = User.find_by(email: params[:email]) + end + + # 有効なユーザーかどうか確認する + def valid_user + unless (@user && @user.activated? && + @user.authenticated?(:reset, params[:id])) + redirect_to root_url + end + end + + # トークンが期限切れかどうか確認する + def check_expiration + if @user.password_reset_expired? + flash[:danger] = "Password reset has expired." + redirect_to new_password_reset_url + end + end +end diff --git a/8_0/ch12/app/controllers/sessions_controller.rb b/8_0/ch12/app/controllers/sessions_controller.rb new file mode 100644 index 00000000..4fa0043a --- /dev/null +++ b/8_0/ch12/app/controllers/sessions_controller.rb @@ -0,0 +1,31 @@ +class SessionsController < ApplicationController + + def new + end + + def create + user = User.find_by(email: params[:session][:email].downcase) + if user && user.authenticate(params[:session][:password]) + if user.activated? + forwarding_url = session[:forwarding_url] + reset_session + params[:session][:remember_me] == '1' ? remember(user) : forget(user) + log_in user + redirect_to forwarding_url || user + else + message = "Account not activated. " + message += "Check your email for the activation link." + flash[:warning] = message + redirect_to root_url + end + else + flash.now[:danger] = 'Invalid email/password combination' + render 'new', status: :unprocessable_entity + end + end + + def destroy + log_out if logged_in? + redirect_to root_url, status: :see_other + end +end diff --git a/8_0/ch12/app/controllers/static_pages_controller.rb b/8_0/ch12/app/controllers/static_pages_controller.rb new file mode 100644 index 00000000..0673ad5e --- /dev/null +++ b/8_0/ch12/app/controllers/static_pages_controller.rb @@ -0,0 +1,14 @@ +class StaticPagesController < ApplicationController + + def home + end + + def help + end + + def about + end + + def contact + end +end diff --git a/8_0/ch12/app/controllers/users_controller.rb b/8_0/ch12/app/controllers/users_controller.rb new file mode 100644 index 00000000..05d245b3 --- /dev/null +++ b/8_0/ch12/app/controllers/users_controller.rb @@ -0,0 +1,75 @@ +class UsersController < ApplicationController + before_action :logged_in_user, only: [:index, :edit, :update, :destroy] + before_action :correct_user, only: [:edit, :update] + before_action :admin_user, only: :destroy + + def index + @users = User.paginate(page: params[:page]) + end + + def show + @user = User.find(params[:id]) + end + + def new + @user = User.new + end + + def create + @user = User.new(user_params) + if @user.save + @user.send_activation_email + flash[:info] = "Please check your email to activate your account." + redirect_to root_url + else + render 'new', status: :unprocessable_entity + end + end + + def edit + end + + def update + if @user.update(user_params) + flash[:success] = "Profile updated" + redirect_to @user + else + render 'edit', status: :unprocessable_entity + end + end + + def destroy + User.find(params[:id]).destroy + flash[:success] = "User deleted" + redirect_to users_url, status: :see_other + end + + private + + def user_params + params.expect(user: [:name, :email, :password, + :password_confirmation]) + end + + # beforeフィルタ + + # ログイン済みユーザーかどうか確認 + def logged_in_user + unless logged_in? + store_location + flash[:danger] = "Please log in." + redirect_to login_url, status: :see_other + end + end + + # 正しいユーザーかどうか確認 + def correct_user + @user = User.find(params[:id]) + redirect_to(root_url, status: :see_other) unless current_user?(@user) + end + + # 管理者かどうか確認 + def admin_user + redirect_to(root_url, status: :see_other) unless current_user.admin? + end +end diff --git a/8_0/ch12/app/helpers/account_activations_helper.rb b/8_0/ch12/app/helpers/account_activations_helper.rb new file mode 100644 index 00000000..c4d5ac72 --- /dev/null +++ b/8_0/ch12/app/helpers/account_activations_helper.rb @@ -0,0 +1,2 @@ +module AccountActivationsHelper +end diff --git a/8_0/ch12/app/helpers/application_helper.rb b/8_0/ch12/app/helpers/application_helper.rb new file mode 100644 index 00000000..708e4e48 --- /dev/null +++ b/8_0/ch12/app/helpers/application_helper.rb @@ -0,0 +1,12 @@ +module ApplicationHelper + + # ページごとの完全なタイトルを返します。 # コメント行 + def full_title(page_title = '') # メソッド定義とオプション引数 + base_title = "Ruby on Rails Tutorial Sample App" # 変数への代入 + if page_title.empty? # 論理値テスト + base_title # 暗黙の戻り値 + else + "#{page_title} | #{base_title}" # 文字列の結合 + end + end +end diff --git a/8_0/ch12/app/helpers/hello_codespaces_helper.rb b/8_0/ch12/app/helpers/hello_codespaces_helper.rb new file mode 100644 index 00000000..874a2ca2 --- /dev/null +++ b/8_0/ch12/app/helpers/hello_codespaces_helper.rb @@ -0,0 +1,2 @@ +module HelloCodespacesHelper +end diff --git a/8_0/ch12/app/helpers/password_resets_helper.rb b/8_0/ch12/app/helpers/password_resets_helper.rb new file mode 100644 index 00000000..0c9d96ec --- /dev/null +++ b/8_0/ch12/app/helpers/password_resets_helper.rb @@ -0,0 +1,2 @@ +module PasswordResetsHelper +end diff --git a/8_0/ch12/app/helpers/sessions_helper.rb b/8_0/ch12/app/helpers/sessions_helper.rb new file mode 100644 index 00000000..a38a35bd --- /dev/null +++ b/8_0/ch12/app/helpers/sessions_helper.rb @@ -0,0 +1,60 @@ +module SessionsHelper + + # 渡されたユーザーでログインする + def log_in(user) + session[:user_id] = user.id + # セッションリプレイ攻撃から保護する + # 詳しくは https://techracho.bpsinc.jp/hachi8833/2023_06_02/130443 を参照 + session[:session_token] = user.session_token + end + + # ユーザーを永続的セッションに保存する + def remember(user) + user.remember + cookies.permanent.encrypted[:user_id] = user.id + cookies.permanent[:remember_token] = user.remember_token + end + + # 現在ログイン中のユーザーを返す(いる場合) + def current_user + if (user_id = session[:user_id]) + user = User.find_by(id: user_id) + @current_user ||= user if session[:session_token] == user.session_token + elsif (user_id = cookies.encrypted[:user_id]) + user = User.find_by(id: user_id) + if user && user.authenticated?(:remember, cookies[:remember_token]) + log_in user + @current_user = user + end + end + end + + # 渡されたユーザーがカレントユーザーであればtrueを返す + def current_user?(user) + user && user == current_user + end + + # ユーザーがログインしていればtrue、その他ならfalseを返す + def logged_in? + !current_user.nil? + end + + # 永続的セッションを破棄する + def forget(user) + user.forget + cookies.delete(:user_id) + cookies.delete(:remember_token) + end + + # 現在のユーザーをログアウトする + def log_out + forget(current_user) + reset_session + @current_user = nil + end + + # アクセスしようとしたURLを保存する + def store_location + session[:forwarding_url] = request.original_url if request.get? + end +end diff --git a/8_0/ch12/app/helpers/static_pages_helper.rb b/8_0/ch12/app/helpers/static_pages_helper.rb new file mode 100644 index 00000000..2d63e79e --- /dev/null +++ b/8_0/ch12/app/helpers/static_pages_helper.rb @@ -0,0 +1,2 @@ +module StaticPagesHelper +end diff --git a/8_0/ch12/app/helpers/users_helper.rb b/8_0/ch12/app/helpers/users_helper.rb new file mode 100644 index 00000000..53790499 --- /dev/null +++ b/8_0/ch12/app/helpers/users_helper.rb @@ -0,0 +1,10 @@ +module UsersHelper + + # 引数で与えられたユーザーのGravatar画像を返す + def gravatar_for(user, options = { size: 80 }) + size = options[:size] + gravatar_id = Digest::MD5::hexdigest(user.email.downcase) + gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}" + image_tag(gravatar_url, alt: user.name, class: "gravatar") + end +end diff --git a/8_0/ch12/app/javascript/application.js b/8_0/ch12/app/javascript/application.js new file mode 100644 index 00000000..46ebafe8 --- /dev/null +++ b/8_0/ch12/app/javascript/application.js @@ -0,0 +1,5 @@ +// Configure your import map in config/importmap.rb. +// Read more: https://github.com/rails/importmap-rails +import "@hotwired/turbo-rails" +import "controllers" +import "custom/menu" diff --git a/8_0/ch12/app/javascript/controllers/application.js b/8_0/ch12/app/javascript/controllers/application.js new file mode 100644 index 00000000..1213e85c --- /dev/null +++ b/8_0/ch12/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/8_0/ch12/app/javascript/controllers/hello_controller.js b/8_0/ch12/app/javascript/controllers/hello_controller.js new file mode 100644 index 00000000..5975c078 --- /dev/null +++ b/8_0/ch12/app/javascript/controllers/hello_controller.js @@ -0,0 +1,7 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.element.textContent = "Hello World!" + } +} diff --git a/8_0/ch12/app/javascript/controllers/index.js b/8_0/ch12/app/javascript/controllers/index.js new file mode 100644 index 00000000..54ad4cad --- /dev/null +++ b/8_0/ch12/app/javascript/controllers/index.js @@ -0,0 +1,11 @@ +// Import and register all your controllers from the importmap under controllers/* + +import { application } from "controllers/application" + +// Eager load all controllers defined in the import map under controllers/**/*_controller +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) + +// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) +// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" +// lazyLoadControllersFrom("controllers", application) diff --git a/8_0/ch12/app/javascript/custom/menu.js b/8_0/ch12/app/javascript/custom/menu.js new file mode 100644 index 00000000..92e4a8dd --- /dev/null +++ b/8_0/ch12/app/javascript/custom/menu.js @@ -0,0 +1,22 @@ +// メニュー操作 + +// トグルリスナーを追加してクリックをリッスンする +document.addEventListener("turbo:load", function() { + let hamburger = document.querySelector("#hamburger"); + if (hamburger){ + hamburger.addEventListener("click", function(event) { + event.preventDefault(); + let menu = document.querySelector("#navbar-menu"); + menu.classList.toggle("collapse"); + }); + } + + let account = document.querySelector("#account"); + if (account) { + account.addEventListener("click", function(event) { + event.preventDefault(); + let menu = document.querySelector("#dropdown-menu"); + menu.classList.toggle("active"); + }); + } +}); diff --git a/8_0/ch12/app/jobs/application_job.rb b/8_0/ch12/app/jobs/application_job.rb new file mode 100644 index 00000000..d394c3d1 --- /dev/null +++ b/8_0/ch12/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/8_0/ch12/app/mailers/application_mailer.rb b/8_0/ch12/app/mailers/application_mailer.rb new file mode 100644 index 00000000..b166e6e2 --- /dev/null +++ b/8_0/ch12/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "Mailgunに登録したメールアドレス" + layout "mailer" +end diff --git a/8_0/ch12/app/mailers/user_mailer.rb b/8_0/ch12/app/mailers/user_mailer.rb new file mode 100644 index 00000000..4aa71dab --- /dev/null +++ b/8_0/ch12/app/mailers/user_mailer.rb @@ -0,0 +1,12 @@ +class UserMailer < ApplicationMailer + + def account_activation(user) + @user = user + mail to: user.email, subject: "Account activation" + end + + def password_reset(user) + @user = user + mail to: user.email, subject: "Password reset" + end +end diff --git a/8_0/ch12/app/models/application_record.rb b/8_0/ch12/app/models/application_record.rb new file mode 100644 index 00000000..b63caeb8 --- /dev/null +++ b/8_0/ch12/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/8_0/ch12/app/models/concerns/.keep b/8_0/ch12/app/models/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/app/models/user.rb b/8_0/ch12/app/models/user.rb new file mode 100644 index 00000000..e01787ff --- /dev/null +++ b/8_0/ch12/app/models/user.rb @@ -0,0 +1,90 @@ +class User < ApplicationRecord + attr_accessor :remember_token, :activation_token, :reset_token + before_save :downcase_email + before_create :create_activation_digest + validates :name, presence: true, length: { maximum: 50 } + VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i + validates :email, presence: true, length: { maximum: 255 }, + format: { with: VALID_EMAIL_REGEX }, + uniqueness: true + has_secure_password + validates :password, presence: true, length: { minimum: 6 }, allow_nil: true + + # 渡された文字列のハッシュ値を返す + def User.digest(string) + cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : + BCrypt::Engine.cost + BCrypt::Password.create(string, cost: cost) + end + + # ランダムなトークンを返す + def User.new_token + SecureRandom.urlsafe_base64 + end + + # 永続的セッションのためにユーザーをデータベースに記憶する + def remember + self.remember_token = User.new_token + update_attribute(:remember_digest, User.digest(remember_token)) + remember_digest + end + + # セッションハイジャック防止のためにセッショントークンを返す + # この記憶ダイジェストを再利用しているのは単に利便性のため + def session_token + remember_digest || remember + end + + # 渡されたトークンがダイジェストと一致したらtrueを返す + def authenticated?(attribute, token) + digest = send("#{attribute}_digest") + return false if digest.nil? + BCrypt::Password.new(digest).is_password?(token) + end + + # ユーザーのログイン情報を破棄する + def forget + update_attribute(:remember_digest, nil) + end + + # アカウントを有効にする + def activate + update_attribute(:activated, true) + update_attribute(:activated_at, Time.zone.now) + end + + # 有効化用のメールを送信する + def send_activation_email + UserMailer.account_activation(self).deliver_now + end + + # パスワード再設定の属性を設定する + def create_reset_digest + self.reset_token = User.new_token + update_attribute(:reset_digest, User.digest(reset_token)) + update_attribute(:reset_sent_at, Time.zone.now) + end + + # パスワード再設定のメールを送信する + def send_password_reset_email + UserMailer.password_reset(self).deliver_now + end + + # パスワード再設定の期限が切れている場合はtrueを返す + def password_reset_expired? + reset_sent_at < 2.hours.ago + end + + private + + # メールアドレスをすべて小文字にする + def downcase_email + self.email = email.downcase + end + + # 有効化トークンとダイジェストを作成および代入する + def create_activation_digest + self.activation_token = User.new_token + self.activation_digest = User.digest(activation_token) + end +end diff --git a/8_0/ch12/app/views/hello/index.html.erb b/8_0/ch12/app/views/hello/index.html.erb new file mode 100644 index 00000000..71661105 --- /dev/null +++ b/8_0/ch12/app/views/hello/index.html.erb @@ -0,0 +1,16 @@ +
    +
    + +

    + Codespaces + ♥️ + Railsチュートリアル +

    +

    + 🎓✨
    + ロゴ画像が表示されたらセットアップ完了です! +

    +
    +
    diff --git a/8_0/ch12/app/views/layouts/_footer.html.erb b/8_0/ch12/app/views/layouts/_footer.html.erb new file mode 100644 index 00000000..b6e31ff9 --- /dev/null +++ b/8_0/ch12/app/views/layouts/_footer.html.erb @@ -0,0 +1,13 @@ + diff --git a/8_0/ch12/app/views/layouts/_header.html.erb b/8_0/ch12/app/views/layouts/_header.html.erb new file mode 100644 index 00000000..8b07e211 --- /dev/null +++ b/8_0/ch12/app/views/layouts/_header.html.erb @@ -0,0 +1,39 @@ + diff --git a/8_0/ch12/app/views/layouts/application.html.erb b/8_0/ch12/app/views/layouts/application.html.erb new file mode 100644 index 00000000..eb23ef94 --- /dev/null +++ b/8_0/ch12/app/views/layouts/application.html.erb @@ -0,0 +1,24 @@ + + + + <%= full_title(yield(:title)) %> + + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + + + <%= render 'layouts/header' %> +
    + <% flash.each do |message_type, message| %> +
    <%= message %>
    + <% end %> + <%= yield %> + <%= render 'layouts/footer' %> + <%= debug(params) if Rails.env.development? %> +
    + + diff --git a/8_0/ch12/app/views/layouts/mailer.html.erb b/8_0/ch12/app/views/layouts/mailer.html.erb new file mode 100644 index 00000000..cbd34d2e --- /dev/null +++ b/8_0/ch12/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/8_0/ch12/app/views/layouts/mailer.text.erb b/8_0/ch12/app/views/layouts/mailer.text.erb new file mode 100644 index 00000000..37f0bddb --- /dev/null +++ b/8_0/ch12/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/8_0/ch12/app/views/password_resets/edit.html.erb b/8_0/ch12/app/views/password_resets/edit.html.erb new file mode 100644 index 00000000..1e9385d1 --- /dev/null +++ b/8_0/ch12/app/views/password_resets/edit.html.erb @@ -0,0 +1,20 @@ +<% provide(:title, 'Reset password') %> +

    Reset password

    + +
    +
    + <%= form_with(model: @user, url: password_reset_path(params[:id])) do |f| %> + <%= render 'shared/error_messages' %> + + <%= hidden_field_tag :email, @user.email %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :password_confirmation, "Confirmation" %> + <%= f.password_field :password_confirmation, class: 'form-control' %> + + <%= f.submit "Update password", class: "btn btn-primary" %> + <% end %> +
    +
    diff --git a/8_0/ch12/app/views/password_resets/new.html.erb b/8_0/ch12/app/views/password_resets/new.html.erb new file mode 100644 index 00000000..4a2753d8 --- /dev/null +++ b/8_0/ch12/app/views/password_resets/new.html.erb @@ -0,0 +1,13 @@ +<% provide(:title, "Forgot password") %> +

    Forgot password

    + +
    +
    + <%= form_with(url: password_resets_path, scope: :password_reset) do |f| %> + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.submit "Submit", class: "btn btn-primary" %> + <% end %> +
    +
    diff --git a/8_0/ch12/app/views/sessions/new.html.erb b/8_0/ch12/app/views/sessions/new.html.erb new file mode 100644 index 00000000..8ca43db4 --- /dev/null +++ b/8_0/ch12/app/views/sessions/new.html.erb @@ -0,0 +1,25 @@ +<% provide(:title, "Log in") %> +

    Log in

    + +
    +
    + <%= form_with(url: login_path, scope: :session) do |f| %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= link_to "(forgot password)", new_password_reset_path %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :remember_me, class: "checkbox inline" do %> + <%= f.check_box :remember_me %> + Remember me on this computer + <% end %> + + <%= f.submit "Log in", class: "btn btn-primary" %> + <% end %> + +

    New user? <%= link_to "Sign up now!", signup_path %>

    +
    +
    diff --git a/8_0/ch12/app/views/shared/_error_messages.html.erb b/8_0/ch12/app/views/shared/_error_messages.html.erb new file mode 100644 index 00000000..f80053e4 --- /dev/null +++ b/8_0/ch12/app/views/shared/_error_messages.html.erb @@ -0,0 +1,12 @@ +<% if @user.errors.any? %> +
    +
    + The form contains <%= pluralize(@user.errors.count, "error") %>. +
    + +
    +<% end %> diff --git a/8_0/ch12/app/views/static_pages/about.html.erb b/8_0/ch12/app/views/static_pages/about.html.erb new file mode 100644 index 00000000..dce8fef2 --- /dev/null +++ b/8_0/ch12/app/views/static_pages/about.html.erb @@ -0,0 +1,10 @@ +<% provide(:title, "About") %> +

    About

    +

    + Ruby on Rails Tutorial + is a book and + screencast + to teach web development with + Ruby on Rails. + This is the sample application for the tutorial. +

    diff --git a/8_0/ch12/app/views/static_pages/contact.html.erb b/8_0/ch12/app/views/static_pages/contact.html.erb new file mode 100644 index 00000000..4a5a35a6 --- /dev/null +++ b/8_0/ch12/app/views/static_pages/contact.html.erb @@ -0,0 +1,6 @@ +<% provide(:title, 'Contact') %> +

    Contact

    +

    + Contact the Ruby on Rails Tutorial about the sample app at the + contact page. +

    diff --git a/8_0/ch12/app/views/static_pages/help.html.erb b/8_0/ch12/app/views/static_pages/help.html.erb new file mode 100644 index 00000000..4d06919d --- /dev/null +++ b/8_0/ch12/app/views/static_pages/help.html.erb @@ -0,0 +1,9 @@ +<% provide(:title, "Help") %> +

    Help

    +

    + Get help on the Ruby on Rails Tutorial at the + Rails Tutorial Help page. + To get help on this sample app, see the + Ruby on Rails Tutorial + book. +

    diff --git a/8_0/ch12/app/views/static_pages/home.html.erb b/8_0/ch12/app/views/static_pages/home.html.erb new file mode 100644 index 00000000..f87607de --- /dev/null +++ b/8_0/ch12/app/views/static_pages/home.html.erb @@ -0,0 +1,14 @@ +
    +

    Welcome to the Sample App

    + +

    + This is the home page for the + Ruby on Rails Tutorial + sample application. +

    + + <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %> +
    + +<%= link_to image_tag("rails.svg", alt: "Rails logo", width: "200"), + "https://rubyonrails.org/" %> diff --git a/8_0/ch12/app/views/user_mailer/account_activation.html.erb b/8_0/ch12/app/views/user_mailer/account_activation.html.erb new file mode 100644 index 00000000..c95644ae --- /dev/null +++ b/8_0/ch12/app/views/user_mailer/account_activation.html.erb @@ -0,0 +1,10 @@ +

    Sample App

    + +

    Hi <%= @user.name %>,

    + +

    +Welcome to the Sample App! Click on the link below to activate your account: +

    + +<%= link_to "Activate", edit_account_activation_url(@user.activation_token, + email: @user.email) %> diff --git a/8_0/ch12/app/views/user_mailer/account_activation.text.erb b/8_0/ch12/app/views/user_mailer/account_activation.text.erb new file mode 100644 index 00000000..fecf2be3 --- /dev/null +++ b/8_0/ch12/app/views/user_mailer/account_activation.text.erb @@ -0,0 +1,5 @@ +Hi <%= @user.name %>, + +Welcome to the Sample App! Click on the link below to activate your account: + +<%= edit_account_activation_url(@user.activation_token, email: @user.email) %> diff --git a/8_0/ch12/app/views/user_mailer/password_reset.html.erb b/8_0/ch12/app/views/user_mailer/password_reset.html.erb new file mode 100644 index 00000000..d5a6c379 --- /dev/null +++ b/8_0/ch12/app/views/user_mailer/password_reset.html.erb @@ -0,0 +1,13 @@ +

    Password reset

    + +

    To reset your password click the link below:

    + +<%= link_to "Reset password", edit_password_reset_url(@user.reset_token, + email: @user.email) %> + +

    This link will expire in two hours.

    + +

    +If you did not request your password to be reset, please ignore this email and +your password will stay as it is. +

    diff --git a/8_0/ch12/app/views/user_mailer/password_reset.text.erb b/8_0/ch12/app/views/user_mailer/password_reset.text.erb new file mode 100644 index 00000000..d44d7fc9 --- /dev/null +++ b/8_0/ch12/app/views/user_mailer/password_reset.text.erb @@ -0,0 +1,8 @@ +To reset your password click the link below: + +<%= edit_password_reset_url(@user.reset_token, email: @user.email) %> + +This link will expire in two hours. + +If you did not request your password to be reset, please ignore this email and +your password will stay as it is. diff --git a/8_0/ch12/app/views/users/_user.html.erb b/8_0/ch12/app/views/users/_user.html.erb new file mode 100644 index 00000000..cf66c339 --- /dev/null +++ b/8_0/ch12/app/views/users/_user.html.erb @@ -0,0 +1,8 @@ +
  • + <%= gravatar_for user, size: 50 %> + <%= link_to user.name, user %> + <% if current_user.admin? && !current_user?(user) %> + | <%= link_to "delete", user, data: { "turbo-method": :delete, + turbo_confirm: "You sure?" } %> + <% end %> +
  • diff --git a/8_0/ch12/app/views/users/edit.html.erb b/8_0/ch12/app/views/users/edit.html.erb new file mode 100644 index 00000000..ae672af0 --- /dev/null +++ b/8_0/ch12/app/views/users/edit.html.erb @@ -0,0 +1,29 @@ +<% provide(:title, "Edit user") %> +

    Update your profile

    + +
    +
    + <%= form_with(model: @user) do |f| %> + <%= render 'shared/error_messages' %> + + <%= f.label :name %> + <%= f.text_field :name, class: 'form-control' %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :password_confirmation, "Confirmation" %> + <%= f.password_field :password_confirmation, class: 'form-control' %> + + <%= f.submit "Save changes", class: "btn btn-primary" %> + <% end %> + +
    + <%= gravatar_for @user %> + change +
    +
    +
    diff --git a/8_0/ch12/app/views/users/index.html.erb b/8_0/ch12/app/views/users/index.html.erb new file mode 100644 index 00000000..5b6dbaeb --- /dev/null +++ b/8_0/ch12/app/views/users/index.html.erb @@ -0,0 +1,10 @@ +<% provide(:title, 'All users') %> +

    All users

    + +<%= will_paginate %> + + + +<%= will_paginate %> diff --git a/8_0/ch12/app/views/users/new.html.erb b/8_0/ch12/app/views/users/new.html.erb new file mode 100644 index 00000000..79e39f80 --- /dev/null +++ b/8_0/ch12/app/views/users/new.html.erb @@ -0,0 +1,24 @@ +<% provide(:title, 'Sign up') %> +

    Sign up

    + +
    +
    + <%= form_with(model: @user) do |f| %> + <%= render 'shared/error_messages' %> + + <%= f.label :name %> + <%= f.text_field :name, class: 'form-control' %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :password_confirmation, "Confirmation" %> + <%= f.password_field :password_confirmation, class: 'form-control' %> + + <%= f.submit "Create my account", class: "btn btn-primary" %> + <% end %> +
    +
    diff --git a/8_0/ch12/app/views/users/show.html.erb b/8_0/ch12/app/views/users/show.html.erb new file mode 100644 index 00000000..4fda5f89 --- /dev/null +++ b/8_0/ch12/app/views/users/show.html.erb @@ -0,0 +1,11 @@ +<% provide(:title, @user.name) %> +
    + +
    diff --git a/8_0/ch12/bin/bundle b/8_0/ch12/bin/bundle new file mode 100755 index 00000000..981e650b --- /dev/null +++ b/8_0/ch12/bin/bundle @@ -0,0 +1,114 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + requirement = bundler_gem_version.approximate_recommendation + + return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0") + + requirement += ".a" if bundler_gem_version.prerelease? + + requirement + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/8_0/ch12/bin/dev b/8_0/ch12/bin/dev new file mode 100755 index 00000000..5f91c205 --- /dev/null +++ b/8_0/ch12/bin/dev @@ -0,0 +1,2 @@ +#!/usr/bin/env ruby +exec "./bin/rails", "server", *ARGV diff --git a/8_0/ch12/bin/importmap b/8_0/ch12/bin/importmap new file mode 100755 index 00000000..36502ab1 --- /dev/null +++ b/8_0/ch12/bin/importmap @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require_relative "../config/application" +require "importmap/commands" diff --git a/8_0/ch12/bin/rails b/8_0/ch12/bin/rails new file mode 100755 index 00000000..efc03774 --- /dev/null +++ b/8_0/ch12/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/8_0/ch12/bin/rake b/8_0/ch12/bin/rake new file mode 100755 index 00000000..4fbf10b9 --- /dev/null +++ b/8_0/ch12/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/8_0/ch12/bin/render-build.sh b/8_0/ch12/bin/render-build.sh new file mode 100644 index 00000000..b0156aa6 --- /dev/null +++ b/8_0/ch12/bin/render-build.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# exit on error +set -o errexit +bundle install +bundle exec rails assets:precompile +bundle exec rails assets:clean +DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rails db:migrate:reset +bundle exec rails db:seed diff --git a/8_0/ch12/bin/rubocop b/8_0/ch12/bin/rubocop new file mode 100755 index 00000000..40330c0f --- /dev/null +++ b/8_0/ch12/bin/rubocop @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +# explicit rubocop config increases performance slightly while avoiding config confusion. +ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) + +load Gem.bin_path("rubocop", "rubocop") diff --git a/8_0/ch12/bin/setup b/8_0/ch12/bin/setup new file mode 100755 index 00000000..be3db3c0 --- /dev/null +++ b/8_0/ch12/bin/setup @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +require "fileutils" + +APP_ROOT = File.expand_path("..", __dir__) + +def system!(*args) + system(*args, exception: true) +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts "== Installing dependencies ==" + system("bundle check") || system!("bundle install") + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + puts "\n== Removing old logs and tempfiles ==" + system! "bin/rails log:clear tmp:clear" + + unless ARGV.include?("--skip-server") + puts "\n== Starting development server ==" + STDOUT.flush # flush the output before exec(2) so that it displays + exec "bin/dev" + end +end diff --git a/8_0/ch12/config.ru b/8_0/ch12/config.ru new file mode 100644 index 00000000..4a3c09a6 --- /dev/null +++ b/8_0/ch12/config.ru @@ -0,0 +1,6 @@ +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/8_0/ch12/config/application.rb b/8_0/ch12/config/application.rb new file mode 100644 index 00000000..0461e72f --- /dev/null +++ b/8_0/ch12/config/application.rb @@ -0,0 +1,27 @@ +require_relative "boot" + +require "rails/all" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module SampleApp + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 8.0 + + # Please, add to the `ignore` list any other `lib` subdirectories that do + # not contain `.rb` files, or that should not be reloaded or eager loaded. + # Common ones are `templates`, `generators`, or `middleware`, for example. + config.autoload_lib(ignore: %w[assets tasks]) + + # Configuration for the application, engines, and railties goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + # + # config.time_zone = "Central Time (US & Canada)" + # config.eager_load_paths << Rails.root.join("extras") + end +end diff --git a/8_0/ch12/config/boot.rb b/8_0/ch12/config/boot.rb new file mode 100644 index 00000000..988a5ddc --- /dev/null +++ b/8_0/ch12/config/boot.rb @@ -0,0 +1,4 @@ +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "bundler/setup" # Set up gems listed in the Gemfile. +require "bootsnap/setup" # Speed up boot time by caching expensive operations. diff --git a/8_0/ch12/config/cable.yml b/8_0/ch12/config/cable.yml new file mode 100644 index 00000000..ae7a2256 --- /dev/null +++ b/8_0/ch12/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: codespaces_try_rails_production diff --git a/8_0/ch12/config/credentials.yml.enc b/8_0/ch12/config/credentials.yml.enc new file mode 100644 index 00000000..339d64d2 --- /dev/null +++ b/8_0/ch12/config/credentials.yml.enc @@ -0,0 +1 @@ +NCs9M8uakg1CDYJENuGaipzymY8uG6i5gzghsy4npa3gYmGfYKPHMnEcJHCVHlM3gxDNJBteUqkRCxHQ5WQId8hzgxoQCL5RX7rtQ8XUzkJwgSUjKvbCrG5atjKkN9XuL91is5vtEN5W7vEz3qxN7Rp33QbNFiDfLs71F3zHVDYEMi6Dy1QMhVCa1v/tyRjBu/5m37RuiYF5FzWJnh4LhexSVr7Agm4LRLLN6qLCpoAe6D3TPHHBdtu7Pvy8jlrEfnnkUJ61wrj8+kOL4uLtpFShdYKhDkoTjQk7TCOgWyifnHAXazkBZoJZ1QYv/tZ9/ZoHvMEQ2dtGHlyTX8fbKtI13d2CFjGaDNKvRuurxE8dbeyiTfbycvve46+cz9OTlmhh0SGt24R1WV6GBKoDUeoHrIiBvW5qmKka--dQ0rodxmfgFLg2y/--t2x0gQjh8FYlk8v79vLSNg== \ No newline at end of file diff --git a/8_0/ch12/config/database.yml b/8_0/ch12/config/database.yml new file mode 100644 index 00000000..fcba57f1 --- /dev/null +++ b/8_0/ch12/config/database.yml @@ -0,0 +1,25 @@ +# SQLite. Versions 3.8.0 and up are supported. +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem "sqlite3" +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/8_0/ch12/config/environment.rb b/8_0/ch12/config/environment.rb new file mode 100644 index 00000000..cac53157 --- /dev/null +++ b/8_0/ch12/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/8_0/ch12/config/environments/development.rb b/8_0/ch12/config/environments/development.rb new file mode 100644 index 00000000..6e0241dd --- /dev/null +++ b/8_0/ch12/config/environments/development.rb @@ -0,0 +1,90 @@ +require 'active_support/core_ext/integer/time' + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Make code changes take effect immediately without server restart. + config.enable_reloading = true + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing. + config.server_timing = true + + # Add the following line to disable forgery_protection_origin_check + config.action_controller.forgery_protection_origin_check = false + + # Enable/disable Action Controller caching. By default Action Controller caching is disabled. + # Run rails dev:cache to toggle Action Controller caching. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + config.public_file_server.headers = { 'cache-control' => "public, max-age=#{2.days.to_i}" } + else + config.action_controller.perform_caching = false + end + + # Change to :null_store to avoid any caching. + config.cache_store = :memory_store + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Make template changes take effect immediately. + config.action_mailer.perform_caching = false + + # Set localhost to be used by links generated in mailer templates. + host = 'example.com' # ここをコピペすると失敗します。自分の環境のホストに変えてください。 + # クラウドIDEの場合は以下をお使いください + config.action_mailer.default_url_options = { host: host, protocol: 'https' } + # localhostで開発している場合は以下をお使いください + # config.action_mailer.default_url_options = { host: host, protocol: 'http' } + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Append comments with runtime information tags to SQL queries in logs. + config.active_record.query_log_tags_enabled = true + + # Highlight code that enqueued background job in logs. + config.active_job.verbose_enqueue_logs = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true + + # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. + # config.generators.apply_rubocop_autocorrect_after_generate! + + pf_domain = ENV['GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN'] + config.action_dispatch.default_headers = { + 'X-Frame-Options' => "ALLOW-FROM #{pf_domain}" + } + + # Allow requests from our preview domain. + pf_host = "#{ENV['CODESPACE_NAME']}-3000.#{pf_domain}" + config.hosts << pf_host + + config.action_cable.allowed_request_origins = ["https://#{pf_host}"] +end diff --git a/8_0/ch12/config/environments/production.rb b/8_0/ch12/config/environments/production.rb new file mode 100644 index 00000000..6ed5c3fa --- /dev/null +++ b/8_0/ch12/config/environments/production.rb @@ -0,0 +1,94 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.enable_reloading = false + + # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). + config.eager_load = true + + # Full error reports are disabled. + config.consider_all_requests_local = false + + # Turn on fragment caching in view templates. + config.action_controller.perform_caching = true + + # Cache assets for far-future expiry since they are all digest stamped. + config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" } + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = "http://assets.example.com" + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Assume all access to the app is happening through a SSL-terminating reverse proxy. + config.assume_ssl = true + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = true + + # Skip http-to-https redirect for the default health check endpoint. + # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } + + # Log to STDOUT with the current request id as a default log tag. + config.log_tags = [ :request_id ] + config.logger = ActiveSupport::TaggedLogging.logger(STDOUT) + + # Change to "debug" to log everything (including potentially personally-identifiable information!) + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + + # Prevent health checks from clogging up the logs. + config.silence_healthcheck_path = "/up" + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Replace the default in-process memory cache store with a durable alternative. + # config.cache_store = :mem_cache_store + + # Replace the default in-process and non-durable queuing backend for Active Job. + # config.active_job.queue_adapter = :resque + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + config.action_mailer.raise_delivery_errors = true + host = 'https://<あなたのRenderアプリ名>.onrender.com' + config.action_mailer.default_url_options = { host: host } + # Mailgun API設定 + config.action_mailer.delivery_method = :mailgun + config.action_mailer.mailgun_settings = { + api_key: ENV['MAILGUN_API_KEY'], + domain: ENV['MAILGUN_DOMAIN'] + } + + # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. + # config.action_mailer.smtp_settings = { + # user_name: Rails.application.credentials.dig(:smtp, :user_name), + # password: Rails.application.credentials.dig(:smtp, :password), + # address: "smtp.example.com", + # port: 587, + # authentication: :plain + # } + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Only use :id for inspections in production. + config.active_record.attributes_for_inspect = [ :id ] + + # Enable DNS rebinding protection and other `Host` header attacks. + # config.hosts = [ + # "example.com", # Allow requests from example.com + # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` + # ] + # + # Skip DNS rebinding protection for the default health check endpoint. + # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } +end diff --git a/8_0/ch12/config/environments/test.rb b/8_0/ch12/config/environments/test.rb new file mode 100644 index 00000000..0ebe3b4e --- /dev/null +++ b/8_0/ch12/config/environments/test.rb @@ -0,0 +1,53 @@ +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # While tests run files are not watched, reloading is not necessary. + config.enable_reloading = false + + # Eager loading loads your entire application. When running a single test locally, + # this is usually not necessary, and can slow down your test suite. However, it's + # recommended that you enable it in continuous integration systems to ensure eager + # loading is working properly before deploying your code. + config.eager_load = ENV["CI"].present? + + # Configure public file server for tests with cache-control for performance. + config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } + + # Show full error reports. + config.consider_all_requests_local = true + config.cache_store = :null_store + + # Render exception templates for rescuable exceptions and raise for other exceptions. + config.action_dispatch.show_exceptions = :none + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :test + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true +end diff --git a/8_0/ch12/config/importmap.rb b/8_0/ch12/config/importmap.rb new file mode 100644 index 00000000..caf3ed06 --- /dev/null +++ b/8_0/ch12/config/importmap.rb @@ -0,0 +1,8 @@ +# Pin npm packages by running ./bin/importmap + +pin "application", preload: true +pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true +pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true +pin_all_from "app/javascript/controllers", under: "controllers" +pin_all_from "app/javascript/custom", under: "custom" diff --git a/8_0/ch12/config/initializers/assets.rb b/8_0/ch12/config/initializers/assets.rb new file mode 100644 index 00000000..48732442 --- /dev/null +++ b/8_0/ch12/config/initializers/assets.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = "1.0" + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path diff --git a/8_0/ch12/config/initializers/content_security_policy.rb b/8_0/ch12/config/initializers/content_security_policy.rb new file mode 100644 index 00000000..b3076b38 --- /dev/null +++ b/8_0/ch12/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header + +# Rails.application.configure do +# config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end +# +# # Generate session nonces for permitted importmap, inline scripts, and inline styles. +# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } +# config.content_security_policy_nonce_directives = %w(script-src style-src) +# +# # Report violations without enforcing the policy. +# # config.content_security_policy_report_only = true +# end diff --git a/8_0/ch12/config/initializers/filter_parameter_logging.rb b/8_0/ch12/config/initializers/filter_parameter_logging.rb new file mode 100644 index 00000000..c0b717f7 --- /dev/null +++ b/8_0/ch12/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. +# Use this to limit dissemination of sensitive information. +# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. +Rails.application.config.filter_parameters += [ + :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc +] diff --git a/8_0/ch12/config/initializers/inflections.rb b/8_0/ch12/config/initializers/inflections.rb new file mode 100644 index 00000000..3860f659 --- /dev/null +++ b/8_0/ch12/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym "RESTful" +# end diff --git a/8_0/ch12/config/initializers/new_framework_defaults_8_0.rb b/8_0/ch12/config/initializers/new_framework_defaults_8_0.rb new file mode 100644 index 00000000..92efa951 --- /dev/null +++ b/8_0/ch12/config/initializers/new_framework_defaults_8_0.rb @@ -0,0 +1,30 @@ +# Be sure to restart your server when you modify this file. +# +# This file eases your Rails 8.0 framework defaults upgrade. +# +# Uncomment each configuration one by one to switch to the new default. +# Once your application is ready to run with all new defaults, you can remove +# this file and set the `config.load_defaults` to `8.0`. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. +# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html + +### +# Specifies whether `to_time` methods preserve the UTC offset of their receivers or preserves the timezone. +# If set to `:zone`, `to_time` methods will use the timezone of their receivers. +# If set to `:offset`, `to_time` methods will use the UTC offset. +# If `false`, `to_time` methods will convert to the local system UTC offset instead. +#++ +# Rails.application.config.active_support.to_time_preserves_timezone = :zone + +### +# When both `If-Modified-Since` and `If-None-Match` are provided by the client +# only consider `If-None-Match` as specified by RFC 7232 Section 6. +# If set to `false` both conditions need to be satisfied. +#++ +# Rails.application.config.action_dispatch.strict_freshness = true + +### +# Set `Regexp.timeout` to `1`s by default to improve security over Regexp Denial-of-Service attacks. +#++ +# Regexp.timeout = 1 diff --git a/8_0/ch12/config/initializers/permissions_policy.rb b/8_0/ch12/config/initializers/permissions_policy.rb new file mode 100644 index 00000000..00f64d71 --- /dev/null +++ b/8_0/ch12/config/initializers/permissions_policy.rb @@ -0,0 +1,11 @@ +# Define an application-wide HTTP permissions policy. For further +# information see https://developers.google.com/web/updates/2018/06/feature-policy +# +# Rails.application.config.permissions_policy do |f| +# f.camera :none +# f.gyroscope :none +# f.microphone :none +# f.usb :none +# f.fullscreen :self +# f.payment :self, "https://secure.example.com" +# end diff --git a/8_0/ch12/config/locales/en.yml b/8_0/ch12/config/locales/en.yml new file mode 100644 index 00000000..8ca56fc7 --- /dev/null +++ b/8_0/ch12/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# "true": "foo" +# +# To learn more, please read the Rails Internationalization guide +# available at https://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/8_0/ch12/config/puma.rb b/8_0/ch12/config/puma.rb new file mode 100644 index 00000000..a248513b --- /dev/null +++ b/8_0/ch12/config/puma.rb @@ -0,0 +1,41 @@ +# This configuration file will be evaluated by Puma. The top-level methods that +# are invoked here are part of Puma's configuration DSL. For more information +# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. +# +# Puma starts a configurable number of processes (workers) and each process +# serves each request in a thread from an internal thread pool. +# +# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You +# should only set this value when you want to run 2 or more workers. The +# default is already 1. +# +# The ideal number of threads per worker depends both on how much time the +# application spends waiting for IO operations and on how much you wish to +# prioritize throughput over latency. +# +# As a rule of thumb, increasing the number of threads will increase how much +# traffic a given process can handle (throughput), but due to CRuby's +# Global VM Lock (GVL) it has diminishing returns and will degrade the +# response time (latency) of the application. +# +# The default is set to 3 threads as it's deemed a decent compromise between +# throughput and latency for the average Rails application. +# +# Any libraries that use a connection pool or another resource pool should +# be configured to provide at least as many connections as the number of +# threads. This includes Active Record's `pool` parameter in `database.yml`. +threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +port ENV.fetch("PORT", 3000) + +# Allow puma to be restarted by `bin/rails restart` command. +plugin :tmp_restart + +# Run the Solid Queue supervisor inside of Puma for single-server deployments +plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] + +# Specify the PID file. Defaults to tmp/pids/server.pid in development. +# In other environments, only set the PID file if requested. +pidfile ENV["PIDFILE"] if ENV["PIDFILE"] diff --git a/8_0/ch12/config/routes.rb b/8_0/ch12/config/routes.rb new file mode 100644 index 00000000..8bc92fad --- /dev/null +++ b/8_0/ch12/config/routes.rb @@ -0,0 +1,13 @@ +Rails.application.routes.draw do + root "static_pages#home" + get "/help", to: "static_pages#help" + get "/about", to: "static_pages#about" + get "/contact", to: "static_pages#contact" + get "/signup", to: "users#new" + get "/login", to: "sessions#new" + post "/login", to: "sessions#create" + delete "/logout", to: "sessions#destroy" + resources :users + resources :account_activations, only: [:edit] + resources :password_resets, only: [:new, :create, :edit, :update] +end diff --git a/8_0/ch12/config/storage.yml b/8_0/ch12/config/storage.yml new file mode 100644 index 00000000..4942ab66 --- /dev/null +++ b/8_0/ch12/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket-<%= Rails.env %> + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket-<%= Rails.env %> + +# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name-<%= Rails.env %> + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/8_0/ch12/db/migrate/20260512080056_create_users.rb b/8_0/ch12/db/migrate/20260512080056_create_users.rb new file mode 100644 index 00000000..3b80eb00 --- /dev/null +++ b/8_0/ch12/db/migrate/20260512080056_create_users.rb @@ -0,0 +1,10 @@ +class CreateUsers < ActiveRecord::Migration[8.0] + def change + create_table :users do |t| + t.string :name + t.string :email + + t.timestamps + end + end +end diff --git a/8_0/ch12/db/migrate/20260512080840_add_index_to_users_email.rb b/8_0/ch12/db/migrate/20260512080840_add_index_to_users_email.rb new file mode 100644 index 00000000..a8e4ed8e --- /dev/null +++ b/8_0/ch12/db/migrate/20260512080840_add_index_to_users_email.rb @@ -0,0 +1,5 @@ +class AddIndexToUsersEmail < ActiveRecord::Migration[8.0] + def change + add_index :users, :email, unique: true + end +end diff --git a/8_0/ch12/db/migrate/20260512081511_add_password_digest_to_users.rb b/8_0/ch12/db/migrate/20260512081511_add_password_digest_to_users.rb new file mode 100644 index 00000000..81c7c9e0 --- /dev/null +++ b/8_0/ch12/db/migrate/20260512081511_add_password_digest_to_users.rb @@ -0,0 +1,5 @@ +class AddPasswordDigestToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :password_digest, :string + end +end diff --git a/8_0/ch12/db/migrate/20260626064202_add_remember_digest_to_users.rb b/8_0/ch12/db/migrate/20260626064202_add_remember_digest_to_users.rb new file mode 100644 index 00000000..7a464448 --- /dev/null +++ b/8_0/ch12/db/migrate/20260626064202_add_remember_digest_to_users.rb @@ -0,0 +1,5 @@ +class AddRememberDigestToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :remember_digest, :string + end +end diff --git a/8_0/ch12/db/migrate/20260626075635_add_admin_to_users.rb b/8_0/ch12/db/migrate/20260626075635_add_admin_to_users.rb new file mode 100644 index 00000000..c1f08cf5 --- /dev/null +++ b/8_0/ch12/db/migrate/20260626075635_add_admin_to_users.rb @@ -0,0 +1,5 @@ +class AddAdminToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :admin, :boolean, default: false + end +end diff --git a/8_0/ch12/db/migrate/20260630012638_add_activation_to_users.rb b/8_0/ch12/db/migrate/20260630012638_add_activation_to_users.rb new file mode 100644 index 00000000..be38e387 --- /dev/null +++ b/8_0/ch12/db/migrate/20260630012638_add_activation_to_users.rb @@ -0,0 +1,7 @@ +class AddActivationToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :activation_digest, :string + add_column :users, :activated, :boolean, default: false + add_column :users, :activated_at, :datetime + end +end diff --git a/8_0/ch12/db/migrate/20260630015608_add_reset_to_users.rb b/8_0/ch12/db/migrate/20260630015608_add_reset_to_users.rb new file mode 100644 index 00000000..b5e1c928 --- /dev/null +++ b/8_0/ch12/db/migrate/20260630015608_add_reset_to_users.rb @@ -0,0 +1,6 @@ +class AddResetToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :reset_digest, :string + add_column :users, :reset_sent_at, :datetime + end +end diff --git a/8_0/ch12/db/schema.rb b/8_0/ch12/db/schema.rb new file mode 100644 index 00000000..6eabed47 --- /dev/null +++ b/8_0/ch12/db/schema.rb @@ -0,0 +1,29 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[8.0].define(version: 2026_06_30_015608) do + create_table "users", force: :cascade do |t| + t.string "name" + t.string "email" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "password_digest" + t.string "remember_digest" + t.boolean "admin", default: false + t.string "activation_digest" + t.boolean "activated", default: false + t.datetime "activated_at" + t.string "reset_digest" + t.datetime "reset_sent_at" + t.index ["email"], name: "index_users_on_email", unique: true + end +end diff --git a/8_0/ch12/db/seeds.rb b/8_0/ch12/db/seeds.rb new file mode 100644 index 00000000..28f895b6 --- /dev/null +++ b/8_0/ch12/db/seeds.rb @@ -0,0 +1,21 @@ +# メインのサンプルユーザーを1人作成する +User.create!(name: "Example User", + email: "example@railstutorial.org", + password: "foobar", + password_confirmation: "foobar", + admin: true, + activated: true, + activated_at: Time.zone.now) + +# 追加のユーザーをまとめて生成する +99.times do |n| + name = Faker::Name.name + email = "example-#{n+1}@railstutorial.org" + password = "password" + User.create!(name: name, + email: email, + password: password, + password_confirmation: password, + activated: true, + activated_at: Time.zone.now) +end diff --git a/8_0/ch12/lib/assets/.keep b/8_0/ch12/lib/assets/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/lib/tasks/.keep b/8_0/ch12/lib/tasks/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/log/.keep b/8_0/ch12/log/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/public/400.html b/8_0/ch12/public/400.html new file mode 100644 index 00000000..282dbc8c --- /dev/null +++ b/8_0/ch12/public/400.html @@ -0,0 +1,114 @@ + + + + + + + The server cannot process the request due to a client error (400 Bad Request) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    The server cannot process the request due to a client error. Please check the request and try again. If you’re the application owner check the logs for more information.

    +
    +
    + + + + diff --git a/8_0/ch12/public/404.html b/8_0/ch12/public/404.html new file mode 100644 index 00000000..c0670bc8 --- /dev/null +++ b/8_0/ch12/public/404.html @@ -0,0 +1,114 @@ + + + + + + + The page you were looking for doesn’t exist (404 Not found) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    The page you were looking for doesn’t exist. You may have mistyped the address or the page may have moved. If you’re the application owner check the logs for more information.

    +
    +
    + + + + diff --git a/8_0/ch12/public/406-unsupported-browser.html b/8_0/ch12/public/406-unsupported-browser.html new file mode 100644 index 00000000..9532a9cc --- /dev/null +++ b/8_0/ch12/public/406-unsupported-browser.html @@ -0,0 +1,114 @@ + + + + + + + Your browser is not supported (406 Not Acceptable) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    Your browser is not supported.
    Please upgrade your browser to continue.

    +
    +
    + + + + diff --git a/8_0/ch12/public/422.html b/8_0/ch12/public/422.html new file mode 100644 index 00000000..8bcf0601 --- /dev/null +++ b/8_0/ch12/public/422.html @@ -0,0 +1,114 @@ + + + + + + + The change you wanted was rejected (422 Unprocessable Entity) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    The change you wanted was rejected. Maybe you tried to change something you didn’t have access to. If you’re the application owner check the logs for more information.

    +
    +
    + + + + diff --git a/8_0/ch12/public/500.html b/8_0/ch12/public/500.html new file mode 100644 index 00000000..d77718c3 --- /dev/null +++ b/8_0/ch12/public/500.html @@ -0,0 +1,114 @@ + + + + + + + We’re sorry, but something went wrong (500 Internal Server Error) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    We’re sorry, but something went wrong.
    If you’re the application owner check the logs for more information.

    +
    +
    + + + + diff --git a/8_0/ch12/public/apple-touch-icon-precomposed.png b/8_0/ch12/public/apple-touch-icon-precomposed.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/public/apple-touch-icon.png b/8_0/ch12/public/apple-touch-icon.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/public/favicon.ico b/8_0/ch12/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/public/icon.png b/8_0/ch12/public/icon.png new file mode 100644 index 00000000..c4c9dbfb Binary files /dev/null and b/8_0/ch12/public/icon.png differ diff --git a/8_0/ch12/public/icon.svg b/8_0/ch12/public/icon.svg new file mode 100644 index 00000000..04b34bf8 --- /dev/null +++ b/8_0/ch12/public/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/8_0/ch12/public/railstutorial.png b/8_0/ch12/public/railstutorial.png new file mode 100644 index 00000000..70835e6b Binary files /dev/null and b/8_0/ch12/public/railstutorial.png differ diff --git a/8_0/ch12/public/robots.txt b/8_0/ch12/public/robots.txt new file mode 100644 index 00000000..c19f78ab --- /dev/null +++ b/8_0/ch12/public/robots.txt @@ -0,0 +1 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/8_0/ch12/storage/.keep b/8_0/ch12/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/test/application_system_test_case.rb b/8_0/ch12/test/application_system_test_case.rb new file mode 100644 index 00000000..d19212ab --- /dev/null +++ b/8_0/ch12/test/application_system_test_case.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/8_0/ch12/test/channels/application_cable/connection_test.rb b/8_0/ch12/test/channels/application_cable/connection_test.rb new file mode 100644 index 00000000..800405f1 --- /dev/null +++ b/8_0/ch12/test/channels/application_cable/connection_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase + # test "connects with cookies" do + # cookies.signed[:user_id] = 42 + # + # connect + # + # assert_equal connection.user_id, "42" + # end +end diff --git a/8_0/ch12/test/controllers/.keep b/8_0/ch12/test/controllers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/test/controllers/account_activations_controller_test.rb b/8_0/ch12/test/controllers/account_activations_controller_test.rb new file mode 100644 index 00000000..bcd21995 --- /dev/null +++ b/8_0/ch12/test/controllers/account_activations_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class AccountActivationsControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/8_0/ch12/test/controllers/hello_codespaces_controller_test.rb b/8_0/ch12/test/controllers/hello_codespaces_controller_test.rb new file mode 100644 index 00000000..54353ea6 --- /dev/null +++ b/8_0/ch12/test/controllers/hello_codespaces_controller_test.rb @@ -0,0 +1,8 @@ +require "test_helper" + +class HelloCodespacesControllerTest < ActionDispatch::IntegrationTest + # test "should get index" do + # get "/" + # assert_response :success + # end +end diff --git a/8_0/ch12/test/controllers/sessions_controller_test.rb b/8_0/ch12/test/controllers/sessions_controller_test.rb new file mode 100644 index 00000000..8cc3f29e --- /dev/null +++ b/8_0/ch12/test/controllers/sessions_controller_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +class SessionsControllerTest < ActionDispatch::IntegrationTest + + test "should get new" do + get login_path + assert_response :success + end +end diff --git a/8_0/ch12/test/controllers/static_pages_controller_test.rb b/8_0/ch12/test/controllers/static_pages_controller_test.rb new file mode 100644 index 00000000..847856a3 --- /dev/null +++ b/8_0/ch12/test/controllers/static_pages_controller_test.rb @@ -0,0 +1,28 @@ +require "test_helper" + +class StaticPagesControllerTest < ActionDispatch::IntegrationTest + + test "should get home" do + get root_path + assert_response :success + assert_select "title", "Ruby on Rails Tutorial Sample App" + end + + test "should get help" do + get help_path + assert_response :success + assert_select "title", "Help | Ruby on Rails Tutorial Sample App" + end + + test "should get about" do + get about_path + assert_response :success + assert_select "title", "About | Ruby on Rails Tutorial Sample App" + end + + test "should get contact" do + get contact_path + assert_response :success + assert_select "title", "Contact | Ruby on Rails Tutorial Sample App" + end +end diff --git a/8_0/ch12/test/controllers/users_controller_test.rb b/8_0/ch12/test/controllers/users_controller_test.rb new file mode 100644 index 00000000..ffd2ec99 --- /dev/null +++ b/8_0/ch12/test/controllers/users_controller_test.rb @@ -0,0 +1,64 @@ +require "test_helper" + +class UsersControllerTest < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + @other_user = users(:archer) + end + + test "should get new" do + get signup_path + assert_response :success + end + + test "should redirect index when not logged in" do + get users_path + assert_redirected_to login_url + end + + test "should redirect edit when not logged in" do + get edit_user_path(@user) + assert_not flash.empty? + assert_redirected_to login_url + end + + test "should redirect update when not logged in" do + patch user_path(@user), params: { user: { name: @user.name, + email: @user.email } } + assert_not flash.empty? + assert_redirected_to login_url + end + + test "should redirect edit when logged in as wrong user" do + log_in_as(@other_user) + get edit_user_path(@user) + assert flash.empty? + assert_redirected_to root_url + end + + test "should redirect update when logged in as wrong user" do + log_in_as(@other_user) + patch user_path(@user), params: { user: { name: @user.name, + email: @user.email } } + assert flash.empty? + assert_redirected_to root_url + end + + test "should redirect destroy when not logged in" do + assert_no_difference 'User.count' do + delete user_path(@user) + end + assert_response :see_other + assert_redirected_to login_url + end + + test "should redirect destroy when logged in as a non-admin" do + log_in_as(@other_user) + assert_no_difference 'User.count' do + delete user_path(@user) + end + assert_response :see_other + assert_redirected_to root_url + end +end diff --git a/8_0/ch12/test/fixtures/files/.keep b/8_0/ch12/test/fixtures/files/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/test/fixtures/users.yml b/8_0/ch12/test/fixtures/users.yml new file mode 100644 index 00000000..02f3d49a --- /dev/null +++ b/8_0/ch12/test/fixtures/users.yml @@ -0,0 +1,37 @@ +michael: + name: Michael Example + email: michael@example.com + password_digest: <%= User.digest('password') %> + admin: true + activated: true + activated_at: <%= Time.zone.now %> + +archer: + name: Sterling Archer + email: duchess@example.gov + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> + +lana: + name: Lana Kane + email: hands@example.gov + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> + +malory: + name: Malory Archer + email: boss@example.gov + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> + +<% 30.times do |n| %> +user_<%= n %>: + name: <%= "User #{n}" %> + email: <%= "user-#{n}@example.com" %> + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> +<% end %> diff --git a/8_0/ch12/test/helpers/.keep b/8_0/ch12/test/helpers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/test/helpers/sessions_helper_test.rb b/8_0/ch12/test/helpers/sessions_helper_test.rb new file mode 100644 index 00000000..735ff17a --- /dev/null +++ b/8_0/ch12/test/helpers/sessions_helper_test.rb @@ -0,0 +1,19 @@ +require "test_helper" + +class SessionsHelperTest < ActionView::TestCase + + def setup + @user = users(:michael) + remember(@user) + end + + test "current_user returns right user when session is nil" do + assert_equal @user, current_user + assert is_logged_in? + end + + test "current_user returns nil when remember digest is wrong" do + @user.update_attribute(:remember_digest, User.digest(User.new_token)) + assert_nil current_user + end +end diff --git a/8_0/ch12/test/integration/.keep b/8_0/ch12/test/integration/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/test/integration/password_resets_test.rb b/8_0/ch12/test/integration/password_resets_test.rb new file mode 100644 index 00000000..2daf9c61 --- /dev/null +++ b/8_0/ch12/test/integration/password_resets_test.rb @@ -0,0 +1,98 @@ +require "test_helper" + +class PasswordResets < ActionDispatch::IntegrationTest + + def setup + ActionMailer::Base.deliveries.clear + end +end + +class ForgotPasswordFormTest < PasswordResets + + test "password reset path" do + get new_password_reset_path + assert_template 'password_resets/new' + assert_select 'input[name=?]', 'password_reset[email]' + end + + test "reset path with invalid email" do + post password_resets_path, params: { password_reset: { email: "" } } + assert_response :unprocessable_entity + assert_not flash.empty? + assert_template 'password_resets/new' + end +end + +class PasswordResetForm < PasswordResets + + def setup + super + @user = users(:michael) + post password_resets_path, + params: { password_reset: { email: @user.email } } + @reset_user = assigns(:user) + end +end + +class PasswordFormTest < PasswordResetForm + + test "reset with valid email" do + assert_not_equal @user.reset_digest, @reset_user.reset_digest + assert_equal 1, ActionMailer::Base.deliveries.size + assert_not flash.empty? + assert_redirected_to root_url + end + + test "reset with wrong email" do + get edit_password_reset_path(@reset_user.reset_token, email: "") + assert_redirected_to root_url + end + + test "reset with inactive user" do + @reset_user.toggle!(:activated) + get edit_password_reset_path(@reset_user.reset_token, + email: @reset_user.email) + assert_redirected_to root_url + end + + test "reset with right email but wrong token" do + get edit_password_reset_path('wrong token', email: @reset_user.email) + assert_redirected_to root_url + end + + test "reset with right email and right token" do + get edit_password_reset_path(@reset_user.reset_token, + email: @reset_user.email) + assert_template 'password_resets/edit' + assert_select "input[name=email][type=hidden][value=?]", @reset_user.email + end +end + +class PasswordUpdateTest < PasswordResetForm + + test "update with invalid password and confirmation" do + patch password_reset_path(@reset_user.reset_token), + params: { email: @reset_user.email, + user: { password: "foobaz", + password_confirmation: "barquux" } } + assert_select 'div#error_explanation' + end + + test "update with empty password" do + patch password_reset_path(@reset_user.reset_token), + params: { email: @reset_user.email, + user: { password: "", + password_confirmation: "" } } + assert_select 'div#error_explanation' + end + + test "update with valid password and confirmation" do + patch password_reset_path(@reset_user.reset_token), + params: { email: @reset_user.email, + user: { password: "foobaz", + password_confirmation: "foobaz" } } + assert is_logged_in? + assert_not flash.empty? + assert_redirected_to @reset_user + end +end diff --git a/8_0/ch12/test/integration/site_layout_test.rb b/8_0/ch12/test/integration/site_layout_test.rb new file mode 100644 index 00000000..0f7ca649 --- /dev/null +++ b/8_0/ch12/test/integration/site_layout_test.rb @@ -0,0 +1,13 @@ +require "test_helper" + +class SiteLayoutTest < ActionDispatch::IntegrationTest + + test "layout links" do + get root_path + assert_template 'static_pages/home' + assert_select "a[href=?]", root_path, count: 2 + assert_select "a[href=?]", help_path + assert_select "a[href=?]", about_path + assert_select "a[href=?]", contact_path + end +end diff --git a/8_0/ch12/test/integration/users_edit_test.rb b/8_0/ch12/test/integration/users_edit_test.rb new file mode 100644 index 00000000..23ca6b68 --- /dev/null +++ b/8_0/ch12/test/integration/users_edit_test.rb @@ -0,0 +1,37 @@ +require "test_helper" + +class UsersEditTest < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + end + + test "unsuccessful edit" do + log_in_as(@user) + get edit_user_path(@user) + assert_template 'users/edit' + patch user_path(@user), params: { user: { name: "", + email: "foo@invalid", + password: "foo", + password_confirmation: "bar" } } + + assert_template 'users/edit' + end + + test "successful edit with friendly forwarding" do + get edit_user_path(@user) + log_in_as(@user) + assert_redirected_to edit_user_url(@user) + name = "Foo Bar" + email = "foo@bar.com" + patch user_path(@user), params: { user: { name: name, + email: email, + password: "", + password_confirmation: "" } } + assert_not flash.empty? + assert_redirected_to @user + @user.reload + assert_equal name, @user.name + assert_equal email, @user.email + end +end diff --git a/8_0/ch12/test/integration/users_index_test.rb b/8_0/ch12/test/integration/users_index_test.rb new file mode 100644 index 00000000..63bb8d9e --- /dev/null +++ b/8_0/ch12/test/integration/users_index_test.rb @@ -0,0 +1,34 @@ +require "test_helper" + +class UsersIndexTest < ActionDispatch::IntegrationTest + + def setup + @admin = users(:michael) + @non_admin = users(:archer) + end + + test "index as admin including pagination and delete links" do + log_in_as(@admin) + get users_path + assert_template 'users/index' + assert_select 'div.pagination' + first_page_of_users = User.paginate(page: 1) + first_page_of_users.each do |user| + assert_select 'a[href=?]', user_path(user), text: user.name + unless user == @admin + assert_select 'a[href=?]', user_path(user), text: 'delete' + end + end + assert_difference 'User.count', -1 do + delete user_path(@non_admin) + assert_response :see_other + assert_redirected_to users_url + end + end + + test "index as non-admin" do + log_in_as(@non_admin) + get users_path + assert_select 'a', text: 'delete', count: 0 + end +end diff --git a/8_0/ch12/test/integration/users_login_test.rb b/8_0/ch12/test/integration/users_login_test.rb new file mode 100644 index 00000000..a9e70eaa --- /dev/null +++ b/8_0/ch12/test/integration/users_login_test.rb @@ -0,0 +1,96 @@ +require "test_helper" + +class UsersLogin < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + end +end + +class InvalidPasswordTest < UsersLogin + + test "login path" do + get login_path + assert_template 'sessions/new' + end + + test "login with valid email/invalid password" do + post login_path, params: { session: { email: @user.email, + password: "invalid" } } + assert_not is_logged_in? + assert_template 'sessions/new' + assert_not flash.empty? + get root_path + assert flash.empty? + end +end + +class ValidLogin < UsersLogin + + def setup + super + post login_path, params: { session: { email: @user.email, + password: 'password' } } + end +end + +class ValidLoginTest < ValidLogin + + test "valid login" do + assert is_logged_in? + assert_redirected_to @user + end + + test "redirect after login" do + follow_redirect! + assert_template 'users/show' + assert_select "a[href=?]", login_path, count: 0 + assert_select "a[href=?]", logout_path + assert_select "a[href=?]", user_path(@user) + end +end + +class Logout < ValidLogin + + def setup + super + delete logout_path + end +end + +class LogoutTest < Logout + + test "successful logout" do + assert_not is_logged_in? + assert_response :see_other + assert_redirected_to root_url + end + + test "redirect after logout" do + follow_redirect! + assert_select "a[href=?]", login_path + assert_select "a[href=?]", logout_path, count: 0 + assert_select "a[href=?]", user_path(@user), count: 0 + end + + test "should still work after logout in second window" do + delete logout_path + assert_redirected_to root_url + end +end + +class RememberingTest < UsersLogin + + test "login with remembering" do + log_in_as(@user, remember_me: '1') + assert_not cookies[:remember_token].blank? + end + + test "login without remembering" do + # Cookieを保存してログイン + log_in_as(@user, remember_me: '1') + # Cookieが削除されていることを検証してからログイン + log_in_as(@user, remember_me: '0') + assert cookies[:remember_token].blank? + end +end diff --git a/8_0/ch12/test/integration/users_signup_test.rb b/8_0/ch12/test/integration/users_signup_test.rb new file mode 100644 index 00000000..92172d43 --- /dev/null +++ b/8_0/ch12/test/integration/users_signup_test.rb @@ -0,0 +1,73 @@ +require "test_helper" + +class UsersSignup < ActionDispatch::IntegrationTest + + def setup + ActionMailer::Base.deliveries.clear + end +end + +class UsersSignupTest < UsersSignup + + test "invalid signup information" do + assert_no_difference 'User.count' do + post users_path, params: { user: { name: "", + email: "user@invalid", + password: "foo", + password_confirmation: "bar" } } + end + assert_response :unprocessable_entity + assert_template 'users/new' + assert_select 'div#error_explanation' + assert_select 'div.field_with_errors' + end + + test "valid signup information with account activation" do + assert_difference 'User.count', 1 do + post users_path, params: { user: { name: "Example User", + email: "user@example.com", + password: "password", + password_confirmation: "password" } } + end + assert_equal 1, ActionMailer::Base.deliveries.size + end +end + +class AccountActivationTest < UsersSignup + + def setup + super + post users_path, params: { user: { name: "Example User", + email: "user@example.com", + password: "password", + password_confirmation: "password" } } + @user = assigns(:user) + end + + test "should not be activated" do + assert_not @user.activated? + end + + test "should not be able to log in before account activation" do + log_in_as(@user) + assert_not is_logged_in? + end + + test "should not be able to log in with invalid activation token" do + get edit_account_activation_path("invalid token", email: @user.email) + assert_not is_logged_in? + end + + test "should not be able to log in with invalid email" do + get edit_account_activation_path(@user.activation_token, email: 'wrong') + assert_not is_logged_in? + end + + test "should log in successfully with valid activation token and email" do + get edit_account_activation_path(@user.activation_token, email: @user.email) + assert @user.reload.activated? + follow_redirect! + assert_template 'users/show' + assert is_logged_in? + end +end diff --git a/8_0/ch12/test/mailers/.keep b/8_0/ch12/test/mailers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/test/mailers/previews/user_mailer_preview.rb b/8_0/ch12/test/mailers/previews/user_mailer_preview.rb new file mode 100644 index 00000000..86da64f1 --- /dev/null +++ b/8_0/ch12/test/mailers/previews/user_mailer_preview.rb @@ -0,0 +1,19 @@ +# Preview all emails at http://localhost:3000/rails/mailers/user_mailer +class UserMailerPreview < ActionMailer::Preview + + # Preview this email at + # http://localhost:3000/rails/mailers/user_mailer/account_activation + def account_activation + user = User.first + user.activation_token = User.new_token + UserMailer.account_activation(user) + end + + # Preview this email at + # http://localhost:3000/rails/mailers/user_mailer/password_reset + def password_reset + user = User.first + user.reset_token = User.new_token + UserMailer.password_reset(user) + end +end diff --git a/8_0/ch12/test/mailers/user_mailer_test.rb b/8_0/ch12/test/mailers/user_mailer_test.rb new file mode 100644 index 00000000..d927329d --- /dev/null +++ b/8_0/ch12/test/mailers/user_mailer_test.rb @@ -0,0 +1,27 @@ +require "test_helper" + +class UserMailerTest < ActionMailer::TestCase + + test "account_activation" do + user = users(:michael) + user.activation_token = User.new_token + mail = UserMailer.account_activation(user) + assert_equal "Account activation", mail.subject + assert_equal [user.email], mail.to + assert_equal ["Mailgunに登録したメールアドレス"], mail.from + assert_match user.name, mail.body.encoded + assert_match user.activation_token, mail.body.encoded + assert_match CGI.escape(user.email), mail.body.encoded + end + + test "password_reset" do + user = users(:michael) + user.reset_token = User.new_token + mail = UserMailer.password_reset(user) + assert_equal "Password reset", mail.subject + assert_equal [user.email], mail.to + assert_equal ["Mailgunに登録したメールアドレス"], mail.from + assert_match user.reset_token, mail.body.encoded + assert_match CGI.escape(user.email), mail.body.encoded + end +end diff --git a/8_0/ch12/test/models/.keep b/8_0/ch12/test/models/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/test/models/user_test.rb b/8_0/ch12/test/models/user_test.rb new file mode 100644 index 00000000..f9730c50 --- /dev/null +++ b/8_0/ch12/test/models/user_test.rb @@ -0,0 +1,71 @@ +require "test_helper" + +class UserTest < ActiveSupport::TestCase + + def setup + @user = User.new(name: "Example User", email: "user@example.com", + password: "foobar", password_confirmation: "foobar") + end + + test "should be valid" do + assert @user.valid? + end + + test "name should be present" do + @user.name = " " + assert_not @user.valid? + end + + test "email should be present" do + @user.email = " " + assert_not @user.valid? + end + + test "name should not be too long" do + @user.name = "a" * 51 + assert_not @user.valid? + end + + test "email should not be too long" do + @user.email = "a" * 244 + "@example.com" + assert_not @user.valid? + end + + test "email validation should accept valid addresses" do + valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org + first.last@foo.jp alice+bob@baz.cn] + valid_addresses.each do |valid_address| + @user.email = valid_address + assert @user.valid?, "#{valid_address.inspect} should be valid" + end + end + + test "email validation should reject invalid addresses" do + invalid_addresses = %w[user@example,com user_at_foo.org user.name@example. + foo@bar_baz.com foo@bar+baz.com] + invalid_addresses.each do |invalid_address| + @user.email = invalid_address + assert_not @user.valid?, "#{invalid_address.inspect} should be invalid" + end + end + + test "email addresses should be unique" do + duplicate_user = @user.dup + @user.save + assert_not duplicate_user.valid? + end + + test "password should be present (nonblank)" do + @user.password = @user.password_confirmation = " " * 6 + assert_not @user.valid? + end + + test "password should have a minimum length" do + @user.password = @user.password_confirmation = "a" * 5 + assert_not @user.valid? + end + + test "authenticated? should return false for a user with nil digest" do + assert_not @user.authenticated?(:remember, '') + end +end diff --git a/8_0/ch12/test/system/.keep b/8_0/ch12/test/system/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/test/test_helper.rb b/8_0/ch12/test/test_helper.rb new file mode 100644 index 00000000..5fbcafd0 --- /dev/null +++ b/8_0/ch12/test/test_helper.rb @@ -0,0 +1,33 @@ +ENV["RAILS_ENV"] ||= "test" +require_relative "../config/environment" +require "rails/test_help" +require "minitest/reporters" +Minitest::Reporters.use! + +class ActiveSupport::TestCase + # 指定のワーカー数でテストを並列実行する + parallelize(workers: :number_of_processors) + + # test/fixtures/*.ymlにあるすべてのfixtureをセットアップする + fixtures :all + + # テストユーザーがログイン中の場合にtrueを返す + def is_logged_in? + !session[:user_id].nil? + end + + # テストユーザーとしてログインする + def log_in_as(user) + session[:user_id] = user.id + end +end + +class ActionDispatch::IntegrationTest + + # テストユーザーとしてログインする + def log_in_as(user, password: 'password', remember_me: '1') + post login_path, params: { session: { email: user.email, + password: password, + remember_me: remember_me } } + end +end \ No newline at end of file diff --git a/8_0/ch12/tmp/.keep b/8_0/ch12/tmp/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/tmp/pids/.keep b/8_0/ch12/tmp/pids/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/tmp/storage/.keep b/8_0/ch12/tmp/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/vendor/.keep b/8_0/ch12/vendor/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch12/vendor/javascript/.keep b/8_0/ch12/vendor/javascript/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/.devcontainer/devcontainer.json b/8_0/ch13/.devcontainer/devcontainer.json new file mode 100644 index 00000000..e7243217 --- /dev/null +++ b/8_0/ch13/.devcontainer/devcontainer.json @@ -0,0 +1,105 @@ +// Specifications: https://containers.dev/implementors/json_reference/ +// Format details: https://aka.ms/devcontainer.json +// Config options: https://github.com/microsoft/vscode-dev-containers/tree/main/containers/ruby +{ + "name": "railstutorial", + + // Universal is well-customized image for Codespaces: + // https://hub.docker.com/_/microsoft-devcontainers-universal + //"image": "mcr.microsoft.com/devcontainers/universal:2", + //"features": { + // "ghcr.io/devcontainers/features/ruby:1": {} + //}, + + // Use Ruby image if you want to pin Ruby version like '3.2' + // https://github.com/devcontainers/images/tree/main/src/ruby + "image": "mcr.microsoft.com/devcontainers/ruby:3.2", + //"workspaceFolder": "/railstutorial", => Container build failed + + // Enable learners to choose an affordable spec, starting at minimum one. + //"hostRequirements": { + // "cpus": 2, + // "memory": "4gb", + // "storage": "32gb" + //}, + + "waitFor": "onCreateCommand", + //"onCreateCommand": "", + "onCreateCommand": "gem install solargraph -v '0.58.2' -N", + //"onCreateCommand": "gem install solargraph -v '0.50.0' -N && gem install ruby-lsp -N", + //"onCreateCommand": "gem install ruby-lsp -N", + //# => Solargraph gem not found. Run `gem install solargraph` or update your Gemfile. + "updateContentCommand": "bundle install", + "postCreateCommand": "", + "postAttachCommand": { + "server": "rails server" + }, + "customizations": { + "codespaces": { + "openFiles": [ + "app/views/hello/index.html.erb" + ] + }, + "vscode": { + "extensions": [ + "GitHub.codespaces", + "Shopify.ruby-lsp", // https://github.com/Shopify/ruby-lsp + "castwide.solargraph" // https://github.com/castwide/vscode-solargraph + //"rebornix.Ruby", // https://github.com/rubyide/vscode-ruby (Deprecated) + + ], + "settings": { + // General settings for Codespaces (VS Code) + //"ruby.useLanguageServer": true , + //"ruby.format": "rubocop", + //"ruby.lint": { "rubocop": true }, + //"ruby.intellisense": "rubyLocate", + "editor.tabSize": 2, + "editor.formatOnSave": false, // Disable onSave to show diff edited by learners only + "editor.formatOnType": false, // Disable onType for the same reason above + "editor.insertSpaces": true, // Use spaces, not tabs, to avoid errors for learners + "editor.renderWhitespace": "none", + "[ruby]": { + "editor.defaultFormatter": "castwide.solargraph", + "editor.semanticHighlighting.enabled": true // Enable semantic highlighting + }, + "files.associations": { "*.erb": "erb" }, + "emmet.includeLanguages": { "erb": "html" }, + + // Settings for Solargraph + // https://github.com/castwide/solargraph + "solargraph.useBundler": false, + "solargraph.diagnostics": false, + "solargraph.formatting": true, // Use Ctrl+Shift+P->Format to format + "solargraph.autoformat": false, + "solargraph.definitions": true, + "solargraph.completion": true, + "solargraph.references": true, + "solargraph.symbols": true, + "solargraph.rename": true, + "solargraph.hover": true, + + // Settings for Ruby LSP + // https://github.com/Shopify/ruby-lsp + "rubyLsp.rubyVersionManager": { + "identifier": "none" + }, + "rubyLsp.formatter": "none", + "rubyLsp.enabledFeatures": { + "diagnostics": false, + "formatting": false + } + } + } + }, + "remoteEnv": { + "EDITOR": "code --wait" + }, + "portsAttributes": { + "3000": { + "label": "Application", + "onAutoForward": "openPreview" + } + }, + "forwardPorts": [3000] +} diff --git a/8_0/ch13/.devcontainer/icon.svg b/8_0/ch13/.devcontainer/icon.svg new file mode 100644 index 00000000..f8444a4a --- /dev/null +++ b/8_0/ch13/.devcontainer/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/8_0/ch13/.gitattributes b/8_0/ch13/.gitattributes new file mode 100644 index 00000000..31eeee0b --- /dev/null +++ b/8_0/ch13/.gitattributes @@ -0,0 +1,7 @@ +# See https://git-scm.com/docs/gitattributes for more about git attribute files. + +# Mark the database schema as having been generated. +db/schema.rb linguist-generated + +# Mark any vendored files as having been vendored. +vendor/* linguist-vendored diff --git a/8_0/ch13/.gitignore b/8_0/ch13/.gitignore new file mode 100644 index 00000000..e1ef53ef --- /dev/null +++ b/8_0/ch13/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-* + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore uploaded files in development. +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/ +!/tmp/storage/.keep + +/public/assets + +# Ignore master key for decrypting credentials and more. +/config/master.key + +# Ignore history file used by IRB for interactive Ruby. +.irb_history + +dump.rdb diff --git a/8_0/ch13/.irbrc b/8_0/ch13/.irbrc new file mode 100644 index 00000000..3737e036 --- /dev/null +++ b/8_0/ch13/.irbrc @@ -0,0 +1 @@ +IRB.conf[:COMPLETOR] = :type # default is :regexp diff --git a/8_0/ch13/.solargraph.yml b/8_0/ch13/.solargraph.yml new file mode 100644 index 00000000..76ac24a6 --- /dev/null +++ b/8_0/ch13/.solargraph.yml @@ -0,0 +1,32 @@ +--- +include: +- "**/*.rb" +exclude: +- spec/**/* +- test/**/* +- vendor/**/* +- ".bundle/**/*" +require: +- actioncable +- actionmailer +- actionpack +- actionview +- activejob +- activemodel +- activerecord +- activestorage +- activesupport +domains: [] +reporters: +- rubocop +- require_not_found +- typecheck +formatter: + rubocop: + cops: safe + except: [] + only: [] + extra_args: [] +require_paths: [] +plugins: [] +max_files: 5000 diff --git a/8_0/ch13/.vscode/settings.json b/8_0/ch13/.vscode/settings.json new file mode 100644 index 00000000..cf85f519 --- /dev/null +++ b/8_0/ch13/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.minimap.enabled": false +} diff --git a/8_0/ch13/Gemfile b/8_0/ch13/Gemfile new file mode 100644 index 00000000..d32a17e0 --- /dev/null +++ b/8_0/ch13/Gemfile @@ -0,0 +1,53 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '3.2.10' + +gem 'active_storage_validations', '0.9.8' +gem 'bcrypt' , '3.1.18' +gem 'bootsnap' , '1.16.0', require: false +gem 'bootstrap-sass' , '3.4.1' +gem 'bootstrap-will_paginate' , '1.0.0' +gem 'concurrent-ruby' , '1.3.4' +gem 'faker' , '2.21.0' +gem 'image_processing' , '1.12.2' +gem 'importmap-rails' , '1.1.5' +gem 'jbuilder' , '2.14.1' +gem 'puma' , '6.6.1' +gem 'rails' , '8.0.2.1' +gem 'sassc-rails' , '2.1.2' +gem 'sprockets-rails' , '3.4.2' +gem 'sqlite3' , '2.7.3' +gem 'stimulus-rails' , '1.2.1' +gem 'turbo-rails' , '1.4.0' +gem 'will_paginate' , '3.3.1' + +group :development, :test do + gem 'debug', '1.7.1', platforms: %i[mri mingw x64_mingw] + gem 'reline', '0.5.10' +end + +group :development do + gem 'irb', '1.15.2' + gem 'repl_type_completor', '0.1.10' + gem 'solargraph', '0.58.2' + gem 'web-console', '4.2.0' +end + +group :test do + gem 'capybara', '3.38.0' + gem 'guard', '2.18.0' + gem 'guard-minitest', '2.4.6' + gem 'minitest', '5.18.0' + gem 'minitest-reporters', '1.6.0' + gem 'rails-controller-testing', '1.0.5' + gem 'selenium-webdriver', '4.8.3' + gem 'webdrivers', '5.2.0' +end + +group :production do + gem 'mailgun-ruby', "1.3.10" +end + +# Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります +# gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] diff --git a/8_0/ch13/Gemfile.lock b/8_0/ch13/Gemfile.lock new file mode 100644 index 00000000..9aeecd76 --- /dev/null +++ b/8_0/ch13/Gemfile.lock @@ -0,0 +1,480 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + actionmailer (8.0.2.1) + actionpack (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.0.2.1) + actionview (= 8.0.2.1) + activesupport (= 8.0.2.1) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.0.2.1) + actionpack (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (8.0.2.1) + activesupport (= 8.0.2.1) + builder (~> 3.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + active_storage_validations (0.9.8) + activejob (>= 5.2.0) + activemodel (>= 5.2.0) + activestorage (>= 5.2.0) + activesupport (>= 5.2.0) + activejob (8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.3.6) + activemodel (8.0.2.1) + activesupport (= 8.0.2.1) + activerecord (8.0.2.1) + activemodel (= 8.0.2.1) + activesupport (= 8.0.2.1) + timeout (>= 0.4.0) + activestorage (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activesupport (= 8.0.2.1) + marcel (~> 1.0) + activesupport (8.0.2.1) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + ansi (1.5.0) + ast (2.4.3) + autoprefixer-rails (10.4.21.0) + execjs (~> 2) + backport (1.2.0) + base64 (0.3.0) + bcrypt (3.1.18) + benchmark (0.4.1) + bigdecimal (3.2.3) + bindex (0.8.1) + bootsnap (1.16.0) + msgpack (~> 1.2) + bootstrap-sass (3.4.1) + autoprefixer-rails (>= 5.2.1) + sassc (>= 2.0.0) + bootstrap-will_paginate (1.0.0) + will_paginate + builder (3.3.0) + capybara (3.38.0) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + coderay (1.1.3) + concurrent-ruby (1.3.4) + connection_pool (2.5.4) + crass (1.0.6) + date (3.4.1) + debug (1.7.1) + irb (>= 1.5.0) + reline (>= 0.3.1) + diff-lcs (1.6.2) + drb (2.2.3) + erb (5.0.2) + erubi (1.13.1) + execjs (2.10.1) + faker (2.21.0) + i18n (>= 1.8.11, < 2) + faraday (2.14.3) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-multipart (1.1.1) + multipart-post (~> 2.0) + faraday-net_http (3.4.4) + net-http (~> 0.5) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) + formatador (1.2.0) + reline + globalid (1.2.1) + activesupport (>= 6.1) + guard (2.18.0) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.13.0) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-minitest (2.4.6) + guard-compat (~> 1.2) + minitest (>= 3.0) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + image_processing (1.12.2) + mini_magick (>= 4.9.5, < 5) + ruby-vips (>= 2.0.17, < 3) + importmap-rails (1.1.5) + actionpack (>= 6.0.0) + railties (>= 6.0.0) + io-console (0.8.1) + irb (1.15.2) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jaro_winkler (1.6.1) + jbuilder (2.14.1) + actionview (>= 7.0.0) + activesupport (>= 7.0.0) + json (2.13.2) + kramdown (2.5.1) + rexml (>= 3.3.9) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) + loofah (2.24.1) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + lumberjack (1.4.1) + mail (2.8.1) + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + mailgun-ruby (1.3.10) + faraday (~> 2.1) + faraday-multipart (~> 1.1.0) + mini_mime + marcel (1.0.4) + matrix (0.4.3) + method_source (1.1.0) + mini_magick (4.13.2) + mini_mime (1.1.5) + minitest (5.18.0) + minitest-reporters (1.6.0) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + msgpack (1.8.0) + multipart-post (2.4.1) + nenv (0.3.0) + net-http (0.9.1) + uri (>= 0.11.1) + net-imap (0.5.10) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.4) + nokogiri (1.19.1-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-musl) + racc (~> 1.4) + notiffany (0.1.3) + nenv (~> 0.1) + shellany (~> 0.0) + observer (0.1.2) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.9.0) + ast (~> 2.4.1) + racc + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + prism (1.4.0) + pry (0.15.2) + coderay (~> 1.1) + method_source (~> 1.0) + psych (5.2.6) + date + stringio + public_suffix (6.0.2) + puma (6.6.1) + nio4r (~> 2.0) + racc (1.8.1) + rack (3.2.5) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.2.1) + rack (>= 3) + rails (8.0.2.1) + actioncable (= 8.0.2.1) + actionmailbox (= 8.0.2.1) + actionmailer (= 8.0.2.1) + actionpack (= 8.0.2.1) + actiontext (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activemodel (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + bundler (>= 1.15.0) + railties (= 8.0.2.1) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.0) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rbs (3.6.1) + logger + rdoc (6.14.2) + erb + psych (>= 4.0.0) + regexp_parser (2.11.2) + reline (0.5.10) + io-console (~> 0.5) + repl_type_completor (0.1.10) + prism (~> 1.0) + rbs (>= 2.7.0, < 4.0.0) + reverse_markdown (3.0.0) + nokogiri + rexml (3.4.2) + rubocop (1.80.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.46.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.46.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + ruby-progressbar (1.13.0) + ruby-vips (2.3.0) + ffi (~> 1.12) + logger + rubyzip (2.4.1) + sassc (2.4.0) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt + securerandom (0.4.1) + selenium-webdriver (4.8.3) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) + shellany (0.0.1) + solargraph (0.58.2) + backport (~> 1.2) + benchmark (~> 0.4) + bundler (~> 2.0) + diff-lcs (~> 1.4) + jaro_winkler (~> 1.6, >= 1.6.1) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.1) + logger (~> 1.6) + observer (~> 0.1) + ostruct (~> 0.6) + parser (~> 3.0) + prism (~> 1.4) + rbs (~> 3.6.1) + reverse_markdown (~> 3.0) + rubocop (~> 1.38) + thor (~> 1.0) + tilt (~> 2.0) + yard (~> 0.9, >= 0.9.24) + yard-solargraph (~> 0.1) + sprockets (4.2.2) + concurrent-ruby (~> 1.0) + logger + rack (>= 2.2.4, < 4) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + sprockets (>= 3.0.0) + sqlite3 (2.7.3-aarch64-linux-gnu) + sqlite3 (2.7.3-aarch64-linux-musl) + sqlite3 (2.7.3-arm-linux-gnu) + sqlite3 (2.7.3-arm-linux-musl) + sqlite3 (2.7.3-arm64-darwin) + sqlite3 (2.7.3-x86_64-darwin) + sqlite3 (2.7.3-x86_64-linux-gnu) + sqlite3 (2.7.3-x86_64-linux-musl) + stimulus-rails (1.2.1) + railties (>= 6.0.0) + stringio (3.1.7) + thor (1.4.0) + tilt (2.6.1) + timeout (0.4.3) + turbo-rails (1.4.0) + actionpack (>= 6.0.0) + activejob (>= 6.0.0) + railties (>= 6.0.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.1.5) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + uri (1.0.4) + useragent (0.16.11) + web-console (4.2.0) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + webdrivers (5.2.0) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (~> 4.0) + websocket (1.2.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + will_paginate (3.3.1) + xpath (3.2.0) + nokogiri (~> 1.8) + yard (0.9.37) + yard-solargraph (0.1.0) + yard (~> 0.9) + zeitwerk (2.7.3) + +PLATFORMS + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + active_storage_validations (= 0.9.8) + bcrypt (= 3.1.18) + bootsnap (= 1.16.0) + bootstrap-sass (= 3.4.1) + bootstrap-will_paginate (= 1.0.0) + capybara (= 3.38.0) + concurrent-ruby (= 1.3.4) + debug (= 1.7.1) + faker (= 2.21.0) + guard (= 2.18.0) + guard-minitest (= 2.4.6) + image_processing (= 1.12.2) + importmap-rails (= 1.1.5) + irb (= 1.15.2) + jbuilder (= 2.14.1) + mailgun-ruby (= 1.3.10) + minitest (= 5.18.0) + minitest-reporters (= 1.6.0) + puma (= 6.6.1) + rails (= 8.0.2.1) + rails-controller-testing (= 1.0.5) + reline (= 0.5.10) + repl_type_completor (= 0.1.10) + sassc-rails (= 2.1.2) + selenium-webdriver (= 4.8.3) + solargraph (= 0.58.2) + sprockets-rails (= 3.4.2) + sqlite3 (= 2.7.3) + stimulus-rails (= 1.2.1) + turbo-rails (= 1.4.0) + web-console (= 4.2.0) + webdrivers (= 5.2.0) + will_paginate (= 3.3.1) + +RUBY VERSION + ruby 3.2.10p266 + +BUNDLED WITH + 2.5.6 diff --git a/8_0/ch13/Guardfile b/8_0/ch13/Guardfile new file mode 100644 index 00000000..5def34a0 --- /dev/null +++ b/8_0/ch13/Guardfile @@ -0,0 +1,76 @@ + require "active_support/inflector" + # Guardのマッチング規則を定義 + guard :minitest, all_on_start: false do + watch(%r{^test/(.*)/?(.*)_test\.rb$}) + watch('test/test_helper.rb') { 'test' } + watch('config/routes.rb') { interface_tests } + watch(%r{app/views/layouts/*}) { interface_tests } + watch(%r{^app/models/(.*?)\.rb$}) do |matches| + ["test/models/#{matches[1]}_test.rb", + "test/integration/microposts_interface_test.rb"] + end + watch(%r{^test/fixtures/(.*?)\.yml$}) do |matches| + "test/models/#{matches[1].singularize}_test.rb" + end + watch(%r{^app/mailers/(.*?)\.rb$}) do |matches| + "test/mailers/#{matches[1]}_test.rb" + end + watch(%r{^app/views/(.*)_mailer/.*$}) do |matches| + "test/mailers/#{matches[1]}_mailer_test.rb" + end + watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches| + resource_tests(matches[1]) + end + watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches| + ["test/controllers/#{matches[1]}_controller_test.rb"] + + integration_tests(matches[1]) + end + watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches| + integration_tests(matches[1]) + end + watch('app/views/layouts/application.html.erb') do + 'test/integration/site_layout_test.rb' + end + watch('app/helpers/sessions_helper.rb') do + integration_tests << 'test/helpers/sessions_helper_test.rb' + end + watch('app/controllers/sessions_controller.rb') do + ['test/controllers/sessions_controller_test.rb', + 'test/integration/users_login_test.rb'] + end + watch('app/controllers/account_activations_controller.rb') do + 'test/integration/users_signup_test.rb' + end + watch(%r{app/views/users/*}) do + resource_tests('users') + + ['test/integration/microposts_interface_test.rb'] + end + watch('app/controllers/relationships_controller.rb') do + ['test/controllers/relationships_controller_test.rb', + 'test/integration/following_test.rb'] + end + end + + # 指定のリソースに対応する統合テストを返す + def integration_tests(resource = :all) + if resource == :all + Dir["test/integration/*"] + else + Dir["test/integration/#{resource}_*.rb"] + end + end + + # インターフェースが該当するすべてのテストを返す + def interface_tests + integration_tests << "test/controllers" + end + + # 指定のリソースに対応するコントローラのテストを返す + def controller_test(resource) + "test/controllers/#{resource}_controller_test.rb" + end + + # 指定のリソースに対応するすべてのテストを返す + def resource_tests(resource) + integration_tests(resource) << controller_test(resource) + end diff --git a/8_0/ch13/LICENSE b/8_0/ch13/LICENSE new file mode 100644 index 00000000..20622e0c --- /dev/null +++ b/8_0/ch13/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 GitHub & 2023 YassLab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/8_0/ch13/README.md b/8_0/ch13/README.md new file mode 100644 index 00000000..17ca733d --- /dev/null +++ b/8_0/ch13/README.md @@ -0,0 +1,35 @@ +# Ruby on Rails チュートリアルのサンプルアプリケーション + +これは、次の教材で作られたサンプルアプリケーションです。 +[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +(第8版) +[Michael Hartl](https://www.michaelhartl.com/) 著 + +## ライセンス + +[Ruby on Rails チュートリアル](https://railstutorial.jp/)内にある +ソースコードはMITライセンスとBeerwareライセンスのもとで公開されています。 +詳細は [LICENSE.md](LICENSE.md) をご覧ください。 + +## 使い方 + +このアプリケーションを動かす場合は、まずはリポジトリをフォークしてください。 + +フォークしたリポジトリで、「Code」から「Codespaces」タブに移動し、 +「Create codespace on main」をクリックすると環境構築がスタートします。 +Railsサーバーが立ち上がり、シンプルブラウザが表示されるまでしばらくお待ちください。 + +次に、データベースへのマイグレーションを実行します。 + +``` +$ rails db:migrate +``` + +最後に、テストを実行してうまく動いているかどうか確認してください。 + +``` +$ rails test +``` + +詳しくは、[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +を参考にしてください。 diff --git a/8_0/ch13/Rakefile b/8_0/ch13/Rakefile new file mode 100644 index 00000000..9a5ea738 --- /dev/null +++ b/8_0/ch13/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/8_0/ch13/app/assets/config/manifest.js b/8_0/ch13/app/assets/config/manifest.js new file mode 100644 index 00000000..ddd546a0 --- /dev/null +++ b/8_0/ch13/app/assets/config/manifest.js @@ -0,0 +1,4 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js diff --git a/8_0/ch13/app/assets/images/.keep b/8_0/ch13/app/assets/images/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/app/assets/images/rails.svg b/8_0/ch13/app/assets/images/rails.svg new file mode 100644 index 00000000..e1971850 --- /dev/null +++ b/8_0/ch13/app/assets/images/rails.svg @@ -0,0 +1,35 @@ + + + +rails-logo + + + + + + + + + + + + + + + + + + + + + diff --git a/8_0/ch13/app/assets/stylesheets/application.css b/8_0/ch13/app/assets/stylesheets/application.css new file mode 100644 index 00000000..288b9ab7 --- /dev/null +++ b/8_0/ch13/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/8_0/ch13/app/assets/stylesheets/custom.scss b/8_0/ch13/app/assets/stylesheets/custom.scss new file mode 100644 index 00000000..5f7bd3c6 --- /dev/null +++ b/8_0/ch13/app/assets/stylesheets/custom.scss @@ -0,0 +1,274 @@ +@import "bootstrap-sprockets"; +@import "bootstrap"; + +/* mixins, variables, etc. */ + +$gray-medium-light: #eaeaea; + +@mixin box_sizing { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* universal */ + +body { + padding-top: 60px; +} + +section { + overflow: auto; +} + +textarea { + resize: vertical; +} + +.center { + text-align: center; + h1 { + margin-bottom: 10px; + } +} + +/* typography */ + +h1, h2, h3, h4, h5, h6 { + line-height: 1; +} + +h1 { + font-size: 3em; + letter-spacing: -2px; + margin-bottom: 30px; + text-align: center; +} + +h2 { + font-size: 1.2em; + letter-spacing: -1px; + margin-bottom: 30px; + text-align: center; + font-weight: normal; + color: $gray-light; +} + +p { + font-size: 1.1em; + line-height: 1.7em; +} + + +/* header */ + +#logo { + float: left; + margin-right: 10px; + font-size: 1.7em; + color: white; + text-transform: uppercase; + letter-spacing: -1px; + padding-top: 9px; + font-weight: bold; + &:hover { + color: white; + text-decoration: none; + } +} + +/* footer */ + +footer { + margin-top: 45px; + padding-top: 5px; + border-top: 1px solid $gray-medium-light; + color: $gray-light; + a { + color: $gray; + &:hover { + color: $gray-darker; + } + } + small { + float: left; + } + ul { + float: right; + list-style: none; + li { + float: left; + margin-left: 15px; + } + } +} +@media (max-width: 800px) { + footer { + small { + display: block; + float: none; + margin-bottom: 1em; + } + ul { + float: none; + padding: 0; + li { + float: none; + margin-left: 0; + } + } + } +} + +/* miscellaneous */ + +.debug_dump { + clear: both; + float: left; + width: 100%; + margin-top: 45px; +} + +/* sidebar */ + +aside { + section.user_info { + margin-top: 20px; + } + section { + padding: 10px 0; + margin-top: 20px; + &:first-child { + border: 0; + padding-top: 0; + } + span { + display: block; + margin-bottom: 3px; + line-height: 1; + } + h1 { + font-size: 1.4em; + text-align: left; + letter-spacing: -1px; + margin-bottom: 3px; + margin-top: 0px; + } + } +} + +.gravatar { + float: left; + margin-right: 10px; +} + +.gravatar_edit { + margin-top: 15px; +} + +/* forms */ + +input, textarea { + border: 1px solid #bbb; + width: 100%; + margin-bottom: 15px; + @include box_sizing; +} + +input { + height: auto !important; +} + +#error_explanation { + color: red; + ul { + color: red; + margin: 0 0 30px 0; + } +} + +.field_with_errors { + @extend .has-error; + .form-control { + color: $state-danger-text; + } +} + +.checkbox { + margin-top: -10px; + margin-bottom: 10px; + span { + margin-left: 20px; + font-weight: normal; + } +} + +#session_remember_me { + width: auto; + margin-left: 0; +} + +/* Dropdown menu */ + +.dropdown-menu.active { + display: block; +} + +/* Users index */ + +.users { + list-style: none; + margin: 0; + li { + overflow: auto; + padding: 10px 0; + border-bottom: 1px solid $gray-lighter; + } +} + +/* microposts */ + +.microposts { + list-style: none; + padding: 0; + li { + padding: 10px 0; + border-top: 1px solid #e8e8e8; + } + .user { + margin-top: 5em; + padding-top: 0; + } + .content { + display: block; + margin-left: 60px; + img { + display: block; + padding: 5px 0; + } + } + .timestamp { + color: $gray-light; + display: block; + margin-left: 60px; + } + .gravatar { + float: left; + margin-right: 10px; + margin-top: 5px; + } +} + +aside { + textarea { + height: 100px; + margin-bottom: 5px; + } +} + +span.image { + margin-top: 10px; + input { + border: 0; + } +} diff --git a/8_0/ch13/app/assets/stylesheets/hello.css b/8_0/ch13/app/assets/stylesheets/hello.css new file mode 100644 index 00000000..497881a4 --- /dev/null +++ b/8_0/ch13/app/assets/stylesheets/hello.css @@ -0,0 +1,40 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.codespaces { + text-align: center; +} +.codespaces code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} +.codespaces-logo { + height: 40vmin; + pointer-events: none; +} +.codespaces-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} +.codespaces-link { + color: #61dafb; +} + +.heart { + color: #ff0000; +} +.small { + font-size: 0.75rem; +} diff --git a/8_0/ch13/app/channels/application_cable/channel.rb b/8_0/ch13/app/channels/application_cable/channel.rb new file mode 100644 index 00000000..d6726972 --- /dev/null +++ b/8_0/ch13/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/8_0/ch13/app/channels/application_cable/connection.rb b/8_0/ch13/app/channels/application_cable/connection.rb new file mode 100644 index 00000000..0ff5442f --- /dev/null +++ b/8_0/ch13/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/8_0/ch13/app/controllers/account_activations_controller.rb b/8_0/ch13/app/controllers/account_activations_controller.rb new file mode 100644 index 00000000..4985c73e --- /dev/null +++ b/8_0/ch13/app/controllers/account_activations_controller.rb @@ -0,0 +1,15 @@ +class AccountActivationsController < ApplicationController + + def edit + user = User.find_by(email: params[:email]) + if user && !user.activated? && user.authenticated?(:activation, params[:id]) + user.activate + log_in user + flash[:success] = "Account activated!" + redirect_to user + else + flash[:danger] = "Invalid activation link" + redirect_to root_url + end + end +end diff --git a/8_0/ch13/app/controllers/application_controller.rb b/8_0/ch13/app/controllers/application_controller.rb new file mode 100644 index 00000000..24e570fd --- /dev/null +++ b/8_0/ch13/app/controllers/application_controller.rb @@ -0,0 +1,14 @@ +class ApplicationController < ActionController::Base + include SessionsHelper + + private + + # ユーザーのログインを確認する + def logged_in_user + unless logged_in? + store_location + flash[:danger] = "Please log in." + redirect_to login_url, status: :see_other + end + end +end diff --git a/8_0/ch13/app/controllers/concerns/.keep b/8_0/ch13/app/controllers/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/app/controllers/hello_controller.rb b/8_0/ch13/app/controllers/hello_controller.rb new file mode 100644 index 00000000..52037537 --- /dev/null +++ b/8_0/ch13/app/controllers/hello_controller.rb @@ -0,0 +1,4 @@ +class HelloController < ApplicationController + def index + end +end diff --git a/8_0/ch13/app/controllers/microposts_controller.rb b/8_0/ch13/app/controllers/microposts_controller.rb new file mode 100644 index 00000000..f871083f --- /dev/null +++ b/8_0/ch13/app/controllers/microposts_controller.rb @@ -0,0 +1,37 @@ +class MicropostsController < ApplicationController + before_action :logged_in_user, only: [:create, :destroy] + before_action :correct_user, only: :destroy + + def create + @micropost = current_user.microposts.build(micropost_params) + @micropost.image.attach(params[:micropost][:image]) + if @micropost.save + flash[:success] = "Micropost created!" + redirect_to root_url + else + @feed_items = current_user.feed.paginate(page: params[:page]) + render 'static_pages/home', status: :unprocessable_entity + end + end + + def destroy + @micropost.destroy + flash[:success] = "Micropost deleted" + if request.referrer.nil? || request.referrer == microposts_url + redirect_to root_url, status: :see_other + else + redirect_to request.referrer, status: :see_other + end + end + + private + + def micropost_params + params.expect(micropost: [:content, :image]) + end + + def correct_user + @micropost = current_user.microposts.find_by(id: params[:id]) + redirect_to root_url, status: :see_other if @micropost.nil? + end +end diff --git a/8_0/ch13/app/controllers/password_resets_controller.rb b/8_0/ch13/app/controllers/password_resets_controller.rb new file mode 100644 index 00000000..260165c3 --- /dev/null +++ b/8_0/ch13/app/controllers/password_resets_controller.rb @@ -0,0 +1,66 @@ +class PasswordResetsController < ApplicationController + before_action :get_user, only: [:edit, :update] + before_action :valid_user, only: [:edit, :update] + before_action :check_expiration, only: [:edit, :update] # (1)への対応 + + def new + end + + def create + @user = User.find_by(email: params[:password_reset][:email].downcase) + if @user + @user.create_reset_digest + @user.send_password_reset_email + flash[:info] = "Email sent with password reset instructions" + redirect_to root_url + else + flash.now[:danger] = "Email address not found" + render 'new', status: :unprocessable_entity + end + end + + def edit + end + + def update + if params[:user][:password].empty? # (3)への対応 + @user.errors.add(:password, "can't be empty") + render 'edit', status: :unprocessable_entity + elsif @user.update(user_params) # (4)への対応 + reset_session + log_in @user + flash[:success] = "Password has been reset." + redirect_to @user + else + render 'edit', status: :unprocessable_entity # (2)への対応 + end + end + + private + + def user_params + params.expect(user: [:password, :password_confirmation]) + end + + # beforeフィルタ + + def get_user + @user = User.find_by(email: params[:email]) + end + + # 有効なユーザーかどうか確認する + def valid_user + unless (@user && @user.activated? && + @user.authenticated?(:reset, params[:id])) + redirect_to root_url + end + end + + # トークンが期限切れかどうか確認する + def check_expiration + if @user.password_reset_expired? + flash[:danger] = "Password reset has expired." + redirect_to new_password_reset_url + end + end +end diff --git a/8_0/ch13/app/controllers/sessions_controller.rb b/8_0/ch13/app/controllers/sessions_controller.rb new file mode 100644 index 00000000..4fa0043a --- /dev/null +++ b/8_0/ch13/app/controllers/sessions_controller.rb @@ -0,0 +1,31 @@ +class SessionsController < ApplicationController + + def new + end + + def create + user = User.find_by(email: params[:session][:email].downcase) + if user && user.authenticate(params[:session][:password]) + if user.activated? + forwarding_url = session[:forwarding_url] + reset_session + params[:session][:remember_me] == '1' ? remember(user) : forget(user) + log_in user + redirect_to forwarding_url || user + else + message = "Account not activated. " + message += "Check your email for the activation link." + flash[:warning] = message + redirect_to root_url + end + else + flash.now[:danger] = 'Invalid email/password combination' + render 'new', status: :unprocessable_entity + end + end + + def destroy + log_out if logged_in? + redirect_to root_url, status: :see_other + end +end diff --git a/8_0/ch13/app/controllers/static_pages_controller.rb b/8_0/ch13/app/controllers/static_pages_controller.rb new file mode 100644 index 00000000..29311977 --- /dev/null +++ b/8_0/ch13/app/controllers/static_pages_controller.rb @@ -0,0 +1,18 @@ +class StaticPagesController < ApplicationController + + def home + if logged_in? + @micropost = current_user.microposts.build + @feed_items = current_user.feed.paginate(page: params[:page]) + end + end + + def help + end + + def about + end + + def contact + end +end diff --git a/8_0/ch13/app/controllers/users_controller.rb b/8_0/ch13/app/controllers/users_controller.rb new file mode 100644 index 00000000..56176649 --- /dev/null +++ b/8_0/ch13/app/controllers/users_controller.rb @@ -0,0 +1,67 @@ +class UsersController < ApplicationController + before_action :logged_in_user, only: [:index, :edit, :update, :destroy] + before_action :correct_user, only: [:edit, :update] + before_action :admin_user, only: :destroy + + def index + @users = User.paginate(page: params[:page]) + end + + def show + @user = User.find(params[:id]) + @microposts = @user.microposts.paginate(page: params[:page]) + end + + def new + @user = User.new + end + + def create + @user = User.new(user_params) + if @user.save + @user.send_activation_email + flash[:info] = "Please check your email to activate your account." + redirect_to root_url + else + render 'new', status: :unprocessable_entity + end + end + + def edit + end + + def update + if @user.update(user_params) + flash[:success] = "Profile updated" + redirect_to @user + else + render 'edit', status: :unprocessable_entity + end + end + + def destroy + User.find(params[:id]).destroy + flash[:success] = "User deleted" + redirect_to users_url, status: :see_other + end + + private + + def user_params + params.expect(user: [:name, :email, :password, + :password_confirmation]) + end + + # beforeフィルター + + # 正しいユーザーかどうかを確認 + def correct_user + @user = User.find(params[:id]) + redirect_to(root_url, status: :see_other) unless current_user?(@user) + end + + # 管理者かどうかを確認 + def admin_user + redirect_to(root_url, status: :see_other) unless current_user.admin? + end +end diff --git a/8_0/ch13/app/helpers/account_activations_helper.rb b/8_0/ch13/app/helpers/account_activations_helper.rb new file mode 100644 index 00000000..c4d5ac72 --- /dev/null +++ b/8_0/ch13/app/helpers/account_activations_helper.rb @@ -0,0 +1,2 @@ +module AccountActivationsHelper +end diff --git a/8_0/ch13/app/helpers/application_helper.rb b/8_0/ch13/app/helpers/application_helper.rb new file mode 100644 index 00000000..708e4e48 --- /dev/null +++ b/8_0/ch13/app/helpers/application_helper.rb @@ -0,0 +1,12 @@ +module ApplicationHelper + + # ページごとの完全なタイトルを返します。 # コメント行 + def full_title(page_title = '') # メソッド定義とオプション引数 + base_title = "Ruby on Rails Tutorial Sample App" # 変数への代入 + if page_title.empty? # 論理値テスト + base_title # 暗黙の戻り値 + else + "#{page_title} | #{base_title}" # 文字列の結合 + end + end +end diff --git a/8_0/ch13/app/helpers/hello_codespaces_helper.rb b/8_0/ch13/app/helpers/hello_codespaces_helper.rb new file mode 100644 index 00000000..874a2ca2 --- /dev/null +++ b/8_0/ch13/app/helpers/hello_codespaces_helper.rb @@ -0,0 +1,2 @@ +module HelloCodespacesHelper +end diff --git a/8_0/ch13/app/helpers/microposts_helper.rb b/8_0/ch13/app/helpers/microposts_helper.rb new file mode 100644 index 00000000..f08aad24 --- /dev/null +++ b/8_0/ch13/app/helpers/microposts_helper.rb @@ -0,0 +1,2 @@ +module MicropostsHelper +end diff --git a/8_0/ch13/app/helpers/password_resets_helper.rb b/8_0/ch13/app/helpers/password_resets_helper.rb new file mode 100644 index 00000000..0c9d96ec --- /dev/null +++ b/8_0/ch13/app/helpers/password_resets_helper.rb @@ -0,0 +1,2 @@ +module PasswordResetsHelper +end diff --git a/8_0/ch13/app/helpers/sessions_helper.rb b/8_0/ch13/app/helpers/sessions_helper.rb new file mode 100644 index 00000000..a38a35bd --- /dev/null +++ b/8_0/ch13/app/helpers/sessions_helper.rb @@ -0,0 +1,60 @@ +module SessionsHelper + + # 渡されたユーザーでログインする + def log_in(user) + session[:user_id] = user.id + # セッションリプレイ攻撃から保護する + # 詳しくは https://techracho.bpsinc.jp/hachi8833/2023_06_02/130443 を参照 + session[:session_token] = user.session_token + end + + # ユーザーを永続的セッションに保存する + def remember(user) + user.remember + cookies.permanent.encrypted[:user_id] = user.id + cookies.permanent[:remember_token] = user.remember_token + end + + # 現在ログイン中のユーザーを返す(いる場合) + def current_user + if (user_id = session[:user_id]) + user = User.find_by(id: user_id) + @current_user ||= user if session[:session_token] == user.session_token + elsif (user_id = cookies.encrypted[:user_id]) + user = User.find_by(id: user_id) + if user && user.authenticated?(:remember, cookies[:remember_token]) + log_in user + @current_user = user + end + end + end + + # 渡されたユーザーがカレントユーザーであればtrueを返す + def current_user?(user) + user && user == current_user + end + + # ユーザーがログインしていればtrue、その他ならfalseを返す + def logged_in? + !current_user.nil? + end + + # 永続的セッションを破棄する + def forget(user) + user.forget + cookies.delete(:user_id) + cookies.delete(:remember_token) + end + + # 現在のユーザーをログアウトする + def log_out + forget(current_user) + reset_session + @current_user = nil + end + + # アクセスしようとしたURLを保存する + def store_location + session[:forwarding_url] = request.original_url if request.get? + end +end diff --git a/8_0/ch13/app/helpers/static_pages_helper.rb b/8_0/ch13/app/helpers/static_pages_helper.rb new file mode 100644 index 00000000..2d63e79e --- /dev/null +++ b/8_0/ch13/app/helpers/static_pages_helper.rb @@ -0,0 +1,2 @@ +module StaticPagesHelper +end diff --git a/8_0/ch13/app/helpers/users_helper.rb b/8_0/ch13/app/helpers/users_helper.rb new file mode 100644 index 00000000..53790499 --- /dev/null +++ b/8_0/ch13/app/helpers/users_helper.rb @@ -0,0 +1,10 @@ +module UsersHelper + + # 引数で与えられたユーザーのGravatar画像を返す + def gravatar_for(user, options = { size: 80 }) + size = options[:size] + gravatar_id = Digest::MD5::hexdigest(user.email.downcase) + gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}" + image_tag(gravatar_url, alt: user.name, class: "gravatar") + end +end diff --git a/8_0/ch13/app/javascript/application.js b/8_0/ch13/app/javascript/application.js new file mode 100644 index 00000000..d1ec8140 --- /dev/null +++ b/8_0/ch13/app/javascript/application.js @@ -0,0 +1,6 @@ +// Configure your import map in config/importmap.rb. +// Read more: https://github.com/rails/importmap-rails +import "@hotwired/turbo-rails" +import "controllers" +import "custom/menu" +import "custom/image_upload" diff --git a/8_0/ch13/app/javascript/controllers/application.js b/8_0/ch13/app/javascript/controllers/application.js new file mode 100644 index 00000000..1213e85c --- /dev/null +++ b/8_0/ch13/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/8_0/ch13/app/javascript/controllers/hello_controller.js b/8_0/ch13/app/javascript/controllers/hello_controller.js new file mode 100644 index 00000000..5975c078 --- /dev/null +++ b/8_0/ch13/app/javascript/controllers/hello_controller.js @@ -0,0 +1,7 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.element.textContent = "Hello World!" + } +} diff --git a/8_0/ch13/app/javascript/controllers/index.js b/8_0/ch13/app/javascript/controllers/index.js new file mode 100644 index 00000000..54ad4cad --- /dev/null +++ b/8_0/ch13/app/javascript/controllers/index.js @@ -0,0 +1,11 @@ +// Import and register all your controllers from the importmap under controllers/* + +import { application } from "controllers/application" + +// Eager load all controllers defined in the import map under controllers/**/*_controller +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) + +// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) +// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" +// lazyLoadControllersFrom("controllers", application) diff --git a/8_0/ch13/app/javascript/custom/image_upload.js b/8_0/ch13/app/javascript/custom/image_upload.js new file mode 100644 index 00000000..e8b1b359 --- /dev/null +++ b/8_0/ch13/app/javascript/custom/image_upload.js @@ -0,0 +1,13 @@ +// 巨大画像のアップロードを防止する +document.addEventListener("turbo:load", function() { + document.addEventListener("change", function(event) { + let image_upload = document.querySelector('#micropost_image'); + if (image_upload && image_upload.files.length > 0) { + const size_in_megabytes = image_upload.files[0].size/1024/1024; + if (size_in_megabytes > 5) { + alert("Maximum file size is 5MB. Please choose a smaller file."); + image_upload.value = ""; + } + } + }); +}); diff --git a/8_0/ch13/app/javascript/custom/menu.js b/8_0/ch13/app/javascript/custom/menu.js new file mode 100644 index 00000000..92e4a8dd --- /dev/null +++ b/8_0/ch13/app/javascript/custom/menu.js @@ -0,0 +1,22 @@ +// メニュー操作 + +// トグルリスナーを追加してクリックをリッスンする +document.addEventListener("turbo:load", function() { + let hamburger = document.querySelector("#hamburger"); + if (hamburger){ + hamburger.addEventListener("click", function(event) { + event.preventDefault(); + let menu = document.querySelector("#navbar-menu"); + menu.classList.toggle("collapse"); + }); + } + + let account = document.querySelector("#account"); + if (account) { + account.addEventListener("click", function(event) { + event.preventDefault(); + let menu = document.querySelector("#dropdown-menu"); + menu.classList.toggle("active"); + }); + } +}); diff --git a/8_0/ch13/app/jobs/application_job.rb b/8_0/ch13/app/jobs/application_job.rb new file mode 100644 index 00000000..d394c3d1 --- /dev/null +++ b/8_0/ch13/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/8_0/ch13/app/mailers/application_mailer.rb b/8_0/ch13/app/mailers/application_mailer.rb new file mode 100644 index 00000000..b166e6e2 --- /dev/null +++ b/8_0/ch13/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "Mailgunに登録したメールアドレス" + layout "mailer" +end diff --git a/8_0/ch13/app/mailers/user_mailer.rb b/8_0/ch13/app/mailers/user_mailer.rb new file mode 100644 index 00000000..4aa71dab --- /dev/null +++ b/8_0/ch13/app/mailers/user_mailer.rb @@ -0,0 +1,12 @@ +class UserMailer < ApplicationMailer + + def account_activation(user) + @user = user + mail to: user.email, subject: "Account activation" + end + + def password_reset(user) + @user = user + mail to: user.email, subject: "Password reset" + end +end diff --git a/8_0/ch13/app/models/application_record.rb b/8_0/ch13/app/models/application_record.rb new file mode 100644 index 00000000..b63caeb8 --- /dev/null +++ b/8_0/ch13/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/8_0/ch13/app/models/concerns/.keep b/8_0/ch13/app/models/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/app/models/micropost.rb b/8_0/ch13/app/models/micropost.rb new file mode 100644 index 00000000..e5be8190 --- /dev/null +++ b/8_0/ch13/app/models/micropost.rb @@ -0,0 +1,13 @@ +class Micropost < ApplicationRecord + belongs_to :user + has_one_attached :image do |attachable| + attachable.variant :display, resize_to_limit: [500, 500] + end + default_scope -> { order(created_at: :desc) } + validates :user_id, presence: true + validates :content, presence: true, length: { maximum: 140 } + validates :image, content_type: { in: %w[image/jpeg image/gif image/png], + message: "must be a valid image format" }, + size: { less_than: 5.megabytes, + message: "should be less than 5MB" } +end diff --git a/8_0/ch13/app/models/user.rb b/8_0/ch13/app/models/user.rb new file mode 100644 index 00000000..0c50a7e5 --- /dev/null +++ b/8_0/ch13/app/models/user.rb @@ -0,0 +1,97 @@ +class User < ApplicationRecord + has_many :microposts, dependent: :destroy + attr_accessor :remember_token, :activation_token, :reset_token + before_save :downcase_email + before_create :create_activation_digest + validates :name, presence: true, length: { maximum: 50 } + VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i + validates :email, presence: true, length: { maximum: 255 }, + format: { with: VALID_EMAIL_REGEX }, + uniqueness: true + has_secure_password + validates :password, presence: true, length: { minimum: 6 }, allow_nil: true + + # 渡された文字列のハッシュ値を返す + def User.digest(string) + cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : + BCrypt::Engine.cost + BCrypt::Password.create(string, cost: cost) + end + + # ランダムなトークンを返す + def User.new_token + SecureRandom.urlsafe_base64 + end + + # 永続的セッションのためにユーザーをデータベースに記憶する + def remember + self.remember_token = User.new_token + update_attribute(:remember_digest, User.digest(remember_token)) + remember_digest + end + + # セッションハイジャック防止のためにセッショントークンを返す + # この記憶ダイジェストを再利用しているのは単に利便性のため + def session_token + remember_digest || remember + end + + # 渡されたトークンがダイジェストと一致したらtrueを返す + def authenticated?(attribute, token) + digest = send("#{attribute}_digest") + return false if digest.nil? + BCrypt::Password.new(digest).is_password?(token) + end + + # ユーザーのログイン情報を破棄する + def forget + update_attribute(:remember_digest, nil) + end + + # アカウントを有効にする + def activate + update_attribute(:activated, true) + update_attribute(:activated_at, Time.zone.now) + end + + # 有効化用のメールを送信する + def send_activation_email + UserMailer.account_activation(self).deliver_now + end + + # パスワード再設定の属性を設定する + def create_reset_digest + self.reset_token = User.new_token + update_attribute(:reset_digest, User.digest(reset_token)) + update_attribute(:reset_sent_at, Time.zone.now) + end + + # パスワード再設定のメールを送信する + def send_password_reset_email + UserMailer.password_reset(self).deliver_now + end + + # パスワード再設定の期限が切れている場合はtrueを返す + def password_reset_expired? + reset_sent_at < 2.hours.ago + end + + # 試作feedの定義 + # 完全な実装は次章の「ユーザーをフォローする」を参照 + def feed + Micropost.where("user_id = ?", id) + end + + private + + # メールアドレスをすべて小文字にする + def downcase_email + self.email = email.downcase + end + + # 有効化トークンとダイジェストを作成および代入する + def create_activation_digest + self.activation_token = User.new_token + self.activation_digest = User.digest(activation_token) + end +end diff --git a/8_0/ch13/app/views/hello/index.html.erb b/8_0/ch13/app/views/hello/index.html.erb new file mode 100644 index 00000000..71661105 --- /dev/null +++ b/8_0/ch13/app/views/hello/index.html.erb @@ -0,0 +1,16 @@ +
    +
    + +

    + Codespaces + ♥️ + Railsチュートリアル +

    +

    + 🎓✨
    + ロゴ画像が表示されたらセットアップ完了です! +

    +
    +
    diff --git a/8_0/ch13/app/views/layouts/_footer.html.erb b/8_0/ch13/app/views/layouts/_footer.html.erb new file mode 100644 index 00000000..b6e31ff9 --- /dev/null +++ b/8_0/ch13/app/views/layouts/_footer.html.erb @@ -0,0 +1,13 @@ + diff --git a/8_0/ch13/app/views/layouts/_header.html.erb b/8_0/ch13/app/views/layouts/_header.html.erb new file mode 100644 index 00000000..8b07e211 --- /dev/null +++ b/8_0/ch13/app/views/layouts/_header.html.erb @@ -0,0 +1,39 @@ + diff --git a/8_0/ch13/app/views/layouts/application.html.erb b/8_0/ch13/app/views/layouts/application.html.erb new file mode 100644 index 00000000..eb23ef94 --- /dev/null +++ b/8_0/ch13/app/views/layouts/application.html.erb @@ -0,0 +1,24 @@ + + + + <%= full_title(yield(:title)) %> + + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + + + <%= render 'layouts/header' %> +
    + <% flash.each do |message_type, message| %> +
    <%= message %>
    + <% end %> + <%= yield %> + <%= render 'layouts/footer' %> + <%= debug(params) if Rails.env.development? %> +
    + + diff --git a/8_0/ch13/app/views/layouts/mailer.html.erb b/8_0/ch13/app/views/layouts/mailer.html.erb new file mode 100644 index 00000000..cbd34d2e --- /dev/null +++ b/8_0/ch13/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/8_0/ch13/app/views/layouts/mailer.text.erb b/8_0/ch13/app/views/layouts/mailer.text.erb new file mode 100644 index 00000000..37f0bddb --- /dev/null +++ b/8_0/ch13/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/8_0/ch13/app/views/microposts/_micropost.html.erb b/8_0/ch13/app/views/microposts/_micropost.html.erb new file mode 100644 index 00000000..91117986 --- /dev/null +++ b/8_0/ch13/app/views/microposts/_micropost.html.erb @@ -0,0 +1,17 @@ +
  • + <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %> + <%= link_to micropost.user.name, micropost.user %> + + <%= micropost.content %> + <% if micropost.image.attached? %> + <%= image_tag micropost.image.variant(:display) %> + <% end %> + + + Posted <%= time_ago_in_words(micropost.created_at) %> ago. + <% if current_user?(micropost.user) %> + <%= link_to "delete", micropost, data: { "turbo-method": :delete, + turbo_confirm: "You sure?" } %> + <% end %> + +
  • diff --git a/8_0/ch13/app/views/password_resets/edit.html.erb b/8_0/ch13/app/views/password_resets/edit.html.erb new file mode 100644 index 00000000..84bf26ba --- /dev/null +++ b/8_0/ch13/app/views/password_resets/edit.html.erb @@ -0,0 +1,20 @@ +<% provide(:title, 'Reset password') %> +

    Reset password

    + +
    +
    + <%= form_with(model: @user, url: password_reset_path(params[:id])) do |f| %> + <%= render 'shared/error_messages', object: f.object %> + + <%= hidden_field_tag :email, @user.email %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :password_confirmation, "Confirmation" %> + <%= f.password_field :password_confirmation, class: 'form-control' %> + + <%= f.submit "Update password", class: "btn btn-primary" %> + <% end %> +
    +
    diff --git a/8_0/ch13/app/views/password_resets/new.html.erb b/8_0/ch13/app/views/password_resets/new.html.erb new file mode 100644 index 00000000..4a2753d8 --- /dev/null +++ b/8_0/ch13/app/views/password_resets/new.html.erb @@ -0,0 +1,13 @@ +<% provide(:title, "Forgot password") %> +

    Forgot password

    + +
    +
    + <%= form_with(url: password_resets_path, scope: :password_reset) do |f| %> + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.submit "Submit", class: "btn btn-primary" %> + <% end %> +
    +
    diff --git a/8_0/ch13/app/views/sessions/new.html.erb b/8_0/ch13/app/views/sessions/new.html.erb new file mode 100644 index 00000000..8ca43db4 --- /dev/null +++ b/8_0/ch13/app/views/sessions/new.html.erb @@ -0,0 +1,25 @@ +<% provide(:title, "Log in") %> +

    Log in

    + +
    +
    + <%= form_with(url: login_path, scope: :session) do |f| %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= link_to "(forgot password)", new_password_reset_path %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :remember_me, class: "checkbox inline" do %> + <%= f.check_box :remember_me %> + Remember me on this computer + <% end %> + + <%= f.submit "Log in", class: "btn btn-primary" %> + <% end %> + +

    New user? <%= link_to "Sign up now!", signup_path %>

    +
    +
    diff --git a/8_0/ch13/app/views/shared/_error_messages.html.erb b/8_0/ch13/app/views/shared/_error_messages.html.erb new file mode 100644 index 00000000..211bc9d3 --- /dev/null +++ b/8_0/ch13/app/views/shared/_error_messages.html.erb @@ -0,0 +1,12 @@ +<% if object.errors.any? %> +
    +
    + The form contains <%= pluralize(object.errors.count, "error") %>. +
    + +
    +<% end %> diff --git a/8_0/ch13/app/views/shared/_feed.html.erb b/8_0/ch13/app/views/shared/_feed.html.erb new file mode 100644 index 00000000..d46bc93f --- /dev/null +++ b/8_0/ch13/app/views/shared/_feed.html.erb @@ -0,0 +1,7 @@ +<% if @feed_items.any? %> +
      + <%= render @feed_items %> +
    + <%= will_paginate @feed_items, + params: { controller: :static_pages, action: :home } %> +<% end %> diff --git a/8_0/ch13/app/views/shared/_micropost_form.html.erb b/8_0/ch13/app/views/shared/_micropost_form.html.erb new file mode 100644 index 00000000..d93953e8 --- /dev/null +++ b/8_0/ch13/app/views/shared/_micropost_form.html.erb @@ -0,0 +1,10 @@ +<%= form_with(model: @micropost) do |f| %> + <%= render 'shared/error_messages', object: f.object %> +
    + <%= f.text_area :content, placeholder: "Compose new micropost..." %> +
    + <%= f.submit "Post", class: "btn btn-primary" %> + + <%= f.file_field :image, accept: "image/jpeg,image/gif,image/png" %> + +<% end %> diff --git a/8_0/ch13/app/views/shared/_user_info.html.erb b/8_0/ch13/app/views/shared/_user_info.html.erb new file mode 100644 index 00000000..f4d08eb6 --- /dev/null +++ b/8_0/ch13/app/views/shared/_user_info.html.erb @@ -0,0 +1,4 @@ +<%= link_to gravatar_for(current_user, size: 50), current_user %> +

    <%= current_user.name %>

    +<%= link_to "view my profile", current_user %> +<%= pluralize(current_user.microposts.count, "micropost") %> diff --git a/8_0/ch13/app/views/static_pages/about.html.erb b/8_0/ch13/app/views/static_pages/about.html.erb new file mode 100644 index 00000000..dce8fef2 --- /dev/null +++ b/8_0/ch13/app/views/static_pages/about.html.erb @@ -0,0 +1,10 @@ +<% provide(:title, "About") %> +

    About

    +

    + Ruby on Rails Tutorial + is a book and + screencast + to teach web development with + Ruby on Rails. + This is the sample application for the tutorial. +

    diff --git a/8_0/ch13/app/views/static_pages/contact.html.erb b/8_0/ch13/app/views/static_pages/contact.html.erb new file mode 100644 index 00000000..4a5a35a6 --- /dev/null +++ b/8_0/ch13/app/views/static_pages/contact.html.erb @@ -0,0 +1,6 @@ +<% provide(:title, 'Contact') %> +

    Contact

    +

    + Contact the Ruby on Rails Tutorial about the sample app at the + contact page. +

    diff --git a/8_0/ch13/app/views/static_pages/help.html.erb b/8_0/ch13/app/views/static_pages/help.html.erb new file mode 100644 index 00000000..4d06919d --- /dev/null +++ b/8_0/ch13/app/views/static_pages/help.html.erb @@ -0,0 +1,9 @@ +<% provide(:title, "Help") %> +

    Help

    +

    + Get help on the Ruby on Rails Tutorial at the + Rails Tutorial Help page. + To get help on this sample app, see the + Ruby on Rails Tutorial + book. +

    diff --git a/8_0/ch13/app/views/static_pages/home.html.erb b/8_0/ch13/app/views/static_pages/home.html.erb new file mode 100644 index 00000000..24da17c8 --- /dev/null +++ b/8_0/ch13/app/views/static_pages/home.html.erb @@ -0,0 +1,31 @@ +<% if logged_in? %> +
    + +
    +

    Micropost Feed

    + <%= render 'shared/feed' %> +
    +
    +<% else %> +
    +

    Welcome to the Sample App

    + +

    + This is the home page for the + Ruby on Rails Tutorial + sample application. +

    + + <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %> +
    + + <%= link_to image_tag("rails.svg", alt: "Rails logo", width: "200px"), + "https://rubyonrails.org/" %> +<% end %> diff --git a/8_0/ch13/app/views/user_mailer/account_activation.html.erb b/8_0/ch13/app/views/user_mailer/account_activation.html.erb new file mode 100644 index 00000000..c95644ae --- /dev/null +++ b/8_0/ch13/app/views/user_mailer/account_activation.html.erb @@ -0,0 +1,10 @@ +

    Sample App

    + +

    Hi <%= @user.name %>,

    + +

    +Welcome to the Sample App! Click on the link below to activate your account: +

    + +<%= link_to "Activate", edit_account_activation_url(@user.activation_token, + email: @user.email) %> diff --git a/8_0/ch13/app/views/user_mailer/account_activation.text.erb b/8_0/ch13/app/views/user_mailer/account_activation.text.erb new file mode 100644 index 00000000..fecf2be3 --- /dev/null +++ b/8_0/ch13/app/views/user_mailer/account_activation.text.erb @@ -0,0 +1,5 @@ +Hi <%= @user.name %>, + +Welcome to the Sample App! Click on the link below to activate your account: + +<%= edit_account_activation_url(@user.activation_token, email: @user.email) %> diff --git a/8_0/ch13/app/views/user_mailer/password_reset.html.erb b/8_0/ch13/app/views/user_mailer/password_reset.html.erb new file mode 100644 index 00000000..d5a6c379 --- /dev/null +++ b/8_0/ch13/app/views/user_mailer/password_reset.html.erb @@ -0,0 +1,13 @@ +

    Password reset

    + +

    To reset your password click the link below:

    + +<%= link_to "Reset password", edit_password_reset_url(@user.reset_token, + email: @user.email) %> + +

    This link will expire in two hours.

    + +

    +If you did not request your password to be reset, please ignore this email and +your password will stay as it is. +

    diff --git a/8_0/ch13/app/views/user_mailer/password_reset.text.erb b/8_0/ch13/app/views/user_mailer/password_reset.text.erb new file mode 100644 index 00000000..d44d7fc9 --- /dev/null +++ b/8_0/ch13/app/views/user_mailer/password_reset.text.erb @@ -0,0 +1,8 @@ +To reset your password click the link below: + +<%= edit_password_reset_url(@user.reset_token, email: @user.email) %> + +This link will expire in two hours. + +If you did not request your password to be reset, please ignore this email and +your password will stay as it is. diff --git a/8_0/ch13/app/views/users/_user.html.erb b/8_0/ch13/app/views/users/_user.html.erb new file mode 100644 index 00000000..cf66c339 --- /dev/null +++ b/8_0/ch13/app/views/users/_user.html.erb @@ -0,0 +1,8 @@ +
  • + <%= gravatar_for user, size: 50 %> + <%= link_to user.name, user %> + <% if current_user.admin? && !current_user?(user) %> + | <%= link_to "delete", user, data: { "turbo-method": :delete, + turbo_confirm: "You sure?" } %> + <% end %> +
  • diff --git a/8_0/ch13/app/views/users/edit.html.erb b/8_0/ch13/app/views/users/edit.html.erb new file mode 100644 index 00000000..03bfc46b --- /dev/null +++ b/8_0/ch13/app/views/users/edit.html.erb @@ -0,0 +1,29 @@ +<% provide(:title, "Edit user") %> +

    Update your profile

    + +
    +
    + <%= form_with(model: @user) do |f| %> + <%= render 'shared/error_messages', object: f.object %> + + <%= f.label :name %> + <%= f.text_field :name, class: 'form-control' %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :password_confirmation, "Confirmation" %> + <%= f.password_field :password_confirmation, class: 'form-control' %> + + <%= f.submit "Save changes", class: "btn btn-primary" %> + <% end %> + +
    + <%= gravatar_for @user %> + change +
    +
    +
    diff --git a/8_0/ch13/app/views/users/index.html.erb b/8_0/ch13/app/views/users/index.html.erb new file mode 100644 index 00000000..5b6dbaeb --- /dev/null +++ b/8_0/ch13/app/views/users/index.html.erb @@ -0,0 +1,10 @@ +<% provide(:title, 'All users') %> +

    All users

    + +<%= will_paginate %> + + + +<%= will_paginate %> diff --git a/8_0/ch13/app/views/users/new.html.erb b/8_0/ch13/app/views/users/new.html.erb new file mode 100644 index 00000000..8b436c56 --- /dev/null +++ b/8_0/ch13/app/views/users/new.html.erb @@ -0,0 +1,23 @@ +<% provide(:title, 'Sign up') %> +

    Sign up

    + +
    +
    + <%= form_with(model: @user) do |f| %> + <%= render 'shared/error_messages', object: f.object %> + <%= f.label :name %> + <%= f.text_field :name, class: 'form-control' %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :password_confirmation, "Confirmation" %> + <%= f.password_field :password_confirmation, class: 'form-control' %> + + <%= f.submit "Create my account", class: "btn btn-primary" %> + <% end %> +
    +
    diff --git a/8_0/ch13/app/views/users/show.html.erb b/8_0/ch13/app/views/users/show.html.erb new file mode 100644 index 00000000..9065d9a1 --- /dev/null +++ b/8_0/ch13/app/views/users/show.html.erb @@ -0,0 +1,20 @@ +<% provide(:title, @user.name) %> +
    + +
    + <% if @user.microposts.any? %> +

    Microposts (<%= @user.microposts.count %>)

    +
      + <%= render @microposts %> +
    + <%= will_paginate @microposts %> + <% end %> +
    +
    diff --git a/8_0/ch13/bin/bundle b/8_0/ch13/bin/bundle new file mode 100755 index 00000000..981e650b --- /dev/null +++ b/8_0/ch13/bin/bundle @@ -0,0 +1,114 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + requirement = bundler_gem_version.approximate_recommendation + + return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0") + + requirement += ".a" if bundler_gem_version.prerelease? + + requirement + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/8_0/ch13/bin/dev b/8_0/ch13/bin/dev new file mode 100755 index 00000000..5f91c205 --- /dev/null +++ b/8_0/ch13/bin/dev @@ -0,0 +1,2 @@ +#!/usr/bin/env ruby +exec "./bin/rails", "server", *ARGV diff --git a/8_0/ch13/bin/importmap b/8_0/ch13/bin/importmap new file mode 100755 index 00000000..36502ab1 --- /dev/null +++ b/8_0/ch13/bin/importmap @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require_relative "../config/application" +require "importmap/commands" diff --git a/8_0/ch13/bin/rails b/8_0/ch13/bin/rails new file mode 100755 index 00000000..efc03774 --- /dev/null +++ b/8_0/ch13/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/8_0/ch13/bin/rake b/8_0/ch13/bin/rake new file mode 100755 index 00000000..4fbf10b9 --- /dev/null +++ b/8_0/ch13/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/8_0/ch13/bin/render-build.sh b/8_0/ch13/bin/render-build.sh new file mode 100644 index 00000000..b0156aa6 --- /dev/null +++ b/8_0/ch13/bin/render-build.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# exit on error +set -o errexit +bundle install +bundle exec rails assets:precompile +bundle exec rails assets:clean +DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rails db:migrate:reset +bundle exec rails db:seed diff --git a/8_0/ch13/bin/rubocop b/8_0/ch13/bin/rubocop new file mode 100755 index 00000000..40330c0f --- /dev/null +++ b/8_0/ch13/bin/rubocop @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +# explicit rubocop config increases performance slightly while avoiding config confusion. +ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) + +load Gem.bin_path("rubocop", "rubocop") diff --git a/8_0/ch13/bin/setup b/8_0/ch13/bin/setup new file mode 100755 index 00000000..be3db3c0 --- /dev/null +++ b/8_0/ch13/bin/setup @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +require "fileutils" + +APP_ROOT = File.expand_path("..", __dir__) + +def system!(*args) + system(*args, exception: true) +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts "== Installing dependencies ==" + system("bundle check") || system!("bundle install") + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + puts "\n== Removing old logs and tempfiles ==" + system! "bin/rails log:clear tmp:clear" + + unless ARGV.include?("--skip-server") + puts "\n== Starting development server ==" + STDOUT.flush # flush the output before exec(2) so that it displays + exec "bin/dev" + end +end diff --git a/8_0/ch13/config.ru b/8_0/ch13/config.ru new file mode 100644 index 00000000..4a3c09a6 --- /dev/null +++ b/8_0/ch13/config.ru @@ -0,0 +1,6 @@ +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/8_0/ch13/config/application.rb b/8_0/ch13/config/application.rb new file mode 100644 index 00000000..7e1f5d14 --- /dev/null +++ b/8_0/ch13/config/application.rb @@ -0,0 +1,10 @@ +require_relative "boot" +require "rails/all" +Bundler.require(*Rails.groups) + +module SampleApp + class Application < Rails::Application + config.load_defaults 8.0 + config.active_storage.variant_processor = :mini_magick + end +end diff --git a/8_0/ch13/config/boot.rb b/8_0/ch13/config/boot.rb new file mode 100644 index 00000000..988a5ddc --- /dev/null +++ b/8_0/ch13/config/boot.rb @@ -0,0 +1,4 @@ +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "bundler/setup" # Set up gems listed in the Gemfile. +require "bootsnap/setup" # Speed up boot time by caching expensive operations. diff --git a/8_0/ch13/config/cable.yml b/8_0/ch13/config/cable.yml new file mode 100644 index 00000000..ae7a2256 --- /dev/null +++ b/8_0/ch13/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: codespaces_try_rails_production diff --git a/8_0/ch13/config/credentials.yml.enc b/8_0/ch13/config/credentials.yml.enc new file mode 100644 index 00000000..339d64d2 --- /dev/null +++ b/8_0/ch13/config/credentials.yml.enc @@ -0,0 +1 @@ +NCs9M8uakg1CDYJENuGaipzymY8uG6i5gzghsy4npa3gYmGfYKPHMnEcJHCVHlM3gxDNJBteUqkRCxHQ5WQId8hzgxoQCL5RX7rtQ8XUzkJwgSUjKvbCrG5atjKkN9XuL91is5vtEN5W7vEz3qxN7Rp33QbNFiDfLs71F3zHVDYEMi6Dy1QMhVCa1v/tyRjBu/5m37RuiYF5FzWJnh4LhexSVr7Agm4LRLLN6qLCpoAe6D3TPHHBdtu7Pvy8jlrEfnnkUJ61wrj8+kOL4uLtpFShdYKhDkoTjQk7TCOgWyifnHAXazkBZoJZ1QYv/tZ9/ZoHvMEQ2dtGHlyTX8fbKtI13d2CFjGaDNKvRuurxE8dbeyiTfbycvve46+cz9OTlmhh0SGt24R1WV6GBKoDUeoHrIiBvW5qmKka--dQ0rodxmfgFLg2y/--t2x0gQjh8FYlk8v79vLSNg== \ No newline at end of file diff --git a/8_0/ch13/config/database.yml b/8_0/ch13/config/database.yml new file mode 100644 index 00000000..fcba57f1 --- /dev/null +++ b/8_0/ch13/config/database.yml @@ -0,0 +1,25 @@ +# SQLite. Versions 3.8.0 and up are supported. +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem "sqlite3" +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/8_0/ch13/config/environment.rb b/8_0/ch13/config/environment.rb new file mode 100644 index 00000000..cac53157 --- /dev/null +++ b/8_0/ch13/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/8_0/ch13/config/environments/development.rb b/8_0/ch13/config/environments/development.rb new file mode 100644 index 00000000..6e0241dd --- /dev/null +++ b/8_0/ch13/config/environments/development.rb @@ -0,0 +1,90 @@ +require 'active_support/core_ext/integer/time' + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Make code changes take effect immediately without server restart. + config.enable_reloading = true + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing. + config.server_timing = true + + # Add the following line to disable forgery_protection_origin_check + config.action_controller.forgery_protection_origin_check = false + + # Enable/disable Action Controller caching. By default Action Controller caching is disabled. + # Run rails dev:cache to toggle Action Controller caching. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + config.public_file_server.headers = { 'cache-control' => "public, max-age=#{2.days.to_i}" } + else + config.action_controller.perform_caching = false + end + + # Change to :null_store to avoid any caching. + config.cache_store = :memory_store + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Make template changes take effect immediately. + config.action_mailer.perform_caching = false + + # Set localhost to be used by links generated in mailer templates. + host = 'example.com' # ここをコピペすると失敗します。自分の環境のホストに変えてください。 + # クラウドIDEの場合は以下をお使いください + config.action_mailer.default_url_options = { host: host, protocol: 'https' } + # localhostで開発している場合は以下をお使いください + # config.action_mailer.default_url_options = { host: host, protocol: 'http' } + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Append comments with runtime information tags to SQL queries in logs. + config.active_record.query_log_tags_enabled = true + + # Highlight code that enqueued background job in logs. + config.active_job.verbose_enqueue_logs = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true + + # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. + # config.generators.apply_rubocop_autocorrect_after_generate! + + pf_domain = ENV['GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN'] + config.action_dispatch.default_headers = { + 'X-Frame-Options' => "ALLOW-FROM #{pf_domain}" + } + + # Allow requests from our preview domain. + pf_host = "#{ENV['CODESPACE_NAME']}-3000.#{pf_domain}" + config.hosts << pf_host + + config.action_cable.allowed_request_origins = ["https://#{pf_host}"] +end diff --git a/8_0/ch13/config/environments/production.rb b/8_0/ch13/config/environments/production.rb new file mode 100644 index 00000000..6ed5c3fa --- /dev/null +++ b/8_0/ch13/config/environments/production.rb @@ -0,0 +1,94 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.enable_reloading = false + + # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). + config.eager_load = true + + # Full error reports are disabled. + config.consider_all_requests_local = false + + # Turn on fragment caching in view templates. + config.action_controller.perform_caching = true + + # Cache assets for far-future expiry since they are all digest stamped. + config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" } + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = "http://assets.example.com" + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Assume all access to the app is happening through a SSL-terminating reverse proxy. + config.assume_ssl = true + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = true + + # Skip http-to-https redirect for the default health check endpoint. + # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } + + # Log to STDOUT with the current request id as a default log tag. + config.log_tags = [ :request_id ] + config.logger = ActiveSupport::TaggedLogging.logger(STDOUT) + + # Change to "debug" to log everything (including potentially personally-identifiable information!) + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + + # Prevent health checks from clogging up the logs. + config.silence_healthcheck_path = "/up" + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Replace the default in-process memory cache store with a durable alternative. + # config.cache_store = :mem_cache_store + + # Replace the default in-process and non-durable queuing backend for Active Job. + # config.active_job.queue_adapter = :resque + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + config.action_mailer.raise_delivery_errors = true + host = 'https://<あなたのRenderアプリ名>.onrender.com' + config.action_mailer.default_url_options = { host: host } + # Mailgun API設定 + config.action_mailer.delivery_method = :mailgun + config.action_mailer.mailgun_settings = { + api_key: ENV['MAILGUN_API_KEY'], + domain: ENV['MAILGUN_DOMAIN'] + } + + # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. + # config.action_mailer.smtp_settings = { + # user_name: Rails.application.credentials.dig(:smtp, :user_name), + # password: Rails.application.credentials.dig(:smtp, :password), + # address: "smtp.example.com", + # port: 587, + # authentication: :plain + # } + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Only use :id for inspections in production. + config.active_record.attributes_for_inspect = [ :id ] + + # Enable DNS rebinding protection and other `Host` header attacks. + # config.hosts = [ + # "example.com", # Allow requests from example.com + # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` + # ] + # + # Skip DNS rebinding protection for the default health check endpoint. + # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } +end diff --git a/8_0/ch13/config/environments/test.rb b/8_0/ch13/config/environments/test.rb new file mode 100644 index 00000000..0ebe3b4e --- /dev/null +++ b/8_0/ch13/config/environments/test.rb @@ -0,0 +1,53 @@ +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # While tests run files are not watched, reloading is not necessary. + config.enable_reloading = false + + # Eager loading loads your entire application. When running a single test locally, + # this is usually not necessary, and can slow down your test suite. However, it's + # recommended that you enable it in continuous integration systems to ensure eager + # loading is working properly before deploying your code. + config.eager_load = ENV["CI"].present? + + # Configure public file server for tests with cache-control for performance. + config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } + + # Show full error reports. + config.consider_all_requests_local = true + config.cache_store = :null_store + + # Render exception templates for rescuable exceptions and raise for other exceptions. + config.action_dispatch.show_exceptions = :none + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :test + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true +end diff --git a/8_0/ch13/config/importmap.rb b/8_0/ch13/config/importmap.rb new file mode 100644 index 00000000..caf3ed06 --- /dev/null +++ b/8_0/ch13/config/importmap.rb @@ -0,0 +1,8 @@ +# Pin npm packages by running ./bin/importmap + +pin "application", preload: true +pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true +pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true +pin_all_from "app/javascript/controllers", under: "controllers" +pin_all_from "app/javascript/custom", under: "custom" diff --git a/8_0/ch13/config/initializers/assets.rb b/8_0/ch13/config/initializers/assets.rb new file mode 100644 index 00000000..48732442 --- /dev/null +++ b/8_0/ch13/config/initializers/assets.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = "1.0" + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path diff --git a/8_0/ch13/config/initializers/content_security_policy.rb b/8_0/ch13/config/initializers/content_security_policy.rb new file mode 100644 index 00000000..b3076b38 --- /dev/null +++ b/8_0/ch13/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header + +# Rails.application.configure do +# config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end +# +# # Generate session nonces for permitted importmap, inline scripts, and inline styles. +# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } +# config.content_security_policy_nonce_directives = %w(script-src style-src) +# +# # Report violations without enforcing the policy. +# # config.content_security_policy_report_only = true +# end diff --git a/8_0/ch13/config/initializers/filter_parameter_logging.rb b/8_0/ch13/config/initializers/filter_parameter_logging.rb new file mode 100644 index 00000000..c0b717f7 --- /dev/null +++ b/8_0/ch13/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. +# Use this to limit dissemination of sensitive information. +# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. +Rails.application.config.filter_parameters += [ + :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc +] diff --git a/8_0/ch13/config/initializers/inflections.rb b/8_0/ch13/config/initializers/inflections.rb new file mode 100644 index 00000000..3860f659 --- /dev/null +++ b/8_0/ch13/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym "RESTful" +# end diff --git a/8_0/ch13/config/initializers/new_framework_defaults_8_0.rb b/8_0/ch13/config/initializers/new_framework_defaults_8_0.rb new file mode 100644 index 00000000..92efa951 --- /dev/null +++ b/8_0/ch13/config/initializers/new_framework_defaults_8_0.rb @@ -0,0 +1,30 @@ +# Be sure to restart your server when you modify this file. +# +# This file eases your Rails 8.0 framework defaults upgrade. +# +# Uncomment each configuration one by one to switch to the new default. +# Once your application is ready to run with all new defaults, you can remove +# this file and set the `config.load_defaults` to `8.0`. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. +# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html + +### +# Specifies whether `to_time` methods preserve the UTC offset of their receivers or preserves the timezone. +# If set to `:zone`, `to_time` methods will use the timezone of their receivers. +# If set to `:offset`, `to_time` methods will use the UTC offset. +# If `false`, `to_time` methods will convert to the local system UTC offset instead. +#++ +# Rails.application.config.active_support.to_time_preserves_timezone = :zone + +### +# When both `If-Modified-Since` and `If-None-Match` are provided by the client +# only consider `If-None-Match` as specified by RFC 7232 Section 6. +# If set to `false` both conditions need to be satisfied. +#++ +# Rails.application.config.action_dispatch.strict_freshness = true + +### +# Set `Regexp.timeout` to `1`s by default to improve security over Regexp Denial-of-Service attacks. +#++ +# Regexp.timeout = 1 diff --git a/8_0/ch13/config/initializers/permissions_policy.rb b/8_0/ch13/config/initializers/permissions_policy.rb new file mode 100644 index 00000000..00f64d71 --- /dev/null +++ b/8_0/ch13/config/initializers/permissions_policy.rb @@ -0,0 +1,11 @@ +# Define an application-wide HTTP permissions policy. For further +# information see https://developers.google.com/web/updates/2018/06/feature-policy +# +# Rails.application.config.permissions_policy do |f| +# f.camera :none +# f.gyroscope :none +# f.microphone :none +# f.usb :none +# f.fullscreen :self +# f.payment :self, "https://secure.example.com" +# end diff --git a/8_0/ch13/config/locales/en.yml b/8_0/ch13/config/locales/en.yml new file mode 100644 index 00000000..8ca56fc7 --- /dev/null +++ b/8_0/ch13/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# "true": "foo" +# +# To learn more, please read the Rails Internationalization guide +# available at https://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/8_0/ch13/config/puma.rb b/8_0/ch13/config/puma.rb new file mode 100644 index 00000000..a248513b --- /dev/null +++ b/8_0/ch13/config/puma.rb @@ -0,0 +1,41 @@ +# This configuration file will be evaluated by Puma. The top-level methods that +# are invoked here are part of Puma's configuration DSL. For more information +# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. +# +# Puma starts a configurable number of processes (workers) and each process +# serves each request in a thread from an internal thread pool. +# +# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You +# should only set this value when you want to run 2 or more workers. The +# default is already 1. +# +# The ideal number of threads per worker depends both on how much time the +# application spends waiting for IO operations and on how much you wish to +# prioritize throughput over latency. +# +# As a rule of thumb, increasing the number of threads will increase how much +# traffic a given process can handle (throughput), but due to CRuby's +# Global VM Lock (GVL) it has diminishing returns and will degrade the +# response time (latency) of the application. +# +# The default is set to 3 threads as it's deemed a decent compromise between +# throughput and latency for the average Rails application. +# +# Any libraries that use a connection pool or another resource pool should +# be configured to provide at least as many connections as the number of +# threads. This includes Active Record's `pool` parameter in `database.yml`. +threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +port ENV.fetch("PORT", 3000) + +# Allow puma to be restarted by `bin/rails restart` command. +plugin :tmp_restart + +# Run the Solid Queue supervisor inside of Puma for single-server deployments +plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] + +# Specify the PID file. Defaults to tmp/pids/server.pid in development. +# In other environments, only set the PID file if requested. +pidfile ENV["PIDFILE"] if ENV["PIDFILE"] diff --git a/8_0/ch13/config/routes.rb b/8_0/ch13/config/routes.rb new file mode 100644 index 00000000..35199cf5 --- /dev/null +++ b/8_0/ch13/config/routes.rb @@ -0,0 +1,15 @@ +Rails.application.routes.draw do + root "static_pages#home" + get "/help", to: "static_pages#help" + get "/about", to: "static_pages#about" + get "/contact", to: "static_pages#contact" + get "/signup", to: "users#new" + get "/login", to: "sessions#new" + post "/login", to: "sessions#create" + delete "/logout", to: "sessions#destroy" + resources :users + resources :account_activations, only: [:edit] + resources :password_resets, only: [:new, :create, :edit, :update] + resources :microposts, only: [:create, :destroy] + get "/microposts", to: "static_pages#home" +end diff --git a/8_0/ch13/config/storage.yml b/8_0/ch13/config/storage.yml new file mode 100644 index 00000000..4942ab66 --- /dev/null +++ b/8_0/ch13/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket-<%= Rails.env %> + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket-<%= Rails.env %> + +# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name-<%= Rails.env %> + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/8_0/ch13/db/migrate/20260512080056_create_users.rb b/8_0/ch13/db/migrate/20260512080056_create_users.rb new file mode 100644 index 00000000..3b80eb00 --- /dev/null +++ b/8_0/ch13/db/migrate/20260512080056_create_users.rb @@ -0,0 +1,10 @@ +class CreateUsers < ActiveRecord::Migration[8.0] + def change + create_table :users do |t| + t.string :name + t.string :email + + t.timestamps + end + end +end diff --git a/8_0/ch13/db/migrate/20260512080840_add_index_to_users_email.rb b/8_0/ch13/db/migrate/20260512080840_add_index_to_users_email.rb new file mode 100644 index 00000000..a8e4ed8e --- /dev/null +++ b/8_0/ch13/db/migrate/20260512080840_add_index_to_users_email.rb @@ -0,0 +1,5 @@ +class AddIndexToUsersEmail < ActiveRecord::Migration[8.0] + def change + add_index :users, :email, unique: true + end +end diff --git a/8_0/ch13/db/migrate/20260512081511_add_password_digest_to_users.rb b/8_0/ch13/db/migrate/20260512081511_add_password_digest_to_users.rb new file mode 100644 index 00000000..81c7c9e0 --- /dev/null +++ b/8_0/ch13/db/migrate/20260512081511_add_password_digest_to_users.rb @@ -0,0 +1,5 @@ +class AddPasswordDigestToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :password_digest, :string + end +end diff --git a/8_0/ch13/db/migrate/20260626064202_add_remember_digest_to_users.rb b/8_0/ch13/db/migrate/20260626064202_add_remember_digest_to_users.rb new file mode 100644 index 00000000..7a464448 --- /dev/null +++ b/8_0/ch13/db/migrate/20260626064202_add_remember_digest_to_users.rb @@ -0,0 +1,5 @@ +class AddRememberDigestToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :remember_digest, :string + end +end diff --git a/8_0/ch13/db/migrate/20260626075635_add_admin_to_users.rb b/8_0/ch13/db/migrate/20260626075635_add_admin_to_users.rb new file mode 100644 index 00000000..c1f08cf5 --- /dev/null +++ b/8_0/ch13/db/migrate/20260626075635_add_admin_to_users.rb @@ -0,0 +1,5 @@ +class AddAdminToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :admin, :boolean, default: false + end +end diff --git a/8_0/ch13/db/migrate/20260630012638_add_activation_to_users.rb b/8_0/ch13/db/migrate/20260630012638_add_activation_to_users.rb new file mode 100644 index 00000000..be38e387 --- /dev/null +++ b/8_0/ch13/db/migrate/20260630012638_add_activation_to_users.rb @@ -0,0 +1,7 @@ +class AddActivationToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :activation_digest, :string + add_column :users, :activated, :boolean, default: false + add_column :users, :activated_at, :datetime + end +end diff --git a/8_0/ch13/db/migrate/20260630015608_add_reset_to_users.rb b/8_0/ch13/db/migrate/20260630015608_add_reset_to_users.rb new file mode 100644 index 00000000..b5e1c928 --- /dev/null +++ b/8_0/ch13/db/migrate/20260630015608_add_reset_to_users.rb @@ -0,0 +1,6 @@ +class AddResetToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :reset_digest, :string + add_column :users, :reset_sent_at, :datetime + end +end diff --git a/8_0/ch13/db/migrate/20260630022950_create_microposts.rb b/8_0/ch13/db/migrate/20260630022950_create_microposts.rb new file mode 100644 index 00000000..fffe3f55 --- /dev/null +++ b/8_0/ch13/db/migrate/20260630022950_create_microposts.rb @@ -0,0 +1,11 @@ +class CreateMicroposts < ActiveRecord::Migration[8.0] + def change + create_table :microposts do |t| + t.text :content + t.references :user, null: false, foreign_key: true + + t.timestamps + end + add_index :microposts, [:user_id, :created_at] + end +end diff --git a/8_0/ch13/db/migrate/20260630031036_create_active_storage_tables.active_storage.rb b/8_0/ch13/db/migrate/20260630031036_create_active_storage_tables.active_storage.rb new file mode 100644 index 00000000..6bd8bd08 --- /dev/null +++ b/8_0/ch13/db/migrate/20260630031036_create_active_storage_tables.active_storage.rb @@ -0,0 +1,57 @@ +# This migration comes from active_storage (originally 20170806125915) +class CreateActiveStorageTables < ActiveRecord::Migration[7.0] + def change + # Use Active Record's configured type for primary and foreign keys + primary_key_type, foreign_key_type = primary_and_foreign_key_types + + create_table :active_storage_blobs, id: primary_key_type do |t| + t.string :key, null: false + t.string :filename, null: false + t.string :content_type + t.text :metadata + t.string :service_name, null: false + t.bigint :byte_size, null: false + t.string :checksum + + if connection.supports_datetime_with_precision? + t.datetime :created_at, precision: 6, null: false + else + t.datetime :created_at, null: false + end + + t.index [ :key ], unique: true + end + + create_table :active_storage_attachments, id: primary_key_type do |t| + t.string :name, null: false + t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type + t.references :blob, null: false, type: foreign_key_type + + if connection.supports_datetime_with_precision? + t.datetime :created_at, precision: 6, null: false + else + t.datetime :created_at, null: false + end + + t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + + create_table :active_storage_variant_records, id: primary_key_type do |t| + t.belongs_to :blob, null: false, index: false, type: foreign_key_type + t.string :variation_digest, null: false + + t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + end + + private + def primary_and_foreign_key_types + config = Rails.configuration.generators + setting = config.options[config.orm][:primary_key_type] + primary_key_type = setting || :primary_key + foreign_key_type = setting || :bigint + [ primary_key_type, foreign_key_type ] + end +end diff --git a/8_0/ch13/db/schema.rb b/8_0/ch13/db/schema.rb new file mode 100644 index 00000000..292fbba9 --- /dev/null +++ b/8_0/ch13/db/schema.rb @@ -0,0 +1,70 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[8.0].define(version: 2026_06_30_031036) do + create_table "active_storage_attachments", force: :cascade do |t| + t.string "name", null: false + t.string "record_type", null: false + t.bigint "record_id", null: false + t.bigint "blob_id", null: false + t.datetime "created_at", null: false + t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" + t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true + end + + create_table "active_storage_blobs", force: :cascade do |t| + t.string "key", null: false + t.string "filename", null: false + t.string "content_type" + t.text "metadata" + t.string "service_name", null: false + t.bigint "byte_size", null: false + t.string "checksum" + t.datetime "created_at", null: false + t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true + end + + create_table "active_storage_variant_records", force: :cascade do |t| + t.bigint "blob_id", null: false + t.string "variation_digest", null: false + t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true + end + + create_table "microposts", force: :cascade do |t| + t.text "content" + t.integer "user_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["user_id", "created_at"], name: "index_microposts_on_user_id_and_created_at" + t.index ["user_id"], name: "index_microposts_on_user_id" + end + + create_table "users", force: :cascade do |t| + t.string "name" + t.string "email" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "password_digest" + t.string "remember_digest" + t.boolean "admin", default: false + t.string "activation_digest" + t.boolean "activated", default: false + t.datetime "activated_at" + t.string "reset_digest" + t.datetime "reset_sent_at" + t.index ["email"], name: "index_users_on_email", unique: true + end + + add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" + add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" + add_foreign_key "microposts", "users" +end diff --git a/8_0/ch13/db/seeds.rb b/8_0/ch13/db/seeds.rb new file mode 100644 index 00000000..f5b6ee93 --- /dev/null +++ b/8_0/ch13/db/seeds.rb @@ -0,0 +1,28 @@ +# メインのサンプルユーザーを1人作成する +User.create!(name: "Example User", + email: "example@railstutorial.org", + password: "foobar", + password_confirmation: "foobar", + admin: true, + activated: true, + activated_at: Time.zone.now) + +# 追加のユーザーをまとめて生成する +99.times do |n| + name = Faker::Name.name + email = "example-#{n+1}@railstutorial.org" + password = "password" + User.create!(name: name, + email: email, + password: password, + password_confirmation: password, + activated: true, + activated_at: Time.zone.now) +end + +# ユーザーの一部を対象にマイクロポストを生成する +users = User.order(:created_at).take(6) +50.times do + content = Faker::Lorem.sentence(word_count: 5) + users.each { |user| user.microposts.create!(content: content) } +end \ No newline at end of file diff --git a/8_0/ch13/lib/assets/.keep b/8_0/ch13/lib/assets/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/lib/tasks/.keep b/8_0/ch13/lib/tasks/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/log/.keep b/8_0/ch13/log/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/public/400.html b/8_0/ch13/public/400.html new file mode 100644 index 00000000..282dbc8c --- /dev/null +++ b/8_0/ch13/public/400.html @@ -0,0 +1,114 @@ + + + + + + + The server cannot process the request due to a client error (400 Bad Request) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    The server cannot process the request due to a client error. Please check the request and try again. If you’re the application owner check the logs for more information.

    +
    +
    + + + + diff --git a/8_0/ch13/public/404.html b/8_0/ch13/public/404.html new file mode 100644 index 00000000..c0670bc8 --- /dev/null +++ b/8_0/ch13/public/404.html @@ -0,0 +1,114 @@ + + + + + + + The page you were looking for doesn’t exist (404 Not found) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    The page you were looking for doesn’t exist. You may have mistyped the address or the page may have moved. If you’re the application owner check the logs for more information.

    +
    +
    + + + + diff --git a/8_0/ch13/public/406-unsupported-browser.html b/8_0/ch13/public/406-unsupported-browser.html new file mode 100644 index 00000000..9532a9cc --- /dev/null +++ b/8_0/ch13/public/406-unsupported-browser.html @@ -0,0 +1,114 @@ + + + + + + + Your browser is not supported (406 Not Acceptable) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    Your browser is not supported.
    Please upgrade your browser to continue.

    +
    +
    + + + + diff --git a/8_0/ch13/public/422.html b/8_0/ch13/public/422.html new file mode 100644 index 00000000..8bcf0601 --- /dev/null +++ b/8_0/ch13/public/422.html @@ -0,0 +1,114 @@ + + + + + + + The change you wanted was rejected (422 Unprocessable Entity) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    The change you wanted was rejected. Maybe you tried to change something you didn’t have access to. If you’re the application owner check the logs for more information.

    +
    +
    + + + + diff --git a/8_0/ch13/public/500.html b/8_0/ch13/public/500.html new file mode 100644 index 00000000..d77718c3 --- /dev/null +++ b/8_0/ch13/public/500.html @@ -0,0 +1,114 @@ + + + + + + + We’re sorry, but something went wrong (500 Internal Server Error) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    We’re sorry, but something went wrong.
    If you’re the application owner check the logs for more information.

    +
    +
    + + + + diff --git a/8_0/ch13/public/apple-touch-icon-precomposed.png b/8_0/ch13/public/apple-touch-icon-precomposed.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/public/apple-touch-icon.png b/8_0/ch13/public/apple-touch-icon.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/public/favicon.ico b/8_0/ch13/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/public/icon.png b/8_0/ch13/public/icon.png new file mode 100644 index 00000000..c4c9dbfb Binary files /dev/null and b/8_0/ch13/public/icon.png differ diff --git a/8_0/ch13/public/icon.svg b/8_0/ch13/public/icon.svg new file mode 100644 index 00000000..04b34bf8 --- /dev/null +++ b/8_0/ch13/public/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/8_0/ch13/public/railstutorial.png b/8_0/ch13/public/railstutorial.png new file mode 100644 index 00000000..70835e6b Binary files /dev/null and b/8_0/ch13/public/railstutorial.png differ diff --git a/8_0/ch13/public/robots.txt b/8_0/ch13/public/robots.txt new file mode 100644 index 00000000..c19f78ab --- /dev/null +++ b/8_0/ch13/public/robots.txt @@ -0,0 +1 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/8_0/ch13/storage/.keep b/8_0/ch13/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/test/application_system_test_case.rb b/8_0/ch13/test/application_system_test_case.rb new file mode 100644 index 00000000..d19212ab --- /dev/null +++ b/8_0/ch13/test/application_system_test_case.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/8_0/ch13/test/channels/application_cable/connection_test.rb b/8_0/ch13/test/channels/application_cable/connection_test.rb new file mode 100644 index 00000000..800405f1 --- /dev/null +++ b/8_0/ch13/test/channels/application_cable/connection_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase + # test "connects with cookies" do + # cookies.signed[:user_id] = 42 + # + # connect + # + # assert_equal connection.user_id, "42" + # end +end diff --git a/8_0/ch13/test/controllers/.keep b/8_0/ch13/test/controllers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/test/controllers/account_activations_controller_test.rb b/8_0/ch13/test/controllers/account_activations_controller_test.rb new file mode 100644 index 00000000..bcd21995 --- /dev/null +++ b/8_0/ch13/test/controllers/account_activations_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class AccountActivationsControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/8_0/ch13/test/controllers/hello_codespaces_controller_test.rb b/8_0/ch13/test/controllers/hello_codespaces_controller_test.rb new file mode 100644 index 00000000..54353ea6 --- /dev/null +++ b/8_0/ch13/test/controllers/hello_codespaces_controller_test.rb @@ -0,0 +1,8 @@ +require "test_helper" + +class HelloCodespacesControllerTest < ActionDispatch::IntegrationTest + # test "should get index" do + # get "/" + # assert_response :success + # end +end diff --git a/8_0/ch13/test/controllers/microposts_controller_test.rb b/8_0/ch13/test/controllers/microposts_controller_test.rb new file mode 100644 index 00000000..6bea99f9 --- /dev/null +++ b/8_0/ch13/test/controllers/microposts_controller_test.rb @@ -0,0 +1,33 @@ +require "test_helper" + +class MicropostsControllerTest < ActionDispatch::IntegrationTest + + def setup + @micropost = microposts(:orange) + end + + test "should redirect create when not logged in" do + assert_no_difference 'Micropost.count' do + post microposts_path, params: { micropost: { content: "Lorem ipsum" } } + end + assert_redirected_to login_url + end + + test "should redirect destroy when not logged in" do + assert_no_difference 'Micropost.count' do + delete micropost_path(@micropost) + end + assert_response :see_other + assert_redirected_to login_url + end + + test "should redirect destroy for wrong micropost" do + log_in_as(users(:michael)) + micropost = microposts(:ants) + assert_no_difference 'Micropost.count' do + delete micropost_path(micropost) + end + assert_response :see_other + assert_redirected_to root_url + end +end diff --git a/8_0/ch13/test/controllers/sessions_controller_test.rb b/8_0/ch13/test/controllers/sessions_controller_test.rb new file mode 100644 index 00000000..8cc3f29e --- /dev/null +++ b/8_0/ch13/test/controllers/sessions_controller_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +class SessionsControllerTest < ActionDispatch::IntegrationTest + + test "should get new" do + get login_path + assert_response :success + end +end diff --git a/8_0/ch13/test/controllers/static_pages_controller_test.rb b/8_0/ch13/test/controllers/static_pages_controller_test.rb new file mode 100644 index 00000000..847856a3 --- /dev/null +++ b/8_0/ch13/test/controllers/static_pages_controller_test.rb @@ -0,0 +1,28 @@ +require "test_helper" + +class StaticPagesControllerTest < ActionDispatch::IntegrationTest + + test "should get home" do + get root_path + assert_response :success + assert_select "title", "Ruby on Rails Tutorial Sample App" + end + + test "should get help" do + get help_path + assert_response :success + assert_select "title", "Help | Ruby on Rails Tutorial Sample App" + end + + test "should get about" do + get about_path + assert_response :success + assert_select "title", "About | Ruby on Rails Tutorial Sample App" + end + + test "should get contact" do + get contact_path + assert_response :success + assert_select "title", "Contact | Ruby on Rails Tutorial Sample App" + end +end diff --git a/8_0/ch13/test/controllers/users_controller_test.rb b/8_0/ch13/test/controllers/users_controller_test.rb new file mode 100644 index 00000000..ffd2ec99 --- /dev/null +++ b/8_0/ch13/test/controllers/users_controller_test.rb @@ -0,0 +1,64 @@ +require "test_helper" + +class UsersControllerTest < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + @other_user = users(:archer) + end + + test "should get new" do + get signup_path + assert_response :success + end + + test "should redirect index when not logged in" do + get users_path + assert_redirected_to login_url + end + + test "should redirect edit when not logged in" do + get edit_user_path(@user) + assert_not flash.empty? + assert_redirected_to login_url + end + + test "should redirect update when not logged in" do + patch user_path(@user), params: { user: { name: @user.name, + email: @user.email } } + assert_not flash.empty? + assert_redirected_to login_url + end + + test "should redirect edit when logged in as wrong user" do + log_in_as(@other_user) + get edit_user_path(@user) + assert flash.empty? + assert_redirected_to root_url + end + + test "should redirect update when logged in as wrong user" do + log_in_as(@other_user) + patch user_path(@user), params: { user: { name: @user.name, + email: @user.email } } + assert flash.empty? + assert_redirected_to root_url + end + + test "should redirect destroy when not logged in" do + assert_no_difference 'User.count' do + delete user_path(@user) + end + assert_response :see_other + assert_redirected_to login_url + end + + test "should redirect destroy when logged in as a non-admin" do + log_in_as(@other_user) + assert_no_difference 'User.count' do + delete user_path(@user) + end + assert_response :see_other + assert_redirected_to root_url + end +end diff --git a/8_0/ch13/test/fixtures/files/.keep b/8_0/ch13/test/fixtures/files/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/test/fixtures/microposts.yml b/8_0/ch13/test/fixtures/microposts.yml new file mode 100644 index 00000000..d3beb59b --- /dev/null +++ b/8_0/ch13/test/fixtures/microposts.yml @@ -0,0 +1,41 @@ +orange: + content: "I just ate an orange!" + created_at: <%= 10.minutes.ago %> + user: michael + +tau_manifesto: + content: "Check out the @tauday site by @mhartl: https://tauday.com" + created_at: <%= 3.years.ago %> + user: michael + +cat_video: + content: "Sad cats are sad: https://youtu.be/PKffm2uI4dk" + created_at: <%= 2.hours.ago %> + user: michael + +most_recent: + content: "Writing a short test" + created_at: <%= Time.zone.now %> + user: michael + +<% 30.times do |n| %> +micropost_<%= n %>: + content: <%= Faker::Lorem.sentence(word_count: 5) %> + created_at: <%= 42.days.ago %> + user: michael +<% end %> + +ants: + content: "Oh, is that what you want? Because that's how you get ants!" + created_at: <%= 2.years.ago %> + user: archer + +zone: + content: "Danger zone!" + created_at: <%= 3.days.ago %> + user: archer + +tone: + content: "I'm sorry. Your words made sense, but your sarcastic tone did not." + created_at: <%= 10.minutes.ago %> + user: lana diff --git a/8_0/ch13/test/fixtures/users.yml b/8_0/ch13/test/fixtures/users.yml new file mode 100644 index 00000000..02f3d49a --- /dev/null +++ b/8_0/ch13/test/fixtures/users.yml @@ -0,0 +1,37 @@ +michael: + name: Michael Example + email: michael@example.com + password_digest: <%= User.digest('password') %> + admin: true + activated: true + activated_at: <%= Time.zone.now %> + +archer: + name: Sterling Archer + email: duchess@example.gov + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> + +lana: + name: Lana Kane + email: hands@example.gov + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> + +malory: + name: Malory Archer + email: boss@example.gov + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> + +<% 30.times do |n| %> +user_<%= n %>: + name: <%= "User #{n}" %> + email: <%= "user-#{n}@example.com" %> + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> +<% end %> diff --git a/8_0/ch13/test/helpers/.keep b/8_0/ch13/test/helpers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/test/helpers/sessions_helper_test.rb b/8_0/ch13/test/helpers/sessions_helper_test.rb new file mode 100644 index 00000000..735ff17a --- /dev/null +++ b/8_0/ch13/test/helpers/sessions_helper_test.rb @@ -0,0 +1,19 @@ +require "test_helper" + +class SessionsHelperTest < ActionView::TestCase + + def setup + @user = users(:michael) + remember(@user) + end + + test "current_user returns right user when session is nil" do + assert_equal @user, current_user + assert is_logged_in? + end + + test "current_user returns nil when remember digest is wrong" do + @user.update_attribute(:remember_digest, User.digest(User.new_token)) + assert_nil current_user + end +end diff --git a/8_0/ch13/test/integration/.keep b/8_0/ch13/test/integration/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/test/integration/microposts_interface_test.rb b/8_0/ch13/test/integration/microposts_interface_test.rb new file mode 100644 index 00000000..c6242446 --- /dev/null +++ b/8_0/ch13/test/integration/microposts_interface_test.rb @@ -0,0 +1,52 @@ +require "test_helper" + +class MicropostsInterface < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + log_in_as(@user) + end +end + +class MicropostsInterfaceTest < MicropostsInterface + + test "should paginate microposts" do + get root_path + assert_select 'div.pagination' + end + + test "should show errors but not create micropost on invalid submission" do + assert_no_difference 'Micropost.count' do + post microposts_path, params: { micropost: { content: "" } } + end + assert_select 'div#error_explanation' + assert_select 'a[href=?]', '/?page=2' # 正しいページネーションリンク + end + + test "should create a micropost on valid submission" do + content = "This micropost really ties the room together" + assert_difference 'Micropost.count', 1 do + post microposts_path, params: { micropost: { content: content } } + end + assert_redirected_to root_url + follow_redirect! + assert_match content, response.body + end + + test "should have micropost delete links on own profile page" do + get user_path(@user) + assert_select 'a', text: 'delete' + end + + test "should be able to delete own micropost" do + first_micropost = @user.microposts.paginate(page: 1).first + assert_difference 'Micropost.count', -1 do + delete micropost_path(first_micropost) + end + end + + test "should not have delete links on other user's profile page" do + get user_path(users(:archer)) + assert_select 'a', { text: 'delete', count: 0 } + end +end diff --git a/8_0/ch13/test/integration/password_resets_test.rb b/8_0/ch13/test/integration/password_resets_test.rb new file mode 100644 index 00000000..2daf9c61 --- /dev/null +++ b/8_0/ch13/test/integration/password_resets_test.rb @@ -0,0 +1,98 @@ +require "test_helper" + +class PasswordResets < ActionDispatch::IntegrationTest + + def setup + ActionMailer::Base.deliveries.clear + end +end + +class ForgotPasswordFormTest < PasswordResets + + test "password reset path" do + get new_password_reset_path + assert_template 'password_resets/new' + assert_select 'input[name=?]', 'password_reset[email]' + end + + test "reset path with invalid email" do + post password_resets_path, params: { password_reset: { email: "" } } + assert_response :unprocessable_entity + assert_not flash.empty? + assert_template 'password_resets/new' + end +end + +class PasswordResetForm < PasswordResets + + def setup + super + @user = users(:michael) + post password_resets_path, + params: { password_reset: { email: @user.email } } + @reset_user = assigns(:user) + end +end + +class PasswordFormTest < PasswordResetForm + + test "reset with valid email" do + assert_not_equal @user.reset_digest, @reset_user.reset_digest + assert_equal 1, ActionMailer::Base.deliveries.size + assert_not flash.empty? + assert_redirected_to root_url + end + + test "reset with wrong email" do + get edit_password_reset_path(@reset_user.reset_token, email: "") + assert_redirected_to root_url + end + + test "reset with inactive user" do + @reset_user.toggle!(:activated) + get edit_password_reset_path(@reset_user.reset_token, + email: @reset_user.email) + assert_redirected_to root_url + end + + test "reset with right email but wrong token" do + get edit_password_reset_path('wrong token', email: @reset_user.email) + assert_redirected_to root_url + end + + test "reset with right email and right token" do + get edit_password_reset_path(@reset_user.reset_token, + email: @reset_user.email) + assert_template 'password_resets/edit' + assert_select "input[name=email][type=hidden][value=?]", @reset_user.email + end +end + +class PasswordUpdateTest < PasswordResetForm + + test "update with invalid password and confirmation" do + patch password_reset_path(@reset_user.reset_token), + params: { email: @reset_user.email, + user: { password: "foobaz", + password_confirmation: "barquux" } } + assert_select 'div#error_explanation' + end + + test "update with empty password" do + patch password_reset_path(@reset_user.reset_token), + params: { email: @reset_user.email, + user: { password: "", + password_confirmation: "" } } + assert_select 'div#error_explanation' + end + + test "update with valid password and confirmation" do + patch password_reset_path(@reset_user.reset_token), + params: { email: @reset_user.email, + user: { password: "foobaz", + password_confirmation: "foobaz" } } + assert is_logged_in? + assert_not flash.empty? + assert_redirected_to @reset_user + end +end diff --git a/8_0/ch13/test/integration/site_layout_test.rb b/8_0/ch13/test/integration/site_layout_test.rb new file mode 100644 index 00000000..0f7ca649 --- /dev/null +++ b/8_0/ch13/test/integration/site_layout_test.rb @@ -0,0 +1,13 @@ +require "test_helper" + +class SiteLayoutTest < ActionDispatch::IntegrationTest + + test "layout links" do + get root_path + assert_template 'static_pages/home' + assert_select "a[href=?]", root_path, count: 2 + assert_select "a[href=?]", help_path + assert_select "a[href=?]", about_path + assert_select "a[href=?]", contact_path + end +end diff --git a/8_0/ch13/test/integration/users_edit_test.rb b/8_0/ch13/test/integration/users_edit_test.rb new file mode 100644 index 00000000..23ca6b68 --- /dev/null +++ b/8_0/ch13/test/integration/users_edit_test.rb @@ -0,0 +1,37 @@ +require "test_helper" + +class UsersEditTest < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + end + + test "unsuccessful edit" do + log_in_as(@user) + get edit_user_path(@user) + assert_template 'users/edit' + patch user_path(@user), params: { user: { name: "", + email: "foo@invalid", + password: "foo", + password_confirmation: "bar" } } + + assert_template 'users/edit' + end + + test "successful edit with friendly forwarding" do + get edit_user_path(@user) + log_in_as(@user) + assert_redirected_to edit_user_url(@user) + name = "Foo Bar" + email = "foo@bar.com" + patch user_path(@user), params: { user: { name: name, + email: email, + password: "", + password_confirmation: "" } } + assert_not flash.empty? + assert_redirected_to @user + @user.reload + assert_equal name, @user.name + assert_equal email, @user.email + end +end diff --git a/8_0/ch13/test/integration/users_index_test.rb b/8_0/ch13/test/integration/users_index_test.rb new file mode 100644 index 00000000..63bb8d9e --- /dev/null +++ b/8_0/ch13/test/integration/users_index_test.rb @@ -0,0 +1,34 @@ +require "test_helper" + +class UsersIndexTest < ActionDispatch::IntegrationTest + + def setup + @admin = users(:michael) + @non_admin = users(:archer) + end + + test "index as admin including pagination and delete links" do + log_in_as(@admin) + get users_path + assert_template 'users/index' + assert_select 'div.pagination' + first_page_of_users = User.paginate(page: 1) + first_page_of_users.each do |user| + assert_select 'a[href=?]', user_path(user), text: user.name + unless user == @admin + assert_select 'a[href=?]', user_path(user), text: 'delete' + end + end + assert_difference 'User.count', -1 do + delete user_path(@non_admin) + assert_response :see_other + assert_redirected_to users_url + end + end + + test "index as non-admin" do + log_in_as(@non_admin) + get users_path + assert_select 'a', text: 'delete', count: 0 + end +end diff --git a/8_0/ch13/test/integration/users_login_test.rb b/8_0/ch13/test/integration/users_login_test.rb new file mode 100644 index 00000000..a9e70eaa --- /dev/null +++ b/8_0/ch13/test/integration/users_login_test.rb @@ -0,0 +1,96 @@ +require "test_helper" + +class UsersLogin < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + end +end + +class InvalidPasswordTest < UsersLogin + + test "login path" do + get login_path + assert_template 'sessions/new' + end + + test "login with valid email/invalid password" do + post login_path, params: { session: { email: @user.email, + password: "invalid" } } + assert_not is_logged_in? + assert_template 'sessions/new' + assert_not flash.empty? + get root_path + assert flash.empty? + end +end + +class ValidLogin < UsersLogin + + def setup + super + post login_path, params: { session: { email: @user.email, + password: 'password' } } + end +end + +class ValidLoginTest < ValidLogin + + test "valid login" do + assert is_logged_in? + assert_redirected_to @user + end + + test "redirect after login" do + follow_redirect! + assert_template 'users/show' + assert_select "a[href=?]", login_path, count: 0 + assert_select "a[href=?]", logout_path + assert_select "a[href=?]", user_path(@user) + end +end + +class Logout < ValidLogin + + def setup + super + delete logout_path + end +end + +class LogoutTest < Logout + + test "successful logout" do + assert_not is_logged_in? + assert_response :see_other + assert_redirected_to root_url + end + + test "redirect after logout" do + follow_redirect! + assert_select "a[href=?]", login_path + assert_select "a[href=?]", logout_path, count: 0 + assert_select "a[href=?]", user_path(@user), count: 0 + end + + test "should still work after logout in second window" do + delete logout_path + assert_redirected_to root_url + end +end + +class RememberingTest < UsersLogin + + test "login with remembering" do + log_in_as(@user, remember_me: '1') + assert_not cookies[:remember_token].blank? + end + + test "login without remembering" do + # Cookieを保存してログイン + log_in_as(@user, remember_me: '1') + # Cookieが削除されていることを検証してからログイン + log_in_as(@user, remember_me: '0') + assert cookies[:remember_token].blank? + end +end diff --git a/8_0/ch13/test/integration/users_profile_test.rb b/8_0/ch13/test/integration/users_profile_test.rb new file mode 100644 index 00000000..4a74f1cd --- /dev/null +++ b/8_0/ch13/test/integration/users_profile_test.rb @@ -0,0 +1,22 @@ +require "test_helper" + +class UsersProfileTest < ActionDispatch::IntegrationTest + include ApplicationHelper + + def setup + @user = users(:michael) + end + + test "profile display" do + get user_path(@user) + assert_template 'users/show' + assert_select 'title', full_title(@user.name) + assert_select 'h1', text: @user.name + assert_select 'h1>img.gravatar' + assert_match @user.microposts.count.to_s, response.body + assert_select 'div.pagination' + @user.microposts.paginate(page: 1).each do |micropost| + assert_match micropost.content, response.body + end + end +end diff --git a/8_0/ch13/test/integration/users_signup_test.rb b/8_0/ch13/test/integration/users_signup_test.rb new file mode 100644 index 00000000..92172d43 --- /dev/null +++ b/8_0/ch13/test/integration/users_signup_test.rb @@ -0,0 +1,73 @@ +require "test_helper" + +class UsersSignup < ActionDispatch::IntegrationTest + + def setup + ActionMailer::Base.deliveries.clear + end +end + +class UsersSignupTest < UsersSignup + + test "invalid signup information" do + assert_no_difference 'User.count' do + post users_path, params: { user: { name: "", + email: "user@invalid", + password: "foo", + password_confirmation: "bar" } } + end + assert_response :unprocessable_entity + assert_template 'users/new' + assert_select 'div#error_explanation' + assert_select 'div.field_with_errors' + end + + test "valid signup information with account activation" do + assert_difference 'User.count', 1 do + post users_path, params: { user: { name: "Example User", + email: "user@example.com", + password: "password", + password_confirmation: "password" } } + end + assert_equal 1, ActionMailer::Base.deliveries.size + end +end + +class AccountActivationTest < UsersSignup + + def setup + super + post users_path, params: { user: { name: "Example User", + email: "user@example.com", + password: "password", + password_confirmation: "password" } } + @user = assigns(:user) + end + + test "should not be activated" do + assert_not @user.activated? + end + + test "should not be able to log in before account activation" do + log_in_as(@user) + assert_not is_logged_in? + end + + test "should not be able to log in with invalid activation token" do + get edit_account_activation_path("invalid token", email: @user.email) + assert_not is_logged_in? + end + + test "should not be able to log in with invalid email" do + get edit_account_activation_path(@user.activation_token, email: 'wrong') + assert_not is_logged_in? + end + + test "should log in successfully with valid activation token and email" do + get edit_account_activation_path(@user.activation_token, email: @user.email) + assert @user.reload.activated? + follow_redirect! + assert_template 'users/show' + assert is_logged_in? + end +end diff --git a/8_0/ch13/test/mailers/.keep b/8_0/ch13/test/mailers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/test/mailers/previews/user_mailer_preview.rb b/8_0/ch13/test/mailers/previews/user_mailer_preview.rb new file mode 100644 index 00000000..86da64f1 --- /dev/null +++ b/8_0/ch13/test/mailers/previews/user_mailer_preview.rb @@ -0,0 +1,19 @@ +# Preview all emails at http://localhost:3000/rails/mailers/user_mailer +class UserMailerPreview < ActionMailer::Preview + + # Preview this email at + # http://localhost:3000/rails/mailers/user_mailer/account_activation + def account_activation + user = User.first + user.activation_token = User.new_token + UserMailer.account_activation(user) + end + + # Preview this email at + # http://localhost:3000/rails/mailers/user_mailer/password_reset + def password_reset + user = User.first + user.reset_token = User.new_token + UserMailer.password_reset(user) + end +end diff --git a/8_0/ch13/test/mailers/user_mailer_test.rb b/8_0/ch13/test/mailers/user_mailer_test.rb new file mode 100644 index 00000000..d927329d --- /dev/null +++ b/8_0/ch13/test/mailers/user_mailer_test.rb @@ -0,0 +1,27 @@ +require "test_helper" + +class UserMailerTest < ActionMailer::TestCase + + test "account_activation" do + user = users(:michael) + user.activation_token = User.new_token + mail = UserMailer.account_activation(user) + assert_equal "Account activation", mail.subject + assert_equal [user.email], mail.to + assert_equal ["Mailgunに登録したメールアドレス"], mail.from + assert_match user.name, mail.body.encoded + assert_match user.activation_token, mail.body.encoded + assert_match CGI.escape(user.email), mail.body.encoded + end + + test "password_reset" do + user = users(:michael) + user.reset_token = User.new_token + mail = UserMailer.password_reset(user) + assert_equal "Password reset", mail.subject + assert_equal [user.email], mail.to + assert_equal ["Mailgunに登録したメールアドレス"], mail.from + assert_match user.reset_token, mail.body.encoded + assert_match CGI.escape(user.email), mail.body.encoded + end +end diff --git a/8_0/ch13/test/models/.keep b/8_0/ch13/test/models/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/test/models/micropost_test.rb b/8_0/ch13/test/models/micropost_test.rb new file mode 100644 index 00000000..f6acce8e --- /dev/null +++ b/8_0/ch13/test/models/micropost_test.rb @@ -0,0 +1,32 @@ +require "test_helper" + +class MicropostTest < ActiveSupport::TestCase + + def setup + @user = users(:michael) + @micropost = @user.microposts.build(content: "Lorem ipsum") + end + + test "should be valid" do + assert @micropost.valid? + end + + test "user id should be present" do + @micropost.user_id = nil + assert_not @micropost.valid? + end + + test "content should be present" do + @micropost.content = " " + assert_not @micropost.valid? + end + + test "content should be at most 140 characters" do + @micropost.content = "a" * 141 + assert_not @micropost.valid? + end + + test "order should be most recent first" do + assert_equal microposts(:most_recent), Micropost.first + end +end diff --git a/8_0/ch13/test/models/user_test.rb b/8_0/ch13/test/models/user_test.rb new file mode 100644 index 00000000..f02502d3 --- /dev/null +++ b/8_0/ch13/test/models/user_test.rb @@ -0,0 +1,79 @@ +require "test_helper" + +class UserTest < ActiveSupport::TestCase + + def setup + @user = User.new(name: "Example User", email: "user@example.com", + password: "foobar", password_confirmation: "foobar") + end + + test "should be valid" do + assert @user.valid? + end + + test "name should be present" do + @user.name = " " + assert_not @user.valid? + end + + test "email should be present" do + @user.email = " " + assert_not @user.valid? + end + + test "name should not be too long" do + @user.name = "a" * 51 + assert_not @user.valid? + end + + test "email should not be too long" do + @user.email = "a" * 244 + "@example.com" + assert_not @user.valid? + end + + test "email validation should accept valid addresses" do + valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org + first.last@foo.jp alice+bob@baz.cn] + valid_addresses.each do |valid_address| + @user.email = valid_address + assert @user.valid?, "#{valid_address.inspect} should be valid" + end + end + + test "email validation should reject invalid addresses" do + invalid_addresses = %w[user@example,com user_at_foo.org user.name@example. + foo@bar_baz.com foo@bar+baz.com] + invalid_addresses.each do |invalid_address| + @user.email = invalid_address + assert_not @user.valid?, "#{invalid_address.inspect} should be invalid" + end + end + + test "email addresses should be unique" do + duplicate_user = @user.dup + @user.save + assert_not duplicate_user.valid? + end + + test "password should be present (nonblank)" do + @user.password = @user.password_confirmation = " " * 6 + assert_not @user.valid? + end + + test "password should have a minimum length" do + @user.password = @user.password_confirmation = "a" * 5 + assert_not @user.valid? + end + + test "authenticated? should return false for a user with nil digest" do + assert_not @user.authenticated?(:remember, '') + end + + test "associated microposts should be destroyed" do + @user.save + @user.microposts.create!(content: "Lorem ipsum") + assert_difference 'Micropost.count', -1 do + @user.destroy + end + end +end diff --git a/8_0/ch13/test/system/.keep b/8_0/ch13/test/system/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/test/test_helper.rb b/8_0/ch13/test/test_helper.rb new file mode 100644 index 00000000..5fbcafd0 --- /dev/null +++ b/8_0/ch13/test/test_helper.rb @@ -0,0 +1,33 @@ +ENV["RAILS_ENV"] ||= "test" +require_relative "../config/environment" +require "rails/test_help" +require "minitest/reporters" +Minitest::Reporters.use! + +class ActiveSupport::TestCase + # 指定のワーカー数でテストを並列実行する + parallelize(workers: :number_of_processors) + + # test/fixtures/*.ymlにあるすべてのfixtureをセットアップする + fixtures :all + + # テストユーザーがログイン中の場合にtrueを返す + def is_logged_in? + !session[:user_id].nil? + end + + # テストユーザーとしてログインする + def log_in_as(user) + session[:user_id] = user.id + end +end + +class ActionDispatch::IntegrationTest + + # テストユーザーとしてログインする + def log_in_as(user, password: 'password', remember_me: '1') + post login_path, params: { session: { email: user.email, + password: password, + remember_me: remember_me } } + end +end \ No newline at end of file diff --git a/8_0/ch13/tmp/.keep b/8_0/ch13/tmp/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/tmp/pids/.keep b/8_0/ch13/tmp/pids/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/tmp/storage/.keep b/8_0/ch13/tmp/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/vendor/.keep b/8_0/ch13/vendor/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch13/vendor/javascript/.keep b/8_0/ch13/vendor/javascript/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/.devcontainer/devcontainer.json b/8_0/ch14/.devcontainer/devcontainer.json new file mode 100644 index 00000000..e7243217 --- /dev/null +++ b/8_0/ch14/.devcontainer/devcontainer.json @@ -0,0 +1,105 @@ +// Specifications: https://containers.dev/implementors/json_reference/ +// Format details: https://aka.ms/devcontainer.json +// Config options: https://github.com/microsoft/vscode-dev-containers/tree/main/containers/ruby +{ + "name": "railstutorial", + + // Universal is well-customized image for Codespaces: + // https://hub.docker.com/_/microsoft-devcontainers-universal + //"image": "mcr.microsoft.com/devcontainers/universal:2", + //"features": { + // "ghcr.io/devcontainers/features/ruby:1": {} + //}, + + // Use Ruby image if you want to pin Ruby version like '3.2' + // https://github.com/devcontainers/images/tree/main/src/ruby + "image": "mcr.microsoft.com/devcontainers/ruby:3.2", + //"workspaceFolder": "/railstutorial", => Container build failed + + // Enable learners to choose an affordable spec, starting at minimum one. + //"hostRequirements": { + // "cpus": 2, + // "memory": "4gb", + // "storage": "32gb" + //}, + + "waitFor": "onCreateCommand", + //"onCreateCommand": "", + "onCreateCommand": "gem install solargraph -v '0.58.2' -N", + //"onCreateCommand": "gem install solargraph -v '0.50.0' -N && gem install ruby-lsp -N", + //"onCreateCommand": "gem install ruby-lsp -N", + //# => Solargraph gem not found. Run `gem install solargraph` or update your Gemfile. + "updateContentCommand": "bundle install", + "postCreateCommand": "", + "postAttachCommand": { + "server": "rails server" + }, + "customizations": { + "codespaces": { + "openFiles": [ + "app/views/hello/index.html.erb" + ] + }, + "vscode": { + "extensions": [ + "GitHub.codespaces", + "Shopify.ruby-lsp", // https://github.com/Shopify/ruby-lsp + "castwide.solargraph" // https://github.com/castwide/vscode-solargraph + //"rebornix.Ruby", // https://github.com/rubyide/vscode-ruby (Deprecated) + + ], + "settings": { + // General settings for Codespaces (VS Code) + //"ruby.useLanguageServer": true , + //"ruby.format": "rubocop", + //"ruby.lint": { "rubocop": true }, + //"ruby.intellisense": "rubyLocate", + "editor.tabSize": 2, + "editor.formatOnSave": false, // Disable onSave to show diff edited by learners only + "editor.formatOnType": false, // Disable onType for the same reason above + "editor.insertSpaces": true, // Use spaces, not tabs, to avoid errors for learners + "editor.renderWhitespace": "none", + "[ruby]": { + "editor.defaultFormatter": "castwide.solargraph", + "editor.semanticHighlighting.enabled": true // Enable semantic highlighting + }, + "files.associations": { "*.erb": "erb" }, + "emmet.includeLanguages": { "erb": "html" }, + + // Settings for Solargraph + // https://github.com/castwide/solargraph + "solargraph.useBundler": false, + "solargraph.diagnostics": false, + "solargraph.formatting": true, // Use Ctrl+Shift+P->Format to format + "solargraph.autoformat": false, + "solargraph.definitions": true, + "solargraph.completion": true, + "solargraph.references": true, + "solargraph.symbols": true, + "solargraph.rename": true, + "solargraph.hover": true, + + // Settings for Ruby LSP + // https://github.com/Shopify/ruby-lsp + "rubyLsp.rubyVersionManager": { + "identifier": "none" + }, + "rubyLsp.formatter": "none", + "rubyLsp.enabledFeatures": { + "diagnostics": false, + "formatting": false + } + } + } + }, + "remoteEnv": { + "EDITOR": "code --wait" + }, + "portsAttributes": { + "3000": { + "label": "Application", + "onAutoForward": "openPreview" + } + }, + "forwardPorts": [3000] +} diff --git a/8_0/ch14/.devcontainer/icon.svg b/8_0/ch14/.devcontainer/icon.svg new file mode 100644 index 00000000..f8444a4a --- /dev/null +++ b/8_0/ch14/.devcontainer/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/8_0/ch14/.gitattributes b/8_0/ch14/.gitattributes new file mode 100644 index 00000000..31eeee0b --- /dev/null +++ b/8_0/ch14/.gitattributes @@ -0,0 +1,7 @@ +# See https://git-scm.com/docs/gitattributes for more about git attribute files. + +# Mark the database schema as having been generated. +db/schema.rb linguist-generated + +# Mark any vendored files as having been vendored. +vendor/* linguist-vendored diff --git a/8_0/ch14/.gitignore b/8_0/ch14/.gitignore new file mode 100644 index 00000000..e1ef53ef --- /dev/null +++ b/8_0/ch14/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-* + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore uploaded files in development. +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/ +!/tmp/storage/.keep + +/public/assets + +# Ignore master key for decrypting credentials and more. +/config/master.key + +# Ignore history file used by IRB for interactive Ruby. +.irb_history + +dump.rdb diff --git a/8_0/ch14/.irbrc b/8_0/ch14/.irbrc new file mode 100644 index 00000000..3737e036 --- /dev/null +++ b/8_0/ch14/.irbrc @@ -0,0 +1 @@ +IRB.conf[:COMPLETOR] = :type # default is :regexp diff --git a/8_0/ch14/.solargraph.yml b/8_0/ch14/.solargraph.yml new file mode 100644 index 00000000..76ac24a6 --- /dev/null +++ b/8_0/ch14/.solargraph.yml @@ -0,0 +1,32 @@ +--- +include: +- "**/*.rb" +exclude: +- spec/**/* +- test/**/* +- vendor/**/* +- ".bundle/**/*" +require: +- actioncable +- actionmailer +- actionpack +- actionview +- activejob +- activemodel +- activerecord +- activestorage +- activesupport +domains: [] +reporters: +- rubocop +- require_not_found +- typecheck +formatter: + rubocop: + cops: safe + except: [] + only: [] + extra_args: [] +require_paths: [] +plugins: [] +max_files: 5000 diff --git a/8_0/ch14/.vscode/settings.json b/8_0/ch14/.vscode/settings.json new file mode 100644 index 00000000..cf85f519 --- /dev/null +++ b/8_0/ch14/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.minimap.enabled": false +} diff --git a/8_0/ch14/Gemfile b/8_0/ch14/Gemfile new file mode 100644 index 00000000..d32a17e0 --- /dev/null +++ b/8_0/ch14/Gemfile @@ -0,0 +1,53 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '3.2.10' + +gem 'active_storage_validations', '0.9.8' +gem 'bcrypt' , '3.1.18' +gem 'bootsnap' , '1.16.0', require: false +gem 'bootstrap-sass' , '3.4.1' +gem 'bootstrap-will_paginate' , '1.0.0' +gem 'concurrent-ruby' , '1.3.4' +gem 'faker' , '2.21.0' +gem 'image_processing' , '1.12.2' +gem 'importmap-rails' , '1.1.5' +gem 'jbuilder' , '2.14.1' +gem 'puma' , '6.6.1' +gem 'rails' , '8.0.2.1' +gem 'sassc-rails' , '2.1.2' +gem 'sprockets-rails' , '3.4.2' +gem 'sqlite3' , '2.7.3' +gem 'stimulus-rails' , '1.2.1' +gem 'turbo-rails' , '1.4.0' +gem 'will_paginate' , '3.3.1' + +group :development, :test do + gem 'debug', '1.7.1', platforms: %i[mri mingw x64_mingw] + gem 'reline', '0.5.10' +end + +group :development do + gem 'irb', '1.15.2' + gem 'repl_type_completor', '0.1.10' + gem 'solargraph', '0.58.2' + gem 'web-console', '4.2.0' +end + +group :test do + gem 'capybara', '3.38.0' + gem 'guard', '2.18.0' + gem 'guard-minitest', '2.4.6' + gem 'minitest', '5.18.0' + gem 'minitest-reporters', '1.6.0' + gem 'rails-controller-testing', '1.0.5' + gem 'selenium-webdriver', '4.8.3' + gem 'webdrivers', '5.2.0' +end + +group :production do + gem 'mailgun-ruby', "1.3.10" +end + +# Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります +# gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] diff --git a/8_0/ch14/Gemfile.lock b/8_0/ch14/Gemfile.lock new file mode 100644 index 00000000..9aeecd76 --- /dev/null +++ b/8_0/ch14/Gemfile.lock @@ -0,0 +1,480 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + actionmailer (8.0.2.1) + actionpack (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activesupport (= 8.0.2.1) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.0.2.1) + actionview (= 8.0.2.1) + activesupport (= 8.0.2.1) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.0.2.1) + actionpack (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (8.0.2.1) + activesupport (= 8.0.2.1) + builder (~> 3.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + active_storage_validations (0.9.8) + activejob (>= 5.2.0) + activemodel (>= 5.2.0) + activestorage (>= 5.2.0) + activesupport (>= 5.2.0) + activejob (8.0.2.1) + activesupport (= 8.0.2.1) + globalid (>= 0.3.6) + activemodel (8.0.2.1) + activesupport (= 8.0.2.1) + activerecord (8.0.2.1) + activemodel (= 8.0.2.1) + activesupport (= 8.0.2.1) + timeout (>= 0.4.0) + activestorage (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activesupport (= 8.0.2.1) + marcel (~> 1.0) + activesupport (8.0.2.1) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + ansi (1.5.0) + ast (2.4.3) + autoprefixer-rails (10.4.21.0) + execjs (~> 2) + backport (1.2.0) + base64 (0.3.0) + bcrypt (3.1.18) + benchmark (0.4.1) + bigdecimal (3.2.3) + bindex (0.8.1) + bootsnap (1.16.0) + msgpack (~> 1.2) + bootstrap-sass (3.4.1) + autoprefixer-rails (>= 5.2.1) + sassc (>= 2.0.0) + bootstrap-will_paginate (1.0.0) + will_paginate + builder (3.3.0) + capybara (3.38.0) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + coderay (1.1.3) + concurrent-ruby (1.3.4) + connection_pool (2.5.4) + crass (1.0.6) + date (3.4.1) + debug (1.7.1) + irb (>= 1.5.0) + reline (>= 0.3.1) + diff-lcs (1.6.2) + drb (2.2.3) + erb (5.0.2) + erubi (1.13.1) + execjs (2.10.1) + faker (2.21.0) + i18n (>= 1.8.11, < 2) + faraday (2.14.3) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-multipart (1.1.1) + multipart-post (~> 2.0) + faraday-net_http (3.4.4) + net-http (~> 0.5) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) + formatador (1.2.0) + reline + globalid (1.2.1) + activesupport (>= 6.1) + guard (2.18.0) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.13.0) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-minitest (2.4.6) + guard-compat (~> 1.2) + minitest (>= 3.0) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + image_processing (1.12.2) + mini_magick (>= 4.9.5, < 5) + ruby-vips (>= 2.0.17, < 3) + importmap-rails (1.1.5) + actionpack (>= 6.0.0) + railties (>= 6.0.0) + io-console (0.8.1) + irb (1.15.2) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jaro_winkler (1.6.1) + jbuilder (2.14.1) + actionview (>= 7.0.0) + activesupport (>= 7.0.0) + json (2.13.2) + kramdown (2.5.1) + rexml (>= 3.3.9) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) + loofah (2.24.1) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + lumberjack (1.4.1) + mail (2.8.1) + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + mailgun-ruby (1.3.10) + faraday (~> 2.1) + faraday-multipart (~> 1.1.0) + mini_mime + marcel (1.0.4) + matrix (0.4.3) + method_source (1.1.0) + mini_magick (4.13.2) + mini_mime (1.1.5) + minitest (5.18.0) + minitest-reporters (1.6.0) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + msgpack (1.8.0) + multipart-post (2.4.1) + nenv (0.3.0) + net-http (0.9.1) + uri (>= 0.11.1) + net-imap (0.5.10) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.4) + nokogiri (1.19.1-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.19.1-arm64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.1-x86_64-linux-musl) + racc (~> 1.4) + notiffany (0.1.3) + nenv (~> 0.1) + shellany (~> 0.0) + observer (0.1.2) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.9.0) + ast (~> 2.4.1) + racc + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + prism (1.4.0) + pry (0.15.2) + coderay (~> 1.1) + method_source (~> 1.0) + psych (5.2.6) + date + stringio + public_suffix (6.0.2) + puma (6.6.1) + nio4r (~> 2.0) + racc (1.8.1) + rack (3.2.5) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.2.1) + rack (>= 3) + rails (8.0.2.1) + actioncable (= 8.0.2.1) + actionmailbox (= 8.0.2.1) + actionmailer (= 8.0.2.1) + actionpack (= 8.0.2.1) + actiontext (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activemodel (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) + bundler (>= 1.15.0) + railties (= 8.0.2.1) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.0) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rbs (3.6.1) + logger + rdoc (6.14.2) + erb + psych (>= 4.0.0) + regexp_parser (2.11.2) + reline (0.5.10) + io-console (~> 0.5) + repl_type_completor (0.1.10) + prism (~> 1.0) + rbs (>= 2.7.0, < 4.0.0) + reverse_markdown (3.0.0) + nokogiri + rexml (3.4.2) + rubocop (1.80.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.46.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.46.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + ruby-progressbar (1.13.0) + ruby-vips (2.3.0) + ffi (~> 1.12) + logger + rubyzip (2.4.1) + sassc (2.4.0) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt + securerandom (0.4.1) + selenium-webdriver (4.8.3) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) + shellany (0.0.1) + solargraph (0.58.2) + backport (~> 1.2) + benchmark (~> 0.4) + bundler (~> 2.0) + diff-lcs (~> 1.4) + jaro_winkler (~> 1.6, >= 1.6.1) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.1) + logger (~> 1.6) + observer (~> 0.1) + ostruct (~> 0.6) + parser (~> 3.0) + prism (~> 1.4) + rbs (~> 3.6.1) + reverse_markdown (~> 3.0) + rubocop (~> 1.38) + thor (~> 1.0) + tilt (~> 2.0) + yard (~> 0.9, >= 0.9.24) + yard-solargraph (~> 0.1) + sprockets (4.2.2) + concurrent-ruby (~> 1.0) + logger + rack (>= 2.2.4, < 4) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + sprockets (>= 3.0.0) + sqlite3 (2.7.3-aarch64-linux-gnu) + sqlite3 (2.7.3-aarch64-linux-musl) + sqlite3 (2.7.3-arm-linux-gnu) + sqlite3 (2.7.3-arm-linux-musl) + sqlite3 (2.7.3-arm64-darwin) + sqlite3 (2.7.3-x86_64-darwin) + sqlite3 (2.7.3-x86_64-linux-gnu) + sqlite3 (2.7.3-x86_64-linux-musl) + stimulus-rails (1.2.1) + railties (>= 6.0.0) + stringio (3.1.7) + thor (1.4.0) + tilt (2.6.1) + timeout (0.4.3) + turbo-rails (1.4.0) + actionpack (>= 6.0.0) + activejob (>= 6.0.0) + railties (>= 6.0.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.1.5) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + uri (1.0.4) + useragent (0.16.11) + web-console (4.2.0) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + webdrivers (5.2.0) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (~> 4.0) + websocket (1.2.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + will_paginate (3.3.1) + xpath (3.2.0) + nokogiri (~> 1.8) + yard (0.9.37) + yard-solargraph (0.1.0) + yard (~> 0.9) + zeitwerk (2.7.3) + +PLATFORMS + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + active_storage_validations (= 0.9.8) + bcrypt (= 3.1.18) + bootsnap (= 1.16.0) + bootstrap-sass (= 3.4.1) + bootstrap-will_paginate (= 1.0.0) + capybara (= 3.38.0) + concurrent-ruby (= 1.3.4) + debug (= 1.7.1) + faker (= 2.21.0) + guard (= 2.18.0) + guard-minitest (= 2.4.6) + image_processing (= 1.12.2) + importmap-rails (= 1.1.5) + irb (= 1.15.2) + jbuilder (= 2.14.1) + mailgun-ruby (= 1.3.10) + minitest (= 5.18.0) + minitest-reporters (= 1.6.0) + puma (= 6.6.1) + rails (= 8.0.2.1) + rails-controller-testing (= 1.0.5) + reline (= 0.5.10) + repl_type_completor (= 0.1.10) + sassc-rails (= 2.1.2) + selenium-webdriver (= 4.8.3) + solargraph (= 0.58.2) + sprockets-rails (= 3.4.2) + sqlite3 (= 2.7.3) + stimulus-rails (= 1.2.1) + turbo-rails (= 1.4.0) + web-console (= 4.2.0) + webdrivers (= 5.2.0) + will_paginate (= 3.3.1) + +RUBY VERSION + ruby 3.2.10p266 + +BUNDLED WITH + 2.5.6 diff --git a/8_0/ch14/Guardfile b/8_0/ch14/Guardfile new file mode 100644 index 00000000..5def34a0 --- /dev/null +++ b/8_0/ch14/Guardfile @@ -0,0 +1,76 @@ + require "active_support/inflector" + # Guardのマッチング規則を定義 + guard :minitest, all_on_start: false do + watch(%r{^test/(.*)/?(.*)_test\.rb$}) + watch('test/test_helper.rb') { 'test' } + watch('config/routes.rb') { interface_tests } + watch(%r{app/views/layouts/*}) { interface_tests } + watch(%r{^app/models/(.*?)\.rb$}) do |matches| + ["test/models/#{matches[1]}_test.rb", + "test/integration/microposts_interface_test.rb"] + end + watch(%r{^test/fixtures/(.*?)\.yml$}) do |matches| + "test/models/#{matches[1].singularize}_test.rb" + end + watch(%r{^app/mailers/(.*?)\.rb$}) do |matches| + "test/mailers/#{matches[1]}_test.rb" + end + watch(%r{^app/views/(.*)_mailer/.*$}) do |matches| + "test/mailers/#{matches[1]}_mailer_test.rb" + end + watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches| + resource_tests(matches[1]) + end + watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches| + ["test/controllers/#{matches[1]}_controller_test.rb"] + + integration_tests(matches[1]) + end + watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches| + integration_tests(matches[1]) + end + watch('app/views/layouts/application.html.erb') do + 'test/integration/site_layout_test.rb' + end + watch('app/helpers/sessions_helper.rb') do + integration_tests << 'test/helpers/sessions_helper_test.rb' + end + watch('app/controllers/sessions_controller.rb') do + ['test/controllers/sessions_controller_test.rb', + 'test/integration/users_login_test.rb'] + end + watch('app/controllers/account_activations_controller.rb') do + 'test/integration/users_signup_test.rb' + end + watch(%r{app/views/users/*}) do + resource_tests('users') + + ['test/integration/microposts_interface_test.rb'] + end + watch('app/controllers/relationships_controller.rb') do + ['test/controllers/relationships_controller_test.rb', + 'test/integration/following_test.rb'] + end + end + + # 指定のリソースに対応する統合テストを返す + def integration_tests(resource = :all) + if resource == :all + Dir["test/integration/*"] + else + Dir["test/integration/#{resource}_*.rb"] + end + end + + # インターフェースが該当するすべてのテストを返す + def interface_tests + integration_tests << "test/controllers" + end + + # 指定のリソースに対応するコントローラのテストを返す + def controller_test(resource) + "test/controllers/#{resource}_controller_test.rb" + end + + # 指定のリソースに対応するすべてのテストを返す + def resource_tests(resource) + integration_tests(resource) << controller_test(resource) + end diff --git a/8_0/ch14/LICENSE b/8_0/ch14/LICENSE new file mode 100644 index 00000000..20622e0c --- /dev/null +++ b/8_0/ch14/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 GitHub & 2023 YassLab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/8_0/ch14/README.md b/8_0/ch14/README.md new file mode 100644 index 00000000..17ca733d --- /dev/null +++ b/8_0/ch14/README.md @@ -0,0 +1,35 @@ +# Ruby on Rails チュートリアルのサンプルアプリケーション + +これは、次の教材で作られたサンプルアプリケーションです。 +[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +(第8版) +[Michael Hartl](https://www.michaelhartl.com/) 著 + +## ライセンス + +[Ruby on Rails チュートリアル](https://railstutorial.jp/)内にある +ソースコードはMITライセンスとBeerwareライセンスのもとで公開されています。 +詳細は [LICENSE.md](LICENSE.md) をご覧ください。 + +## 使い方 + +このアプリケーションを動かす場合は、まずはリポジトリをフォークしてください。 + +フォークしたリポジトリで、「Code」から「Codespaces」タブに移動し、 +「Create codespace on main」をクリックすると環境構築がスタートします。 +Railsサーバーが立ち上がり、シンプルブラウザが表示されるまでしばらくお待ちください。 + +次に、データベースへのマイグレーションを実行します。 + +``` +$ rails db:migrate +``` + +最後に、テストを実行してうまく動いているかどうか確認してください。 + +``` +$ rails test +``` + +詳しくは、[*Ruby on Rails チュートリアル*](https://railstutorial.jp/) +を参考にしてください。 diff --git a/8_0/ch14/Rakefile b/8_0/ch14/Rakefile new file mode 100644 index 00000000..9a5ea738 --- /dev/null +++ b/8_0/ch14/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/8_0/ch14/app/assets/config/manifest.js b/8_0/ch14/app/assets/config/manifest.js new file mode 100644 index 00000000..ddd546a0 --- /dev/null +++ b/8_0/ch14/app/assets/config/manifest.js @@ -0,0 +1,4 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js diff --git a/8_0/ch14/app/assets/images/.keep b/8_0/ch14/app/assets/images/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/app/assets/images/rails.svg b/8_0/ch14/app/assets/images/rails.svg new file mode 100644 index 00000000..e1971850 --- /dev/null +++ b/8_0/ch14/app/assets/images/rails.svg @@ -0,0 +1,35 @@ + + + +rails-logo + + + + + + + + + + + + + + + + + + + + + diff --git a/8_0/ch14/app/assets/stylesheets/application.css b/8_0/ch14/app/assets/stylesheets/application.css new file mode 100644 index 00000000..288b9ab7 --- /dev/null +++ b/8_0/ch14/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/8_0/ch14/app/assets/stylesheets/custom.scss b/8_0/ch14/app/assets/stylesheets/custom.scss new file mode 100644 index 00000000..85044a3e --- /dev/null +++ b/8_0/ch14/app/assets/stylesheets/custom.scss @@ -0,0 +1,312 @@ +@import "bootstrap-sprockets"; +@import "bootstrap"; + +/* mixins, variables, etc. */ + +$gray-medium-light: #eaeaea; + +@mixin box_sizing { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* universal */ + +body { + padding-top: 60px; +} + +section { + overflow: auto; +} + +textarea { + resize: vertical; +} + +.center { + text-align: center; + h1 { + margin-bottom: 10px; + } +} + +/* typography */ + +h1, h2, h3, h4, h5, h6 { + line-height: 1; +} + +h1 { + font-size: 3em; + letter-spacing: -2px; + margin-bottom: 30px; + text-align: center; +} + +h2 { + font-size: 1.2em; + letter-spacing: -1px; + margin-bottom: 30px; + text-align: center; + font-weight: normal; + color: $gray-light; +} + +p { + font-size: 1.1em; + line-height: 1.7em; +} + + +/* header */ + +#logo { + float: left; + margin-right: 10px; + font-size: 1.7em; + color: white; + text-transform: uppercase; + letter-spacing: -1px; + padding-top: 9px; + font-weight: bold; + &:hover { + color: white; + text-decoration: none; + } +} + +/* footer */ + +footer { + margin-top: 45px; + padding-top: 5px; + border-top: 1px solid $gray-medium-light; + color: $gray-light; + a { + color: $gray; + &:hover { + color: $gray-darker; + } + } + small { + float: left; + } + ul { + float: right; + list-style: none; + li { + float: left; + margin-left: 15px; + } + } +} +@media (max-width: 800px) { + footer { + small { + display: block; + float: none; + margin-bottom: 1em; + } + ul { + float: none; + padding: 0; + li { + float: none; + margin-left: 0; + } + } + } +} + +/* miscellaneous */ + +.debug_dump { + clear: both; + float: left; + width: 100%; + margin-top: 45px; +} + +/* sidebar */ + +aside { + section.user_info { + margin-top: 20px; + } + section { + padding: 10px 0; + margin-top: 20px; + &:first-child { + border: 0; + padding-top: 0; + } + span { + display: block; + margin-bottom: 3px; + line-height: 1; + } + h1 { + font-size: 1.4em; + text-align: left; + letter-spacing: -1px; + margin-bottom: 3px; + margin-top: 0px; + } + } +} + +.gravatar { + float: left; + margin-right: 10px; +} + +.gravatar_edit { + margin-top: 15px; +} + +.stats { + overflow: auto; + margin-top: 0; + padding: 0; + a { + float: left; + padding: 0 10px; + border-left: 1px solid $gray-lighter; + color: gray; + &:first-child { + padding-left: 0; + border: 0; + } + &:hover { + text-decoration: none; + color: blue; + } + } + strong { + display: block; + } +} + +.user_avatars { + overflow: auto; + margin-top: 10px; + .gravatar { + margin: 1px 1px; + } + a { + padding: 0; + } +} + +.users.follow { + padding: 0; +} + +/* forms */ + +input, textarea { + border: 1px solid #bbb; + width: 100%; + margin-bottom: 15px; + @include box_sizing; +} + +input { + height: auto !important; +} + +#error_explanation { + color: red; + ul { + color: red; + margin: 0 0 30px 0; + } +} + +.field_with_errors { + @extend .has-error; + .form-control { + color: $state-danger-text; + } +} + +.checkbox { + margin-top: -10px; + margin-bottom: 10px; + span { + margin-left: 20px; + font-weight: normal; + } +} + +#session_remember_me { + width: auto; + margin-left: 0; +} + +/* Dropdown menu */ + +.dropdown-menu.active { + display: block; +} + +/* Users index */ + +.users { + list-style: none; + margin: 0; + li { + overflow: auto; + padding: 10px 0; + border-bottom: 1px solid $gray-lighter; + } +} + +/* microposts */ + +.microposts { + list-style: none; + padding: 0; + li { + padding: 10px 0; + border-top: 1px solid #e8e8e8; + } + .user { + margin-top: 5em; + padding-top: 0; + } + .content { + display: block; + margin-left: 60px; + img { + display: block; + padding: 5px 0; + } + } + .timestamp { + color: $gray-light; + display: block; + margin-left: 60px; + } + .gravatar { + float: left; + margin-right: 10px; + margin-top: 5px; + } +} + +aside { + textarea { + height: 100px; + margin-bottom: 5px; + } +} + +span.image { + margin-top: 10px; + input { + border: 0; + } +} diff --git a/8_0/ch14/app/assets/stylesheets/hello.css b/8_0/ch14/app/assets/stylesheets/hello.css new file mode 100644 index 00000000..497881a4 --- /dev/null +++ b/8_0/ch14/app/assets/stylesheets/hello.css @@ -0,0 +1,40 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.codespaces { + text-align: center; +} +.codespaces code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} +.codespaces-logo { + height: 40vmin; + pointer-events: none; +} +.codespaces-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} +.codespaces-link { + color: #61dafb; +} + +.heart { + color: #ff0000; +} +.small { + font-size: 0.75rem; +} diff --git a/8_0/ch14/app/channels/application_cable/channel.rb b/8_0/ch14/app/channels/application_cable/channel.rb new file mode 100644 index 00000000..d6726972 --- /dev/null +++ b/8_0/ch14/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/8_0/ch14/app/channels/application_cable/connection.rb b/8_0/ch14/app/channels/application_cable/connection.rb new file mode 100644 index 00000000..0ff5442f --- /dev/null +++ b/8_0/ch14/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/8_0/ch14/app/controllers/account_activations_controller.rb b/8_0/ch14/app/controllers/account_activations_controller.rb new file mode 100644 index 00000000..4985c73e --- /dev/null +++ b/8_0/ch14/app/controllers/account_activations_controller.rb @@ -0,0 +1,15 @@ +class AccountActivationsController < ApplicationController + + def edit + user = User.find_by(email: params[:email]) + if user && !user.activated? && user.authenticated?(:activation, params[:id]) + user.activate + log_in user + flash[:success] = "Account activated!" + redirect_to user + else + flash[:danger] = "Invalid activation link" + redirect_to root_url + end + end +end diff --git a/8_0/ch14/app/controllers/application_controller.rb b/8_0/ch14/app/controllers/application_controller.rb new file mode 100644 index 00000000..24e570fd --- /dev/null +++ b/8_0/ch14/app/controllers/application_controller.rb @@ -0,0 +1,14 @@ +class ApplicationController < ActionController::Base + include SessionsHelper + + private + + # ユーザーのログインを確認する + def logged_in_user + unless logged_in? + store_location + flash[:danger] = "Please log in." + redirect_to login_url, status: :see_other + end + end +end diff --git a/8_0/ch14/app/controllers/concerns/.keep b/8_0/ch14/app/controllers/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/app/controllers/hello_controller.rb b/8_0/ch14/app/controllers/hello_controller.rb new file mode 100644 index 00000000..52037537 --- /dev/null +++ b/8_0/ch14/app/controllers/hello_controller.rb @@ -0,0 +1,4 @@ +class HelloController < ApplicationController + def index + end +end diff --git a/8_0/ch14/app/controllers/microposts_controller.rb b/8_0/ch14/app/controllers/microposts_controller.rb new file mode 100644 index 00000000..f871083f --- /dev/null +++ b/8_0/ch14/app/controllers/microposts_controller.rb @@ -0,0 +1,37 @@ +class MicropostsController < ApplicationController + before_action :logged_in_user, only: [:create, :destroy] + before_action :correct_user, only: :destroy + + def create + @micropost = current_user.microposts.build(micropost_params) + @micropost.image.attach(params[:micropost][:image]) + if @micropost.save + flash[:success] = "Micropost created!" + redirect_to root_url + else + @feed_items = current_user.feed.paginate(page: params[:page]) + render 'static_pages/home', status: :unprocessable_entity + end + end + + def destroy + @micropost.destroy + flash[:success] = "Micropost deleted" + if request.referrer.nil? || request.referrer == microposts_url + redirect_to root_url, status: :see_other + else + redirect_to request.referrer, status: :see_other + end + end + + private + + def micropost_params + params.expect(micropost: [:content, :image]) + end + + def correct_user + @micropost = current_user.microposts.find_by(id: params[:id]) + redirect_to root_url, status: :see_other if @micropost.nil? + end +end diff --git a/8_0/ch14/app/controllers/password_resets_controller.rb b/8_0/ch14/app/controllers/password_resets_controller.rb new file mode 100644 index 00000000..260165c3 --- /dev/null +++ b/8_0/ch14/app/controllers/password_resets_controller.rb @@ -0,0 +1,66 @@ +class PasswordResetsController < ApplicationController + before_action :get_user, only: [:edit, :update] + before_action :valid_user, only: [:edit, :update] + before_action :check_expiration, only: [:edit, :update] # (1)への対応 + + def new + end + + def create + @user = User.find_by(email: params[:password_reset][:email].downcase) + if @user + @user.create_reset_digest + @user.send_password_reset_email + flash[:info] = "Email sent with password reset instructions" + redirect_to root_url + else + flash.now[:danger] = "Email address not found" + render 'new', status: :unprocessable_entity + end + end + + def edit + end + + def update + if params[:user][:password].empty? # (3)への対応 + @user.errors.add(:password, "can't be empty") + render 'edit', status: :unprocessable_entity + elsif @user.update(user_params) # (4)への対応 + reset_session + log_in @user + flash[:success] = "Password has been reset." + redirect_to @user + else + render 'edit', status: :unprocessable_entity # (2)への対応 + end + end + + private + + def user_params + params.expect(user: [:password, :password_confirmation]) + end + + # beforeフィルタ + + def get_user + @user = User.find_by(email: params[:email]) + end + + # 有効なユーザーかどうか確認する + def valid_user + unless (@user && @user.activated? && + @user.authenticated?(:reset, params[:id])) + redirect_to root_url + end + end + + # トークンが期限切れかどうか確認する + def check_expiration + if @user.password_reset_expired? + flash[:danger] = "Password reset has expired." + redirect_to new_password_reset_url + end + end +end diff --git a/8_0/ch14/app/controllers/relationships_controller.rb b/8_0/ch14/app/controllers/relationships_controller.rb new file mode 100644 index 00000000..814ad5f4 --- /dev/null +++ b/8_0/ch14/app/controllers/relationships_controller.rb @@ -0,0 +1,21 @@ +class RelationshipsController < ApplicationController + before_action :logged_in_user + + def create + @user = User.find(params[:followed_id]) + current_user.follow(@user) + respond_to do |format| + format.html { redirect_to @user } + format.turbo_stream + end + end + + def destroy + @user = Relationship.find(params[:id]).followed + current_user.unfollow(@user) + respond_to do |format| + format.html { redirect_to @user, status: :see_other } + format.turbo_stream + end + end +end diff --git a/8_0/ch14/app/controllers/sessions_controller.rb b/8_0/ch14/app/controllers/sessions_controller.rb new file mode 100644 index 00000000..4fa0043a --- /dev/null +++ b/8_0/ch14/app/controllers/sessions_controller.rb @@ -0,0 +1,31 @@ +class SessionsController < ApplicationController + + def new + end + + def create + user = User.find_by(email: params[:session][:email].downcase) + if user && user.authenticate(params[:session][:password]) + if user.activated? + forwarding_url = session[:forwarding_url] + reset_session + params[:session][:remember_me] == '1' ? remember(user) : forget(user) + log_in user + redirect_to forwarding_url || user + else + message = "Account not activated. " + message += "Check your email for the activation link." + flash[:warning] = message + redirect_to root_url + end + else + flash.now[:danger] = 'Invalid email/password combination' + render 'new', status: :unprocessable_entity + end + end + + def destroy + log_out if logged_in? + redirect_to root_url, status: :see_other + end +end diff --git a/8_0/ch14/app/controllers/static_pages_controller.rb b/8_0/ch14/app/controllers/static_pages_controller.rb new file mode 100644 index 00000000..29311977 --- /dev/null +++ b/8_0/ch14/app/controllers/static_pages_controller.rb @@ -0,0 +1,18 @@ +class StaticPagesController < ApplicationController + + def home + if logged_in? + @micropost = current_user.microposts.build + @feed_items = current_user.feed.paginate(page: params[:page]) + end + end + + def help + end + + def about + end + + def contact + end +end diff --git a/8_0/ch14/app/controllers/users_controller.rb b/8_0/ch14/app/controllers/users_controller.rb new file mode 100644 index 00000000..be1f3654 --- /dev/null +++ b/8_0/ch14/app/controllers/users_controller.rb @@ -0,0 +1,82 @@ +class UsersController < ApplicationController + before_action :logged_in_user, only: [:index, :edit, :update, :destroy, + :following, :followers] + before_action :correct_user, only: [:edit, :update] + before_action :admin_user, only: :destroy + + def index + @users = User.paginate(page: params[:page]) + end + + def show + @user = User.find(params[:id]) + @microposts = @user.microposts.paginate(page: params[:page]) + end + + def new + @user = User.new + end + + def create + @user = User.new(user_params) + if @user.save + @user.send_activation_email + flash[:info] = "Please check your email to activate your account." + redirect_to root_url + else + render 'new', status: :unprocessable_entity + end + end + + def edit + end + + def update + if @user.update(user_params) + flash[:success] = "Profile updated" + redirect_to @user + else + render 'edit', status: :unprocessable_entity + end + end + + def destroy + User.find(params[:id]).destroy + flash[:success] = "User deleted" + redirect_to users_url, status: :see_other + end + + def following + @title = "Following" + @user = User.find(params[:id]) + @users = @user.following.paginate(page: params[:page]) + render 'show_follow' + end + + def followers + @title = "Followers" + @user = User.find(params[:id]) + @users = @user.followers.paginate(page: params[:page]) + render 'show_follow' + end + + private + + def user_params + params.expect(user: [:name, :email, :password, + :password_confirmation]) + end + + # beforeフィルター + + # 正しいユーザーかどうかを確認 + def correct_user + @user = User.find(params[:id]) + redirect_to(root_url, status: :see_other) unless current_user?(@user) + end + + # 管理者かどうかを確認 + def admin_user + redirect_to(root_url, status: :see_other) unless current_user.admin? + end +end diff --git a/8_0/ch14/app/helpers/account_activations_helper.rb b/8_0/ch14/app/helpers/account_activations_helper.rb new file mode 100644 index 00000000..c4d5ac72 --- /dev/null +++ b/8_0/ch14/app/helpers/account_activations_helper.rb @@ -0,0 +1,2 @@ +module AccountActivationsHelper +end diff --git a/8_0/ch14/app/helpers/application_helper.rb b/8_0/ch14/app/helpers/application_helper.rb new file mode 100644 index 00000000..708e4e48 --- /dev/null +++ b/8_0/ch14/app/helpers/application_helper.rb @@ -0,0 +1,12 @@ +module ApplicationHelper + + # ページごとの完全なタイトルを返します。 # コメント行 + def full_title(page_title = '') # メソッド定義とオプション引数 + base_title = "Ruby on Rails Tutorial Sample App" # 変数への代入 + if page_title.empty? # 論理値テスト + base_title # 暗黙の戻り値 + else + "#{page_title} | #{base_title}" # 文字列の結合 + end + end +end diff --git a/8_0/ch14/app/helpers/hello_codespaces_helper.rb b/8_0/ch14/app/helpers/hello_codespaces_helper.rb new file mode 100644 index 00000000..874a2ca2 --- /dev/null +++ b/8_0/ch14/app/helpers/hello_codespaces_helper.rb @@ -0,0 +1,2 @@ +module HelloCodespacesHelper +end diff --git a/8_0/ch14/app/helpers/microposts_helper.rb b/8_0/ch14/app/helpers/microposts_helper.rb new file mode 100644 index 00000000..f08aad24 --- /dev/null +++ b/8_0/ch14/app/helpers/microposts_helper.rb @@ -0,0 +1,2 @@ +module MicropostsHelper +end diff --git a/8_0/ch14/app/helpers/password_resets_helper.rb b/8_0/ch14/app/helpers/password_resets_helper.rb new file mode 100644 index 00000000..0c9d96ec --- /dev/null +++ b/8_0/ch14/app/helpers/password_resets_helper.rb @@ -0,0 +1,2 @@ +module PasswordResetsHelper +end diff --git a/8_0/ch14/app/helpers/relationships_helper.rb b/8_0/ch14/app/helpers/relationships_helper.rb new file mode 100644 index 00000000..3b96a9c0 --- /dev/null +++ b/8_0/ch14/app/helpers/relationships_helper.rb @@ -0,0 +1,2 @@ +module RelationshipsHelper +end diff --git a/8_0/ch14/app/helpers/sessions_helper.rb b/8_0/ch14/app/helpers/sessions_helper.rb new file mode 100644 index 00000000..a38a35bd --- /dev/null +++ b/8_0/ch14/app/helpers/sessions_helper.rb @@ -0,0 +1,60 @@ +module SessionsHelper + + # 渡されたユーザーでログインする + def log_in(user) + session[:user_id] = user.id + # セッションリプレイ攻撃から保護する + # 詳しくは https://techracho.bpsinc.jp/hachi8833/2023_06_02/130443 を参照 + session[:session_token] = user.session_token + end + + # ユーザーを永続的セッションに保存する + def remember(user) + user.remember + cookies.permanent.encrypted[:user_id] = user.id + cookies.permanent[:remember_token] = user.remember_token + end + + # 現在ログイン中のユーザーを返す(いる場合) + def current_user + if (user_id = session[:user_id]) + user = User.find_by(id: user_id) + @current_user ||= user if session[:session_token] == user.session_token + elsif (user_id = cookies.encrypted[:user_id]) + user = User.find_by(id: user_id) + if user && user.authenticated?(:remember, cookies[:remember_token]) + log_in user + @current_user = user + end + end + end + + # 渡されたユーザーがカレントユーザーであればtrueを返す + def current_user?(user) + user && user == current_user + end + + # ユーザーがログインしていればtrue、その他ならfalseを返す + def logged_in? + !current_user.nil? + end + + # 永続的セッションを破棄する + def forget(user) + user.forget + cookies.delete(:user_id) + cookies.delete(:remember_token) + end + + # 現在のユーザーをログアウトする + def log_out + forget(current_user) + reset_session + @current_user = nil + end + + # アクセスしようとしたURLを保存する + def store_location + session[:forwarding_url] = request.original_url if request.get? + end +end diff --git a/8_0/ch14/app/helpers/static_pages_helper.rb b/8_0/ch14/app/helpers/static_pages_helper.rb new file mode 100644 index 00000000..2d63e79e --- /dev/null +++ b/8_0/ch14/app/helpers/static_pages_helper.rb @@ -0,0 +1,2 @@ +module StaticPagesHelper +end diff --git a/8_0/ch14/app/helpers/users_helper.rb b/8_0/ch14/app/helpers/users_helper.rb new file mode 100644 index 00000000..53790499 --- /dev/null +++ b/8_0/ch14/app/helpers/users_helper.rb @@ -0,0 +1,10 @@ +module UsersHelper + + # 引数で与えられたユーザーのGravatar画像を返す + def gravatar_for(user, options = { size: 80 }) + size = options[:size] + gravatar_id = Digest::MD5::hexdigest(user.email.downcase) + gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}" + image_tag(gravatar_url, alt: user.name, class: "gravatar") + end +end diff --git a/8_0/ch14/app/javascript/application.js b/8_0/ch14/app/javascript/application.js new file mode 100644 index 00000000..d1ec8140 --- /dev/null +++ b/8_0/ch14/app/javascript/application.js @@ -0,0 +1,6 @@ +// Configure your import map in config/importmap.rb. +// Read more: https://github.com/rails/importmap-rails +import "@hotwired/turbo-rails" +import "controllers" +import "custom/menu" +import "custom/image_upload" diff --git a/8_0/ch14/app/javascript/controllers/application.js b/8_0/ch14/app/javascript/controllers/application.js new file mode 100644 index 00000000..1213e85c --- /dev/null +++ b/8_0/ch14/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/8_0/ch14/app/javascript/controllers/hello_controller.js b/8_0/ch14/app/javascript/controllers/hello_controller.js new file mode 100644 index 00000000..5975c078 --- /dev/null +++ b/8_0/ch14/app/javascript/controllers/hello_controller.js @@ -0,0 +1,7 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.element.textContent = "Hello World!" + } +} diff --git a/8_0/ch14/app/javascript/controllers/index.js b/8_0/ch14/app/javascript/controllers/index.js new file mode 100644 index 00000000..54ad4cad --- /dev/null +++ b/8_0/ch14/app/javascript/controllers/index.js @@ -0,0 +1,11 @@ +// Import and register all your controllers from the importmap under controllers/* + +import { application } from "controllers/application" + +// Eager load all controllers defined in the import map under controllers/**/*_controller +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) + +// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) +// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" +// lazyLoadControllersFrom("controllers", application) diff --git a/8_0/ch14/app/javascript/custom/image_upload.js b/8_0/ch14/app/javascript/custom/image_upload.js new file mode 100644 index 00000000..e8b1b359 --- /dev/null +++ b/8_0/ch14/app/javascript/custom/image_upload.js @@ -0,0 +1,13 @@ +// 巨大画像のアップロードを防止する +document.addEventListener("turbo:load", function() { + document.addEventListener("change", function(event) { + let image_upload = document.querySelector('#micropost_image'); + if (image_upload && image_upload.files.length > 0) { + const size_in_megabytes = image_upload.files[0].size/1024/1024; + if (size_in_megabytes > 5) { + alert("Maximum file size is 5MB. Please choose a smaller file."); + image_upload.value = ""; + } + } + }); +}); diff --git a/8_0/ch14/app/javascript/custom/menu.js b/8_0/ch14/app/javascript/custom/menu.js new file mode 100644 index 00000000..92e4a8dd --- /dev/null +++ b/8_0/ch14/app/javascript/custom/menu.js @@ -0,0 +1,22 @@ +// メニュー操作 + +// トグルリスナーを追加してクリックをリッスンする +document.addEventListener("turbo:load", function() { + let hamburger = document.querySelector("#hamburger"); + if (hamburger){ + hamburger.addEventListener("click", function(event) { + event.preventDefault(); + let menu = document.querySelector("#navbar-menu"); + menu.classList.toggle("collapse"); + }); + } + + let account = document.querySelector("#account"); + if (account) { + account.addEventListener("click", function(event) { + event.preventDefault(); + let menu = document.querySelector("#dropdown-menu"); + menu.classList.toggle("active"); + }); + } +}); diff --git a/8_0/ch14/app/jobs/application_job.rb b/8_0/ch14/app/jobs/application_job.rb new file mode 100644 index 00000000..d394c3d1 --- /dev/null +++ b/8_0/ch14/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/8_0/ch14/app/mailers/application_mailer.rb b/8_0/ch14/app/mailers/application_mailer.rb new file mode 100644 index 00000000..b166e6e2 --- /dev/null +++ b/8_0/ch14/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "Mailgunに登録したメールアドレス" + layout "mailer" +end diff --git a/8_0/ch14/app/mailers/user_mailer.rb b/8_0/ch14/app/mailers/user_mailer.rb new file mode 100644 index 00000000..4aa71dab --- /dev/null +++ b/8_0/ch14/app/mailers/user_mailer.rb @@ -0,0 +1,12 @@ +class UserMailer < ApplicationMailer + + def account_activation(user) + @user = user + mail to: user.email, subject: "Account activation" + end + + def password_reset(user) + @user = user + mail to: user.email, subject: "Password reset" + end +end diff --git a/8_0/ch14/app/models/application_record.rb b/8_0/ch14/app/models/application_record.rb new file mode 100644 index 00000000..b63caeb8 --- /dev/null +++ b/8_0/ch14/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/8_0/ch14/app/models/concerns/.keep b/8_0/ch14/app/models/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/app/models/micropost.rb b/8_0/ch14/app/models/micropost.rb new file mode 100644 index 00000000..e5be8190 --- /dev/null +++ b/8_0/ch14/app/models/micropost.rb @@ -0,0 +1,13 @@ +class Micropost < ApplicationRecord + belongs_to :user + has_one_attached :image do |attachable| + attachable.variant :display, resize_to_limit: [500, 500] + end + default_scope -> { order(created_at: :desc) } + validates :user_id, presence: true + validates :content, presence: true, length: { maximum: 140 } + validates :image, content_type: { in: %w[image/jpeg image/gif image/png], + message: "must be a valid image format" }, + size: { less_than: 5.megabytes, + message: "should be less than 5MB" } +end diff --git a/8_0/ch14/app/models/relationship.rb b/8_0/ch14/app/models/relationship.rb new file mode 100644 index 00000000..027ce413 --- /dev/null +++ b/8_0/ch14/app/models/relationship.rb @@ -0,0 +1,6 @@ +class Relationship < ApplicationRecord + belongs_to :follower, class_name: "User" + belongs_to :followed, class_name: "User" + validates :follower_id, presence: true + validates :followed_id, presence: true +end diff --git a/8_0/ch14/app/models/user.rb b/8_0/ch14/app/models/user.rb new file mode 100644 index 00000000..a180d54d --- /dev/null +++ b/8_0/ch14/app/models/user.rb @@ -0,0 +1,122 @@ +class User < ApplicationRecord + has_many :microposts, dependent: :destroy + has_many :active_relationships, class_name: "Relationship", + foreign_key: "follower_id", + dependent: :destroy + has_many :passive_relationships, class_name: "Relationship", + foreign_key: "followed_id", + dependent: :destroy + has_many :following, through: :active_relationships, source: :followed + has_many :followers, through: :passive_relationships, source: :follower + attr_accessor :remember_token, :activation_token, :reset_token + before_save :downcase_email + before_create :create_activation_digest + validates :name, presence: true, length: { maximum: 50 } + VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i + validates :email, presence: true, length: { maximum: 255 }, + format: { with: VALID_EMAIL_REGEX }, + uniqueness: true + has_secure_password + validates :password, presence: true, length: { minimum: 6 }, allow_nil: true + + # 渡された文字列のハッシュ値を返す + def User.digest(string) + cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : + BCrypt::Engine.cost + BCrypt::Password.create(string, cost: cost) + end + + # ランダムなトークンを返す + def User.new_token + SecureRandom.urlsafe_base64 + end + + # 永続的セッションのためにユーザーをデータベースに記憶する + def remember + self.remember_token = User.new_token + update_attribute(:remember_digest, User.digest(remember_token)) + remember_digest + end + + # セッションハイジャック防止のためにセッショントークンを返す + # この記憶ダイジェストを再利用しているのは単に利便性のため + def session_token + remember_digest || remember + end + + # 渡されたトークンがダイジェストと一致したらtrueを返す + def authenticated?(attribute, token) + digest = send("#{attribute}_digest") + return false if digest.nil? + BCrypt::Password.new(digest).is_password?(token) + end + + # ユーザーのログイン情報を破棄する + def forget + update_attribute(:remember_digest, nil) + end + + # アカウントを有効にする + def activate + update_attribute(:activated, true) + update_attribute(:activated_at, Time.zone.now) + end + + # 有効化用のメールを送信する + def send_activation_email + UserMailer.account_activation(self).deliver_now + end + + # パスワード再設定の属性を設定する + def create_reset_digest + self.reset_token = User.new_token + update_attribute(:reset_digest, User.digest(reset_token)) + update_attribute(:reset_sent_at, Time.zone.now) + end + + # パスワード再設定のメールを送信する + def send_password_reset_email + UserMailer.password_reset(self).deliver_now + end + + # パスワード再設定の期限が切れている場合はtrueを返す + def password_reset_expired? + reset_sent_at < 2.hours.ago + end + + # ユーザーのステータスフィードを返す + def feed + part_of_feed = "relationships.follower_id = :id or microposts.user_id = :id" + Micropost.left_outer_joins(user: :followers) + .where(part_of_feed, { id: id }).distinct + .includes(:user, image_attachment: :blob) + end + + # ユーザーをフォローする + def follow(other_user) + following << other_user unless self == other_user + end + + # ユーザーをフォロー解除する + def unfollow(other_user) + following.delete(other_user) + end + + # 現在のユーザーが他のユーザーをフォローしていればtrueを返す + def following?(other_user) + following.include?(other_user) + end + + private + + # メールアドレスをすべて小文字にする + def downcase_email + self.email = email.downcase + end + + # 有効化トークンとダイジェストを作成および代入する + def create_activation_digest + self.activation_token = User.new_token + self.activation_digest = User.digest(activation_token) + end +end diff --git a/8_0/ch14/app/views/hello/index.html.erb b/8_0/ch14/app/views/hello/index.html.erb new file mode 100644 index 00000000..71661105 --- /dev/null +++ b/8_0/ch14/app/views/hello/index.html.erb @@ -0,0 +1,16 @@ +
    +
    + +

    + Codespaces + ♥️ + Railsチュートリアル +

    +

    + 🎓✨
    + ロゴ画像が表示されたらセットアップ完了です! +

    +
    +
    diff --git a/8_0/ch14/app/views/layouts/_footer.html.erb b/8_0/ch14/app/views/layouts/_footer.html.erb new file mode 100644 index 00000000..b6e31ff9 --- /dev/null +++ b/8_0/ch14/app/views/layouts/_footer.html.erb @@ -0,0 +1,13 @@ + diff --git a/8_0/ch14/app/views/layouts/_header.html.erb b/8_0/ch14/app/views/layouts/_header.html.erb new file mode 100644 index 00000000..8b07e211 --- /dev/null +++ b/8_0/ch14/app/views/layouts/_header.html.erb @@ -0,0 +1,39 @@ + diff --git a/8_0/ch14/app/views/layouts/application.html.erb b/8_0/ch14/app/views/layouts/application.html.erb new file mode 100644 index 00000000..eb23ef94 --- /dev/null +++ b/8_0/ch14/app/views/layouts/application.html.erb @@ -0,0 +1,24 @@ + + + + <%= full_title(yield(:title)) %> + + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + + + <%= render 'layouts/header' %> +
    + <% flash.each do |message_type, message| %> +
    <%= message %>
    + <% end %> + <%= yield %> + <%= render 'layouts/footer' %> + <%= debug(params) if Rails.env.development? %> +
    + + diff --git a/8_0/ch14/app/views/layouts/mailer.html.erb b/8_0/ch14/app/views/layouts/mailer.html.erb new file mode 100644 index 00000000..cbd34d2e --- /dev/null +++ b/8_0/ch14/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/8_0/ch14/app/views/layouts/mailer.text.erb b/8_0/ch14/app/views/layouts/mailer.text.erb new file mode 100644 index 00000000..37f0bddb --- /dev/null +++ b/8_0/ch14/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/8_0/ch14/app/views/microposts/_micropost.html.erb b/8_0/ch14/app/views/microposts/_micropost.html.erb new file mode 100644 index 00000000..91117986 --- /dev/null +++ b/8_0/ch14/app/views/microposts/_micropost.html.erb @@ -0,0 +1,17 @@ +
  • + <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %> + <%= link_to micropost.user.name, micropost.user %> + + <%= micropost.content %> + <% if micropost.image.attached? %> + <%= image_tag micropost.image.variant(:display) %> + <% end %> + + + Posted <%= time_ago_in_words(micropost.created_at) %> ago. + <% if current_user?(micropost.user) %> + <%= link_to "delete", micropost, data: { "turbo-method": :delete, + turbo_confirm: "You sure?" } %> + <% end %> + +
  • diff --git a/8_0/ch14/app/views/password_resets/edit.html.erb b/8_0/ch14/app/views/password_resets/edit.html.erb new file mode 100644 index 00000000..84bf26ba --- /dev/null +++ b/8_0/ch14/app/views/password_resets/edit.html.erb @@ -0,0 +1,20 @@ +<% provide(:title, 'Reset password') %> +

    Reset password

    + +
    +
    + <%= form_with(model: @user, url: password_reset_path(params[:id])) do |f| %> + <%= render 'shared/error_messages', object: f.object %> + + <%= hidden_field_tag :email, @user.email %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :password_confirmation, "Confirmation" %> + <%= f.password_field :password_confirmation, class: 'form-control' %> + + <%= f.submit "Update password", class: "btn btn-primary" %> + <% end %> +
    +
    diff --git a/8_0/ch14/app/views/password_resets/new.html.erb b/8_0/ch14/app/views/password_resets/new.html.erb new file mode 100644 index 00000000..4a2753d8 --- /dev/null +++ b/8_0/ch14/app/views/password_resets/new.html.erb @@ -0,0 +1,13 @@ +<% provide(:title, "Forgot password") %> +

    Forgot password

    + +
    +
    + <%= form_with(url: password_resets_path, scope: :password_reset) do |f| %> + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.submit "Submit", class: "btn btn-primary" %> + <% end %> +
    +
    diff --git a/8_0/ch14/app/views/relationships/create.turbo_stream.erb b/8_0/ch14/app/views/relationships/create.turbo_stream.erb new file mode 100644 index 00000000..bf7d2e4f --- /dev/null +++ b/8_0/ch14/app/views/relationships/create.turbo_stream.erb @@ -0,0 +1,6 @@ +<%= turbo_stream.update "follow_form" do %> + <%= render partial: "users/unfollow" %> +<% end %> +<%= turbo_stream.update "followers" do %> + <%= @user.followers.count %> +<% end %> diff --git a/8_0/ch14/app/views/relationships/destroy.turbo_stream.erb b/8_0/ch14/app/views/relationships/destroy.turbo_stream.erb new file mode 100644 index 00000000..61dcc9cb --- /dev/null +++ b/8_0/ch14/app/views/relationships/destroy.turbo_stream.erb @@ -0,0 +1,6 @@ +<%= turbo_stream.update "follow_form" do %> + <%= render partial: "users/follow" %> +<% end %> +<%= turbo_stream.update "followers" do %> + <%= @user.followers.count %> +<% end %> diff --git a/8_0/ch14/app/views/sessions/new.html.erb b/8_0/ch14/app/views/sessions/new.html.erb new file mode 100644 index 00000000..8ca43db4 --- /dev/null +++ b/8_0/ch14/app/views/sessions/new.html.erb @@ -0,0 +1,25 @@ +<% provide(:title, "Log in") %> +

    Log in

    + +
    +
    + <%= form_with(url: login_path, scope: :session) do |f| %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= link_to "(forgot password)", new_password_reset_path %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :remember_me, class: "checkbox inline" do %> + <%= f.check_box :remember_me %> + Remember me on this computer + <% end %> + + <%= f.submit "Log in", class: "btn btn-primary" %> + <% end %> + +

    New user? <%= link_to "Sign up now!", signup_path %>

    +
    +
    diff --git a/8_0/ch14/app/views/shared/_error_messages.html.erb b/8_0/ch14/app/views/shared/_error_messages.html.erb new file mode 100644 index 00000000..211bc9d3 --- /dev/null +++ b/8_0/ch14/app/views/shared/_error_messages.html.erb @@ -0,0 +1,12 @@ +<% if object.errors.any? %> +
    +
    + The form contains <%= pluralize(object.errors.count, "error") %>. +
    + +
    +<% end %> diff --git a/8_0/ch14/app/views/shared/_feed.html.erb b/8_0/ch14/app/views/shared/_feed.html.erb new file mode 100644 index 00000000..d46bc93f --- /dev/null +++ b/8_0/ch14/app/views/shared/_feed.html.erb @@ -0,0 +1,7 @@ +<% if @feed_items.any? %> +
      + <%= render @feed_items %> +
    + <%= will_paginate @feed_items, + params: { controller: :static_pages, action: :home } %> +<% end %> diff --git a/8_0/ch14/app/views/shared/_micropost_form.html.erb b/8_0/ch14/app/views/shared/_micropost_form.html.erb new file mode 100644 index 00000000..d93953e8 --- /dev/null +++ b/8_0/ch14/app/views/shared/_micropost_form.html.erb @@ -0,0 +1,10 @@ +<%= form_with(model: @micropost) do |f| %> + <%= render 'shared/error_messages', object: f.object %> +
    + <%= f.text_area :content, placeholder: "Compose new micropost..." %> +
    + <%= f.submit "Post", class: "btn btn-primary" %> + + <%= f.file_field :image, accept: "image/jpeg,image/gif,image/png" %> + +<% end %> diff --git a/8_0/ch14/app/views/shared/_stats.html.erb b/8_0/ch14/app/views/shared/_stats.html.erb new file mode 100644 index 00000000..d60c3f4f --- /dev/null +++ b/8_0/ch14/app/views/shared/_stats.html.erb @@ -0,0 +1,15 @@ +<% @user ||= current_user %> +
    + + + <%= @user.following.count %> + + following + + + + <%= @user.followers.count %> + + followers + +
    diff --git a/8_0/ch14/app/views/shared/_user_info.html.erb b/8_0/ch14/app/views/shared/_user_info.html.erb new file mode 100644 index 00000000..f4d08eb6 --- /dev/null +++ b/8_0/ch14/app/views/shared/_user_info.html.erb @@ -0,0 +1,4 @@ +<%= link_to gravatar_for(current_user, size: 50), current_user %> +

    <%= current_user.name %>

    +<%= link_to "view my profile", current_user %> +<%= pluralize(current_user.microposts.count, "micropost") %> diff --git a/8_0/ch14/app/views/static_pages/about.html.erb b/8_0/ch14/app/views/static_pages/about.html.erb new file mode 100644 index 00000000..dce8fef2 --- /dev/null +++ b/8_0/ch14/app/views/static_pages/about.html.erb @@ -0,0 +1,10 @@ +<% provide(:title, "About") %> +

    About

    +

    + Ruby on Rails Tutorial + is a book and + screencast + to teach web development with + Ruby on Rails. + This is the sample application for the tutorial. +

    diff --git a/8_0/ch14/app/views/static_pages/contact.html.erb b/8_0/ch14/app/views/static_pages/contact.html.erb new file mode 100644 index 00000000..4a5a35a6 --- /dev/null +++ b/8_0/ch14/app/views/static_pages/contact.html.erb @@ -0,0 +1,6 @@ +<% provide(:title, 'Contact') %> +

    Contact

    +

    + Contact the Ruby on Rails Tutorial about the sample app at the + contact page. +

    diff --git a/8_0/ch14/app/views/static_pages/help.html.erb b/8_0/ch14/app/views/static_pages/help.html.erb new file mode 100644 index 00000000..4d06919d --- /dev/null +++ b/8_0/ch14/app/views/static_pages/help.html.erb @@ -0,0 +1,9 @@ +<% provide(:title, "Help") %> +

    Help

    +

    + Get help on the Ruby on Rails Tutorial at the + Rails Tutorial Help page. + To get help on this sample app, see the + Ruby on Rails Tutorial + book. +

    diff --git a/8_0/ch14/app/views/static_pages/home.html.erb b/8_0/ch14/app/views/static_pages/home.html.erb new file mode 100644 index 00000000..fe3d7ca0 --- /dev/null +++ b/8_0/ch14/app/views/static_pages/home.html.erb @@ -0,0 +1,34 @@ +<% if logged_in? %> +
    + +
    +

    Micropost Feed

    + <%= render 'shared/feed' %> +
    +
    +<% else %> +
    +

    Welcome to the Sample App

    + +

    + This is the home page for the + Ruby on Rails Tutorial + sample application. +

    + + <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %> +
    + + <%= link_to image_tag("rails.svg", alt: "Rails logo", width: "200px"), + "https://rubyonrails.org/" %> +<% end %> diff --git a/8_0/ch14/app/views/user_mailer/account_activation.html.erb b/8_0/ch14/app/views/user_mailer/account_activation.html.erb new file mode 100644 index 00000000..c95644ae --- /dev/null +++ b/8_0/ch14/app/views/user_mailer/account_activation.html.erb @@ -0,0 +1,10 @@ +

    Sample App

    + +

    Hi <%= @user.name %>,

    + +

    +Welcome to the Sample App! Click on the link below to activate your account: +

    + +<%= link_to "Activate", edit_account_activation_url(@user.activation_token, + email: @user.email) %> diff --git a/8_0/ch14/app/views/user_mailer/account_activation.text.erb b/8_0/ch14/app/views/user_mailer/account_activation.text.erb new file mode 100644 index 00000000..fecf2be3 --- /dev/null +++ b/8_0/ch14/app/views/user_mailer/account_activation.text.erb @@ -0,0 +1,5 @@ +Hi <%= @user.name %>, + +Welcome to the Sample App! Click on the link below to activate your account: + +<%= edit_account_activation_url(@user.activation_token, email: @user.email) %> diff --git a/8_0/ch14/app/views/user_mailer/password_reset.html.erb b/8_0/ch14/app/views/user_mailer/password_reset.html.erb new file mode 100644 index 00000000..d5a6c379 --- /dev/null +++ b/8_0/ch14/app/views/user_mailer/password_reset.html.erb @@ -0,0 +1,13 @@ +

    Password reset

    + +

    To reset your password click the link below:

    + +<%= link_to "Reset password", edit_password_reset_url(@user.reset_token, + email: @user.email) %> + +

    This link will expire in two hours.

    + +

    +If you did not request your password to be reset, please ignore this email and +your password will stay as it is. +

    diff --git a/8_0/ch14/app/views/user_mailer/password_reset.text.erb b/8_0/ch14/app/views/user_mailer/password_reset.text.erb new file mode 100644 index 00000000..d44d7fc9 --- /dev/null +++ b/8_0/ch14/app/views/user_mailer/password_reset.text.erb @@ -0,0 +1,8 @@ +To reset your password click the link below: + +<%= edit_password_reset_url(@user.reset_token, email: @user.email) %> + +This link will expire in two hours. + +If you did not request your password to be reset, please ignore this email and +your password will stay as it is. diff --git a/8_0/ch14/app/views/users/_follow.html.erb b/8_0/ch14/app/views/users/_follow.html.erb new file mode 100644 index 00000000..e7a59fff --- /dev/null +++ b/8_0/ch14/app/views/users/_follow.html.erb @@ -0,0 +1,4 @@ +<%= form_with(model: current_user.active_relationships.build) do |f| %> +
    <%= hidden_field_tag :followed_id, @user.id %>
    + <%= f.submit "Follow", class: "btn btn-primary" %> +<% end %> diff --git a/8_0/ch14/app/views/users/_follow_form.html.erb b/8_0/ch14/app/views/users/_follow_form.html.erb new file mode 100644 index 00000000..2ff9ff18 --- /dev/null +++ b/8_0/ch14/app/views/users/_follow_form.html.erb @@ -0,0 +1,9 @@ +<% unless current_user?(@user) %> +
    + <% if current_user.following?(@user) %> + <%= render 'unfollow' %> + <% else %> + <%= render 'follow' %> + <% end %> +
    +<% end %> diff --git a/8_0/ch14/app/views/users/_unfollow.html.erb b/8_0/ch14/app/views/users/_unfollow.html.erb new file mode 100644 index 00000000..e8f0ffe1 --- /dev/null +++ b/8_0/ch14/app/views/users/_unfollow.html.erb @@ -0,0 +1,4 @@ +<%= form_with(model: current_user.active_relationships.find_by(followed: @user), + html: { method: :delete }) do |f| %> + <%= f.submit "Unfollow", class: "btn" %> +<% end %> diff --git a/8_0/ch14/app/views/users/_user.html.erb b/8_0/ch14/app/views/users/_user.html.erb new file mode 100644 index 00000000..cf66c339 --- /dev/null +++ b/8_0/ch14/app/views/users/_user.html.erb @@ -0,0 +1,8 @@ +
  • + <%= gravatar_for user, size: 50 %> + <%= link_to user.name, user %> + <% if current_user.admin? && !current_user?(user) %> + | <%= link_to "delete", user, data: { "turbo-method": :delete, + turbo_confirm: "You sure?" } %> + <% end %> +
  • diff --git a/8_0/ch14/app/views/users/edit.html.erb b/8_0/ch14/app/views/users/edit.html.erb new file mode 100644 index 00000000..03bfc46b --- /dev/null +++ b/8_0/ch14/app/views/users/edit.html.erb @@ -0,0 +1,29 @@ +<% provide(:title, "Edit user") %> +

    Update your profile

    + +
    +
    + <%= form_with(model: @user) do |f| %> + <%= render 'shared/error_messages', object: f.object %> + + <%= f.label :name %> + <%= f.text_field :name, class: 'form-control' %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :password_confirmation, "Confirmation" %> + <%= f.password_field :password_confirmation, class: 'form-control' %> + + <%= f.submit "Save changes", class: "btn btn-primary" %> + <% end %> + +
    + <%= gravatar_for @user %> + change +
    +
    +
    diff --git a/8_0/ch14/app/views/users/index.html.erb b/8_0/ch14/app/views/users/index.html.erb new file mode 100644 index 00000000..5b6dbaeb --- /dev/null +++ b/8_0/ch14/app/views/users/index.html.erb @@ -0,0 +1,10 @@ +<% provide(:title, 'All users') %> +

    All users

    + +<%= will_paginate %> + + + +<%= will_paginate %> diff --git a/8_0/ch14/app/views/users/new.html.erb b/8_0/ch14/app/views/users/new.html.erb new file mode 100644 index 00000000..8b436c56 --- /dev/null +++ b/8_0/ch14/app/views/users/new.html.erb @@ -0,0 +1,23 @@ +<% provide(:title, 'Sign up') %> +

    Sign up

    + +
    +
    + <%= form_with(model: @user) do |f| %> + <%= render 'shared/error_messages', object: f.object %> + <%= f.label :name %> + <%= f.text_field :name, class: 'form-control' %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :password_confirmation, "Confirmation" %> + <%= f.password_field :password_confirmation, class: 'form-control' %> + + <%= f.submit "Create my account", class: "btn btn-primary" %> + <% end %> +
    +
    diff --git a/8_0/ch14/app/views/users/show.html.erb b/8_0/ch14/app/views/users/show.html.erb new file mode 100644 index 00000000..27c2cae2 --- /dev/null +++ b/8_0/ch14/app/views/users/show.html.erb @@ -0,0 +1,24 @@ +<% provide(:title, @user.name) %> +
    + +
    + <%= render 'follow_form' if logged_in? %> + <% if @user.microposts.any? %> +

    Microposts (<%= @user.microposts.count %>)

    +
      + <%= render @microposts %> +
    + <%= will_paginate @microposts %> + <% end %> +
    +
    diff --git a/8_0/ch14/app/views/users/show_follow.html.erb b/8_0/ch14/app/views/users/show_follow.html.erb new file mode 100644 index 00000000..59d34c0d --- /dev/null +++ b/8_0/ch14/app/views/users/show_follow.html.erb @@ -0,0 +1,30 @@ +<% provide(:title, @title) %> +
    + +
    +

    <%= @title %>

    + <% if @users.any? %> + + <%= will_paginate %> + <% end %> +
    +
    diff --git a/8_0/ch14/bin/bundle b/8_0/ch14/bin/bundle new file mode 100755 index 00000000..981e650b --- /dev/null +++ b/8_0/ch14/bin/bundle @@ -0,0 +1,114 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + requirement = bundler_gem_version.approximate_recommendation + + return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0") + + requirement += ".a" if bundler_gem_version.prerelease? + + requirement + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/8_0/ch14/bin/dev b/8_0/ch14/bin/dev new file mode 100755 index 00000000..5f91c205 --- /dev/null +++ b/8_0/ch14/bin/dev @@ -0,0 +1,2 @@ +#!/usr/bin/env ruby +exec "./bin/rails", "server", *ARGV diff --git a/8_0/ch14/bin/importmap b/8_0/ch14/bin/importmap new file mode 100755 index 00000000..36502ab1 --- /dev/null +++ b/8_0/ch14/bin/importmap @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require_relative "../config/application" +require "importmap/commands" diff --git a/8_0/ch14/bin/rails b/8_0/ch14/bin/rails new file mode 100755 index 00000000..efc03774 --- /dev/null +++ b/8_0/ch14/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/8_0/ch14/bin/rake b/8_0/ch14/bin/rake new file mode 100755 index 00000000..4fbf10b9 --- /dev/null +++ b/8_0/ch14/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/8_0/ch14/bin/render-build.sh b/8_0/ch14/bin/render-build.sh new file mode 100644 index 00000000..b0156aa6 --- /dev/null +++ b/8_0/ch14/bin/render-build.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# exit on error +set -o errexit +bundle install +bundle exec rails assets:precompile +bundle exec rails assets:clean +DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rails db:migrate:reset +bundle exec rails db:seed diff --git a/8_0/ch14/bin/rubocop b/8_0/ch14/bin/rubocop new file mode 100755 index 00000000..40330c0f --- /dev/null +++ b/8_0/ch14/bin/rubocop @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +# explicit rubocop config increases performance slightly while avoiding config confusion. +ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) + +load Gem.bin_path("rubocop", "rubocop") diff --git a/8_0/ch14/bin/setup b/8_0/ch14/bin/setup new file mode 100755 index 00000000..be3db3c0 --- /dev/null +++ b/8_0/ch14/bin/setup @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +require "fileutils" + +APP_ROOT = File.expand_path("..", __dir__) + +def system!(*args) + system(*args, exception: true) +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts "== Installing dependencies ==" + system("bundle check") || system!("bundle install") + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + puts "\n== Removing old logs and tempfiles ==" + system! "bin/rails log:clear tmp:clear" + + unless ARGV.include?("--skip-server") + puts "\n== Starting development server ==" + STDOUT.flush # flush the output before exec(2) so that it displays + exec "bin/dev" + end +end diff --git a/8_0/ch14/config.ru b/8_0/ch14/config.ru new file mode 100644 index 00000000..4a3c09a6 --- /dev/null +++ b/8_0/ch14/config.ru @@ -0,0 +1,6 @@ +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/8_0/ch14/config/application.rb b/8_0/ch14/config/application.rb new file mode 100644 index 00000000..7e1f5d14 --- /dev/null +++ b/8_0/ch14/config/application.rb @@ -0,0 +1,10 @@ +require_relative "boot" +require "rails/all" +Bundler.require(*Rails.groups) + +module SampleApp + class Application < Rails::Application + config.load_defaults 8.0 + config.active_storage.variant_processor = :mini_magick + end +end diff --git a/8_0/ch14/config/boot.rb b/8_0/ch14/config/boot.rb new file mode 100644 index 00000000..988a5ddc --- /dev/null +++ b/8_0/ch14/config/boot.rb @@ -0,0 +1,4 @@ +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "bundler/setup" # Set up gems listed in the Gemfile. +require "bootsnap/setup" # Speed up boot time by caching expensive operations. diff --git a/8_0/ch14/config/cable.yml b/8_0/ch14/config/cable.yml new file mode 100644 index 00000000..ae7a2256 --- /dev/null +++ b/8_0/ch14/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: codespaces_try_rails_production diff --git a/8_0/ch14/config/credentials.yml.enc b/8_0/ch14/config/credentials.yml.enc new file mode 100644 index 00000000..339d64d2 --- /dev/null +++ b/8_0/ch14/config/credentials.yml.enc @@ -0,0 +1 @@ +NCs9M8uakg1CDYJENuGaipzymY8uG6i5gzghsy4npa3gYmGfYKPHMnEcJHCVHlM3gxDNJBteUqkRCxHQ5WQId8hzgxoQCL5RX7rtQ8XUzkJwgSUjKvbCrG5atjKkN9XuL91is5vtEN5W7vEz3qxN7Rp33QbNFiDfLs71F3zHVDYEMi6Dy1QMhVCa1v/tyRjBu/5m37RuiYF5FzWJnh4LhexSVr7Agm4LRLLN6qLCpoAe6D3TPHHBdtu7Pvy8jlrEfnnkUJ61wrj8+kOL4uLtpFShdYKhDkoTjQk7TCOgWyifnHAXazkBZoJZ1QYv/tZ9/ZoHvMEQ2dtGHlyTX8fbKtI13d2CFjGaDNKvRuurxE8dbeyiTfbycvve46+cz9OTlmhh0SGt24R1WV6GBKoDUeoHrIiBvW5qmKka--dQ0rodxmfgFLg2y/--t2x0gQjh8FYlk8v79vLSNg== \ No newline at end of file diff --git a/8_0/ch14/config/database.yml b/8_0/ch14/config/database.yml new file mode 100644 index 00000000..fcba57f1 --- /dev/null +++ b/8_0/ch14/config/database.yml @@ -0,0 +1,25 @@ +# SQLite. Versions 3.8.0 and up are supported. +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem "sqlite3" +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/8_0/ch14/config/environment.rb b/8_0/ch14/config/environment.rb new file mode 100644 index 00000000..cac53157 --- /dev/null +++ b/8_0/ch14/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/8_0/ch14/config/environments/development.rb b/8_0/ch14/config/environments/development.rb new file mode 100644 index 00000000..6e0241dd --- /dev/null +++ b/8_0/ch14/config/environments/development.rb @@ -0,0 +1,90 @@ +require 'active_support/core_ext/integer/time' + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Make code changes take effect immediately without server restart. + config.enable_reloading = true + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing. + config.server_timing = true + + # Add the following line to disable forgery_protection_origin_check + config.action_controller.forgery_protection_origin_check = false + + # Enable/disable Action Controller caching. By default Action Controller caching is disabled. + # Run rails dev:cache to toggle Action Controller caching. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + config.public_file_server.headers = { 'cache-control' => "public, max-age=#{2.days.to_i}" } + else + config.action_controller.perform_caching = false + end + + # Change to :null_store to avoid any caching. + config.cache_store = :memory_store + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Make template changes take effect immediately. + config.action_mailer.perform_caching = false + + # Set localhost to be used by links generated in mailer templates. + host = 'example.com' # ここをコピペすると失敗します。自分の環境のホストに変えてください。 + # クラウドIDEの場合は以下をお使いください + config.action_mailer.default_url_options = { host: host, protocol: 'https' } + # localhostで開発している場合は以下をお使いください + # config.action_mailer.default_url_options = { host: host, protocol: 'http' } + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Append comments with runtime information tags to SQL queries in logs. + config.active_record.query_log_tags_enabled = true + + # Highlight code that enqueued background job in logs. + config.active_job.verbose_enqueue_logs = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true + + # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. + # config.generators.apply_rubocop_autocorrect_after_generate! + + pf_domain = ENV['GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN'] + config.action_dispatch.default_headers = { + 'X-Frame-Options' => "ALLOW-FROM #{pf_domain}" + } + + # Allow requests from our preview domain. + pf_host = "#{ENV['CODESPACE_NAME']}-3000.#{pf_domain}" + config.hosts << pf_host + + config.action_cable.allowed_request_origins = ["https://#{pf_host}"] +end diff --git a/8_0/ch14/config/environments/production.rb b/8_0/ch14/config/environments/production.rb new file mode 100644 index 00000000..6ed5c3fa --- /dev/null +++ b/8_0/ch14/config/environments/production.rb @@ -0,0 +1,94 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.enable_reloading = false + + # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). + config.eager_load = true + + # Full error reports are disabled. + config.consider_all_requests_local = false + + # Turn on fragment caching in view templates. + config.action_controller.perform_caching = true + + # Cache assets for far-future expiry since they are all digest stamped. + config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" } + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = "http://assets.example.com" + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Assume all access to the app is happening through a SSL-terminating reverse proxy. + config.assume_ssl = true + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = true + + # Skip http-to-https redirect for the default health check endpoint. + # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } + + # Log to STDOUT with the current request id as a default log tag. + config.log_tags = [ :request_id ] + config.logger = ActiveSupport::TaggedLogging.logger(STDOUT) + + # Change to "debug" to log everything (including potentially personally-identifiable information!) + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + + # Prevent health checks from clogging up the logs. + config.silence_healthcheck_path = "/up" + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Replace the default in-process memory cache store with a durable alternative. + # config.cache_store = :mem_cache_store + + # Replace the default in-process and non-durable queuing backend for Active Job. + # config.active_job.queue_adapter = :resque + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + config.action_mailer.raise_delivery_errors = true + host = 'https://<あなたのRenderアプリ名>.onrender.com' + config.action_mailer.default_url_options = { host: host } + # Mailgun API設定 + config.action_mailer.delivery_method = :mailgun + config.action_mailer.mailgun_settings = { + api_key: ENV['MAILGUN_API_KEY'], + domain: ENV['MAILGUN_DOMAIN'] + } + + # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. + # config.action_mailer.smtp_settings = { + # user_name: Rails.application.credentials.dig(:smtp, :user_name), + # password: Rails.application.credentials.dig(:smtp, :password), + # address: "smtp.example.com", + # port: 587, + # authentication: :plain + # } + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Only use :id for inspections in production. + config.active_record.attributes_for_inspect = [ :id ] + + # Enable DNS rebinding protection and other `Host` header attacks. + # config.hosts = [ + # "example.com", # Allow requests from example.com + # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` + # ] + # + # Skip DNS rebinding protection for the default health check endpoint. + # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } +end diff --git a/8_0/ch14/config/environments/test.rb b/8_0/ch14/config/environments/test.rb new file mode 100644 index 00000000..0ebe3b4e --- /dev/null +++ b/8_0/ch14/config/environments/test.rb @@ -0,0 +1,53 @@ +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # While tests run files are not watched, reloading is not necessary. + config.enable_reloading = false + + # Eager loading loads your entire application. When running a single test locally, + # this is usually not necessary, and can slow down your test suite. However, it's + # recommended that you enable it in continuous integration systems to ensure eager + # loading is working properly before deploying your code. + config.eager_load = ENV["CI"].present? + + # Configure public file server for tests with cache-control for performance. + config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } + + # Show full error reports. + config.consider_all_requests_local = true + config.cache_store = :null_store + + # Render exception templates for rescuable exceptions and raise for other exceptions. + config.action_dispatch.show_exceptions = :none + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :test + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true +end diff --git a/8_0/ch14/config/importmap.rb b/8_0/ch14/config/importmap.rb new file mode 100644 index 00000000..caf3ed06 --- /dev/null +++ b/8_0/ch14/config/importmap.rb @@ -0,0 +1,8 @@ +# Pin npm packages by running ./bin/importmap + +pin "application", preload: true +pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true +pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true +pin_all_from "app/javascript/controllers", under: "controllers" +pin_all_from "app/javascript/custom", under: "custom" diff --git a/8_0/ch14/config/initializers/assets.rb b/8_0/ch14/config/initializers/assets.rb new file mode 100644 index 00000000..48732442 --- /dev/null +++ b/8_0/ch14/config/initializers/assets.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = "1.0" + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path diff --git a/8_0/ch14/config/initializers/content_security_policy.rb b/8_0/ch14/config/initializers/content_security_policy.rb new file mode 100644 index 00000000..b3076b38 --- /dev/null +++ b/8_0/ch14/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header + +# Rails.application.configure do +# config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end +# +# # Generate session nonces for permitted importmap, inline scripts, and inline styles. +# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } +# config.content_security_policy_nonce_directives = %w(script-src style-src) +# +# # Report violations without enforcing the policy. +# # config.content_security_policy_report_only = true +# end diff --git a/8_0/ch14/config/initializers/filter_parameter_logging.rb b/8_0/ch14/config/initializers/filter_parameter_logging.rb new file mode 100644 index 00000000..c0b717f7 --- /dev/null +++ b/8_0/ch14/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. +# Use this to limit dissemination of sensitive information. +# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. +Rails.application.config.filter_parameters += [ + :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc +] diff --git a/8_0/ch14/config/initializers/inflections.rb b/8_0/ch14/config/initializers/inflections.rb new file mode 100644 index 00000000..3860f659 --- /dev/null +++ b/8_0/ch14/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym "RESTful" +# end diff --git a/8_0/ch14/config/initializers/new_framework_defaults_8_0.rb b/8_0/ch14/config/initializers/new_framework_defaults_8_0.rb new file mode 100644 index 00000000..92efa951 --- /dev/null +++ b/8_0/ch14/config/initializers/new_framework_defaults_8_0.rb @@ -0,0 +1,30 @@ +# Be sure to restart your server when you modify this file. +# +# This file eases your Rails 8.0 framework defaults upgrade. +# +# Uncomment each configuration one by one to switch to the new default. +# Once your application is ready to run with all new defaults, you can remove +# this file and set the `config.load_defaults` to `8.0`. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. +# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html + +### +# Specifies whether `to_time` methods preserve the UTC offset of their receivers or preserves the timezone. +# If set to `:zone`, `to_time` methods will use the timezone of their receivers. +# If set to `:offset`, `to_time` methods will use the UTC offset. +# If `false`, `to_time` methods will convert to the local system UTC offset instead. +#++ +# Rails.application.config.active_support.to_time_preserves_timezone = :zone + +### +# When both `If-Modified-Since` and `If-None-Match` are provided by the client +# only consider `If-None-Match` as specified by RFC 7232 Section 6. +# If set to `false` both conditions need to be satisfied. +#++ +# Rails.application.config.action_dispatch.strict_freshness = true + +### +# Set `Regexp.timeout` to `1`s by default to improve security over Regexp Denial-of-Service attacks. +#++ +# Regexp.timeout = 1 diff --git a/8_0/ch14/config/initializers/permissions_policy.rb b/8_0/ch14/config/initializers/permissions_policy.rb new file mode 100644 index 00000000..00f64d71 --- /dev/null +++ b/8_0/ch14/config/initializers/permissions_policy.rb @@ -0,0 +1,11 @@ +# Define an application-wide HTTP permissions policy. For further +# information see https://developers.google.com/web/updates/2018/06/feature-policy +# +# Rails.application.config.permissions_policy do |f| +# f.camera :none +# f.gyroscope :none +# f.microphone :none +# f.usb :none +# f.fullscreen :self +# f.payment :self, "https://secure.example.com" +# end diff --git a/8_0/ch14/config/locales/en.yml b/8_0/ch14/config/locales/en.yml new file mode 100644 index 00000000..8ca56fc7 --- /dev/null +++ b/8_0/ch14/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# "true": "foo" +# +# To learn more, please read the Rails Internationalization guide +# available at https://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/8_0/ch14/config/puma.rb b/8_0/ch14/config/puma.rb new file mode 100644 index 00000000..a248513b --- /dev/null +++ b/8_0/ch14/config/puma.rb @@ -0,0 +1,41 @@ +# This configuration file will be evaluated by Puma. The top-level methods that +# are invoked here are part of Puma's configuration DSL. For more information +# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. +# +# Puma starts a configurable number of processes (workers) and each process +# serves each request in a thread from an internal thread pool. +# +# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You +# should only set this value when you want to run 2 or more workers. The +# default is already 1. +# +# The ideal number of threads per worker depends both on how much time the +# application spends waiting for IO operations and on how much you wish to +# prioritize throughput over latency. +# +# As a rule of thumb, increasing the number of threads will increase how much +# traffic a given process can handle (throughput), but due to CRuby's +# Global VM Lock (GVL) it has diminishing returns and will degrade the +# response time (latency) of the application. +# +# The default is set to 3 threads as it's deemed a decent compromise between +# throughput and latency for the average Rails application. +# +# Any libraries that use a connection pool or another resource pool should +# be configured to provide at least as many connections as the number of +# threads. This includes Active Record's `pool` parameter in `database.yml`. +threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +port ENV.fetch("PORT", 3000) + +# Allow puma to be restarted by `bin/rails restart` command. +plugin :tmp_restart + +# Run the Solid Queue supervisor inside of Puma for single-server deployments +plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] + +# Specify the PID file. Defaults to tmp/pids/server.pid in development. +# In other environments, only set the PID file if requested. +pidfile ENV["PIDFILE"] if ENV["PIDFILE"] diff --git a/8_0/ch14/config/routes.rb b/8_0/ch14/config/routes.rb new file mode 100644 index 00000000..29f535d6 --- /dev/null +++ b/8_0/ch14/config/routes.rb @@ -0,0 +1,20 @@ +Rails.application.routes.draw do + root "static_pages#home" + get "/help", to: "static_pages#help" + get "/about", to: "static_pages#about" + get "/contact", to: "static_pages#contact" + get "/signup", to: "users#new" + get "/login", to: "sessions#new" + post "/login", to: "sessions#create" + delete "/logout", to: "sessions#destroy" + resources :users do + member do + get :following, :followers + end + end + resources :account_activations, only: [:edit] + resources :password_resets, only: [:new, :create, :edit, :update] + resources :microposts, only: [:create, :destroy] + resources :relationships, only: [:create, :destroy] + get "/microposts", to: "static_pages#home" +end diff --git a/8_0/ch14/config/storage.yml b/8_0/ch14/config/storage.yml new file mode 100644 index 00000000..4942ab66 --- /dev/null +++ b/8_0/ch14/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket-<%= Rails.env %> + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket-<%= Rails.env %> + +# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name-<%= Rails.env %> + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/8_0/ch14/db/migrate/20260512080056_create_users.rb b/8_0/ch14/db/migrate/20260512080056_create_users.rb new file mode 100644 index 00000000..3b80eb00 --- /dev/null +++ b/8_0/ch14/db/migrate/20260512080056_create_users.rb @@ -0,0 +1,10 @@ +class CreateUsers < ActiveRecord::Migration[8.0] + def change + create_table :users do |t| + t.string :name + t.string :email + + t.timestamps + end + end +end diff --git a/8_0/ch14/db/migrate/20260512080840_add_index_to_users_email.rb b/8_0/ch14/db/migrate/20260512080840_add_index_to_users_email.rb new file mode 100644 index 00000000..a8e4ed8e --- /dev/null +++ b/8_0/ch14/db/migrate/20260512080840_add_index_to_users_email.rb @@ -0,0 +1,5 @@ +class AddIndexToUsersEmail < ActiveRecord::Migration[8.0] + def change + add_index :users, :email, unique: true + end +end diff --git a/8_0/ch14/db/migrate/20260512081511_add_password_digest_to_users.rb b/8_0/ch14/db/migrate/20260512081511_add_password_digest_to_users.rb new file mode 100644 index 00000000..81c7c9e0 --- /dev/null +++ b/8_0/ch14/db/migrate/20260512081511_add_password_digest_to_users.rb @@ -0,0 +1,5 @@ +class AddPasswordDigestToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :password_digest, :string + end +end diff --git a/8_0/ch14/db/migrate/20260626064202_add_remember_digest_to_users.rb b/8_0/ch14/db/migrate/20260626064202_add_remember_digest_to_users.rb new file mode 100644 index 00000000..7a464448 --- /dev/null +++ b/8_0/ch14/db/migrate/20260626064202_add_remember_digest_to_users.rb @@ -0,0 +1,5 @@ +class AddRememberDigestToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :remember_digest, :string + end +end diff --git a/8_0/ch14/db/migrate/20260626075635_add_admin_to_users.rb b/8_0/ch14/db/migrate/20260626075635_add_admin_to_users.rb new file mode 100644 index 00000000..c1f08cf5 --- /dev/null +++ b/8_0/ch14/db/migrate/20260626075635_add_admin_to_users.rb @@ -0,0 +1,5 @@ +class AddAdminToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :admin, :boolean, default: false + end +end diff --git a/8_0/ch14/db/migrate/20260630012638_add_activation_to_users.rb b/8_0/ch14/db/migrate/20260630012638_add_activation_to_users.rb new file mode 100644 index 00000000..be38e387 --- /dev/null +++ b/8_0/ch14/db/migrate/20260630012638_add_activation_to_users.rb @@ -0,0 +1,7 @@ +class AddActivationToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :activation_digest, :string + add_column :users, :activated, :boolean, default: false + add_column :users, :activated_at, :datetime + end +end diff --git a/8_0/ch14/db/migrate/20260630015608_add_reset_to_users.rb b/8_0/ch14/db/migrate/20260630015608_add_reset_to_users.rb new file mode 100644 index 00000000..b5e1c928 --- /dev/null +++ b/8_0/ch14/db/migrate/20260630015608_add_reset_to_users.rb @@ -0,0 +1,6 @@ +class AddResetToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :reset_digest, :string + add_column :users, :reset_sent_at, :datetime + end +end diff --git a/8_0/ch14/db/migrate/20260630022950_create_microposts.rb b/8_0/ch14/db/migrate/20260630022950_create_microposts.rb new file mode 100644 index 00000000..fffe3f55 --- /dev/null +++ b/8_0/ch14/db/migrate/20260630022950_create_microposts.rb @@ -0,0 +1,11 @@ +class CreateMicroposts < ActiveRecord::Migration[8.0] + def change + create_table :microposts do |t| + t.text :content + t.references :user, null: false, foreign_key: true + + t.timestamps + end + add_index :microposts, [:user_id, :created_at] + end +end diff --git a/8_0/ch14/db/migrate/20260630031036_create_active_storage_tables.active_storage.rb b/8_0/ch14/db/migrate/20260630031036_create_active_storage_tables.active_storage.rb new file mode 100644 index 00000000..6bd8bd08 --- /dev/null +++ b/8_0/ch14/db/migrate/20260630031036_create_active_storage_tables.active_storage.rb @@ -0,0 +1,57 @@ +# This migration comes from active_storage (originally 20170806125915) +class CreateActiveStorageTables < ActiveRecord::Migration[7.0] + def change + # Use Active Record's configured type for primary and foreign keys + primary_key_type, foreign_key_type = primary_and_foreign_key_types + + create_table :active_storage_blobs, id: primary_key_type do |t| + t.string :key, null: false + t.string :filename, null: false + t.string :content_type + t.text :metadata + t.string :service_name, null: false + t.bigint :byte_size, null: false + t.string :checksum + + if connection.supports_datetime_with_precision? + t.datetime :created_at, precision: 6, null: false + else + t.datetime :created_at, null: false + end + + t.index [ :key ], unique: true + end + + create_table :active_storage_attachments, id: primary_key_type do |t| + t.string :name, null: false + t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type + t.references :blob, null: false, type: foreign_key_type + + if connection.supports_datetime_with_precision? + t.datetime :created_at, precision: 6, null: false + else + t.datetime :created_at, null: false + end + + t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + + create_table :active_storage_variant_records, id: primary_key_type do |t| + t.belongs_to :blob, null: false, index: false, type: foreign_key_type + t.string :variation_digest, null: false + + t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + end + + private + def primary_and_foreign_key_types + config = Rails.configuration.generators + setting = config.options[config.orm][:primary_key_type] + primary_key_type = setting || :primary_key + foreign_key_type = setting || :bigint + [ primary_key_type, foreign_key_type ] + end +end diff --git a/8_0/ch14/db/migrate/20260630035904_create_relationships.rb b/8_0/ch14/db/migrate/20260630035904_create_relationships.rb new file mode 100644 index 00000000..f12321b3 --- /dev/null +++ b/8_0/ch14/db/migrate/20260630035904_create_relationships.rb @@ -0,0 +1,13 @@ +class CreateRelationships < ActiveRecord::Migration[8.0] + def change + create_table :relationships do |t| + t.integer :follower_id + t.integer :followed_id + + t.timestamps + end + add_index :relationships, :follower_id + add_index :relationships, :followed_id + add_index :relationships, [:follower_id, :followed_id], unique: true + end +end diff --git a/8_0/ch14/db/schema.rb b/8_0/ch14/db/schema.rb new file mode 100644 index 00000000..ccce634e --- /dev/null +++ b/8_0/ch14/db/schema.rb @@ -0,0 +1,80 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[8.0].define(version: 2026_06_30_035904) do + create_table "active_storage_attachments", force: :cascade do |t| + t.string "name", null: false + t.string "record_type", null: false + t.bigint "record_id", null: false + t.bigint "blob_id", null: false + t.datetime "created_at", null: false + t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" + t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true + end + + create_table "active_storage_blobs", force: :cascade do |t| + t.string "key", null: false + t.string "filename", null: false + t.string "content_type" + t.text "metadata" + t.string "service_name", null: false + t.bigint "byte_size", null: false + t.string "checksum" + t.datetime "created_at", null: false + t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true + end + + create_table "active_storage_variant_records", force: :cascade do |t| + t.bigint "blob_id", null: false + t.string "variation_digest", null: false + t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true + end + + create_table "microposts", force: :cascade do |t| + t.text "content" + t.integer "user_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["user_id", "created_at"], name: "index_microposts_on_user_id_and_created_at" + t.index ["user_id"], name: "index_microposts_on_user_id" + end + + create_table "relationships", force: :cascade do |t| + t.integer "follower_id" + t.integer "followed_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["followed_id"], name: "index_relationships_on_followed_id" + t.index ["follower_id", "followed_id"], name: "index_relationships_on_follower_id_and_followed_id", unique: true + t.index ["follower_id"], name: "index_relationships_on_follower_id" + end + + create_table "users", force: :cascade do |t| + t.string "name" + t.string "email" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "password_digest" + t.string "remember_digest" + t.boolean "admin", default: false + t.string "activation_digest" + t.boolean "activated", default: false + t.datetime "activated_at" + t.string "reset_digest" + t.datetime "reset_sent_at" + t.index ["email"], name: "index_users_on_email", unique: true + end + + add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" + add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" + add_foreign_key "microposts", "users" +end diff --git a/8_0/ch14/db/seeds.rb b/8_0/ch14/db/seeds.rb new file mode 100644 index 00000000..715677ac --- /dev/null +++ b/8_0/ch14/db/seeds.rb @@ -0,0 +1,35 @@ +# Users +User.create!(name: "Example User", + email: "example@railstutorial.org", + password: "foobar", + password_confirmation: "foobar", + admin: true, + activated: true, + activated_at: Time.zone.now) + +99.times do |n| + name = Faker::Name.name + email = "example-#{n+1}@railstutorial.org" + password = "password" + User.create!(name: name, + email: email, + password: password, + password_confirmation: password, + activated: true, + activated_at: Time.zone.now) +end + +# マイクロポスト +users = User.order(:created_at).take(6) +50.times do + content = Faker::Lorem.sentence(word_count: 5) + users.each { |user| user.microposts.create!(content: content) } +end + +# ユーザーフォローのリレーションシップを作成する +users = User.all +user = users.first +following = users[2..50] +followers = users[3..40] +following.each { |followed| user.follow(followed) } +followers.each { |follower| follower.follow(user) } diff --git a/8_0/ch14/lib/assets/.keep b/8_0/ch14/lib/assets/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/lib/tasks/.keep b/8_0/ch14/lib/tasks/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/log/.keep b/8_0/ch14/log/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/public/400.html b/8_0/ch14/public/400.html new file mode 100644 index 00000000..282dbc8c --- /dev/null +++ b/8_0/ch14/public/400.html @@ -0,0 +1,114 @@ + + + + + + + The server cannot process the request due to a client error (400 Bad Request) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    The server cannot process the request due to a client error. Please check the request and try again. If you’re the application owner check the logs for more information.

    +
    +
    + + + + diff --git a/8_0/ch14/public/404.html b/8_0/ch14/public/404.html new file mode 100644 index 00000000..c0670bc8 --- /dev/null +++ b/8_0/ch14/public/404.html @@ -0,0 +1,114 @@ + + + + + + + The page you were looking for doesn’t exist (404 Not found) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    The page you were looking for doesn’t exist. You may have mistyped the address or the page may have moved. If you’re the application owner check the logs for more information.

    +
    +
    + + + + diff --git a/8_0/ch14/public/406-unsupported-browser.html b/8_0/ch14/public/406-unsupported-browser.html new file mode 100644 index 00000000..9532a9cc --- /dev/null +++ b/8_0/ch14/public/406-unsupported-browser.html @@ -0,0 +1,114 @@ + + + + + + + Your browser is not supported (406 Not Acceptable) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    Your browser is not supported.
    Please upgrade your browser to continue.

    +
    +
    + + + + diff --git a/8_0/ch14/public/422.html b/8_0/ch14/public/422.html new file mode 100644 index 00000000..8bcf0601 --- /dev/null +++ b/8_0/ch14/public/422.html @@ -0,0 +1,114 @@ + + + + + + + The change you wanted was rejected (422 Unprocessable Entity) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    The change you wanted was rejected. Maybe you tried to change something you didn’t have access to. If you’re the application owner check the logs for more information.

    +
    +
    + + + + diff --git a/8_0/ch14/public/500.html b/8_0/ch14/public/500.html new file mode 100644 index 00000000..d77718c3 --- /dev/null +++ b/8_0/ch14/public/500.html @@ -0,0 +1,114 @@ + + + + + + + We’re sorry, but something went wrong (500 Internal Server Error) + + + + + + + + + + + + + +
    +
    + +
    +
    +

    We’re sorry, but something went wrong.
    If you’re the application owner check the logs for more information.

    +
    +
    + + + + diff --git a/8_0/ch14/public/apple-touch-icon-precomposed.png b/8_0/ch14/public/apple-touch-icon-precomposed.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/public/apple-touch-icon.png b/8_0/ch14/public/apple-touch-icon.png new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/public/favicon.ico b/8_0/ch14/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/public/icon.png b/8_0/ch14/public/icon.png new file mode 100644 index 00000000..c4c9dbfb Binary files /dev/null and b/8_0/ch14/public/icon.png differ diff --git a/8_0/ch14/public/icon.svg b/8_0/ch14/public/icon.svg new file mode 100644 index 00000000..04b34bf8 --- /dev/null +++ b/8_0/ch14/public/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/8_0/ch14/public/railstutorial.png b/8_0/ch14/public/railstutorial.png new file mode 100644 index 00000000..70835e6b Binary files /dev/null and b/8_0/ch14/public/railstutorial.png differ diff --git a/8_0/ch14/public/robots.txt b/8_0/ch14/public/robots.txt new file mode 100644 index 00000000..c19f78ab --- /dev/null +++ b/8_0/ch14/public/robots.txt @@ -0,0 +1 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/8_0/ch14/storage/.keep b/8_0/ch14/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/test/application_system_test_case.rb b/8_0/ch14/test/application_system_test_case.rb new file mode 100644 index 00000000..d19212ab --- /dev/null +++ b/8_0/ch14/test/application_system_test_case.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/8_0/ch14/test/channels/application_cable/connection_test.rb b/8_0/ch14/test/channels/application_cable/connection_test.rb new file mode 100644 index 00000000..800405f1 --- /dev/null +++ b/8_0/ch14/test/channels/application_cable/connection_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase + # test "connects with cookies" do + # cookies.signed[:user_id] = 42 + # + # connect + # + # assert_equal connection.user_id, "42" + # end +end diff --git a/8_0/ch14/test/controllers/.keep b/8_0/ch14/test/controllers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/test/controllers/account_activations_controller_test.rb b/8_0/ch14/test/controllers/account_activations_controller_test.rb new file mode 100644 index 00000000..bcd21995 --- /dev/null +++ b/8_0/ch14/test/controllers/account_activations_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class AccountActivationsControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/8_0/ch14/test/controllers/hello_codespaces_controller_test.rb b/8_0/ch14/test/controllers/hello_codespaces_controller_test.rb new file mode 100644 index 00000000..54353ea6 --- /dev/null +++ b/8_0/ch14/test/controllers/hello_codespaces_controller_test.rb @@ -0,0 +1,8 @@ +require "test_helper" + +class HelloCodespacesControllerTest < ActionDispatch::IntegrationTest + # test "should get index" do + # get "/" + # assert_response :success + # end +end diff --git a/8_0/ch14/test/controllers/microposts_controller_test.rb b/8_0/ch14/test/controllers/microposts_controller_test.rb new file mode 100644 index 00000000..6bea99f9 --- /dev/null +++ b/8_0/ch14/test/controllers/microposts_controller_test.rb @@ -0,0 +1,33 @@ +require "test_helper" + +class MicropostsControllerTest < ActionDispatch::IntegrationTest + + def setup + @micropost = microposts(:orange) + end + + test "should redirect create when not logged in" do + assert_no_difference 'Micropost.count' do + post microposts_path, params: { micropost: { content: "Lorem ipsum" } } + end + assert_redirected_to login_url + end + + test "should redirect destroy when not logged in" do + assert_no_difference 'Micropost.count' do + delete micropost_path(@micropost) + end + assert_response :see_other + assert_redirected_to login_url + end + + test "should redirect destroy for wrong micropost" do + log_in_as(users(:michael)) + micropost = microposts(:ants) + assert_no_difference 'Micropost.count' do + delete micropost_path(micropost) + end + assert_response :see_other + assert_redirected_to root_url + end +end diff --git a/8_0/ch14/test/controllers/relationships_controller_test.rb b/8_0/ch14/test/controllers/relationships_controller_test.rb new file mode 100644 index 00000000..5ce069c1 --- /dev/null +++ b/8_0/ch14/test/controllers/relationships_controller_test.rb @@ -0,0 +1,18 @@ +require "test_helper" + +class RelationshipsControllerTest < ActionDispatch::IntegrationTest + + test "create should require logged-in user" do + assert_no_difference 'Relationship.count' do + post relationships_path + end + assert_redirected_to login_url + end + + test "destroy should require logged-in user" do + assert_no_difference 'Relationship.count' do + delete relationship_path(relationships(:one)) + end + assert_redirected_to login_url + end +end diff --git a/8_0/ch14/test/controllers/sessions_controller_test.rb b/8_0/ch14/test/controllers/sessions_controller_test.rb new file mode 100644 index 00000000..8cc3f29e --- /dev/null +++ b/8_0/ch14/test/controllers/sessions_controller_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +class SessionsControllerTest < ActionDispatch::IntegrationTest + + test "should get new" do + get login_path + assert_response :success + end +end diff --git a/8_0/ch14/test/controllers/static_pages_controller_test.rb b/8_0/ch14/test/controllers/static_pages_controller_test.rb new file mode 100644 index 00000000..847856a3 --- /dev/null +++ b/8_0/ch14/test/controllers/static_pages_controller_test.rb @@ -0,0 +1,28 @@ +require "test_helper" + +class StaticPagesControllerTest < ActionDispatch::IntegrationTest + + test "should get home" do + get root_path + assert_response :success + assert_select "title", "Ruby on Rails Tutorial Sample App" + end + + test "should get help" do + get help_path + assert_response :success + assert_select "title", "Help | Ruby on Rails Tutorial Sample App" + end + + test "should get about" do + get about_path + assert_response :success + assert_select "title", "About | Ruby on Rails Tutorial Sample App" + end + + test "should get contact" do + get contact_path + assert_response :success + assert_select "title", "Contact | Ruby on Rails Tutorial Sample App" + end +end diff --git a/8_0/ch14/test/controllers/users_controller_test.rb b/8_0/ch14/test/controllers/users_controller_test.rb new file mode 100644 index 00000000..dd4641fc --- /dev/null +++ b/8_0/ch14/test/controllers/users_controller_test.rb @@ -0,0 +1,74 @@ +require "test_helper" + +class UsersControllerTest < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + @other_user = users(:archer) + end + + test "should get new" do + get signup_path + assert_response :success + end + + test "should redirect index when not logged in" do + get users_path + assert_redirected_to login_url + end + + test "should redirect edit when not logged in" do + get edit_user_path(@user) + assert_not flash.empty? + assert_redirected_to login_url + end + + test "should redirect update when not logged in" do + patch user_path(@user), params: { user: { name: @user.name, + email: @user.email } } + assert_not flash.empty? + assert_redirected_to login_url + end + + test "should redirect edit when logged in as wrong user" do + log_in_as(@other_user) + get edit_user_path(@user) + assert flash.empty? + assert_redirected_to root_url + end + + test "should redirect update when logged in as wrong user" do + log_in_as(@other_user) + patch user_path(@user), params: { user: { name: @user.name, + email: @user.email } } + assert flash.empty? + assert_redirected_to root_url + end + + test "should redirect destroy when not logged in" do + assert_no_difference 'User.count' do + delete user_path(@user) + end + assert_response :see_other + assert_redirected_to login_url + end + + test "should redirect destroy when logged in as a non-admin" do + log_in_as(@other_user) + assert_no_difference 'User.count' do + delete user_path(@user) + end + assert_response :see_other + assert_redirected_to root_url + end + + test "should redirect following when not logged in" do + get following_user_path(@user) + assert_redirected_to login_url + end + + test "should redirect followers when not logged in" do + get followers_user_path(@user) + assert_redirected_to login_url + end +end diff --git a/8_0/ch14/test/fixtures/files/.keep b/8_0/ch14/test/fixtures/files/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/test/fixtures/microposts.yml b/8_0/ch14/test/fixtures/microposts.yml new file mode 100644 index 00000000..d3beb59b --- /dev/null +++ b/8_0/ch14/test/fixtures/microposts.yml @@ -0,0 +1,41 @@ +orange: + content: "I just ate an orange!" + created_at: <%= 10.minutes.ago %> + user: michael + +tau_manifesto: + content: "Check out the @tauday site by @mhartl: https://tauday.com" + created_at: <%= 3.years.ago %> + user: michael + +cat_video: + content: "Sad cats are sad: https://youtu.be/PKffm2uI4dk" + created_at: <%= 2.hours.ago %> + user: michael + +most_recent: + content: "Writing a short test" + created_at: <%= Time.zone.now %> + user: michael + +<% 30.times do |n| %> +micropost_<%= n %>: + content: <%= Faker::Lorem.sentence(word_count: 5) %> + created_at: <%= 42.days.ago %> + user: michael +<% end %> + +ants: + content: "Oh, is that what you want? Because that's how you get ants!" + created_at: <%= 2.years.ago %> + user: archer + +zone: + content: "Danger zone!" + created_at: <%= 3.days.ago %> + user: archer + +tone: + content: "I'm sorry. Your words made sense, but your sarcastic tone did not." + created_at: <%= 10.minutes.ago %> + user: lana diff --git a/8_0/ch14/test/fixtures/relationships.yml b/8_0/ch14/test/fixtures/relationships.yml new file mode 100644 index 00000000..b567e715 --- /dev/null +++ b/8_0/ch14/test/fixtures/relationships.yml @@ -0,0 +1,15 @@ +one: + follower: michael + followed: lana + +two: + follower: michael + followed: malory + +three: + follower: lana + followed: michael + +four: + follower: archer + followed: michael diff --git a/8_0/ch14/test/fixtures/users.yml b/8_0/ch14/test/fixtures/users.yml new file mode 100644 index 00000000..02f3d49a --- /dev/null +++ b/8_0/ch14/test/fixtures/users.yml @@ -0,0 +1,37 @@ +michael: + name: Michael Example + email: michael@example.com + password_digest: <%= User.digest('password') %> + admin: true + activated: true + activated_at: <%= Time.zone.now %> + +archer: + name: Sterling Archer + email: duchess@example.gov + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> + +lana: + name: Lana Kane + email: hands@example.gov + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> + +malory: + name: Malory Archer + email: boss@example.gov + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> + +<% 30.times do |n| %> +user_<%= n %>: + name: <%= "User #{n}" %> + email: <%= "user-#{n}@example.com" %> + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> +<% end %> diff --git a/8_0/ch14/test/helpers/.keep b/8_0/ch14/test/helpers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/test/helpers/sessions_helper_test.rb b/8_0/ch14/test/helpers/sessions_helper_test.rb new file mode 100644 index 00000000..735ff17a --- /dev/null +++ b/8_0/ch14/test/helpers/sessions_helper_test.rb @@ -0,0 +1,19 @@ +require "test_helper" + +class SessionsHelperTest < ActionView::TestCase + + def setup + @user = users(:michael) + remember(@user) + end + + test "current_user returns right user when session is nil" do + assert_equal @user, current_user + assert is_logged_in? + end + + test "current_user returns nil when remember digest is wrong" do + @user.update_attribute(:remember_digest, User.digest(User.new_token)) + assert_nil current_user + end +end diff --git a/8_0/ch14/test/integration/.keep b/8_0/ch14/test/integration/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/test/integration/following_test.rb b/8_0/ch14/test/integration/following_test.rb new file mode 100644 index 00000000..a753608b --- /dev/null +++ b/8_0/ch14/test/integration/following_test.rb @@ -0,0 +1,76 @@ +require "test_helper" + +class Following < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + @other = users(:archer) + log_in_as(@user) + end +end + +class FollowPagesTest < Following + + test "following page" do + get following_user_path(@user) + assert_response :success + assert_not @user.following.empty? + assert_match @user.following.count.to_s, response.body + @user.following.each do |user| + assert_select "a[href=?]", user_path(user) + end + end + + test "followers page" do + get followers_user_path(@user) + assert_response :success + assert_not @user.followers.empty? + assert_match @user.followers.count.to_s, response.body + @user.followers.each do |user| + assert_select "a[href=?]", user_path(user) + end + end +end + +class FollowTest < Following + + test "should follow a user the standard way" do + assert_difference "@user.following.count", 1 do + post relationships_path, params: { followed_id: @other.id } + end + assert_redirected_to @other + end + + test "should follow a user with Hotwire" do + assert_difference "@user.following.count", 1 do + post relationships_path(format: :turbo_stream), + params: { followed_id: @other.id } + end + end +end + +class Unfollow < Following + + def setup + super + @user.follow(@other) + @relationship = @user.active_relationships.find_by(followed_id: @other.id) + end +end + +class UnfollowTest < Unfollow + + test "should unfollow a user the standard way" do + assert_difference "@user.following.count", -1 do + delete relationship_path(@relationship) + end + assert_response :see_other + assert_redirected_to @other + end + + test "should unfollow a user with Hotwire" do + assert_difference "@user.following.count", -1 do + delete relationship_path(@relationship, format: :turbo_stream) + end + end +end diff --git a/8_0/ch14/test/integration/microposts_interface_test.rb b/8_0/ch14/test/integration/microposts_interface_test.rb new file mode 100644 index 00000000..c6242446 --- /dev/null +++ b/8_0/ch14/test/integration/microposts_interface_test.rb @@ -0,0 +1,52 @@ +require "test_helper" + +class MicropostsInterface < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + log_in_as(@user) + end +end + +class MicropostsInterfaceTest < MicropostsInterface + + test "should paginate microposts" do + get root_path + assert_select 'div.pagination' + end + + test "should show errors but not create micropost on invalid submission" do + assert_no_difference 'Micropost.count' do + post microposts_path, params: { micropost: { content: "" } } + end + assert_select 'div#error_explanation' + assert_select 'a[href=?]', '/?page=2' # 正しいページネーションリンク + end + + test "should create a micropost on valid submission" do + content = "This micropost really ties the room together" + assert_difference 'Micropost.count', 1 do + post microposts_path, params: { micropost: { content: content } } + end + assert_redirected_to root_url + follow_redirect! + assert_match content, response.body + end + + test "should have micropost delete links on own profile page" do + get user_path(@user) + assert_select 'a', text: 'delete' + end + + test "should be able to delete own micropost" do + first_micropost = @user.microposts.paginate(page: 1).first + assert_difference 'Micropost.count', -1 do + delete micropost_path(first_micropost) + end + end + + test "should not have delete links on other user's profile page" do + get user_path(users(:archer)) + assert_select 'a', { text: 'delete', count: 0 } + end +end diff --git a/8_0/ch14/test/integration/password_resets_test.rb b/8_0/ch14/test/integration/password_resets_test.rb new file mode 100644 index 00000000..2daf9c61 --- /dev/null +++ b/8_0/ch14/test/integration/password_resets_test.rb @@ -0,0 +1,98 @@ +require "test_helper" + +class PasswordResets < ActionDispatch::IntegrationTest + + def setup + ActionMailer::Base.deliveries.clear + end +end + +class ForgotPasswordFormTest < PasswordResets + + test "password reset path" do + get new_password_reset_path + assert_template 'password_resets/new' + assert_select 'input[name=?]', 'password_reset[email]' + end + + test "reset path with invalid email" do + post password_resets_path, params: { password_reset: { email: "" } } + assert_response :unprocessable_entity + assert_not flash.empty? + assert_template 'password_resets/new' + end +end + +class PasswordResetForm < PasswordResets + + def setup + super + @user = users(:michael) + post password_resets_path, + params: { password_reset: { email: @user.email } } + @reset_user = assigns(:user) + end +end + +class PasswordFormTest < PasswordResetForm + + test "reset with valid email" do + assert_not_equal @user.reset_digest, @reset_user.reset_digest + assert_equal 1, ActionMailer::Base.deliveries.size + assert_not flash.empty? + assert_redirected_to root_url + end + + test "reset with wrong email" do + get edit_password_reset_path(@reset_user.reset_token, email: "") + assert_redirected_to root_url + end + + test "reset with inactive user" do + @reset_user.toggle!(:activated) + get edit_password_reset_path(@reset_user.reset_token, + email: @reset_user.email) + assert_redirected_to root_url + end + + test "reset with right email but wrong token" do + get edit_password_reset_path('wrong token', email: @reset_user.email) + assert_redirected_to root_url + end + + test "reset with right email and right token" do + get edit_password_reset_path(@reset_user.reset_token, + email: @reset_user.email) + assert_template 'password_resets/edit' + assert_select "input[name=email][type=hidden][value=?]", @reset_user.email + end +end + +class PasswordUpdateTest < PasswordResetForm + + test "update with invalid password and confirmation" do + patch password_reset_path(@reset_user.reset_token), + params: { email: @reset_user.email, + user: { password: "foobaz", + password_confirmation: "barquux" } } + assert_select 'div#error_explanation' + end + + test "update with empty password" do + patch password_reset_path(@reset_user.reset_token), + params: { email: @reset_user.email, + user: { password: "", + password_confirmation: "" } } + assert_select 'div#error_explanation' + end + + test "update with valid password and confirmation" do + patch password_reset_path(@reset_user.reset_token), + params: { email: @reset_user.email, + user: { password: "foobaz", + password_confirmation: "foobaz" } } + assert is_logged_in? + assert_not flash.empty? + assert_redirected_to @reset_user + end +end diff --git a/8_0/ch14/test/integration/site_layout_test.rb b/8_0/ch14/test/integration/site_layout_test.rb new file mode 100644 index 00000000..0f7ca649 --- /dev/null +++ b/8_0/ch14/test/integration/site_layout_test.rb @@ -0,0 +1,13 @@ +require "test_helper" + +class SiteLayoutTest < ActionDispatch::IntegrationTest + + test "layout links" do + get root_path + assert_template 'static_pages/home' + assert_select "a[href=?]", root_path, count: 2 + assert_select "a[href=?]", help_path + assert_select "a[href=?]", about_path + assert_select "a[href=?]", contact_path + end +end diff --git a/8_0/ch14/test/integration/users_edit_test.rb b/8_0/ch14/test/integration/users_edit_test.rb new file mode 100644 index 00000000..23ca6b68 --- /dev/null +++ b/8_0/ch14/test/integration/users_edit_test.rb @@ -0,0 +1,37 @@ +require "test_helper" + +class UsersEditTest < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + end + + test "unsuccessful edit" do + log_in_as(@user) + get edit_user_path(@user) + assert_template 'users/edit' + patch user_path(@user), params: { user: { name: "", + email: "foo@invalid", + password: "foo", + password_confirmation: "bar" } } + + assert_template 'users/edit' + end + + test "successful edit with friendly forwarding" do + get edit_user_path(@user) + log_in_as(@user) + assert_redirected_to edit_user_url(@user) + name = "Foo Bar" + email = "foo@bar.com" + patch user_path(@user), params: { user: { name: name, + email: email, + password: "", + password_confirmation: "" } } + assert_not flash.empty? + assert_redirected_to @user + @user.reload + assert_equal name, @user.name + assert_equal email, @user.email + end +end diff --git a/8_0/ch14/test/integration/users_index_test.rb b/8_0/ch14/test/integration/users_index_test.rb new file mode 100644 index 00000000..63bb8d9e --- /dev/null +++ b/8_0/ch14/test/integration/users_index_test.rb @@ -0,0 +1,34 @@ +require "test_helper" + +class UsersIndexTest < ActionDispatch::IntegrationTest + + def setup + @admin = users(:michael) + @non_admin = users(:archer) + end + + test "index as admin including pagination and delete links" do + log_in_as(@admin) + get users_path + assert_template 'users/index' + assert_select 'div.pagination' + first_page_of_users = User.paginate(page: 1) + first_page_of_users.each do |user| + assert_select 'a[href=?]', user_path(user), text: user.name + unless user == @admin + assert_select 'a[href=?]', user_path(user), text: 'delete' + end + end + assert_difference 'User.count', -1 do + delete user_path(@non_admin) + assert_response :see_other + assert_redirected_to users_url + end + end + + test "index as non-admin" do + log_in_as(@non_admin) + get users_path + assert_select 'a', text: 'delete', count: 0 + end +end diff --git a/8_0/ch14/test/integration/users_login_test.rb b/8_0/ch14/test/integration/users_login_test.rb new file mode 100644 index 00000000..a9e70eaa --- /dev/null +++ b/8_0/ch14/test/integration/users_login_test.rb @@ -0,0 +1,96 @@ +require "test_helper" + +class UsersLogin < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + end +end + +class InvalidPasswordTest < UsersLogin + + test "login path" do + get login_path + assert_template 'sessions/new' + end + + test "login with valid email/invalid password" do + post login_path, params: { session: { email: @user.email, + password: "invalid" } } + assert_not is_logged_in? + assert_template 'sessions/new' + assert_not flash.empty? + get root_path + assert flash.empty? + end +end + +class ValidLogin < UsersLogin + + def setup + super + post login_path, params: { session: { email: @user.email, + password: 'password' } } + end +end + +class ValidLoginTest < ValidLogin + + test "valid login" do + assert is_logged_in? + assert_redirected_to @user + end + + test "redirect after login" do + follow_redirect! + assert_template 'users/show' + assert_select "a[href=?]", login_path, count: 0 + assert_select "a[href=?]", logout_path + assert_select "a[href=?]", user_path(@user) + end +end + +class Logout < ValidLogin + + def setup + super + delete logout_path + end +end + +class LogoutTest < Logout + + test "successful logout" do + assert_not is_logged_in? + assert_response :see_other + assert_redirected_to root_url + end + + test "redirect after logout" do + follow_redirect! + assert_select "a[href=?]", login_path + assert_select "a[href=?]", logout_path, count: 0 + assert_select "a[href=?]", user_path(@user), count: 0 + end + + test "should still work after logout in second window" do + delete logout_path + assert_redirected_to root_url + end +end + +class RememberingTest < UsersLogin + + test "login with remembering" do + log_in_as(@user, remember_me: '1') + assert_not cookies[:remember_token].blank? + end + + test "login without remembering" do + # Cookieを保存してログイン + log_in_as(@user, remember_me: '1') + # Cookieが削除されていることを検証してからログイン + log_in_as(@user, remember_me: '0') + assert cookies[:remember_token].blank? + end +end diff --git a/8_0/ch14/test/integration/users_profile_test.rb b/8_0/ch14/test/integration/users_profile_test.rb new file mode 100644 index 00000000..4a74f1cd --- /dev/null +++ b/8_0/ch14/test/integration/users_profile_test.rb @@ -0,0 +1,22 @@ +require "test_helper" + +class UsersProfileTest < ActionDispatch::IntegrationTest + include ApplicationHelper + + def setup + @user = users(:michael) + end + + test "profile display" do + get user_path(@user) + assert_template 'users/show' + assert_select 'title', full_title(@user.name) + assert_select 'h1', text: @user.name + assert_select 'h1>img.gravatar' + assert_match @user.microposts.count.to_s, response.body + assert_select 'div.pagination' + @user.microposts.paginate(page: 1).each do |micropost| + assert_match micropost.content, response.body + end + end +end diff --git a/8_0/ch14/test/integration/users_signup_test.rb b/8_0/ch14/test/integration/users_signup_test.rb new file mode 100644 index 00000000..92172d43 --- /dev/null +++ b/8_0/ch14/test/integration/users_signup_test.rb @@ -0,0 +1,73 @@ +require "test_helper" + +class UsersSignup < ActionDispatch::IntegrationTest + + def setup + ActionMailer::Base.deliveries.clear + end +end + +class UsersSignupTest < UsersSignup + + test "invalid signup information" do + assert_no_difference 'User.count' do + post users_path, params: { user: { name: "", + email: "user@invalid", + password: "foo", + password_confirmation: "bar" } } + end + assert_response :unprocessable_entity + assert_template 'users/new' + assert_select 'div#error_explanation' + assert_select 'div.field_with_errors' + end + + test "valid signup information with account activation" do + assert_difference 'User.count', 1 do + post users_path, params: { user: { name: "Example User", + email: "user@example.com", + password: "password", + password_confirmation: "password" } } + end + assert_equal 1, ActionMailer::Base.deliveries.size + end +end + +class AccountActivationTest < UsersSignup + + def setup + super + post users_path, params: { user: { name: "Example User", + email: "user@example.com", + password: "password", + password_confirmation: "password" } } + @user = assigns(:user) + end + + test "should not be activated" do + assert_not @user.activated? + end + + test "should not be able to log in before account activation" do + log_in_as(@user) + assert_not is_logged_in? + end + + test "should not be able to log in with invalid activation token" do + get edit_account_activation_path("invalid token", email: @user.email) + assert_not is_logged_in? + end + + test "should not be able to log in with invalid email" do + get edit_account_activation_path(@user.activation_token, email: 'wrong') + assert_not is_logged_in? + end + + test "should log in successfully with valid activation token and email" do + get edit_account_activation_path(@user.activation_token, email: @user.email) + assert @user.reload.activated? + follow_redirect! + assert_template 'users/show' + assert is_logged_in? + end +end diff --git a/8_0/ch14/test/mailers/.keep b/8_0/ch14/test/mailers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/test/mailers/previews/user_mailer_preview.rb b/8_0/ch14/test/mailers/previews/user_mailer_preview.rb new file mode 100644 index 00000000..86da64f1 --- /dev/null +++ b/8_0/ch14/test/mailers/previews/user_mailer_preview.rb @@ -0,0 +1,19 @@ +# Preview all emails at http://localhost:3000/rails/mailers/user_mailer +class UserMailerPreview < ActionMailer::Preview + + # Preview this email at + # http://localhost:3000/rails/mailers/user_mailer/account_activation + def account_activation + user = User.first + user.activation_token = User.new_token + UserMailer.account_activation(user) + end + + # Preview this email at + # http://localhost:3000/rails/mailers/user_mailer/password_reset + def password_reset + user = User.first + user.reset_token = User.new_token + UserMailer.password_reset(user) + end +end diff --git a/8_0/ch14/test/mailers/user_mailer_test.rb b/8_0/ch14/test/mailers/user_mailer_test.rb new file mode 100644 index 00000000..d927329d --- /dev/null +++ b/8_0/ch14/test/mailers/user_mailer_test.rb @@ -0,0 +1,27 @@ +require "test_helper" + +class UserMailerTest < ActionMailer::TestCase + + test "account_activation" do + user = users(:michael) + user.activation_token = User.new_token + mail = UserMailer.account_activation(user) + assert_equal "Account activation", mail.subject + assert_equal [user.email], mail.to + assert_equal ["Mailgunに登録したメールアドレス"], mail.from + assert_match user.name, mail.body.encoded + assert_match user.activation_token, mail.body.encoded + assert_match CGI.escape(user.email), mail.body.encoded + end + + test "password_reset" do + user = users(:michael) + user.reset_token = User.new_token + mail = UserMailer.password_reset(user) + assert_equal "Password reset", mail.subject + assert_equal [user.email], mail.to + assert_equal ["Mailgunに登録したメールアドレス"], mail.from + assert_match user.reset_token, mail.body.encoded + assert_match CGI.escape(user.email), mail.body.encoded + end +end diff --git a/8_0/ch14/test/models/.keep b/8_0/ch14/test/models/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/test/models/micropost_test.rb b/8_0/ch14/test/models/micropost_test.rb new file mode 100644 index 00000000..f6acce8e --- /dev/null +++ b/8_0/ch14/test/models/micropost_test.rb @@ -0,0 +1,32 @@ +require "test_helper" + +class MicropostTest < ActiveSupport::TestCase + + def setup + @user = users(:michael) + @micropost = @user.microposts.build(content: "Lorem ipsum") + end + + test "should be valid" do + assert @micropost.valid? + end + + test "user id should be present" do + @micropost.user_id = nil + assert_not @micropost.valid? + end + + test "content should be present" do + @micropost.content = " " + assert_not @micropost.valid? + end + + test "content should be at most 140 characters" do + @micropost.content = "a" * 141 + assert_not @micropost.valid? + end + + test "order should be most recent first" do + assert_equal microposts(:most_recent), Micropost.first + end +end diff --git a/8_0/ch14/test/models/relationship_test.rb b/8_0/ch14/test/models/relationship_test.rb new file mode 100644 index 00000000..dfccc779 --- /dev/null +++ b/8_0/ch14/test/models/relationship_test.rb @@ -0,0 +1,23 @@ +require "test_helper" + +class RelationshipTest < ActiveSupport::TestCase + + def setup + @relationship = Relationship.new(follower_id: users(:michael).id, + followed_id: users(:archer).id) + end + + test "should be valid" do + assert @relationship.valid? + end + + test "should require a follower_id" do + @relationship.follower_id = nil + assert_not @relationship.valid? + end + + test "should require a followed_id" do + @relationship.followed_id = nil + assert_not @relationship.valid? + end +end diff --git a/8_0/ch14/test/models/user_test.rb b/8_0/ch14/test/models/user_test.rb new file mode 100644 index 00000000..ff7c23bc --- /dev/null +++ b/8_0/ch14/test/models/user_test.rb @@ -0,0 +1,116 @@ +require "test_helper" + +class UserTest < ActiveSupport::TestCase + + def setup + @user = User.new(name: "Example User", email: "user@example.com", + password: "foobar", password_confirmation: "foobar") + end + + test "should be valid" do + assert @user.valid? + end + + test "name should be present" do + @user.name = " " + assert_not @user.valid? + end + + test "email should be present" do + @user.email = " " + assert_not @user.valid? + end + + test "name should not be too long" do + @user.name = "a" * 51 + assert_not @user.valid? + end + + test "email should not be too long" do + @user.email = "a" * 244 + "@example.com" + assert_not @user.valid? + end + + test "email validation should accept valid addresses" do + valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org + first.last@foo.jp alice+bob@baz.cn] + valid_addresses.each do |valid_address| + @user.email = valid_address + assert @user.valid?, "#{valid_address.inspect} should be valid" + end + end + + test "email validation should reject invalid addresses" do + invalid_addresses = %w[user@example,com user_at_foo.org user.name@example. + foo@bar_baz.com foo@bar+baz.com] + invalid_addresses.each do |invalid_address| + @user.email = invalid_address + assert_not @user.valid?, "#{invalid_address.inspect} should be invalid" + end + end + + test "email addresses should be unique" do + duplicate_user = @user.dup + @user.save + assert_not duplicate_user.valid? + end + + test "password should be present (nonblank)" do + @user.password = @user.password_confirmation = " " * 6 + assert_not @user.valid? + end + + test "password should have a minimum length" do + @user.password = @user.password_confirmation = "a" * 5 + assert_not @user.valid? + end + + test "authenticated? should return false for a user with nil digest" do + assert_not @user.authenticated?(:remember, '') + end + + test "associated microposts should be destroyed" do + @user.save + @user.microposts.create!(content: "Lorem ipsum") + assert_difference 'Micropost.count', -1 do + @user.destroy + end + end + + test "should follow and unfollow a user" do + michael = users(:michael) + archer = users(:archer) + assert_not michael.following?(archer) + michael.follow(archer) + assert michael.following?(archer) + assert archer.followers.include?(michael) + michael.unfollow(archer) + assert_not michael.following?(archer) + # ユーザーは自分自身をフォローできない + michael.follow(michael) + assert_not michael.following?(michael) + end + + test "feed should have the right posts" do + michael = users(:michael) + archer = users(:archer) + lana = users(:lana) + # フォローしているユーザーの投稿を確認 + lana.microposts.each do |post_following| + assert michael.feed.include?(post_following) + end + # フォロワーがいるユーザー自身の投稿を確認 + michael.microposts.each do |post_self| + assert michael.feed.include?(post_self) + assert_equal michael.feed.distinct, michael.feed + end + # フォロワーがいないユーザー自身の投稿を確認 + archer.microposts.each do |post_self| + assert archer.feed.include?(post_self) + end + # フォローしていないユーザーの投稿を確認 + archer.microposts.each do |post_unfollowed| + assert_not michael.feed.include?(post_unfollowed) + end + end +end diff --git a/8_0/ch14/test/system/.keep b/8_0/ch14/test/system/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/test/test_helper.rb b/8_0/ch14/test/test_helper.rb new file mode 100644 index 00000000..5fbcafd0 --- /dev/null +++ b/8_0/ch14/test/test_helper.rb @@ -0,0 +1,33 @@ +ENV["RAILS_ENV"] ||= "test" +require_relative "../config/environment" +require "rails/test_help" +require "minitest/reporters" +Minitest::Reporters.use! + +class ActiveSupport::TestCase + # 指定のワーカー数でテストを並列実行する + parallelize(workers: :number_of_processors) + + # test/fixtures/*.ymlにあるすべてのfixtureをセットアップする + fixtures :all + + # テストユーザーがログイン中の場合にtrueを返す + def is_logged_in? + !session[:user_id].nil? + end + + # テストユーザーとしてログインする + def log_in_as(user) + session[:user_id] = user.id + end +end + +class ActionDispatch::IntegrationTest + + # テストユーザーとしてログインする + def log_in_as(user, password: 'password', remember_me: '1') + post login_path, params: { session: { email: user.email, + password: password, + remember_me: remember_me } } + end +end \ No newline at end of file diff --git a/8_0/ch14/tmp/.keep b/8_0/ch14/tmp/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/tmp/pids/.keep b/8_0/ch14/tmp/pids/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/tmp/storage/.keep b/8_0/ch14/tmp/storage/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/vendor/.keep b/8_0/ch14/vendor/.keep new file mode 100644 index 00000000..e69de29b diff --git a/8_0/ch14/vendor/javascript/.keep b/8_0/ch14/vendor/javascript/.keep new file mode 100644 index 00000000..e69de29b diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..f11245d6 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,180 @@ +# AGENTS.md + +This file provides guidance to Codex (Codex.ai/code) when working with code in this repository. + +## リポジトリ概要 + +[Railsチュートリアル](https://railstutorial.jp/)の各章が終わった状態を集めたリポジトリ。Sample App開発中のエラー解決や、[解説動画](https://railstutorial.jp/screencast)視聴時の参考資料として活用されることを目的としています。最終的にTwitterライクなマイクロブログアプリケーション「Sample App」が完成します。 + +### バージョン対応 +- Rails 7.0対応(第7版)- GitHub Codespaces推奨 +- Rails 6.1/6.0対応(第6版)- AWS Cloud9推奨 +- Rails 5.1/5.0対応(第4版) +- Rails 4.2対応(第3版) +- Rails 4.0対応(第2版) + +**注意**: 演習結果やプロ品質のデプロイは含まれていません。 + +## よく使うコマンド + +### セットアップ手順(GitHub Codespaces使用時) +```bash +# 1. リポジトリをフォークしてクローン +git clone https://github.com/<あなたのアカウント名>/sample_apps.git +cd 7_0/ch11 + +# 2. Gitの初期化とコミット +git init +git add . +git commit -m "第11章動作確認" + +# 3. GitHubでリポジトリ作成後、リモート設定 +git remote add origin https://github.com/<あなたのアカウント名>/sample_ch11.git +git push -u origin main + +# 4. Codespacesで環境構築後 +rails db:migrate +rails test +rails db:seed +``` + +### ローカル環境での実行 +```bash +# 特定のバージョン/章に移動 +cd 7_0/ch14 + +# 依存関係のインストール(本番環境用gemをスキップ) +bundle install --without production + +# データベースセットアップ +rails db:migrate +rails db:seed + +# サーバー起動 +rails server +``` + +### テスト実行 +```bash +# 全テスト実行 +rails test + +# 特定のテストファイル実行 +rails test test/models/user_test.rb + +# 特定のテスト実行 +rails test test/models/user_test.rb -n test_should_be_valid + +# Guard自動テスト(利用可能な場合) +bundle exec guard +``` + +### データベース操作 +```bash +# データベースリセット +rails db:reset + +# マイグレーション実行 +rails db:migrate + +# マイグレーションロールバック +rails db:rollback +``` + +## アーキテクチャ概要 + +### アプリケーション構造(Rails 7.0版) + +**主要モデル:** +- `User`: ユーザー認証、プロフィール、フォロー関係 +- `Micropost`: ユーザー投稿(画像添付可能) +- `Relationship`: ユーザー間のフォロー/フォロワー関係 + +**主要コントローラ:** +- `UsersController`: ユーザー登録、プロフィール、フォロー一覧 +- `SessionsController`: ログイン/ログアウト +- `MicropostsController`: 投稿の作成・削除 +- `StaticPagesController`: 静的ページ(Home、Help、About、Contact) +- `AccountActivationsController`: メールによるアカウント有効化 +- `PasswordResetsController`: パスワードリセット + +**章ごとの機能進化:** +- 第3-5章: 静的ページ、基本テスト、Bootstrapスタイル +- 第6-7章: Userモデル、セキュアパスワード、ユーザー登録 +- 第8-9章: ログイン/ログアウト、Remember me機能 +- 第10章: ユーザー更新、管理者機能、ページネーション +- 第11章: メールによるアカウント有効化 +- 第12章: パスワードリセット機能 +- 第13章: 画像アップロード付きマイクロポスト +- 第14章: フォロー/フォロワー関係、ステータスフィード + +### 技術スタック +- **フレームワーク**: Rails(バージョンはディレクトリごとに異なる) +- **データベース**: SQLite3(開発/テスト)、PostgreSQL(本番) +- **フロントエンド**: Bootstrap-sass、Turbo/Stimulus(Rails 7.0) +- **テスト**: MinitestとGuard +- **認証**: bcryptによるパスワードハッシュ化 +- **ファイルストレージ**: Active StorageとAWS S3(本番環境) + +### 重要な設定 +- デフォルトテストユーザー: `example@railstutorial.org` / `foobar` +- 各章は前章の内容を基に段階的に構築 +- 第13章以降の本番デプロイにはAmazon S3のセットアップが必要 +- Rails 7.0ではWebpackerの代わりにImport mapsを使用 +- 動作環境: GitHub Codespaces(第7版)またはAWS Cloud9(第6版以前)推奨 + +### 開発上の注意点 +- 専用のリントツールは含まれていません(Rails 7.0のSolargraphを除く) +- 標準的なRailsの規約に従っています +- 実装が完了したと判断する前にテストが通ることを確認 +- デバッグやデータ確認にはRailsコンソール(`rails console`)を使用 + +## リポジトリ構造 + +``` +sample_apps/ +├── 4_0/ # Rails 4.0対応(第2版) +├── 4_2/ # Rails 4.2対応(第3版) +├── 5_0/ # Rails 5.0対応(第4版) +├── 5_1/ # Rails 5.1対応(第4版) +├── 6_0/ # Rails 6.0対応(第6版) +├── 6_1/ # Rails 6.1対応(第6版) +├── 7_0/ # Rails 7.0対応(第7版) +│ ├── ch01/ # 第1章完了時点 +│ ├── ch02/ # 第2章完了時点 +│ ├── ... # 各章ごと +│ └── ch14/ # 第14章完了時点(完成版) +└── omake/ # 追加サンプル +``` + +## よくあるデバッグコマンド + +```bash +# ログ確認 +tail -f log/development.log + +# Railsコンソール +rails console +rails c # 短縮形 + +# データベースコンソール +rails dbconsole +rails db # 短縮形 + +# ルーティング確認 +rails routes +rails routes | grep users # 特定のルートを検索 + +# 環境情報確認 +rails about + +# アセットプリコンパイル(本番環境向け) +rails assets:precompile +``` + +## 関連リソース + +- [Railsチュートリアル公式サイト](https://railstutorial.jp/) +- [解説動画](https://railstutorial.jp/screencast) +- [お試し視聴](https://railstutorial.jp/trial) +- [GitHub Codespaces設定](https://github.com/yasslab/codespaces-railstutorial) \ No newline at end of file