diff --git a/Gemfile b/Gemfile index 3f1e6961..1e00e191 100644 --- a/Gemfile +++ b/Gemfile @@ -47,6 +47,7 @@ group :development, :test do gem 'capybara' gem 'pry' gem 'rails-controller-testing' + gem 'faker' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 16c3b7bc..94e78895 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -82,6 +82,8 @@ GEM factory_bot_rails (1.0.0.alpha) factory_bot (~> 1.0.0.alpha) railties (>= 3.0.0) + faker (2.2.1) + i18n (>= 0.8) ffi (1.9.25) globalid (0.4.1) activesupport (>= 4.2.0) @@ -233,6 +235,7 @@ DEPENDENCIES coffee-rails (~> 4.2) devise factory_bot_rails + faker jbuilder (~> 2.5) listen (>= 3.0.5, < 3.2) pry @@ -254,4 +257,4 @@ RUBY VERSION ruby 2.4.4p296 BUNDLED WITH - 1.16.2 + 2.1.4 diff --git a/app/controllers/dogs_controller.rb b/app/controllers/dogs_controller.rb index cb9eebc5..50fd8593 100644 --- a/app/controllers/dogs_controller.rb +++ b/app/controllers/dogs_controller.rb @@ -1,10 +1,11 @@ class DogsController < ApplicationController - before_action :set_dog, only: [:show, :edit, :update, :destroy] + before_action :set_dogs, only: %i[index] + before_action :set_dog, only: %i[show edit update destroy] + before_action :redirect_unless_can_update, only: %i[edit update destroy] # GET /dogs # GET /dogs.json def index - @dogs = Dog.all end # GET /dogs/1 @@ -75,4 +76,21 @@ def set_dog def dog_params params.require(:dog).permit(:name, :description, :images) end + + def redirect_unless_can_update + unless current_user.owner_of?(@dog) + flash[:notice] = "Oops you can't do that" + redirect_to dog_path(@dog) + end + end + + def set_dogs + if params[:sort] + scope = params[:sort] + else + scope = :all + end + + @dogs = Dog.send(scope) + end end diff --git a/app/controllers/likes_controller.rb b/app/controllers/likes_controller.rb new file mode 100644 index 00000000..25848528 --- /dev/null +++ b/app/controllers/likes_controller.rb @@ -0,0 +1,28 @@ +class LikesController < ApplicationController + before_action :set_dog, only: %i[create destroy] + + def create + like = @dog.likes.where(user_id: current_user.id).first_or_initialize + + respond_to do |format| + if like.save + format.js { render :create } + else + format.json { render json: { errors: like.errors }, status: :unprocessable_entity } + end + end + end + + def destroy + @dog.likes.where(user_id: current_user.id).destroy_all + + respond_to do |format| + format.js { render :destroy } + end + end + + private + def set_dog + @dog = Dog.find(params[:dog_id]) + end +end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb new file mode 100644 index 00000000..7e9a60ff --- /dev/null +++ b/app/helpers/users_helper.rb @@ -0,0 +1,18 @@ +module UsersHelper + def can_edit?(dog) + logged_in? && + current_user.owner_of?(dog) + end + + def can_like?(dog) + logged_in? && + !current_user.owner_of?(dog) + end + + private + + def logged_in? + current_user.present? + end +end + diff --git a/app/models/dog.rb b/app/models/dog.rb index eb40bf6e..45087199 100644 --- a/app/models/dog.rb +++ b/app/models/dog.rb @@ -1,3 +1,8 @@ class Dog < ApplicationRecord has_many_attached :images + has_many :likes + belongs_to :user + + # TODO: Still needs ordering by recent likes (in the last hour) + scope :recent_likes, -> { order("likes_count DESC") } end diff --git a/app/models/like.rb b/app/models/like.rb new file mode 100644 index 00000000..9f363f3a --- /dev/null +++ b/app/models/like.rb @@ -0,0 +1,4 @@ +class Like < ApplicationRecord + belongs_to :dog, counter_cache: true + belongs_to :user +end diff --git a/app/models/user.rb b/app/models/user.rb index b2091f9a..d55272f3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,4 +3,15 @@ class User < ApplicationRecord # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable + + has_many :dogs + has_many :likes + + def owner_of?(dog) + id == dog.user_id + end + + def likes?(dog) + dog.likes.where(user_id: id).any? + end end diff --git a/app/views/dogs/_likes.html.erb b/app/views/dogs/_likes.html.erb new file mode 100644 index 00000000..e4eb5149 --- /dev/null +++ b/app/views/dogs/_likes.html.erb @@ -0,0 +1,9 @@ +<% if can_like?(dog) %> + <% if current_user.likes?(dog) %> + <%= link_to "Unlike", dog_like_path(dog), method: :delete, remote: true %> + <% else %> + <%= link_to "Like", dog_likes_path(dog), method: :post, remote: true %> + <% end %> +<% end %> + +

