Airbnb-clone by ruby and rails
tutorial
- databaseを作る
bundle exec rails db:create
- サーバー起動
bundle exec rails s
- rootをsettingする
// config/routes.rb
Rails.application.routes.draw do
root "home#index"
end
- rspecをtestとしてsetting https://github.com/rspec/rspec-rails testing framework。コマンド1つで何度でもテストを実行できるので、プログラムのバグを減らしたり、コードの品質を上げる
gem install rspec-rails
//Gemfile
gem "rspec-rails", "~> 6.0.0"
- Airbnbのinspectを利用してfaviconをdownload setting
// app/views/layouts/application.html.erb
<%= favicon_link_tag asset_path('gatsby-icon.png') %>
- generate controller spec/requests/home_spec.rbを作成、ほかのhelperはremove
─(00:10:02 on feature ✹ ✭)──> bundle exec rails g controller home --force
force app/controllers/home_controller.rb
invoke tailwindcss
exist app/views/home
invoke rspec
create spec/requests/home_spec.rb
invoke helper
create app/helpers/home_helper.rb
invoke rspec
create spec/helpers/home_helper_spec.rb
┌─(~/dev/monchifc)──────────────┐
└─(00:10:14 on feature ✹ ✭)──> rm app/helpers/home_helper.rb spec/helpers/home_helper_spec.rb
// app/controllers/home_controller.rb
# frozen_string_literal: true
class HomeController < ApplicationController
def index
end
end
- specをsettingする…specの設定でrspecでhelper fileを作成する
helper…Viewをよりシンプルに書くためのモジュール。Viewでの共通する処理をメソッドとして定義し、簡単に使いまわせるようにした機能。Helperを利用することによって、繰り返し記述するような処理を簡略化することができ、開発効率を高めてくれる
// spec/request/home_spec.erb
require 'rails_helper'
RSpec.describe "Homes", type: :request do
describe "GET /index" do
it "succeeds" do
get root_path
expect(response).to be_successful
end
end
end
- rspecでhelper fileを作成
└─(00:13:17 on feature ✹ ✭)──> bundle exec rails generate rspec:install
create .rspec
exist spec
create spec/spec_helper.rb
create spec/rails_helper.rb
└─(13:19:59 on feature ✹ ✭)──> bundle exec rspec 1 ↵ ──(Wed,Jul19)─┘
.
Finished in 0.48665 seconds (files took 4.93 seconds to load)
1 example, 0 failures
create a user model
- install devise
gem "devise"
bundle
└─(14:17:45 on feature ✹)──> bundle exec rails g devise:install
create config/initializers/devise.rb
create config/locales/devise.en.yml
===============================================================================
Depending on your application's configuration some manual setup may be required:
1. Ensure you have defined default url options in your environments files. Here
is an example of default_url_options appropriate for a development environment
in config/environments/development.rb:
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
In production, :host should be set to the actual host of your application.
* Required for all applications. *
2. Ensure you have defined root_url to *something* in your config/routes.rb.
For example:
root to: "home#index"
* Not required for API-only Applications *
3. Ensure you have flash messages in app/views/layouts/application.html.erb.
For example:
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
* Not required for API-only Applications *
4. You can copy Devise views (for customization) to your app by running:
rails g devise:views
* Not required *
- deviseを使ってuserのmodelを作る
devise…railsで作ったwebアプリケーションに簡単に認証機能を実装できるgem
migrate fileとmodel fileが作成される
└─(14:30:16 on feature ✹ ✭)──> bundle exec rails g devise user
invoke active_record
create db/migrate/20230719103644_devise_create_users.rb
create app/models/user.rb
invoke rspec
create spec/models/user_spec.rb
insert app/models/user.rb
route devise_for :users
db:migrate→migrationファイルを読み込むためのコマンド
└─(14:36:44 on feature ✹ ✭)──> bundle exec rails db:migrate db:test:prepare
== 20230719103644 DeviseCreateUsers: migrating ================================
-- create_table(:users)
-> 0.1579s
-- add_index(:users, :email, {:unique=>true})
-> 0.0042s
-- add_index(:users, :reset_password_token, {:unique=>true})
-> 0.0026s
== 20230719103644 DeviseCreateUsers: migrated (0.1648s) =======================
// app/models/user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
end
// config/routes.rb
Rails.application.routes.draw do
devise_for :users
root "home#index"
end
- 認証画面用のviewsを大量生産
└─(14:41:26 on feature ✹ ✭)──> bundle exec rails generate devise:views
invoke Devise::Generators::SharedViewsGenerator
create app/views/devise/shared
create app/views/devise/shared/_error_messages.html.erb
create app/views/devise/shared/_links.html.erb
invoke form_for
create app/views/devise/confirmations
create app/views/devise/confirmations/new.html.erb
create app/views/devise/passwords
create app/views/devise/passwords/edit.html.erb
create app/views/devise/passwords/new.html.erb
create app/views/devise/registrations
create app/views/devise/registrations/edit.html.erb
create app/views/devise/registrations/new.html.erb
create app/views/devise/sessions
create app/views/devise/sessions/new.html.erb
create app/views/devise/unlocks
create app/views/devise/unlocks/new.html.erb
invoke erb
create app/views/devise/mailer
create app/views/devise/mailer/confirmation_instructions.html.erb
create app/views/devise/mailer/email_changed.html.erb
create app/views/devise/mailer/password_change.html.erb
create app/views/devise/mailer/reset_password_instructions.html.erb
create app/views/devise/mailer/unlock_instructions.html.erb
- ログイン画面はこちらのファイル, サーバー確認 http://127.0.0.1:3000/users/sign_inで確認
// app/views/devise/sessions/new.html.erb
<h2>Log in</h2>
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>
<div class="field">
<%= f.label :password %><br />
<%= f.password_field :password, autocomplete: "current-password" %>
</div>
<% if devise_mapping.rememberable? %>
<div class="field">
<%= f.check_box :remember_me %>
<%= f.label :remember_me %>
</div>
<% end %>
<div class="actions">
<%= f.submit "Log in" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
// app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Vacation Homes & Condo Rentals - Monchibnb</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
<%= favicon_link_tag asset_path('gatsby-icon.png') %>
</head>
<body>
<nav class="bg-gray-800">
<div class="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
<div class="relative flex h-16 items-center justify-between">
<div class="absolute inset-y-0 left-0 flex items-center sm:hidden">
<!-- Mobile menu button-->
<button type="button" class="inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white" aria-controls="mobile-menu" aria-expanded="false">
<span class="sr-only">Open main menu</span>
<!--
Icon when menu is closed.
Menu open: "hidden", Menu closed: "block"
-->
<svg class="block h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
</svg>
<!--
Icon when menu is open.
Menu open: "block", Menu closed: "hidden"
-->
<svg class="hidden h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start">
<div class="flex flex-shrink-0 items-center">
<img class="h-8 w-auto" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=500" alt="Your Company">
</div>
<div class="hidden sm:ml-6 sm:block">
<div class="flex space-x-4">
<!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
<a href="#" class="bg-gray-900 text-white rounded-md px-3 py-2 text-sm font-medium" aria-current="page">Dashboard</a>
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium">Team</a>
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium">Projects</a>
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium">Calendar</a>
</div>
</div>
</div>
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
<button type="button" class="rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800">
<span class="sr-only">View notifications</span>
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" />
</svg>
</button>
<!-- Profile dropdown -->
<div class="relative ml-3">
<div>
<button type="button" class="flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800" id="user-menu-button" aria-expanded="false" aria-haspopup="true">
<span class="sr-only">Open user menu</span>
<img class="h-8 w-8 rounded-full" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="">
</button>
</div>
<!--
Dropdown menu, show/hide based on menu state.
Entering: "transition ease-out duration-100"
From: "transform opacity-0 scale-95"
To: "transform opacity-100 scale-100"
Leaving: "transition ease-in duration-75"
From: "transform opacity-100 scale-100"
To: "transform opacity-0 scale-95"
-->
<div class="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1">
<!-- Active: "bg-gray-100", Not Active: "" -->
<a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-0">Your Profile</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-1">Settings</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-2">Sign out</a>
</div>
</div>
</div>
</div>
</div>
<!-- Mobile menu, show/hide based on menu state. -->
<div class="sm:hidden" id="mobile-menu">
<div class="space-y-1 px-2 pb-3 pt-2">
<!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
<a href="#" class="bg-gray-900 text-white block rounded-md px-3 py-2 text-base font-medium" aria-current="page">Dashboard</a>
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md px-3 py-2 text-base font-medium">Team</a>
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md px-3 py-2 text-base font-medium">Projects</a>
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md px-3 py-2 text-base font-medium">Calendar</a>
</div>
</div>
</nav>
<%= yield %>
</body>
</html>
- しかしRails s コマンドやbin/rails server コマンドではCSSとjsをcompileできないのでNavigation Barがうまく読み込めない。bin/dev コマンドを使ってサーバーを立ち上げる。ref:コマンドでサーバを起動する
- [error] bin/dev ファイルが不在 → tailwindcssが手入力でgemfileやconfigは修正したものの、createができていないfileが多く存在したことがわかってよかった。さっそくtailwindcss:install する。ref: Rails 7.0 + Ruby 3.1でゼロからアプリを作ってみたときにハマったところあれこれ
- ref: stackoverflow
- ref: Learning to love bin/dev in Rails 7 bin/rails tailwindcss:install
└─(21:04:15 on feature-a)──> bin/rails tailwindcss:install ──(Thu,Jul20
Add Tailwindcss include tags and container element in application layout
insert app/views/layouts/application.html.erb
Build into app/assets/builds
create app/assets/builds
create app/assets/builds/.keep
append app/assets/config/manifest.js
append .gitignore
Add default config/tailwindcss.config.js
create config/tailwind.config.js
Add default app/assets/stylesheets/application.tailwind.css
create app/assets/stylesheets/application.tailwind.css
Add default Procfile.dev
create Procfile.dev
Ensure foreman is installed
run gem install foreman from "."
Fetching foreman-0.87.2.gem
Successfully installed foreman-0.87.2
Parsing documentation for foreman-0.87.2
Installing ri documentation for foreman-0.87.2
Done installing documentation for foreman after 0 seconds
1 gem installed
Add bin/dev to start foreman
create bin/dev
Compile initial Tailwind build
run rails tailwindcss:build from "."
Rebuilding...
Done in 467ms.
- tailwindcssのinstallでbin/devがgenerateされた。
// bin/dev
#!/usr/bin/env sh
if ! gem list foreman -i --silent; then
echo "Installing foreman..."
gem install foreman
fi
exec foreman start -f Procfile.dev "$@"
さっそく./bin/devでサーバーを立ち上げてjsを使えるようにする
└─(22:19:28 on feature-a ✹ ✭)──> ./bin/dev 1 ↵ ──(Thu,Jul20)─┘
./bin/dev
22:20:18 web.1 | started with pid 16307
22:20:18 css.1 | started with pid 16308
22:20:19 web.1 | => Booting Puma
22:20:19 web.1 | => Rails 7.0.6 application starting in development
22:20:19 web.1 | => Run `bin/rails server --help` for more startup options
22:20:20 web.1 | Puma starting in single mode...
22:20:20 web.1 | * Puma version: 5.6.6 (ruby 3.2.2-p53) ("Birdie's Version")
22:20:20 web.1 | * Min threads: 5
22:20:20 web.1 | * Max threads: 5
22:20:20 web.1 | * Environment: development
22:20:20 web.1 | * PID: 16307
22:20:20 web.1 | * Listening on http://127.0.0.1:3000
22:20:20 web.1 | * Listening on http://[::1]:3000
22:20:20 web.1 | Use Ctrl-C to stop
22:20:21 css.1 |
22:20:21 css.1 | Rebuilding...
22:20:22 css.1 |
22:20:22 css.1 | Done in 512ms.
- [tailwindcss] With centered search and secondary link dark にcssを変更したいんだけど有料だからchatGPTでcodeを書きたい。今はとりあえず無料のcodeで構成
- partial構成にする。詳しくはhttps://guides.rubyonrails.org/getting_started.html#using-partials-to-share-view-code
app/views/layouts/_header.html.erb
<!--/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{html,js}"],
theme: {
extend: {},
},
plugins: [],
}
-->
<header class="bg-gray-800">
<div class="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
<div class="relative flex h-16 items-center justify-between">
<div class="absolute inset-y-0 left-0 flex items-center sm:hidden">
<!-- Mobile menu button-->
<button type="button" class="inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white" aria-controls="mobile-menu" aria-expanded="false">
<span class="sr-only">Open main menu</span>
<!--
Icon when menu is closed.
Menu open: "hidden", Menu closed: "block"
-->
<svg class="block h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
</svg>
<!--
Icon when menu is open.
Menu open: "block", Menu closed: "hidden"
-->
<svg class="hidden h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start">
<div class="flex flex-shrink-0 items-center">
<img class="h-8 w-auto" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=500" alt="Your Company">
</div>
<div class="hidden sm:ml-6 sm:block">
<div class="flex space-x-4">
<!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
<a href="#" class="bg-gray-900 text-white rounded-md px-3 py-2 text-sm font-medium" aria-current="page">Dashboard</a>
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium">Team</a>
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium">Projects</a>
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium">Calendar</a>
</div>
</div>
</div>
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
<button type="button" class="rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800">
<span class="sr-only">View notifications</span>
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" />
</svg>
</button>
<!-- Profile dropdown -->
<div class="relative ml-3">
<div>
<button type="button" class="flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800" id="user-menu-button" aria-expanded="false" aria-haspopup="true">
<span class="sr-only">Open user menu</span>
<img class="h-8 w-8 rounded-full" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="">
</button>
</div>
<!--
Dropdown menu, show/hide based on menu state.
Entering: "transition ease-out duration-100"
From: "transform opacity-0 scale-95"
To: "transform opacity-100 scale-100"
Leaving: "transition ease-in duration-75"
From: "transform opacity-100 scale-100"
To: "transform opacity-0 scale-95"
-->
<div class="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1">
<!-- Active: "bg-gray-100", Not Active: "" -->
<a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-0">Your Profile</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-1">Settings</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-2">Sign out</a>
</div>
</div>
</div>
</div>
</div>
<!-- Mobile menu, show/hide based on menu state. -->
<div class="sm:hidden" id="mobile-menu">
<div class="space-y-1 px-2 pb-3 pt-2">
<!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
<a href="#" class="bg-gray-900 text-white block rounded-md px-3 py-2 text-base font-medium" aria-current="page">Dashboard</a>
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md px-3 py-2 text-base font-medium">Team</a>
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md px-3 py-2 text-base font-medium">Projects</a>
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md px-3 py-2 text-base font-medium">Calendar</a>
</div>
</div>
</header>
- importmapがinstall成功しているかどうか。importmap.rbに各種ツールが定義されているかcheck
// app/javascript/application.js
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails"
import "controllers"
importmap-rails
ではconfig/importmap.rb
にDSLを使ってマッピングの設定を記載 します。
Rails 7.0 で標準になった importmap-rails とは何なのか?
config/importmap.rb
# 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"
- tailwindのconfig fileを確認
config/tailwindcss
const defaultTheme = require('tailwindcss/defaultTheme')
module.exports = {
content: [
'./public/*.html',
'./app/helpers/**/*.rb',
'./app/javascript/**/*.js',
'./app/views/**/*.{erb,haml,html,slim}'
],
theme: {
extend: {
fontFamily: {
sans: ['Inter var', ...defaultTheme.fontFamily.sans],
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/aspect-ratio'),
require('@tailwindcss/typography'),
require('@tailwindcss/container-queries'),
]
}
- scss fileを有効にするためにgem追加→ displayに反映
/ gemfile
gem "sassc-rails"
- airbnb logoのsvg codeの上にroot_pathをsetting
// app/views/layouts/_header.html.erb
<%= link_to root_path, class: "logo-link" do %>
// app/assets/stylesheets/_header.scss
.nav-container {
min-height: 80px;
}
.logo-link {
svg{
display: block;
color: $airbnb-logo;
}
}
// app/assets/stylesheets/_colors.scss
$airbnb-logo: #eb4c60;
- _header.html.erbが反映されるようにlayouts設定
app/views/layouts/application.html.erb
<body>
<%= render "layouts/header"%>
<%= yield %>
</body>
- app/assets/stylesheets/application.scss: cssからscssに拡張子を変更するとerrorになる。 scss fileを反映させるためにManifest.jsをedit, application.cssを追加。 ref: manifestjs
- The
manifest.js
file is meant to specify which files to use as a top-level target using sprockets methodslink
,link_directory
, andlink_tree
.
app/assets/config/manifest.js
//= link_tree ../images
//= link application.css
//= link_tree ../../javascript .js
//= link_tree ../../../vendor/javascript .js
//= link_tree ../builds
- Stimulus’s purpose is to automatically connect DOM elements to JavaScript objects
- stimulusでjs fileを動かす。header controllerを作成
//app/javascript/controllers/header_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
connect() {
this.element.textContent = "Hello Monchi!"
}
}
- data-controller header method追加
// app/views/layouts/_header.html.erb
<nav class="nav-header" data-controller="header">
- git pushでerror発生。Mac再起動でなおった
└─(00:18:01 on feature-a)──> git push origin HEAD ──(Sun,Jul23)─┘
fatal: unable to access 'https://github.com/TRkizaki/monchifc.git/': LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to github.com:443
Understanding SSL_ERROR_SYSCALL Error
This error typically occurs when the TCP three-way handshake between client and server completes but then a TCP reset packet (often written as “RST”) is received by the client, terminating the connection during the SSL phase.
This error is not produced when a client receives a RST packet during the three-way handshake, or after completion of the SSL/TLS negotiation (SSL phase).
- eltransitionの実装
└─(13:39:21 on main)──> ./bin/importmap pin el-transition ──(Thu,Jul27)─┘
Pinning "el-transition" to https://ga.jspm.io/npm:el-transition@0.0.7/index.js
//app/javascript/controllers/header_controller.js
import { Controller } from "@hotwired/stimulus"
import {enter, leave, toggle} from 'el-transition'
export default class extends Controller {
static targets = ['openUserMenu'];
connect() {
}
}
//app/views/layouts/_header.html.erb
data-transition-enter="transition ease-out duration-100"
data-transition-enter-start="transform opacity-0 scale-95"
data-transition-enter-end="transform opacity-100 scale-100"
data-transition-leave="transition ease-in duration-75"
data-transition-leave-start="transform opacity-100 scale-100"
data-transition-leave-end="transform opacity-0 scale-95"
- payment reject Modalを実装してみる ref: https://tailwindui.com/components/application-ui/overlays/modals
└─(19:09:04 on main)──> mkdir app/views/shared ──(Thu,Jul27)─┘
┌─(~/dev/monchifc)──────────────┐
└─(19:28:15 on main)──> touch app/views/shared/_modal.html.erb
//app/views/shared/_modal.html.erb
<div data-controller="modal"
id="modal-wrapper"
class="hidden relative z-10"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true"
>
<!--
Background backdrop, show/hide based on modal state.
Entering: "ease-out duration-300"
From: "opacity-0"
To: "opacity-100"
Leaving: "ease-in duration-200"
From: "opacity-100"
To: "opacity-0"
-->
<div id="modal-backdrop"
class="hidden fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
data-transition-enter="ease-out duration-300"
data-transition-enter-start="opacity-0"
data-transition-enter-end="opacity-100"
data-transition-leave="ease-in duration-200"
data-transition-leave-start="opacity-100"
data-transition-leave-end="opacity-0"
></div>
<div class="fixed inset-0 z-10 overflow-y-auto">
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<!--
Modal panel, show/hide based on modal state.
Entering: "ease-out duration-300"
From: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
To: "opacity-100 translate-y-0 sm:scale-100"
Leaving: "ease-in duration-200"
From: "opacity-100 translate-y-0 sm:scale-100"
To: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
-->
<div id="modal-panel"
class="hidden relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg"
data-transition-enter="ease-out duration-300"
data-transition-enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
data-transition-enter-end="opacity-100 translate-y-0 sm:scale-100"
data-transition-leave="ease-in duration-200"
data-transition-leave-start="opacity-100 translate-y-0 sm:scale-100"
data-transition-leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<div class="bg-white px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<svg class="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
</svg>
</div>
<div class="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">Deactivate account</h3>
<div class="mt-2">
<p class="text-sm text-gray-500">Are you sure you want to deactivate your account? All of your data will be permanently removed. This action cannot be undone.</p>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
<button type="button" class="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto">Deactivate</button>
<button type="button" class="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto">Cancel</button>
</div>
</div>
</div>
</div>
</div>
//app/views/layouts/_header.html.erb
<%= render "shared/modal" %>
//app/javascript/controllers/modal_controller.js
import { Controller } from "@hotwired/stimulus"
import { enter, leave, toggle } from 'el-transition'
export default class extends Controller {
connect() {
enter(document.getElementById('modal-wrapper'));
enter(document.getElementById('modal-backdrop'));
enter(document.getElementById('modal-panel'));
}
}
- setUp DropDownMenu(右端のやつ) openUserMenu → this.openUserMenuTarget
app/javascript/controllers/header_controller.js
import { Controller } from "@hotwired/stimulus"
import { enter, leave, toggle } from 'el-transition'
export default class extends Controller {
static targets = ['openUserMenu'];
connect() {
this.openUserMenuTarget.addEventListener('click', this.toggleDropdownMenu)
}
toggleDropdownMenu(){
toggle(document.getElementById('menu-dropdown-items'));
}
}
- userAuthLinkつくる log inとかsign upできるようにしたい
//app/views/layouts/_header.html.erb
<!-- Active: "bg-gray-100", Not Active: "" -->
<div class="py-1" role="none">
<a href="#" data-header-target="userAuthLink" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" tabindex="-1" id="user-menu-item-0">Sign Up</a>
<a href="#" data-header-target="userAuthLink" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" tabindex="-1" id="user-menu-item-1">Log In</a>
</div>
<div class="py-1" role="none">
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" tabindex="-1" id="user-menu-item-0">Host Your Home</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" tabindex="-1" id="user-menu-item-1">Host an experience</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" tabindex="-1" id="user-menu-item-2">Help</a>
</div>
</div>
- clickしたらどうなるかaddEventListenerするの基本よね
- stimulus 今回のcaseはopenUserMenu, userAuthLink
issen_controller.js側にstatic targets = [“name”, “output”]という記述をする必要もありますので忘れずに書きましょう
これで要素をJS側でthis.outputTarget、this.nameTargetというような形で使用することができます
//app/javascript/controllers/header_controller.js
import { Controller } from "@hotwired/stimulus"
import { enter, leave, toggle } from 'el-transition'
export default class extends Controller {
static targets = ['openUserMenu', 'userAuthLink'];
connect() {
this.openUserMenuTarget.addEventListener('click', this.toggleDropdownMenu)
this.userAuthLinkTargets.forEach((link) => {
link.addEventListener('click', () => {
console.log('user links clicked');
});
//link.addEventListener('click', () => {});
});
}
toggleDropdownMenu(){
toggle(document.getElementById('menu-dropdown-items'));
}
}
- Connecting the Action
- document.getElementById('modal-wrapper').addEventListener('click', this.closeModal); でmodalの動きまとめちゃうの重要。modalがどこか画面クリックしたらleaveする仕組みにした。
//app/javascript/controllers/modal_controller.js
import { Controller } from "@hotwired/stimulus"
import { enter, leave, toggle } from 'el-transition'
export default class extends Controller {
connect() {
// enter(document.getElementById('modal-wrapper'));
// enter(document.getElementById('modal-backdrop'));
// enter(document.getElementById('modal-panel'));
document.getElementById('modal-wrapper').addEventListener('click', this.closeModal);
}
closeModal(event) {
const modalPanelClicked = document.getElementById('modal-panel').contains(event.target);
if(!modalPanelClicked) {
leave(document.getElementById('modal-wrapper'));
leave(document.getElementById('modal-backdrop'));
leave(document.getElementById('modal-panel'));
}
}
}
https://stimulus.hotwired.dev/handbook/building-something-real#connecting-the-action
- ボタンが押された時に色を変更
- ハンバーガーメニューの処理
- モーダル画面の表示
- stimulus参考→Railsはより少ない(JavaScriptの)記述で今どきのアプリケーションを作りたいという方向に舵を切り、その結果としてHotwireという技術が出来上がりました Hotwireは大きくは「Turbo + Stimulus」という要素で構成されています。
config/importmap.rb
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 "el-transition", to: "https://ga.jspm.io/npm:el-transition@0.0.7/index.js"
//app/views/shared/_modal.html.erb
data-action="click->modal#showModal"
- showModal method追加
app/javascript/controllers/modal_controller.js
import { Controller } from "@hotwired/stimulus"
import { enter, leave, toggle } from 'el-transition'
export default class extends Controller {
connect() {
document.getElementById('modal-wrapper').addEventListener('click', this.closeModal);
}
closeModal(event) {
const modalPanelClicked = document.getElementById('modal-panel').contains(event.target);
if(!modalPanelClicked) {
leave(document.getElementById('modal-wrapper'));
leave(document.getElementById('modal-backdrop'));
leave(document.getElementById('modal-panel'));
}
}
showModal() {
enter(document.getElementById('modal-wrapper'));
enter(document.getElementById('modal-backdrop'));
enter(document.getElementById('modal-panel'));
}
}
- document.getElementById('modal-trigger').click(); で 右端のsign-upとLog-in clickするとmodal displayが出現する仕様
app/javascript/controllers/header_controller.js
import { Controller } from "@hotwired/stimulus"
import { enter, leave, toggle } from 'el-transition'
export default class extends Controller {
static targets = ['openUserMenu', 'userAuthLink'];
connect() {
this.openUserMenuTarget.addEventListener('click', this.toggleDropdownMenu)
this.userAuthLinkTargets.forEach((link) => {
link.addEventListener('click', (e) => {
e.preventDefault();
document.getElementById('modal-trigger').click();
});
});
}
toggleDropdownMenu(){
toggle(document.getElementById('menu-dropdown-items'));
}
}
Error修正:modal-triggerを独立で指定するのが大事。https://stimulus.hotwired.dev/handbook/building-something-real#connecting-the-action showModal methodは modal_controller.js。