├── .github └── workflows │ └── linters.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .ruby-version ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── images │ │ └── .keep │ ├── javascripts │ │ ├── application.js │ │ ├── cable.js │ │ ├── channels │ │ │ └── .keep │ │ ├── comments.coffee │ │ ├── followings.coffee │ │ ├── likes.coffee │ │ ├── opinions.coffee │ │ ├── sessions.coffee │ │ └── users.coffee │ └── stylesheets │ │ ├── application.scss │ │ ├── comments.scss │ │ ├── followings.scss │ │ ├── likes.scss │ │ ├── opinions.scss │ │ ├── sessions.scss │ │ └── users.scss ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── controllers │ ├── application_controller.rb │ ├── concerns │ │ └── .keep │ ├── followings_controller.rb │ ├── likes_controller.rb │ ├── opinions_controller.rb │ ├── sessions_controller.rb │ └── users_controller.rb ├── helpers │ ├── application_helper.rb │ ├── followings_helper.rb │ ├── likes_helper.rb │ ├── opinions_helper.rb │ ├── sessions_helper.rb │ └── users_helper.rb ├── jobs │ └── application_job.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── application_record.rb │ ├── concerns │ │ └── .keep │ ├── following.rb │ ├── like.rb │ ├── opinion.rb │ └── user.rb └── views │ ├── layouts │ ├── _navbar.html.erb │ ├── application.html.erb │ ├── mailer.html.erb │ └── mailer.text.erb │ ├── opinions │ ├── _all-opinions.html.erb │ ├── _opinion.html.erb │ └── _tweets.html.erb │ ├── sessions │ └── new.html.erb │ └── users │ ├── _edit-form.html.erb │ ├── _left-sidebar.html.erb │ ├── _main-content-index.html.erb │ ├── _main-content-show.html.erb │ ├── _right-bar-index.html.erb │ ├── _right-bar-show.html.erb │ ├── _sign-up-form.html.erb │ ├── edit.html.erb │ ├── index.html.erb │ ├── new.html.erb │ └── show.html.erb ├── bin ├── bundle ├── rails ├── rake ├── setup ├── spring ├── update └── yarn ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── credentials.yml.enc ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── content_security_policy.rb │ ├── cookies_serializer.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml ├── puma.rb ├── routes.rb ├── spring.rb └── storage.yml ├── db ├── migrate │ ├── 20200808064724_create_users.rb │ ├── 20200808065156_create_opinions.rb │ ├── 20200808065322_create_followings.rb │ ├── 20200810180204_create_active_storage_tables.active_storage.rb │ └── 20200812152601_create_likes.rb ├── schema.rb └── seeds.rb ├── docs └── Coding_Twitter ERD.pdf ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── log └── .keep ├── package.json ├── public ├── 404.html ├── 422.html ├── 500.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── favicon.ico └── robots.txt ├── spec ├── Integration │ ├── sign_in_errors_spec.rb │ ├── sign_in_spec.rb │ ├── sign_up_errors_spec.rb │ ├── sign_up_spec.rb │ ├── tweet_errors_spec.rb │ └── tweet_spec.rb ├── examples.txt ├── models │ ├── following_spec.rb │ ├── like_spec.rb │ ├── opinion_spec.rb │ └── user_spec.rb ├── rails_helper.rb └── spec_helper.rb ├── storage └── .keep ├── test ├── application_system_test_case.rb ├── controllers │ ├── .keep │ ├── comments_controller_test.rb │ ├── followings_controller_test.rb │ ├── likes_controller_test.rb │ ├── opinions_controller_test.rb │ ├── sessions_controller_test.rb │ └── users_controller_test.rb ├── fixtures │ ├── .keep │ ├── files │ │ └── .keep │ ├── followings.yml │ ├── likes.yml │ ├── opinions.yml │ └── users.yml ├── helpers │ └── .keep ├── integration │ └── .keep ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── following_test.rb │ ├── like_test.rb │ ├── opinion_test.rb │ └── user_test.rb ├── system │ └── .keep └── test_helper.rb ├── tmp └── .keep └── vendor └── .keep /.github/workflows/linters.yml: -------------------------------------------------------------------------------- 1 | name: Linters 2 | 3 | on: pull_request 4 | 5 | env: 6 | FORCE_COLOR: 1 7 | 8 | jobs: 9 | rubocop: 10 | name: Rubocop 11 | runs-on: ubuntu-18.04 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-ruby@v1 15 | with: 16 | ruby-version: 2.6.x 17 | - name: Setup Rubocop 18 | run: | 19 | gem install --no-document rubocop:'~>0.81.0' # https://docs.rubocop.org/en/stable/installation/ 20 | [ -f .rubocop.yml ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/ror/.rubocop.yml 21 | - name: Rubocop Report 22 | run: rubocop --color 23 | stylelint: 24 | name: Stylelint 25 | runs-on: ubuntu-18.04 26 | steps: 27 | - uses: actions/checkout@v2 28 | - uses: actions/setup-node@v1 29 | with: 30 | node-version: "12.x" 31 | - name: Setup Stylelint 32 | run: | 33 | npm install --save-dev stylelint@13.3.x stylelint-scss@3.17.x stylelint-config-standard@20.0.x stylelint-csstree-validator 34 | [ -f .stylelintrc.json ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/ror/.stylelintrc.json 35 | - name: Stylelint Report 36 | run: npx stylelint "**/*.{css,scss}" -------------------------------------------------------------------------------- /.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 the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/* 16 | /tmp/* 17 | !/log/.keep 18 | !/tmp/.keep 19 | 20 | # Ignore uploaded files in development 21 | /storage/* 22 | !/storage/.keep 23 | 24 | /node_modules 25 | /yarn-error.log 26 | 27 | /public/assets 28 | .byebug_history 29 | 30 | # Ignore master key for decrypting credentials and more. 31 | /config/master.key 32 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - "db/**/*" 4 | - "bin/*" 5 | - "config/**/*" 6 | - "Guardfile" 7 | - "Rakefile" 8 | - "README.md" 9 | - "node_modules/**/*" 10 | 11 | DisplayCopNames: true 12 | 13 | Layout/LineLength: 14 | Max: 120 15 | Metrics/MethodLength: 16 | Include: 17 | - "app/controllers/*" 18 | - "app/models/*" 19 | Max: 20 20 | Metrics/AbcSize: 21 | Include: 22 | - "app/controllers/*" 23 | - "app/models/*" 24 | Max: 50 25 | Metrics/ClassLength: 26 | Max: 150 27 | Metrics/BlockLength: 28 | ExcludedMethods: ['describe'] 29 | Max: 30 30 | 31 | Style/Documentation: 32 | Enabled: false 33 | Style/ClassAndModuleChildren: 34 | Enabled: false 35 | Style/EachForSimpleLoop: 36 | Enabled: false 37 | Style/AndOr: 38 | Enabled: false 39 | Style/DefWithParentheses: 40 | Enabled: false 41 | Style/FrozenStringLiteralComment: 42 | EnforcedStyle: never 43 | 44 | Layout/HashAlignment: 45 | EnforcedColonStyle: key 46 | Layout/ExtraSpacing: 47 | AllowForAlignment: false 48 | Layout/MultilineMethodCallIndentation: 49 | Enabled: true 50 | EnforcedStyle: indented -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.6.5 -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | ruby '2.6.5' 5 | 6 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 7 | gem 'rails', '~> 5.2.4', '>= 5.2.4.3' 8 | # Use sqlite3 as the database for Active Record 9 | group :development, :test do 10 | gem 'sqlite3' 11 | end 12 | group :production do 13 | gem 'pg' 14 | end 15 | # Use Puma as the app server 16 | gem 'puma', '~> 3.11' 17 | # Use SCSS for stylesheets 18 | gem 'sass-rails', '~> 5.0' 19 | # Use Uglifier as compressor for JavaScript assets 20 | gem 'uglifier', '>= 1.3.0' 21 | # See https://github.com/rails/execjs#readme for more supported runtimes 22 | # gem 'mini_racer', platforms: :ruby 23 | 24 | # Use CoffeeScript for .coffee assets and views 25 | gem 'coffee-rails', '~> 4.2' 26 | # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks 27 | gem 'turbolinks', '~> 5' 28 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 29 | gem 'jbuilder', '~> 2.5' 30 | # Use Redis adapter to run Action Cable in production 31 | # gem 'redis', '~> 4.0' 32 | # Use ActiveModel has_secure_password 33 | # gem 'bcrypt', '~> 3.1.7' 34 | 35 | # Use ActiveStorage variant 36 | # gem 'mini_magick', '~> 4.8' 37 | 38 | # Use Capistrano for deployment 39 | # gem 'capistrano-rails', group: :development 40 | 41 | # Reduces boot times through caching; required in config/boot.rb 42 | gem 'bootsnap', '>= 1.1.0', require: false 43 | gem 'bootstrap', '~> 4.5' 44 | gem 'font-awesome-sass', '~> 5.12.0' 45 | gem 'gravatar_image_tag', '~> 1.2' 46 | 47 | group :development, :test do 48 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 49 | gem 'byebug', platforms: %i[mri mingw x64_mingw] 50 | gem 'database_cleaner' 51 | gem 'rspec-rails' 52 | end 53 | 54 | group :development do 55 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code. 56 | gem 'listen', '>= 3.0.5', '< 3.2' 57 | gem 'web-console', '>= 3.3.0' 58 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 59 | gem 'spring' 60 | gem 'spring-watcher-listen', '~> 2.0.0' 61 | end 62 | 63 | group :test do 64 | # Adds support for Capybara system testing and selenium driver 65 | gem 'capybara' 66 | gem 'rspec' 67 | gem 'selenium-webdriver' 68 | # Easy installation and use of chromedriver to run system tests with Chrome 69 | gem 'chromedriver-helper' 70 | end 71 | 72 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 73 | gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] 74 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (5.2.4.3) 5 | actionpack (= 5.2.4.3) 6 | nio4r (~> 2.0) 7 | websocket-driver (>= 0.6.1) 8 | actionmailer (5.2.4.3) 9 | actionpack (= 5.2.4.3) 10 | actionview (= 5.2.4.3) 11 | activejob (= 5.2.4.3) 12 | mail (~> 2.5, >= 2.5.4) 13 | rails-dom-testing (~> 2.0) 14 | actionpack (5.2.4.3) 15 | actionview (= 5.2.4.3) 16 | activesupport (= 5.2.4.3) 17 | rack (~> 2.0, >= 2.0.8) 18 | rack-test (>= 0.6.3) 19 | rails-dom-testing (~> 2.0) 20 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 21 | actionview (5.2.4.3) 22 | activesupport (= 5.2.4.3) 23 | builder (~> 3.1) 24 | erubi (~> 1.4) 25 | rails-dom-testing (~> 2.0) 26 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 27 | activejob (5.2.4.3) 28 | activesupport (= 5.2.4.3) 29 | globalid (>= 0.3.6) 30 | activemodel (5.2.4.3) 31 | activesupport (= 5.2.4.3) 32 | activerecord (5.2.4.3) 33 | activemodel (= 5.2.4.3) 34 | activesupport (= 5.2.4.3) 35 | arel (>= 9.0) 36 | activestorage (5.2.4.3) 37 | actionpack (= 5.2.4.3) 38 | activerecord (= 5.2.4.3) 39 | marcel (~> 0.3.1) 40 | activesupport (5.2.4.3) 41 | concurrent-ruby (~> 1.0, >= 1.0.2) 42 | i18n (>= 0.7, < 2) 43 | minitest (~> 5.1) 44 | tzinfo (~> 1.1) 45 | addressable (2.7.0) 46 | public_suffix (>= 2.0.2, < 5.0) 47 | archive-zip (0.12.0) 48 | io-like (~> 0.3.0) 49 | arel (9.0.0) 50 | autoprefixer-rails (9.8.4) 51 | execjs 52 | bindex (0.8.1) 53 | bootsnap (1.4.7) 54 | msgpack (~> 1.0) 55 | bootstrap (4.5.0) 56 | autoprefixer-rails (>= 9.1.0) 57 | popper_js (>= 1.14.3, < 2) 58 | sassc-rails (>= 2.0.0) 59 | builder (3.2.4) 60 | byebug (11.1.3) 61 | capybara (3.33.0) 62 | addressable 63 | mini_mime (>= 0.1.3) 64 | nokogiri (~> 1.8) 65 | rack (>= 1.6.0) 66 | rack-test (>= 0.6.3) 67 | regexp_parser (~> 1.5) 68 | xpath (~> 3.2) 69 | childprocess (3.0.0) 70 | chromedriver-helper (2.1.1) 71 | archive-zip (~> 0.10) 72 | nokogiri (~> 1.8) 73 | coffee-rails (4.2.2) 74 | coffee-script (>= 2.2.0) 75 | railties (>= 4.0.0) 76 | coffee-script (2.4.1) 77 | coffee-script-source 78 | execjs 79 | coffee-script-source (1.12.2) 80 | concurrent-ruby (1.1.6) 81 | crass (1.0.6) 82 | database_cleaner (1.8.5) 83 | diff-lcs (1.4.4) 84 | erubi (1.9.0) 85 | execjs (2.7.0) 86 | ffi (1.13.1) 87 | font-awesome-sass (5.12.0) 88 | sassc (>= 1.11) 89 | globalid (0.4.2) 90 | activesupport (>= 4.2.0) 91 | gravatar_image_tag (1.2.0) 92 | i18n (1.8.5) 93 | concurrent-ruby (~> 1.0) 94 | io-like (0.3.1) 95 | jbuilder (2.10.0) 96 | activesupport (>= 5.0.0) 97 | listen (3.1.5) 98 | rb-fsevent (~> 0.9, >= 0.9.4) 99 | rb-inotify (~> 0.9, >= 0.9.7) 100 | ruby_dep (~> 1.2) 101 | loofah (2.6.0) 102 | crass (~> 1.0.2) 103 | nokogiri (>= 1.5.9) 104 | mail (2.7.1) 105 | mini_mime (>= 0.1.1) 106 | marcel (0.3.3) 107 | mimemagic (~> 0.3.2) 108 | method_source (1.0.0) 109 | mimemagic (0.3.5) 110 | mini_mime (1.0.2) 111 | mini_portile2 (2.4.0) 112 | minitest (5.14.1) 113 | msgpack (1.3.3) 114 | nio4r (2.5.2) 115 | nokogiri (1.10.10) 116 | mini_portile2 (~> 2.4.0) 117 | pg (1.2.3) 118 | popper_js (1.16.0) 119 | public_suffix (4.0.5) 120 | puma (3.12.6) 121 | rack (2.2.3) 122 | rack-test (1.1.0) 123 | rack (>= 1.0, < 3) 124 | rails (5.2.4.3) 125 | actioncable (= 5.2.4.3) 126 | actionmailer (= 5.2.4.3) 127 | actionpack (= 5.2.4.3) 128 | actionview (= 5.2.4.3) 129 | activejob (= 5.2.4.3) 130 | activemodel (= 5.2.4.3) 131 | activerecord (= 5.2.4.3) 132 | activestorage (= 5.2.4.3) 133 | activesupport (= 5.2.4.3) 134 | bundler (>= 1.3.0) 135 | railties (= 5.2.4.3) 136 | sprockets-rails (>= 2.0.0) 137 | rails-dom-testing (2.0.3) 138 | activesupport (>= 4.2.0) 139 | nokogiri (>= 1.6) 140 | rails-html-sanitizer (1.3.0) 141 | loofah (~> 2.3) 142 | railties (5.2.4.3) 143 | actionpack (= 5.2.4.3) 144 | activesupport (= 5.2.4.3) 145 | method_source 146 | rake (>= 0.8.7) 147 | thor (>= 0.19.0, < 2.0) 148 | rake (13.0.1) 149 | rb-fsevent (0.10.4) 150 | rb-inotify (0.10.1) 151 | ffi (~> 1.0) 152 | regexp_parser (1.7.1) 153 | rspec (3.9.0) 154 | rspec-core (~> 3.9.0) 155 | rspec-expectations (~> 3.9.0) 156 | rspec-mocks (~> 3.9.0) 157 | rspec-core (3.9.2) 158 | rspec-support (~> 3.9.3) 159 | rspec-expectations (3.9.2) 160 | diff-lcs (>= 1.2.0, < 2.0) 161 | rspec-support (~> 3.9.0) 162 | rspec-mocks (3.9.1) 163 | diff-lcs (>= 1.2.0, < 2.0) 164 | rspec-support (~> 3.9.0) 165 | rspec-rails (4.0.1) 166 | actionpack (>= 4.2) 167 | activesupport (>= 4.2) 168 | railties (>= 4.2) 169 | rspec-core (~> 3.9) 170 | rspec-expectations (~> 3.9) 171 | rspec-mocks (~> 3.9) 172 | rspec-support (~> 3.9) 173 | rspec-support (3.9.3) 174 | ruby_dep (1.5.0) 175 | rubyzip (2.3.0) 176 | sass (3.7.4) 177 | sass-listen (~> 4.0.0) 178 | sass-listen (4.0.0) 179 | rb-fsevent (~> 0.9, >= 0.9.4) 180 | rb-inotify (~> 0.9, >= 0.9.7) 181 | sass-rails (5.1.0) 182 | railties (>= 5.2.0) 183 | sass (~> 3.1) 184 | sprockets (>= 2.8, < 4.0) 185 | sprockets-rails (>= 2.0, < 4.0) 186 | tilt (>= 1.1, < 3) 187 | sassc (2.4.0) 188 | ffi (~> 1.9) 189 | sassc-rails (2.1.2) 190 | railties (>= 4.0.0) 191 | sassc (>= 2.0) 192 | sprockets (> 3.0) 193 | sprockets-rails 194 | tilt 195 | selenium-webdriver (3.142.7) 196 | childprocess (>= 0.5, < 4.0) 197 | rubyzip (>= 1.2.2) 198 | spring (2.1.0) 199 | spring-watcher-listen (2.0.1) 200 | listen (>= 2.7, < 4.0) 201 | spring (>= 1.2, < 3.0) 202 | sprockets (3.7.2) 203 | concurrent-ruby (~> 1.0) 204 | rack (> 1, < 3) 205 | sprockets-rails (3.2.1) 206 | actionpack (>= 4.0) 207 | activesupport (>= 4.0) 208 | sprockets (>= 3.0.0) 209 | sqlite3 (1.4.2) 210 | thor (1.0.1) 211 | thread_safe (0.3.6) 212 | tilt (2.0.10) 213 | turbolinks (5.2.1) 214 | turbolinks-source (~> 5.2) 215 | turbolinks-source (5.2.0) 216 | tzinfo (1.2.7) 217 | thread_safe (~> 0.1) 218 | uglifier (4.2.0) 219 | execjs (>= 0.3.0, < 3) 220 | web-console (3.7.0) 221 | actionview (>= 5.0) 222 | activemodel (>= 5.0) 223 | bindex (>= 0.4.0) 224 | railties (>= 5.0) 225 | websocket-driver (0.7.3) 226 | websocket-extensions (>= 0.1.0) 227 | websocket-extensions (0.1.5) 228 | xpath (3.2.0) 229 | nokogiri (~> 1.8) 230 | 231 | PLATFORMS 232 | ruby 233 | 234 | DEPENDENCIES 235 | bootsnap (>= 1.1.0) 236 | bootstrap (~> 4.5) 237 | byebug 238 | capybara 239 | chromedriver-helper 240 | coffee-rails (~> 4.2) 241 | database_cleaner 242 | font-awesome-sass (~> 5.12.0) 243 | gravatar_image_tag (~> 1.2) 244 | jbuilder (~> 2.5) 245 | listen (>= 3.0.5, < 3.2) 246 | pg 247 | puma (~> 3.11) 248 | rails (~> 5.2.4, >= 5.2.4.3) 249 | rspec 250 | rspec-rails 251 | sass-rails (~> 5.0) 252 | selenium-webdriver 253 | spring 254 | spring-watcher-listen (~> 2.0.0) 255 | sqlite3 256 | turbolinks (~> 5) 257 | tzinfo-data 258 | uglifier (>= 1.3.0) 259 | web-console (>= 3.3.0) 260 | 261 | RUBY VERSION 262 | ruby 2.6.5p114 263 | 264 | BUNDLED WITH 265 | 2.1.4 266 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Odufu James Chigozie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # coding_twitter 2 | ## A social media platform where users tweet about coding challenges! 3 | > This is a social media application where users can sign up, create tweets about coding challenges and how to solve them, like other users' tweets, follow and unfollow other users and vice versa. 4 | It is my RoR Capstone Project of the Microverse Curriculum. 5 | 6 | ## Live Demo 7 | 8 | [Coding Twitter](https://fierce-island-70166.herokuapp.com/) 9 | 10 | ## Preview 11 | 12 | ### Login Page 13 | 14 | ![Login-page](https://user-images.githubusercontent.com/57812000/90254458-61cab280-de08-11ea-8eb5-a5f7376a7d0b.png) 15 | 16 | ### Index Page 17 | 18 | ![image](https://user-images.githubusercontent.com/57812000/90253649-18c62e80-de07-11ea-9c73-90dec5a30a25.png) 19 | 20 | ### Show Page 21 | 22 | ![Coding-show](https://user-images.githubusercontent.com/57812000/90254038-bb7ead00-de07-11ea-9fe3-798d36ca805e.png) 23 | 24 | ### Sign Up Page 25 | 26 | ![Sign-up-page1](https://user-images.githubusercontent.com/57812000/90254353-35af3180-de08-11ea-9cf8-ae66e6b46195.png) 27 | 28 | 29 | ## Video Presentation 30 | 31 | [![Coding Twitter Video]![image](https://user-images.githubusercontent.com/57812000/90954895-54cc4580-e43e-11ea-807a-c8aafd13b5c8.png)](https://www.loom.com/share/d1adbcabf16b4c18a9e665feac4caff8) 32 | 33 | ## Built With 34 | 35 | - Ruby 2.6.5 and Rails 5.2.4.3 36 | - Bootstrap 4 37 | - Deployed on Heroku 38 | 39 | To get a local copy up and running follow these steps: 40 | 41 | ### Prerequisites 42 | 43 | - Ruby: 2.7 44 | - Rails: 6.0.3 45 | - SQLite 46 | - Git 47 | 48 | ### Usage 49 | 50 | - Fork/Clone this project to your local machine and checkout to required branch: 51 | 52 | ```Bash 53 | git clone git@github.com:jamezjaz/coding_twitter.git 54 | cd coding_twitter 55 | ``` 56 | 57 | ```Ruby 58 | bundle install 59 | ``` 60 | 61 | Setup database with: 62 | 63 | ```Ruby 64 | yarn install --check-files 65 | rails db:create 66 | rails db:migrate 67 | ``` 68 | 69 | Start server with: 70 | 71 | ```Ruby 72 | rails server 73 | or 74 | rails s 75 | ``` 76 | ### Running tests 77 | 78 | ```Ruby 79 | bundle exec rspec --format documentation 80 | ``` 81 | (Note that to run the test, you will need to have chrome browser installed on your computer) 82 | 83 | ### Deployment 84 | The project was depolyed to heroku. If you set up AWS S3 for active storage and want to deploy your own copy to heroku, then you will need to allow heroku access the encrypted API keys by running 85 | 86 | ```Ruby 87 | heroku config:set RAILS_MASTER_KEY= 88 | ``` 89 | 90 | (The master key can be found in the `/config/master.key` file) 91 | 92 | ## Author 93 | 94 | 👤 **James Chigozie Odufu** 95 | 96 | - Github: [@githubhandle](https://github.com/jamezjaz) 97 | - Twitter: [@twitterhandle](https://twitter.com/jamezjaz90) 98 | - Linkedin: [@linkedin](https://linkedin.com/in/james-odufu-ba2a4a125) 99 | 100 | ## 🤝 Contributing 101 | 102 | Contributions, issues and feature requests are welcome! 103 | 104 | Feel free to check the [issues page](https://github.com/jamezjaz/coding_twitter/issues) 105 | 106 | Start by: 107 | 108 | - Forking the project 109 | - Cloning the project to your local machine 110 | - `cd` into the project directory 111 | - Run `git checkout -b your-branch-name.` 112 | - Make your contributions 113 | - Push your branch up to your forked repository 114 | - Open a Pull Request with a detailed description of the development(or master if not available) branch of the original project for a review 115 | 116 | ## Show your support 117 | 118 | Give a ⭐️ if you like this project! 119 | 120 | ## Acknowledgments 121 | 122 | - [Microverse](https://www.microverse.org) 123 | - [Gregoire Vella](https://www.behance.net/gregoirevella) for the design. 124 | 125 | ## 📝 License 126 | 127 | This project is [MIT](LICENSE) licensed with the exception of the design, which is under the [Creative Commons License](https://creativecommons.org/licenses/by-nc-nd/4.0/) and attributed to [Gregoire Vella](https://www.behance.net/gregoirevella). 128 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamezjaz/coding_twitter/03c53a163f81c888a8d45bde21198493138c5c37/app/assets/images/.keep -------------------------------------------------------------------------------- /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 rails-ujs 14 | //= require activestorage 15 | //= require turbolinks 16 | //= require_tree . 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/assets/javascripts/channels/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamezjaz/coding_twitter/03c53a163f81c888a8d45bde21198493138c5c37/app/assets/javascripts/channels/.keep -------------------------------------------------------------------------------- /app/assets/javascripts/comments.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/followings.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/likes.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/opinions.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/sessions.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/users.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /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, or any plugin's 6 | * vendor/assets/stylesheets directory 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 other CSS/SCSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | 17 | @import "bootstrap"; 18 | 19 | .navbar-custom { 20 | color: #53a8e9; 21 | } 22 | 23 | .icons { 24 | color: rgb(64, 64, 141); 25 | } 26 | 27 | .img-fluid { 28 | max-width: 6rem; 29 | } 30 | -------------------------------------------------------------------------------- /app/assets/stylesheets/comments.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the comments controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/followings.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the followings controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/likes.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the likes controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/opinions.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the opinions controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/sessions.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the sessions controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | 5 | .textarea, 6 | .login-btn { 7 | width: 20%; 8 | } 9 | 10 | .sign-up-btn { 11 | width: 100%; 12 | } 13 | 14 | .new-account { 15 | font-size: 1.5rem; 16 | } 17 | 18 | .sign-in-twitter, 19 | .sign-up-twitter { 20 | font-size: 2rem; 21 | } 22 | 23 | .sign-in-quote, 24 | .sign-up-quote { 25 | font-size: 2.5rem; 26 | } 27 | -------------------------------------------------------------------------------- /app/assets/stylesheets/users.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the users controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | .user-index { 5 | display: grid; 6 | grid-template-columns: 0.8fr 3fr; 7 | } 8 | 9 | .tweets { 10 | display: grid; 11 | grid-template-columns: 3fr 1fr; 12 | } 13 | 14 | .main-mid { 15 | display: grid; 16 | grid-template-rows: 1fr; 17 | } 18 | 19 | .left-sidebar { 20 | display: grid; 21 | grid-template-columns: repeat(2, 1fr); 22 | grid-auto-rows: minmax(2rem, auto); 23 | grid-template-areas: 24 | 'following followers' 25 | 'home home' 26 | 'profile profile' 27 | 'logout logout'; 28 | } 29 | 30 | .user { 31 | grid-area: user; 32 | } 33 | 34 | .following { 35 | grid-area: following; 36 | } 37 | 38 | .followers { 39 | grid-area: followers; 40 | } 41 | 42 | .home { 43 | grid-area: home; 44 | } 45 | 46 | .profile { 47 | grid-area: profile; 48 | } 49 | 50 | .logout { 51 | grid-area: logout; 52 | } 53 | 54 | .right-bar2 { 55 | display: grid; 56 | grid-template-rows: 1fr; 57 | } 58 | 59 | .left-aside { 60 | background-color: #213246; 61 | } 62 | 63 | .photo-name { 64 | display: grid; 65 | grid-template-columns: repeat(2, 1fr); 66 | } 67 | 68 | .profile-img-show { 69 | width: 10rem; 70 | } 71 | 72 | .user-info { 73 | display: grid; 74 | grid-template-rows: repeat(2, 1fr); 75 | grid-template-areas: 76 | 'profile-show profile-show' 77 | 'name name'; 78 | } 79 | 80 | .profile-show { 81 | grid-area: profile-show; 82 | } 83 | 84 | .name-show { 85 | grid-area: name; 86 | justify-content: center; 87 | } 88 | 89 | .identifier, 90 | .name { 91 | padding-left: 6rem; 92 | } 93 | 94 | .name, 95 | .name-show { 96 | font-size: large; 97 | } 98 | 99 | .cover-image { 100 | width: 100%; 101 | height: 20rem; 102 | } 103 | 104 | .gravatar-cover { 105 | width: 100%; 106 | height: 15rem; 107 | } 108 | 109 | .user-details { 110 | font-size: 1.5rem; 111 | } 112 | 113 | .follow-count { 114 | margin-left: 0.8rem; 115 | } 116 | 117 | .follower-name { 118 | padding: 2rem 1rem; 119 | } 120 | 121 | .follow_unfollow_links { 122 | padding-top: 2rem; 123 | } 124 | 125 | .followed-by-icon { 126 | padding-top: 1.3rem; 127 | } 128 | 129 | .users-photo-right-show { 130 | width: 5rem; 131 | } 132 | 133 | @media screen and (max-width: 768px) { 134 | .left-aside { 135 | display: none; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | def current_user 3 | @current_user ||= User.includes(:opinions, opinions: :likes).find(session[:user_id]) if session[:user_id] 4 | end 5 | 6 | helper_method :current_user 7 | 8 | def authenticate 9 | flash[:alert] = 'You need to login first!' unless current_user 10 | redirect_to new_session_path unless current_user 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamezjaz/coding_twitter/03c53a163f81c888a8d45bde21198493138c5c37/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/followings_controller.rb: -------------------------------------------------------------------------------- 1 | class FollowingsController < ApplicationController 2 | def create 3 | @following = current_user.followings.build(following_params) 4 | if @following.save 5 | flash[:notice] = 'Followed successfully' 6 | else 7 | flash[:alert] = 'Followed unsuccessfully' 8 | end 9 | redirect_to users_path 10 | end 11 | 12 | def destroy 13 | @following = current_user.followings.find_by(following_params) 14 | if @following.present? 15 | @following.destroy 16 | flash[:notice] = 'Unfollow successfull' 17 | else 18 | flash[:alert] = 'Unfollow unsuccessfull' 19 | end 20 | redirect_to users_path 21 | end 22 | 23 | private 24 | 25 | def following_params 26 | params.permit(:followed_id) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/controllers/likes_controller.rb: -------------------------------------------------------------------------------- 1 | class LikesController < ApplicationController 2 | def create 3 | @like = current_user.likes.build(like_params) 4 | if @like.save 5 | redirect_to users_path, notice: 'You liked this opinion' 6 | else 7 | redirect_to users_path, alert: 'You cannot like this opinion.' 8 | end 9 | end 10 | 11 | def destroy 12 | @like = current_user.likes.find_by(like_params) 13 | redirect_to users_path, alert: 'You unliked this opinion' if @like.destroy 14 | end 15 | 16 | private 17 | 18 | def like_params 19 | params.permit(:opinion_id) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/controllers/opinions_controller.rb: -------------------------------------------------------------------------------- 1 | class OpinionsController < ApplicationController 2 | before_action :authenticate 3 | 4 | def create 5 | @opinion = current_user.opinions.build(opinion_params) 6 | if @opinion.save 7 | flash[:notice] = 'Your tweet has been posted.' 8 | else 9 | flash[:alert] = 'Your tweet cannot be created.' 10 | end 11 | redirect_to users_path 12 | end 13 | 14 | def destroy 15 | @opinion = Opinion.find(params[:id]) 16 | @opinion.destroy 17 | flash[:notice] = 'Tweet successfully deleted!' 18 | redirect_to user_path(current_user.id) 19 | end 20 | 21 | private 22 | 23 | def opinion_params 24 | params.permit(:text) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class SessionsController < ApplicationController 2 | def new 3 | redirect_to users_path(session[:current_user_id]) if session[:current_user] 4 | end 5 | 6 | def create 7 | @user = User.find_by(session_params) 8 | if @user 9 | session[:user_id] = @user.id 10 | flash[:notice] = 'You have logged in successfully' 11 | redirect_to users_path 12 | else 13 | flash.now[:alert] = 'Invalid Credentials' 14 | render 'new' 15 | end 16 | end 17 | 18 | def destroy 19 | session.destroy 20 | flash[:alert] = 'You have logged out' 21 | redirect_to new_session_path 22 | end 23 | 24 | private 25 | 26 | def session_params 27 | params.permit(:username) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | before_action :authenticate, only: %i[index show] 3 | 4 | def index 5 | @user = User.all.includes(:opinions, :likes) 6 | @opinions = Opinion.all.includes(:likes, user: [:likes]) 7 | @follow_users = User.where(id: (@user.ids - current_user.following_users.ids)).order(created_at: :desc) 8 | @timeline_tweets = timeline_tweets 9 | end 10 | 11 | def new 12 | @user = User.new 13 | end 14 | 15 | def create 16 | @user = User.new(user_params) 17 | if @user.save 18 | flash[:notice] = 'Registered successfully' 19 | session[:user_id] = @user.id 20 | flash[:notice] = 'You have logged in successfully' 21 | redirect_to users_path 22 | else 23 | render 'new' 24 | end 25 | end 26 | 27 | def show 28 | @user = User.includes(:opinions, opinions: :likes).find(params[:id]) 29 | end 30 | 31 | def edit 32 | @user = User.find(current_user.id) 33 | end 34 | 35 | def update 36 | @user = User.find(current_user.id) 37 | @user.update(user_params) 38 | if @user.save 39 | flash[:notice] = 'Profile updated successfully' 40 | else 41 | flash[:alert] = 'Profile not updated' 42 | end 43 | redirect_to users_path 44 | end 45 | 46 | private 47 | 48 | def user_params 49 | params.require(:user).permit(:username, :fullname, :photo, :coverimage) 50 | end 51 | 52 | def timeline_tweets 53 | @timeline_tweets ||= current_user.followings_and_own_tweets 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | # rubocop:disable Layout/LineLength 2 | 3 | module ApplicationHelper 4 | def like_or_dislike(opinion) 5 | @like = current_user.likes.find_by(opinion_id: opinion) 6 | if @like 7 | link_to "".html_safe, like_path(opinion_id: opinion, id: current_user.id), method: :delete 8 | else 9 | link_to "".html_safe, likes_path(opinion_id: opinion), method: :post 10 | end 11 | end 12 | 13 | def image_check(user_image) 14 | if user_image.attached? 15 | link_to image_tag(user_image, class: 'img-fluid rounded'), user_path(current_user) 16 | else 17 | link_to image_tag('http://www.gravatar.com/avatar', class: 'rounded'), user_path(current_user) 18 | end 19 | end 20 | 21 | def image_check_right(user_image) 22 | if user_image.attached? 23 | link_to image_tag(user_image, class: 'img-fluid rounded-circle'), user_path(current_user) 24 | else 25 | link_to image_tag('http://www.gravatar.com/avatar', class: 'rounded-circle'), user_path(current_user) 26 | end 27 | end 28 | 29 | def image_check_cover(user_image) 30 | if user_image.attached? 31 | link_to image_tag(user_image, class: 'cover-image'), user_path(current_user) 32 | else 33 | link_to image_tag('http://www.gravatar.com/avatar', class: 'gravatar-cover'), user_path(current_user) 34 | end 35 | end 36 | end 37 | 38 | # rubocop:enable Layout/LineLength 39 | -------------------------------------------------------------------------------- /app/helpers/followings_helper.rb: -------------------------------------------------------------------------------- 1 | module FollowingsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/likes_helper.rb: -------------------------------------------------------------------------------- 1 | module LikesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/opinions_helper.rb: -------------------------------------------------------------------------------- 1 | module OpinionsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/sessions_helper.rb: -------------------------------------------------------------------------------- 1 | module SessionsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/users_helper.rb: -------------------------------------------------------------------------------- 1 | # rubocop:disable Layout/LineLength, Style/NegatedUnless 2 | 3 | module UsersHelper 4 | def follower_check(current_user, user) 5 | return unless user.id != current_user.id 6 | 7 | concat(content_tag(:div, class: 'd-flex border-top') do 8 | concat(content_tag(:div, class: 'pt-2 pr-2') do 9 | image_check_right(user.photo) 10 | end) 11 | concat(content_tag(:div, class: 'follower-name') do 12 | concat(content_tag(:h4, (link_to user.fullname, user_path(user.id)))) 13 | end) 14 | end) 15 | end 16 | 17 | def follow_unfollow_links(current_user, user) 18 | return unless user.id != current_user.id 19 | 20 | if user.follower?(current_user, user.id) 21 | link_to "".html_safe, following_path(followed_id: user.id), method: :delete 22 | else 23 | link_to "".html_safe, followings_path(followed_id: user.id), method: :post 24 | end 25 | end 26 | 27 | def followed_by(current_user, user) 28 | return unless user.id != current_user.id 29 | 30 | return unless !user.follower?(current_user, user.id) 31 | 32 | concat(content_tag(:div, class: 'd-flex border-top px-1') do 33 | concat(content_tag(:div, class: '') do 34 | image_check_right(user.photo) 35 | end) 36 | concat(content_tag(:div, class: 'pt-3') do 37 | concat(content_tag(:h4, (link_to user.fullname, user_path(user.id)))) 38 | end) 39 | end) 40 | link_to "".html_safe, followings_path(followed_id: user.id), method: :post 41 | end 42 | 43 | def edit_btn(current_user, user) 44 | link_to ''.html_safe, edit_user_path(current_user.id), method: :get if user.id == current_user.id 45 | end 46 | end 47 | 48 | # rubocop:enable Layout/LineLength, Style/NegatedUnless 49 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: 'from@example.com' 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamezjaz/coding_twitter/03c53a163f81c888a8d45bde21198493138c5c37/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/following.rb: -------------------------------------------------------------------------------- 1 | class Following < ApplicationRecord 2 | # belongs_to :followers, foreign_key: 'follower_id', class_name: 'User' 3 | # belongs_to :user, foreign_key: 'followed_id', class_name: 'User' 4 | 5 | belongs_to :user, foreign_key: 'follower_id' 6 | belongs_to :inverse_user, foreign_key: 'followed_id', class_name: 'User' 7 | end 8 | -------------------------------------------------------------------------------- /app/models/like.rb: -------------------------------------------------------------------------------- 1 | class Like < ApplicationRecord 2 | validates :user_id, uniqueness: { scope: :opinion_id } 3 | 4 | belongs_to :user 5 | belongs_to :opinion 6 | end 7 | -------------------------------------------------------------------------------- /app/models/opinion.rb: -------------------------------------------------------------------------------- 1 | # rubocop:disable Layout/LineLength 2 | 3 | class Opinion < ApplicationRecord 4 | validates :text, presence: true, length: { maximum: 1000, too_long: '1000 characters in post is the maximum allowed.' } 5 | 6 | belongs_to :user, foreign_key: 'author_id' 7 | scope :ordered_by_created_at, -> { order(created_at: :desc) } 8 | has_many :likes, dependent: :destroy 9 | end 10 | 11 | # rubocop:enable Layout/LineLength 12 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | validates :username, presence: true, uniqueness: true, length: { minimum: 3, maximum: 20 } 3 | validates :fullname, presence: true, length: { minimum: 5, maximum: 20 } 4 | 5 | has_many :opinions, foreign_key: 'author_id' 6 | has_many :likes, dependent: :destroy 7 | 8 | has_many :followings, foreign_key: 'follower_id' 9 | has_many :following_users, through: :followings, source: :inverse_user 10 | 11 | has_many :followers, foreign_key: 'followed_id', class_name: 'Following' 12 | has_many :follower_users, through: :followers, source: :user 13 | 14 | has_one_attached :photo 15 | has_one_attached :coverimage 16 | 17 | def follower?(current_user, user) 18 | current_user.following_users.ids.include?(user) 19 | end 20 | 21 | def followings_and_own_tweets 22 | Opinion.where(user: following_users).or(Opinion.where(user: self)).ordered_by_created_at 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/views/layouts/_navbar.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodingTwitter 5 | <%= csrf_meta_tags %> 6 | <%= csp_meta_tag %> 7 | 8 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> 9 | <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> 10 | 11 | 12 | 13 | 14 |
15 | <%= flash[:notice] %> 16 |
17 |
18 | <%= flash[:alert] %> 19 |
20 | <%= yield %> 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/views/opinions/_all-opinions.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% @timeline_tweets.each do |x| %> 3 |
4 |
5 | <%= image_check(x.user.photo) %> 6 |
7 |
8 | <%= link_to x.user.fullname, user_path(x.user) %> 9 |