Likes: <%= dog.likes.count %>

diff --git a/app/views/dogs/_thumbnail.html.erb b/app/views/dogs/_thumbnail.html.erb index 4d6bb441..7e19af86 100644 --- a/app/views/dogs/_thumbnail.html.erb +++ b/app/views/dogs/_thumbnail.html.erb @@ -1,6 +1,13 @@ - -
-

<%= dog.name %>

- <%= image_tag url_for(dog.images.first), class: "dog-photo", alt: "Photo of #{dog.name}" %> -
-
+<% if (dog_counter + 1) % 3 == 0 %> + <%= render "shared/ad" %> +<% else %> + +
+

<%= dog.name %>

+
+ <%= render "dogs/likes", dog: dog %> +
+ <%= image_tag url_for(dog.images.first), class: "dog-photo", alt: "Photo of #{dog.name}" %> +
+
+<% end %> diff --git a/app/views/dogs/index.html.erb b/app/views/dogs/index.html.erb index 91ea5603..f68ac7cc 100644 --- a/app/views/dogs/index.html.erb +++ b/app/views/dogs/index.html.erb @@ -1 +1,6 @@ +<%= form_tag dogs_path, method: :get do %> + <%= radio_button_tag :sort, "recent_likes", params[:sort] == "recent_likes" ? true : false %> + <%= label_tag :sort_likes, "Most likes in the last hour" %> + <%= submit_tag "Sort" %> +<% end %> <%= render partial: 'thumbnail', collection: @dogs, as: :dog %> diff --git a/app/views/dogs/show.html.erb b/app/views/dogs/show.html.erb index 562324d2..5f4b2ca1 100644 --- a/app/views/dogs/show.html.erb +++ b/app/views/dogs/show.html.erb @@ -7,7 +7,13 @@

<%= @dog.description %>

