├── db
├── seeds.rb
└── migrate
│ ├── 20200603052105_tweets.rb
│ ├── 20200515024136_add_nickname_to_users.rb
│ ├── 20200521050256_add_column_from_users.rb
│ ├── 20200521050717_create_rooms.rb
│ ├── 20200524230028_a_ddd_admin_to_users.rb
│ ├── 20200521050817_create_entries.rb
│ ├── 20200519001917_create_likes.rb
│ ├── 20200521050902_create_messages.rb
│ ├── 20200515005529_create_tweets.rb
│ ├── 20200515064321_create_comments.rb
│ ├── 20200527224117_create_donations.rb
│ ├── 20200520074806_create_relationships.rb
│ ├── 20200523235326_add_missing_taggable_index.acts_as_taggable_on_engine.rb
│ ├── 20200523235327_change_collation_for_tag_names.acts_as_taggable_on_engine.rb
│ ├── 20200523015543_create_notifications.rb
│ ├── 20200523235325_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb
│ ├── 20200523235324_add_missing_unique_indices.acts_as_taggable_on_engine.rb
│ ├── 20200523235323_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb
│ ├── 20200523235328_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb
│ └── 20200515005429_devise_create_users.rb
├── log
└── .keep
├── storage
└── .keep
├── tmp
└── .keep
├── vendor
├── .keep
└── assets
│ ├── javascript
│ └── .DS_Store
│ └── stylesheets
│ └── jquery.tagit.scss
├── lib
├── assets
│ └── .keep
└── tasks
│ └── .keep
├── .ruby-version
├── app
├── assets
│ ├── images
│ │ ├── .keep
│ │ ├── avatar.png
│ │ ├── default.png
│ │ └── image.jpeg
│ ├── javascripts
│ │ ├── channels
│ │ │ └── .keep
│ │ ├── flash.js
│ │ ├── header.js
│ │ ├── cable.js
│ │ ├── tweet-title.js
│ │ ├── application.js
│ │ ├── tagit.js
│ │ ├── tweet_show.js
│ │ ├── notification.js
│ │ ├── preview.js
│ │ └── tweet_form.js
│ ├── stylesheets
│ │ ├── mixin
│ │ │ ├── _tweet-items.scss
│ │ │ └── _media.scss
│ │ ├── modules
│ │ │ ├── _flash.scss
│ │ │ ├── _user-index.scss
│ │ │ ├── _session.scss
│ │ │ ├── _tweet-tags.scss
│ │ │ ├── _donation.scss
│ │ │ ├── _new-btn.scss
│ │ │ ├── _room-index.scss
│ │ │ ├── _common.scss
│ │ │ ├── _notifications.scss
│ │ │ ├── _tweet-comment.scss
│ │ │ ├── _tweet-form.scss
│ │ │ ├── _room-show.scss
│ │ │ └── _tweet-index.scss
│ │ ├── config
│ │ │ └── _colors.scss
│ │ ├── application.scss
│ │ └── _reset.scss
│ └── config
│ │ └── manifest.js
├── models
│ ├── concerns
│ │ └── .keep
│ ├── application_record.rb
│ ├── entry.rb
│ ├── message.rb
│ ├── donation.rb
│ ├── relationship.rb
│ ├── notification.rb
│ ├── like.rb
│ ├── comment.rb
│ ├── room.rb
│ ├── user.rb
│ └── tweet.rb
├── controllers
│ ├── concerns
│ │ └── .keep
│ ├── api
│ │ └── tweets_controller.rb
│ ├── tweets
│ │ └── searches_controller.rb
│ ├── application_controller.rb
│ ├── relationships_controller.rb
│ ├── likes_controller.rb
│ ├── donations_controller.rb
│ ├── users
│ │ └── sessions_controller.rb
│ ├── notifications_controller.rb
│ ├── rooms_controller.rb
│ ├── comments_controller.rb
│ ├── messages_controller.rb
│ ├── tweets_controller.rb
│ └── users_controller.rb
├── views
│ ├── tweets
│ │ ├── index.html.haml
│ │ ├── likes.html.haml
│ │ ├── new.html.haml
│ │ ├── edit.html.haml
│ │ ├── _tag_list.html.haml
│ │ ├── _top.html.haml
│ │ ├── searches
│ │ │ └── index.html.haml
│ │ ├── _new_btn.html.haml
│ │ ├── tags.html.haml
│ │ ├── _tweet.html.haml
│ │ ├── _form.html.haml
│ │ └── show.html.haml
│ ├── users
│ │ ├── timeline.html.haml
│ │ ├── index.html.haml
│ │ ├── followers.html.haml
│ │ ├── following.html.haml
│ │ ├── _user.html.haml
│ │ ├── edit.html.haml
│ │ ├── likes.html.haml
│ │ ├── show.html.haml
│ │ ├── _contact.html.haml
│ │ ├── _info.html.haml
│ │ └── _form.html.haml
│ ├── api
│ │ └── tweets
│ │ │ └── preview.json.jbuilder
│ ├── .DS_Store
│ ├── comments
│ │ ├── destroy.js.haml
│ │ ├── update.js.haml
│ │ ├── edit.js.haml
│ │ ├── _top.html.haml
│ │ ├── create.js.haml
│ │ ├── _form.html.haml
│ │ └── _comment.html.haml
│ ├── messages
│ │ ├── destroy.js.haml
│ │ ├── update.js.haml
│ │ ├── edit.js.haml
│ │ └── create.js.haml
│ ├── relationships
│ │ ├── create.js.haml
│ │ ├── destroy.js.haml
│ │ └── _follow.html.haml
│ ├── rooms
│ │ ├── show.html.haml
│ │ ├── index.html.haml
│ │ ├── _chat_form.html.haml
│ │ ├── _room.html.haml
│ │ └── _chat_area.html.haml
│ ├── layouts
│ │ ├── _flash.html.haml
│ │ ├── _circle.html.haml
│ │ ├── application.html.haml
│ │ └── _header.html.haml
│ ├── likes
│ │ ├── destroy.js.haml
│ │ ├── create.js.haml
│ │ └── _like.html.haml
│ ├── kaminari
│ │ ├── _gap.html.slim
│ │ ├── _last_page.html.slim
│ │ ├── _first_page.html.slim
│ │ ├── _next_page.html.slim
│ │ ├── _prev_page.html.slim
│ │ ├── _page.html.slim
│ │ └── _paginator.html.slim
│ ├── devise
│ │ ├── sessions
│ │ │ ├── _testuser.html.haml
│ │ │ └── new.html.haml
│ │ ├── mailer
│ │ │ ├── password_change.html.haml
│ │ │ ├── confirmation_instructions.html.haml
│ │ │ ├── unlock_instructions.html.haml
│ │ │ ├── email_changed.html.haml
│ │ │ └── reset_password_instructions.html.haml
│ │ ├── shared
│ │ │ ├── _error_messages.html.haml
│ │ │ └── _links.html.haml
│ │ ├── unlocks
│ │ │ └── new.html.haml
│ │ ├── passwords
│ │ │ ├── new.html.haml
│ │ │ └── edit.html.haml
│ │ ├── confirmations
│ │ │ └── new.html.haml
│ │ └── registrations
│ │ │ ├── new.html.haml
│ │ │ └── edit.html.haml
│ ├── donations
│ │ ├── create.js.haml
│ │ ├── _donation.html.haml
│ │ └── _form.html.haml
│ ├── notifications
│ │ ├── index.html.haml
│ │ └── _notification.html.haml
│ └── activities
│ │ └── _activity.html.haml
├── helpers
│ ├── likes_helper.rb
│ ├── users_helper.rb
│ ├── activities_helper.rb
│ ├── comments_helper.rb
│ ├── donations_helper.rb
│ ├── messages_helper.rb
│ ├── relationships_helper.rb
│ ├── tweets
│ │ └── searches_helper.rb
│ ├── tweets_helper.rb
│ ├── notifications_helper.rb
│ ├── applications_helper.rb
│ ├── rooms_helper.rb
│ ├── devise_helper.rb
│ └── markdown_helper.rb
├── jobs
│ └── application_job.rb
├── channels
│ └── application_cable
│ │ ├── channel.rb
│ │ └── connection.rb
├── mailers
│ └── application_mailer.rb
└── uploaders
│ ├── avatar_uploader.rb
│ └── image_uploader.rb
├── public
├── apple-touch-icon.png
├── apple-touch-icon-precomposed.png
├── favicon.ico
├── robots.txt
├── 500.html
├── 422.html
└── 404.html
├── .rspec
├── spec
├── factories
│ ├── room.rb
│ ├── like.rb
│ ├── donation.rb
│ ├── comment.rb
│ ├── relationship.rb
│ ├── entry.rb
│ ├── message.rb
│ ├── tweet.rb
│ ├── user.rb
│ └── notification.rb
├── controllers
│ ├── likes_controller_spec.rb
│ ├── rooms_controller_spec.rb
│ ├── users_controller_spec.rb
│ ├── comments_controller_spec.rb
│ ├── donations_controller_spec.rb
│ ├── messages_controller_spec.rb
│ ├── notifications_controller_spec.rb
│ └── relationships_controller_spec.rb
├── support
│ └── controller_macros.rb
├── models
│ ├── room_spec.rb
│ ├── donation_spec.rb
│ ├── like_spec.rb
│ ├── entry_spec.rb
│ ├── comment_spec.rb
│ ├── relationship_spec.rb
│ ├── message_spec.rb
│ └── notification_spec.rb
└── features
│ └── tweet_spec.rb
├── config
├── deploy
│ ├── production.rb
│ └── staging.rb
├── spring.rb
├── database.yml.ci
├── environment.rb
├── initializers
│ ├── mime_types.rb
│ ├── filter_parameter_logging.rb
│ ├── application_controller_renderer.rb
│ ├── cookies_serializer.rb
│ ├── backtrace_silencers.rb
│ ├── wrap_parameters.rb
│ ├── assets.rb
│ ├── inflections.rb
│ ├── carrierwave.rb
│ └── content_security_policy.rb
├── boot.rb
├── cable.yml
├── puma.rb
├── credentials.yml.enc
├── locales
│ └── en.yml
├── application.rb
├── storage.yml
├── routes.rb
├── database.yml
├── environments
│ ├── test.rb
│ └── development.rb
├── deploy.rb
└── unicorn.rb
├── bin
├── bundle
├── rake
├── rails
├── yarn
├── spring
├── update
└── setup
├── docker
├── mysql
│ ├── charset.cnf
│ ├── password.yml
│ └── Dockerfile
├── rails
│ ├── puma.rb
│ └── Dockerfile
└── nginx
│ ├── Dockerfile
│ ├── default.conf
│ └── nginx.conf
├── package.json
├── config.ru
├── Rakefile
├── .rubocop_todo.yml
├── Capfile
├── .gitignore
├── docker-compose-dev.yml
├── docker-compose.yml
├── .rubocop.yml
└── .circleci
└── config.yml
/db/seeds.rb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/log/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/storage/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tmp/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vendor/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/assets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/tasks/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.5.1
2 |
--------------------------------------------------------------------------------
/app/assets/images/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/models/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/javascripts/channels/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/views/tweets/index.html.haml:
--------------------------------------------------------------------------------
1 | = render "tweets/top"
--------------------------------------------------------------------------------
/app/views/tweets/likes.html.haml:
--------------------------------------------------------------------------------
1 | = render 'tweets/top'
--------------------------------------------------------------------------------
/app/views/tweets/new.html.haml:
--------------------------------------------------------------------------------
1 | = render '/tweets/form'
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --require spec_helper
2 | --format documentation
--------------------------------------------------------------------------------
/app/helpers/likes_helper.rb:
--------------------------------------------------------------------------------
1 | module LikesHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/users_helper.rb:
--------------------------------------------------------------------------------
1 | module UsersHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/views/users/timeline.html.haml:
--------------------------------------------------------------------------------
1 | = render 'tweets/top'
--------------------------------------------------------------------------------
/app/views/api/tweets/preview.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.text @html
2 |
--------------------------------------------------------------------------------
/app/views/tweets/edit.html.haml:
--------------------------------------------------------------------------------
1 | = render '/tweets/form'
2 |
--------------------------------------------------------------------------------
/app/helpers/activities_helper.rb:
--------------------------------------------------------------------------------
1 | module ActivitiesHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/comments_helper.rb:
--------------------------------------------------------------------------------
1 | module CommentsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/donations_helper.rb:
--------------------------------------------------------------------------------
1 | module DonationsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/messages_helper.rb:
--------------------------------------------------------------------------------
1 | module MessagesHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/relationships_helper.rb:
--------------------------------------------------------------------------------
1 | module RelationshipsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/tweets/searches_helper.rb:
--------------------------------------------------------------------------------
1 | module Tweets::SearchesHelper
2 | end
3 |
--------------------------------------------------------------------------------
/spec/factories/room.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :room do
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/mixin/_tweet-items.scss:
--------------------------------------------------------------------------------
1 | @mixin tweet-items{
2 | width: 300px;
3 | }
--------------------------------------------------------------------------------
/app/views/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohishikaito/MyNote/HEAD/app/views/.DS_Store
--------------------------------------------------------------------------------
/app/views/comments/destroy.js.haml:
--------------------------------------------------------------------------------
1 | $('#comment_area').html("#{ j(render 'comments/top') }");
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohishikaito/MyNote/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/app/views/comments/update.js.haml:
--------------------------------------------------------------------------------
1 | $('#comment_area').html("#{ j(render 'comments/top') }");
2 |
--------------------------------------------------------------------------------
/app/views/messages/destroy.js.haml:
--------------------------------------------------------------------------------
1 | $("#chat_area").html("#{j(render("rooms/chat_area"))}");
2 |
--------------------------------------------------------------------------------
/app/views/messages/update.js.haml:
--------------------------------------------------------------------------------
1 | $("#chat_area").html("#{j(render("rooms/chat_area"))}");
2 |
--------------------------------------------------------------------------------
/app/helpers/tweets_helper.rb:
--------------------------------------------------------------------------------
1 | module TweetsHelper
2 | include ActsAsTaggableOn::TagsHelper
3 | end
4 |
--------------------------------------------------------------------------------
/app/views/comments/edit.js.haml:
--------------------------------------------------------------------------------
1 | $('#comment_#{ @comment.id }').html("#{ j(render 'comments/form') }");
--------------------------------------------------------------------------------
/app/views/relationships/create.js.haml:
--------------------------------------------------------------------------------
1 | $("#follow_form").html("#{j(render("/relationships/follow"))}");
--------------------------------------------------------------------------------
/app/views/relationships/destroy.js.haml:
--------------------------------------------------------------------------------
1 | $("#follow_form").html("#{j(render("/relationships/follow"))}");
--------------------------------------------------------------------------------
/app/assets/images/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohishikaito/MyNote/HEAD/app/assets/images/avatar.png
--------------------------------------------------------------------------------
/app/assets/images/default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohishikaito/MyNote/HEAD/app/assets/images/default.png
--------------------------------------------------------------------------------
/app/assets/images/image.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohishikaito/MyNote/HEAD/app/assets/images/image.jpeg
--------------------------------------------------------------------------------
/app/views/users/index.html.haml:
--------------------------------------------------------------------------------
1 | .wrapper
2 | %ul.users
3 | .users__title
4 | ユーザー一覧
5 | = render @users
--------------------------------------------------------------------------------
/app/assets/javascripts/flash.js:
--------------------------------------------------------------------------------
1 | $(function () {
2 | setTimeout("$('.notice, .alert').fadeOut('slow')", 2000);
3 | });
4 |
--------------------------------------------------------------------------------
/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | self.abstract_class = true
3 | end
4 |
--------------------------------------------------------------------------------
/app/views/rooms/show.html.haml:
--------------------------------------------------------------------------------
1 | #chat_area
2 | = render "/rooms/chat_area"
3 | #chat_form
4 | = render "/rooms/chat_form"
--------------------------------------------------------------------------------
/app/views/users/followers.html.haml:
--------------------------------------------------------------------------------
1 | .wrapper
2 | %ul.users
3 | .users__title
4 | フォロワー
5 | = render @users
--------------------------------------------------------------------------------
/app/views/users/following.html.haml:
--------------------------------------------------------------------------------
1 | .wrapper
2 | %ul.users
3 | .users__title
4 | フォロー中
5 | = render @users
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 |
--------------------------------------------------------------------------------
/vendor/assets/javascript/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ohishikaito/MyNote/HEAD/vendor/assets/javascript/.DS_Store
--------------------------------------------------------------------------------
/app/views/layouts/_flash.html.haml:
--------------------------------------------------------------------------------
1 | .notification
2 | - flash.each do |key, value|
3 | = content_tag :div, value, class: key
--------------------------------------------------------------------------------
/app/views/likes/destroy.js.haml:
--------------------------------------------------------------------------------
1 | $("#like_#{@tweet.id}").html("#{j(render partial: 'likes/like', locals: { tweet: @tweet })}");
--------------------------------------------------------------------------------
/app/views/likes/create.js.haml:
--------------------------------------------------------------------------------
1 | $("#like_#{@tweet.id}").html("#{j(render partial: 'likes/like', locals: { tweet: @tweet })}");
2 |
--------------------------------------------------------------------------------
/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/views/kaminari/_gap.html.slim:
--------------------------------------------------------------------------------
1 | li.page-item.disabled
2 | = link_to raw(t 'views.pagination.truncate'), '#', class: 'page-link'
3 |
--------------------------------------------------------------------------------
/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../javascripts .js
3 | //= link_directory ../stylesheets .css
4 |
--------------------------------------------------------------------------------
/config/deploy/production.rb:
--------------------------------------------------------------------------------
1 | # EC2サーバーのIP、EC2サーバーにログインするユーザー名、サーバーのロールを記述
2 | server '54.150.220.39', user: 'ec2-user', roles: %w{app db web}
--------------------------------------------------------------------------------
/spec/factories/like.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :like do
3 | association :tweet
4 | user { tweet.user }
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/views/devise/sessions/_testuser.html.haml:
--------------------------------------------------------------------------------
1 | = link_to users_guest_sign_in_path, method: :post, class: "btn btn-warning" do
2 | ゲストユーザーでログイン
--------------------------------------------------------------------------------
/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: 'from@example.com'
3 | layout 'mailer'
4 | end
5 |
--------------------------------------------------------------------------------
/config/spring.rb:
--------------------------------------------------------------------------------
1 | %w[
2 | .ruby-version
3 | .rbenv-vars
4 | tmp/restart.txt
5 | tmp/caching-dev.txt
6 | ].each { |path| Spring.watch(path) }
7 |
--------------------------------------------------------------------------------
/docker/mysql/charset.cnf:
--------------------------------------------------------------------------------
1 | [mysqld]
2 | character-set-server=utf8mb4
3 | collation-server=utf8mb4_general_ci
4 | [client]
5 | default-character-set=utf8mb4
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my_app",
3 | "private": true,
4 | "dependencies": {
5 | "marked": "^1.1.0",
6 | "simplemde": "^1.11.2"
7 | }
8 | }
--------------------------------------------------------------------------------
/app/views/donations/create.js.haml:
--------------------------------------------------------------------------------
1 | $('#donation_area').html("#{ j(render 'donations/donation') }");
2 | $('#donation_form').html("#{ j(render 'donations/form') }");
--------------------------------------------------------------------------------
/app/views/tweets/_tag_list.html.haml:
--------------------------------------------------------------------------------
1 | - tag_list.each do |tag|
2 | = link_to tweets_path(tag_name: tag) do
3 | %i.btn.btn-outline-dark.tags
4 | = tag
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative 'config/environment'
4 |
5 | run Rails.application
6 |
--------------------------------------------------------------------------------
/app/models/entry.rb:
--------------------------------------------------------------------------------
1 | class Entry < ApplicationRecord
2 | belongs_to :user
3 | belongs_to :room
4 | validates :room_id, uniqueness: { scope: :user_id }
5 | end
6 |
--------------------------------------------------------------------------------
/app/views/devise/mailer/password_change.html.haml:
--------------------------------------------------------------------------------
1 | %p
2 | Hello #{@resource.email}!
3 | %p We're contacting you to notify you that your password has been changed.
4 |
--------------------------------------------------------------------------------
/app/views/kaminari/_last_page.html.slim:
--------------------------------------------------------------------------------
1 | li.page-item
2 | = link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, remote: remote, class: 'page-link'
3 |
--------------------------------------------------------------------------------
/app/views/rooms/index.html.haml:
--------------------------------------------------------------------------------
1 | .wrapper
2 | .room-index
3 | .room-index__title.room-index-hover
4 | メッセージ
5 | %ul.room-index__items
6 | = render @rooms
--------------------------------------------------------------------------------
/config/database.yml.ci:
--------------------------------------------------------------------------------
1 | test:
2 | adapter: mysql2
3 | encoding: utf8
4 | pool: 5
5 | username: 'root'
6 | port: 3306
7 | host: '127.0.0.1'
8 | database: ci_test
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative 'application'
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/db/migrate/20200603052105_tweets.rb:
--------------------------------------------------------------------------------
1 | class Tweets < ActiveRecord::Migration[5.2]
2 | def change
3 | add_column :tweets, :likes_count, :integer
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/views/kaminari/_first_page.html.slim:
--------------------------------------------------------------------------------
1 | li.page-item
2 | = link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, remote: remote, class: 'page-link'
3 |
--------------------------------------------------------------------------------
/spec/factories/donation.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :donation do
3 | association :tweet
4 | user { tweet.user }
5 | amount { 10_000 }
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/app/views/kaminari/_next_page.html.slim:
--------------------------------------------------------------------------------
1 | li.page-item
2 | = link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, rel: 'next', remote: remote, class: 'page-link'
3 |
--------------------------------------------------------------------------------
/spec/controllers/likes_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe 'Likes', type: :request do
4 | describe 'GET #index' do
5 | # 現在実装中
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/controllers/rooms_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe 'Rooms', type: :request do
4 | describe 'GET #index' do
5 | # 現在実装中
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/controllers/users_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe 'Users', type: :request do
4 | describe 'GET #index' do
5 | # 現在実装中
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/factories/comment.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :comment do
3 | association :tweet
4 | user { tweet.user }
5 | content { Faker::Lorem.word }
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/app/views/kaminari/_prev_page.html.slim:
--------------------------------------------------------------------------------
1 | li.page-item
2 | = link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote, class: 'page-link'
3 |
--------------------------------------------------------------------------------
/db/migrate/20200515024136_add_nickname_to_users.rb:
--------------------------------------------------------------------------------
1 | class AddNicknameToUsers < ActiveRecord::Migration[5.2]
2 | def change
3 | add_column :users, :nickname, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20200521050256_add_column_from_users.rb:
--------------------------------------------------------------------------------
1 | class AddColumnFromUsers < ActiveRecord::Migration[5.2]
2 | def change
3 | add_column :users, :avatar, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/controllers/comments_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe 'Comments', type: :request do
4 | describe 'GET #index' do
5 | # 現在実装中
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/controllers/donations_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe 'Donations', type: :request do
4 | describe 'GET #index' do
5 | # 現在実装中
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/controllers/messages_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe 'Messages', type: :request do
4 | describe 'GET #index' do
5 | # 現在実装中
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/support/controller_macros.rb:
--------------------------------------------------------------------------------
1 | module ControllerMacros
2 | def login_user(user)
3 | @request.env["devise.mapping"] = Devise.mappings[:user]
4 | sign_in user
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/app/helpers/notifications_helper.rb:
--------------------------------------------------------------------------------
1 | module NotificationsHelper
2 | def unchecked_notifications
3 | @notifications = current_user.passive_notifications.where(checked: false)
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20200521050717_create_rooms.rb:
--------------------------------------------------------------------------------
1 | class CreateRooms < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :rooms do |t|
4 | t.timestamps
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/db/migrate/20200524230028_a_ddd_admin_to_users.rb:
--------------------------------------------------------------------------------
1 | class ADddAdminToUsers < ActiveRecord::Migration[5.2]
2 | def change
3 | add_column :users, :admin, :boolean, default: false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/controllers/notifications_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe 'Notifications', type: :request do
4 | describe 'GET #index' do
5 | # 現在実装中
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/controllers/relationships_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe 'Relationships', type: :request do
4 | describe 'GET #index' do
5 | # 現在実装中
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/app/views/users/_user.html.haml:
--------------------------------------------------------------------------------
1 | %li
2 | = link_to user_path(user), class: "users__item" do
3 | = image_tag "#{user.avatar}", class: "avatar"
4 | .users__item--nickname.black-important
5 | = user.nickname
--------------------------------------------------------------------------------
/spec/factories/relationship.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :relationship do
3 | follower_id { FactoryBot.create(:user).id }
4 | following_id { FactoryBot.create(:user).id }
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/app/assets/javascripts/header.js:
--------------------------------------------------------------------------------
1 | $(function () {
2 | // ヘッダーのリンクをクリックしたら、リストを表示させる。
3 | $(".header-top__right__user__info--link").click(function () {
4 | $(".header-top__box").slideToggle(0);
5 | });
6 | });
7 |
--------------------------------------------------------------------------------
/app/controllers/api/tweets_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::TweetsController < ApplicationController
2 | # 受け取ったテキストをマークダウンに変換するメソッド
3 | def preview
4 | @html = view_context.markdown(params[:text])
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/app/helpers/applications_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationsHelper
2 | def current_user_has?(instance)
3 | user_signed_in? && current_user == instance.user || user_signed_in? && current_user.admin?
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/views/messages/edit.js.haml:
--------------------------------------------------------------------------------
1 | $("#message_#{ @message.id }").html("#{ j(render 'rooms/chat_form' )}");
2 |
3 | -# $('#comment_#{ @comment.id }').html("#{ j(render 'comments/form') }");
4 | -# %div{id: "message_#{m.id}"}
--------------------------------------------------------------------------------
/docker/mysql/password.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | password:
4 | environment:
5 | MYSQL_ROOT_PASSWORD: password
6 | MYSQL_USER: root
7 | MYSQL_PASSWORD: password
8 | TZ: "Asia/Tokyo"
9 |
--------------------------------------------------------------------------------
/app/views/tweets/_top.html.haml:
--------------------------------------------------------------------------------
1 | .wrapper
2 | %ul.tweet-index
3 | .tweet-index__title
4 | %p.tweet-index__title--text
5 | = render @tweets
6 | .tweet-index--footer
7 | = paginate @tweets
8 | = render 'tweets/new_btn'
--------------------------------------------------------------------------------
/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
2 |
3 | require 'bundler/setup' # Set up gems listed in the Gemfile.
4 | require 'bootsnap/setup' # Speed up boot time by caching expensive operations.
5 |
--------------------------------------------------------------------------------
/app/models/message.rb:
--------------------------------------------------------------------------------
1 | class Message < ApplicationRecord
2 | belongs_to :user
3 | belongs_to :room
4 |
5 | validates :message, presence: true, length: { maximum: 1000 }
6 |
7 | has_many :notifications, dependent: :destroy
8 | end
9 |
--------------------------------------------------------------------------------
/app/views/devise/mailer/confirmation_instructions.html.haml:
--------------------------------------------------------------------------------
1 | %p
2 | Welcome #{@email}!
3 | %p You can confirm your account email through the link below:
4 | %p= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token)
5 |
--------------------------------------------------------------------------------
/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: async
6 |
7 | production:
8 | adapter: redis
9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
10 | channel_prefix: my_app_production
11 |
--------------------------------------------------------------------------------
/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require_relative 'config/application'
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | require_relative '../config/boot'
8 | require 'rake'
9 | Rake.application.run
10 |
--------------------------------------------------------------------------------
/app/controllers/tweets/searches_controller.rb:
--------------------------------------------------------------------------------
1 | class Tweets::SearchesController < ApplicationController
2 | def index
3 | @tweets = Tweet.search(params[:keyword]).includes(%i[taggings user]).order('updated_at desc').page(params[:page]).per(10)
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/views/comments/_top.html.haml:
--------------------------------------------------------------------------------
1 | .tweet-show__comment__title
2 | .tweet-show__comment--count
3 | コメント(
4 | = @comments.count
5 | )
6 | %ul.tweet-show__comment__items
7 | - @comments.each do |comment|
8 | = render "comments/comment", comment: comment
--------------------------------------------------------------------------------
/app/views/kaminari/_page.html.slim:
--------------------------------------------------------------------------------
1 | - if page.current?
2 | li.page-item.active
3 | = content_tag :a, page, data: { remote: remote }, rel: page.rel, class: 'page-link'
4 | - else
5 | li.page-item
6 | = link_to page, url, remote: remote, rel: page.rel, class: 'page-link'
7 |
--------------------------------------------------------------------------------
/app/views/messages/create.js.haml:
--------------------------------------------------------------------------------
1 | $("#chat_area").html("#{j(render("rooms/chat_area"))}");
2 | $("#chat_form").html("#{j(render("rooms/chat_form"))}");
3 |
4 | -# $('#comment_area').html("#{ j(render 'comments/comment') }");
5 | -# $('#comment_form').html("#{ j(render 'comments/form') }");
--------------------------------------------------------------------------------
/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # ActiveSupport::Reloader.to_prepare do
4 | # ApplicationController.renderer.defaults.merge!(
5 | # http_host: 'example.org',
6 | # https: false
7 | # )
8 | # end
9 |
--------------------------------------------------------------------------------
/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Specify a serializer for the signed and encrypted cookie jars.
4 | # Valid options are :json, :marshal, and :hybrid.
5 | Rails.application.config.action_dispatch.cookies_serializer = :json
6 |
--------------------------------------------------------------------------------
/db/migrate/20200521050817_create_entries.rb:
--------------------------------------------------------------------------------
1 | class CreateEntries < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :entries do |t|
4 | t.references :user, foreign_key: true
5 | t.references :room, foreign_key: true
6 | t.timestamps
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/app/views/devise/mailer/unlock_instructions.html.haml:
--------------------------------------------------------------------------------
1 | %p
2 | Hello #{@resource.email}!
3 | %p Your account has been locked due to an excessive number of unsuccessful sign in attempts.
4 | %p Click the link below to unlock your account:
5 | %p= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token)
6 |
--------------------------------------------------------------------------------
/app/views/tweets/searches/index.html.haml:
--------------------------------------------------------------------------------
1 | .wrapper
2 | - if @tweets.present?
3 | %ul.tweet-index
4 | = render partial: 'tweets/tweet', collection: @tweets
5 | .tweet-index--footer
6 | = paginate @tweets
7 | - else
8 | .text-center
9 | 一致する投稿はありませんでした
10 | = render 'tweets/new_btn'
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | APP_PATH = File.expand_path('../config/application', __dir__)
8 | require_relative '../config/boot'
9 | require 'rails/commands'
10 |
--------------------------------------------------------------------------------
/spec/factories/entry.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :entry do
3 | # @user = create(:user)
4 | # @user2 = create(:user)
5 | # @room = create(:room)
6 | # @room2 = create(:room)
7 | user_id { FactoryBot.create(:user).id }
8 | room_id { FactoryBot.create(:room).id }
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/app/views/layouts/_circle.html.haml:
--------------------------------------------------------------------------------
1 | = link_to(notifications_path, class: "header-top__right--bell") do
2 | - if unchecked_notifications.any?
3 | %span.fa-stack
4 | %i.far.fa-bell.fa-lg.fa-stack-2x.black-important
5 | %i.fas.fa-circle.n-circle.fa-stack-1x
6 | - else
7 | %i.far.fa-bell.fa-lg.black-important
--------------------------------------------------------------------------------
/db/migrate/20200519001917_create_likes.rb:
--------------------------------------------------------------------------------
1 | class CreateLikes < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :likes do |t|
4 | t.references :tweet, null: false, foreign_key: true
5 | t.references :user, null: false, foreign_key: true
6 | t.timestamps
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/app/helpers/rooms_helper.rb:
--------------------------------------------------------------------------------
1 | module RoomsHelper
2 | def opened_user(room)
3 | entry = room.entries.where.not(user_id: current_user)
4 | entry[0].user
5 | end
6 |
7 | def get_most_new_message(room)
8 | last_message = room.messages.order(created_at: :desc).limit(1)
9 | last_message[0]
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | before_action :configure_permitted_parameters, if: :devise_controller?
3 |
4 | protected
5 |
6 | def configure_permitted_parameters
7 | devise_parameter_sanitizer.permit(:sign_up, keys: %i[nickname avatar])
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/docker/rails/puma.rb:
--------------------------------------------------------------------------------
1 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
2 | threads threads_count, threads_count
3 | port ENV.fetch("PORT") { 3000 }
4 | environment ENV.fetch("RAILS_ENV") { "development" }
5 | plugin :tmp_restart
6 |
7 | app_root = File.expand_path('..', __dir__)
8 | bind "unix://#{app_root}/tmp/sockets/puma.sock"
9 |
--------------------------------------------------------------------------------
/db/migrate/20200521050902_create_messages.rb:
--------------------------------------------------------------------------------
1 | class CreateMessages < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :messages do |t|
4 | t.references :user, foreign_key: true
5 | t.references :room, foreign_key: true
6 | t.text :message, null: false
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/docker/mysql/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mysql:5.7
2 |
3 | RUN apt-get update && \
4 | apt-get install -y apt-utils \
5 | locales && \
6 | rm -rf /var/lib/apt/lists/* && \
7 | echo "ja_JP.UTF-8 UTF-8" > /etc/locale.gen && \
8 | locale-gen ja_JP.UTF-8
9 | ENV LC_ALL ja_JP.UTF-8
10 | ADD ./docker/mysql/charset.cnf /etc/mysql/conf.d/charset.cnf
11 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/modules/_flash.scss:
--------------------------------------------------------------------------------
1 | .notification {
2 | .notice {
3 | background-color: $light_blue;
4 | color: white;
5 | text-align: center;
6 | box-shadow: none;
7 | }
8 |
9 | .alert {
10 | background-color: $alert_orange;
11 | color: white;
12 | text-align: center;
13 | box-shadow: none;
14 | }
15 | }
--------------------------------------------------------------------------------
/app/models/donation.rb:
--------------------------------------------------------------------------------
1 | class Donation < ApplicationRecord
2 | # tweets-------------------------------------------------------------------
3 | belongs_to :tweet
4 | # users-------------------------------------------------------------------
5 | belongs_to :user
6 |
7 | validates :amount, presence: true, numericality: { greater_than: 0 }
8 | end
9 |
--------------------------------------------------------------------------------
/app/views/devise/mailer/email_changed.html.haml:
--------------------------------------------------------------------------------
1 | %p
2 | Hello #{@email}!
3 | - if @resource.try(:unconfirmed_email?)
4 | %p
5 | We're contacting you to notify you that your email is being changed to #{@resource.unconfirmed_email}.
6 | - else
7 | %p
8 | We're contacting you to notify you that your email has been changed to #{@resource.email}.
9 |
--------------------------------------------------------------------------------
/app/views/users/edit.html.haml:
--------------------------------------------------------------------------------
1 | .wrapper
2 | .login
3 | = form_for(current_user) do |f|
4 | .login__title
5 | 更新する
6 | %br
7 | パスワードの変更をしない場合は入力不要です
8 | .login__field
9 | = render "users/form", f: f
10 | .login__field
11 | .center
12 | = f.submit "更新する", class: "btn btn-success"
13 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/config/_colors.scss:
--------------------------------------------------------------------------------
1 | $white: #fff;
2 | $side_blue_dark: #253141;
3 | $side_blue_light: #2f3e51;
4 | $light_blue: #38aef0;
5 | $light_gray: #999;
6 | $black: #434a54;
7 | $alert_orange: #f35500;
8 | $header_border: #e1e1e1;
9 | $header_tags: #787c7b;
10 | $body_color: #f5f5f5;
11 | $new_green: #26a645;
12 | $login_yellow: #ffc108;
13 |
--------------------------------------------------------------------------------
/bin/yarn:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_ROOT = File.expand_path('..', __dir__)
3 | Dir.chdir(APP_ROOT) do
4 | begin
5 | exec "yarnpkg", *ARGV
6 | rescue Errno::ENOENT
7 | $stderr.puts "Yarn executable was not detected in the system."
8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
9 | exit 1
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/app/models/relationship.rb:
--------------------------------------------------------------------------------
1 | class Relationship < ApplicationRecord
2 | # 自分をフォローしているユーザー
3 | belongs_to :follower, class_name: "User"
4 | # 自分がフォローしているユーザー
5 | belongs_to :following, class_name: "User"
6 | # バリデーション
7 | validates :follower_id, presence: true, uniqueness: { scope: :following_id }
8 | validates :following_id, presence: true
9 | end
10 |
--------------------------------------------------------------------------------
/app/views/comments/create.js.haml:
--------------------------------------------------------------------------------
1 | $('#comment_area').html("#{ j(render 'comments/top') }");
2 | $('#comment_form').html("#{ j(render 'comments/form') }");
3 | -# 省略する前の形
4 | -# $('#comment_area').html("<%= j(render 'comments/comment', comments: @comments) %>");
5 | -# $('#comment_form').html("<%= j(render 'comments/form', tweet: @tweet, comment: @comment) %>");
6 |
7 |
8 |
--------------------------------------------------------------------------------
/db/migrate/20200515005529_create_tweets.rb:
--------------------------------------------------------------------------------
1 | class CreateTweets < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :tweets do |t|
4 | t.string :title, null: false
5 | t.text :text, null: false
6 | t.string :image
7 | t.references :user, null: false, foreign_key: true
8 | t.timestamps
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20200515064321_create_comments.rb:
--------------------------------------------------------------------------------
1 | class CreateComments < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :comments do |t|
4 | t.references :tweet, null: false, foreign_key: true
5 | t.references :user, null: false, foreign_key: true
6 | t.text :content, null: false
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/docker/nginx/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM nginx:1.16
2 | RUN apt-get update && \
3 | apt-get install -y apt-utils \
4 | locales && \
5 | echo "ja_JP.UTF-8 UTF-8" > /etc/locale.gen && \
6 | locale-gen ja_JP.UTF-8
7 | ENV LC_ALL ja_JP.UTF-8
8 | # 初期状態の設定ファイル
9 | ADD ./docker/nginx/nginx.conf /etc/nginx/nginx.conf
10 | ADD ./docker/nginx/default.conf /etc/nginx/conf.d/default.conf
--------------------------------------------------------------------------------
/db/migrate/20200527224117_create_donations.rb:
--------------------------------------------------------------------------------
1 | class CreateDonations < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :donations do |t|
4 | t.references :tweet, null: false, foreign_key: true
5 | t.references :user, null: false, foreign_key: true
6 | t.integer :amount, null: false
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/app/views/rooms/_chat_form.html.haml:
--------------------------------------------------------------------------------
1 | .room-show__form
2 | .room-show__form__box
3 | = form_with(model: @message, id: "room-show__form") do |f|
4 | = f.text_field :message, placeholder: "メッセージを入力して下さい" ,class: "room-show__form--input"
5 | = f.hidden_field :room_id, value: @room.id
6 | %button{type: "submit", class: "fa fa-send fa-2x room-show__form--btn"}
7 |
--------------------------------------------------------------------------------
/spec/factories/message.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :message do
3 | # user
4 | # room
5 | # association :user
6 | # association :room
7 | # user_id { user.id }
8 | # room_id { room.id }
9 | user_id { FactoryBot.create(:user).id }
10 | room_id { FactoryBot.create(:room).id }
11 | message { Faker::Lorem.word }
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/controllers/relationships_controller.rb:
--------------------------------------------------------------------------------
1 | class RelationshipsController < ApplicationController
2 | def create
3 | @user = User.find(params[:following_id])
4 | current_user.follow(@user)
5 | @user.create_notification_follow!(current_user)
6 | end
7 |
8 | def destroy
9 | @user = User.find(params[:id])
10 | current_user.unfollow(@user)
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/mixin/_media.scss:
--------------------------------------------------------------------------------
1 | @mixin sm { @media (min-width: 0px) { @content; } }
2 | @mixin md { @media (min-width: 768px) { @content; } }
3 | @mixin lg { @media (min-width: 1020px) { @content; } }
4 |
5 | @mixin lg-max { @media (max-width: 1020px) { @content; } }
6 | @mixin md-max { @media (max-width: 768px) { @content; } }
7 | @mixin sm-max { @media (max-width: 580px) { @content; } }
--------------------------------------------------------------------------------
/spec/factories/tweet.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :tweet do
3 | title { Faker::Lorem.word }
4 | text { Faker::Lorem.word }
5 | image { File.open("#{Rails.root}/app/assets/images/default.png") }
6 | created_at { Faker::Time.between(from: DateTime.now - 2, to: DateTime.now) }
7 | # user
8 | association :user
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/app/views/devise/shared/_error_messages.html.haml:
--------------------------------------------------------------------------------
1 | - if resource.errors.any?
2 | #error_explanation
3 | %h2
4 | = I18n.t("errors.messages.not_saved", |
5 | count: resource.errors.count, |
6 | resource: resource.class.model_name.human.downcase) |
7 | %ul
8 | - resource.errors.full_messages.each do |message|
9 | %li= message
10 |
--------------------------------------------------------------------------------
/app/views/tweets/_new_btn.html.haml:
--------------------------------------------------------------------------------
1 | - if user_signed_in?
2 | = link_to new_tweet_path, class: "new-btn" do
3 | .new-btn__tweet
4 | %p.new-btn__tweet--text
5 | 投稿
6 | .new-btn__tweet--icon
7 | %i.fas.fa-plus
8 | - else
9 | = link_to users_guest_sign_in_path, method: :post, class: "new-btn" do
10 | .new-btn__session
11 | .new-btn__session--text
12 | %p ゲストログイン
13 |
--------------------------------------------------------------------------------
/db/migrate/20200520074806_create_relationships.rb:
--------------------------------------------------------------------------------
1 | class CreateRelationships < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :relationships do |t|
4 | t.references :follower, foreign_key: { to_table: :users }
5 | t.references :following, foreign_key: { to_table: :users }
6 | t.index [:follower_id, :following_id], unique: true
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/.rubocop_todo.yml:
--------------------------------------------------------------------------------
1 | # This configuration was generated by
2 | # `rubocop --auto-gen-config`
3 | # on 2020-08-21 15:02:15 +0900 using RuboCop version 0.85.1.
4 | # The point is for the user to remove these configuration records
5 | # one by one as the offenses are removed from the code base.
6 | # Note that changes in the inspected code, or installation of new
7 | # versions of RuboCop, may require this file to be generated again.
8 |
--------------------------------------------------------------------------------
/app/views/devise/mailer/reset_password_instructions.html.haml:
--------------------------------------------------------------------------------
1 | %p
2 | Hello #{@resource.email}!
3 | %p Someone has requested a link to change your password. You can do this through the link below.
4 | %p= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token)
5 | %p If you didn't request this, please ignore this email.
6 | %p Your password won't change until you access the link above and create a new one.
7 |
--------------------------------------------------------------------------------
/app/views/users/likes.html.haml:
--------------------------------------------------------------------------------
1 | .wrapper
2 | = render "/users/info"
3 | .tweet-index
4 | .user-show__select
5 | .user-show__select__left
6 | = link_to "最近の投稿", user_path
7 | .user-show__select__right
8 | .user-show__select--active.cursor-pointer
9 | いいねした投稿
10 | = render @tweets
11 | - unless @tweets.present? # @tweetsがなければtrue(下記を表示)
12 | いいねした投稿がありません
13 | = paginate @tweets
--------------------------------------------------------------------------------
/app/assets/javascripts/cable.js:
--------------------------------------------------------------------------------
1 | // Action Cable provides the framework to deal with WebSockets in Rails.
2 | // You can generate new channels where WebSocket features live using the `rails generate channel` command.
3 | //
4 | //= require action_cable
5 | //= require_self
6 | //= require_tree ./channels
7 |
8 | (function() {
9 | this.App || (this.App = {});
10 |
11 | App.cable = ActionCable.createConsumer();
12 |
13 | }).call(this);
14 |
--------------------------------------------------------------------------------
/spec/factories/user.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :user do
3 | password = Faker::Internet.password(min_length: 8)
4 | nickname { Faker::Name.last_name }
5 | email { Faker::Internet.email }
6 | password { password }
7 | password_confirmation { password }
8 | avatar { File.open("#{Rails.root}/app/assets/images/default.png") }
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/Capfile:
--------------------------------------------------------------------------------
1 | # 元々の記述は全て削除して大丈夫です
2 | require "capistrano/setup"
3 | require "capistrano/deploy"
4 | require "capistrano/scm/git"
5 | install_plugin Capistrano::SCM::Git
6 | require 'capistrano/rails/console'
7 | require 'capistrano/rbenv'
8 | require 'capistrano/bundler'
9 | require 'capistrano/rails/assets'
10 | require 'capistrano/rails/migrations'
11 | require 'capistrano3/unicorn'
12 | Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }
13 |
--------------------------------------------------------------------------------
/app/assets/javascripts/tweet-title.js:
--------------------------------------------------------------------------------
1 | $(function () {
2 | // いいねランキングが呼ばれたら表示
3 | if (document.location.href.match(/\/tweets\/likes/)) {
4 | $(".tweet-index__title").show();
5 | $(".tweet-index__title--text").append("人気投稿");
6 | }
7 |
8 | // タイムラインが呼ばれたら表示
9 | if (document.location.href.match(/\/users\/timeline/)) {
10 | $(".tweet-index__title").show();
11 | $(".tweet-index__title--text").append("タイムライン");
12 | }
13 | })
--------------------------------------------------------------------------------
/spec/factories/notification.rb:
--------------------------------------------------------------------------------
1 | # t.integer "visitor_id", null: false
2 | # t.integer "visited_id", null: false
3 | # t.integer "tweet_id"
4 | # t.integer "comment_id"
5 | # t.integer "message_id"
6 | # t.integer "room_id"
7 | # t.string "action", default: "", null: false
8 | # t.boolean "checked", default: false, null: false
9 |
10 | FactoryBot.define do
11 | factory :notification do
12 | # ページ遷移するとcheckedが変わるテストしてみたい
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/app/views/users/show.html.haml:
--------------------------------------------------------------------------------
1 | .wrapper
2 | = render "/users/info"
3 | .tweet-index
4 | .user-show__select
5 | .user-show__select__left
6 | .user-show__select--active
7 | 最近の投稿
8 | .user-show__select__right
9 | = link_to "いいねした投稿", likes_user_path, class: "a-hover"
10 | = render @tweets
11 | - unless @tweets.present? # @tweetsがなければtrue(下記を表示)
12 | 投稿がありません
13 | = paginate @tweets
14 |
15 |
--------------------------------------------------------------------------------
/app/views/donations/_donation.html.haml:
--------------------------------------------------------------------------------
1 | .tweet-show__content__donation--text
2 | この記事への寄付額
3 | %strong
4 | = @donations.to_s(:delimited)
5 | 円
6 | .tweet-show__content__donation--submit
7 | - if user_signed_in? && current_user.id != @tweet.user_id
8 | %button.btn.btn-info.rounded-pill.donation-btn
9 | 寄付する
10 | - elsif user_signed_in? && current_user.id == @tweet.user_id
11 | 自分の投稿には寄付ができません
12 | - else
13 | 寄付をするにはログインが必要です
--------------------------------------------------------------------------------
/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/app/views/devise/unlocks/new.html.haml:
--------------------------------------------------------------------------------
1 | %h2 Resend unlock instructions
2 | = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f|
3 | = render "devise/shared/error_messages", resource: resource
4 | .field
5 | = f.label :email
6 | %br/
7 | = f.email_field :email, autofocus: true, autocomplete: "email"
8 | .actions
9 | = f.submit "Resend unlock instructions"
10 | = render "devise/shared/links"
11 |
--------------------------------------------------------------------------------
/app/models/notification.rb:
--------------------------------------------------------------------------------
1 | class Notification < ApplicationRecord
2 | default_scope -> { order(created_at: :desc) }
3 | belongs_to :tweet, optional: true
4 | belongs_to :comment, optional: true
5 | belongs_to :message, optional: true
6 | belongs_to :room, optional: true
7 |
8 | belongs_to :visitor, class_name: 'User', foreign_key: 'visitor_id', optional: true
9 | belongs_to :visited, class_name: 'User', foreign_key: 'visited_id', optional: true
10 | end
11 |
--------------------------------------------------------------------------------
/app/views/devise/passwords/new.html.haml:
--------------------------------------------------------------------------------
1 | %h2 Forgot your password?
2 | = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f|
3 | = render "devise/shared/error_messages", resource: resource
4 | .field
5 | = f.label :email
6 | %br/
7 | = f.email_field :email, autofocus: true, autocomplete: "email"
8 | .actions
9 | = f.submit "Send me reset password instructions"
10 | = render "devise/shared/links"
11 |
--------------------------------------------------------------------------------
/app/models/like.rb:
--------------------------------------------------------------------------------
1 | class Like < ApplicationRecord
2 | # likes-------------------------------------------------------------------
3 | validates :tweet_id, uniqueness: { scope: :user_id }, presence: true
4 | validates :user_id, presence: true
5 | # tweets-------------------------------------------------------------------
6 | belongs_to :tweet, counter_cache: :likes_count
7 | # users-------------------------------------------------------------------
8 | belongs_to :user
9 | end
10 |
--------------------------------------------------------------------------------
/app/views/donations/_form.html.haml:
--------------------------------------------------------------------------------
1 | = form_with(model: [@tweet, @donation], data: { confirm: '本当に寄付しますか?(一度寄付したら取り消せません)' }, id: "donation-form") do |f|
2 | .donation-modal--body
3 | = f.number_field :amount, placeholder: "金額を入力して下さい", class: "donation-modal--body--input"
4 | .form
5 | .donation-modal--close
6 | %i.btn.btn-dark.donation-modal--close--btn
7 | 閉じる
8 | .donation-modal--submit
9 | = f.submit "送信する", class: "page-link text-dark d-inline-block donation-modal--submit--btn"
--------------------------------------------------------------------------------
/app/views/kaminari/_paginator.html.slim:
--------------------------------------------------------------------------------
1 | = paginator.render do
2 | nav
3 | ul.pagination
4 | == first_page_tag unless current_page.first?
5 | == prev_page_tag unless current_page.first?
6 | - each_page do |page|
7 | - if page.left_outer? || page.right_outer? || page.inside_window?
8 | == page_tag page
9 | - elsif !page.was_truncated?
10 | == gap_tag
11 | == next_page_tag unless current_page.last?
12 | == last_page_tag unless current_page.last?
13 |
--------------------------------------------------------------------------------
/app/models/comment.rb:
--------------------------------------------------------------------------------
1 | class Comment < ApplicationRecord
2 | # tweets-------------------------------------------------------------------
3 | belongs_to :tweet
4 | # users-------------------------------------------------------------------
5 | belongs_to :user
6 | # comments-------------------------------------------------------------------
7 | validates :content, presence: true, length: { maximum: 1000 }
8 | # notifications-------------------------------------------------------------------
9 | has_many :notifications, dependent: :destroy
10 | end
11 |
--------------------------------------------------------------------------------
/app/views/users/_contact.html.haml:
--------------------------------------------------------------------------------
1 | - if user_signed_in?
2 | - unless @user.id == current_user.id
3 | - if @have_room == true
4 | = link_to room_path(@room_id) do
5 | %i.btn.btn-info.user-show__top--contact--btn
6 | メッセージを送る
7 | - else
8 | = form_with(model:@room, local: true) do |f|
9 | = fields_for @entry do |e|
10 | = e.hidden_field :user_id, value: @user.id
11 | = f.submit "メッセージ送る", class: "btn btn-info user-show__top--contact--btn"
--------------------------------------------------------------------------------
/app/views/devise/confirmations/new.html.haml:
--------------------------------------------------------------------------------
1 | %h2 Resend confirmation instructions
2 | = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f|
3 | = render "devise/shared/error_messages", resource: resource
4 | .field
5 | = f.label :email
6 | %br/
7 | = f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email)
8 | .actions
9 | = f.submit "Resend confirmation instructions"
10 | = render "devise/shared/links"
11 |
--------------------------------------------------------------------------------
/app/views/comments/_form.html.haml:
--------------------------------------------------------------------------------
1 | - if user_signed_in?
2 | = form_with(model: [@tweet, @comment], class: "tweet-show__comment__form") do |form|
3 | .tweet-show__comment__form__top
4 | = image_tag "#{current_user.avatar}", class: "avatar"
5 | .tweet-show__comment__form--title
6 | コメントする
7 | = form.text_area :content, class: 'textarea tweet-show__comment__form--body'
8 | .tweet-show__comment__form--submit
9 | = form.submit value: "投稿する", class: "btn btn-success"
10 | - else
11 | .tweet-show__comment__form--text
12 | コメントの投稿にはログインが必要です。
--------------------------------------------------------------------------------
/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/app/views/likes/_like.html.haml:
--------------------------------------------------------------------------------
1 | - if user_signed_in?
2 | - if tweet.like_user(current_user.id)
3 | = link_to tweet_like_path(tweet.likes, tweet_id: tweet.id), method: :delete, remote: true do
4 | %i.fa.fa-heart{"aria-hidden" => "true", style: "color: red;"}
5 | = tweet.likes_count
6 | - else
7 | = link_to tweet_likes_path(tweet.id), method: :post, remote: true do
8 | %i.fa.fa-heart{"aria-hidden" => "true", style: "color: #C0C0C0;"}
9 | = tweet.likes_count
10 | - else
11 | %i.fa.fa-heart{"aria-hidden" => "true"}
12 | = tweet.likes_count
13 |
--------------------------------------------------------------------------------
/bin/spring:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # This file loads Spring without using Bundler, in order to be fast.
4 | # It gets overwritten when you run the `spring binstub` command.
5 |
6 | unless defined?(Spring)
7 | require 'rubygems'
8 | require 'bundler'
9 |
10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read)
11 | spring = lockfile.specs.detect { |spec| spec.name == 'spring' }
12 | if spring
13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
14 | gem 'spring', spring.version
15 | require 'spring/binstub'
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/app/controllers/likes_controller.rb:
--------------------------------------------------------------------------------
1 | class LikesController < ApplicationController
2 | before_action :set_tweet, only: %i[create destroy]
3 |
4 | def create
5 | like = Like.create(user_id: current_user.id, tweet_id: @tweet.id)
6 | like.save
7 | @tweet.create_notification_like!(current_user)
8 | @tweet.reload
9 | end
10 |
11 | def destroy
12 | like = Like.find_by(user_id: current_user.id, tweet_id: @tweet.id)
13 | like.destroy
14 | @tweet.reload
15 | end
16 |
17 | private
18 |
19 | def set_tweet
20 | @tweet = Tweet.find(params[:tweet_id])
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/spec/models/room_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Room, type: :model do
4 | describe "各モデルとのアソシエーション" do
5 | let(:association) do
6 | described_class.reflect_on_association(target)
7 | end
8 |
9 | context "アソシエーションのテスト" do
10 | let(:target) { :rooms }
11 | it "Roomsとの関連付けはhas_manyであること" do
12 | expect(association.macro).to eq :has_many
13 | end
14 |
15 | let(:target) { :messages }
16 | it "Messagesとの関連付けはhas_manyであること" do
17 | expect(association.macro).to eq :has_many
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/config/puma.rb:
--------------------------------------------------------------------------------
1 | # 初期状態 --------------------------
2 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
3 | threads threads_count, threads_count
4 | port ENV.fetch("PORT") { 3000 }
5 | environment ENV.fetch("RAILS_ENV") { "development" }
6 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
7 | plugin :tmp_restart
8 |
9 | # Docker使用設定
10 | # threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
11 | # threads threads_count, threads_count
12 | # port ENV.fetch("PORT") { 3000 }
13 | # environment ENV.fetch("RAILS_ENV") { "development" }
14 | # plugin :tmp_restart
15 | # app_root = File.expand_path("../..", __FILE__)
16 | # bind "unix://#{app_root}/tmp/sockets/puma.sock"
17 |
--------------------------------------------------------------------------------
/db/migrate/20200523235326_add_missing_taggable_index.acts_as_taggable_on_engine.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from acts_as_taggable_on_engine (originally 4)
2 | if ActiveRecord.gem_version >= Gem::Version.new('5.0')
3 | class AddMissingTaggableIndex < ActiveRecord::Migration[4.2]; end
4 | else
5 | class AddMissingTaggableIndex < ActiveRecord::Migration; end
6 | end
7 | AddMissingTaggableIndex.class_eval do
8 | def self.up
9 | add_index ActsAsTaggableOn.taggings_table, [:taggable_id, :taggable_type, :context], name: 'taggings_taggable_context_idx'
10 | end
11 |
12 | def self.down
13 | remove_index ActsAsTaggableOn.taggings_table, name: 'taggings_taggable_context_idx'
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/app/helpers/devise_helper.rb:
--------------------------------------------------------------------------------
1 | module DeviseHelper
2 | def devise_error_messages!
3 | return "" if resource.errors.empty?
4 |
5 | html = ""
6 | resource.errors.full_messages.each do |errmsg|
7 | html += <<-EOF
8 |
9 |
13 | #{errmsg}
14 |
15 | EOF
16 | end
17 | html.html_safe
18 | end
19 |
20 | def devise_error_messages?
21 | resource.errors.empty? ? false : true
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = '1.0'
5 |
6 | # Add additional assets to the asset load path.
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 | # Add Yarn node_modules folder to the asset load path.
9 | Rails.application.config.assets.paths << Rails.root.join('node_modules')
10 |
11 | # Precompile additional assets.
12 | # application.js, application.css, and all non-JS/CSS in the app/assets
13 | # folder are already added.
14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css )
15 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/modules/_user-index.scss:
--------------------------------------------------------------------------------
1 | .users {
2 | // width: 600px;
3 | @include common-sizing;
4 | margin: 0 auto;
5 | border: 1px solid $header_border;
6 | background-color: white;
7 | border-radius: 5px;
8 | box-shadow: 0px 4px 6px #ccc;
9 | &__title {
10 | height: 80px;
11 | text-align: center;
12 | line-height: 80px;
13 | font-size: 22px;
14 | }
15 | &__item {
16 | height: 80px;
17 | width: 100%;
18 | display: flex;
19 | padding: 30px;
20 | border-bottom: 1px solid $header_border;
21 | &:hover {
22 | color: black;
23 | background-color: $header_border;
24 | }
25 | &--nickname {
26 | padding: 5px 15px;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/views/rooms/_room.html.haml:
--------------------------------------------------------------------------------
1 | %li.room-index__items__item.room-index-hover
2 | - user = opened_user(room)
3 | = link_to user_path(user.id) do
4 | = image_tag "#{user.avatar}", class: "room-index__items__item--icon"
5 | = link_to room_path(room), class: 'room-index__items__item__content' do
6 | .room-index__items__item__content--body
7 | %strong.room-index__items__item__content--nickname
8 | = user.nickname
9 | - message = get_most_new_message(room)
10 | - if message.present?
11 | .room-index__items__item__content--message
12 | = truncate(message.message, length: 20)
13 | .room-index__items__item__content--date
14 | = message.created_at.strftime("%Y-%m-%d %H:%M")
15 |
--------------------------------------------------------------------------------
/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/db/migrate/20200523235327_change_collation_for_tag_names.acts_as_taggable_on_engine.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from acts_as_taggable_on_engine (originally 5)
2 | # This migration is added to circumvent issue #623 and have special characters
3 | # work properly
4 | if ActiveRecord.gem_version >= Gem::Version.new('5.0')
5 | class ChangeCollationForTagNames < ActiveRecord::Migration[4.2]; end
6 | else
7 | class ChangeCollationForTagNames < ActiveRecord::Migration; end
8 | end
9 | ChangeCollationForTagNames.class_eval do
10 | def up
11 | if ActsAsTaggableOn::Utils.using_mysql?
12 | execute("ALTER TABLE #{ActsAsTaggableOn.tags_table} MODIFY name varchar(255) CHARACTER SET utf8 COLLATE utf8_bin;")
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/app/views/devise/sessions/new.html.haml:
--------------------------------------------------------------------------------
1 | .wrapper
2 | .login
3 | .login__field
4 | .center
5 | = render 'devise/sessions/testuser'
6 | = form_with model: @user, url: user_session_path, local: true do |f|
7 | .login__title
8 | ログイン
9 | .login__field
10 | .text-center
11 | = f.label :email
12 | .center
13 | = f.email_field :email, autofocus: true, class: "login--input"
14 | .login__field
15 | .text-center
16 | = f.label :password
17 | .center
18 | = f.password_field :password, autocomplete: "off", class: "login--input"
19 | .login__field
20 | .center
21 | = f.submit "ログインする", class: "btn btn-success"
22 |
--------------------------------------------------------------------------------
/docker/rails/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ruby:2.5.1
2 |
3 | RUN apt-get update -qq && \
4 | apt-get install -y apt-utils \
5 | build-essential \
6 | libpq-dev \
7 | nodejs \
8 | vim \
9 | default-mysql-client
10 |
11 | # 作業ディレクトリの作成、設定
12 | RUN mkdir /app
13 | ##作業ディレクトリ名をAPP_ROOTに割り当てて、以下$APP_ROOTで参照
14 | ENV APP_ROOT /app
15 | WORKDIR $APP_ROOT
16 |
17 | # ホスト側(ローカル)のGemfileを追加する(ローカルのGemfileは【3】で作成)
18 | ADD ./Gemfile $APP_ROOT/Gemfile
19 | ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock
20 |
21 | # bundlerのバージョンを2系にする
22 | ENV BUNDLER_VERSION='2.1.4'
23 | RUN gem install bundler --no-document -v '2.1.4'
24 |
25 | # Gemfileのbundle install
26 | RUN bundle install
27 | ADD . $APP_ROOT
28 |
29 | RUN mkdir -p $APP_ROOT/tmp/sockets
30 |
31 | EXPOSE 3000
32 |
33 |
--------------------------------------------------------------------------------
/docker/nginx/default.conf:
--------------------------------------------------------------------------------
1 | # プロキシ先の指定
2 | # Nginxが受け取ったリクエストをバックエンドのpumaに送信
3 | upstream app {
4 | # ソケット通信したいのでpuma.sockを指定
5 | server unix:///app/tmp/sockets/puma.sock;
6 | }
7 |
8 | server {
9 | listen 80;
10 | # ドメインもしくはIPを指定 54.238.171.38; # デプロイする時は変更する(環境変数で用意すべきかも)
11 | server_name localhost;
12 |
13 | access_log /var/log/nginx/access.log;
14 | error_log /var/log/nginx/error.log;
15 |
16 | # ドキュメントルートの指定
17 | root /app/public;
18 |
19 | # リバースプロキシ設定
20 | location / {
21 | proxy_pass http://app;
22 | }
23 |
24 | client_max_body_size 100m;
25 | error_page 404 /404.html;
26 | error_page 505 502 503 504 /500.html;
27 | try_files $uri/index.html $uri @app;
28 | keepalive_timeout 5;
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/app/controllers/donations_controller.rb:
--------------------------------------------------------------------------------
1 | class DonationsController < ApplicationController
2 | before_action :set_tweet, only: [:create]
3 |
4 | def create
5 | @donation = @tweet.donations.create(donation_params)
6 | if @donation.save
7 | # 寄付の通知を実装予定!
8 | # @donation.create_notification_donation!(current_user, @donation.id)
9 | @donation = Donation.new
10 | end
11 | gets_all_donations
12 | end
13 |
14 | private
15 |
16 | def set_tweet
17 | @tweet = Tweet.find(params[:tweet_id])
18 | end
19 |
20 | def gets_all_donations
21 | @donations = @tweet.donations.sum(:amount)
22 | end
23 |
24 | def donation_params
25 | params.require(:donation).permit(:amount).merge(user_id: current_user.id)
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/app/helpers/markdown_helper.rb:
--------------------------------------------------------------------------------
1 | module MarkdownHelper
2 | def markdown(text)
3 | unless @markdown
4 | options = {
5 | hard_wrap: true, # 改行を反映
6 | filter_html: true, # HTMLタグを出力しない。h1タグ等。XSS対策済み
7 | space_after_headers: true # 「#」の後に空白がないと見出し(h1)と認めない
8 | }
9 | extensions = {
10 | autolink: true, # URLをクリックすると飛べる
11 | fenced_code_blocks: true, # Qiitaのようにコードを装飾できる「```」
12 | tables: true, # テーブル表もどきになる「| td | td | td |&|---|---|---|」
13 | strikethrough: true # 取り消し線を有効「~~AA~~」
14 | }
15 | renderer = Redcarpet::Render::HTML.new(options)
16 | @markdown = Redcarpet::Markdown.new(renderer, extensions)
17 | end
18 | @markdown.render(text).html_safe
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/modules/_session.scss:
--------------------------------------------------------------------------------
1 | .login{
2 | width: 400px;
3 | @include md-max{
4 | width: 300px;
5 | }
6 | margin: 0 auto;
7 | padding: 30px;
8 | background-color: white;
9 | border: 1px solid $header_border;
10 | border-radius: 5px;
11 | box-shadow: 0px 4px 6px #ccc;
12 | display: flex;
13 | flex-direction: column;
14 | &__title{
15 | text-align: center;
16 | font-size: 23px;
17 | font-weight: bold;
18 | padding: 10px;
19 | }
20 | &__field{
21 | padding: 10px;
22 | }
23 | &--input{
24 | width: 200px;
25 | color: #222;
26 | background: white;
27 | border: 1px solid #e6e6e6;
28 | border-radius: 5px;
29 | padding: 0 4px;
30 | line-height: 38px;
31 | font-size: 14px;
32 | }
33 | }
--------------------------------------------------------------------------------
/config/initializers/carrierwave.rb:
--------------------------------------------------------------------------------
1 | require 'carrierwave/storage/abstract'
2 | require 'carrierwave/storage/file'
3 | require 'carrierwave/storage/fog'
4 |
5 | CarrierWave.configure do |config|
6 | if Rails.env.development? || Rails.env.test?
7 | config.storage = :file
8 | else
9 | config.storage = :fog
10 | config.fog_provider = 'fog/aws'
11 | config.fog_credentials = {
12 | provider: 'AWS',
13 | aws_access_key_id: Rails.application.credentials.aws[:ACCESS_KEY_ID],
14 | aws_secret_access_key: Rails.application.credentials.aws[:SECRET_ACCESS_KEY],
15 | region: 'ap-northeast-1'
16 | }
17 | config.fog_directory = 'my-app0014'
18 | config.asset_host = 'https://s3-ap-northeast-1.amazonaws.com/my-app0014'
19 | end
20 | end
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2 | #
3 | # If you find yourself ignoring temporary files generated by your text editor
4 | # or operating system, you probably want to add a global ignore instead:
5 | # git config --global core.excludesfile '~/.gitignore_global'
6 |
7 | # Ignore bundler config.
8 | /.bundle
9 |
10 | # Ignore all logfiles and tempfiles.
11 | /log/*
12 | /tmp/*
13 | !/log/.keep
14 | !/tmp/.keep
15 |
16 | # Ignore uploaded files in development
17 | /storage/*
18 | !/storage/.keep
19 |
20 | /node_modules
21 | /yarn-error.log
22 |
23 | /public/assets
24 | .byebug_history
25 |
26 | # Ignore master key for decrypting credentials and more.
27 | /config/master.key
28 | public/uploads/*
29 | .DS_Store
30 |
31 | /.bundle
32 | /vendor/bundle
33 |
--------------------------------------------------------------------------------
/app/models/room.rb:
--------------------------------------------------------------------------------
1 | class Room < ApplicationRecord
2 | # messages-------------------------------------------------------------------
3 | has_many :messages
4 | has_many :entries
5 | has_many :users, through: :entries
6 | # notifications-------------------------------------------------------------------
7 | has_many :notifications
8 | def create_notification_message!(current_user, message_id)
9 | temp_ids = Entry.select(:user_id).where(room_id: id).where.not(user_id: current_user.id).distinct
10 | temp_ids.each do |temp_id|
11 | notification = current_user.active_notifications.new(
12 | room_id: id,
13 | message_id: message_id,
14 | visited_id: temp_id['user_id'],
15 | action: 'message'
16 | )
17 | notification.save
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/db/migrate/20200523015543_create_notifications.rb:
--------------------------------------------------------------------------------
1 | class CreateNotifications < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :notifications do |t|
4 | t.integer :visitor_id, null: false
5 | t.integer :visited_id, null: false
6 | t.integer :tweet_id
7 | t.integer :comment_id
8 | t.integer :message_id
9 | t.integer :room_id
10 | t.string :action, default: '', null: false
11 | t.boolean :checked, default: false, null: false
12 |
13 | t.timestamps
14 | end
15 |
16 | add_index :notifications, :visitor_id
17 | add_index :notifications, :visited_id
18 | add_index :notifications, :tweet_id
19 | add_index :notifications, :comment_id
20 | add_index :notifications, :message_id
21 | add_index :notifications, :room_id
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
5 | // vendor/assets/javascripts directory can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file. JavaScript code in this file should be added after the last require_* statement.
9 | //
10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require jquery
14 | //= require jquery-ui
15 | //= require jquery_ujs
16 | //= require tag-it
17 | //= require_tree .
18 |
--------------------------------------------------------------------------------
/app/views/devise/passwords/edit.html.haml:
--------------------------------------------------------------------------------
1 | %h2 Change your password
2 | = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f|
3 | = render "devise/shared/error_messages", resource: resource
4 | = f.hidden_field :reset_password_token
5 | .field
6 | = f.label :password, "New password"
7 | %br/
8 | - if @minimum_password_length
9 | %em
10 | (#{@minimum_password_length} characters minimum)
11 | %br/
12 | = f.password_field :password, autofocus: true, autocomplete: "new-password"
13 | .field
14 | = f.label :password_confirmation, "Confirm new password"
15 | %br/
16 | = f.password_field :password_confirmation, autocomplete: "new-password"
17 | .actions
18 | = f.submit "Change my password"
19 | = render "devise/shared/links"
20 |
--------------------------------------------------------------------------------
/config/credentials.yml.enc:
--------------------------------------------------------------------------------
1 | edlF06Z2gir1liaggAr35+C5elyeURwREiBqvrnUCNqY8O29ZQBbIwraj5t4Kev3ZpGtq84BZ1QuVkryVt59C7LPkecnxJjr0E65k5qZGtu9o7pl57yCae7c7eotP2VHFg9Dl94/qCM/xJi6totzdS/9n14Myz9Qi44kaEoZhRUKTQbDtCTJynucmnpFbejRiOHD/Dio5AoPxwKs9kXIa05+w3fqT8FAjmyiiCbgeO8/ww5kyxVBxKX35+IkbAppfQdZZ2JjoHCneyKng2S+k268/i/VvktseGLmsdqwxRczgrDEO/Q676GJKm7iK1ki1FnAo9dr0cKLknrZPT8rd+uXZN2cHgCjIxWCAw1Yda6N746wbZLpoFg99mYQMZiF09Bs3F0lkVBPCQSWTUb3Bz+YKqcBHY8X1Z49U/5sqAklVUzj9IoV9SkOYYqCWlgldp8M45l5vOiir0AU/fzEp57K7rz3DbiSQN2L7moG4rTm7bbc39fERKkH3mvLENV481ytjiwqySy85Q2mIxgf4EqRKfvACTu6FwPoImDbwHAKbafkOJ0tnfDhs6sCrawE0nk+VsQN57cgh5XyExhVryVH/NTfPbhkPryaDQAiVbaqQxgkYeaawrBRag1X0Kmz3Ih8MsFRt0mb0hnXW+djIbwe6tGQJuOLbMMZg+n2bAXuMTK3LGZxU0sO0h1ndhuh5LljhVvaSVOB6GMWFy+yUdTgsx2RSAKfheOBbKcCrP41a9kR7NS7Qk4D8k6EzzVpFVL0x6ecJQ+7FtBC+wYX+AE=--ttdZjxSiHZHinCbW--fDARRCar37G+xfF2dNjhLA==
--------------------------------------------------------------------------------
/app/assets/javascripts/tagit.js:
--------------------------------------------------------------------------------
1 | $(function () {
2 | // URLがtweets/newかeditならtrue
3 | if (
4 | document.location.href.match(/\/tweets\/new/) ||
5 | document.location.href.match(/\/tweets\/\d+\/edit/)
6 | ) {
7 | var i, len, ref, results, tag; // 変数を定義
8 |
9 | $("#tweet-tags").tagit({
10 | // tagitを発火
11 | fieldName: "tweet[tag_list]", // paramterの値をtag_listに指定。これでcontrollerで受け取れるようにする。
12 | singleField: true,
13 | availableTags: gon.available_tags, // オートコンプリートを有効にする
14 | });
15 | // 既存のタグがあれば表示させる
16 | if (gon.tweet_tags != null) {
17 | ref = gon.tweet_tags;
18 | results = [];
19 | for (i = 0, len = ref.length; i < len; i++) {
20 | tag = ref[i];
21 | results.push($("#tweet-tags").tagit("createTag", tag));
22 | }
23 | return results;
24 | }
25 | }
26 | });
27 |
--------------------------------------------------------------------------------
/db/migrate/20200523235325_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from acts_as_taggable_on_engine (originally 3)
2 | if ActiveRecord.gem_version >= Gem::Version.new('5.0')
3 | class AddTaggingsCounterCacheToTags < ActiveRecord::Migration[4.2]; end
4 | else
5 | class AddTaggingsCounterCacheToTags < ActiveRecord::Migration; end
6 | end
7 | AddTaggingsCounterCacheToTags.class_eval do
8 | def self.up
9 | add_column ActsAsTaggableOn.tags_table, :taggings_count, :integer, default: 0
10 |
11 | ActsAsTaggableOn::Tag.reset_column_information
12 | ActsAsTaggableOn::Tag.find_each do |tag|
13 | ActsAsTaggableOn::Tag.reset_counters(tag.id, ActsAsTaggableOn.taggings_table)
14 | end
15 | end
16 |
17 | def self.down
18 | remove_column ActsAsTaggableOn.tags_table, :taggings_count
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/app/controllers/users/sessions_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Users::SessionsController < Devise::SessionsController
4 | # before_action :configure_sign_in_params, only: [:create]
5 |
6 | def new_guest
7 | user = User.find_by(email: "guest@user")
8 | sign_in user
9 | redirect_to root_path, notice: 'ゲストユーザーとしてログインしました。'
10 | end
11 |
12 | # GET /resource/sign_in
13 | # def new
14 | # super
15 | # end
16 |
17 | # POST /resource/sign_in
18 | # def create
19 | # super
20 | # end
21 |
22 | # DELETE /resource/sign_out
23 | # def destroy
24 | # super
25 | # end
26 |
27 | # protected
28 |
29 | # If you have extra params to permit, append them to the sanitizer.
30 | # def configure_sign_in_params
31 | # devise_parameter_sanitizer.permit(:sign_in, keys: [:attribute])
32 | # end
33 | end
34 |
--------------------------------------------------------------------------------
/spec/features/tweet_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | feature 'tweet', type: :feature do
4 | let(:user) { create(:user) }
5 |
6 | scenario 'ユーザー情報が更新されていること' do
7 | # ログイン前には投稿ボタンがない
8 | visit root_path
9 | expect(page).to have_no_content("投稿")
10 |
11 | # ログイン処理
12 | visit new_user_session_path
13 | fill_in 'user_email', with: user.email
14 | fill_in 'user_password', with: user.password
15 | find('input[value="ログインする"]').click
16 | expect(current_path).to eq root_path
17 | expect(page).to have_content("投稿")
18 |
19 | # 投稿処理
20 | expect do
21 | click_link("投稿")
22 | expect(current_path).to eq new_tweet_path
23 | fill_in 'tweet_title', with: "結合テスト"
24 | fill_in 'md-textarea', with: "結合テスト"
25 | find('input[type="submit"]').click
26 | end.to change(Tweet, :count).by(1)
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/app/views/comments/_comment.html.haml:
--------------------------------------------------------------------------------
1 | %li.tweet-show__comment__item
2 | %div{id: "comment_#{comment.id}"}
3 | .tweet-show__comment__item__top
4 | = link_to user_path(comment.user.id) do
5 | .tweet-show__comment__item__top--user
6 | = image_tag "#{comment.user.avatar}", class: "avatar"
7 | .tweet-show__comment__item__top--nickname
8 | = comment.user.nickname
9 | .tweet-show__comment__item__top--date
10 | = comment.created_at.strftime("%Y-%m-%d %H:%M")
11 | .tweet-show__comment__item--text
12 | = simple_format(comment.content)
13 | .tweet-show__comment__item--edit
14 | - if current_user_has?(comment)
15 | = link_to "編集", edit_tweet_comment_path(@tweet.id, comment), remote: true, class: "tweet-show__comment__item--edit--left"
16 | = link_to "削除", tweet_comment_path(@tweet, comment.id), method: :delete, remote: true, data: { confirm: '削除してよろしいですか?' }
--------------------------------------------------------------------------------
/docker-compose-dev.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | db:
4 | container_name: mynote
5 | image: mysql:5.7
6 | environment:
7 | MYSQL_DATABASE: mynote_development
8 | MYSQL_USER: root
9 | MYSQL_PASSWORD: password
10 | MYSQL_ROOT_PASSWORD: password
11 | ports:
12 | - "4306:3306"
13 | volumes:
14 | - mysql-data:/var/lib/mysql #データの永続化のために必要
15 |
16 | web:
17 | build: .
18 | command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
19 | volumes:
20 | - .:/myapp
21 | - bundle:/usr/local/bundle #bundle installした後buildし直さなくてよくなる
22 | ports:
23 | - "3000:3000"
24 | depends_on:
25 | - db
26 | tty: true #コンテナ上でbinding.pryするために必要
27 | stdin_open: true #コンテナ上でbinding.pryするために必要
28 | links:
29 | - db
30 | volumes:
31 | mysql-data:
32 | bundle: #bundle installした後buildし直さなくてよくなる
33 |
--------------------------------------------------------------------------------
/bin/update:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'fileutils'
3 | include FileUtils
4 |
5 | # path to your application root.
6 | APP_ROOT = File.expand_path('..', __dir__)
7 |
8 | def system!(*args)
9 | system(*args) || abort("\n== Command #{args} failed ==")
10 | end
11 |
12 | chdir APP_ROOT do
13 | # This script is a way to update your development environment automatically.
14 | # Add necessary update steps to this file.
15 |
16 | puts '== Installing dependencies =='
17 | system! 'gem install bundler --conservative'
18 | system('bundle check') || system!('bundle install')
19 |
20 | # Install JavaScript dependencies if using Yarn
21 | # system('bin/yarn')
22 |
23 | puts "\n== Updating database =="
24 | system! 'bin/rails db:migrate'
25 |
26 | puts "\n== Removing old logs and tempfiles =="
27 | system! 'bin/rails log:clear tmp:clear'
28 |
29 | puts "\n== Restarting application server =="
30 | system! 'bin/rails restart'
31 | end
32 |
--------------------------------------------------------------------------------
/docker/nginx/nginx.conf:
--------------------------------------------------------------------------------
1 |
2 | # 初期状態の設定ファイル
3 |
4 | user nginx;
5 | # workerプロセスの起動数を定義 autoに設定すると最適値を試みる
6 | worker_processes 1;
7 |
8 | error_log /var/log/nginx/error.log warn;
9 | pid /var/run/nginx.pid;
10 |
11 |
12 | events {
13 | #一つのworkerプロセグが開ける最大コネクション数
14 |
15 | worker_connections 1024;
16 | }
17 |
18 |
19 | http {
20 | include /etc/nginx/mime.types;
21 | default_type application/octet-stream;
22 |
23 | log_format main '$remote_addr - $remote_user [$time_local] "$request" '
24 | '$status $body_bytes_sent "$http_referer" '
25 | '"$http_user_agent" "$http_x_forwarded_for"';
26 |
27 | access_log /var/log/nginx/access.log main;
28 |
29 | sendfile on;
30 | #tcp_nopush on;
31 |
32 | keepalive_timeout 65;
33 |
34 | #gzip on;
35 |
36 | #これでdefault.confなどをincludeしてる
37 | include /etc/nginx/conf.d/*.conf;
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/app/views/tweets/tags.html.haml:
--------------------------------------------------------------------------------
1 | .wrapper
2 | .tweet-tags
3 | .tweet-tags__title
4 | -# タグ検索機能を実装予定
5 | -# .tweet-tags__title__left
6 | -# %input{ type: "text", class: "header-top__right--search", id: "tag-input", placeholder: "検索するタグを入力してください"}
7 | -# -# %input{ type: "submit", id: "tag-submit"}
8 | -# #makeImg.searchArea
9 | -# %input#inText.searchText{type: "text"}
10 | -# .searchButton
11 | -# %img.searchImg{src: "search30.png"}
12 | .tweet-tags__title__right
13 | %p.tweet-tags__title--body
14 | = "人気タグ"
15 | %ul.tweet-tags__items
16 | - @tags.each do |tag|
17 | %li.tweet-tags__item
18 | = link_to tweets_path(tag_name: tag.name), class: "tweet-tags__item--link" do
19 | %i.btn.btn-outline-dark.tweet-tags__item--btn
20 | = tag.name
21 | .tweet-tags__item--body
22 | = tag.count
23 | = "件"
24 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # 'true': 'foo'
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at http://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/config/application.rb:
--------------------------------------------------------------------------------
1 | require_relative 'boot'
2 |
3 | require 'rails/all'
4 |
5 | # Require the gems listed in Gemfile, including any gems
6 | # you've limited to :test, :development, or :production.
7 | Bundler.require(*Rails.groups)
8 |
9 | module MyApp
10 | class Application < Rails::Application
11 | # Initialize configuration defaults for originally generated Rails version.
12 | config.load_defaults 5.2
13 | config.i18n.default_locale = :ja
14 | config.time_zone = 'Tokyo'
15 | # 不要なファイルを作成しない
16 | config.generators do |g|
17 | g.stylesheets false
18 | g.javascripts false
19 | g.helper false
20 | g.test_framework false
21 | end
22 |
23 | # Settings in config/environments/* take precedence over those specified here.
24 | # Application configuration can go into files in config/initializers
25 | # -- all .rb files in that directory are automatically loaded after loading
26 | # the framework and any gems in your application.
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/app/assets/javascripts/tweet_show.js:
--------------------------------------------------------------------------------
1 | $(function () {
2 | $(document).on("click", ".donation-btn", function () {
3 | $(".donation").fadeIn(200);
4 | });
5 | $(document).on("click", ".donation-modal--close--btn", function () {
6 | console.log("a");
7 | $(".donation").fadeOut(200);
8 | });
9 | $(document).on("submit", "#donation-form", function () {
10 | if ($(".donation-modal--body--input").val() < 1) {
11 | alert("1円以上で入力してください");
12 | return;
13 | } else if ($(".donation-modal--body--input").val() > 999999) {
14 | alert("100万円以下で入力してください");
15 | return;
16 | }
17 | $(".donation").fadeOut();
18 | });
19 |
20 | // いいねした人一覧を表示する機能。現在停止中(2020/10/25)
21 | // $(".tweet-show__content__like--link").on("click", function () {
22 | // $(".tweet-show__content__like--bgc").fadeToggle();
23 | // });
24 | // $(".tweet-show__content__like--close").click(function () {
25 | // $(".tweet-show__content__like--bgc").fadeToggle();
26 | // });
27 | });
28 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/modules/_tweet-tags.scss:
--------------------------------------------------------------------------------
1 |
2 | .tags{
3 | margin: 4px;
4 | }
5 |
6 | .tweet-tags{
7 | @include common-sizing;
8 | margin: 30px auto;
9 | border: 1px solid $header_border;
10 | background-color: white;
11 | box-shadow: 0px 4px 6px #ccc;
12 | &__title{
13 | height: 70px;
14 | text-align: center;
15 | line-height: 70px;
16 | color: $header_tags;
17 | border-bottom: 1px solid $header_border;
18 | // display: flex;
19 | &--body{
20 | font-size: 19px;
21 | &:hover{
22 | color: black;
23 | }
24 | }
25 | &__left{
26 | // padding-right: 260px;
27 | // padding-left: 20px;
28 | }
29 | }
30 | &__items{
31 | display: flex;
32 | flex-wrap: wrap;
33 | }
34 | &__item{
35 | display: inline-block;
36 | // width: 33.3%;
37 | padding: 30px;
38 | &--btn{
39 | // width: 60%;
40 | }
41 | &--link{
42 | display: flex;
43 | }
44 | &--body{
45 | color: black;
46 | padding: 8px;
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/app/controllers/notifications_controller.rb:
--------------------------------------------------------------------------------
1 | class NotificationsController < ApplicationController
2 | def index
3 | @notifications = current_user.passive_notifications.includes(%i[visitor tweet room visited])
4 | .page(params[:page]).per(20).where.not(visitor_id: current_user.id)
5 | @notifications.where(checked: false).each do |notification|
6 | notification.update_attributes(checked: true)
7 | end
8 | @activities = current_user.active_notifications.includes(%i[visitor tweet room visited]).page(params[:page]).per(20)
9 | end
10 |
11 | # bulletを無効にするメソッド---------------------------------------------------------------------------------------
12 | around_action :skip_bullet, if: -> { defined?(Bullet) }
13 |
14 | def skip_bullet
15 | previous_value = Bullet.enable?
16 | Bullet.enable = false
17 | yield
18 | ensure
19 | Bullet.enable = previous_value
20 | end
21 | # -------------------------------------------------------------------------------------------------------------------------
22 | end
23 |
--------------------------------------------------------------------------------
/app/views/notifications/index.html.haml:
--------------------------------------------------------------------------------
1 | .wrapper
2 | .container
3 | .notification-top
4 | .notification-top__title
5 | .notification-top__title__left.notification-top__title--show
6 | %h2.notification-top__title--name
7 | 通知
8 | .notification-top__title__right.notification-top__title--down
9 | %h2.notification-top__title--name 自分の活動
10 | %ul.notification-top__items
11 | - if @notifications.exists?
12 | = render @notifications
13 | = paginate @notifications
14 | - else
15 | %p.text-center.notification-top__items--none
16 | 通知はありません
17 | %ul.notification-top__activities
18 | - if @activities.exists?
19 | -# 同ページで2回renderすると、正しく読み込まれないためeach文を使用しています
20 | -# = render @activities
21 | - @activities.each do |activity|
22 | = render "activities/activity", activity: activity
23 | = paginate @activities
24 | - else
25 | %p.text-center.notification-top__items--none
26 | 活動はありません
27 |
--------------------------------------------------------------------------------
/app/views/tweets/_tweet.html.haml:
--------------------------------------------------------------------------------
1 | %li.tweet-index__item
2 | .tweet-index__item__user
3 | %span
4 | = link_to user_path(tweet.user.id), class: "avatar--text" do
5 | = image_tag "#{tweet.user.avatar}", class: "avatar"
6 | .tweet-index__item__user--nickname
7 | = tweet.user.nickname
8 | = link_to tweet_path(tweet.id) do
9 | .tweet-index__item--sub
10 | = tweet.created_at.strftime("%Y-%m-%d")
11 | = link_to tweet_path(tweet.id) do
12 | %p.tweet-index__item--title
13 | = tweet.title
14 | - if tweet.image?
15 | = image_tag "#{tweet.image}", class: "tweet-index__item--image"
16 | - else
17 | = image_tag asset_path("default.png"), class: "tweet-index__item--image"
18 | .tweet-index__item--tags
19 | = render '/tweets/tag_list', tag_list: tweet.tag_list
20 | .tweet-index__item__footer
21 | .tweet-index__item--like
22 | %div{id: "like_#{tweet.id}"}
23 | = render 'likes/like', tweet: tweet
24 | = link_to "もっと見る", tweet_path(tweet.id), class: "tweet-index__item--show"
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/controllers/rooms_controller.rb:
--------------------------------------------------------------------------------
1 | class RoomsController < ApplicationController
2 | def index
3 | @rooms = current_user.rooms.includes(:messages).order("messages.created_at desc")
4 | end
5 |
6 | def create
7 | @room = Room.create
8 | @join_current_user = Entry.create(user_id: current_user.id, room_id: @room.id)
9 | @join_user = Entry.create(join_room_params)
10 | @first_message = @room.messages.create(user_id: current_user.id, message: "初めまして!")
11 | redirect_to room_path(@room.id)
12 | end
13 |
14 | def show
15 | @room = Room.find(params[:id])
16 | if Entry.where(user_id: current_user.id, room_id: @room.id).present?
17 | @messages = @room.messages.includes(:user).order("created_at asc")
18 | @message = Message.new
19 | @entries = @room.entries.includes(:user)
20 | else
21 | flash[:alert] = "不正な操作です"
22 | redirect_back(fallback_location: root_path)
23 | end
24 | end
25 |
26 | private
27 |
28 | def join_room_params
29 | params.require(:entry).permit(:user_id, :room_id).merge(room_id: @room.id)
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/app/views/devise/shared/_links.html.haml:
--------------------------------------------------------------------------------
1 | - if controller_name != 'sessions'
2 | = link_to "Log in", new_session_path(resource_name)
3 | %br/
4 | - if devise_mapping.registerable? && controller_name != 'registrations'
5 | = link_to "Sign up", new_registration_path(resource_name)
6 | %br/
7 | - if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations'
8 | = link_to "Forgot your password?", new_password_path(resource_name)
9 | %br/
10 | - if devise_mapping.confirmable? && controller_name != 'confirmations'
11 | = link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name)
12 | %br/
13 | - if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks'
14 | = link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name)
15 | %br/
16 | - if devise_mapping.omniauthable?
17 | - resource_class.omniauth_providers.each do |provider|
18 | = link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider)
19 | %br/
20 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'fileutils'
3 | include FileUtils
4 |
5 | # path to your application root.
6 | APP_ROOT = File.expand_path('..', __dir__)
7 |
8 | def system!(*args)
9 | system(*args) || abort("\n== Command #{args} failed ==")
10 | end
11 |
12 | chdir APP_ROOT do
13 | # This script is a starting point to setup your application.
14 | # Add necessary setup steps to this file.
15 |
16 | puts '== Installing dependencies =='
17 | system! 'gem install bundler --conservative'
18 | system('bundle check') || system!('bundle install')
19 |
20 | # Install JavaScript dependencies if using Yarn
21 | # system('bin/yarn')
22 |
23 | # puts "\n== Copying sample files =="
24 | # unless File.exist?('config/database.yml')
25 | # cp 'config/database.yml.sample', 'config/database.yml'
26 | # end
27 |
28 | puts "\n== Preparing database =="
29 | system! 'bin/rails db:setup'
30 |
31 | puts "\n== Removing old logs and tempfiles =="
32 | system! 'bin/rails log:clear tmp:clear'
33 |
34 | puts "\n== Restarting application server =="
35 | system! 'bin/rails restart'
36 | end
37 |
--------------------------------------------------------------------------------
/app/controllers/comments_controller.rb:
--------------------------------------------------------------------------------
1 | class CommentsController < ApplicationController
2 | before_action :set_tweet, only: %i[create edit update destroy]
3 | before_action :set_comment, only: %i[edit update destroy]
4 |
5 | def create
6 | @comment = @tweet.comments.new(comment_params)
7 | if @comment.save
8 | @tweet.create_notification_comment!(current_user, @comment.id)
9 | @comment = Comment.new
10 | end
11 | gets_all_comments
12 | end
13 |
14 | def edit; end
15 |
16 | def update
17 | gets_all_comments if @comment.update(comment_params)
18 | end
19 |
20 | def destroy
21 | gets_all_comments if @comment.destroy
22 | end
23 |
24 | private
25 |
26 | def set_tweet
27 | @tweet = Tweet.find(params[:tweet_id])
28 | end
29 |
30 | def set_comment
31 | @comment = Comment.find(params[:id])
32 | end
33 |
34 | def gets_all_comments
35 | @comments = @tweet.comments.includes(:user).order('created_at desc')
36 | end
37 |
38 | def comment_params
39 | params.require(:comment).permit(:content).merge(user_id: current_user.id)
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "2"
2 | services:
3 | app:
4 | build:
5 | context: .
6 | dockerfile: ./docker/rails/Dockerfile
7 | command: bundle exec puma -C config/puma.rb
8 | ports:
9 | - "3000:3000"
10 | volumes:
11 | - .:/app
12 | - /var/tmp
13 | # socketファイルの共有
14 | - sockets:/app/tmp/sockets
15 | tty: true
16 | depends_on:
17 | - db
18 | extends:
19 | file: ./docker/mysql/password.yml
20 | service: password
21 | environment:
22 | - EDITOR=vim
23 | db:
24 | build:
25 | context: .
26 | dockerfile: ./docker/mysql/Dockerfile
27 | ports:
28 | - "3306:3306"
29 | volumes:
30 | - db_data:/var/lib/mysql
31 | extends:
32 | file: ./docker/mysql/password.yml
33 | service: password
34 | nginx:
35 | build:
36 | context: .
37 | dockerfile: ./docker/nginx/Dockerfile
38 | ports:
39 | - "80:80"
40 | #socketファイルの共有
41 | volumes:
42 | - sockets:/app/tmp/sockets
43 | depends_on:
44 | - app
45 |
46 | volumes:
47 | db_data:
48 | sockets:
49 |
--------------------------------------------------------------------------------
/app/assets/javascripts/notification.js:
--------------------------------------------------------------------------------
1 | $(function () {
2 | // それぞれの変数を定義
3 | var nt_left = $(".notification-top__title__left");
4 | var nt_right = $(".notification-top__title__right");
5 | var nt_items = $(".notification-top__items");
6 | var nt_activities = $(".notification-top__activities");
7 |
8 | // 左側(通知欄)をクリックしたら、左側に適切なクラスを除外&追加する。同時に右側に適切なクラスを除外&追加。
9 | nt_left.click(function () {
10 | nt_items.fadeIn(0);
11 | nt_activities.fadeOut(0);
12 | nt_left.addClass("notification-top__title--show");
13 | nt_left.removeClass("notification-top__title--down");
14 | nt_right.removeClass("notification-top__title--show");
15 | nt_right.addClass("notification-top__title--down");
16 | });
17 |
18 | // 右側(自分の行動)をクリックしたら、右側に適切なクラスを除外&追加する。同時に左側に適切なクラスを除外&追加。
19 | nt_right.click(function () {
20 | nt_items.fadeOut(0);
21 | nt_activities.fadeIn(0);
22 | nt_right.addClass("notification-top__title--show");
23 | nt_right.removeClass("notification-top__title--down");
24 | nt_left.removeClass("notification-top__title--show");
25 | nt_left.addClass("notification-top__title--down");
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/views/devise/registrations/new.html.haml:
--------------------------------------------------------------------------------
1 | .wrapper
2 | .login
3 | .login__field
4 | .center
5 | = render 'devise/sessions/testuser'
6 | = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
7 | = devise_error_messages!
8 | .login__title
9 | 新規登録
10 | = render "users/form", f: f
11 | .login__field
12 | .center
13 | = f.submit "登録する", class: "btn btn-success"
14 |
15 | :javascript
16 | $(function(){
17 | $fileField = $('#file')
18 |
19 | // 選択された画像を取得し表示
20 | $($fileField).on('change', $fileField, function(e) {
21 | file = e.target.files[0]
22 | reader = new FileReader(),
23 | $preview = $("#img_field");
24 |
25 | reader.onload = (function(file) {
26 | return function(e) {
27 | $preview.empty();
28 | $preview.append($('
').attr({
29 | src: e.target.result,
30 | class: "avatar",
31 | title: file.name
32 | }));
33 | };
34 | })(file);
35 | reader.readAsDataURL(file);
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/app/controllers/messages_controller.rb:
--------------------------------------------------------------------------------
1 | class MessagesController < ApplicationController
2 | before_action :set_room, only: %i[create edit update destroy]
3 | before_action :set_message, only: %i[edit update destroy]
4 |
5 | def create
6 | @message = Message.create(message_params)
7 | if @message.save
8 | @room.create_notification_message!(current_user, @message.id)
9 | @message = Message.new
10 | end
11 | gets_entries_all_messages
12 | end
13 |
14 | def edit; end
15 |
16 | def update
17 | gets_entries_all_messages if @message.update(message_params)
18 | end
19 |
20 | def destroy
21 | gets_entries_all_messages if @message.destroy
22 | end
23 |
24 | private
25 |
26 | def set_room
27 | @room = Room.find(params[:message][:room_id])
28 | end
29 |
30 | def set_message
31 | @message = Message.find(params[:id])
32 | end
33 |
34 | def gets_entries_all_messages
35 | @messages = @room.messages.includes(:user).order("created_at asc")
36 | @entries = @room.entries
37 | end
38 |
39 | def message_params
40 | params.require(:message).permit(:user_id, :message, :room_id).merge(user_id: current_user.id)
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/modules/_donation.scss:
--------------------------------------------------------------------------------
1 | .donation{
2 | display: none;
3 | position: fixed;
4 | top: 0;
5 | right: 0;
6 | bottom: 0;
7 | left: 0;
8 | background-color: rgba(0,0,0,.5);
9 | opacity: 1;
10 | &-modal{
11 | border: 1px solid $header_border;
12 | position: fixed;
13 | z-index: 3;
14 | top: 30%;
15 | background-color: white;
16 | padding: 30px;
17 | border-radius: 10px;
18 |
19 | .form{
20 | display: flex;
21 | justify-content: space-between;
22 | }
23 | &--header{
24 | height: 50px;
25 | border-bottom: 1px solid $header_border;
26 | font-size: 18px;
27 | text-align: center;
28 | line-height: 50px;
29 | }
30 | &--close{
31 | display: flex;
32 | justify-content: flex-end;
33 | }
34 | &--body{
35 | padding: 20px 0;
36 | display: flex;
37 | justify-content: center;
38 | &--input{
39 | width: 300px;
40 | padding: 20px 20px;
41 | text-align: center;
42 | border-radius: 10px;
43 | border: 1px solid #e1e1e1;
44 | }
45 | }
46 | &--submit{
47 | display: flex;
48 | justify-content: center;
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/config/initializers/content_security_policy.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Define an application-wide content security policy
4 | # For further information see the following documentation
5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
6 |
7 | # Rails.application.config.content_security_policy do |policy|
8 | # policy.default_src :self, :https
9 | # policy.font_src :self, :https, :data
10 | # policy.img_src :self, :https, :data
11 | # policy.object_src :none
12 | # policy.script_src :self, :https
13 | # policy.style_src :self, :https
14 |
15 | # # Specify URI for violation reports
16 | # # policy.report_uri "/csp-violation-report-endpoint"
17 | # end
18 |
19 | # If you are using UJS then enable automatic nonce generation
20 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
21 |
22 | # Report CSP violations to a specified URI
23 | # For further information see the following documentation:
24 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
25 | # Rails.application.config.content_security_policy_report_only = true
26 |
--------------------------------------------------------------------------------
/config/storage.yml:
--------------------------------------------------------------------------------
1 | test:
2 | service: Disk
3 | root: <%= Rails.root.join("tmp/storage") %>
4 |
5 | local:
6 | service: Disk
7 | root: <%= Rails.root.join("storage") %>
8 |
9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
10 | # amazon:
11 | # service: S3
12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
14 | # region: us-east-1
15 | # bucket: your_own_bucket
16 |
17 | # Remember not to checkin your GCS keyfile to a repository
18 | # google:
19 | # service: GCS
20 | # project: your_project
21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
22 | # bucket: your_own_bucket
23 |
24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
25 | # microsoft:
26 | # service: AzureStorage
27 | # storage_account_name: your_account_name
28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
29 | # container: your_container_name
30 |
31 | # mirror:
32 | # service: Mirror
33 | # primary: local
34 | # mirrors: [ amazon, google, microsoft ]
35 |
--------------------------------------------------------------------------------
/app/views/users/_info.html.haml:
--------------------------------------------------------------------------------
1 | .wrapper
2 | .user-show
3 | .user-show__top
4 | .user-show__top--icon
5 | = image_tag "#{@user.avatar}", class: "user-show__top--avatar"
6 | - if user_signed_in? && current_user == @user
7 | .user-show__top__body
8 | .user-show__top--follow
9 | = link_to 'ユーザー編集', edit_user_path(current_user.id), class: "user-show__top--btn"
10 | .user-show__top--contact
11 | = link_to 'ログアウト', destroy_user_session_path, method: :delete, class: "user-show__top--btn"
12 | - else
13 | .user-show__top__body
14 | .user-show__top--follow
15 | = render "/relationships/follow"
16 | .user-show__top--contact
17 | = render "/users/contact"
18 | .user-show__bottom
19 | .user-show__bottom--name
20 | %strong
21 | = @user.nickname
22 | .user-show__bottom__follow
23 | .user-show__bottom__follow--people
24 | = link_to following_user_path(@user) do
25 | = @user.following.count
26 | フォロー
27 | .user-show__bottom__follow--people
28 | = link_to followers_user_path(@user) do
29 | = @user.followers.count
30 | フォロワー
--------------------------------------------------------------------------------
/app/assets/stylesheets/modules/_new-btn.scss:
--------------------------------------------------------------------------------
1 | @mixin new-btn($color) {
2 | right: 5%;
3 | width: 140px;
4 | height: 140px;
5 | border-radius: 50%;
6 | display: flex;
7 | flex-direction: column;
8 | position: fixed;
9 | bottom: 20px;
10 | background-color: $color;
11 | border: 1px solid #ccc;
12 | opacity: 1;
13 | box-shadow: 0px 4px 6px #ccc;
14 | transition: all 0.3s ease 0s;
15 | &:hover {
16 | color: #333;
17 | }
18 | }
19 |
20 | @mixin new-btn--text($top, $left) {
21 | top: $top;
22 | left: $left;
23 | font-weight: bold;
24 | position: absolute;
25 | font-size: 16px;
26 | text-align: center;
27 | }
28 |
29 | .new-btn {
30 | display: block;
31 | &__tweet {
32 | @include new-btn($new_green);
33 | &--text {
34 | @include new-btn--text($top: 25px, $left: 53px);
35 | & p {
36 | color: #333;
37 | }
38 | }
39 | &--icon {
40 | color: #333;
41 | position: absolute;
42 | left: 35%;
43 | top: 30%;
44 | font-size: 3rem;
45 | }
46 | }
47 | &__session {
48 | @include new-btn($login_yellow);
49 | &--text {
50 | @include new-btn--text($top: 55px, $left: 15px);
51 | & p {
52 | color: #333;
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/spec/models/donation_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Donation, type: :model do
4 | let(:donation) { create(:donation) }
5 | describe '#create' do
6 | context '保存できる場合' do
7 | it "全てのパラメーターが揃っていれば保存できる" do
8 | expect(donation).to be_valid
9 | end
10 |
11 | it "amountが1以上なら保存できる" do
12 | donation.amount = 1
13 | expect(donation).to be_valid
14 | end
15 | end
16 |
17 | context "保存できない場合" do
18 | it "user_idがnilの場合は保存できない" do
19 | donation.user_id = nil
20 | donation.valid?
21 | expect(donation.errors[:user]).to include("を入力してください")
22 | end
23 |
24 | it "tweet_idがnilの場合は保存できない" do
25 | donation.tweet_id = nil
26 | donation.valid?
27 | expect(donation.errors[:tweet]).to include("を入力してください")
28 | end
29 |
30 | it "amountがnilの場合は保存できない" do
31 | donation.amount = nil
32 | donation.valid?
33 | expect(donation.errors[:amount]).to include("を入力してください")
34 | end
35 |
36 | it "amountが0の場合は保存できない" do
37 | donation.amount = 0
38 | donation.valid?
39 | expect(donation.errors[:amount]).to include("は0より大きい値にしてください")
40 | end
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/modules/_room-index.scss:
--------------------------------------------------------------------------------
1 | .room-index-hover{
2 | &:hover{
3 | background-color: #e6e6e6;
4 | }
5 | }
6 |
7 | .room-index{
8 | @include common-sizing;
9 | margin: 0 auto;
10 | border: 1px solid $header_border;
11 | background-color: white;
12 | &__title{
13 | height: 100px;
14 | border-bottom: 1px solid $header_border;
15 | text-align: center;
16 | line-height: 100px;
17 | font-size: 25px;
18 | }
19 | &__items{
20 | &__item{
21 | border-top: 1px solid $header_border;
22 | height: 100px;
23 | // width: 798px;
24 | color: #333;
25 | position: relative;
26 | &--icon{
27 | height: 60px;
28 | width: 60px;
29 | border-radius: 100px;
30 | float: left;
31 | margin: 20px 20px;
32 | }
33 | &__content{
34 | display: block;
35 | padding-left: 90px;
36 | &--body{
37 | padding: 20px;
38 | }
39 | &:hover{
40 | color: #505050;
41 | }
42 | &--message{
43 | color: $header_tags;
44 | }
45 | &--date{
46 | color: $header_tags;
47 | position: absolute;
48 | bottom: 5px;
49 | right: 5px;
50 | }
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/assets/javascripts/preview.js:
--------------------------------------------------------------------------------
1 | $(function () {
2 | // previewボタンが押されたらイベント発火
3 | $("#preview").on("click", function () {
4 | var text = $("#md-textarea").val();
5 | if (text == "") {
6 | return false;
7 | } else if ($("#md-textarea").val().length > 1000) {
8 | alert("本文を1,000文字以内で入力してください。");
9 | return false;
10 | }
11 | // ajax通信を開始。
12 | $.ajax({
13 | url: "/api/tweets/preview", //apiフォルダのtweetsコントローラーのpreviewメソッドを参照
14 | type: "GET",
15 | dataType: "json",
16 | data: { text: text },
17 | })
18 | .done(function (html) {
19 | // ajax成功したら、テキストエリアを非表示にする。
20 | $("#md-textarea").parent().css("display", "none");
21 | $("#preview-area").show().empty().append(html.text);
22 | // markdownボタンとpreviewボタンのdisabledを入れ替える。
23 | $("#markdown").removeClass("disabled");
24 | $("#preview").addClass("disabled");
25 | })
26 | .fail(function () {
27 | alert("1,000文字以内で入力してください。");
28 | });
29 | });
30 |
31 | // markdownボタンが押されたらイベント発火
32 | $("#markdown").on("click", function () {
33 | $("#md-textarea").parent().css("display", "");
34 | $("#preview-area").hide();
35 | $("#preview").removeClass("disabled");
36 | $("#markdown").addClass("disabled");
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 |
3 | devise_for :users, :controllers => {
4 | sessions: 'users/sessions' # Devise::sessionsクラスを継承していることを明示
5 | }
6 | devise_scope :user do
7 | post 'users/guest_sign_in', to: 'users/sessions#new_guest' # ゲストログイン用メソッド
8 | end
9 |
10 | root to: 'tweets#index' # トップページ
11 | namespace :tweets do
12 | resources :searches, only: :index # 検索機能
13 | end
14 | resources :tweets do
15 | collection do
16 | get :likes, :tags # 人気投稿とタグ一覧
17 | end
18 | resources :donations, only: [:create, :destroy] # 寄付機能
19 | resources :likes, only: [:create, :destroy] # いいね機能
20 | resources :comments, only: [:create, :edit, :update, :destroy] # コメント機能
21 | end
22 |
23 | namespace :api, format: 'json' do
24 | get 'tweets/preview' # プレビュー用API。controllers/api/tweets_controller#previewメソッドを参照
25 | end
26 |
27 | resources :users, only: [:index, :show, :edit, :update] do
28 | member do
29 | get :likes, :following, :followers, :setting
30 | end
31 | get 'timeline', on: :collection
32 | end
33 | resources :relationships, only: [:create, :destroy]
34 | resources :messages, only: [:create, :edit, :update, :destroy]
35 | resources :rooms, only: [:index, :show, :create]
36 | resources :notifications, only: :index
37 |
38 | end
--------------------------------------------------------------------------------
/db/migrate/20200523235324_add_missing_unique_indices.acts_as_taggable_on_engine.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from acts_as_taggable_on_engine (originally 2)
2 | if ActiveRecord.gem_version >= Gem::Version.new('5.0')
3 | class AddMissingUniqueIndices < ActiveRecord::Migration[4.2]; end
4 | else
5 | class AddMissingUniqueIndices < ActiveRecord::Migration; end
6 | end
7 | AddMissingUniqueIndices.class_eval do
8 | def self.up
9 | # add_index ActsAsTaggableOn.tags_table, :name, unique: true
10 |
11 | # remove_index ActsAsTaggableOn.taggings_table, :tag_id if index_exists?(ActsAsTaggableOn.taggings_table, :tag_id)
12 | remove_index ActsAsTaggableOn.taggings_table, name: 'taggings_taggable_context_idx'
13 | add_index ActsAsTaggableOn.taggings_table,
14 | [:tag_id, :taggable_id, :taggable_type, :context, :tagger_id, :tagger_type],
15 | unique: true, name: 'taggings_idx'
16 | end
17 |
18 | def self.down
19 | remove_index ActsAsTaggableOn.tags_table, :name
20 |
21 | remove_index ActsAsTaggableOn.taggings_table, name: 'taggings_idx'
22 |
23 | add_index ActsAsTaggableOn.taggings_table, :tag_id unless index_exists?(ActsAsTaggableOn.taggings_table, :tag_id)
24 | add_index ActsAsTaggableOn.taggings_table, [:taggable_id, :taggable_type, :context], name: 'taggings_taggable_context_idx'
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/app/assets/javascripts/tweet_form.js:
--------------------------------------------------------------------------------
1 | $(function () {
2 | $fileField = $('#file')
3 |
4 | // 選択された画像を取得し表示
5 | $($fileField).on('change', $fileField, function (e) {
6 | file = e.target.files[0]
7 | reader = new FileReader(),
8 | $preview = $("#img_field");
9 |
10 | reader.onload = (function (file) {
11 | return function (e) {
12 | $preview.empty();
13 | $preview.append($('
').attr({
14 | src: e.target.result,
15 | width: "100%",
16 | class: "tweet-form__bonus__preview--image",
17 | title: file.name
18 | }));
19 | };
20 | })(file);
21 | reader.readAsDataURL(file);
22 | });
23 |
24 | $("#md-textarea").keyup(function () {
25 | $("#tweet-text-count").text($(this).val().length + '/1000');
26 | });
27 |
28 | if (document.location.href.match(/\/tweets\/\d+\/edit/)) {
29 | $("#tweet-text-count").text($("#md-textarea").val().length + '/1000');
30 | }
31 |
32 | $("#tweet-form").submit(function () {
33 | if ($("#md-textarea").val().length > 1000) {
34 | alert('本文を1,000文字以内で入力してください。');
35 | return false;
36 | }
37 | });
38 |
39 | // 実装予定のタグ検索機能です!
40 | // $("#tag-submit").on("click", function () {
41 | // let input = $("#tag-input").val();
42 | // window.location.href = `http://localhost:3000/tweets?tag_name=${input}`
43 | // });
44 |
45 | });
46 |
47 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/application.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any styles
10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11 | * file per style scope.
12 | *
13 | *= require_self
14 | *= require jquery.tagit
15 | *= require tagit.ui-zendesk
16 | */
17 |
18 | @import "config/colors";
19 | @import "reset";
20 | @import "mixin/media";
21 | @import "mixin/tweet-items";
22 | @import "modules/common";
23 | @import "modules/tweet-index";
24 | @import "modules/tweet-show";
25 | @import "modules/tweet-comment";
26 | @import "modules/tweet-tags";
27 | @import "modules/tweet-form";
28 | @import "modules/new-btn";
29 | @import "modules/user-show";
30 | @import "modules/user-index";
31 | @import "modules/flash";
32 | @import "modules/header";
33 | @import "modules/notifications";
34 | @import "modules/room-index";
35 | @import "modules/room-show";
36 | @import "modules/session";
37 | @import "modules/donation";
38 |
--------------------------------------------------------------------------------
/app/views/devise/registrations/edit.html.haml:
--------------------------------------------------------------------------------
1 | %h2
2 | -# Edit #{resource_name.to_s.humanize}
3 | = form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f|
4 | = render "devise/shared/error_messages", resource: resource
5 | .field
6 | = f.label :email
7 | %br/
8 | = f.email_field :email, autofocus: true, autocomplete: "email"
9 | - if devise_mapping.confirmable? && resource.pending_reconfirmation?
10 | %div
11 | Currently waiting confirmation for: #{resource.unconfirmed_email}
12 | .field
13 | = f.label :password
14 | %i (leave blank if you don't want to change it)
15 | %br/
16 | = f.password_field :password, autocomplete: "new-password"
17 | - if @minimum_password_length
18 | %br/
19 | %em
20 | = @minimum_password_length
21 | characters minimum
22 | .field
23 | = f.label :password_confirmation
24 | %br/
25 | = f.password_field :password_confirmation, autocomplete: "new-password"
26 | .field
27 | = f.label :current_password
28 | %i (we need your current password to confirm your changes)
29 | %br/
30 | = f.password_field :current_password, autocomplete: "current-password"
31 | .actions
32 | = f.submit "Update"
33 | %h3 Cancel my account
34 | %p
35 | Unhappy? #{button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete}
36 | = link_to "Back", :back
--------------------------------------------------------------------------------
/db/migrate/20200523235323_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from acts_as_taggable_on_engine (originally 1)
2 | if ActiveRecord.gem_version >= Gem::Version.new('5.0')
3 | class ActsAsTaggableOnMigration < ActiveRecord::Migration[4.2]; end
4 | else
5 | class ActsAsTaggableOnMigration < ActiveRecord::Migration; end
6 | end
7 | ActsAsTaggableOnMigration.class_eval do
8 | def self.up
9 | create_table ActsAsTaggableOn.tags_table do |t|
10 | t.string :name
11 | t.timestamps
12 | end
13 |
14 | create_table ActsAsTaggableOn.taggings_table do |t|
15 | t.references :tag, foreign_key: { to_table: ActsAsTaggableOn.tags_table }
16 |
17 | # You should make sure that the column created is
18 | # long enough to store the required class names.
19 | t.references :taggable, polymorphic: true
20 | t.references :tagger, polymorphic: true
21 |
22 | # Limit is created to prevent MySQL error on index
23 | # length for MyISAM table type: http://bit.ly/vgW2Ql
24 | t.string :context, limit: 20
25 |
26 | t.datetime :created_at
27 | end
28 |
29 | add_index ActsAsTaggableOn.taggings_table, :tag_id
30 | add_index ActsAsTaggableOn.taggings_table, [:taggable_id, :taggable_type, :context], name: 'taggings_taggable_context_idx'
31 | end
32 |
33 | def self.down
34 | drop_table ActsAsTaggableOn.taggings_table
35 | drop_table ActsAsTaggableOn.tags_table
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/config/database.yml:
--------------------------------------------------------------------------------
1 | default: &default
2 | adapter: mysql2
3 | encoding: utf8
4 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
5 |
6 | # ローカル環境(rails sで起動)
7 | development:
8 | <<: *default
9 | database: mynote_development
10 | username: root
11 | password:
12 | socket: /tmp/mysql.sock
13 | # docker環境(docker-compose upで起動) config/puma.rbも変更すること
14 | # development:
15 | # <<: *default
16 | # database: mynote_development
17 | # username: root
18 | # password: password
19 | # host: db
20 | #
21 | # test:
22 | # <<: *default
23 | # database: app_test
24 |
25 | # EC2からRDSに接続
26 | production:
27 | <<: *default
28 | adapter: mysql2
29 | charset: utf8
30 | database: <%= Rails.application.credentials.aws[:rds][:DATABASE_NAME] %>
31 | username: <%= Rails.application.credentials.aws[:rds][:DATABASE_USERNAME] %>
32 | password: <%= Rails.application.credentials.aws[:rds][:DATABASE_PASSWORD] %>
33 | host: <%= Rails.application.credentials.aws[:rds][:DATABASE_HOST] %>
34 | # EC2内のMySQLに接続
35 | # production:
36 | # <<: *default
37 | # database: my_app_production
38 | # username: root
39 | # password: password
40 | # socket: /var/lib/mysql/mysql.sock
41 |
42 | # ECSからRDSに接続
43 | # production:
44 | # <<: *default
45 | # database: <%= ENV['DB_DATABASE'] %>
46 | # adapter: mysql2
47 | # encoding: utf8mb4
48 | # charset: utf8mb4
49 | # collation: utf8mb4_general_ci
50 | # host: <%= ENV['DB_HOST'] %>
51 | # username: <%= ENV['DB_USERNAME'] %>
52 | # password: <%= ENV['DB_PASSWORD'] %>
53 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/modules/_common.scss:
--------------------------------------------------------------------------------
1 | .wrapper{
2 | height: 100%;
3 | width: 100%;
4 | background-color: #F5F5F5;
5 | margin-top: 5px;
6 | padding: 30px 0;
7 | }
8 |
9 | @mixin common-sizing{
10 | @include sm{
11 | width: 450px;
12 | }
13 | @include sm-max{
14 | width: 300px;
15 | }
16 | @include md{
17 | width: 650px;
18 | }
19 | @include lg{
20 | width: 800px;
21 | }
22 | }
23 |
24 | .jc-center{
25 | display: flex;
26 | justify-content: center;
27 | }
28 |
29 | .avatar {
30 | width: 39px;
31 | height: 39px;
32 | border-radius: 100px;
33 | object-fit: cover;
34 | padding-bottom: 5px;
35 | &--text:hover{
36 | color: #505050;
37 | }
38 | }
39 |
40 | .pagination {
41 | justify-content: center;
42 | }
43 |
44 | .active{
45 | display: block;
46 | }
47 |
48 | .hide{
49 | display: none;
50 | }
51 |
52 | .text-center{
53 | text-align: center;
54 | }
55 |
56 | .cursor-pointer{
57 | cursor: pointer;
58 | }
59 |
60 | .center{
61 | display: flex;
62 | justify-content: center;
63 | }
64 |
65 | .color-black .color-black a{
66 | color: #333;
67 | }
68 |
69 | .black-important{
70 | color: #333 !important;
71 | }
72 |
73 | .a-hover{
74 | color: black;
75 | &:hover{
76 | color: #555;
77 | }
78 | }
79 |
80 | .display-flex{
81 | display: flex;
82 | }
83 |
84 | .label-form{
85 | display: block;
86 | width: 100%;
87 | height: 100%;
88 | cursor: pointer;
89 | }
90 |
91 | .pdw-15{
92 | padding: 0 15px;
93 | }
94 |
95 | .mt05rem{
96 | margin-top: 0.5rem;
97 | }
--------------------------------------------------------------------------------
/db/migrate/20200523235328_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from acts_as_taggable_on_engine (originally 6)
2 | if ActiveRecord.gem_version >= Gem::Version.new('5.0')
3 | class AddMissingIndexesOnTaggings < ActiveRecord::Migration[4.2]; end
4 | else
5 | class AddMissingIndexesOnTaggings < ActiveRecord::Migration; end
6 | end
7 | AddMissingIndexesOnTaggings.class_eval do
8 | def change
9 | add_index ActsAsTaggableOn.taggings_table, :tag_id unless index_exists? ActsAsTaggableOn.taggings_table, :tag_id
10 | add_index ActsAsTaggableOn.taggings_table, :taggable_id unless index_exists? ActsAsTaggableOn.taggings_table, :taggable_id
11 | add_index ActsAsTaggableOn.taggings_table, :taggable_type unless index_exists? ActsAsTaggableOn.taggings_table, :taggable_type
12 | add_index ActsAsTaggableOn.taggings_table, :tagger_id unless index_exists? ActsAsTaggableOn.taggings_table, :tagger_id
13 | add_index ActsAsTaggableOn.taggings_table, :context unless index_exists? ActsAsTaggableOn.taggings_table, :context
14 |
15 | unless index_exists? ActsAsTaggableOn.taggings_table, [:tagger_id, :tagger_type]
16 | add_index ActsAsTaggableOn.taggings_table, [:tagger_id, :tagger_type]
17 | end
18 |
19 | unless index_exists? ActsAsTaggableOn.taggings_table, [:taggable_id, :taggable_type, :tagger_id, :context], name: 'taggings_idy'
20 | add_index ActsAsTaggableOn.taggings_table, [:taggable_id, :taggable_type, :tagger_id, :context], name: 'taggings_idy'
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/app/views/layouts/application.html.haml:
--------------------------------------------------------------------------------
1 | !!!
2 | %html
3 | %head
4 | %meta{content: "text/html; charset=UTF-8", "http-equiv": "Content-Type", name: "viewport", content:"width=device-width, initial-scale=1, shrink-to-fit=no"}
5 | %title MyNote
6 | = csrf_meta_tags
7 | = csp_meta_tag
8 | = include_gon
9 | = stylesheet_link_tag 'application', media: 'all'
10 | = javascript_include_tag 'application'
11 | %link{crossorigin: "anonymous", href: "https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css", integrity: "sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk", rel: "stylesheet"}
12 | %link{href: "https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css", rel: "stylesheet"}
13 | %body
14 | %script{crossorigin: "anonymous", integrity: "sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"}
15 | %script{crossorigin: "anonymous", integrity: "sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1", src: "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"}
16 | %script{crossorigin: "anonymous", integrity: "sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM", src: "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"}
17 |
18 | %script{crossorigin: "anonymous", src: "https://kit.fontawesome.com/b167c0ffa1.js"}
19 | %script{src: "https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"}
20 |
21 | = render 'layouts/flash'
22 | = render 'layouts/header'
23 | = yield
--------------------------------------------------------------------------------
/app/views/rooms/_chat_area.html.haml:
--------------------------------------------------------------------------------
1 | .wrapper
2 | .room-show
3 | .room-show__title
4 | - @entries.each do |e|
5 | - if e.user.id != current_user.id
6 | = link_to user_path(e.user.id) do
7 | = image_tag "#{e.user.avatar}", class:"avatar"
8 | = e.user.nickname
9 | さんとのメッセージ
10 | .room-show__items
11 | - if @messages.present?
12 | - @messages.each do |m|
13 | %div.room-show__item{id: "message_#{m.id}"}
14 | -# 右側にくるように
15 | - if current_user_has?(m)
16 | .room-show__item__right
17 | = form_with(model: @message, url: message_path(m.id), class: "room-show__item__right--btn", remote: true, method: :delete, data: { confirm: '削除してよろしいですか?' }) do |f|
18 | = f.hidden_field :room_id, value: @room.id
19 | = f.submit "削除", class: "btn rounded-pill"
20 | %span.room-show__item--message
21 | = m.message
22 | .room-show__item__right
23 | .room-show__item--date
24 | = m.created_at.strftime("%Y-%m-%d %H:%M")
25 | -# 左側に来る処理
26 | - else
27 | .room-show__item__left
28 | = link_to user_path(m.user.avatar) do
29 | = image_tag "#{m.user.avatar}", class:"avatar mt05rem"
30 | %span.room-show__item--message
31 | = m.message
32 | .room-show__item__left
33 | .room-show__item--date
34 | = m.created_at.strftime("%Y-%m-%d %H:%M")
--------------------------------------------------------------------------------
/app/views/relationships/_follow.html.haml:
--------------------------------------------------------------------------------
1 | - if user_signed_in? && @user.id != current_user.id
2 | #follow_form
3 | - if current_user.following?(@user)
4 | = form_with(model: current_user, url: relationship_path(@user.id), method: :delete, remote: true) do |f|
5 | = f.submit "フォロー解除", class: "btn btn-primary user-show__top--contact--btn"
6 | - else
7 | = form_with(model: current_user, url: relationships_path, method: :post, remote: true) do |f|
8 | = hidden_field_tag :following_id, @user.id
9 | = f.submit "フォローする", class: "btn btn-outline-primary user-show__top--contact--btn"
10 |
11 | -# relationship DELETE /relationships/:id(.:format) relationships#destroy
12 | -# relationships POST /relationships(.:format) relationships#create
13 |
14 | -# - unless current_user == user
15 | -# - if current_user.following?(user)
16 | -# = link_to "フォローを外す", relationship_path(follow_id: user.id), method: :delete
17 | -# - else
18 | -# = link_to "フォローする", relationships_path, method: :post
19 |
20 | -# - if user_signed_in? && @user != current_user
21 | -# #follow_form
22 | -# - if current_user.following?(@user)
23 | -# = form_for(current_user, url: relationship_path(@user), method: :delete, remote: true) do |f|
24 | -# = f.submit "フォロー解除", class: "btn btn-outline-secondary"
25 | -# - else
26 | -# = form_for(current_user, url: relationships_path, method: :post, remote: true) do |f|
27 | -# = hidden_field_tag :following_id, @user.id
28 | -# = f.submit "フォローする", class: "btn btn-outline-secondary"
--------------------------------------------------------------------------------
/db/migrate/20200515005429_devise_create_users.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class DeviseCreateUsers < ActiveRecord::Migration[5.2]
4 | def change
5 | create_table :users do |t|
6 | ## Database authenticatable
7 | t.string :email, null: false, default: ""
8 | t.string :encrypted_password, null: false, default: ""
9 |
10 | ## Recoverable
11 | t.string :reset_password_token
12 | t.datetime :reset_password_sent_at
13 |
14 | ## Rememberable
15 | t.datetime :remember_created_at
16 |
17 | ## Trackable
18 | # t.integer :sign_in_count, default: 0, null: false
19 | # t.datetime :current_sign_in_at
20 | # t.datetime :last_sign_in_at
21 | # t.string :current_sign_in_ip
22 | # t.string :last_sign_in_ip
23 |
24 | ## Confirmable
25 | # t.string :confirmation_token
26 | # t.datetime :confirmed_at
27 | # t.datetime :confirmation_sent_at
28 | # t.string :unconfirmed_email # Only if using reconfirmable
29 |
30 | ## Lockable
31 | # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
32 | # t.string :unlock_token # Only if unlock strategy is :email or :both
33 | # t.datetime :locked_at
34 |
35 |
36 | t.timestamps null: false
37 | end
38 |
39 | add_index :users, :email, unique: true
40 | add_index :users, :reset_password_token, unique: true
41 | # add_index :users, :confirmation_token, unique: true
42 | # add_index :users, :unlock_token, unique: true
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/app/views/tweets/_form.html.haml:
--------------------------------------------------------------------------------
1 | .wrapper
2 | = form_with(model: @tweet, local: true, id: "tweet-form") do |f|
3 | .tweet-form
4 | .tweet-form__title
5 | = f.text_field :title, class: "tweet-form__title--body", placeholder: "タイトルを入力して下さい(30文字以内)", required: "require"
6 | .tweet-form__text__btn
7 | .btn.btn-secondary.rounded-pill#markdown.disabled 編集
8 | .btn.btn-secondary.rounded-pill#preview プレビュー
9 | .tweet-form__text
10 | #preview-area
11 | %div
12 | = f.label "0/1000", for: "md-textarea", id: "tweet-text-count"
13 | -# #tweet-text-count 0文字
14 | = f.text_area :text, class: "tweet-form__text--body", placeholder: "本文を入力して下さい(マークダウン対応)", id: "md-textarea", required: "require"
15 | %br
16 | .tweet-form--tags
17 | タグを追加
18 | %div
19 | %ul#tweet-tags
20 | .tweet-form__bonus__preview--title
21 | イメージ写真
22 | .tweet-form__bonus__preview
23 | .label-form
24 | .field.image
25 | // id "file"で、fileとdivを紐付けクリック時に連動
26 | #img_field{:onclick => "$('#file').click()"}
27 | // 画像があるときは画像を表示する
28 | - if @tweet.image.present?
29 | = image_tag "#{@tweet.image.url}", class: "tweet-form__bonus__preview--image"
30 | -# = image_tag(@tweet.image.url)
31 | - else
32 | %i.fas.fa-camera.fa-6x.tweet-form__bonus__preview--icon
33 | // id "file"をつけ、「display:none;」で隠す
34 | = f.file_field :image, class: "image", style: "display:none;", id: "file"
35 | .tweet-form__submit
36 | = f.submit '投稿する', class: "btn btn-success"
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | inherit_from: .rubocop_todo.yml
2 |
3 | AllCops:
4 | # 除外するディレクトリ(自動生成されたファイル)
5 | # デフォルト設定にある"vendor/**/*"が無効化されないように記述
6 | Exclude:
7 | - "bin/*"
8 | - "lib/*"
9 | - "node_modules/**/*"
10 | - "db/**/*"
11 | - "vendor/**/*"
12 | - "config/**/*"
13 | - "script/**/*"
14 | - "test/**"
15 | - "docker/rails/puma.rb" # app_rootのディレクトリを変えてはいけないため
16 | # Rails向けのRails copsを実行。"rubocop -R"と同じ
17 | Rails:
18 | enabled: true
19 |
20 | # "Missing top-level class documentation comment."を無効
21 | Style/Documentation:
22 | Enabled: false
23 |
24 | # コメントにはアスキー記号のみを使用してください
25 | AsciiComments:
26 | Enabled: false
27 |
28 | # "Prefer single-quoted strings when you don't need string interpolation or special symbols."を無効
29 | Style/StringLiterals:
30 | Enabled: false
31 |
32 | # "Line is too long"を無効
33 | Metrics/LineLength:
34 | Enabled: false
35 |
36 | # join_roomの記述が引っかかる為。省略は難しい
37 | Etrics/MethodLength:
38 | Enabled: true
39 | Max: 18
40 |
41 | #'frozen_string_literal: true'を無効
42 | Style/FrozenStringLiteralComment:
43 | Enabled: false
44 |
45 | # tweet_searchやtestのnamespaceが引っかかるため
46 | Style/ClassAndModuleChildren:
47 | Enabled: false
48 |
49 | # 線が多い長いブロックは避けてください
50 | Metrics/BlockLength:
51 | Exclude:
52 | - "spec/**/*"
53 |
54 | # NotificationsControllerが22文字の為
55 | Metrics/AbcSize:
56 | Enabled: true
57 | Max: 23
58 |
59 | # if~else~endの方がひと目で分かりやすいと思うためfalse。詳細は以下を参照
60 | # https://docs.rubocop.org/rubocop/cops_style.html#styleguardclause
61 | Style/GuardClause:
62 | Enabled: false
63 |
64 | # devise_helperの関係のため
65 | Naming/HeredocDelimiterNaming:
66 | Enabled: false
67 |
68 | # テストコードを追加したら、tweetなどの変数名が規約違反で引っかかるため無効
69 | Lint/UselessAssignment:
70 | Enabled: false
71 |
--------------------------------------------------------------------------------
/vendor/assets/stylesheets/jquery.tagit.scss:
--------------------------------------------------------------------------------
1 | .ui-helper-hidden-accessible {
2 | display: none;
3 | }
4 |
5 | ul.tagit {
6 | padding: 1px 5px;
7 | overflow: auto;
8 | margin-left: inherit; /* usually we don't want the regular ul margins. */
9 | margin-right: inherit;
10 | }
11 | ul.tagit li {
12 | display: block;
13 | float: left;
14 | margin: 2px 5px 2px 0;
15 | }
16 | ul.tagit li.tagit-choice {
17 | position: relative;
18 | line-height: inherit;
19 | }
20 | input.tagit-hidden-field {
21 | display: none;
22 | }
23 | ul.tagit li.tagit-choice-read-only {
24 | padding: .2em .5em .2em .5em;
25 | }
26 |
27 | ul.tagit li.tagit-choice-editable {
28 | padding: .2em 18px .2em .5em;
29 | // padding: .375rem .75rem;
30 | &:hover{
31 | color: white;
32 | }
33 | }
34 |
35 | ul.tagit li.tagit-new {
36 | padding: .25em 4px .25em 0;
37 | }
38 |
39 | ul.tagit li.tagit-choice a.tagit-label {
40 | cursor: pointer;
41 | text-decoration: none;
42 | }
43 | ul.tagit li.tagit-choice .tagit-close {
44 | cursor: pointer;
45 | position: absolute;
46 | right: .1em;
47 | top: 50%;
48 | margin-top: -8px;
49 | line-height: 17px;
50 | }
51 |
52 | /* used for some custom themes that don't need image icons */
53 | ul.tagit li.tagit-choice .tagit-close .text-icon {
54 | display: none;
55 | }
56 |
57 | ul.tagit li.tagit-choice input {
58 | display: block;
59 | float: left;
60 | margin: 2px 5px 2px 0;
61 | }
62 | ul.tagit input[type="text"] {
63 | -moz-box-sizing: border-box;
64 | -webkit-box-sizing: border-box;
65 | box-sizing: border-box;
66 |
67 | -moz-box-shadow: none;
68 | -webkit-box-shadow: none;
69 | box-shadow: none;
70 |
71 | border: none;
72 | margin: 0;
73 | padding: 0;
74 | width: inherit;
75 | background-color: inherit;
76 | outline: none;
77 | }
78 |
79 |
--------------------------------------------------------------------------------
/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/spec/models/like_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Like, type: :model do
4 | describe '正常値と異常値の確認' do
5 | context 'likeモデルのバリデーション' do
6 | it "user_idとtweet_idがあれば保存できる" do
7 | # expect(FactoryBot.build_stubbed(:like)).to be_valid
8 | expect(FactoryBot.create(:like)).to be_valid
9 | end
10 |
11 | it "user_idがなければ無効な状態であること" do
12 | like = build(:like, user_id: nil)
13 | like.valid?
14 | expect(like.errors[:user_id]).to include("を入力してください")
15 | end
16 |
17 | it "tweet_idがなければ無効な状態であること" do
18 | like = build(:like, tweet_id: nil)
19 | like.valid?
20 | expect(like.errors[:tweet_id]).to include("を入力してください")
21 | end
22 |
23 | it "tweet_idが同じでもuser_idが違うと保存できる" do
24 | like = create(:like)
25 | expect(FactoryBot.create(:like, tweet_id: like.tweet_id)).to be_valid
26 | end
27 |
28 | it "user_idが同じでもtweet_idが違うと保存できる" do
29 | like = create(:like)
30 | expect(FactoryBot.create(:like, user_id: like.user_id)).to be_valid
31 | end
32 |
33 | it "tweet_idとuser_idは一意でなければ保存できない" do
34 | like = create(:like)
35 | like2 = build(:like, tweet_id: like.tweet_id, user_id: like.user_id)
36 | like2.valid?
37 | expect(like2.errors[:tweet_id]).to include("はすでに存在します")
38 | end
39 | end
40 | end
41 |
42 | describe "各モデルとのアソシエーション" do
43 | let(:association) do
44 | described_class.reflect_on_association(target)
45 | end
46 |
47 | context "Userモデルとのアソシエーション" do
48 | let(:target) { :user }
49 |
50 | it "Userとの関連付けはbelongs_toであること" do
51 | expect(association.macro).to eq :belongs_to
52 | end
53 | end
54 |
55 | context "Tweetモデルとのアソシエーション" do
56 | let(:target) { :tweet }
57 |
58 | it "Tweetとの関連付けはbelongs_toであること" do
59 | expect(association.macro).to eq :belongs_to
60 | end
61 | end
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/modules/_notifications.scss:
--------------------------------------------------------------------------------
1 | .notification-top{
2 | @include common-sizing;
3 | margin: 0 auto;
4 | border: 1px solid $header_border;
5 | background-color: white;
6 | cursor: pointer;
7 | &__activities{
8 | display: none;
9 | }
10 | &__title{
11 | height: 100px;
12 | width: 100%;
13 | display: flex;
14 | font-size: 20px;
15 | &--show{
16 | background-color: white;
17 | border-top:2px solid red;
18 | }
19 | &--down{
20 | background-color: #EEEEEE;
21 | }
22 | &--name{
23 | text-align: center;
24 | line-height: 100px;
25 | display: block;
26 | font-size: 25px;
27 | &:hover{
28 | background-color: #f7f7f7;
29 | color: #505050;
30 | }
31 | }
32 | &__left{
33 | width: 50%;
34 | }
35 | &__right{
36 | width: 50%;
37 | }
38 | }
39 | &__items{
40 | display: block;
41 | &--none{
42 | height: 100px;
43 | line-height: 100px;
44 | border-bottom: 1px solid $header_border;
45 | }
46 | &__item{
47 | display: block;
48 | height: 100px;
49 | border-bottom: 1px solid $header_border;
50 | padding: 20px;
51 | color: #333;
52 | position: relative;
53 | &:hover{
54 | background-color: #fafafa;
55 | color: #505050;
56 | }
57 | &--icon{
58 | padding-top: 14px;
59 | height: 60px;
60 | width: 60px;
61 | }
62 | &--content{
63 | position: absolute;
64 | top: 34px;
65 | left: 80px;
66 | }
67 | &--message{
68 | position: absolute;
69 | top: 62px;
70 | left: 125px;
71 | color: $header_tags;
72 | @include md-max{
73 | display: none;
74 | }
75 | }
76 | &--date{
77 | position: absolute;
78 | right: 5px;
79 | bottom: 5px;
80 | color: $header_tags;
81 | }
82 | }
83 | }
84 | }
--------------------------------------------------------------------------------
/app/assets/stylesheets/modules/_tweet-comment.scss:
--------------------------------------------------------------------------------
1 | .tweet-show__comment{
2 | border-top: 1px solid $header_border;
3 | @include md{
4 | padding: 20px 40px;
5 | }
6 | @include md-max{
7 | padding: 20px 0px;
8 | }
9 | display: flex;
10 | flex-direction: column;
11 | &--text{
12 | text-align: center;
13 | }
14 | &__form{
15 | display: flex;
16 | flex-direction: column;
17 | &__top {
18 | margin-top: 20px;
19 | display: flex;
20 | justify-content: center;
21 | }
22 | &--title{
23 | padding: 10px 10px;
24 | }
25 | &--body{
26 | height: 100px;
27 | width: 100%;
28 | color: #222;
29 | background: white;
30 | border: 1px solid #a8abb1;
31 | border-radius: 5px;
32 | padding: 5px;
33 | outline: none;
34 | }
35 | &--submit{
36 | display: flex;
37 | justify-content: center;
38 | padding: 20px 0;
39 | }
40 | &--text{
41 | text-align: center;
42 | }
43 | }
44 | &__title{
45 | padding: 20px 0;
46 | display: flex;
47 | justify-content: center;
48 | }
49 | &--text{
50 | padding-left: 50px;
51 | padding-top: 3px;
52 | }
53 | &__items{
54 | }
55 | &__item{
56 | padding: 20px 0px;
57 | &__top{
58 | display: flex;
59 | justify-content: space-between;
60 | &--user{
61 | display: flex;
62 | &:hover{
63 | color: #505050;
64 | }
65 | }
66 | &--nickname{
67 | padding: 0px 10px;
68 | }
69 | &--date{
70 | color: $header_tags;
71 | font-size: 13px;
72 | }
73 | }
74 | &--text{
75 | padding: 10px;
76 | font-size: 19px;
77 | }
78 | &--edit{
79 | display: flex;
80 | justify-content: flex-end;
81 | font-size: 13px;
82 | &--left{
83 | padding-right: 10px;
84 | }
85 | & a, &:hover a{
86 | color: #333;
87 | }
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/spec/models/entry_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Entry, type: :model do
4 | let(:entry) { create(:entry) }
5 | describe '#create' do
6 | context "保存できる場合" do
7 | it "全てのパラメーターが揃っていれば保存できる" do
8 | expect(entry).to be_valid
9 | end
10 | end
11 |
12 | context "保存できない場合" do
13 | it "user_idがnilの場合は保存できない" do
14 | entry.user_id = nil
15 | entry.valid?
16 | expect(entry.errors[:user]).to include("を入力してください")
17 | end
18 |
19 | it "room_idがnilの場合は保存できない" do
20 | entry.room_id = nil
21 | entry.valid?
22 | expect(entry.errors[:room]).to include("を入力してください")
23 | end
24 | end
25 |
26 | context "一意性の検証" do
27 | it "user_idとroom_idの組み合わせは一意でなければ保存できない" do
28 | entry2 = build(:entry, user_id: entry.user_id, room_id: entry.room_id)
29 | entry2.valid?
30 | expect(entry2.errors[:room_id]).to include("はすでに存在します")
31 | end
32 |
33 | it "room_idが同じでもuser_idが違うと保存できる" do
34 | user2 = create(:user)
35 | entry2 = build(:entry, user_id: user2.id, room_id: entry.room_id)
36 | expect(entry2).to be_valid
37 | end
38 |
39 | it "user_idが同じでもroom_idが違うと保存できる" do
40 | room2 = create(:room)
41 | entry2 = build(:entry, user_id: entry.user_id, room_id: room2.id)
42 | expect(entry2).to be_valid
43 | end
44 | end
45 | end
46 |
47 | describe "各モデルとのアソシエーション" do
48 | let(:association) do
49 | described_class.reflect_on_association(target)
50 | end
51 |
52 | context "Userモデルとのアソシエーション" do
53 | let(:target) { :user }
54 |
55 | it "Userとの関連付けはbelongs_toであること" do
56 | expect(association.macro).to eq :belongs_to
57 | end
58 | end
59 |
60 | context "Roomモデルとのアソシエーション" do
61 | let(:target) { :room }
62 |
63 | it "Roomとの関連付けはbelongs_toであること" do
64 | expect(association.macro).to eq :belongs_to
65 | end
66 | end
67 | end
68 | end
69 |
--------------------------------------------------------------------------------
/app/views/users/_form.html.haml:
--------------------------------------------------------------------------------
1 | .login__field
2 | .text-center
3 | = f.label :nickname, value: 'ニックネーム'
4 | .center
5 | = f.text_field :nickname, autofocus: true, autocomplete: "nickname", class: "login--input"
6 | .login__field
7 | .text-center
8 | = f.label :email
9 | .center
10 | = f.email_field :email, autofocus: true, class: "login--input"
11 | .login__field
12 | .text-center
13 | = f.label :password
14 | .center
15 | = f.password_field :password, autocomplete: "off", class: "login--input"
16 | .login__field
17 | .text-center
18 | = f.label :password_confirmation, "パスワード(確認)"
19 | .center
20 | = f.password_field :password_confirmation, autocomplete: "new-password", class: "login--input"
21 | .login__field
22 | .text-center
23 | = f.label :avatar
24 | .center
25 | .lofin__preview
26 | .field.image
27 | // id "file"で、fileとdivを紐付けクリック時に連動
28 | #img_field{:onclick => "$('#file').click()"}
29 | // 画像があるときは画像を表示する
30 | - if @user.avatar.present?
31 | = image_tag "#{@user.avatar.url}", class: "avatar"
32 | -# = image_tag(@user.avatar.url)
33 | - else
34 | %i.fas.fa-camera.fa-3x
35 | // id "file"をつけ、「display:none;」で隠す
36 | = f.file_field :avatar, class: "avatar", style: "display:none;", id: "file"
37 |
38 | :javascript
39 | $(function(){
40 | $fileField = $('#file')
41 |
42 | // 選択された画像を取得し表示
43 | $($fileField).on('change', $fileField, function(e) {
44 | file = e.target.files[0]
45 | reader = new FileReader(),
46 | $preview = $("#img_field");
47 |
48 | reader.onload = (function(file) {
49 | return function(e) {
50 | $preview.empty();
51 | $preview.append($('
').attr({
52 | src: e.target.result,
53 | class: "avatar",
54 | title: file.name
55 | }));
56 | };
57 | })(file);
58 | reader.readAsDataURL(file);
59 | });
60 | });
--------------------------------------------------------------------------------
/spec/models/comment_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Comment, type: :model do
4 | let(:comment) { create(:comment) }
5 | describe '#create' do
6 | context '保存できる場合' do
7 | it "全てのパラメーターが揃っていれば保存できる" do
8 | expect(comment).to be_valid
9 | end
10 |
11 | it "contentが1文字以上なら保存できる" do
12 | comment.content = "A" * 1
13 | expect(comment).to be_valid
14 | end
15 |
16 | it "contentが1,000文字以内なら保存できる" do
17 | comment.content = "A" * 1000
18 | expect(comment).to be_valid
19 | end
20 | end
21 |
22 | context "保存できない場合" do
23 | it "user_idがnilの場合は保存できない" do
24 | comment.user_id = nil
25 | comment.valid?
26 | expect(comment.errors[:user]).to include("を入力してください")
27 | end
28 |
29 | it "tweet_idがnilの場合は保存できない" do
30 | comment.tweet_id = nil
31 | comment.valid?
32 | expect(comment.errors[:tweet]).to include("を入力してください")
33 | end
34 |
35 | it "contentがnilの場合は保存できない" do
36 | comment.content = nil
37 | comment.valid?
38 | expect(comment.errors[:content]).to include("を入力してください")
39 | end
40 |
41 | it "contentが1001文字以上の場合は保存できない" do
42 | comment.content = "A" * 1001
43 | comment.valid?
44 | expect(comment.errors[:content]).to include("は1000文字以内で入力してください")
45 | end
46 | end
47 | end
48 |
49 | describe "各モデルとのアソシエーション" do
50 | let(:association) do
51 | described_class.reflect_on_association(target)
52 | end
53 |
54 | context "Notificationモデルとのアソシエーション" do
55 | let(:target) { :notifications }
56 |
57 | it "Notificationとの関連付けはhas_manyであること" do
58 | expect(association.macro).to eq :has_many
59 | end
60 |
61 | it "Commentが削除されたらNotificationも削除されること" do
62 | comment = create(:comment)
63 | notification = create(:notification, comment_id: comment.id, visitor_id: 1, visited_id: 1)
64 | expect { comment.destroy }.to change(Comment, :count).by(-1)
65 | end
66 | end
67 | end
68 | end
69 |
--------------------------------------------------------------------------------
/app/uploaders/avatar_uploader.rb:
--------------------------------------------------------------------------------
1 | class AvatarUploader < CarrierWave::Uploader::Base
2 | # Include RMagick or MiniMagick support:
3 | # include CarrierWave::RMagick
4 | include CarrierWave::MiniMagick
5 |
6 | if Rails.env.development? || Rails.env.test?
7 | storage :file
8 | else
9 | storage :fog
10 | end
11 |
12 | # Create different versions of your uploaded files:
13 | version :thumb do
14 | process resize_to_fit: [800, 800]
15 | end
16 |
17 | # 保存形式をJPGにする
18 | process convert: 'jpg'
19 |
20 | # jpg,jpeg,gif,pngしか受け付けない
21 | def extension_white_list
22 | %w[jpg jpeg gif png]
23 | end
24 |
25 | # 拡張子が同じでないとGIFをJPGとかにコンバートできないので、ファイル名を変更
26 | def filename
27 | super.chomp(File.extname(super)) + '.jpg' if original_filename.present?
28 | end
29 |
30 | # Choose what kind of storage to use for this uploader:
31 | # storage :file
32 | # storage :fog
33 |
34 | # Override the directory where uploaded files will be stored.
35 | # This is a sensible default for uploaders that are meant to be mounted:
36 | def store_dir
37 | "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
38 | end
39 |
40 | # Provide a default URL as a default if there hasn't been a file uploaded:
41 | def default_url(*_args)
42 | # # For Rails 3.1+ asset pipeline compatibility:
43 | # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
44 | "avatar.png"
45 | # "/images/fallback/" + [version_name, "default.png"].compact.join('_')
46 | end
47 |
48 | # Process files as they are uploaded:
49 | # process scale: [200, 300]
50 | #
51 | # def scale(width, height)
52 | # # do something
53 | # end
54 |
55 | # Add a white list of extensions which are allowed to be uploaded.
56 | # For images you might use something like this:
57 | # def extension_whitelist
58 | # %w(jpg jpeg gif png)
59 | # end
60 |
61 | # Override the filename of the uploaded files:
62 | # Avoid using model.id or version_name here, see uploader/store.rb for details.
63 | # def filename
64 | # "something.jpg" if original_filename
65 | # end
66 | end
67 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/modules/_tweet-form.scss:
--------------------------------------------------------------------------------
1 |
2 | #preview-area{
3 | min-height: 300px;
4 | display: none;
5 | }
6 |
7 | #img_field {
8 | width: 100%;
9 | height: 100%;
10 | }
11 |
12 | .tweet-form{
13 | @include common-sizing;
14 | margin: 0px auto 100px;
15 | font-size: 15px;
16 | &__title{
17 | height: 50px;
18 | &--body{
19 | height: 50px;
20 | width: 100%;
21 | outline: none;
22 | padding-left: 5px;
23 | border-radius: 5px;
24 | border: 1px solid #e6e6e6;
25 | &:hover{
26 | border: 1px solid #a8abb1;
27 | }
28 | }
29 | }
30 | &__text{
31 | position: relative;
32 | margin-top: 10px;
33 | &--body{
34 | height: 300px;
35 | width: 100%;
36 | outline: none;
37 | padding: 5px;
38 | border: 1px solid #e6e6e6;
39 | border-radius: 5px;
40 | }
41 | &__btn{
42 | display: flex;
43 | justify-content: space-around;
44 | padding: 20px 0px;
45 | }
46 | }
47 | &--tags{
48 | padding-bottom: 10px;
49 | }
50 | &__bonus{
51 | &__preview{
52 | margin: 10px auto;
53 | background-color: white;
54 | border: solid 1px #eee;
55 | color: #666;
56 | position: relative;
57 | border-radius: 5px;
58 | cursor: pointer;
59 | height: 300px;
60 | width: 400px;
61 | @include md-max {
62 | @include tweet-items(); // 画面縮小時のwidthを指定
63 | }
64 | object-fit: cover;
65 | transition: 0.3s ease-out;
66 | text-align: center;
67 | &:hover{
68 | background-color: gray;
69 | color: white;
70 | }
71 | &--title{
72 | text-align: center;
73 | }
74 | &--image{
75 | border-radius: 5px;
76 | height: 300px;
77 | width: 400px;
78 | @include md-max{
79 | @include tweet-items(); // 画面縮小時のwidthを指定
80 | }
81 | object-fit: cover;
82 | }
83 | &--icon{
84 | position: absolute;
85 | top: 32%;
86 | left: 39%;
87 | }
88 | }
89 | }
90 | &__submit{
91 | text-align: center;
92 | margin-bottom: 30px;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/uploaders/image_uploader.rb:
--------------------------------------------------------------------------------
1 | class ImageUploader < CarrierWave::Uploader::Base
2 | # Include RMagick or MiniMagick support:
3 | # include CarrierWave::RMagick
4 | include CarrierWave::MiniMagick
5 |
6 | if Rails.env.development? || Rails.env.test?
7 | storage :file
8 | else
9 | storage :fog
10 | end
11 |
12 | # Create different versions of your uploaded files:
13 | version :thumb do
14 | process resize_to_fit: [800, 800]
15 | end
16 |
17 | # 保存形式をJPGにする
18 | process convert: 'jpg'
19 |
20 | # jpg,jpeg,gif,pngしか受け付けない
21 | def extension_white_list
22 | %w[jpg jpeg gif png]
23 | end
24 |
25 | # 拡張子が同じでないとGIFをJPGとかにコンバートできないので、ファイル名を変更
26 | def filename
27 | super.chomp(File.extname(super)) + '.jpg' if original_filename.present?
28 | end
29 |
30 | # Choose what kind of storage to use for this uploader:
31 | # storage :file
32 | # storage :fog
33 |
34 | # Override the directory where uploaded files will be stored.
35 | # This is a sensible default for uploaders that are meant to be mounted:
36 | def store_dir
37 | "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
38 | end
39 |
40 | # Provide a default URL as a default if there hasn't been a file uploaded:
41 | def default_url(*args)
42 | # # For Rails 3.1+ asset pipeline compatibility:
43 | # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
44 | # "image.png"
45 | # "avatar.png"
46 | # "/images/fallback/" + [version_name, "default.png"].compact.join('_')
47 | end
48 |
49 | # Process files as they are uploaded:
50 | # process scale: [200, 300]
51 | #
52 | # def scale(width, height)
53 | # # do something
54 | # end
55 |
56 | # Add a white list of extensions which are allowed to be uploaded.
57 | # For images you might use something like this:
58 | # def extension_whitelist
59 | # %w(jpg jpeg gif png)
60 | # end
61 |
62 | # Override the filename of the uploaded files:
63 | # Avoid using model.id or version_name here, see uploader/store.rb for details.
64 | # def filename
65 | # "something.jpg" if original_filename
66 | # end
67 | end
68 |
--------------------------------------------------------------------------------
/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | config.after_initialize do
3 | Bullet.enable = true
4 | Bullet.bullet_logger = true
5 | Bullet.raise = true # raise an error if n+1 query occurs
6 | end # Settings specified here will take precedence over those in config/application.rb.
7 |
8 | # The test environment is used exclusively to run your application's
9 | # test suite. You never need to work with it otherwise. Remember that
10 | # your test database is "scratch space" for the test suite and is wiped
11 | # and recreated between test runs. Don't rely on the data there!
12 | config.cache_classes = true
13 |
14 | # Do not eager load code on boot. This avoids loading your whole application
15 | # just for the purpose of running a single test. If you are using a tool that
16 | # preloads Rails for running tests, you may have to set it to true.
17 | config.eager_load = false
18 |
19 | # Configure public file server for tests with Cache-Control for performance.
20 | config.public_file_server.enabled = true
21 | config.public_file_server.headers = {
22 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}"
23 | }
24 |
25 | # Show full error reports and disable caching.
26 | config.consider_all_requests_local = true
27 | config.action_controller.perform_caching = false
28 |
29 | # Raise exceptions instead of rendering exception templates.
30 | config.action_dispatch.show_exceptions = false
31 |
32 | # Disable request forgery protection in test environment.
33 | config.action_controller.allow_forgery_protection = false
34 |
35 | # Store uploaded files on the local file system in a temporary directory
36 | config.active_storage.service = :test
37 |
38 | config.action_mailer.perform_caching = false
39 |
40 | # Tell Action Mailer not to deliver emails to the real world.
41 | # The :test delivery method accumulates sent emails in the
42 | # ActionMailer::Base.deliveries array.
43 | config.action_mailer.delivery_method = :test
44 |
45 | # Print deprecation notices to the stderr.
46 | config.active_support.deprecation = :stderr
47 |
48 | # Raises error for missing translations
49 | # config.action_view.raise_on_missing_translations = true
50 | end
51 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/modules/_room-show.scss:
--------------------------------------------------------------------------------
1 |
2 | // chat_area のScss------------------------------------------------------
3 | .room-show{
4 | @include common-sizing();
5 | min-height: 500px;
6 | margin: 30px auto;
7 | border: 1px solid $header_border;
8 | background-color: white;
9 | color: #333;
10 | overflow: scroll;
11 | &__title{
12 | height: 100px;
13 | font-size: 25px;
14 | @include md-max{
15 | font-size: 18px;
16 | }
17 | text-align: center;
18 | line-height: 100px;
19 | border-bottom: 1px solid $header_border;
20 | position: relative;
21 | &--btn{
22 | position: absolute;
23 | top: 0;
24 | left: 10px;
25 | }
26 | &:hover a{
27 | color: #505050;
28 | }
29 | }
30 | &__item {
31 | margin-top: 10px;
32 | &--message{
33 | font-size: 15px;
34 | background-color: #F5F5F5;
35 | padding: 0.4rem 1rem;
36 | border-radius: 1.2rem;
37 | display: inline-block;
38 | max-width: 80%;
39 | margin: 0.5rem;
40 | }
41 | &--date{
42 | padding: 5px 10px;
43 | color: $header_tags;
44 | }
45 | &__right {
46 | margin: 0.5rem;
47 | display: flex;
48 | justify-content: flex-end;
49 | &--btn{
50 | padding-top: 8px;
51 | }
52 | }
53 | &__left {
54 | margin: 0.5rem;
55 | display: flex;
56 | justify-content: flex-start;
57 | }
58 | }
59 | }
60 | // chat_form のScss------------------------------------------------------
61 | .room-show__form{
62 | height: 70px;
63 | width: 100%;
64 | padding: 10px;
65 | display: flex;
66 | border-top: 1px solid $header_border;
67 | background-color: #bcbcbc;
68 | position: fixed;
69 | bottom: 0;
70 | &__box{
71 | position: relative;
72 | margin: 0 auto;
73 | }
74 | &--input{
75 | padding-left: 20px;
76 | @include common-sizing;
77 | @include sm-max{
78 | width: 250px;
79 | }
80 | height: 50px;
81 | border-radius: 100px;
82 | border: 1px solid #ccc;
83 | outline: 0;
84 | }
85 | &--btn{
86 | border: none;
87 | font-size: 1.3rem;
88 | color: #666;
89 | background-color: #bcbcbc;
90 | position: absolute;
91 | bottom: 7px;
92 | right: -40px;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/modules/_tweet-index.scss:
--------------------------------------------------------------------------------
1 | .tweet-index{
2 | margin: 0px auto;
3 | display: flex;
4 | justify-content: space-around;
5 | flex-wrap: wrap;
6 | @include lg-max{
7 | @include tweet-items; // widthを指定
8 | }
9 | width: 1000px;
10 | &__title{
11 | display: none;
12 | // height: 50px;
13 | width: 100%;
14 | &--text{
15 | font-size: 20px;
16 | border-bottom: 1px solid $header_border;
17 | text-align: center;
18 | width: 130px;
19 | margin: 0 auto;
20 | }
21 | }
22 | &__item{
23 | display: inline-block;
24 | @include lg-max{
25 | @include tweet-items;
26 | }
27 | width: 460px;
28 | padding: 30px;
29 | margin: 5% 0;
30 | border: 1px solid $header_border;
31 | background-color: white;
32 | border-radius: 5px;
33 | position: relative;
34 | box-shadow: 0px 4px 6px #ccc;
35 | &--image{
36 | height: 300px;
37 | width: 100%;
38 | object-fit: cover;
39 | background-color: black;
40 | margin: 20px 0;
41 | }
42 | &--title{
43 | font-size: 18px;
44 | font-weight: bold;
45 | color: #333;
46 | &:hover{
47 | color: #505050;
48 | }
49 | }
50 | &--sub{
51 | color: $header_tags;
52 | font-size: 13px;
53 | margin-top: 30px;
54 | }
55 | &__footer{
56 | padding-top: 20px;
57 | border-top: 1px solid #e1e1e1;
58 | margin-top: 20px;
59 | display: flex;
60 | justify-content: space-between;
61 | }
62 | &--show{
63 | color: $header_tags;
64 | font-size: 13px;
65 | bottom: 5px;
66 | right: 5px;
67 | &:hover{
68 | color: #333;
69 | }
70 | }
71 | &--like{
72 | bottom: 5px;
73 | left: 5px;
74 | }
75 | &__user{
76 | display: flex;
77 | justify-content: space-between;
78 | position: relative;
79 | line-height: 40px;
80 | padding-bottom: 20px;
81 | &--nickname{
82 | position: absolute;
83 | left: 55px;
84 | top: 0;
85 | color: #333;
86 | &:hover{
87 | color: #505050;
88 | }
89 | }
90 | }
91 | }
92 | &--footer{
93 | // justify-contents: centerが効かないので追加していた。効くは聞くため削除
94 | // padding: 0 400px;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/config/deploy/staging.rb:
--------------------------------------------------------------------------------
1 | # server-based syntax
2 | # ======================
3 | # Defines a single server with a list of roles and multiple properties.
4 | # You can define all roles on a single server, or split them:
5 |
6 | # server "example.com", user: "deploy", roles: %w{app db web}, my_property: :my_value
7 | # server "example.com", user: "deploy", roles: %w{app web}, other_property: :other_value
8 | # server "db.example.com", user: "deploy", roles: %w{db}
9 |
10 |
11 |
12 | # role-based syntax
13 | # ==================
14 |
15 | # Defines a role with one or multiple servers. The primary server in each
16 | # group is considered to be the first unless any hosts have the primary
17 | # property set. Specify the username and a domain or IP for the server.
18 | # Don't use `:all`, it's a meta role.
19 |
20 | # role :app, %w{deploy@example.com}, my_property: :my_value
21 | # role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value
22 | # role :db, %w{deploy@example.com}
23 |
24 |
25 |
26 | # Configuration
27 | # =============
28 | # You can set any configuration variable like in config/deploy.rb
29 | # These variables are then only loaded and set in this stage.
30 | # For available Capistrano configuration variables see the documentation page.
31 | # http://capistranorb.com/documentation/getting-started/configuration/
32 | # Feel free to add new variables to customise your setup.
33 |
34 |
35 |
36 | # Custom SSH Options
37 | # ==================
38 | # You may pass any option but keep in mind that net/ssh understands a
39 | # limited set of options, consult the Net::SSH documentation.
40 | # http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start
41 | #
42 | # Global options
43 | # --------------
44 | # set :ssh_options, {
45 | # keys: %w(/home/user_name/.ssh/id_rsa),
46 | # forward_agent: false,
47 | # auth_methods: %w(password)
48 | # }
49 | #
50 | # The server-based syntax can be used to override options:
51 | # ------------------------------------
52 | # server "example.com",
53 | # user: "user_name",
54 | # roles: %w{web app},
55 | # ssh_options: {
56 | # user: "user_name", # overrides user setting above
57 | # keys: %w(/home/user_name/.ssh/id_rsa),
58 | # forward_agent: false,
59 | # auth_methods: %w(publickey password)
60 | # # password: "please use keys"
61 | # }
62 |
--------------------------------------------------------------------------------
/spec/models/relationship_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Relationship, type: :model do
4 | let(:relationship) { create(:relationship) }
5 | describe '#create' do
6 | context "保存できる場合" do
7 | it "全てのパラメーターが揃っていれば保存できる" do
8 | expect(relationship).to be_valid
9 | end
10 | end
11 |
12 | context "保存できない場合" do
13 | it "follower_idがnilの場合は保存できない" do
14 | relationship.follower_id = nil
15 | relationship.valid?
16 | expect(relationship.errors[:follower_id]).to include("を入力してください")
17 | end
18 |
19 | it "following_idがnilの場合は保存できない" do
20 | relationship.following_id = nil
21 | relationship.valid?
22 | expect(relationship.errors[:following_id]).to include("を入力してください")
23 | end
24 | end
25 |
26 | context "一意性の検証" do
27 | before do
28 | @relation = create(:relationship)
29 | @user1 = build(:relationship)
30 | end
31 | it "follower_idとfollowing_idの組み合わせは一意でなければ保存できない" do
32 | relation2 = build(:relationship, follower_id: @relation.follower_id, following_id: @relation.following_id)
33 | relation2.valid?
34 | expect(relation2.errors[:follower_id]).to include("はすでに存在します")
35 | end
36 |
37 | it "follower_idが同じでもfollowing_idが違うなら保存できる" do
38 | relation2 = build(:relationship, follower_id: @relation.follower_id, following_id: @user1.following_id)
39 | expect(relation2).to be_valid
40 | end
41 |
42 | it "follower_idが違うならfollowing_idが同じでも保存できる" do
43 | relation2 = build(:relationship, follower_id: @user1.follower_id, following_id: @relation.following_id)
44 | expect(relation2).to be_valid
45 | end
46 | end
47 | end
48 |
49 | describe "各モデルとのアソシエーション" do
50 | let(:association) do
51 | described_class.reflect_on_association(target)
52 | end
53 |
54 | context "仮想モデルfollowerとのアソシエーション" do
55 | let(:target) { :follower }
56 |
57 | it "Followerとの関連付けはbelongs_toであること" do
58 | expect(association.macro).to eq :belongs_to
59 | end
60 | end
61 |
62 | context "仮想モデルfollowingとのアソシエーション" do
63 | let(:target) { :following }
64 |
65 | it "Followingとの関連付けはbelongs_toであること" do
66 | expect(association.macro).to eq :belongs_to
67 | end
68 | end
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/_reset.scss:
--------------------------------------------------------------------------------
1 | /*!
2 | * YUI 3.5.0 - reset.css (http://developer.yahoo.com/yui/3/cssreset/)
3 | * http://cssreset.com
4 | * Copyright 2012 Yahoo! Inc. All rights reserved.
5 | * http://yuilibrary.com/license/
6 | */
7 | /*
8 | */
9 |
10 | html {
11 | color: #333;
12 | background-color: $body_color;
13 | }
14 | body {
15 | background-color: $body_color;
16 | }
17 |
18 | a,
19 | a:visited,
20 | a:focus,
21 | a:hover {
22 | text-decoration: none !important;
23 | color: #333;
24 | }
25 |
26 | li{
27 | text-decoration: none;
28 | }
29 |
30 |
31 | * {
32 | margin: 0px;
33 | padding: 0px;
34 | }
35 |
36 | /*
37 | */
38 | /*
39 | - Fails on FF.
40 | */
41 | body,
42 | div,
43 | dl,
44 | dt,
45 | dd,
46 | ul,
47 | ol,
48 | li,
49 | h1,
50 | h2,
51 | h3,
52 | h4,
53 | h5,
54 | h6,
55 | pre,
56 | code,
57 | form,
58 | fieldset,
59 | legend,
60 | input,
61 | textarea,
62 | p,
63 | blockquote,
64 | th,
65 | td {
66 | margin: 0;
67 | padding: 0;
68 | }
69 | table {
70 | border-collapse: collapse;
71 | border-spacing: 0;
72 | }
73 | fieldset,
74 | img {
75 | border: 0;
76 | }
77 | /*
78 | */
79 | address,
80 | caption,
81 | cite,
82 | code,
83 | dfn,
84 | em,
85 | strong,
86 | th,
87 | var {
88 | font-style: normal;
89 | font-weight: normal;
90 | }
91 |
92 | ol,
93 | ul {
94 | list-style: none;
95 | }
96 |
97 | caption,
98 | th {
99 | text-align: left;
100 | }
101 | h1,
102 | h2,
103 | h3,
104 | h4,
105 | h5,
106 | h6 {
107 | font-size: 100%;
108 | font-weight: normal;
109 | }
110 | q:before,
111 | q:after {
112 | content: "";
113 | }
114 | abbr,
115 | acronym {
116 | border: 0;
117 | font-variant: normal;
118 | }
119 | /* to preserve line-height and selector appearance */
120 | sup {
121 | vertical-align: text-top;
122 | }
123 | sub {
124 | vertical-align: text-bottom;
125 | }
126 | input,
127 | textarea,
128 | select {
129 | font-family: inherit;
130 | font-size: inherit;
131 | font-weight: inherit;
132 | }
133 | /*to enable resizing for IE*/
134 | input,
135 | textarea,
136 | select {
137 | *font-size: 100%;
138 | }
139 | /*because legend doesn't inherit in IE */
140 | legend {
141 | color: #000;
142 | }
143 | /* YUI CSS Detection Stamp */
144 | #yui3-css-stamp.cssreset {
145 | display: none;
146 | }
--------------------------------------------------------------------------------
/app/controllers/tweets_controller.rb:
--------------------------------------------------------------------------------
1 | class TweetsController < ApplicationController
2 | before_action :authenticate_user!, only: %i[new create edit update destroy]
3 | before_action :set_tweet, only: %i[edit destroy show update]
4 | before_action :blocking_edit_tweet, only: %i[edit update destroy]
5 | before_action :set_available_tags_to_gon, only: %i[new edit]
6 |
7 | def index
8 | @tweets = Tweet.includes(%i[taggings user]).order('created_at desc').page(params[:page]).per(10)
9 | @tweets = @tweets.tagged_with(params[:tag_name].to_s) if params[:tag_name]
10 | end
11 |
12 | def new
13 | @tweet = Tweet.new
14 | @tags = Tweet.includes([:taggings]).tag_counts_on(:tags)
15 | end
16 |
17 | def create
18 | @tweet = Tweet.create(tweet_params)
19 | gon.tweet_tags = @tweet.tag_list
20 | if @tweet.save
21 | redirect_to root_path, notice: '投稿しました!'
22 | else
23 | flash.now[:alert] = "文字数を確認してください。"
24 | render :new
25 | end
26 | end
27 |
28 | def edit
29 | gon.tweet_tags = @tweet.tag_list
30 | end
31 |
32 | def update
33 | @tweet.update(tweet_params)
34 | gon.tweet_tags = @tweet.tag_list
35 | if @tweet.save
36 | redirect_to tweet_path(@tweet.id), notice: '投稿を編集しました!'
37 | else
38 | flash.now[:alert] = "文字数を確認してください。"
39 | render :edit
40 | end
41 | end
42 |
43 | def show
44 | @comment = Comment.new
45 | @comments = @tweet.comments.includes(:user).order('created_at desc')
46 | @user = @tweet.user
47 | @donation = Donation.new
48 | @donations = @tweet.donations.includes(:user).sum(:amount)
49 | end
50 |
51 | def destroy
52 | @tweet.destroy
53 | redirect_to root_path, notice: '投稿を削除しました'
54 | end
55 |
56 | def likes
57 | @tweets = Tweet.includes(%i[taggings user]).order('likes_count desc').page(params[:page]).per(10)
58 | end
59 |
60 | def tags
61 | @tags = Tweet.includes(:taggings).tag_counts_on(:tags)
62 | end
63 |
64 | private
65 |
66 | def tweet_params
67 | params.require(:tweet).permit(:title, :text, :image, :tag_list).merge(user_id: current_user.id)
68 | end
69 |
70 | def set_tweet
71 | @tweet = Tweet.find(params[:id])
72 | end
73 |
74 | def blocking_edit_tweet
75 | redirect_to root_path, alert: "不正な操作です" unless (@tweet.user == current_user) || current_user.admin?
76 | end
77 |
78 | def set_available_tags_to_gon
79 | gon.available_tags = Tweet.includes(:taggings).tags_on(:tags).pluck(:name)
80 | end
81 | end
82 |
--------------------------------------------------------------------------------
/spec/models/message_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Message, type: :model do
4 | let(:message) { create(:message) }
5 | describe '#create' do
6 | context '保存できる場合' do
7 | it "全てのパラメーターが揃っていれば保存できる" do
8 | expect(message).to be_valid
9 | end
10 |
11 | it "messageが1文字以上なら保存できる" do
12 | message.message = "A" * 1
13 | expect(message).to be_valid
14 | end
15 |
16 | it "messageが1,000文字以内なら保存できる" do
17 | message.message = "A" * 1000
18 | expect(message).to be_valid
19 | end
20 | end
21 |
22 | context "保存できない場合" do
23 | it "user_idがnilの場合は保存できない" do
24 | message.user_id = nil
25 | message.valid?
26 | expect(message.errors[:user]).to include("を入力してください")
27 | end
28 |
29 | it "room_idがnilの場合は保存できない" do
30 | message.room_id = nil
31 | message.valid?
32 | expect(message.errors[:room]).to include("を入力してください")
33 | end
34 |
35 | it "messageがnilの場合は保存できない" do
36 | message.message = nil
37 | message.valid?
38 | expect(message.errors[:message]).to include("を入力してください")
39 | end
40 |
41 | it "messageが1001文字以上の場合は保存できない" do
42 | message.message = "A" * 1001
43 | message.valid?
44 | expect(message.errors[:message]).to include("は1000文字以内で入力してください")
45 | end
46 | end
47 | end
48 |
49 | describe "各モデルとのアソシエーション" do
50 | let(:association) do
51 | described_class.reflect_on_association(target)
52 | end
53 |
54 | context "Userモデルとのアソシエーション" do
55 | let(:target) { :user }
56 |
57 | it "Userとの関連付けはbelongs_toであること" do
58 | expect(association.macro).to eq :belongs_to
59 | end
60 | end
61 |
62 | context "Roomモデルとのアソシエーション" do
63 | let(:target) { :room }
64 |
65 | it "Roomとの関連付けはbelongs_toであること" do
66 | expect(association.macro).to eq :belongs_to
67 | end
68 | end
69 |
70 | context "Notificationモデルとのアソシエーション" do
71 | let(:target) { :notifications }
72 |
73 | it "Notificationとの関連付けはhas_manyであること" do
74 | expect(association.macro).to eq :has_many
75 | end
76 |
77 | it "Messageが削除されたらNotificationも削除されること" do
78 | message = create(:message)
79 | notification = create(:notification, message_id: message.id, visitor_id: 1, visited_id: 1)
80 | expect { message.destroy }.to change(Notification, :count).by(-1)
81 | end
82 | end
83 | end
84 | end
85 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | executors:
4 | default:
5 | working_directory: ~/repo
6 | docker:
7 | - image: circleci/ruby:2.5.1-node-browsers
8 | - image: circleci/mysql:5.7
9 | environment:
10 | - MYSQL_USER: root
11 | - MYSQL_ALLOW_EMPTY_PASSWORD: true
12 | - RAILS_ENV: "test"
13 | - BUNDLER_VERSION: 2.1.4
14 |
15 | commands:
16 | setup:
17 | steps:
18 | - checkout
19 | - restore_cache:
20 | keys:
21 | - v1-dependencies-{{ checksum "Gemfile.lock" }}
22 | - v1-dependencies-
23 | - run:
24 | name: install dependencies
25 | command: |
26 | gem install bundler -v 2.1.4
27 | bundle install --jobs=4 --retry=3 --path vendor/bundle
28 | - save_cache:
29 | paths:
30 | - ./vendor/bundle
31 | key: v1-dependencies-{{ checksum "Gemfile.lock" }}
32 |
33 | jobs:
34 | test:
35 | executor: default
36 | environment:
37 | RAILS_ENV: test
38 | steps:
39 | - setup
40 | - run: mv config/database.yml.ci config/database.yml
41 | - run: bundle exec rake db:create
42 | - run: bundle exec rake db:schema:load
43 |
44 | - run:
45 | name: Rubocop
46 | command: bundle exec rubocop
47 |
48 | - run:
49 | name: run tests
50 | command: |
51 | mkdir /tmp/test-results
52 | TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \
53 | circleci tests split --split-by=timings)"
54 | bundle exec rspec \
55 | --format progress \
56 | --format RspecJunitFormatter \
57 | --out /tmp/test-results/rspec.xml \
58 | --format progress \
59 | $TEST_FILES
60 |
61 | - store_test_results:
62 | path: /tmp/test-results
63 | - store_artifacts:
64 | path: /tmp/test-results
65 | destination: test-results
66 |
67 | deploy:
68 | executor: default
69 | steps:
70 | - setup
71 | - add_ssh_keys:
72 | fingerprints:
73 | - "33:33:a8:e3:91:b6:5d:59:10:1d:0f:dc:a6:fe:29:db" # GitHubのUserキーを使用
74 | - deploy:
75 | name: Capistrano deploy
76 | command: bundle exec cap production deploy
77 |
78 | workflows:
79 | test_and_deploy:
80 | jobs:
81 | - test
82 | - deploy:
83 | requires:
84 | - test
85 | filters:
86 | branches:
87 | only: master
88 |
--------------------------------------------------------------------------------
/app/views/layouts/_header.html.haml:
--------------------------------------------------------------------------------
1 | .header
2 | .header-contents
3 | .header-top
4 | .header-top__left
5 | = link_to root_path do
6 | %h1.header-top__left--title My Note
7 | .header-top__right
8 | = form_with(url: tweets_searches_path, local: true, method: :get) do |f|
9 | = f.text_field :keyword, placeholder: "検索する", class: "header-top__right--search"
10 | .header-top__right__user
11 | - if user_signed_in?
12 | %div
13 | = render 'layouts/circle'
14 | .header-top__right__user__info
15 | .header-top__right__user__info--link.display-flex
16 | .header-top__right__user__info--avatar
17 | = image_tag "#{current_user.avatar}", class: "avatar"
18 | .header-top__right__user__info--nickname
19 | = current_user.nickname
20 | %ul.header-top__box
21 | %li
22 | = link_to "マイページを表示", user_path(current_user), class: "header-top__box--content"
23 | %li.header-top__box--borderTop
24 | = link_to "メッセージ", rooms_path, class: "header-top__box--content"
25 | %li.header-top__box--borderTop
26 | = link_to "タイムライン", timeline_users_path, class: "header-top__box--content"
27 | %li.header-top__box--borderTop
28 | = link_to 'ユーザー一覧', users_path, class: "header-top__box--content"
29 | %li.header-top__box--borderTop
30 | = link_to "人気投稿", likes_tweets_path, class: "header-top__box--content"
31 | %li.header-top__box--borderTop
32 | = link_to 'ログアウト', destroy_user_session_path, method: :delete, class: "header-top__box--content"
33 | - else
34 | = link_to new_user_session_path do
35 | %input.btn.btn-success.header-top__right--box{type: "submit", value: "ログイン"}
36 | = link_to new_user_registration_path do
37 | %input.btn.btn-success.header-top__right--box{type: "submit", value: "新規登録"}
38 |
39 | .header-bottom
40 | %ul.header-bottom__list
41 | - @tags = ["テクノロジー","経済","マンガ","ファッション","エンタメ","スポーツ","国際","コラム"]
42 | - @tags.each do |tag|
43 | %li.header-bottom__list__item
44 | = link_to tag, tweets_path(tag_name: tag), class: "header-bottom__list--name"
45 | %ul.header-bottom__list__tags
46 | = link_to "全てのタグ", tags_tweets_path, class: ".header-bottom__list--name"
--------------------------------------------------------------------------------
/app/controllers/users_controller.rb:
--------------------------------------------------------------------------------
1 | class UsersController < ApplicationController
2 | before_action :authenticate_user!, only: %i[edit update timeline]
3 | before_action :set_user, except: %i[index timeline]
4 | before_action :blocking_edit_user, only: %i[edit update]
5 | before_action :blocking_edit_test_user, only: %i[edit update]
6 | before_action :join_room, only: %i[show likes]
7 |
8 | def index
9 | @users = User.all
10 | end
11 |
12 | def show
13 | @tweets = @user.tweets.includes(:taggings).order('created_at desc').page(params[:page]).per(10)
14 | end
15 |
16 | def edit; end
17 |
18 | def update
19 | if current_user.update(user_params)
20 | redirect_to root_path, notice: 'ユーザー情報を更新しました'
21 | else
22 | flash.now[:alert] = "入力内容に誤りがあります。入力漏れ、文字数をご確認ください。"
23 | render :edit
24 | end
25 | end
26 |
27 | def likes
28 | @tweets = @user.liked_tweets.includes(%i[taggings user]).order('updated_at desc').page(params[:page]).per(10)
29 | end
30 |
31 | def following
32 | @users = @user.following
33 | end
34 |
35 | def followers
36 | @users = @user.followers
37 | end
38 |
39 | def timeline
40 | if user_signed_in?
41 | @user = User.find(current_user.id)
42 | @following_users = @user.following
43 | @tweets = Tweet.includes(%i[taggings user]).where(user_id: @following_users).order('created_at desc').page(params[:page]).per(10)
44 | end
45 | end
46 |
47 | private
48 |
49 | def set_user
50 | @user = User.find(params[:id])
51 | end
52 |
53 | def user_params
54 | params.require(:user).permit(:nickname, :email, :avatar)
55 | end
56 |
57 | def blocking_edit_user
58 | redirect_to root_path, alert: "不正な操作です" unless (@user == current_user) || current_user.admin?
59 | end
60 |
61 | def blocking_edit_test_user
62 | redirect_to root_path, alert: "ゲストユーザーのため編集できません" if current_user.email == "guest@user"
63 | end
64 |
65 | def join_room
66 | if user_signed_in?
67 | unless @user.id == current_user.id
68 | @current_user_entry = Entry.where(user_id: current_user.id)
69 | @user_entry = Entry.where(user_id: @user.id)
70 | @current_user_entry.each do |cu|
71 | @user_entry.each do |u|
72 | if cu.room_id == u.room_id
73 | @have_room = true
74 | @room_id = cu.room_id
75 | end
76 | end
77 | end
78 | unless @have_room
79 | @room = Room.new
80 | @entry = Entry.new
81 | end
82 | end
83 | end
84 | end
85 | end
86 |
--------------------------------------------------------------------------------
/app/models/user.rb:
--------------------------------------------------------------------------------
1 | class User < ApplicationRecord
2 | # devise_login------------------------------ ------------------------------------
3 | # Include default devise modules. Others available are:
4 | # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
5 | devise :database_authenticatable, :registerable,
6 | :recoverable, :rememberable, :validatable
7 | validates :nickname, presence: true, length: { maximum: 10 }
8 | validates :email, length: { maximum: 50 }, presence: true, uniqueness: true
9 | mount_uploader :avatar, AvatarUploader
10 | # tweets-------------------------------------------------------------------
11 | has_many :tweets
12 | # comments-------------------------------------------------------------------
13 | has_many :comments
14 | # likes-------------------------------------------------------------------
15 | has_many :likes
16 | has_many :liked_tweets, through: :likes, source: :tweet
17 | # messages-------------------------------------------------------------------
18 | has_many :messages
19 | has_many :entries
20 | has_many :rooms, through: :entries
21 | # follow------------------------------------------------------------------
22 | has_many :following_relationships, foreign_key: "follower_id", class_name: "Relationship"
23 | has_many :following, through: :following_relationships
24 | has_many :follower_relationships, foreign_key: "following_id", class_name: "Relationship"
25 | has_many :followers, through: :follower_relationships
26 |
27 | # フォローしているかを確認するメソッド
28 | def following?(user)
29 | following_relationships.find_by(following_id: user.id)
30 | end
31 |
32 | # フォローするときのメソッド
33 | def follow(user)
34 | following_relationships.create!(following_id: user.id)
35 | end
36 |
37 | # フォローを外すときのメソッド
38 | def unfollow(user)
39 | following_relationships.find_by(following_id: user.id).destroy
40 | end
41 | # notifications-------------------------------------------------------------------
42 | has_many :active_notifications, class_name: 'Notification', foreign_key: 'visitor_id'
43 | has_many :passive_notifications, class_name: 'Notification', foreign_key: 'visited_id'
44 |
45 | def create_notification_follow!(current_user)
46 | temp = Notification.where(["visitor_id = ? and visited_id = ? and action = ? ", current_user.id, id, 'follow'])
47 | if temp.blank?
48 | notification = current_user.active_notifications.new(
49 | visited_id: id,
50 | action: 'follow'
51 | )
52 | notification.save if notification.valid?
53 | end
54 | end
55 | # donations-------------------------------------------------------------------
56 | has_many :donations
57 | end
58 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # config.after_initialize do
3 | # Bullet.enable = true
4 | # Bullet.alert = true
5 | # Bullet.bullet_logger = true
6 | # Bullet.console = true
7 | # # Bullet.growl = true
8 | # Bullet.rails_logger = true
9 | # Bullet.add_footer = true
10 | # end # Settings specified here will take precedence over those in config/application.rb.
11 |
12 | # In the development environment your application's code is reloaded on
13 | # every request. This slows down response time but is perfect for development
14 | # since you don't have to restart the web server when you make code changes.
15 | config.cache_classes = false
16 |
17 | # Do not eager load code on boot.
18 | config.eager_load = false
19 |
20 | # Show full error reports.
21 | config.consider_all_requests_local = true
22 |
23 | # Enable/disable caching. By default caching is disabled.
24 | # Run rails dev:cache to toggle caching.
25 | if Rails.root.join('tmp', 'caching-dev.txt').exist?
26 | config.action_controller.perform_caching = true
27 |
28 | config.cache_store = :memory_store
29 | config.public_file_server.headers = {
30 | 'Cache-Control' => "public, max-age=#{2.days.to_i}"
31 | }
32 | else
33 | config.action_controller.perform_caching = false
34 |
35 | config.cache_store = :null_store
36 | end
37 |
38 | # Store uploaded files on the local file system (see config/storage.yml for options)
39 | config.active_storage.service = :local
40 |
41 | # Don't care if the mailer can't send.
42 | config.action_mailer.raise_delivery_errors = false
43 |
44 | config.action_mailer.perform_caching = false
45 |
46 | # Print deprecation notices to the Rails logger.
47 | config.active_support.deprecation = :log
48 |
49 | # Raise an error on page load if there are pending migrations.
50 | config.active_record.migration_error = :page_load
51 |
52 | # Highlight code that triggered database queries in logs.
53 | config.active_record.verbose_query_logs = true
54 |
55 | # Debug mode disables concatenation and preprocessing of assets.
56 | # This option may cause significant delays in view rendering with a large
57 | # number of complex assets.
58 | config.assets.debug = true
59 |
60 | # Suppress logger output for asset requests.
61 | config.assets.quiet = true
62 |
63 | # Raises error for missing translations
64 | # config.action_view.raise_on_missing_translations = true
65 |
66 | # Use an evented file watcher to asynchronously detect changes in source code,
67 | # routes, locales, etc. This feature depends on the listen gem.
68 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker
69 | end
70 |
--------------------------------------------------------------------------------
/config/deploy.rb:
--------------------------------------------------------------------------------
1 | # config valid only for current version of Capistrano
2 | # capistranoのバージョンを記載。固定のバージョンを利用し続け、バージョン変更によるトラブルを防止する
3 | lock "3.14.1"
4 |
5 | # 自身のアプリ名、リポジトリ名を記述
6 | set :application, 'MyNote'
7 |
8 | # どのリポジトリからアプリをpullするかを指定する
9 | set :repo_url, 'git@github.com:ohishikaito/MyNote.git'
10 |
11 | # バージョンが変わっても共通で参照するディレクトリを指定
12 | set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system', 'public/uploads')
13 |
14 | set :rbenv_type, :user
15 | set :rbenv_ruby, '2.5.1'
16 |
17 | # どの公開鍵を利用してデプロイするか
18 | set :ssh_options, auth_methods: ['publickey'],
19 | keys: ['~/.ssh/kaitoaws.pem']
20 |
21 | # プロセス番号を記載したファイルの場所
22 | set :unicorn_pid, -> { "#{shared_path}/tmp/pids/unicorn.pid" }
23 |
24 | # Unicornの設定ファイルの場所
25 | set :unicorn_config_path, -> { "#{current_path}/config/unicorn.rb" }
26 | set :keep_releases, 5
27 |
28 | # デプロイ処理が終わった後、Unicornを再起動するための記述
29 | after 'deploy:publishing', 'deploy:restart'
30 | namespace :deploy do
31 | task :restart do
32 | invoke 'unicorn:restart'
33 | end
34 | end
35 |
36 | # # Gemfile.lockを見てcapistranoのバージョンを入れる
37 | # lock "3.14.1"
38 |
39 | # # 自身のアプリ名、リポジトリ名を記述
40 | # set :application, 'MyNote'
41 |
42 | # # どのリポジトリからアプリをpullするかを指定する
43 | # set :repo_url, 'git@github.com:ohishikaito/MyNote.git'
44 |
45 | # # バージョンが変わっても共通で参照するディレクトリを指定
46 | # set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system', 'public/uploads')
47 |
48 | # set :rbenv_type, :user
49 | # set :rbenv_ruby, '2.5.1'
50 |
51 | # # chat-spaceで使ったpemを指定、どの公開鍵を利用してデプロイするか
52 | # set :ssh_options, auth_methods: ['publickey'],
53 | # keys: ['~/.ssh/kaitoaws.pem']
54 |
55 | # # どの公開鍵を利用してデプロイするか
56 | # set :unicorn_pid, -> { "#{shared_path}/tmp/pids/unicorn.pid" }
57 | # # Unicornの設定ファイルの場所
58 | # set :unicorn_config_path, -> { "#{current_path}/config/unicorn.rb" }
59 | # set :keep_releases, 5
60 |
61 | # set :linked_files, %w{ config/master.key }
62 | # # master.key用のシンボリックリンクを追加
63 | # # set :linked_files, %w[config/master.key]
64 |
65 | # # デプロイ処理が終わった後、Unicornを再起動するための記述
66 | # after 'deploy:publishing', 'deploy:restart'
67 | # namespace :deploy do
68 | # task :restart do
69 | # invoke 'unicorn:stop'
70 | # invoke 'unicorn:start'
71 | # end
72 |
73 | # # desc 'upload master.key'
74 | # # task :upload do
75 | # # on roles(:app) do |host|
76 | # # if test "[ ! -d #{shared_path}/config ]"
77 | # # execute "mkdir -p #{shared_path}/config"
78 | # # end
79 | # # # circleCIでマスターキーを取得するためコメントアウト
80 | # # upload!('config/master.key', "#{shared_path}/config/master.key")
81 | # # end
82 | # # end
83 | # # before :starting, 'deploy:upload'
84 | # # after :finishing, 'deploy:cleanup'
85 | # end
--------------------------------------------------------------------------------
/spec/models/notification_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Notification, type: :model do
4 | describe "#create" do
5 | context "Tweet関連の通知" do
6 | before do
7 | @tweet = build(:tweet)
8 | end
9 |
10 | it "コメントが行われた場合に保存できる" do
11 | comment = build(:comment)
12 | notification = build(:notification, tweet_id: @tweet.id, comment_id: comment.id, action: "comment")
13 | expect(notification).to be_valid
14 | end
15 |
16 | it "いいねが行われた場合に保存できる" do
17 | like = build(:like)
18 | notification = build(:notification, tweet_id: @tweet.id, action: "like")
19 | expect(notification).to be_valid
20 | end
21 | end
22 |
23 | context "ダイレクトメッセージ関連の通知" do
24 | it "ダイレクトメッセージが行われた場合に保存できる" do
25 | room = build(:room)
26 | message = build(:message)
27 | notification = build(:notification, room_id: room.id, message_id: message.id, action: "message")
28 | expect(notification).to be_valid
29 | end
30 | end
31 |
32 | context "フォロー関連の通知" do
33 | it "フォローが行われた場合に保存できる" do
34 | user1 = build(:user)
35 | user2 = build(:user)
36 | notification = build(:notification, visitor_id: user1.id, visited_id: user2.id, action: "follow")
37 | expect(notification).to be_valid
38 | end
39 | end
40 | end
41 |
42 | describe "アソシエーションのテスト" do
43 | let(:association) do
44 | described_class.reflect_on_association(target)
45 | end
46 |
47 | context "Messageモデルとのアソシエーション" do
48 | let(:target) { :message }
49 |
50 | it "Messageとの関連付けはbelongs_toであること" do
51 | expect(association.macro).to eq :belongs_to
52 | end
53 | end
54 |
55 | context "Tweetモデルとのアソシエーション" do
56 | let(:target) { :tweet }
57 |
58 | it "Tweetとの関連付けはbelongs_toであること" do
59 | expect(association.macro).to eq :belongs_to
60 | end
61 | end
62 |
63 | context "Roomモデルとのアソシエーション" do
64 | let(:target) { :room }
65 |
66 | it "Roomとの関連付けはbelongs_toであること" do
67 | expect(association.macro).to eq :belongs_to
68 | end
69 | end
70 |
71 | context "Commentモデルとのアソシエーション" do
72 | let(:target) { :comment }
73 |
74 | it "Commentとの関連付けはbelongs_toであること" do
75 | expect(association.macro).to eq :belongs_to
76 | end
77 | end
78 |
79 | context "visitorとのアソシエーション" do
80 | let(:target) { :visitor }
81 |
82 | it "Visitorとの関連付けはbelongs_toであること" do
83 | expect(association.macro).to eq :belongs_to
84 | end
85 | end
86 |
87 | context "visitedとのアソシエーション" do
88 | let(:target) { :visited }
89 |
90 | it "Visitedとの関連付けはbelongs_toであること" do
91 | expect(association.macro).to eq :belongs_to
92 | end
93 | end
94 | end
95 | end
96 |
--------------------------------------------------------------------------------
/app/views/activities/_activity.html.haml:
--------------------------------------------------------------------------------
1 | - visitor = activity.visitor
2 | - visited = activity.visited
3 | %li
4 | - case activity.action
5 |
6 | - when 'like'
7 | = link_to tweet_path(activity.tweet.id), class: "notification-top__items__item" do
8 | .notification-top__items__item--icon
9 | %i.fa.fa-heart.fa-2x{"aria-hidden" => "true", style: "color: red;"}
10 | .notification-top__items__item--content
11 | - if activity.tweet.user_id == visitor.id
12 | = image_tag "#{visitor.avatar}", class: "avatar"
13 | あなたの投稿
14 | - else
15 | = image_tag "#{visited.avatar}", class: "avatar"
16 | %strong
17 | = activity.tweet.user.nickname + 'さんの投稿'
18 | にいいねしました
19 | .notification-top__items__item--message
20 | = activity.tweet.title
21 | .notification-top__items__item--date
22 | = time_ago_in_words(activity.created_at).upcase
23 |
24 | - when 'comment'
25 | = link_to tweet_path(activity.tweet.id), class: "notification-top__items__item" do
26 | .notification-top__items__item--icon
27 | %i.fa.fa-edit.fa-2x{"aria-hidden" => "true"}
28 | .notification-top__items__item--content
29 | - if activity.tweet.user_id == visitor.id
30 | = image_tag "#{visited.avatar}", class: "avatar"
31 | あなたの投稿
32 | - else
33 | = image_tag "#{activity.tweet.user.avatar}", class: "avatar"
34 | %strong
35 | = activity.tweet.user.nickname + 'さんの投稿'
36 | にコメントしました
37 | .notification-top__items__item--message
38 | = truncate(Comment.find_by(id: activity.comment_id)&.content, length: 20)
39 | .notification-top__items__item--date
40 | = time_ago_in_words(activity.created_at).upcase
41 |
42 | - when 'message'
43 | = link_to room_path(activity.room), class: "notification-top__items__item" do
44 | .notification-top__items__item--icon
45 | %i.fa.fa-envelope-o.fa-2x{"aria-hidden" => "true"}
46 | .notification-top__items__item--content
47 | = image_tag "#{visited.avatar}", class: "avatar"
48 | %strong
49 | = visited.nickname
50 | さんにメッセージを送信しました
51 | .notification-top__items__item--message
52 | = truncate(Message.find_by(id: activity.message_id)&.message, length: 20)
53 | .notification-top__items__item--date
54 | = time_ago_in_words(activity.created_at).upcase
55 |
56 | - when 'follow'
57 | = link_to user_path(visited), class: "notification-top__items__item" do
58 | .notification-top__items__item--icon
59 | %i.fa.fa-user.fa-2x{"aria-hidden" => "true"}
60 | .notification-top__items__item--content
61 | = image_tag "#{visited.avatar}", class: "avatar"
62 | %strong
63 | = visited.nickname
64 | さんをフォローしました
65 | .notification-top__items__item--date
66 | = time_ago_in_words(activity.created_at).upcase
--------------------------------------------------------------------------------
/config/unicorn.rb:
--------------------------------------------------------------------------------
1 | # ../が一つ増えている
2 | app_path = File.expand_path('../../../', __FILE__)
3 | # unicornがエラー吐いたので直接ディレクトリを指定
4 | # app_path = File.expand_path('/var/www/MyNote')
5 |
6 | worker_processes 1
7 | # currentを指定
8 | working_directory "#{app_path}/current"
9 |
10 | # それぞれ、sharedの中を参照するよう変更
11 | listen "#{app_path}/shared/tmp/sockets/unicorn.sock"
12 | pid "#{app_path}/shared/tmp/pids/unicorn.pid"
13 | stderr_path "#{app_path}/shared/log/unicorn.stderr.log"
14 | stdout_path "#{app_path}/shared/log/unicorn.stdout.log"
15 |
16 | #Railsアプリケーションの応答を待つ上限時間を設定
17 | timeout 60
18 |
19 | # 以下は応用的な設定なので説明は割愛
20 |
21 | preload_app true
22 | GC.respond_to?(:copy_on_write_friendly=) && GC.copy_on_write_friendly = true
23 |
24 | check_client_connection false
25 |
26 | run_once = true
27 |
28 | before_fork do |server, worker|
29 | defined?(ActiveRecord::Base) &&
30 | ActiveRecord::Base.connection.disconnect!
31 |
32 | if run_once
33 | run_once = false # prevent from firing again
34 | end
35 |
36 | old_pid = "#{server.config[:pid]}.oldbin"
37 | if File.exist?(old_pid) && server.pid != old_pid
38 | begin
39 | sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
40 | Process.kill(sig, File.read(old_pid).to_i)
41 | rescue Errno::ENOENT, Errno::ESRCH => e
42 | logger.error e
43 | end
44 | end
45 | end
46 |
47 | after_fork do |_server, _worker|
48 | defined?(ActiveRecord::Base) && ActiveRecord::Base.establish_connection
49 | end
50 |
51 | # # ファイルが無いのでconfigフォルダにunicorn.rbを新規作成します
52 |
53 | # app_path = File.expand_path('../../../', __FILE__)
54 | # # 上記からEC2からunicornを再起動可能にするために修正(6/29) 問題があれば上記に戻す
55 | # # app_path = File.expand_path('/var/www/MyNote')
56 |
57 | # worker_processes 1
58 |
59 | # working_directory "#{app_path}/current"
60 | # pid "#{app_path}/shared/tmp/pids/unicorn.pid"
61 | # listen "#{app_path}/shared/tmp/sockets/unicorn.sock"
62 | # stderr_path "#{app_path}/shared/log/unicorn.stderr.log"
63 | # stdout_path "#{app_path}/shared/log/unicorn.stdout.log"
64 |
65 | # timeout 60
66 |
67 | # preload_app true
68 | # GC.respond_to?(:copy_on_write_friendly=) && GC.copy_on_write_friendly = true
69 |
70 | # check_client_connection false
71 |
72 | # run_once = true
73 |
74 | # before_fork do |server, worker|
75 | # defined?(ActiveRecord::Base) &&
76 | # ActiveRecord::Base.connection.disconnect!
77 |
78 | # if run_once
79 | # run_once = false # prevent from firing again
80 | # end
81 |
82 | # old_pid = "#{server.config[:pid]}.oldbin"
83 | # if File.exist?(old_pid) && server.pid != old_pid
84 | # begin
85 | # sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
86 | # Process.kill(sig, File.read(old_pid).to_i)
87 | # rescue Errno::ENOENT, Errno::ESRCH => e
88 | # logger.error e
89 | # end
90 | # end
91 | # end
92 |
93 | # after_fork do |_server, _worker|
94 | # defined?(ActiveRecord::Base) && ActiveRecord::Base.establish_connection
95 | # end
--------------------------------------------------------------------------------
/app/views/tweets/show.html.haml:
--------------------------------------------------------------------------------
1 | .wrapper
2 | .tweet-show
3 | .tweet-show__title
4 | .tweet-show__title__top
5 | = link_to user_path(@tweet.user.id), class: "tweet-show__title__top__user" do
6 | = image_tag "#{@tweet.user.avatar}", class: "avatar"
7 | = @tweet.user.nickname
8 | .tweet-show__title__top--edit
9 | - if current_user_has?(@tweet)
10 | = link_to '編集', edit_tweet_path(@tweet.id)
11 | = link_to '削除', tweet_path(@tweet.id), method: :delete, data: { confirm: '削除してよろしいですか?' }
12 | %h1.tweet-show__title--body
13 | = @tweet.title
14 | .tweet-show__title--date
15 | = @tweet.updated_at.strftime("%Y-%m-%d %H:%M")
16 | .tweet-show__content
17 | .tweet-show__content--text
18 | %span
19 | = markdown(@tweet.text)
20 | - if @tweet.image?
21 | .tweet-show__content--imageBox
22 | = image_tag "#{@tweet.image}", class: "tweet-show__content--image"
23 | .tweet-show__content--tags
24 | = render '/tweets/tag_list', tag_list: @tweet.tag_list
25 | .tweet-show__content__user
26 | = link_to user_path(@tweet.user.id), class: "tweet-show__content__user--top" do
27 | .display-flex
28 | = image_tag "#{@tweet.user.avatar}", class: "tweet-show__content__user--avatar"
29 | .tweet-show__content__user--names
30 | %strong
31 | = @tweet.user.nickname
32 | .tweet-show__content__user--follow
33 | = render "relationships/follow"
34 | .tweet-show__content__donation
35 | #donation_area
36 | = render "donations/donation"
37 | .tweet-show__content__like
38 | %div{id: "like_#{@tweet.id}"}
39 | = render 'likes/like', tweet: @tweet
40 | -# .tweet-show__content__like--link
41 | -# - if @tweet.liked_users.present?
42 | -# この記事にいいねした人を見る
43 | .tweet-show__comment
44 | #comment_form
45 | = render "comments/form"
46 | #comment_area
47 | = render "comments/top"
48 |
49 | .tweet-show__content__like--bgc
50 | .jc-center
51 | %ul.tweet-show__content__like__modal
52 | - @tweet.liked_users.each do |user|
53 | %li
54 | = link_to user_path(user.id), class: "tweet-show__content__like--user" do
55 | = image_tag "#{user.avatar}", class: "avatar"
56 | .tweet-show__content__like--user--name
57 | = user.nickname
58 | さん
59 | .tweet-show__content__like--close
60 | %i.btn.btn-dark
61 | 閉じる
62 |
63 | .donation
64 | .jc-center
65 | .donation-modal
66 | .donation-modal--header
67 | %strong
68 | = @tweet.user.nickname
69 | さんに寄付をする
70 | #donation_form
71 | = render "donations/form"
72 | -# .donation-modal--close
73 | -# %i.btn.btn-dark.donation-modal--close--btn
74 | 閉じる
--------------------------------------------------------------------------------
/app/views/notifications/_notification.html.haml:
--------------------------------------------------------------------------------
1 | - visitor = notification.visitor
2 | - visited = notification.visited
3 | %li
4 | - case notification.action
5 |
6 | - when 'like'
7 | = link_to tweet_path(notification.tweet.id), class: "notification-top__items__item" do
8 | .notification-top__items__item--icon
9 | %i.fa.fa-heart.fa-2x{"aria-hidden" => "true", style: "color: red;"}
10 | .notification-top__items__item--content
11 | = image_tag "#{visitor.avatar}", class: "avatar"
12 | %strong
13 | = visitor.nickname
14 | さんがあなたの投稿にいいねしました
15 | .notification-top__items__item--message
16 | = notification.tweet.title
17 | .notification-top__items__item--date
18 | = time_ago_in_words(notification.created_at).upcase
19 | - when 'comment'
20 | = link_to tweet_path(notification.tweet.id), class: "notification-top__items__item" do
21 | .notification-top__items__item--icon
22 | %i.fa.fa-edit.fa-2x{"aria-hidden" => "true"}
23 | .notification-top__items__item--content
24 | = image_tag "#{visitor.avatar}", class: "avatar"
25 | %strong
26 | = visitor.nickname
27 | さんが
28 | - if notification.tweet.user_id == visited.id
29 | あなたの投稿にコメントしました
30 | - elsif notification.tweet.user_id == visitor.id
31 | あなたのコメントに返信しました
32 | - else
33 | = image_tag "#{notification.tweet.user.avatar}", class: "avatar"
34 | %strong
35 | = notification.tweet.user.nickname + 'さんの投稿'
36 | にコメントしました
37 | .notification-top__items__item--message
38 | = truncate(Comment.find_by(id: notification.comment_id)&.content, length: 20)
39 | .notification-top__items__item--date
40 | = time_ago_in_words(notification.created_at).upcase
41 | - when 'message'
42 | = link_to room_path(notification.room), class: "notification-top__items__item" do
43 | .notification-top__items__item--icon
44 | %i.fa.fa-envelope-o.fa-2x{"aria-hidden" => "true"}
45 | .notification-top__items__item--content
46 | = image_tag "#{visitor.avatar}", class: "avatar"
47 | %strong
48 | = visitor.nickname
49 | さんがあなたにメッセージを送信しました
50 | .notification-top__items__item--message
51 | = truncate(Message.find_by(id: notification.message_id)&.message, length: 20)
52 | .notification-top__items__item--date
53 | = time_ago_in_words(notification.created_at).upcase
54 | - when 'follow'
55 | = link_to user_path(visitor), class: "notification-top__items__item" do
56 | .notification-top__items__item--icon
57 | %i.fa.fa-user.fa-2x{"aria-hidden" => "true"}
58 | .notification-top__items__item--content
59 | = image_tag "#{visitor.avatar}", class: "avatar"
60 | %strong
61 | = visitor.nickname
62 | さんがあなたをフォローしました
63 | .notification-top__items__item--date
64 | = time_ago_in_words(notification.created_at).upcase
--------------------------------------------------------------------------------
/app/models/tweet.rb:
--------------------------------------------------------------------------------
1 | class Tweet < ApplicationRecord
2 | # search-------------------------------------------------------------------
3 | def self.search(search)
4 | if search
5 | Tweet.where('title Like(?) OR text Like(?)', "%#{search}%", "%#{search}%")
6 | else
7 | Tweet.all
8 | end
9 | end
10 | # likes-------------------------------------------------------------------
11 | has_many :likes, dependent: :destroy
12 | has_many :liked_users, through: :likes, source: :user
13 |
14 | # 既にいいねしているか確認するメソッド
15 | def like_user(user_id)
16 | likes.find_by(user_id: user_id)
17 | end
18 |
19 | # tweets-------------------------------------------------------------------
20 | mount_uploader :image, ImageUploader
21 | # RailsとPumaの仕様上1500文字制限(おそらく空白も含まれるため)1000文字制限だと思わぬ不具合が発生する
22 | validates :text, presence: true, length: { maximum: 1500 }
23 | validates :title, presence: true, length: { maximum: 40 }
24 | # users-------------------------------------------------------------------
25 | belongs_to :user
26 | # comments-------------------------------------------------------------------
27 | has_many :comments, dependent: :destroy
28 | # notifications -------------------------------------------------------------------
29 | has_many :notifications, dependent: :destroy
30 |
31 | def create_notification_like!(current_user)
32 | # 既にいいねされてるか検索
33 | temp = Notification.where(["visitor_id = ? and visited_id = ? and tweet_id = ? and action = ? ", current_user.id, user_id, id, 'like'])
34 | # いいねされてなかったら通知を作成
35 | if temp.blank?
36 | notification = current_user.active_notifications.new(
37 | tweet_id: id,
38 | visited_id: user_id,
39 | action: 'like'
40 | )
41 | # 自分の投稿にはいいね通知済みにする
42 | notification.checked = true if notification.visitor_id == notification.visited_id
43 | notification.save if notification.valid?
44 | end
45 | end
46 |
47 | def create_notification_comment!(current_user, comment_id)
48 | # 自分以外にコメントしている人をすべて取得し、全員に通知を送る
49 | temp_ids = Comment.select(:user_id).where(tweet_id: id).where.not(user_id: current_user.id).distinct
50 | temp_ids.each do |temp_id|
51 | save_notification_comment!(current_user, comment_id, temp_id['user_id'])
52 | end
53 | # まだ誰もコメントしていない場合は、投稿者に通知を送る
54 | save_notification_comment!(current_user, comment_id, user_id) if temp_ids.blank?
55 | end
56 |
57 | def save_notification_comment!(current_user, comment_id, visited_id)
58 | # コメントは複数回することが考えられるため、1つの投稿に複数回通知する
59 | notification = current_user.active_notifications.new(
60 | tweet_id: id,
61 | comment_id: comment_id,
62 | visited_id: visited_id,
63 | action: 'comment'
64 | )
65 | # 自分の投稿に対するコメントの場合は、通知済みとする
66 | notification.checked = true if notification.visitor_id == notification.visited_id
67 | notification.save if notification.valid?
68 | end
69 | # ActAsTaggable-------------------------------------------------------------------
70 | acts_as_taggable
71 | # donations-------------------------------------------------------------------
72 | has_many :donations, dependent: :destroy
73 | end
74 |
--------------------------------------------------------------------------------