<%= x.text %>

10 |
11 | <%= x.created_at.strftime("%Y/%m/%d") %> 12 | <%= like_or_dislike(x.id) %> 13 |
14 |
15 |
16 | <% end %> 17 |
-------------------------------------------------------------------------------- /app/views/opinions/_opinion.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= form_tag(opinions_path, method: "post") do %> 4 |
5 | <%= label_tag(:text, 'TWEET SOMETHING') %> 6 | <%= text_area_tag :text, "", class: 'form-control', placeholder: 'Compose new tweet', autofocus: "true", rows: "2" %> 7 |
8 |
9 | <%= submit_tag('Tweet', class: "btn btn-primary") %> 10 |
11 | <% end %> 12 |
13 |
-------------------------------------------------------------------------------- /app/views/opinions/_tweets.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% @user.opinions.ordered_by_created_at.each do |x| %> 3 |
4 |
5 | <%= image_check(x.user.photo) %> 6 |
7 |
8 | <%= link_to x.user.fullname, user_path(x.user) %> 9 |

<%= x.text %>

10 |
11 | <%= x.created_at.strftime("%Y/%m/%d") %> 12 | <%= like_or_dislike(x.id) %> 13 |
14 |
15 | <%= link_to 'Delete', opinion_path(x.id), method: :delete, data: {confirm: 'Really delete the article?'} %> 16 |
17 |
18 |
19 | <% end %> 20 |
-------------------------------------------------------------------------------- /app/views/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 5 | 6 | 7 | <%= form_tag '/sessions', :method => 'post' do %> 8 | <%= label_tag :username, 'Please enter Username' %>
9 | <%= text_field_tag :username, params[:username], class: 'textarea' %>
10 | <%= submit_tag "Login", class: 'btn btn-primary login-btn' %>
11 | 12 | <%= link_to "Sign up for Coding Twitter", new_user_path, class: 'new-account' %> 13 | <% end %> 14 |
15 | -------------------------------------------------------------------------------- /app/views/users/_edit-form.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= form_for(@user) do |form| %> 4 | <% @user.errors.full_messages.each do |error| %> 5 |
  • <%= error %>
  • 6 | <% end %> 7 | <%= form.label :username %> 8 | <%= form.text_field :username, class: 'form-control', placeholder: 'Enter Username' %> 9 | <%= form.label :fullname %> 10 | <%= form.text_field :fullname, class: 'form-control', placeholder: 'Enter Fullname' %> 11 | <%= form.label :photo %> 12 | <%= form.file_field :photo, class: 'form-control' %> 13 | <%= form.label :coverimage %> 14 | <%= form.file_field :coverimage, class: 'form-control' %> 15 | <%= form.submit 'Update', class: 'btn btn-primary sign-up-btn' %> 16 | <% end %> 17 |
    18 |
    -------------------------------------------------------------------------------- /app/views/users/_left-sidebar.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | <%= image_check(current_user.photo) %> 5 |
    6 |
    7 | <%=link_to current_user.fullname.upcase, user_path(current_user.id), class: 'text-secondary font-weight-bold' %> 8 |
    9 |
    10 | 11 | 34 |
    -------------------------------------------------------------------------------- /app/views/users/_main-content-index.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | 3 | <%= link_to "TWEETS", users_path, class: "active-link"%> 4 | <%= link_to "PHOTOS", users_path, class: "pl-3"%> 5 | <%= link_to "VIDEOS", users_path, class: "pl-3"%> 6 |
    7 | <%= render 'opinions/opinion' %> 8 |
    9 |

    RECENT TWEETS

    10 | <%= render 'opinions/all-opinions' %> 11 |
    -------------------------------------------------------------------------------- /app/views/users/_main-content-show.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | <%= image_check_cover(@user.coverimage) %> 4 |
    5 |
    6 |

    @<%= @user.username %>

    7 |
    8 |
    9 |
    ALL TWEETS
    10 | <%= render 'opinions/tweets' %> 11 |
    12 |
    -------------------------------------------------------------------------------- /app/views/users/_right-bar-index.html.erb: -------------------------------------------------------------------------------- 1 | 2 |
    3 | 6 |