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