- <%= link_to "Edit #{@dog.name}'s Profile", edit_dog_path %> -
- <%= link_to "Delete #{@dog.name}'s Profile", dog_path, method: :delete, data: { confirm: 'Are you sure?' } %> +
+ <%= render "dogs/likes", dog: @dog %> +
+ + <% if can_edit?(@dog) %> + <%= link_to "Edit #{@dog.name}'s Profile", edit_dog_path %> +
+ <%= link_to "Delete #{@dog.name}'s Profile", dog_path, method: :delete, data: { confirm: 'Are you sure?' } %> + <% end %> diff --git a/app/views/likes/create.js.erb b/app/views/likes/create.js.erb new file mode 100644 index 00000000..d7cd05cc --- /dev/null +++ b/app/views/likes/create.js.erb @@ -0,0 +1,3 @@ +document +.getElementById("dog_<%= @dog.id %>_likes") +.innerHTML = "<%= j render partial: "dogs/likes", locals: { dog: @dog } %>"; diff --git a/app/views/likes/destroy.js.erb b/app/views/likes/destroy.js.erb new file mode 100644 index 00000000..d7cd05cc --- /dev/null +++ b/app/views/likes/destroy.js.erb @@ -0,0 +1,3 @@ +document +.getElementById("dog_<%= @dog.id %>_likes") +.innerHTML = "<%= j render partial: "dogs/likes", locals: { dog: @dog } %>"; diff --git a/app/views/shared/_ad.html.erb b/app/views/shared/_ad.html.erb new file mode 100644 index 00000000..11c8b7fc --- /dev/null +++ b/app/views/shared/_ad.html.erb @@ -0,0 +1 @@ +<%= image_tag("ad.jpg") %> diff --git a/config/routes.rb b/config/routes.rb index 06b01adc..38ee4186 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,9 @@ Rails.application.routes.draw do # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html devise_for :users - resources :dogs + resources :dogs do + resources :likes, only: %i[create] + delete "/:dog_id", to: "likes#destroy", as: :like + end root to: "dogs#index" end diff --git a/db/migrate/20200825221745_add_user_to_dogs.rb b/db/migrate/20200825221745_add_user_to_dogs.rb new file mode 100644 index 00000000..f94e637b --- /dev/null +++ b/db/migrate/20200825221745_add_user_to_dogs.rb @@ -0,0 +1,5 @@ +class AddUserToDogs < ActiveRecord::Migration[5.2] + def change + add_reference :dogs, :user, foreign_key: true + end +end diff --git a/db/migrate/20200826004158_create_likes.rb b/db/migrate/20200826004158_create_likes.rb new file mode 100644 index 00000000..7528c949 --- /dev/null +++ b/db/migrate/20200826004158_create_likes.rb @@ -0,0 +1,8 @@ +class CreateLikes < ActiveRecord::Migration[5.2] + def change + create_table :likes do |t| + t.references :user, foreign_key: true + t.references :dog, foreign_key: true + end + end +end diff --git a/db/migrate/20200826022636_add_likes_count_to_dogs.rb b/db/migrate/20200826022636_add_likes_count_to_dogs.rb new file mode 100644 index 00000000..cef9a661 --- /dev/null +++ b/db/migrate/20200826022636_add_likes_count_to_dogs.rb @@ -0,0 +1,5 @@ +class AddLikesCountToDogs < ActiveRecord::Migration[5.2] + def change + add_column :dogs, :likes_count, :integer + end +end diff --git a/db/migrate/20200826023426_add_time_stamp_to_likes.rb b/db/migrate/20200826023426_add_time_stamp_to_likes.rb new file mode 100644 index 00000000..331f569a --- /dev/null +++ b/db/migrate/20200826023426_add_time_stamp_to_likes.rb @@ -0,0 +1,5 @@ +class AddTimeStampToLikes < ActiveRecord::Migration[5.2] + def change + add_timestamps :likes, null: false, default: Time.now + end +end diff --git a/db/schema.rb b/db/schema.rb index 462bd430..ab56fce4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2018_06_07_114248) do +ActiveRecord::Schema.define(version: 2020_08_26_023426) do create_table "active_storage_attachments", force: :cascade do |t| t.string "name", null: false @@ -40,6 +40,18 @@ t.text "description" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "user_id" + t.integer "likes_count" + t.index ["user_id"], name: "index_dogs_on_user_id" + end + + create_table "likes", force: :cascade do |t| + t.integer "user_id" + t.integer "dog_id" + t.datetime "created_at", default: "2020-08-26 02:36:39", null: false + t.datetime "updated_at", default: "2020-08-26 02:36:39", null: false + t.index ["dog_id"], name: "index_likes_on_dog_id" + t.index ["user_id"], name: "index_likes_on_user_id" end create_table "users", force: :cascade do |t| diff --git a/db/seeds.rb b/db/seeds.rb index d3e6fde3..8daaf63b 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -6,6 +6,9 @@ # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) # Character.create(name: 'Luke', movie: movies.first) +Dog.destroy_all +User.destroy_all + dogs = [ { name: 'Bowie', @@ -49,8 +52,10 @@ }, ] +5.times { FactoryBot.create(:user) } + dogs.each do |dog| - dog = Dog.find_or_create_by(name: dog[:name], description: dog[:description]) + dog = Dog.find_or_create_by(name: dog[:name], description: dog[:description], user: User.all.sample) directory_name = File.join(Rails.root, 'db', 'seed', "#{dog[:name].downcase}", "*") Dir.glob(directory_name).each do |filename| diff --git a/spec/factories/dogs.rb b/spec/factories/dogs.rb index 406874f5..25f7cfe0 100644 --- a/spec/factories/dogs.rb +++ b/spec/factories/dogs.rb @@ -9,5 +9,7 @@ filename: 'bowie_toys.jpeg', content_type: 'image/jpeg') end + + user { create :user } end end diff --git a/spec/factories/likes.rb b/spec/factories/likes.rb new file mode 100644 index 00000000..31d14bd6 --- /dev/null +++ b/spec/factories/likes.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :like do + user { create :user } + dog { create :dog } + end +end diff --git a/spec/factories/users.rb b/spec/factories/users.rb new file mode 100644 index 00000000..6bf9ed14 --- /dev/null +++ b/spec/factories/users.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :user do + email { Faker::Internet.email } + password { Faker::Internet.password } + end +end diff --git a/spec/models/users_spec.rb b/spec/models/users_spec.rb new file mode 100644 index 00000000..537934d0 --- /dev/null +++ b/spec/models/users_spec.rb @@ -0,0 +1,33 @@ +require "rails_helper" + +describe User do + describe "#owner_of?" do + subject { user.owner_of? dog } + + let(:user) { create :user } + + context "when the user owns the dog" do + let(:dog) { create :dog, user: user } + it { is_expected.to be true } + end + + context "when the dog belongs to another user" do + let(:dog) { create :dog, user: create(:user) } + it { is_expected.to be false } + end + end + + describe "#likes?" do + subject { user.likes? dog } + + let(:dog) { create(:dog) } + let(:user) { dog.user } + + it { is_expected.to be false } + + context "when a user has liked a dog" do + before { create :like, user: user, dog: dog } + it { is_expected.to be true } + end + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 4e322aeb..58192bba 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -56,4 +56,5 @@ config.filter_rails_from_backtrace! # arbitrary gems may also be filtered via: # config.filter_gems_from_backtrace("gem name") + config.include Devise::Test::ControllerHelpers, type: :controller end