├── log └── .keep ├── storage └── .keep ├── tmp ├── .keep └── pids │ └── .keep ├── vendor └── .keep ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── public ├── favicon.ico ├── apple-touch-icon.png ├── apple-touch-icon-precomposed.png ├── robots.txt ├── 500.html ├── 422.html └── 404.html ├── test ├── helpers │ └── .keep ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── user_test.rb │ ├── comment_test.rb │ ├── liking_test.rb │ ├── opinion_test.rb │ └── following_test.rb ├── system │ └── .keep ├── controllers │ ├── .keep │ ├── create_controller_test.rb │ ├── likings_controller_test.rb │ ├── sessions_controller_test.rb │ ├── comments_controller_test.rb │ ├── users_controller_test.rb │ └── opinions_controller_test.rb ├── fixtures │ ├── .keep │ ├── files │ │ └── .keep │ ├── likings.yml │ ├── comments.yml │ ├── users.yml │ ├── opinions.yml │ └── followings.yml ├── integration │ └── .keep ├── application_system_test_case.rb ├── channels │ └── application_cable │ │ └── connection_test.rb └── test_helper.rb ├── .ruby-version ├── app ├── assets │ ├── images │ │ ├── .keep │ │ ├── logo.png │ │ ├── logo-2.png │ │ ├── article-image3.jpg │ │ └── control-remedy.jpg │ ├── config │ │ └── manifest.js │ └── stylesheets │ │ └── application.scss ├── models │ ├── concerns │ │ └── .keep │ ├── application_record.rb │ ├── liking.rb │ ├── comment.rb │ ├── following.rb │ ├── opinion.rb │ └── user.rb ├── controllers │ ├── concerns │ │ └── .keep │ ├── application_controller.rb │ ├── likings_controller.rb │ ├── sessions_controller.rb │ ├── comments_controller.rb │ ├── users_controller.rb │ └── opinions_controller.rb ├── views │ ├── layouts │ │ ├── mailer.text.erb │ │ ├── mailer.html.erb │ │ └── application.html.erb │ ├── opinions │ │ ├── new.html.erb │ │ ├── create.html.erb │ │ ├── destroy.html.erb │ │ ├── update.html.erb │ │ ├── _opinion_form.html.erb │ │ ├── _opinion.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ └── show.html.erb │ ├── users │ │ ├── create.html.erb │ │ ├── update.html.erb │ │ ├── _follows_button.html.erb │ │ ├── _follow_button.html.erb │ │ ├── new.html.erb │ │ ├── _user.html.erb │ │ ├── _user_form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ └── show.html.erb │ ├── comments │ │ ├── create.html.erb │ │ ├── destroy.html.erb │ │ ├── update.html.erb │ │ ├── _comment_form.html.erb │ │ ├── _comment.html.erb │ │ └── edit.html.erb │ ├── sessions │ │ ├── create.html.erb │ │ ├── destroy.html.erb │ │ └── new.html.erb │ └── likings │ │ ├── _like_button.html.erb │ │ ├── _dislike_button.html.erb │ │ └── _liking_buttons.html.erb ├── helpers │ ├── create_helper.rb │ ├── likings_helper.rb │ ├── sessions_helper.rb │ ├── users_helper.rb │ ├── comments_helper.rb │ ├── application_helper.rb │ └── opinions_helper.rb ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── mailers │ └── application_mailer.rb ├── javascript │ ├── channels │ │ ├── index.js │ │ └── consumer.js │ └── packs │ │ └── application.js └── jobs │ └── application_job.rb ├── .browserslistrc ├── .rspec ├── app_video.png ├── app_screenshot.png ├── docs └── book_review_erd.png ├── config ├── webpack │ ├── environment.js │ ├── test.js │ ├── production.js │ └── development.js ├── spring.rb ├── environment.rb ├── cloudinary.yml ├── 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 │ └── content_security_policy.rb ├── boot.rb ├── cable.yml ├── credentials.yml.enc ├── routes.rb ├── application.rb ├── locales │ └── en.yml ├── storage.yml ├── puma.rb ├── environments │ ├── test.rb │ ├── development.rb │ └── production.rb ├── webpacker.yml └── database.yml ├── config.ru ├── db ├── migrate │ ├── 20200607115327_create_followings.rb │ ├── 20200609101408_create_likings.rb │ ├── 20200608213446_create_comments.rb │ ├── 20200607180501_add_user_foreign_key_to_opinions.rb │ ├── 20200606121305_create_users.rb │ ├── 20200609125526_add_user_id.rb │ ├── 20200607180132_create_opinions.rb │ ├── 20200608214445_add_author_id_and_opinion_id_to_comments.rb │ ├── 20200607132106_add_user_foreign_keys_to_followings.rb │ └── 20200606135300_create_active_storage_tables.active_storage.rb ├── seeds.rb └── schema.rb ├── spec ├── models │ ├── liking_spec.rb │ ├── following_spec.rb │ ├── comment_spec.rb │ ├── user_spec.rb │ └── opinion_spec.rb ├── features │ ├── opinion_management_spec.rb │ └── authentication_spec.rb ├── rails_helper.rb └── spec_helper.rb ├── Rakefile ├── bin ├── rake ├── rails ├── yarn ├── webpack ├── webpack-dev-server ├── spring ├── setup └── bundle ├── postcss.config.js ├── .stylelintrc.json ├── package.json ├── .stickler.yml ├── .gitignore ├── .github └── workflows │ └── linters.yml ├── .rubocop.yml ├── babel.config.js ├── Gemfile ├── README.md └── Gemfile.lock /log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /storage/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/helpers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/system/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tmp/pids/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.6.5 2 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | defaults 2 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/files/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/helpers/create_helper.rb: -------------------------------------------------------------------------------- 1 | module CreateHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/likings_helper.rb: -------------------------------------------------------------------------------- 1 | module LikingsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/sessions_helper.rb: -------------------------------------------------------------------------------- 1 | module SessionsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patriciachrysy/Rails-social-media/HEAD/app_video.png -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | -------------------------------------------------------------------------------- /app/views/opinions/new.html.erb: -------------------------------------------------------------------------------- 1 |

Post a new book review

2 | <%= render 'opinion_form' %> 3 | -------------------------------------------------------------------------------- /app_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patriciachrysy/Rails-social-media/HEAD/app_screenshot.png -------------------------------------------------------------------------------- /app/views/users/create.html.erb: -------------------------------------------------------------------------------- 1 |

Users#create

2 |

Find me in app/views/users/create.html.erb

3 | -------------------------------------------------------------------------------- /app/views/users/update.html.erb: -------------------------------------------------------------------------------- 1 |

Users#update

2 |

Find me in app/views/users/update.html.erb

3 | -------------------------------------------------------------------------------- /docs/book_review_erd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patriciachrysy/Rails-social-media/HEAD/docs/book_review_erd.png -------------------------------------------------------------------------------- /app/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patriciachrysy/Rails-social-media/HEAD/app/assets/images/logo.png -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /app/views/comments/create.html.erb: -------------------------------------------------------------------------------- 1 |

Comments#create

2 |

Find me in app/views/comments/create.html.erb

3 | -------------------------------------------------------------------------------- /app/views/comments/destroy.html.erb: -------------------------------------------------------------------------------- 1 |

Comments#destroy

2 |

Find me in app/views/comments/destroy.html.erb

3 | -------------------------------------------------------------------------------- /app/views/comments/update.html.erb: -------------------------------------------------------------------------------- 1 |

Comments#update

2 |

Find me in app/views/comments/update.html.erb

3 | -------------------------------------------------------------------------------- /app/views/opinions/create.html.erb: -------------------------------------------------------------------------------- 1 |

Opinions#create

2 |

Find me in app/views/opinions/create.html.erb

3 | -------------------------------------------------------------------------------- /app/views/opinions/destroy.html.erb: -------------------------------------------------------------------------------- 1 |

Opinions#destroy

2 |

Find me in app/views/opinions/destroy.html.erb

3 | -------------------------------------------------------------------------------- /app/views/opinions/update.html.erb: -------------------------------------------------------------------------------- 1 |

Opinions#update

2 |

Find me in app/views/opinions/update.html.erb

3 | -------------------------------------------------------------------------------- /app/views/sessions/create.html.erb: -------------------------------------------------------------------------------- 1 |

Sessions#create

2 |

Find me in app/views/sessions/create.html.erb

3 | -------------------------------------------------------------------------------- /app/views/sessions/destroy.html.erb: -------------------------------------------------------------------------------- 1 |

Sessions#destroy

2 |

Find me in app/views/sessions/destroy.html.erb

3 | -------------------------------------------------------------------------------- /config/webpack/environment.js: -------------------------------------------------------------------------------- 1 | const { environment } = require('@rails/webpacker') 2 | 3 | module.exports = environment 4 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /app/assets/images/logo-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patriciachrysy/Rails-social-media/HEAD/app/assets/images/logo-2.png -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | Spring.watch( 2 | ".ruby-version", 3 | ".rbenv-vars", 4 | "tmp/restart.txt", 5 | "tmp/caching-dev.txt" 6 | ) 7 | -------------------------------------------------------------------------------- /app/assets/images/article-image3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patriciachrysy/Rails-social-media/HEAD/app/assets/images/article-image3.jpg -------------------------------------------------------------------------------- /app/assets/images/control-remedy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patriciachrysy/Rails-social-media/HEAD/app/assets/images/control-remedy.jpg -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: 'from@example.com' 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /test/models/user_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UserTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/comment_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class CommentTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/liking_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class LikingTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/opinion_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class OpinionTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/liking.rb: -------------------------------------------------------------------------------- 1 | class Liking < ApplicationRecord 2 | belongs_to :opinion 3 | belongs_to :user 4 | 5 | validates :user_id, uniqueness: { scope: :opinion_id } 6 | end 7 | -------------------------------------------------------------------------------- /test/models/following_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class FollowingTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/fixtures/likings.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | status: false 5 | 6 | two: 7 | status: false 8 | -------------------------------------------------------------------------------- /test/fixtures/comments.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | content: MyText 5 | 6 | two: 7 | content: MyText 8 | -------------------------------------------------------------------------------- /config/webpack/test.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /config/cloudinary.yml: -------------------------------------------------------------------------------- 1 | production: 2 | cloud_name: patricia237 3 | api_key: '541554285859874' 4 | api_secret: dVBMAX1T8Dxhg0L6-O-O_3OzaGs 5 | enhance_image_tag: true 6 | static_file_support: true -------------------------------------------------------------------------------- /config/webpack/production.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'production' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /app/views/users/_follows_button.html.erb: -------------------------------------------------------------------------------- 1 | 2 | <%= link_to '', {controller: :users, action: :unfollow, id: user.id}, method: :delete, class: "fa fa-minus-circle grey" %> -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/webpack/development.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /app/views/likings/_like_button.html.erb: -------------------------------------------------------------------------------- 1 | 2 | <%= link_to '', opinion_liking_path(opinion, id: liking.id, status: true), method: :put, class: "fa fa-thumbs-up grey mx-2" %> -------------------------------------------------------------------------------- /test/application_system_test_case.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase 4 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400] 5 | end 6 | -------------------------------------------------------------------------------- /app/views/likings/_dislike_button.html.erb: -------------------------------------------------------------------------------- 1 | 2 | <%= link_to '', opinion_liking_path(opinion, id: liking.id, status: false), method: :put, class: "fa fa-thumbs-down grey mx-2" %> -------------------------------------------------------------------------------- /db/migrate/20200607115327_create_followings.rb: -------------------------------------------------------------------------------- 1 | class CreateFollowings < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :followings do |t| 4 | 5 | t.timestamps 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/models/liking_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Liking, type: :model do 4 | describe 'Associations' do 5 | it { should belong_to(:user) } 6 | it { should belong_to(:opinion) } 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20200609101408_create_likings.rb: -------------------------------------------------------------------------------- 1 | class CreateLikings < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :likings do |t| 4 | t.boolean :status 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20200608213446_create_comments.rb: -------------------------------------------------------------------------------- 1 | class CreateComments < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :comments do |t| 4 | t.text :content 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/views/users/_follow_button.html.erb: -------------------------------------------------------------------------------- 1 | <%= link_to '', {controller: :users, action: :follow, id: user.id}, method: :post, class:"fa fa-plus-circle grey px-2" %> 2 | <% if current_user.followed?(user) %> 3 | 4 | <% end %> -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/controllers/create_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class CreateControllerTest < ActionDispatch::IntegrationTest 4 | test 'should get destroy' do 5 | get create_destroy_url 6 | assert_response :success 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/fixtures/users.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | username: MyString 5 | fullname: MyString 6 | 7 | two: 8 | username: MyString 9 | fullname: MyString 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: rails_template_production 11 | -------------------------------------------------------------------------------- /app/javascript/channels/index.js: -------------------------------------------------------------------------------- 1 | // Load all the channels within this directory and all subdirectories. 2 | // Channel files must be named *_channel.js. 3 | 4 | const channels = require.context('.', true, /_channel\.js$/) 5 | channels.keys().forEach(channels) 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20200607180501_add_user_foreign_key_to_opinions.rb: -------------------------------------------------------------------------------- 1 | class AddUserForeignKeyToOpinions < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :opinions, :author_id, :integer 4 | 5 | add_index :opinions, :author_id 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20200606121305_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :users do |t| 4 | t.string :username 5 | t.string :fullname 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/views/likings/_liking_buttons.html.erb: -------------------------------------------------------------------------------- 1 | <%= link_to '', opinion_likings_path(opinion, status: true), method: :post, class: "fa fa-thumbs-up grey mx-2" %> 2 | <%= link_to '', opinion_likings_path(opinion, status: false), method: :post, class: "fa fa-thumbs-down grey mx-2" %> -------------------------------------------------------------------------------- /spec/models/following_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Following, type: :model do 4 | describe 'Associations' do 5 | it { should belong_to(:follower).class_name('User') } 6 | it { should belong_to(:followed).class_name('User') } 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/models/comment.rb: -------------------------------------------------------------------------------- 1 | class Comment < ApplicationRecord 2 | belongs_to :author, class_name: 'User' 3 | belongs_to :opinion 4 | 5 | validates :content, presence: true, length: { minimum: 30, maximum: 300 } 6 | 7 | scope :ordered_by_most_recent, -> { order(created_at: :desc) } 8 | end 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('postcss-import'), 4 | require('postcss-flexbugs-fixes'), 5 | require('postcss-preset-env')({ 6 | autoprefixer: { 7 | flexbox: 'no-2009' 8 | }, 9 | stage: 3 10 | }) 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20200609125526_add_user_id.rb: -------------------------------------------------------------------------------- 1 | class AddUserId < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :likings, :user_id, :integer 4 | add_column :likings, :opinion_id, :integer 5 | 6 | add_index :likings, :user_id 7 | add_index :likings, :opinion_id 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/javascript/channels/consumer.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 | import { createConsumer } from "@rails/actioncable" 5 | 6 | export default createConsumer() 7 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | # Automatically retry jobs that encountered a deadlock 3 | # retry_on ActiveRecord::Deadlocked 4 | 5 | # Most jobs are safe to ignore if the underlying records are no longer available 6 | # discard_on ActiveJob::DeserializationError 7 | end 8 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/channels/application_cable/connection_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase 4 | # test "connects with cookies" do 5 | # cookies.signed[:user_id] = 42 6 | # 7 | # connect 8 | # 9 | # assert_equal connection.user_id, "42" 10 | # end 11 | end 12 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard"], 3 | "plugins": ["stylelint-scss"], 4 | "rules": { 5 | "at-rule-no-unknown": null, 6 | "scss/at-rule-no-unknown": true 7 | }, 8 | "ignoreFiles": [ 9 | "build/**", 10 | "dist/**", 11 | "**/reset*.css", 12 | "**/bootstrap*.css" 13 | ] 14 | } -------------------------------------------------------------------------------- /db/migrate/20200607180132_create_opinions.rb: -------------------------------------------------------------------------------- 1 | class CreateOpinions < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :opinions do |t| 4 | t.string :book_name 5 | t.string :book_author 6 | t.date :published_at 7 | t.string :book_link 8 | t.text :content 9 | 10 | t.timestamps 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20200608214445_add_author_id_and_opinion_id_to_comments.rb: -------------------------------------------------------------------------------- 1 | class AddAuthorIdAndOpinionIdToComments < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :comments, :author_id, :integer 4 | add_column :comments, :opinion_id, :integer 5 | 6 | add_index :comments, :author_id 7 | add_index :comments, :opinion_id 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20200607132106_add_user_foreign_keys_to_followings.rb: -------------------------------------------------------------------------------- 1 | class AddUserForeignKeysToFollowings < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :followings, :follower_id, :integer 4 | add_column :followings, :followed_id, :integer 5 | 6 | add_index :followings, :follower_id 7 | add_index :followings, :followed_id 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/controllers/likings_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class LikingsControllerTest < ActionDispatch::IntegrationTest 4 | test 'should get create' do 5 | get likings_create_url 6 | assert_response :success 7 | end 8 | 9 | test 'should get destroy' do 10 | get likings_destroy_url 11 | assert_response :success 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) 7 | # Character.create(name: 'Luke', movie: movies.first) 8 | -------------------------------------------------------------------------------- /app/views/comments/_comment_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: [@opinion, @opinion.comments.new], local: true, class: "full-width") do |f|%> 2 |
3 | <%= f.label :content, hidden: true %> 4 | <%= f.text_area :content, placeholder: 'Leave a comment', class: "form-control" %> 5 |
6 | <%= f.submit 'Done', class: "btn bg-blue text-white" %> 7 | <% end %> -------------------------------------------------------------------------------- /test/fixtures/opinions.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | book_name: MyString 5 | book_author: MyString 6 | published_at: 2020-06-07 7 | book_link: MyString 8 | content: MyText 9 | 10 | two: 11 | book_name: MyString 12 | book_author: MyString 13 | published_at: 2020-06-07 14 | book_link: MyString 15 | content: MyText 16 | -------------------------------------------------------------------------------- /app/models/following.rb: -------------------------------------------------------------------------------- 1 | class Following < ApplicationRecord 2 | belongs_to :follower, class_name: 'User' 3 | belongs_to :followed, class_name: 'User' 4 | 5 | validates :follower_id, uniqueness: { scope: :followed_id } 6 | validate :self_following 7 | 8 | def self_following 9 | return unless follower_id == followed_id 10 | 11 | errors.add(:follower_id, 'You cannot follow yourself') 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/fixtures/followings.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | # This model initially had no columns defined. If you add columns to the 4 | # model remove the '{}' from the fixture names and add the columns immediately 5 | # below each fixture, per the syntax in the comments below 6 | # 7 | one: {} 8 | # column: value 9 | # 10 | two: {} 11 | # column: value 12 | -------------------------------------------------------------------------------- /app/views/users/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | Sign up 4 |
5 |
6 |
7 | <%= render 'user_form' %> 8 |
9 |
10 |
11 | 12 | -------------------------------------------------------------------------------- /app/helpers/users_helper.rb: -------------------------------------------------------------------------------- 1 | module UsersHelper 2 | def following_button(user) 3 | if current_user.follows?(user) 4 | render partial: 'users/follows_button', locals: { user: user } 5 | else 6 | render partial: 'users/follow_button', locals: { user: user } 7 | end 8 | end 9 | 10 | def edit_profile_button(user) 11 | link_to 'Update my profile', edit_user_path(user) if current_user.id == user.id 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV['RAILS_ENV'] ||= 'test' 2 | require_relative '../config/environment' 3 | require 'rails/test_help' 4 | 5 | class ActiveSupport::TestCase 6 | # Run tests in parallel with specified workers 7 | parallelize(workers: :number_of_processors) 8 | 9 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 10 | fixtures :all 11 | 12 | # Add more helper methods to be used by all tests here... 13 | end 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | wfQJPGGtpWTMqBWYw8UG/PAZx4DYBljrG9xLMRjx4DXImBbRTsrqsZEvH2YHDgl6i3W8Ls7OAorA72l2wAPUISV5ICnHz5pEnnhHu3V2nADtaX8xab6Mj9NqKVPKAun10AZH7si9vHH2r+mMe+gZ3uq4W8z0QOwS2j+PvLo5L0tItljd0NJy53c52InYYRfm8GNrMG8VYSDx9Z7FB7LufdR5ofj7x0QgWgNObe3i3wz70L8U6tEHt7vUo2EvgwWnCVjR49kWHA0I9odBf0GYVlwaUlYqBabC3D1tigXbB1zd6tLRjcLPEB9Y2CLYFynC2YIJUxYmxZ2eTRNjUInTvkvx9DVungwZlEvASymyaWJs3VAu3kszdbJiXPLroZ5Ep2TYpNNVZZDtos5AsfWdpGC/tWfjP6bbYI+6--r6xQQrgNL7PQCf5P--SMd3/x6lJlNmV9hRBdojZg== -------------------------------------------------------------------------------- /test/controllers/sessions_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SessionsControllerTest < ActionDispatch::IntegrationTest 4 | test 'should get new' do 5 | get sessions_new_url 6 | assert_response :success 7 | end 8 | 9 | test 'should get create' do 10 | get sessions_create_url 11 | assert_response :success 12 | end 13 | 14 | test 'should get destroy' do 15 | get sessions_destroy_url 16 | assert_response :success 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /bin/webpack: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 4 | ENV["NODE_ENV"] ||= "development" 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "bundler/setup" 11 | 12 | require "webpacker" 13 | require "webpacker/webpack_runner" 14 | 15 | APP_ROOT = File.expand_path("..", __dir__) 16 | Dir.chdir(APP_ROOT) do 17 | Webpacker::WebpackRunner.run(ARGV) 18 | end 19 | -------------------------------------------------------------------------------- /app/views/users/_user.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= image_tag user.photo.variant(resize_to_limit: [100, 100]), class: "user-pic" %> 4 |
5 |
6 | <%= link_to user.fullname, user_path(user), class: "text-dark small-text font-weight-bold" %> 7 |

@<%= user.username %>

8 |
9 |
10 | <%= following_button(user) %> 11 |
12 |
-------------------------------------------------------------------------------- /bin/webpack-dev-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 4 | ENV["NODE_ENV"] ||= "development" 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "bundler/setup" 11 | 12 | require "webpacker" 13 | require "webpacker/dev_server_runner" 14 | 15 | APP_ROOT = File.expand_path("..", __dir__) 16 | Dir.chdir(APP_ROOT) do 17 | Webpacker::DevServerRunner.run(ARGV) 18 | end 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rails_template", 3 | "private": true, 4 | "dependencies": { 5 | "@rails/actioncable": "^6.0.0", 6 | "@rails/activestorage": "^6.0.0", 7 | "@rails/ujs": "^6.0.0", 8 | "@rails/webpacker": "4.2.2", 9 | "turbolinks": "^5.2.0" 10 | }, 11 | "version": "0.1.0", 12 | "devDependencies": { 13 | "stylelint": "^13.3.3", 14 | "stylelint-config-standard": "^20.0.0", 15 | "stylelint-scss": "^3.17.2", 16 | "webpack-dev-server": "^3.11.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/helpers/comments_helper.rb: -------------------------------------------------------------------------------- 1 | module CommentsHelper 2 | def edit_comment_button(opinion, comment) 3 | return unless current_user&.owns_comment?(comment.id) 4 | 5 | link_to '', edit_opinion_comment_path(opinion, comment), class: 'fa fa-pencil blue mx-2' 6 | end 7 | 8 | def delete_comment_button(opinion, comment) 9 | return unless current_user&.owns_comment?(comment.id) 10 | 11 | link_to '', opinion_comment_path(opinion, comment), method: :delete, class: 'fa fa-trash blue mx-2' 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | def current_user 3 | session[:current_user] ? User.includes(:photo_attachment).find(session[:current_user]) : nil 4 | end 5 | 6 | def display_username(user) 7 | '@' + user.username if current_user 8 | end 9 | 10 | def most_popular_posts 11 | opinions = Opinion.all.includes(:author) 12 | liked_opinions = opinions.select { |opinion| opinion.likes_count.positive? } 13 | my_hash = {} 14 | liked_opinions.each do |op| 15 | my_hash[op.likes_count] = op 16 | end 17 | my_hash.sort_by.reverse_each { |key, _value| key }.to_h 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :users, except: [:destroy] 3 | resources :sessions, only: [:new, :create] 4 | resources :opinions do 5 | resources :comments, except: [:new, :index, :show] 6 | resources :likings, only: [:create, :update] 7 | end 8 | get '/sign-up', to: 'users#new' 9 | get '/sign-in', to: 'sessions#new' 10 | get '/sign-out', to: 'sessions#destroy' 11 | post '/follow/:id', to: 'users#follow' 12 | delete '/unfollow/:id', to: 'users#unfollow' 13 | root 'opinions#index' 14 | # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html 15 | end 16 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | def current_user 3 | session[:current_user] ? User.includes(:photo_attachment).find(session[:current_user]) : nil 4 | end 5 | 6 | def user_already_signed_in 7 | return if current_user.nil? 8 | 9 | flash[:notice] = 'Already signed in, sign out if you want to reconnect as another user or create a new account' 10 | redirect_to root_path 11 | false 12 | end 13 | 14 | def user_signed_in 15 | return unless current_user.nil? 16 | 17 | flash[:notice] = 'Please sign in.' 18 | redirect_to sign_in_path 19 | false 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /.stickler.yml: -------------------------------------------------------------------------------- 1 | # add the linters you want stickler to use for this project 2 | linters: 3 | rubocop: 4 | display_cop_names: true 5 | # indicate where is the config file for rubocop 6 | config: './rubocop.yml' 7 | 8 | # add the files here you want to be ignored by stickler 9 | files: 10 | ignore: 11 | - "bin/*" 12 | - "db/*" 13 | - "config/*" 14 | - "Guardfile" 15 | - "Rakefile" 16 | - "README.md" 17 | - "node_modules/**/*" 18 | 19 | # PLEASE DO NOT enable auto fixing options 20 | # if you need extra support from you linter - do it in your local env as described in README for this config 21 | 22 | # find full documentation here: https://stickler-ci.com/docs -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/controllers/comments_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class CommentsControllerTest < ActionDispatch::IntegrationTest 4 | test 'should get new' do 5 | get comments_new_url 6 | assert_response :success 7 | end 8 | 9 | test 'should get create' do 10 | get comments_create_url 11 | assert_response :success 12 | end 13 | 14 | test 'should get edit' do 15 | get comments_edit_url 16 | assert_response :success 17 | end 18 | 19 | test 'should get update' do 20 | get comments_update_url 21 | assert_response :success 22 | end 23 | 24 | test 'should get destroy' do 25 | get comments_destroy_url 26 | assert_response :success 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 RailsTemplate 10 | class Application < Rails::Application 11 | # Initialize configuration defaults for originally generated Rails version. 12 | config.load_defaults 6.0 13 | 14 | # Settings in config/environments/* take precedence over those specified here. 15 | # Application configuration can go into files in config/initializers 16 | # -- all .rb files in that directory are automatically loaded after loading 17 | # the framework and any gems in your application. 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/controllers/users_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UsersControllerTest < ActionDispatch::IntegrationTest 4 | test 'should get index' do 5 | get users_index_url 6 | assert_response :success 7 | end 8 | 9 | test 'should get new' do 10 | get users_new_url 11 | assert_response :success 12 | end 13 | 14 | test 'should get create' do 15 | get users_create_url 16 | assert_response :success 17 | end 18 | 19 | test 'should get show' do 20 | get users_show_url 21 | assert_response :success 22 | end 23 | 24 | test 'should get edit' do 25 | get users_edit_url 26 | assert_response :success 27 | end 28 | 29 | test 'should get update' do 30 | get users_update_url 31 | assert_response :success 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /app/controllers/likings_controller.rb: -------------------------------------------------------------------------------- 1 | class LikingsController < ApplicationController 2 | before_action :user_signed_in 3 | 4 | def create 5 | liking = Liking.new(status: params[:status]) 6 | liking.opinion = Opinion.find(params[:opinion_id]) 7 | liking.user = current_user 8 | if liking.save 9 | redirect_to root_path, notice: 'Thanks for judging this book review' 10 | else 11 | redirect_to root_path, errors: liking.errors.full_messages 12 | end 13 | end 14 | 15 | def update 16 | liking = Liking.find(params[:id]) 17 | if liking.update(status: params[:status]) 18 | redirect_to root_path, notice: 'You judgement has been updated' 19 | else 20 | redirect_to root_path, errors: liking.errors.full_messages 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/models/comment_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Comment, type: :model do 4 | describe 'Associations' do 5 | it { should belong_to(:author).class_name('User') } 6 | it { should belong_to(:opinion) } 7 | end 8 | 9 | describe 'Validator' do 10 | subject do 11 | Comment.create(opinion_id: 1, author_id: 1, content: 'It has been referred to as the greatest 12 | love story of all time, or perhaps the most tragic. Romeo and Juliet serves to satisfy both anyway, 13 | which for a 1595 play has obviously stuck around for a very long time.') 14 | end 15 | 16 | it { should validate_presence_of(:content) } 17 | it { should validate_length_of(:content).is_at_least(30) } 18 | it { should validate_length_of(:content).is_at_most(300) } 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class SessionsController < ApplicationController 2 | before_action :user_already_signed_in, except: %i[destroy] 3 | 4 | def new; end 5 | 6 | def create 7 | @user = User.find_by(sign_in_params) 8 | if @user.nil? 9 | flash[:errors] = ['Incorrect username, please try again or sign up if you do not have an account.'] 10 | redirect_to sign_in_path 11 | else 12 | session[:current_user] = @user.id 13 | flash[:notice] = 'Welcome ' + current_user.fullname 14 | redirect_to root_path 15 | end 16 | end 17 | 18 | def destroy 19 | session[:current_user] = nil 20 | flash[:notice] = 'Thanks for visiting.' 21 | redirect_to root_path 22 | end 23 | 24 | private 25 | 26 | def sign_in_params 27 | params.permit(:username) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/javascript/packs/application.js: -------------------------------------------------------------------------------- 1 | // This file is automatically compiled by Webpack, along with any other files 2 | // present in this directory. You're encouraged to place your actual application logic in 3 | // a relevant structure within app/javascript and only use these pack files to reference 4 | // that code so it'll be compiled. 5 | 6 | require("@rails/ujs").start() 7 | require("turbolinks").start() 8 | require("@rails/activestorage").start() 9 | require("channels") 10 | 11 | 12 | // Uncomment to copy all static images under ../images to the output folder and reference 13 | // them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>) 14 | // or the `imagePath` JavaScript helper below. 15 | // 16 | // const images = require.context('../images', true) 17 | // const imagePath = (name) => images(name, true) 18 | -------------------------------------------------------------------------------- /app/views/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | Sign in 4 |
5 |
6 |
7 | <%= form_with(url: "/sessions", method: "post") do %> 8 |
9 | <%= label_tag(:username, "Enter your username:", class: "dark-grey") %>* 10 | <%= text_field_tag(:username,'', class: "form-control") %> 11 |
12 |
13 | <%= submit_tag("Hop in", class: "btn bg-blue text-white") %> 14 |
15 | <% end %> 16 |
17 |
18 |
19 | 20 | -------------------------------------------------------------------------------- /app/models/opinion.rb: -------------------------------------------------------------------------------- 1 | class Opinion < ApplicationRecord 2 | belongs_to :author, class_name: 'User' 3 | has_many :comments, dependent: :destroy 4 | has_many :commenters, through: :comments, source: :author 5 | has_many :likings, dependent: :destroy 6 | has_many :likes, -> { where(status: true) }, class_name: 'Liking' 7 | has_many :dislikes, -> { where(status: false) }, class_name: 'Liking' 8 | has_many :judges, through: :likings, source: :user 9 | 10 | validates_presence_of :book_name, :book_author, :published_at, :content, :author_id 11 | validates :content, length: { minimum: 10, maximum: 1000 } 12 | 13 | scope :ordered_by_most_recent, -> { order(created_at: :desc) } 14 | 15 | def comments_count 16 | comments.count 17 | end 18 | 19 | def likes_count 20 | likes.count 21 | end 22 | 23 | def dislikes_count 24 | dislikes.count 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/controllers/opinions_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class OpinionsControllerTest < ActionDispatch::IntegrationTest 4 | test 'should get index' do 5 | get opinions_index_url 6 | assert_response :success 7 | end 8 | 9 | test 'should get new' do 10 | get opinions_new_url 11 | assert_response :success 12 | end 13 | 14 | test 'should get create' do 15 | get opinions_create_url 16 | assert_response :success 17 | end 18 | 19 | test 'should get show' do 20 | get opinions_show_url 21 | assert_response :success 22 | end 23 | 24 | test 'should get edit' do 25 | get opinions_edit_url 26 | assert_response :success 27 | end 28 | 29 | test 'should get update' do 30 | get opinions_update_url 31 | assert_response :success 32 | end 33 | 34 | test 'should get destroy' do 35 | get opinions_destroy_url 36 | assert_response :success 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /app/views/users/_user_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: @user, local: true) do |f|%> 2 |
3 | <%= f.label :fullname, class: "dark-grey" %> 4 | <%= f.text_field :fullname, class: "form-control" %> 5 |
6 | 7 |
8 | <%= f.label :username, class: "dark-grey" %> 9 | <%= f.text_field :username, class: "form-control" %> 10 |
11 | 12 |
13 | <%= f.label :photo, class: "dark-grey" %> 14 | <%= f.file_field :photo, class: "form-control", direct_upload: true %> 15 |
16 | 17 |
18 | <%= f.label :cover_image, class: "dark-grey" %> 19 | <%= f.file_field :cover_image, class: "form-control", direct_upload: true %> 20 |
21 | 22 |
23 | <%= f.submit 'Done', class: "btn bg-blue text-white" %> 24 |
25 | <% end %> -------------------------------------------------------------------------------- /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 https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /.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 | /db/*.sqlite3-* 14 | 15 | # Ignore all logfiles and tempfiles. 16 | /log/* 17 | /tmp/* 18 | !/log/.keep 19 | !/tmp/.keep 20 | 21 | # Ignore pidfiles, but keep the directory. 22 | /tmp/pids/* 23 | !/tmp/pids/ 24 | !/tmp/pids/.keep 25 | 26 | # Ignore uploaded files in development. 27 | /storage/* 28 | !/storage/.keep 29 | 30 | /public/assets 31 | .byebug_history 32 | 33 | # Ignore master key for decrypting credentials and more. 34 | /config/master.key 35 | 36 | /public/packs 37 | /public/packs-test 38 | /node_modules 39 | /yarn-error.log 40 | yarn-debug.log* 41 | .yarn-integrity 42 | 43 | node_modules/ 44 | -------------------------------------------------------------------------------- /app/views/comments/_comment.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= image_tag comment.author.photo.variant(resize_to_limit: [100, 100]), class: "profile-pic" %> 4 |
5 | 6 |
7 |
8 |
9 | <%= link_to comment.author.fullname, user_path(comment.author), class: "text-dark text-medium font-weight-bolder" %> 10 |
11 |
12 | <%= distance_of_time_in_words(DateTime.now, comment.created_at)%> 13 |
14 |
15 |
16 |

<%= comment.content %>

17 |
18 |
19 |
20 | <%= edit_comment_button(@opinion, comment) %> 21 | <%= delete_comment_button(@opinion, comment) %> 22 |
23 |
24 |
25 |
-------------------------------------------------------------------------------- /db/migrate/20200606135300_create_active_storage_tables.active_storage.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from active_storage (originally 20170806125915) 2 | class CreateActiveStorageTables < ActiveRecord::Migration[5.2] 3 | def change 4 | create_table :active_storage_blobs do |t| 5 | t.string :key, null: false 6 | t.string :filename, null: false 7 | t.string :content_type 8 | t.text :metadata 9 | t.bigint :byte_size, null: false 10 | t.string :checksum, null: false 11 | t.datetime :created_at, null: false 12 | 13 | t.index [ :key ], unique: true 14 | end 15 | 16 | create_table :active_storage_attachments do |t| 17 | t.string :name, null: false 18 | t.references :record, null: false, polymorphic: true, index: false 19 | t.references :blob, null: false 20 | 21 | t.datetime :created_at, null: false 22 | 23 | t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true 24 | t.foreign_key :active_storage_blobs, column: :blob_id 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path('..', __dir__) 6 | 7 | def system!(*args) 8 | system(*args) || abort("\n== Command #{args} failed ==") 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to setup or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at anytime and get an expectable outcome. 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 21 | # system('bin/yarn') 22 | 23 | # puts "\n== Copying sample files ==" 24 | # unless File.exist?('config/database.yml') 25 | # FileUtils.cp 'config/database.yml.sample', 'config/database.yml' 26 | # end 27 | 28 | puts "\n== Preparing database ==" 29 | system! 'bin/rails db:prepare' 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/views/opinions/_opinion_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: @opinion, local: true) do |f|%> 2 |
3 |
4 | <%= f.label :book_name, class: "dark-grey" %>* 5 | <%= f.text_field :book_name, class: "form-control" %> 6 |
7 | 8 |
9 | <%= f.label :book_author, class: "dark-grey" %>* 10 | <%= f.text_field :book_author, class: "form-control" %> 11 |
12 | 13 |
14 | <%= f.label :published_at, class: "dark-grey" %>* 15 | <%= f.date_field :published_at, class: "form-control" %> 16 |
17 | 18 |
19 | <%= f.label :book_link, class: "dark-grey" %> 20 | <%= f.url_field :book_link, class: "form-control" %> 21 |
22 |
23 | 24 |
25 | <%= f.label :content, hidden: true %> 26 | <%= f.text_area :content, placeholder: 'Your review of the book', class: "form-control" %> 27 |
28 | 29 | <%= f.submit 'Done', class: "btn bg-blue text-white" %> 30 | <% end %> -------------------------------------------------------------------------------- /.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 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}" -------------------------------------------------------------------------------- /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 | cloudinary: 10 | service: Cloudinary 11 | 12 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 13 | # amazon: 14 | # service: S3 15 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 16 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 17 | # region: us-east-1 18 | # bucket: your_own_bucket 19 | 20 | # Remember not to checkin your GCS keyfile to a repository 21 | # google: 22 | # service: GCS 23 | # project: your_project 24 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 25 | # bucket: your_own_bucket 26 | 27 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 28 | # microsoft: 29 | # service: AzureStorage 30 | # storage_account_name: your_account_name 31 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 32 | # container: your_container_name 33 | 34 | # mirror: 35 | # service: Mirror 36 | # primary: local 37 | # mirrors: [ amazon, google, microsoft ] 38 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - "db/**/*" 4 | - "bin/*" 5 | - "config/**/*" 6 | - "Guardfile" 7 | - "Rakefile" 8 | - "node_modules/**/*" 9 | 10 | DisplayCopNames: true 11 | 12 | Layout/LineLength: 13 | Max: 120 14 | Metrics/MethodLength: 15 | Include: 16 | - "app/controllers/*" 17 | - "app/models/*" 18 | Max: 20 19 | Metrics/AbcSize: 20 | Include: 21 | - "app/controllers/*" 22 | - "app/models/*" 23 | Max: 50 24 | Metrics/ClassLength: 25 | Max: 150 26 | Metrics/BlockLength: 27 | ExcludedMethods: ['describe'] 28 | Max: 30 29 | 30 | Style/Documentation: 31 | Enabled: false 32 | Style/ClassAndModuleChildren: 33 | Enabled: false 34 | Style/EachForSimpleLoop: 35 | Enabled: false 36 | Style/AndOr: 37 | Enabled: false 38 | Style/DefWithParentheses: 39 | Enabled: false 40 | Style/FrozenStringLiteralComment: 41 | EnforcedStyle: never 42 | 43 | Layout/HashAlignment: 44 | EnforcedColonStyle: key 45 | Layout/ExtraSpacing: 46 | AllowForAlignment: false 47 | Layout/MultilineMethodCallIndentation: 48 | Enabled: true 49 | EnforcedStyle: indented 50 | Lint/RaiseException: 51 | Enabled: false 52 | Lint/StructNewOverride: 53 | Enabled: false 54 | Style/HashEachMethods: 55 | Enabled: false 56 | Style/HashTransformKeys: 57 | Enabled: false 58 | Style/HashTransformValues: 59 | Enabled: false -------------------------------------------------------------------------------- /app/helpers/opinions_helper.rb: -------------------------------------------------------------------------------- 1 | module OpinionsHelper 2 | def edit_opinion_button(opinion) 3 | link_to '', edit_opinion_path(opinion), class: 'fa fa-pencil blue mx-2' if current_user&.owns?(opinion.id) 4 | end 5 | 6 | def delete_opinion_button(opinion) 7 | return unless current_user&.owns?(opinion.id) 8 | 9 | link_to '', opinion_path(opinion), method: :delete, class: 'fa fa-trash blue mx-2' 10 | end 11 | 12 | def read_opinion_button(opinion) 13 | link_to '', opinion.book_link, class: 'fa fa-book blue mx-2' if current_user && opinion.book_link 14 | end 15 | 16 | def display_comments(opinion) 17 | render partial: opinion.comments.ordered_by_most_recent 18 | end 19 | 20 | def liking_opinion_button(opinion) 21 | return unless current_user 22 | 23 | if current_user.liked?(opinion.id) 24 | render partial: 'likings/dislike_button', locals: { opinion: opinion, 25 | liking: opinion.likings.find_by(user_id: current_user.id) } 26 | elsif current_user.disliked?(opinion.id) 27 | render partial: 'likings/like_button', locals: { opinion: opinion, 28 | liking: opinion.likings.find_by(user_id: current_user.id) } 29 | else 30 | render partial: 'likings/liking_buttons', locals: { opinion: opinion } 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe User, type: :model do 4 | describe 'Associations' do 5 | it { should have_many(:sent_followings).with_foreign_key('follower_id').class_name('Following') } 6 | it { should have_many(:recieved_followings).with_foreign_key('followed_id').class_name('Following') } 7 | it { should have_many(:followers).through(:recieved_followings).source(:follower) } 8 | it { should have_many(:followeds).through(:sent_followings).source(:followed) } 9 | it { should have_many(:opinions).with_foreign_key('author_id') } 10 | it { should have_many(:comments).with_foreign_key('author_id') } 11 | it { should have_many(:commented_opinions).through(:comments).source(:opinion) } 12 | it { should have_many(:likings) } 13 | it { should have_many(:liked_opinions).through(:likings).source(:opinion) } 14 | end 15 | 16 | describe 'Validator' do 17 | subject { User.create(fullname: 'Test user', username: 'great_test') } 18 | 19 | it { should validate_presence_of(:fullname) } 20 | it { should validate_presence_of(:username) } 21 | it { should validate_presence_of(:photo) } 22 | it { should validate_presence_of(:cover_image) } 23 | it { should validate_length_of(:fullname).is_at_least(5) } 24 | it { should validate_length_of(:fullname).is_at_most(50) } 25 | it { should validate_length_of(:username).is_at_least(5) } 26 | it { should validate_length_of(:username).is_at_most(10) } 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/controllers/comments_controller.rb: -------------------------------------------------------------------------------- 1 | class CommentsController < ApplicationController 2 | before_action :user_signed_in 3 | before_action :comment_ownership 4 | 5 | def comment_ownership 6 | return unless current_user.owns_comment?(params[:id]) 7 | 8 | flash[:notice] = 'You do not own this comment, you cannot edit nor delete it!' 9 | redirect_to root_path 10 | false 11 | end 12 | 13 | def create 14 | comment = Comment.new(content: params[:comment][:content]) 15 | comment.opinion = Opinion.find(params[:opinion_id]) 16 | comment.author = current_user 17 | if comment.save 18 | redirect_to opinion_path(comment.opinion), notice: 'Thanks for commenting this review' 19 | else 20 | redirect_to opinion_path(comment.opinion), errors: comment.errors.full_messages 21 | end 22 | end 23 | 24 | def edit 25 | @comment = Comment.find(params[:id]) 26 | end 27 | 28 | def update 29 | comment = Comment.find(params[:id]) 30 | if comment.update(content: params[:comment][:content]) 31 | redirect_to opinion_path(comment.opinion), notice: 'Your comment has been updated' 32 | else 33 | redirect_to opinion_path(comment.opinion), errors: comment.errors.full_messages 34 | end 35 | end 36 | 37 | def destroy 38 | comment = Comment.find(params[:id]) 39 | if comment.destroy 40 | redirect_to root_path, notice: 'Your comment has been deleted' 41 | else 42 | redirect_to root_path, errors: comment.errors.full_messages 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/models/opinion_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Opinion, type: :model do 4 | describe 'Associations' do 5 | it { should belong_to(:author).class_name('User') } 6 | it { should have_many(:comments) } 7 | it { should have_many(:commenters).through(:comments).source(:author) } 8 | it { should have_many(:likings) } 9 | it { should have_many(:likes).class_name('Liking') } 10 | it { should have_many(:dislikes).class_name('Liking') } 11 | it { should have_many(:judges).through(:likings).source(:user) } 12 | end 13 | 14 | describe 'Validator' do 15 | subject do 16 | Opinion.create(book_name: 'Test user', book_author: 'great_test', published_at: DateTime.now, 17 | content: 'It has been referred to as the greatest love story of all time, or perhaps 18 | the most tragic. Romeo and Juliet serves to satisfy both anyway, which for a 1595 play has 19 | obviously stuck around for a very long time, which points to how good a book can turn out to be 20 | centuries after its author graced our good planet.', author_id: 1) 21 | end 22 | 23 | it { should validate_presence_of(:book_name) } 24 | it { should validate_presence_of(:book_author) } 25 | it { should validate_presence_of(:published_at) } 26 | it { should validate_presence_of(:content) } 27 | it { should validate_presence_of(:author_id) } 28 | it { should validate_length_of(:content).is_at_least(10) } 29 | it { should validate_length_of(:content).is_at_most(1000) } 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /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 | # # If you are using webpack-dev-server then specify webpack-dev-server host 15 | # policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development? 16 | 17 | # # Specify URI for violation reports 18 | # # policy.report_uri "/csp-violation-report-endpoint" 19 | # end 20 | 21 | # If you are using UJS then enable automatic nonce generation 22 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } 23 | 24 | # Set the nonce only to specific directives 25 | # Rails.application.config.content_security_policy_nonce_directives = %w(script-src) 26 | 27 | # Report CSP violations to a specified URI 28 | # For further information see the following documentation: 29 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 30 | # Rails.application.config.content_security_policy_report_only = true 31 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } 9 | threads min_threads_count, max_threads_count 10 | 11 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 12 | # 13 | port ENV.fetch("PORT") { 3000 } 14 | 15 | # Specifies the `environment` that Puma will run in. 16 | # 17 | environment ENV.fetch("RAILS_ENV") { "development" } 18 | 19 | # Specifies the `pidfile` that Puma will use. 20 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } 21 | 22 | # Specifies the number of `workers` to boot in clustered mode. 23 | # Workers are forked web server processes. If using threads and workers together 24 | # the concurrency of the application would be max `threads` * `workers`. 25 | # Workers do not work on JRuby or Windows (both of which do not support 26 | # processes). 27 | # 28 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 29 | 30 | # Use the `preload_app!` method when specifying a `workers` number. 31 | # This directive tells Puma to first boot the application and load code 32 | # before forking the application. This takes advantage of Copy On Write 33 | # process behavior so workers use less memory. 34 | # 35 | # preload_app! 36 | 37 | # Allow puma to be restarted by `rails restart` command. 38 | plugin :tmp_restart 39 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | var validEnv = ['development', 'test', 'production'] 3 | var currentEnv = api.env() 4 | var isDevelopmentEnv = api.env('development') 5 | var isProductionEnv = api.env('production') 6 | var isTestEnv = api.env('test') 7 | 8 | if (!validEnv.includes(currentEnv)) { 9 | throw new Error( 10 | 'Please specify a valid `NODE_ENV` or ' + 11 | '`BABEL_ENV` environment variables. Valid values are "development", ' + 12 | '"test", and "production". Instead, received: ' + 13 | JSON.stringify(currentEnv) + 14 | '.' 15 | ) 16 | } 17 | 18 | return { 19 | presets: [ 20 | isTestEnv && [ 21 | '@babel/preset-env', 22 | { 23 | targets: { 24 | node: 'current' 25 | } 26 | } 27 | ], 28 | (isProductionEnv || isDevelopmentEnv) && [ 29 | '@babel/preset-env', 30 | { 31 | forceAllTransforms: true, 32 | useBuiltIns: 'entry', 33 | corejs: 3, 34 | modules: false, 35 | exclude: ['transform-typeof-symbol'] 36 | } 37 | ] 38 | ].filter(Boolean), 39 | plugins: [ 40 | 'babel-plugin-macros', 41 | '@babel/plugin-syntax-dynamic-import', 42 | isTestEnv && 'babel-plugin-dynamic-import-node', 43 | '@babel/plugin-transform-destructuring', 44 | [ 45 | '@babel/plugin-proposal-class-properties', 46 | { 47 | loose: true 48 | } 49 | ], 50 | [ 51 | '@babel/plugin-proposal-object-rest-spread', 52 | { 53 | useBuiltIns: true 54 | } 55 | ], 56 | [ 57 | '@babel/plugin-transform-runtime', 58 | { 59 | helpers: false, 60 | regenerator: true, 61 | corejs: false 62 | } 63 | ], 64 | [ 65 | '@babel/plugin-transform-regenerator', 66 | { 67 | async: false 68 | } 69 | ] 70 | ].filter(Boolean) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | before_action :user_already_signed_in, only: %i[new create] 3 | before_action :user_signed_in, except: %i[new create] 4 | 5 | def index 6 | @users = User.all_users_except_me(current_user) 7 | end 8 | 9 | def new 10 | @user = User.new 11 | end 12 | 13 | def create 14 | @user = User.create(sign_up_params) 15 | if @user.save 16 | session[:current_user] = @user.id 17 | flash[:notice] = 'Welcome ' + current_user.fullname 18 | redirect_to root_path 19 | else 20 | flash[:errors] = @user.errors.full_messages 21 | render :new 22 | end 23 | end 24 | 25 | def show 26 | @user = User.find(params[:id]) 27 | end 28 | 29 | def edit 30 | @user = current_user 31 | end 32 | 33 | def update 34 | if current_user.update(sign_up_params) 35 | flash[:notice] = 'Your account has been successfully updated' 36 | redirect_to current_user 37 | else 38 | flash[:errors] = current_user.errors.full_messages 39 | @user = current_user 40 | render :edit 41 | end 42 | end 43 | 44 | def follow 45 | followed = User.find(params[:id]) 46 | following = current_user.sent_followings.build(followed_id: params[:id]) 47 | if following.save 48 | flash[:notice] = 'You are now following ' + followed.username 49 | else 50 | flash[:errors] = following.errors.full_messages 51 | end 52 | redirect_to users_path 53 | end 54 | 55 | def unfollow 56 | followed = User.find(params[:id]) 57 | following = Following.find_by(follower_id: current_user.id, followed_id: params[:id]) 58 | if following.destroy 59 | flash[:notice] = 'You are no more following ' + followed.username 60 | else 61 | flash[:errors] = ['Could not unfollow'] 62 | end 63 | redirect_to users_path 64 | end 65 | 66 | private 67 | 68 | def sign_up_params 69 | params.require(:user).permit(:username, :fullname, :photo, :cover_image) 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /app/views/opinions/_opinion.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= image_tag opinion.author.photo.variant(resize_to_limit: [100, 100]), class: "profile-pic" %> 4 |
5 | 6 |
7 |
8 |
9 | <%= link_to opinion.author.fullname, user_path(opinion.author), class: "text-dark text-medium font-weight-bolder" %> 10 |
11 |
12 | <%= distance_of_time_in_words(DateTime.now, opinion.created_at)%> 13 |
14 |
15 |
16 | 17 | <%= opinion.book_name %> 18 | by <%= opinion.book_author %>, 19 | <%= opinion.published_at %> 20 | 21 |
22 |
23 |

<%= opinion.content.truncate(100) %>

24 |
25 |
26 |
27 | <%= opinion.likes_count %> 28 | <%= opinion.dislikes_count %> 29 | <%= opinion.comments_count %> 30 |
31 |
32 | <%= link_to '', opinion_path(opinion), class: "fa fa-eye blue mx-2"%> 33 | <%= read_opinion_button(opinion) %> 34 | <%= edit_opinion_button(opinion) %> 35 | <%= delete_opinion_button(opinion) %> 36 | <%= liking_opinion_button(opinion) %> 37 |
38 |
39 |
40 |
41 | 42 | -------------------------------------------------------------------------------- /app/controllers/opinions_controller.rb: -------------------------------------------------------------------------------- 1 | class OpinionsController < ApplicationController 2 | before_action :user_signed_in, except: %i[index] 3 | before_action :ownership, only: %i[edit update] 4 | 5 | def ownership 6 | return unless current_user.owns?(params[:id]) 7 | 8 | flash[:notice] = 'You do not own this post, you cannot edit nor delete it!' 9 | redirect_to root_path 10 | false 11 | end 12 | 13 | def index 14 | @opinions = if current_user.nil? 15 | Opinion.all.ordered_by_most_recent 16 | else 17 | current_user.opinion_feed.ordered_by_most_recent 18 | end 19 | @opinion = Opinion.new 20 | end 21 | 22 | def new 23 | @opinion = Opinion.new 24 | end 25 | 26 | def create 27 | opinion = current_user.opinions.build(opinion_params) 28 | 29 | if opinion.save 30 | flash[:notice] = 'Your book review about ' + opinion.book_name + ' was published' 31 | else 32 | flash[:errors] = opinion.errors.full_messages 33 | end 34 | 35 | redirect_to root_path 36 | end 37 | 38 | def show 39 | @opinion = Opinion.find(params[:id]) 40 | @comments = Comment.all 41 | end 42 | 43 | def edit 44 | @opinion = Opinion.find(params[:id]) 45 | @opinions = Opinion.all 46 | end 47 | 48 | def update 49 | opinion = Opinion.find(params[:id]) 50 | if opinion.update(opinion_params) 51 | flash[:notice] = 'Your book review about ' + opinion.book_name + ' was updated' 52 | redirect_to opinion 53 | else 54 | flash[:errors] = opinion.errors.full_messages 55 | render :edit 56 | end 57 | end 58 | 59 | def destroy 60 | opinion = Opinion.find(params[:id]) 61 | if opinion.destroy 62 | flash[:notice] = 'Your book review about ' + opinion.book_name + ' was deleted' 63 | else 64 | flash[:errors] = ['Could not delete this book review'] 65 | end 66 | redirect_to root_path 67 | end 68 | 69 | private 70 | 71 | def opinion_params 72 | params.require(:opinion).permit(:book_name, :book_author, :published_at, :content, :book_link) 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # The test environment is used exclusively to run your application's 2 | # test suite. You never need to work with it otherwise. Remember that 3 | # your test database is "scratch space" for the test suite and is wiped 4 | # and recreated between test runs. Don't rely on the data there! 5 | 6 | Rails.application.configure do 7 | # Settings specified here will take precedence over those in config/application.rb. 8 | 9 | config.cache_classes = false 10 | config.action_view.cache_template_loading = true 11 | 12 | # Do not eager load code on boot. This avoids loading your whole application 13 | # just for the purpose of running a single test. If you are using a tool that 14 | # preloads Rails for running tests, you may have to set it to true. 15 | config.eager_load = false 16 | 17 | # Configure public file server for tests with Cache-Control for performance. 18 | config.public_file_server.enabled = true 19 | config.public_file_server.headers = { 20 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}" 21 | } 22 | 23 | # Show full error reports and disable caching. 24 | config.consider_all_requests_local = true 25 | config.action_controller.perform_caching = false 26 | config.cache_store = :null_store 27 | 28 | # Raise exceptions instead of rendering exception templates. 29 | config.action_dispatch.show_exceptions = false 30 | 31 | # Disable request forgery protection in test environment. 32 | config.action_controller.allow_forgery_protection = false 33 | 34 | # Store uploaded files on the local file system in a temporary directory. 35 | config.active_storage.service = :test 36 | 37 | config.action_mailer.perform_caching = false 38 | 39 | # Tell Action Mailer not to deliver emails to the real world. 40 | # The :test delivery method accumulates sent emails in the 41 | # ActionMailer::Base.deliveries array. 42 | config.action_mailer.delivery_method = :test 43 | 44 | # Print deprecation notices to the stderr. 45 | config.active_support.deprecation = :stderr 46 | 47 | # Raises error for missing translations. 48 | # config.action_view.raise_on_missing_translations = true 49 | end 50 | -------------------------------------------------------------------------------- /spec/features/opinion_management_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | require 'capybara/rails' 3 | 4 | RSpec.describe 'Opinion management', type: :system do 5 | describe 'Manage opinions' do 6 | before(:each) do 7 | visit sign_up_path 8 | fill_in 'Fullname', with: 'Capybara Testor' 9 | fill_in 'Username', with: 'Capybara' 10 | page.attach_file('user_photo', Rails.root + 'app/assets/images/article-image3.jpg') 11 | page.attach_file('user_cover_image', Rails.root + 'app/assets/images/control-remedy.jpg') 12 | click_on 'Done' 13 | end 14 | 15 | it 'Access the create opinion form' do 16 | expect(page).to have_content('Book name') 17 | end 18 | 19 | it 'Create a new review, should redirect to root page if all is ok' do 20 | fill_in 'Book name', with: 'Capybara Testing Book' 21 | fill_in 'Book author', with: 'Capybara' 22 | fill_in 'Published at', with: DateTime.now 23 | fill_in 'opinion_content', with: 'It has been referred to as the greatest love story of 24 | all time, or perhaps the most tragic. Romeo and Juliet serves to satisfy both anyway, 25 | which for a 1595 play has obviously stuck around for a very long time, which points to how 26 | good a book can turn out to be centuries after its author graced our good planet.' 27 | click_on 'Done' 28 | expect(page).to have_content('Capybara Testing Book') 29 | end 30 | 31 | it 'Show a review, should redirect to review page' do 32 | fill_in 'Book name', with: 'Capybara Testing Book' 33 | fill_in 'Book author', with: 'Capybara' 34 | fill_in 'Published at', with: DateTime.now 35 | fill_in 'opinion_content', with: 'It has been referred to as the greatest love 36 | story of all time, or perhaps the most tragic. Romeo and Juliet serves to satisfy both anyway, 37 | which for a 1595 play has obviously stuck around for a very long time, which points to how 38 | good a book can turn out to be centuries after its author graced our good planet.' 39 | click_on 'Done' 40 | 41 | expect(page).to have_content('Capybara Testing Book') 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /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', '~> 6.0.3', '>= 6.0.3.1' 8 | # Use postgresql as the database for Active Record 9 | gem 'pg', '>= 0.18', '< 2.0' 10 | # Use Puma as the app server 11 | gem 'puma', '~> 4.1' 12 | 13 | gem 'bootstrap', '~> 4.5' 14 | # Use SCSS for stylesheets 15 | gem 'sass-rails', '>= 6' 16 | # Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker 17 | gem 'webpacker', '~> 4.0' 18 | # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks 19 | gem 'turbolinks', '~> 5' 20 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 21 | gem 'jbuilder', '~> 2.7' 22 | # Use Redis adapter to run Action Cable in production 23 | # gem 'redis', '~> 4.0' 24 | # Use Active Model has_secure_password 25 | # gem 'bcrypt', '~> 3.1.7' 26 | 27 | # Use Active Storage variant 28 | gem 'image_processing', '~> 1.2' 29 | 30 | # Reduces boot times through caching; required in config/boot.rb 31 | gem 'bootsnap', '>= 1.4.2', require: false 32 | 33 | gem 'cloudinary' 34 | 35 | group :development, :test do 36 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 37 | gem 'byebug', platforms: %i[mri mingw x64_mingw] 38 | end 39 | 40 | group :development do 41 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code. 42 | gem 'listen', '~> 3.2' 43 | gem 'web-console', '>= 3.3.0' 44 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 45 | gem 'rubocop' 46 | gem 'spring' 47 | gem 'spring-watcher-listen', '~> 2.0.0' 48 | end 49 | 50 | group :test do 51 | # Adds support for Capybara system testing and selenium driver 52 | gem 'capybara', '>= 2.15' 53 | gem 'selenium-webdriver' 54 | # Easy installation and use of web drivers to run system tests with browsers 55 | gem 'rspec-rails' 56 | gem 'shoulda-matchers' 57 | gem 'webdrivers' 58 | end 59 | 60 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 61 | gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] 62 | -------------------------------------------------------------------------------- /app/views/users/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= image_tag @user.cover_image.variant(resize_to_limit: [700, 400]), class: "cover-image" %> 4 |
5 |
6 | Update my profile 7 |
8 |
9 |
10 | <%= render 'user_form' %> 11 |
12 |
13 |
14 |
15 |
16 | <%= image_tag @user.photo.variant(resize_to_limit: [100, 100]), class: "profile-pic rounded-circle"%> 17 |
18 | <%= @user.fullname %> 19 | @<%= @user.username %> 20 |
21 |
22 |
23 |
24 | <%= @user.opinions_count %> 25 | Reviews 26 |
27 |
28 | <%= @user.followeds_count %> 29 | Following 30 |
31 |
32 | <%= @user.followers_count %> 33 | Followers 34 |
35 |
36 | 37 |
38 |
39 | FOLLOWED BY 40 |
41 |
42 | <% @user.followers.each do |user| %> 43 | <%= render user %> 44 | <% end %> 45 |
46 |
47 |
-------------------------------------------------------------------------------- /config/webpacker.yml: -------------------------------------------------------------------------------- 1 | # Note: You must restart bin/webpack-dev-server for changes to take effect 2 | 3 | default: &default 4 | source_path: app/javascript 5 | source_entry_path: packs 6 | public_root_path: public 7 | public_output_path: packs 8 | cache_path: tmp/cache/webpacker 9 | check_yarn_integrity: false 10 | webpack_compile_output: true 11 | 12 | # Additional paths webpack should lookup modules 13 | # ['app/assets', 'engine/foo/app/assets'] 14 | resolved_paths: [] 15 | 16 | # Reload manifest.json on all requests so we reload latest compiled packs 17 | cache_manifest: false 18 | 19 | # Extract and emit a css file 20 | extract_css: false 21 | 22 | static_assets_extensions: 23 | - .jpg 24 | - .jpeg 25 | - .png 26 | - .gif 27 | - .tiff 28 | - .ico 29 | - .svg 30 | - .eot 31 | - .otf 32 | - .ttf 33 | - .woff 34 | - .woff2 35 | 36 | extensions: 37 | - .mjs 38 | - .js 39 | - .sass 40 | - .scss 41 | - .css 42 | - .module.sass 43 | - .module.scss 44 | - .module.css 45 | - .png 46 | - .svg 47 | - .gif 48 | - .jpeg 49 | - .jpg 50 | 51 | development: 52 | <<: *default 53 | compile: true 54 | 55 | # Verifies that correct packages and versions are installed by inspecting package.json, yarn.lock, and node_modules 56 | check_yarn_integrity: true 57 | 58 | # Reference: https://webpack.js.org/configuration/dev-server/ 59 | dev_server: 60 | https: false 61 | host: localhost 62 | port: 3035 63 | public: localhost:3035 64 | hmr: false 65 | # Inline should be set to true if using HMR 66 | inline: true 67 | overlay: true 68 | compress: true 69 | disable_host_check: true 70 | use_local_ip: false 71 | quiet: false 72 | pretty: false 73 | headers: 74 | 'Access-Control-Allow-Origin': '*' 75 | watch_options: 76 | ignored: '**/node_modules/**' 77 | 78 | 79 | test: 80 | <<: *default 81 | compile: true 82 | 83 | # Compile test packs to a separate directory 84 | public_output_path: packs-test 85 | 86 | production: 87 | <<: *default 88 | 89 | # Production depends on precompilation of packs prior to booting for performance. 90 | compile: false 91 | 92 | # Extract and emit a css file 93 | extract_css: true 94 | 95 | # Cache manifest.json for performance 96 | cache_manifest: true 97 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | validates_uniqueness_of :username 3 | validates :fullname, presence: true, length: { minimum: 5, maximum: 50 } 4 | validates :username, presence: true, length: { minimum: 5, maximum: 10 } 5 | validates :photo, presence: true 6 | validates :cover_image, presence: true 7 | 8 | scope :all_users_except_me, ->(user) { where.not(id: user).includes(:photo_attachment) } 9 | 10 | has_one_attached :photo 11 | has_one_attached :cover_image 12 | 13 | has_many :sent_followings, class_name: 'Following', foreign_key: 'follower_id' 14 | has_many :recieved_followings, class_name: 'Following', foreign_key: 'followed_id' 15 | has_many :followers, through: :recieved_followings, source: :follower 16 | has_many :followeds, through: :sent_followings, source: :followed 17 | 18 | has_many :opinions, foreign_key: 'author_id' 19 | 20 | has_many :comments, foreign_key: 'author_id' 21 | has_many :commented_opinions, through: :comments, source: :opinion 22 | 23 | has_many :likings 24 | has_many :liked_opinions, through: :likings, source: :opinion 25 | 26 | def followed?(user) 27 | followers.include?(user) 28 | end 29 | 30 | def follows?(user) 31 | followeds.include?(user) 32 | end 33 | 34 | def followers_count 35 | followers.count 36 | end 37 | 38 | def followeds_count 39 | followeds.count 40 | end 41 | 42 | def opinions_count 43 | opinions.count 44 | end 45 | 46 | def owns?(opinion_id) 47 | opinions.any? { |op| op.id == opinion_id } 48 | end 49 | 50 | def owns_comment?(comment_id) 51 | comments.any? { |comment| comment.id == comment_id } 52 | end 53 | 54 | def judged?(opinion_id) 55 | opinions_likings.any? { |opinion| opinion.id == opinion_id } 56 | end 57 | 58 | def liked?(opinion_id) 59 | likings.any? { |liking| liking.opinion_id == opinion_id && liking.status == true } 60 | end 61 | 62 | def disliked?(opinion_id) 63 | likings.any? { |liking| liking.opinion_id == opinion_id && liking.status == false } 64 | end 65 | 66 | def opinion_feed 67 | followeds_ids = [] 68 | followeds.includes(:photo_attachment).each { |f| followeds_ids << f.id } 69 | Opinion.where(author: (followeds_ids + [id])).includes(:author) 70 | end 71 | 72 | def follow_suggest 73 | (User.all.includes(:photo_attachment) - followeds - [User.find(id)])[0..2] 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable/disable caching. By default caching is disabled. 16 | # Run rails dev:cache to toggle caching. 17 | if Rails.root.join('tmp', 'caching-dev.txt').exist? 18 | config.action_controller.perform_caching = true 19 | config.action_controller.enable_fragment_cache_logging = true 20 | 21 | config.cache_store = :memory_store 22 | config.public_file_server.headers = { 23 | 'Cache-Control' => "public, max-age=#{2.days.to_i}" 24 | } 25 | else 26 | config.action_controller.perform_caching = false 27 | 28 | config.cache_store = :null_store 29 | end 30 | 31 | # Store uploaded files on the local file system (see config/storage.yml for options). 32 | config.active_storage.service = :local 33 | 34 | # Don't care if the mailer can't send. 35 | config.action_mailer.raise_delivery_errors = false 36 | 37 | config.action_mailer.perform_caching = false 38 | 39 | # Print deprecation notices to the Rails logger. 40 | config.active_support.deprecation = :log 41 | 42 | # Raise an error on page load if there are pending migrations. 43 | config.active_record.migration_error = :page_load 44 | 45 | # Highlight code that triggered database queries in logs. 46 | config.active_record.verbose_query_logs = true 47 | 48 | # Debug mode disables concatenation and preprocessing of assets. 49 | # This option may cause significant delays in view rendering with a large 50 | # number of complex assets. 51 | config.assets.debug = true 52 | 53 | # Suppress logger output for asset requests. 54 | config.assets.quiet = true 55 | 56 | # Raises error for missing translations. 57 | # config.action_view.raise_on_missing_translations = true 58 | 59 | # Use an evented file watcher to asynchronously detect changes in source code, 60 | # routes, locales, etc. This feature depends on the listen gem. 61 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 62 | end 63 | -------------------------------------------------------------------------------- /app/views/opinions/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | Update of "<%= @opinion.book_name%>" review 4 |
5 |
6 |
7 | <%= render 'opinion_form' %> 8 |
9 |
10 |
11 |
12 | <% if current_user %> 13 |
14 |
15 | Who to follow 16 |
17 |
18 | <% current_user.follow_suggest.each do |user| %> 19 | <%= render user %> 20 | <% end %> 21 |
22 | <%= link_to 'Popular account', users_path, class: "blue small-text" %> · 23 | <%= link_to 'Find friends', users_path, class: "blue small-text" %> 24 |
25 |
26 |
27 | <% end %> 28 |
29 |
30 | Popular reviews 31 |
32 |
33 | <% most_popular_posts.values[0..2].each do |opinion| %> 34 |
35 |
36 | <%= image_tag opinion.author.photo.variant(resize_to_limit: [100, 100]), class: "user-pic" %> 37 |
38 |
39 | <%= link_to opinion.author.fullname, user_path(opinion.author), class: "text-dark small-text font-weight-bold" %> 40 |

review of <%= opinion.book_name %>: <%= opinion.content.truncate(25) %>

41 |
42 |
43 | <%= link_to '', opinion_path(opinion), class: "fa fa-eye blue mx-2"%> 44 |
45 |
46 | <% end %> 47 |
48 | <%= link_to 'Make a review too', users_path, class: "blue small-text" %> 49 |
50 |
51 |
52 |
53 | -------------------------------------------------------------------------------- /app/views/users/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | Follow some intersting reviewrs 4 |
5 |
6 |
7 |
8 | <%= @users.count %> Reviewer(s) 9 |
10 | <% @users.each do |user| %> 11 | <%= render user %> 12 | <% end %> 13 |
14 |
15 |
16 |
17 | <% if current_user %> 18 |
19 |
20 | Who to follow 21 |
22 |
23 | <% current_user.follow_suggest.each do |user| %> 24 | <%= render user %> 25 | <% end %> 26 |
27 | <%= link_to 'Popular account', users_path, class: "blue small-text" %> · 28 | <%= link_to 'Find friends', users_path, class: "blue small-text" %> 29 |
30 |
31 |
32 | <% end %> 33 |
34 |
35 | Popular reviews 36 |
37 |
38 | <% most_popular_posts.values[0..2].each do |opinion| %> 39 |
40 |
41 | <%= image_tag opinion.author.photo.variant(resize_to_limit: [100, 100]), class: "user-pic" %> 42 |
43 |
44 | <%= link_to opinion.author.fullname, user_path(opinion.author), class: "text-dark small-text font-weight-bold" %> 45 |

review of <%= opinion.book_name %>: <%= opinion.content.truncate(25) %>

46 |
47 |
48 | <%= link_to '', opinion_path(opinion), class: "fa fa-eye blue mx-2"%> 49 |
50 |
51 | <% end %> 52 |
53 | <%= link_to 'Make a review too', users_path, class: "blue small-text" %> 54 |
55 |
56 |
57 |
58 | -------------------------------------------------------------------------------- /spec/features/authentication_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | require 'capybara/rails' 3 | 4 | RSpec.describe 'Athentication process', type: :system do 5 | describe 'signup a new user' do 6 | before(:each) do 7 | visit sign_up_path 8 | end 9 | 10 | it 'Redirect to homepage' do 11 | fill_in 'Fullname', with: 'Capybara Testor' 12 | fill_in 'Username', with: 'Capybara' 13 | page.attach_file('user_photo', Rails.root + 'app/assets/images/article-image3.jpg') 14 | page.attach_file('user_cover_image', Rails.root + 'app/assets/images/control-remedy.jpg') 15 | click_on 'Done' 16 | expect(page).to have_content('Capybara Testor') 17 | end 18 | 19 | it 'Does not redirect to homepage if the username is already taken' do 20 | fill_in 'Fullname', with: 'Capybara Testor' 21 | fill_in 'Username', with: 'Capybara' 22 | page.attach_file('user_photo', Rails.root + 'app/assets/images/article-image3.jpg') 23 | page.attach_file('user_cover_image', Rails.root + 'app/assets/images/control-remedy.jpg') 24 | click_on 'Done' 25 | 26 | click_on 'SIGN OUT' 27 | 28 | visit sign_up_path 29 | 30 | fill_in 'Fullname', with: 'Capybara Testor' 31 | fill_in 'Username', with: 'Capybara' 32 | page.attach_file('user_photo', Rails.root + 'app/assets/images/article-image3.jpg') 33 | page.attach_file('user_cover_image', Rails.root + 'app/assets/images/control-remedy.jpg') 34 | click_on 'Done' 35 | expect(page).to have_content('Username has already been taken') 36 | end 37 | end 38 | 39 | describe 'Sign a user in' do 40 | it 'Redirect to homepage' do 41 | visit sign_up_path 42 | fill_in 'Fullname', with: 'Capybara Testor' 43 | fill_in 'Username', with: 'Capybara' 44 | page.attach_file('user_photo', Rails.root + 'app/assets/images/article-image3.jpg') 45 | page.attach_file('user_cover_image', Rails.root + 'app/assets/images/control-remedy.jpg') 46 | click_on 'Done' 47 | 48 | visit click_on 'SIGN OUT' 49 | 50 | visit sign_in_path 51 | fill_in 'username', with: 'Capybara' 52 | click_on 'Hop in' 53 | expect(page).to have_content('Capybara Testor') 54 | end 55 | 56 | it 'Does not redirect to homepage when the username does not exist' do 57 | visit sign_up_path 58 | fill_in 'Fullname', with: 'Capybara Testor' 59 | fill_in 'Username', with: 'Capybara' 60 | page.attach_file('user_photo', Rails.root + 'app/assets/images/article-image3.jpg') 61 | page.attach_file('user_cover_image', Rails.root + 'app/assets/images/control-remedy.jpg') 62 | click_on 'Done' 63 | 64 | click_on 'SIGN OUT' 65 | 66 | visit sign_in_path 67 | fill_in 'username', with: 'capybara' 68 | click_on 'Hop in' 69 | expect(page).to have_content('Incorrect username, please try again or sign up if you do not have an account.') 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /app/views/opinions/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | All book reviews 4 |
5 |
6 | <% if current_user %> 7 |
8 | <%= render 'opinion_form' %> 9 |
10 | <% end %> 11 | 12 |
13 |
14 | <%= @opinions.count %> book review(s) 15 |
16 | <% @opinions.each do |opinion| %> 17 | <%= render opinion %> 18 | <% end %> 19 |
20 |
21 |
22 |
23 | <% if current_user %> 24 |
25 |
26 | Who to follow 27 |
28 |
29 | <% current_user.follow_suggest.each do |user| %> 30 | <%= render user %> 31 | <% end %> 32 |
33 | <%= link_to 'Popular account', users_path, class: "blue small-text" %> · 34 | <%= link_to 'Find friends', users_path, class: "blue small-text" %> 35 |
36 |
37 |
38 | <% end %> 39 |
40 |
41 | Popular reviews 42 |
43 |
44 | <% most_popular_posts.values[0..2].each do |opinion| %> 45 |
46 |
47 | <%= image_tag opinion.author.photo.variant(resize_to_limit: [100, 100]), class: "user-pic" %> 48 |
49 |
50 | <%= link_to opinion.author.fullname, user_path(opinion.author), class: "text-dark small-text font-weight-bold" %> 51 |

review of <%= opinion.book_name %>: <%= opinion.content.truncate(25) %>

52 |
53 |
54 | <%= link_to '', opinion_path(opinion), class: "fa fa-eye blue mx-2"%> 55 |
56 |
57 | <% end %> 58 |
59 | <%= link_to 'Make a review too', users_path, class: "blue small-text" %> 60 |
61 |
62 |
63 |
64 | -------------------------------------------------------------------------------- /app/views/comments/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | Update my comment 4 |
5 |
6 | <% if current_user %> 7 |
8 | <%= form_with(model: [@comment.opinion, @comment], local: true, class: "full-width") do |f|%> 9 |
10 | <%= f.label :content, hidden: true %> 11 | <%= f.text_area :content, placeholder: 'Leave a comment', class: "form-control" %> 12 |
13 | <%= f.submit 'Done', class: "btn bg-blue text-white" %> 14 | <% end %> 15 |
16 | <% end %> 17 |
18 |
19 |
20 | <% if current_user %> 21 |
22 |
23 | Who to follow 24 |
25 |
26 | <% current_user.follow_suggest.each do |user| %> 27 | <%= render user %> 28 | <% end %> 29 |
30 | <%= link_to 'Popular account', users_path, class: "blue small-text" %> · 31 | <%= link_to 'Find friends', users_path, class: "blue small-text" %> 32 |
33 |
34 |
35 | <% end %> 36 |
37 |
38 | Popular reviews 39 |
40 |
41 | <% most_popular_posts.values[0..2].each do |opinion| %> 42 |
43 |
44 | <%= image_tag opinion.author.photo.variant(resize_to_limit: [100, 100]), class: "user-pic" %> 45 |
46 |
47 | <%= link_to opinion.author.fullname, user_path(opinion.author), class: "text-dark small-text font-weight-bold" %> 48 |

review of <%= opinion.book_name %>: <%= opinion.content.truncate(25) %>

49 |
50 |
51 | <%= link_to '', opinion_path(opinion), class: "fa fa-eye blue mx-2"%> 52 |
53 |
54 | <% end %> 55 |
56 | <%= link_to 'Make a review too', users_path, class: "blue small-text" %> 57 |
58 |
59 |
60 |
61 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 9.1 and up are supported. 2 | # 3 | # Install the pg driver: 4 | # gem install pg 5 | # On OS X with Homebrew: 6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config 7 | # On OS X with MacPorts: 8 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config 9 | # On Windows: 10 | # gem install pg 11 | # Choose the win32 build. 12 | # Install PostgreSQL and put its /bin directory on your path. 13 | # 14 | # Configure Using Gemfile 15 | # gem 'pg' 16 | # 17 | default: &default 18 | adapter: postgresql 19 | encoding: unicode 20 | # For details on connection pooling, see Rails configuration guide 21 | # http://guides.rubyonrails.org/configuring.html#database-pooling 22 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 23 | 24 | 25 | development: 26 | <<: *default 27 | database: book_review_development 28 | 29 | # The specified database role being used to connect to postgres. 30 | # To create additional roles in postgres see `$ createuser --help`. 31 | # When left blank, postgres will use the default role. This is 32 | # the same name as the operating system user that initialized the database. 33 | #username: ror_social_scaffold 34 | 35 | # The password associated with the postgres role (username). 36 | #password: 37 | 38 | # Connect on a TCP socket. Omitted by default since the client uses a 39 | # domain socket that doesn't need configuration. Windows does not have 40 | # domain sockets, so uncomment these lines. 41 | #host: localhost 42 | 43 | # The TCP port the server listens on. Defaults to 5432. 44 | # If your server runs on a different port number, change accordingly. 45 | #port: 5432 46 | 47 | # Schema search path. The server defaults to $user,public 48 | #schema_search_path: myapp,sharedapp,public 49 | 50 | # Minimum log levels, in increasing order: 51 | # debug5, debug4, debug3, debug2, debug1, 52 | # log, notice, warning, error, fatal, and panic 53 | # Defaults to warning. 54 | #min_messages: notice 55 | 56 | # Warning: The database defined as "test" will be erased and 57 | # re-generated from your development database when you run "rake". 58 | # Do not set this db to the same as development or production. 59 | test: 60 | <<: *default 61 | database: book_review_test 62 | 63 | # As with config/secrets.yml, you never want to store sensitive information, 64 | # like your database password, in your source code. If your source code is 65 | # ever seen by anyone, they now have access to your database. 66 | # 67 | # Instead, provide the password as a unix environment variable when you boot 68 | # the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database 69 | # for a full rundown on how to provide these environment variables in a 70 | # production deployment. 71 | # 72 | # On Heroku and other platform providers, you may have a full connection URL 73 | # available as an environment variable. For example: 74 | # 75 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" 76 | # 77 | # You can use this database configuration with: 78 | # 79 | # production: 80 | # url: <%= ENV['DATABASE_URL'] %> 81 | # 82 | production: 83 | <<: *default 84 | database: book_review_production 85 | username: rails_template 86 | password: <%= ENV['BOOK_REVIEW_DATABASE_PASSWORD'] %> 87 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | require 'spec_helper' 3 | ENV['RAILS_ENV'] ||= 'test' 4 | require File.expand_path('../config/environment', __dir__) 5 | # Prevent database truncation if the environment is production 6 | abort('The Rails environment is running in production mode!') if Rails.env.production? 7 | require 'rspec/rails' 8 | # Add additional requires below this line. Rails is not loaded until this point! 9 | require 'shoulda/matchers' 10 | # Requires supporting ruby files with custom matchers and macros, etc, in 11 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 12 | # run as spec files by default. This means that files in spec/support that end 13 | # in _spec.rb will both be required and run as specs, causing the specs to be 14 | # run twice. It is recommended that you do not name files matching this glob to 15 | # end with _spec.rb. You can configure this pattern with the --pattern 16 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 17 | # 18 | # The following line is provided for convenience purposes. It has the downside 19 | # of increasing the boot-up time by auto-requiring all files in the support 20 | # directory. Alternatively, in the individual `*_spec.rb` files, manually 21 | # require only the support files necessary. 22 | # 23 | # Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f } 24 | 25 | # Checks for pending migrations and applies them before tests are run. 26 | # If you are not using ActiveRecord, you can remove these lines. 27 | begin 28 | ActiveRecord::Migration.maintain_test_schema! 29 | rescue ActiveRecord::PendingMigrationError => e 30 | puts e.to_s.strip 31 | exit 1 32 | end 33 | RSpec.configure do |config| 34 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 35 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 36 | 37 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 38 | # examples within a transaction, remove the following line or assign false 39 | # instead of true. 40 | config.use_transactional_fixtures = true 41 | 42 | # You can uncomment this line to turn off ActiveRecord support entirely. 43 | # config.use_active_record = false 44 | 45 | # RSpec Rails can automatically mix in different behaviours to your tests 46 | # based on their file location, for example enabling you to call `get` and 47 | # `post` in specs under `spec/controllers`. 48 | # 49 | # You can disable this behaviour by removing the line below, and instead 50 | # explicitly tag your specs with their type, e.g.: 51 | # 52 | # RSpec.describe UsersController, type: :controller do 53 | # # ... 54 | # end 55 | # 56 | # The different available types are documented in the features, such as in 57 | # https://relishapp.com/rspec/rspec-rails/docs 58 | config.infer_spec_type_from_file_location! 59 | 60 | # Filter lines from Rails gems in backtraces. 61 | config.filter_rails_from_backtrace! 62 | # arbitrary gems may also be filtered via: 63 | # config.filter_gems_from_backtrace("gem name") 64 | end 65 | Shoulda::Matchers.configure do |config| 66 | config.integrate do |with| 67 | with.test_framework :rspec 68 | with.library :rails 69 | end 70 | end 71 | Capybara.default_driver = :selenium_chrome 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Book review social media 3 | 4 | > This is a social media web app for book lovers. It is always good to interact with people who share the same hobbies with you. Most important it is better to have some reviews about things before buying or viewing them, especially for book. 'Book Review' aims to connect books lovers and help them share their experiences about the books they have read before. 5 | 6 | ![screenshot](./app_screenshot.png) 7 | 8 | > The actions the any user could do in the app are 9 | - preview all book reviews and trending ones 10 | - Register 11 | - Sign in 12 | - Post a book review 13 | - Follow some interesting book reviewers 14 | - Like/dislike a book review 15 | - Comment a book review 16 | 17 | > Thechnically speaking, the database schema is quite simple, made up with Lucidchart and goes like this: (You can get the file [here](https://github.com/patriciachrysy/rails-capstone-project/tree/feature/docs) ) 18 | 19 | ![screenshot](./docs/book_review_erd.png) 20 | 21 | 22 | > Wana know more? Watch the presentation video. 23 | 24 | [![screenshot](./app_video.png)](https://www.loom.com/share/8af38510db29488c93d7366e89c5fa05) 25 | 26 | ## Built With 27 | 28 | > The project was built using these technologies 29 | - Ruby v2.7.0 30 | - Ruby on Rails v6.0.3 31 | 32 | ## Live Demo 33 | 34 | 35 | > Try out the app here: [book_review](https://powerful-wildwood-46604.herokuapp.com/) 36 | 37 | 38 | ## Getting Started 39 | 40 | To get a local copy up and running follow these simple example steps. 41 | 42 | - Clone the repository and there you go! ;-) 43 | 44 | ### Prerequisites 45 | 46 | Ruby: 2.7 47 | Rails: 6.0.3 48 | Postgres: >=9.5 49 | 50 | ### Setup 51 | 52 | - cd into the folder 53 | - install the the bundles with the `bundle` or `bundle install` command. If you are asked to do `bundle update` before, do it. 54 | - Set up the database using `rake db:migrate` or `rails db:migrate` 55 | 56 | ### Install 57 | 58 | - Install VSCode or any code editor you like 59 | - Install Ruby on rails 5.1.6 or later if you don't have it yet 60 | - Run this command on your terminal in order to install rubocop: gem install rubocop 61 | 62 | 63 | 64 | 65 | ### Usage 66 | 67 | Start server with: 68 | 69 | ``` 70 | rails server 71 | ``` 72 | 73 | Open `http://localhost:3000/` in your browser. 74 | 75 | ### Run tests 76 | 77 | ``` 78 | rpsec --format documentation 79 | ``` 80 | 81 | ### Deployment 82 | 83 | > Follow the [Heroku deployment doc](https://devcenter.heroku.com/articles/getting-started-with-rails5) to deploy th app on heroku 84 | 85 | ## Special Credit 86 | 87 | > Original site design by Gregoire Vella on Behance [Check it!](https://www.behance.net/gallery/14286087/Twitter-Redesign-of-UI-details) 88 | 89 | ## Authors 90 | 91 | 92 | 👤 **Manezeu Patricia Chrystelle** 93 | - Github: [@githubhandle](https://github.com/patriciachrysy) 94 | - Twitter: [@twitterhandle](https://twitter.com/ManezeuP) 95 | - Linkedin: [linkedin](https://www.linkedin.com/in/manezeu-patricia-chrystelle-095072118/) 96 | 97 | 98 | ## 🤝 Contributing 99 | 100 | Contributions, issues and feature requests are welcome! 101 | 102 | Feel free to check the [issues page](https://github.com/patriciachrysy/rails-capstone-project/issues). 103 | 104 | ## Show your support 105 | 106 | Give a ⭐️ if you like this project! 107 | 108 | -------------------------------------------------------------------------------- /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 | @import 'bootstrap'; 17 | 18 | body { 19 | font-family: 'Lato', sans-serif; 20 | } 21 | 22 | a { 23 | text-decoration: none; 24 | } 25 | 26 | a:hover { 27 | text-decoration: none; 28 | font-size: 20px; 29 | color: #53a7e7; 30 | } 31 | 32 | ul { 33 | list-style: none; 34 | padding: 0 !important; 35 | margin: 0 !important; 36 | width: 100%; 37 | } 38 | 39 | li i { 40 | font-size: 25px !important; 41 | } 42 | 43 | li:hover { 44 | background-color: #53a7e7; 45 | color: white !important; 46 | } 47 | 48 | li a:hover { 49 | font-size: 1rem; 50 | color: white !important; 51 | } 52 | 53 | .blue { 54 | color: #53a7e7; 55 | } 56 | 57 | .bg-blue { 58 | background-color: #53a7e7; 59 | } 60 | 61 | .ice-blue { 62 | color: #396f9b; 63 | } 64 | 65 | .dark-blue { 66 | color: #213246; 67 | } 68 | 69 | .bg-dark-blue { 70 | background-color: #213246; 71 | } 72 | 73 | .dark-grey { 74 | color: #6e8597; 75 | } 76 | 77 | .grey { 78 | color: #bebfc1; 79 | } 80 | 81 | .bg-ligth-grey { 82 | background-color: #f5f6f8; 83 | } 84 | 85 | .black-border { 86 | border: 1px solid #16272f; 87 | } 88 | 89 | .icy-blue { 90 | color: #b4c5cd; 91 | } 92 | 93 | .medium-text { 94 | font-size: 15px; 95 | } 96 | 97 | .big-text { 98 | font-size: 25px !important; 99 | } 100 | 101 | .profile-pic { 102 | width: 100px; 103 | height: 100px; 104 | border-radius: 5px; 105 | } 106 | 107 | .small-profile-pic { 108 | width: 60px; 109 | height: 60px; 110 | border-radius: 5px; 111 | } 112 | 113 | .text-extra-medium { 114 | font-size: 17px; 115 | } 116 | 117 | .logo { 118 | width: 50px; 119 | height: 50px; 120 | } 121 | 122 | .logo-text { 123 | font-family: 'Fredericka the Great', cursive; 124 | font-size: 35px; 125 | color: white; 126 | } 127 | 128 | .logo-text:hover { 129 | text-decoration: none; 130 | font-size: 35px !important; 131 | color: white !important; 132 | } 133 | 134 | .login-button:hover { 135 | text-decoration: none; 136 | font-size: 1rem !important; 137 | color: white !important; 138 | } 139 | 140 | .align-self-center { 141 | overflow-x: hidden; 142 | } 143 | 144 | .grey-border-bottom { 145 | border-bottom: 2px solid #e3e4e6; 146 | } 147 | 148 | .grey-border-right { 149 | border-right: 2px solid #e3e4e6; 150 | } 151 | 152 | .grey-border { 153 | border: 2px solid #e3e4e6; 154 | } 155 | 156 | .small-text { 157 | font-size: 13px; 158 | } 159 | 160 | .user-pic { 161 | width: 50px; 162 | height: 50px; 163 | border-radius: 50%; 164 | } 165 | 166 | .cover-image { 167 | width: 100%; 168 | height: 350px; 169 | } 170 | 171 | .full-width { 172 | width: 100%; 173 | } 174 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 34 | bundler_version = $1 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../../Gemfile", __FILE__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_version 64 | @bundler_version ||= 65 | env_var_version || cli_arg_version || 66 | lockfile_version 67 | end 68 | 69 | def bundler_requirement 70 | return "#{Gem::Requirement.default}.a" unless bundler_version 71 | 72 | bundler_gem_version = Gem::Version.new(bundler_version) 73 | 74 | requirement = bundler_gem_version.approximate_recommendation 75 | 76 | return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0") 77 | 78 | requirement += ".a" if bundler_gem_version.prerelease? 79 | 80 | requirement 81 | end 82 | 83 | def load_bundler! 84 | ENV["BUNDLE_GEMFILE"] ||= gemfile 85 | 86 | activate_bundler 87 | end 88 | 89 | def activate_bundler 90 | gem_error = activation_error_handling do 91 | gem "bundler", bundler_requirement 92 | end 93 | return if gem_error.nil? 94 | require_error = activation_error_handling do 95 | require "bundler/version" 96 | end 97 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 98 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" 99 | exit 42 100 | end 101 | 102 | def activation_error_handling 103 | yield 104 | nil 105 | rescue StandardError, LoadError => e 106 | e 107 | end 108 | end 109 | 110 | m.load_bundler! 111 | 112 | if m.invoked_as_script? 113 | load Gem.bin_path("bundler", "bundle") 114 | end 115 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # This file is the source Rails uses to define your schema when running `rails 6 | # db:schema:load`. When creating a new database, `rails db:schema:load` tends to 7 | # be faster and is potentially less error prone than running all of your 8 | # migrations from scratch. Old migrations may fail to apply correctly if those 9 | # migrations use external dependencies or application code. 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 2020_06_09_125526) do 14 | 15 | # These are extensions that must be enabled in order to support this database 16 | enable_extension "plpgsql" 17 | 18 | create_table "active_storage_attachments", force: :cascade do |t| 19 | t.string "name", null: false 20 | t.string "record_type", null: false 21 | t.bigint "record_id", null: false 22 | t.bigint "blob_id", null: false 23 | t.datetime "created_at", null: false 24 | t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" 25 | t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true 26 | end 27 | 28 | create_table "active_storage_blobs", force: :cascade do |t| 29 | t.string "key", null: false 30 | t.string "filename", null: false 31 | t.string "content_type" 32 | t.text "metadata" 33 | t.bigint "byte_size", null: false 34 | t.string "checksum", null: false 35 | t.datetime "created_at", null: false 36 | t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true 37 | end 38 | 39 | create_table "comments", force: :cascade do |t| 40 | t.text "content" 41 | t.datetime "created_at", precision: 6, null: false 42 | t.datetime "updated_at", precision: 6, null: false 43 | t.integer "author_id" 44 | t.integer "opinion_id" 45 | t.index ["author_id"], name: "index_comments_on_author_id" 46 | t.index ["opinion_id"], name: "index_comments_on_opinion_id" 47 | end 48 | 49 | create_table "followings", force: :cascade do |t| 50 | t.datetime "created_at", precision: 6, null: false 51 | t.datetime "updated_at", precision: 6, null: false 52 | t.integer "follower_id" 53 | t.integer "followed_id" 54 | t.index ["followed_id"], name: "index_followings_on_followed_id" 55 | t.index ["follower_id"], name: "index_followings_on_follower_id" 56 | end 57 | 58 | create_table "likings", force: :cascade do |t| 59 | t.boolean "status" 60 | t.datetime "created_at", precision: 6, null: false 61 | t.datetime "updated_at", precision: 6, null: false 62 | t.integer "user_id" 63 | t.integer "opinion_id" 64 | t.index ["opinion_id"], name: "index_likings_on_opinion_id" 65 | t.index ["user_id"], name: "index_likings_on_user_id" 66 | end 67 | 68 | create_table "opinions", force: :cascade do |t| 69 | t.string "book_name" 70 | t.string "book_author" 71 | t.date "published_at" 72 | t.string "book_link" 73 | t.text "content" 74 | t.datetime "created_at", precision: 6, null: false 75 | t.datetime "updated_at", precision: 6, null: false 76 | t.integer "author_id" 77 | t.index ["author_id"], name: "index_opinions_on_author_id" 78 | end 79 | 80 | create_table "users", force: :cascade do |t| 81 | t.string "username" 82 | t.string "fullname" 83 | t.datetime "created_at", precision: 6, null: false 84 | t.datetime "updated_at", precision: 6, null: false 85 | end 86 | 87 | add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" 88 | end 89 | -------------------------------------------------------------------------------- /app/views/users/show.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= image_tag @user.cover_image.variant(resize_to_limit: [700, 400]), class: "cover-image" %> 4 |
5 |
6 | Book reviews 7 | Following 8 | Followers 9 | Favourite reviews 10 |
11 |
12 |
13 |
14 | <%= @user.opinions_count %> book review(s) 15 |
16 | <% @user.opinions.each do |opinion| %> 17 | <%= render opinion %> 18 | <% end %> 19 |
20 | 21 |
22 |
23 | <%= @user.followeds_count %> following(s) 24 |
25 | <% @user.followeds.each do |user| %> 26 | <%= render user %> 27 | <% end %> 28 |
29 | 30 |
31 |
32 | <%= @user.followers_count %> follower(s) 33 |
34 | <% @user.followers.each do |user| %> 35 | <%= render user %> 36 | <% end %> 37 |
38 | 39 |
40 |
41 | <%= @user.liked_opinions.count %> favourite review(s) 42 |
43 |
44 | <% @user.liked_opinions.each do |opinion| %> 45 | <%= render opinion %> 46 | <% end %> 47 |
48 |
49 |
50 |
51 |
52 |
53 | <% if current_user.followed?(@user) %> 54 | <%= link_to '', {controller: :users, action: :unfollow, id: @user.id}, method: :delete, class: "fa fa-minus-circle grey big-text my-auto mx-3" %> 55 | <% else %> 56 | <%= link_to '', {controller: :users, action: :follow, id: @user.id}, method: :post, class:"fa fa-plus-circle grey big-text my-auto mx-3" %> 57 | <% end %> 58 | <%= image_tag @user.photo.variant(resize_to_limit: [100, 100]), class: "profile-pic rounded-circle"%> 59 | <% if current_user == @user %> 60 | <%= link_to '', edit_user_path(@user), class: "fa fa-cog grey big-text my-auto mx-3" %> 61 | <% else %> 62 | 63 | <% end %> 64 |
65 | <%= @user.fullname %> 66 | @<%= @user.username %> 67 |
68 |
69 |
70 |
71 | <%= @user.opinions_count %> 72 | Reviews 73 |
74 |
75 | <%= @user.followeds_count %> 76 | Following 77 |
78 |
79 | <%= @user.followers_count %> 80 | Followers 81 |
82 |
83 | 84 |
85 |
86 | FOLLOWED BY 87 |
88 |
89 | <% @user.followers.each do |user| %> 90 | <%= render user %> 91 | <% end %> 92 |
93 |
94 |
-------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rails generate rspec:install` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # The generated `.rspec` file contains `--require spec_helper` which will cause 4 | # this file to always be loaded, without a need to explicitly require it in any 5 | # files. 6 | # 7 | # Given that it is always loaded, you are encouraged to keep this file as 8 | # light-weight as possible. Requiring heavyweight dependencies from this file 9 | # will add to the boot time of your test suite on EVERY test run, even for an 10 | # individual file that may not need all of that loaded. Instead, consider making 11 | # a separate helper file that requires the additional dependencies and performs 12 | # the additional setup, and require it from the spec files that actually need 13 | # it. 14 | # 15 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 16 | RSpec.configure do |config| 17 | # rspec-expectations config goes here. You can use an alternate 18 | # assertion/expectation library such as wrong or the stdlib/minitest 19 | # assertions if you prefer. 20 | config.expect_with :rspec do |expectations| 21 | # This option will default to `true` in RSpec 4. It makes the `description` 22 | # and `failure_message` of custom matchers include text for helper methods 23 | # defined using `chain`, e.g.: 24 | # be_bigger_than(2).and_smaller_than(4).description 25 | # # => "be bigger than 2 and smaller than 4" 26 | # ...rather than: 27 | # # => "be bigger than 2" 28 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 29 | end 30 | 31 | # rspec-mocks config goes here. You can use an alternate test double 32 | # library (such as bogus or mocha) by changing the `mock_with` option here. 33 | config.mock_with :rspec do |mocks| 34 | # Prevents you from mocking or stubbing a method that does not exist on 35 | # a real object. This is generally recommended, and will default to 36 | # `true` in RSpec 4. 37 | mocks.verify_partial_doubles = true 38 | end 39 | 40 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 41 | # have no way to turn it off -- the option exists only for backwards 42 | # compatibility in RSpec 3). It causes shared context metadata to be 43 | # inherited by the metadata hash of host groups and examples, rather than 44 | # triggering implicit auto-inclusion in groups with matching metadata. 45 | config.shared_context_metadata_behavior = :apply_to_host_groups 46 | 47 | # The settings below are suggested to provide a good initial experience 48 | # with RSpec, but feel free to customize to your heart's content. 49 | # # This allows you to limit a spec run to individual examples or groups 50 | # # you care about by tagging them with `:focus` metadata. When nothing 51 | # # is tagged with `:focus`, all examples get run. RSpec also provides 52 | # # aliases for `it`, `describe`, and `context` that include `:focus` 53 | # # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 54 | # config.filter_run_when_matching :focus 55 | # 56 | # # Allows RSpec to persist some state between runs in order to support 57 | # # the `--only-failures` and `--next-failure` CLI options. We recommend 58 | # # you configure your source control system to ignore this file. 59 | # config.example_status_persistence_file_path = "spec/examples.txt" 60 | # 61 | # # Limits the available syntax to the non-monkey patched syntax that is 62 | # # recommended. For more details, see: 63 | # # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 64 | # # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 65 | # # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 66 | # config.disable_monkey_patching! 67 | # 68 | # # Many RSpec users commonly either run the entire suite or an individual 69 | # # file, and it's useful to allow more verbose output when running an 70 | # # individual spec file. 71 | # if config.files_to_run.one? 72 | # # Use the documentation formatter for detailed output, 73 | # # unless a formatter has already been configured 74 | # # (e.g. via a command-line flag). 75 | # config.default_formatter = "doc" 76 | # end 77 | # 78 | # # Print the 10 slowest examples and example groups at the 79 | # # end of the spec run, to help surface which specs are running 80 | # # particularly slow. 81 | # config.profile_examples = 10 82 | # 83 | # # Run specs in random order to surface order dependencies. If you find an 84 | # # order dependency and want to debug it, you can fix the order by providing 85 | # # the seed, which is printed after each run. 86 | # # --seed 1234 87 | # config.order = :random 88 | # 89 | # # Seed global randomization in this process using the `--seed` CLI option. 90 | # # Setting this allows you to use `--seed` to deterministically reproduce 91 | # # test failures related to randomization by passing the same `--seed` value 92 | # # as the one that triggered the failure. 93 | # Kernel.srand config.seed 94 | end 95 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 18 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 19 | # config.require_master_key = true 20 | 21 | # Disable serving static files from the `/public` folder by default since 22 | # Apache or NGINX already handles this. 23 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? 24 | 25 | # Compress CSS using a preprocessor. 26 | # config.assets.css_compressor = :sass 27 | 28 | # Do not fallback to assets pipeline if a precompiled asset is missed. 29 | config.assets.compile = false 30 | 31 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 32 | # config.action_controller.asset_host = 'http://assets.example.com' 33 | 34 | # Specifies the header that your server uses for sending files. 35 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 36 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 37 | 38 | # Store uploaded files on the local file system (see config/storage.yml for options). 39 | config.active_storage.service = :cloudinary 40 | 41 | # Mount Action Cable outside main process or domain. 42 | # config.action_cable.mount_path = nil 43 | # config.action_cable.url = 'wss://example.com/cable' 44 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] 45 | 46 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 47 | # config.force_ssl = true 48 | 49 | # Use the lowest log level to ensure availability of diagnostic information 50 | # when problems arise. 51 | config.log_level = :debug 52 | 53 | # Prepend all log lines with the following tags. 54 | config.log_tags = [ :request_id ] 55 | 56 | # Use a different cache store in production. 57 | # config.cache_store = :mem_cache_store 58 | 59 | # Use a real queuing backend for Active Job (and separate queues per environment). 60 | # config.active_job.queue_adapter = :resque 61 | # config.active_job.queue_name_prefix = "rails_template_production" 62 | 63 | config.action_mailer.perform_caching = false 64 | 65 | # Ignore bad email addresses and do not raise email delivery errors. 66 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 67 | # config.action_mailer.raise_delivery_errors = false 68 | 69 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 70 | # the I18n.default_locale when a translation cannot be found). 71 | config.i18n.fallbacks = true 72 | 73 | # Send deprecation notices to registered listeners. 74 | config.active_support.deprecation = :notify 75 | 76 | # Use default logging formatter so that PID and timestamp are not suppressed. 77 | config.log_formatter = ::Logger::Formatter.new 78 | 79 | # Use a different logger for distributed setups. 80 | # require 'syslog/logger' 81 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 82 | 83 | if ENV["RAILS_LOG_TO_STDOUT"].present? 84 | logger = ActiveSupport::Logger.new(STDOUT) 85 | logger.formatter = config.log_formatter 86 | config.logger = ActiveSupport::TaggedLogging.new(logger) 87 | end 88 | 89 | # Do not dump schema after migrations. 90 | config.active_record.dump_schema_after_migration = false 91 | 92 | # Inserts middleware to perform automatic connection switching. 93 | # The `database_selector` hash is used to pass options to the DatabaseSelector 94 | # middleware. The `delay` is used to determine how long to wait after a write 95 | # to send a subsequent read to the primary. 96 | # 97 | # The `database_resolver` class is used by the middleware to determine which 98 | # database is appropriate to use based on the time delay. 99 | # 100 | # The `database_resolver_context` class is used by the middleware to set 101 | # timestamps for the last write to the primary. The resolver uses the context 102 | # class timestamps to determine how long to wait before reading from the 103 | # replica. 104 | # 105 | # By default Rails will store a last write timestamp in the session. The 106 | # DatabaseSelector middleware is designed as such you can define your own 107 | # strategy for connection switching and pass that into the middleware through 108 | # these configuration options. 109 | # config.active_record.database_selector = { delay: 2.seconds } 110 | # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver 111 | # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session 112 | end 113 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Book Review 5 | <%= csrf_meta_tags %> 6 | <%= csp_meta_tag %> 7 | 8 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> 9 | <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | <% if flash[:notice] %> 27 |
28 |

<%= notice %>

29 |
30 | <% end %> 31 | <% if flash[:errors] %> 32 |
33 | <% flash[:errors].each do |error| %> 34 |

<%= error %>;

35 | <% end %> 36 |
37 | <% end %> 38 | 39 |
40 |
41 | <% if current_user %> 42 |
43 |
44 | <%= image_tag current_user.photo.variant(resize_to_limit: [100, 100]), class: "small-profile-pic" %> 45 |
46 |
47 | <%= current_user.fullname %> 48 |
49 |
50 |
51 |
52 | <%= current_user.followeds_count %> 53 | Following 54 |
55 |
56 | <%= current_user.followers_count %> 57 | Followers 58 |
59 |
60 | <% end %> 61 |
62 |
    63 |
  • 64 | 65 | <%= link_to 'HOME', root_path, class: "dark-grey" %> 66 |
  • 67 |
  • 68 | 69 | <%= link_to 'CONNECT', users_path, class: "dark-grey" %> 70 |
  • 71 |
  • 72 | 73 | <%= link_to 'DISCOVER', users_path, class: "dark-grey" %> 74 |
  • 75 | <% if current_user %> 76 |
  • 77 | 78 | <%= link_to 'PROFILE', user_path(current_user), class: "dark-grey" %> 79 |
  • 80 | <% end %> 81 |
  • 82 | 83 | <%= link_to 'STATISTICS', root_path, class: "dark-grey" %> 84 |
  • 85 |
86 |
87 | 88 |
89 |
90 |
91 |
92 | <%= image_tag 'logo-2.png', class: "logo" %> 93 | <%= link_to 'BOOK REVIEWS', root_path, class:"logo-text align-self-center ml-2" %> 94 |
95 |
96 | <% if current_user %> 97 | <%= link_to 'SIGN OUT', sign_out_path, class: "ice-blue login-button font-weight-bolder align-self-center" %> 98 | <% else %> 99 | <%= link_to 'SIGN UP', sign_up_path, class: "ice-blue login-button font-weight-bolder align-self-center mx-2" %> 100 | <%= link_to 'SIGN IN', sign_in_path, class: "ice-blue login-button font-weight-bolder align-self-center" %> 101 | <% end %> 102 |
103 |
104 |
105 | <%= yield %> 106 |
107 |
108 |
109 | 110 |
111 | 112 | 113 | -------------------------------------------------------------------------------- /app/views/opinions/show.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | "<%= @opinion.book_name %>" review 4 |
5 |
6 |
7 |
8 | Review details 9 |
10 |
11 |
12 | <%= image_tag @opinion.author.photo.variant(resize_to_limit: [100, 100]), class: "profile-pic" %> 13 |
14 |
15 |
16 |
17 | <%= link_to @opinion.author.fullname, user_path(@opinion.author), class: "text-dark text-medium font-weight-bolder" %> 18 |
19 |
20 | <%= distance_of_time_in_words(DateTime.now, @opinion.created_at)%> 21 |
22 |
23 |
24 |
25 |
26 |

<%= @opinion.book_name %>

27 |
28 |
29 | 30 | By <%= @opinion.book_author %>, 31 | <%= @opinion.published_at %> 32 |
33 |
34 |
35 |

<%= @opinion.content %>

36 |
37 | 38 |
39 |
40 | <%= @opinion.likes_count %> 41 | <%= @opinion.dislikes_count %> 42 | <%= @opinion.comments_count %> 43 |
44 |
45 | <%= link_to '', opinion_path(@opinion), class: "fa fa-eye blue mx-2"%> 46 | <%= read_opinion_button(@opinion) %> 47 | <%= edit_opinion_button(@opinion) %> 48 | <%= delete_opinion_button(@opinion) %> 49 | <%= liking_opinion_button(@opinion) %> 50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | 58 | <% if current_user %> 59 |
60 | <%= render 'comments/comment_form' %> 61 |
62 | <% end %> 63 | 64 |
65 |
66 | <%= @opinion.comments_count%> Comments 67 |
68 |
69 |
70 | <%= display_comments(@opinion) %> 71 |
72 |
73 |
74 |
75 |
76 |
77 | <% if current_user %> 78 |
79 |
80 | Who to follow 81 |
82 |
83 | <% current_user.follow_suggest.each do |user| %> 84 | <%= render user %> 85 | <% end %> 86 |
87 | <%= link_to 'Popular account', users_path, class: "blue small-text" %> · 88 | <%= link_to 'Find friends', users_path, class: "blue small-text" %> 89 |
90 |
91 |
92 | <% end %> 93 |
94 |
95 | Popular reviews 96 |
97 |
98 | <% most_popular_posts.values[0..2].each do |opinion| %> 99 |
100 |
101 | <%= image_tag opinion.author.photo.variant(resize_to_limit: [100, 100]), class: "user-pic" %> 102 |
103 |
104 | <%= link_to opinion.author.fullname, user_path(opinion.author), class: "text-dark small-text font-weight-bold" %> 105 |

review of <%= opinion.book_name %>: <%= opinion.content.truncate(25) %>

106 |
107 |
108 | <%= link_to '', opinion_path(opinion), class: "fa fa-eye blue mx-2"%> 109 |
110 |
111 | <% end %> 112 |
113 | <%= link_to 'Make a review too', users_path, class: "blue small-text" %> 114 |
115 |
116 |
117 |
118 | 119 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (6.0.3.1) 5 | actionpack (= 6.0.3.1) 6 | nio4r (~> 2.0) 7 | websocket-driver (>= 0.6.1) 8 | actionmailbox (6.0.3.1) 9 | actionpack (= 6.0.3.1) 10 | activejob (= 6.0.3.1) 11 | activerecord (= 6.0.3.1) 12 | activestorage (= 6.0.3.1) 13 | activesupport (= 6.0.3.1) 14 | mail (>= 2.7.1) 15 | actionmailer (6.0.3.1) 16 | actionpack (= 6.0.3.1) 17 | actionview (= 6.0.3.1) 18 | activejob (= 6.0.3.1) 19 | mail (~> 2.5, >= 2.5.4) 20 | rails-dom-testing (~> 2.0) 21 | actionpack (6.0.3.1) 22 | actionview (= 6.0.3.1) 23 | activesupport (= 6.0.3.1) 24 | rack (~> 2.0, >= 2.0.8) 25 | rack-test (>= 0.6.3) 26 | rails-dom-testing (~> 2.0) 27 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 28 | actiontext (6.0.3.1) 29 | actionpack (= 6.0.3.1) 30 | activerecord (= 6.0.3.1) 31 | activestorage (= 6.0.3.1) 32 | activesupport (= 6.0.3.1) 33 | nokogiri (>= 1.8.5) 34 | actionview (6.0.3.1) 35 | activesupport (= 6.0.3.1) 36 | builder (~> 3.1) 37 | erubi (~> 1.4) 38 | rails-dom-testing (~> 2.0) 39 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 40 | activejob (6.0.3.1) 41 | activesupport (= 6.0.3.1) 42 | globalid (>= 0.3.6) 43 | activemodel (6.0.3.1) 44 | activesupport (= 6.0.3.1) 45 | activerecord (6.0.3.1) 46 | activemodel (= 6.0.3.1) 47 | activesupport (= 6.0.3.1) 48 | activestorage (6.0.3.1) 49 | actionpack (= 6.0.3.1) 50 | activejob (= 6.0.3.1) 51 | activerecord (= 6.0.3.1) 52 | marcel (~> 0.3.1) 53 | activesupport (6.0.3.1) 54 | concurrent-ruby (~> 1.0, >= 1.0.2) 55 | i18n (>= 0.7, < 2) 56 | minitest (~> 5.1) 57 | tzinfo (~> 1.1) 58 | zeitwerk (~> 2.2, >= 2.2.2) 59 | addressable (2.7.0) 60 | public_suffix (>= 2.0.2, < 5.0) 61 | ast (2.4.0) 62 | autoprefixer-rails (9.7.6) 63 | execjs 64 | aws_cf_signer (0.1.3) 65 | bindex (0.8.1) 66 | bootsnap (1.4.6) 67 | msgpack (~> 1.0) 68 | bootstrap (4.5.0) 69 | autoprefixer-rails (>= 9.1.0) 70 | popper_js (>= 1.14.3, < 2) 71 | sassc-rails (>= 2.0.0) 72 | builder (3.2.4) 73 | byebug (11.1.3) 74 | capybara (3.32.2) 75 | addressable 76 | mini_mime (>= 0.1.3) 77 | nokogiri (~> 1.8) 78 | rack (>= 1.6.0) 79 | rack-test (>= 0.6.3) 80 | regexp_parser (~> 1.5) 81 | xpath (~> 3.2) 82 | childprocess (3.0.0) 83 | cloudinary (1.15.0) 84 | aws_cf_signer 85 | rest-client 86 | concurrent-ruby (1.1.6) 87 | crass (1.0.6) 88 | diff-lcs (1.3) 89 | domain_name (0.5.20190701) 90 | unf (>= 0.0.5, < 1.0.0) 91 | erubi (1.9.0) 92 | execjs (2.7.0) 93 | ffi (1.13.0) 94 | globalid (0.4.2) 95 | activesupport (>= 4.2.0) 96 | http-accept (1.7.0) 97 | http-cookie (1.0.3) 98 | domain_name (~> 0.5) 99 | i18n (1.8.3) 100 | concurrent-ruby (~> 1.0) 101 | image_processing (1.11.0) 102 | mini_magick (>= 4.9.5, < 5) 103 | ruby-vips (>= 2.0.17, < 3) 104 | jbuilder (2.10.0) 105 | activesupport (>= 5.0.0) 106 | listen (3.2.1) 107 | rb-fsevent (~> 0.10, >= 0.10.3) 108 | rb-inotify (~> 0.9, >= 0.9.10) 109 | loofah (2.5.0) 110 | crass (~> 1.0.2) 111 | nokogiri (>= 1.5.9) 112 | mail (2.7.1) 113 | mini_mime (>= 0.1.1) 114 | marcel (0.3.3) 115 | mimemagic (~> 0.3.2) 116 | method_source (1.0.0) 117 | mime-types (3.3.1) 118 | mime-types-data (~> 3.2015) 119 | mime-types-data (3.2020.0512) 120 | mimemagic (0.3.5) 121 | mini_magick (4.10.1) 122 | mini_mime (1.0.2) 123 | mini_portile2 (2.4.0) 124 | minitest (5.14.1) 125 | msgpack (1.3.3) 126 | netrc (0.11.0) 127 | nio4r (2.5.2) 128 | nokogiri (1.10.9) 129 | mini_portile2 (~> 2.4.0) 130 | parallel (1.19.1) 131 | parser (2.7.1.3) 132 | ast (~> 2.4.0) 133 | pg (1.2.3) 134 | popper_js (1.16.0) 135 | public_suffix (4.0.5) 136 | puma (4.3.5) 137 | nio4r (~> 2.0) 138 | rack (2.2.2) 139 | rack-proxy (0.6.5) 140 | rack 141 | rack-test (1.1.0) 142 | rack (>= 1.0, < 3) 143 | rails (6.0.3.1) 144 | actioncable (= 6.0.3.1) 145 | actionmailbox (= 6.0.3.1) 146 | actionmailer (= 6.0.3.1) 147 | actionpack (= 6.0.3.1) 148 | actiontext (= 6.0.3.1) 149 | actionview (= 6.0.3.1) 150 | activejob (= 6.0.3.1) 151 | activemodel (= 6.0.3.1) 152 | activerecord (= 6.0.3.1) 153 | activestorage (= 6.0.3.1) 154 | activesupport (= 6.0.3.1) 155 | bundler (>= 1.3.0) 156 | railties (= 6.0.3.1) 157 | sprockets-rails (>= 2.0.0) 158 | rails-dom-testing (2.0.3) 159 | activesupport (>= 4.2.0) 160 | nokogiri (>= 1.6) 161 | rails-html-sanitizer (1.3.0) 162 | loofah (~> 2.3) 163 | railties (6.0.3.1) 164 | actionpack (= 6.0.3.1) 165 | activesupport (= 6.0.3.1) 166 | method_source 167 | rake (>= 0.8.7) 168 | thor (>= 0.20.3, < 2.0) 169 | rainbow (3.0.0) 170 | rake (13.0.1) 171 | rb-fsevent (0.10.4) 172 | rb-inotify (0.10.1) 173 | ffi (~> 1.0) 174 | regexp_parser (1.7.0) 175 | rest-client (2.1.0) 176 | http-accept (>= 1.7.0, < 2.0) 177 | http-cookie (>= 1.0.2, < 2.0) 178 | mime-types (>= 1.16, < 4.0) 179 | netrc (~> 0.8) 180 | rexml (3.2.4) 181 | rspec-core (3.9.2) 182 | rspec-support (~> 3.9.3) 183 | rspec-expectations (3.9.2) 184 | diff-lcs (>= 1.2.0, < 2.0) 185 | rspec-support (~> 3.9.0) 186 | rspec-mocks (3.9.1) 187 | diff-lcs (>= 1.2.0, < 2.0) 188 | rspec-support (~> 3.9.0) 189 | rspec-rails (4.0.1) 190 | actionpack (>= 4.2) 191 | activesupport (>= 4.2) 192 | railties (>= 4.2) 193 | rspec-core (~> 3.9) 194 | rspec-expectations (~> 3.9) 195 | rspec-mocks (~> 3.9) 196 | rspec-support (~> 3.9) 197 | rspec-support (3.9.3) 198 | rubocop (0.85.0) 199 | parallel (~> 1.10) 200 | parser (>= 2.7.0.1) 201 | rainbow (>= 2.2.2, < 4.0) 202 | regexp_parser (>= 1.7) 203 | rexml 204 | rubocop-ast (>= 0.0.3) 205 | ruby-progressbar (~> 1.7) 206 | unicode-display_width (>= 1.4.0, < 2.0) 207 | rubocop-ast (0.0.3) 208 | parser (>= 2.7.0.1) 209 | ruby-progressbar (1.10.1) 210 | ruby-vips (2.0.17) 211 | ffi (~> 1.9) 212 | rubyzip (2.3.0) 213 | sass-rails (6.0.0) 214 | sassc-rails (~> 2.1, >= 2.1.1) 215 | sassc (2.4.0) 216 | ffi (~> 1.9) 217 | sassc-rails (2.1.2) 218 | railties (>= 4.0.0) 219 | sassc (>= 2.0) 220 | sprockets (> 3.0) 221 | sprockets-rails 222 | tilt 223 | selenium-webdriver (3.142.7) 224 | childprocess (>= 0.5, < 4.0) 225 | rubyzip (>= 1.2.2) 226 | shoulda-matchers (4.3.0) 227 | activesupport (>= 4.2.0) 228 | spring (2.1.0) 229 | spring-watcher-listen (2.0.1) 230 | listen (>= 2.7, < 4.0) 231 | spring (>= 1.2, < 3.0) 232 | sprockets (4.0.2) 233 | concurrent-ruby (~> 1.0) 234 | rack (> 1, < 3) 235 | sprockets-rails (3.2.1) 236 | actionpack (>= 4.0) 237 | activesupport (>= 4.0) 238 | sprockets (>= 3.0.0) 239 | thor (1.0.1) 240 | thread_safe (0.3.6) 241 | tilt (2.0.10) 242 | turbolinks (5.2.1) 243 | turbolinks-source (~> 5.2) 244 | turbolinks-source (5.2.0) 245 | tzinfo (1.2.7) 246 | thread_safe (~> 0.1) 247 | unf (0.1.4) 248 | unf_ext 249 | unf_ext (0.0.7.7) 250 | unicode-display_width (1.7.0) 251 | web-console (4.0.2) 252 | actionview (>= 6.0.0) 253 | activemodel (>= 6.0.0) 254 | bindex (>= 0.4.0) 255 | railties (>= 6.0.0) 256 | webdrivers (4.4.1) 257 | nokogiri (~> 1.6) 258 | rubyzip (>= 1.3.0) 259 | selenium-webdriver (>= 3.0, < 4.0) 260 | webpacker (4.2.2) 261 | activesupport (>= 4.2) 262 | rack-proxy (>= 0.6.1) 263 | railties (>= 4.2) 264 | websocket-driver (0.7.2) 265 | websocket-extensions (>= 0.1.0) 266 | websocket-extensions (0.1.5) 267 | xpath (3.2.0) 268 | nokogiri (~> 1.8) 269 | zeitwerk (2.3.0) 270 | 271 | PLATFORMS 272 | ruby 273 | 274 | DEPENDENCIES 275 | bootsnap (>= 1.4.2) 276 | bootstrap (~> 4.5) 277 | byebug 278 | capybara (>= 2.15) 279 | cloudinary 280 | image_processing (~> 1.2) 281 | jbuilder (~> 2.7) 282 | listen (~> 3.2) 283 | pg (>= 0.18, < 2.0) 284 | puma (~> 4.1) 285 | rails (~> 6.0.3, >= 6.0.3.1) 286 | rspec-rails 287 | rubocop 288 | sass-rails (>= 6) 289 | selenium-webdriver 290 | shoulda-matchers 291 | spring 292 | spring-watcher-listen (~> 2.0.0) 293 | turbolinks (~> 5) 294 | tzinfo-data 295 | web-console (>= 3.3.0) 296 | webdrivers 297 | webpacker (~> 4.0) 298 | 299 | RUBY VERSION 300 | ruby 2.6.5p114 301 | 302 | BUNDLED WITH 303 | 2.1.4 304 | --------------------------------------------------------------------------------