diff --git a/Gemfile.lock b/Gemfile.lock index b4afcdd92..c62dbb628 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -86,7 +86,7 @@ GEM uniform_notifier (~> 1.11) byebug (11.0.1) cancancan (3.0.1) - capybara (3.25.0) + capybara (3.26.0) addressable mini_mime (>= 0.1.3) nokogiri (~> 1.8) @@ -104,7 +104,7 @@ GEM activemodel (>= 4.0.0) activesupport (>= 4.0.0) mime-types (>= 1.16) - chartkick (3.2.0) + chartkick (3.2.1) childprocess (1.0.1) rake (< 13.0) codeclimate-test-reporter (1.0.9) @@ -181,7 +181,7 @@ GEM figaro (1.1.1) thor (~> 0.14) flickraw (0.9.10) - font-awesome-sass (5.8.1) + font-awesome-sass (5.9.0) sassc (>= 1.11) friendly_id (5.2.5) activerecord (>= 4.0.0) @@ -253,7 +253,7 @@ GEM railties (>= 4) sprockets-rails json (2.2.0) - jsonapi-resources (0.9.9) + jsonapi-resources (0.9.10) activerecord (>= 4.1) concurrent-ruby railties (>= 4.1) @@ -303,8 +303,8 @@ GEM mime-types-data (~> 3.2015) mime-types-data (3.2019.0331) mimemagic (0.3.3) - mini_magick (4.9.3) - mini_mime (1.0.1) + mini_magick (4.9.4) + mini_mime (1.0.2) mini_portile2 (2.4.0) minitest (5.11.3) moneta (1.0.0) @@ -312,7 +312,7 @@ GEM multi_xml (0.6.0) multipart-post (2.1.1) newrelic_rpm (6.5.0.357) - nio4r (2.3.1) + nio4r (2.4.0) nokogiri (1.10.3) mini_portile2 (~> 2.4.0) oauth (0.5.4) @@ -322,7 +322,7 @@ GEM multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) - oj (3.7.12) + oj (3.8.0) omniauth (1.9.0) hashie (>= 3.4.6, < 3.7.0) rack (>= 1.6.2, < 3) @@ -353,7 +353,7 @@ GEM moneta (~> 1.0.0) popper_js (1.14.5) public_suffix (3.1.1) - puma (4.0.0) + puma (4.0.1) nio4r (~> 2.0) rack (2.0.7) rack-protection (2.0.5) @@ -431,14 +431,14 @@ GEM rspec-mocks (~> 3.8.0) rspec-support (~> 3.8.0) rspec-support (3.8.2) - rubocop (0.72.0) + rubocop (0.73.0) jaro_winkler (~> 1.5.1) parallel (~> 1.10) parser (>= 2.6) rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 1.7) - rubocop-rails (2.2.0) + rubocop-rails (2.2.1) rack (>= 1.1) rubocop (>= 0.72.0) rubocop-rspec (1.33.0) @@ -518,7 +518,7 @@ GEM uniform_notifier (1.12.1) warden (1.2.8) rack (>= 2.0.6) - webdrivers (4.1.0) + webdrivers (4.1.1) nokogiri (~> 1.6) rubyzip (~> 1.0) selenium-webdriver (>= 3.0, < 4.0) diff --git a/app/assets/javascripts/likes.js b/app/assets/javascripts/likes.js new file mode 100644 index 000000000..60b3e57e7 --- /dev/null +++ b/app/assets/javascripts/likes.js @@ -0,0 +1,40 @@ +$(document).ready(function() { + $('.like-btn').show(); + + $('.post-like').on('ajax:success', function(event, data) { + var likeButton = $('#post-' + data.id + ' .post-like'); + var likeBadge = $('#post-'+ data.id + ' .like-badge'); + + $('#post-' + data.id + ' .like-count').text(data.like_count); + if (data.liked_by_member) { + likeBadge.addClass('liked'); + likeButton.data('method', 'delete'); + likeButton.attr('href', data.url); + likeButton.text('Unlike'); + } else { + likeBadge.removeClass('liked'); + likeButton.data('method', 'post'); + likeButton.attr('href', '/likes.json?post_id=' + data.id); + likeButton.text('Like'); + } + }); + + + $('.photo-like').on('ajax:success', function(event, data) { + var likeBadge = $('#photo-'+ data.id + ' .like-badge'); + var likeButton = $('#photo-'+ data.id + ' .like-btn'); + + $('#photo-' + data.id + ' .like-count').text(data.like_count); + if (data.liked_by_member) { + likeBadge.addClass('liked'); + // Turn the button into an unlike button + likeButton.data('method', 'delete'); + likeButton.attr('href', data.url); + } else { + likeBadge.removeClass('liked'); + // Turn the button into an *like* button + likeButton.data('method', 'post'); + likeButton.attr('href', '/likes.json?photo_id=' + data.id); + } + }); +}); diff --git a/app/assets/javascripts/posts.js b/app/assets/javascripts/posts.js deleted file mode 100644 index 37269e78e..000000000 --- a/app/assets/javascripts/posts.js +++ /dev/null @@ -1,18 +0,0 @@ -$(document).ready(function() { - $('.post-like').show(); - - $('.post-like').on('ajax:success', function(event, data) { - var likeControl = $('#post-' + data.id + ' .post-like'); - - $('#post-' + data.id + ' .like-count').text(data.description); - if (data.liked_by_member) { - likeControl.data('method', 'delete'); - likeControl.attr('href', data.url); - likeControl.text('Unlike'); - } else { - likeControl.data('method', 'post'); - likeControl.attr('href', '/likes.json?post_id=' + data.id); - likeControl.text('Like'); - } - }); -}); diff --git a/app/assets/stylesheets/_likes.scss b/app/assets/stylesheets/_likes.scss new file mode 100644 index 000000000..39bad5408 --- /dev/null +++ b/app/assets/stylesheets/_likes.scss @@ -0,0 +1,7 @@ +.liked { + color: $red; +} + +.like-btn { + color: $brown; +} diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 03c66c680..cdd758685 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -16,14 +16,6 @@ @import 'rails_bootstrap_forms'; @import 'overrides'; -@import 'crops'; -@import 'harvests'; -@import 'members'; -@import 'notifications'; -@import 'plantings'; -@import 'posts'; -@import 'predictions'; -@import 'seeds'; @import 'homepage'; @import 'photos'; diff --git a/app/controllers/likes_controller.rb b/app/controllers/likes_controller.rb index e942d5b1f..2837dfb0a 100644 --- a/app/controllers/likes_controller.rb +++ b/app/controllers/likes_controller.rb @@ -19,12 +19,17 @@ class LikesController < ApplicationController private def find_likeable - Post.find(params[:post_id]) if params[:post_id] + if params[:post_id] + Post.find(params[:post_id]) + elsif params[:photo_id] + Photo.find(params[:photo_id]) + end end def render_json(like, liked_by_member: true) { id: like.likeable.id, + like_count: like.likeable.likes.count, liked_by_member: liked_by_member, description: ActionController::Base.helpers.pluralize(like.likeable.likes.count, "like"), url: like_path(like, format: :json) @@ -35,7 +40,8 @@ class LikesController < ApplicationController respond_to do |format| format.html { redirect_to like.likeable } format.json do - render(json: render_json(like, liked_by_member: liked_by_member), + render(json: render_json(like, + liked_by_member: liked_by_member), status: status_code) end end diff --git a/app/controllers/photo_associations_controller.rb b/app/controllers/photo_associations_controller.rb index 743966d5d..499dbefc0 100644 --- a/app/controllers/photo_associations_controller.rb +++ b/app/controllers/photo_associations_controller.rb @@ -6,7 +6,7 @@ class PhotoAssociationsController < ApplicationController raise "Photos not supported" unless Photo::PHOTO_CAPABLE.include? item_class @photo = Photo.find_by!(id: params[:photo_id], owner: current_member) - @item = Photographing.item(item_id, item_class) + @item = PhotoAssociation.item(item_id, item_class) @item.photos.delete(@photo) # @photo.destroy_if_unused respond_with(@photo) diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index a38dc6130..fe3f84d02 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -6,7 +6,7 @@ class PhotosController < ApplicationController responders :flash def show - @crops = Crop.distinct.joins(:photographings).where(photographings: { photo: @photo }) + @crops = Crop.distinct.joins(:photo_associations).where(photo_associations: { photo: @photo }) respond_with(@photo) end diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index 2fa078313..a691b5218 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -70,7 +70,7 @@ module IconsHelper end def like_icon - icon('fas', 'thumbs-up') + icon('fas', 'heart') end def sunniness_icon(sunniness) diff --git a/app/models/concerns/likeable.rb b/app/models/concerns/likeable.rb index 328cf3153..4937af53e 100644 --- a/app/models/concerns/likeable.rb +++ b/app/models/concerns/likeable.rb @@ -5,4 +5,8 @@ module Likeable has_many :likes, as: :likeable, inverse_of: :likeable, dependent: :destroy has_many :members, through: :likes end + + def liked_by?(member) + member && members.include?(member) + end end diff --git a/app/models/concerns/photo_capable.rb b/app/models/concerns/photo_capable.rb index 3ae7303d9..dfddef42c 100644 --- a/app/models/concerns/photo_capable.rb +++ b/app/models/concerns/photo_capable.rb @@ -2,9 +2,17 @@ module PhotoCapable extend ActiveSupport::Concern included do - has_many :photographings, as: :photographable, dependent: :destroy, inverse_of: :photographable - has_many :photos, through: :photographings, as: :photographable + has_many :photo_associations, as: :photographable, dependent: :destroy, inverse_of: :photographable + has_many :photos, through: :photo_associations, as: :photographable scope :has_photos, -> { includes(:photos).where.not(photos: { id: nil }) } + + def default_photo + most_liked_photo + end + + def most_liked_photo + photos.order(likes_count: :desc, created_at: :desc).first + end end end diff --git a/app/models/crop.rb b/app/models/crop.rb index 4e56d4645..12eb01975 100644 --- a/app/models/crop.rb +++ b/app/models/crop.rb @@ -1,5 +1,6 @@ class Crop < ApplicationRecord extend FriendlyId + include PhotoCapable friendly_id :name, use: %i(slugged finders) ## @@ -14,8 +15,8 @@ class Crop < ApplicationRecord has_many :plantings, dependent: :destroy has_many :seeds, dependent: :destroy has_many :harvests, dependent: :destroy - has_many :photographings, dependent: :destroy - has_many :photos, through: :photographings + has_many :photo_associations, dependent: :destroy + has_many :photos, through: :photo_associations has_many :plant_parts, -> { distinct.order("plant_parts.name") }, through: :harvests belongs_to :creator, class_name: 'Member', optional: true, inverse_of: :created_crops belongs_to :requester, class_name: 'Member', optional: true, inverse_of: :requested_crops @@ -57,18 +58,6 @@ class Crop < ApplicationRecord # Elastic search configuration searchkick word_start: %i(name alternate_names scientific_names), case_sensitive: false if ENV["GROWSTUFF_ELASTICSEARCH"] == "true" - def planting_photos - Photo.joins(:plantings).where("plantings.crop_id": id) - end - - def harvest_photos - Photo.joins(:harvests).where("harvests.crop_id": id) - end - - def seed_photos - Photo.joins(:seeds).where("seeds.crop_id": id) - end - def to_s name end @@ -78,14 +67,7 @@ class Crop < ApplicationRecord end def default_scientific_name - scientific_names.first.name unless scientific_names.empty? - end - - # currently returns the first available photo, but exists so that - # later we can choose a default photo based on different criteria, - # eg. popularity - def default_photo - first_photo(:plantings) || first_photo(:harvests) || first_photo(:seeds) + scientific_names.first&.name end # returns hash indicating whether this crop is grown in @@ -212,8 +194,4 @@ class Crop < ApplicationRecord errors.add(:rejection_notes, "must be added if the reason for rejection is \"other\"") end - - def first_photo(type) - Photo.joins(type).where("#{type}": { crop_id: id }).order("photos.created_at DESC").first - end end diff --git a/app/models/garden.rb b/app/models/garden.rb index 6c8318f42..8eb27cc78 100644 --- a/app/models/garden.rb +++ b/app/models/garden.rb @@ -89,8 +89,4 @@ class Garden < ApplicationRecord p.save end end - - def default_photo - photos.order(created_at: :desc).first - end end diff --git a/app/models/harvest.rb b/app/models/harvest.rb index a913ec8b9..6dffbca6a 100644 --- a/app/models/harvest.rb +++ b/app/models/harvest.rb @@ -74,6 +74,10 @@ class Harvest < ApplicationRecord harvested_at - planting.planted_at end + def default_photo + most_liked_photo || planting&.default_photo + end + # we're storing the harvest weight in kilograms in the db too # to make data manipulation easier def set_si_weight @@ -135,10 +139,6 @@ class Harvest < ApplicationRecord end.to_s end - def default_photo - photos.order(created_at: :desc).first || crop.default_photo - end - private def crop_must_match_planting diff --git a/app/models/like.rb b/app/models/like.rb index 05911fc95..f74903739 100644 --- a/app/models/like.rb +++ b/app/models/like.rb @@ -1,6 +1,6 @@ class Like < ApplicationRecord belongs_to :member - belongs_to :likeable, polymorphic: true + belongs_to :likeable, polymorphic: true, counter_cache: true validates :member, :likeable, presence: true validates :member, uniqueness: { scope: :likeable } end diff --git a/app/models/photo.rb b/app/models/photo.rb index 40620b6b5..de44ebbbd 100644 --- a/app/models/photo.rb +++ b/app/models/photo.rb @@ -1,23 +1,24 @@ class Photo < ApplicationRecord + include Likeable include Ownable PHOTO_CAPABLE = %w(Garden Planting Harvest Seed Post).freeze - has_many :photographings, foreign_key: :photo_id, dependent: :destroy, inverse_of: :photo - has_many :crops, through: :photographings + has_many :photo_associations, foreign_key: :photo_id, dependent: :destroy, inverse_of: :photo + has_many :crops, through: :photo_associations # creates a relationship for each assignee type PHOTO_CAPABLE.each do |type| has_many type.downcase.pluralize.to_s.to_sym, - through: :photographings, + through: :photo_associations, source: :photographable, source_type: type end default_scope { joins(:owner) } # Ensures the owner still exists - scope :by_crop, ->(crop) { joins(:photographings).where(photographings: { crop: crop }) } + scope :by_crop, ->(crop) { joins(:photo_associations).where(photo_associations: { crop: crop }) } scope :by_model, lambda { |model_name| - joins(:photographings).where(photographings: { photographable_type: model_name.to_s }) + joins(:photo_associations).where(photo_associations: { photographable_type: model_name.to_s }) } # This is split into a side-effect free method and a side-effecting method @@ -39,7 +40,7 @@ class Photo < ApplicationRecord end def associations? - photographings.size.positive? + photo_associations.size.positive? end def destroy_if_unused diff --git a/app/models/photographing.rb b/app/models/photo_association.rb similarity index 88% rename from app/models/photographing.rb rename to app/models/photo_association.rb index 2693725a2..24eb79e80 100644 --- a/app/models/photographing.rb +++ b/app/models/photo_association.rb @@ -1,5 +1,5 @@ -class Photographing < ApplicationRecord - belongs_to :photo, inverse_of: :photographings +class PhotoAssociation < ApplicationRecord + belongs_to :photo, inverse_of: :photo_associations belongs_to :photographable, polymorphic: true belongs_to :crop, optional: true diff --git a/app/models/planting.rb b/app/models/planting.rb index e57bf6fb0..67bdd281d 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -85,10 +85,6 @@ class Planting < ApplicationRecord I18n.t('plantings.string', crop: crop.name, garden: garden.name, owner: owner) end - def default_photo - photos.order(created_at: :desc).first - end - def finished? finished || (finished_at.present? && finished_at <= Time.zone.today) end diff --git a/app/models/seed.rb b/app/models/seed.rb index 8025f66e2..b335127c0 100644 --- a/app/models/seed.rb +++ b/app/models/seed.rb @@ -55,10 +55,6 @@ class Seed < ApplicationRecord scope :recent, -> { order(created_at: :desc) } scope :active, -> { where('finished_at < ?', Time.zone.now) } - def default_photo - photos.order(created_at: :desc).first - end - def tradable? tradable_to != 'nowhere' end diff --git a/app/views/admin/index.html.haml b/app/views/admin/index.html.haml index f21c5f93a..5586cfec3 100644 --- a/app/views/admin/index.html.haml +++ b/app/views/admin/index.html.haml @@ -1,11 +1,7 @@ - content_for :title, 'Admin' - content_for :breadcrumbs do - %nav{"aria-label" => "breadcrumb"} - %ol.breadcrumb - %li.breadcrumb-item - = link_to 'Home', root_path - %li.breadcrumb-item.active= link_to 'Admin', admin_path + %li.breadcrumb-item.active= link_to 'Admin', admin_path %h2 Manage diff --git a/app/views/crops/_photos.html.haml b/app/views/crops/_photos.html.haml index 170867ed8..71358d541 100644 --- a/app/views/crops/_photos.html.haml +++ b/app/views/crops/_photos.html.haml @@ -3,7 +3,7 @@ - [Planting, Harvest, Seed].each do |model_name| - if photos.by_model(model_name).size.positive? %h3 #{@crop.name.capitalize} #{t("activerecord.models.#{model_name.to_s.downcase}.other")} - = render 'photos/gallery', photos: photos.by_model(model_name).limit(8) + = render 'photos/gallery', photos: photos.by_model(model_name).order(likes_count: :desc).limit(8) = link_to 'more photos ยป', crop_photos_path(@crop), class: 'btn' %hr/ diff --git a/app/views/devise/registrations/edit.html.haml b/app/views/devise/registrations/edit.html.haml index 075fdd4a3..3a3fe90b4 100644 --- a/app/views/devise/registrations/edit.html.haml +++ b/app/views/devise/registrations/edit.html.haml @@ -1,5 +1,8 @@ - content_for :title, "Settings for #{current_member.login_name}" +- content_for :breadcrumbs do + %li.breadcrumb-item= link_to 'My Profile', member_path(current_member) + %h1 = member_icon Settings for #{current_member.login_name} diff --git a/app/views/likes/_count.haml b/app/views/likes/_count.haml new file mode 100644 index 000000000..79fece151 --- /dev/null +++ b/app/views/likes/_count.haml @@ -0,0 +1,4 @@ +%span.badge.like-badge{class: (likeable.liked_by?(current_member)) ? 'liked' : ''} + = like_icon +   + %span.like-count= likeable.likes_count diff --git a/app/views/photos/_association_delete_button.haml b/app/views/photos/_association_delete_button.haml index 1a0f0510f..f4144dd48 100644 --- a/app/views/photos/_association_delete_button.haml +++ b/app/views/photos/_association_delete_button.haml @@ -1,5 +1,5 @@ - if can? :edit, photo - = link_to photo_associations_path(photo_id: photo.id, type: type, id: thing.id), + = link_to photo_association_path(photo_id: photo.id, type: type, id: thing.id), data: { confirm: "Removing photo from this #{type}. Are you sure?" }, method: 'delete', class: 'text-warning btn-sm' do = delete_association_icon diff --git a/app/views/photos/_card.html.haml b/app/views/photos/_card.html.haml index d69c0230b..0d17281f5 100644 --- a/app/views/photos/_card.html.haml +++ b/app/views/photos/_card.html.haml @@ -1,9 +1,10 @@ -.card.photo-card +.card.photo-card{id: "photo-#{photo.id}"} = link_to image_tag(photo.fullsize_url, alt: photo.title, class: 'img img-card'), photo .card-body - %h3.ellipsis= link_to photo.title, photo - %p - %i by #{link_to photo.owner, photo.owner} - - if photo.date_taken.present? - %small= I18n.l(photo.date_taken.to_date) - + %h5.ellipsis= link_to photo.title, photo + %i by #{link_to photo.owner, photo.owner} + - if photo.date_taken.present? + %small + %time{datetime: photo.date_taken} + = I18n.l(photo.date_taken.to_date) + = render 'photos/likes', photo: photo diff --git a/app/views/photos/_likes.html.haml b/app/views/photos/_likes.html.haml new file mode 100644 index 000000000..47cca8634 --- /dev/null +++ b/app/views/photos/_likes.html.haml @@ -0,0 +1,13 @@ +- if member_signed_in? + - if can?(:new, Like) && !photo.liked_by?(current_member) + = link_to likes_path(photo_id: photo.id, format: :json), + method: :post, remote: true, class: 'photo-like like-btn' do + = render 'likes/count', likeable: photo + - else + - like = photo.likes.find_by(member: current_member) + - if like && can?(:destroy, like) + = link_to like_path(id: like.id, format: :json), + method: :delete, remote: true, class: 'photo-like like-btn' do + = render 'likes/count', likeable: photo +- else + = render 'likes/count', likeable: photo \ No newline at end of file diff --git a/app/views/photos/_thumbnail.html.haml b/app/views/photos/_thumbnail.html.haml index dfc6483d3..b6b619f63 100644 --- a/app/views/photos/_thumbnail.html.haml +++ b/app/views/photos/_thumbnail.html.haml @@ -1,5 +1,5 @@ .thumbnail - .photo-thumbnail + .photo-thumbnail{id: "photo-#{photo.id}"} = link_to image_tag(photo.thumbnail_url, alt: photo.title, class: 'img img-responsive rounded'), photo .text.ellipsis %p @@ -9,4 +9,3 @@ %i by = link_to photo.owner, photo.owner - diff --git a/app/views/photos/show.html.haml b/app/views/photos/show.html.haml index bd8632f4d..ea309fe42 100644 --- a/app/views/photos/show.html.haml +++ b/app/views/photos/show.html.haml @@ -11,17 +11,23 @@ %li.breadcrumb-item= link_to 'Photos', photos_path %li.breadcrumb-item.active= link_to @photo, @photo -.row +.row{id: "photo-#{@photo.id}"} .col-md-8 %h1.text-center.ellipsis=@photo.title %p.text-center = image_tag(@photo.fullsize_url, alt: @photo.title, class: 'rounded img-fluid shadow-sm') .col-md-4 - %p.text-center + + %p = render 'photos/actions', photo: @photo = link_to "View on Flickr", @photo.link_url, class: 'btn' + %span.btn= render 'photos/likes', photo: @photo + + - if @crops.size.positive? - %p= render @crops + .index-cards + - @crops.each do |crop| + = render 'crops/thumbnail', crop: crop %p = photo_icon %strong Photo by diff --git a/app/views/posts/_likes.html.haml b/app/views/posts/_likes.html.haml index 56ed606d7..18112c308 100644 --- a/app/views/posts/_likes.html.haml +++ b/app/views/posts/_likes.html.haml @@ -1,18 +1,12 @@ - if member_signed_in? - - if !post.members.include? current_member - - if can?(:new, Like) - = link_to 'Like', likes_path(post_id: post.id, format: :json), - method: :post, remote: true, class: 'post-like btn' + - if can?(:new, Like) && !post.liked_by?(current_member) + = link_to 'Like', likes_path(post_id: post.id, format: :json), + method: :post, remote: true, class: 'post-like btn like-btn' - else - like = post.likes.find_by(member: current_member) - if like && can?(:destroy, like) = link_to 'Unlike', like_path(id: like.id, format: :json), - method: :delete, remote: true, class: 'post-like btn' + method: :delete, remote: true, class: 'post-like btn like-btn' - -%span.badge.badge-info - .like-count - - unless post.likes.empty? - = like_icon - = pluralize(post.likes.count, "like") += render 'likes/count', likeable: post diff --git a/config/environments/production.rb b/config/environments/production.rb index fd66532d7..9adee7c27 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -100,7 +100,7 @@ Rails.application.configure do } ActionMailer::Base.delivery_method = :smtp - config.host = 'growstuff.org' + config.host = ENV['HOST'] config.analytics_code = <<-eos diff --git a/config/routes.rb b/config/routes.rb index 04a1220fc..401bc3c7f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -54,7 +54,7 @@ Rails.application.routes.draw do resources :plant_parts resources :photos - delete 'photo_associations' => 'photo_associations#destroy' + resources :photo_associations, only: :destroy resources :crops, param: :slug, concerns: :has_photos do get 'gardens' => 'gardens#index' diff --git a/db/migrate/20190130090437_add_crop_to_photographings.rb b/db/migrate/20190130090437_add_crop_to_photographings.rb index 2ff0596f0..3b4ad8788 100644 --- a/db/migrate/20190130090437_add_crop_to_photographings.rb +++ b/db/migrate/20190130090437_add_crop_to_photographings.rb @@ -7,4 +7,9 @@ class AddCropToPhotographings < ActiveRecord::Migration[5.2] p.set_crop && p.save! end end + class Photographing < ApplicationRecord + belongs_to :photo, inverse_of: :photo_associations + belongs_to :photographable, polymorphic: true + belongs_to :crop, optional: true + end end diff --git a/db/migrate/20190711043651_create_mailboxer.mailboxer_engine.rb b/db/migrate/20190711043651_create_mailboxer.mailboxer_engine.rb deleted file mode 100644 index 3b9acbddb..000000000 --- a/db/migrate/20190711043651_create_mailboxer.mailboxer_engine.rb +++ /dev/null @@ -1,65 +0,0 @@ -# This migration comes from mailboxer_engine (originally 20110511145103) -class CreateMailboxer < ActiveRecord::Migration[4.2] - def self.up - # Tables - # Conversations - create_table :mailboxer_conversations do |t| - t.column :subject, :string, default: "" - t.column :created_at, :datetime, null: false - t.column :updated_at, :datetime, null: false - end - # Receipts - create_table :mailboxer_receipts do |t| - t.references :receiver, polymorphic: true - t.column :notification_id, :integer, null: false - t.column :is_read, :boolean, default: false - t.column :trashed, :boolean, default: false - t.column :deleted, :boolean, default: false - t.column :mailbox_type, :string, limit: 25 - t.column :created_at, :datetime, null: false - t.column :updated_at, :datetime, null: false - end - # Notifications and Messages - create_table :mailboxer_notifications do |t| - t.column :type, :string - t.column :body, :text - t.column :subject, :string, default: "" - t.references :sender, polymorphic: true - t.column :conversation_id, :integer - t.column :draft, :boolean, default: false - t.string :notification_code, default: nil - t.references :notified_object, polymorphic: true, index: { name: 'mailboxer_notifications_notified_object' } - t.column :attachment, :string - t.column :updated_at, :datetime, null: false - t.column :created_at, :datetime, null: false - t.boolean :global, default: false - t.datetime :expires - end - - # Indexes - # Conversations - # Receipts - add_index "mailboxer_receipts", "notification_id" - - # Messages - add_index "mailboxer_notifications", "conversation_id" - - # Foreign keys - # Conversations - # Receipts - add_foreign_key "mailboxer_receipts", "mailboxer_notifications", name: "receipts_on_notification_id", column: "notification_id" - # Messages - add_foreign_key "mailboxer_notifications", "mailboxer_conversations", name: "notifications_on_conversation_id", column: "conversation_id" - end - - def self.down - # Tables - remove_foreign_key "mailboxer_receipts", name: "receipts_on_notification_id" - remove_foreign_key "mailboxer_notifications", name: "notifications_on_conversation_id" - - # Indexes - drop_table :mailboxer_receipts - drop_table :mailboxer_conversations - drop_table :mailboxer_notifications - end -end diff --git a/db/migrate/20190712003735_add_like_counter_caches.rb b/db/migrate/20190712003735_add_like_counter_caches.rb new file mode 100644 index 000000000..92c292494 --- /dev/null +++ b/db/migrate/20190712003735_add_like_counter_caches.rb @@ -0,0 +1,34 @@ +class AddLikeCounterCaches < ActiveRecord::Migration[5.2] + def change + change_table :photos do |t| + t.integer :likes_count, default: 0 + end + change_table :posts do |t| + t.integer :likes_count, default: 0 + end + reversible do |dir| + dir.up { data } + end + end + + def data + execute <<-SQL.squish + UPDATE photos + SET likes_count = ( + SELECT count(1) + FROM likes + WHERE likes.likeable_id = photos.id + AND likeable_type = 'Photo' + ) + SQL + execute <<-SQL.squish + UPDATE posts + SET likes_count = ( + SELECT count(1) + FROM likes + WHERE likes.likeable_id = posts.id + AND likeable_type = 'Post' + ) + SQL + end +end diff --git a/db/migrate/20190712234859_rename_photographings.rb b/db/migrate/20190712234859_rename_photographings.rb new file mode 100644 index 000000000..7f9a5127e --- /dev/null +++ b/db/migrate/20190712234859_rename_photographings.rb @@ -0,0 +1,5 @@ +class RenamePhotographings < ActiveRecord::Migration[5.2] + def change + rename_table :photographings, :photo_associations + end +end diff --git a/db/migrate/20190720000555_create_mailboxer.mailboxer_engine.rb b/db/migrate/20190720000555_create_mailboxer.mailboxer_engine.rb new file mode 100644 index 000000000..d42e89f9a --- /dev/null +++ b/db/migrate/20190720000555_create_mailboxer.mailboxer_engine.rb @@ -0,0 +1,65 @@ +# This migration comes from mailboxer_engine (originally 20110511145103) +class CreateMailboxer < ActiveRecord::Migration[4.2] + def self.up + #Tables + #Conversations + create_table :mailboxer_conversations do |t| + t.column :subject, :string, :default => "" + t.column :created_at, :datetime, :null => false + t.column :updated_at, :datetime, :null => false + end + #Receipts + create_table :mailboxer_receipts do |t| + t.references :receiver, :polymorphic => true + t.column :notification_id, :integer, :null => false + t.column :is_read, :boolean, :default => false + t.column :trashed, :boolean, :default => false + t.column :deleted, :boolean, :default => false + t.column :mailbox_type, :string, :limit => 25 + t.column :created_at, :datetime, :null => false + t.column :updated_at, :datetime, :null => false + end + #Notifications and Messages + create_table :mailboxer_notifications do |t| + t.column :type, :string + t.column :body, :text + t.column :subject, :string, :default => "" + t.references :sender, :polymorphic => true + t.column :conversation_id, :integer + t.column :draft, :boolean, :default => false + t.string :notification_code, :default => nil + t.references :notified_object, :polymorphic => true, index: { name: 'mailboxer_notifications_notified_object' } + t.column :attachment, :string + t.column :updated_at, :datetime, :null => false + t.column :created_at, :datetime, :null => false + t.boolean :global, default: false + t.datetime :expires + end + + #Indexes + #Conversations + #Receipts + add_index "mailboxer_receipts","notification_id" + + #Messages + add_index "mailboxer_notifications","conversation_id" + + #Foreign keys + #Conversations + #Receipts + add_foreign_key "mailboxer_receipts", "mailboxer_notifications", :name => "receipts_on_notification_id", :column => "notification_id" + #Messages + add_foreign_key "mailboxer_notifications", "mailboxer_conversations", :name => "notifications_on_conversation_id", :column => "conversation_id" + end + + def self.down + #Tables + remove_foreign_key "mailboxer_receipts", :name => "receipts_on_notification_id" + remove_foreign_key "mailboxer_notifications", :name => "notifications_on_conversation_id" + + #Indexes + drop_table :mailboxer_receipts + drop_table :mailboxer_conversations + drop_table :mailboxer_notifications + end +end diff --git a/db/migrate/20190711043652_add_conversation_optout.mailboxer_engine.rb b/db/migrate/20190720000556_add_conversation_optout.mailboxer_engine.rb similarity index 61% rename from db/migrate/20190711043652_add_conversation_optout.mailboxer_engine.rb rename to db/migrate/20190720000556_add_conversation_optout.mailboxer_engine.rb index a8aa6bbed..a9860bdcb 100644 --- a/db/migrate/20190711043652_add_conversation_optout.mailboxer_engine.rb +++ b/db/migrate/20190720000556_add_conversation_optout.mailboxer_engine.rb @@ -2,14 +2,14 @@ class AddConversationOptout < ActiveRecord::Migration[4.2] def self.up create_table :mailboxer_conversation_opt_outs do |t| - t.references :unsubscriber, polymorphic: true + t.references :unsubscriber, :polymorphic => true t.references :conversation end - add_foreign_key "mailboxer_conversation_opt_outs", "mailboxer_conversations", name: "mb_opt_outs_on_conversations_id", column: "conversation_id" + add_foreign_key "mailboxer_conversation_opt_outs", "mailboxer_conversations", :name => "mb_opt_outs_on_conversations_id", :column => "conversation_id" end def self.down - remove_foreign_key "mailboxer_conversation_opt_outs", name: "mb_opt_outs_on_conversations_id" + remove_foreign_key "mailboxer_conversation_opt_outs", :name => "mb_opt_outs_on_conversations_id" drop_table :mailboxer_conversation_opt_outs end end diff --git a/db/migrate/20190711043653_add_missing_indices.mailboxer_engine.rb b/db/migrate/20190720000557_add_missing_indices.mailboxer_engine.rb similarity index 68% rename from db/migrate/20190711043653_add_missing_indices.mailboxer_engine.rb rename to db/migrate/20190720000557_add_missing_indices.mailboxer_engine.rb index b079f4a3d..608d7f620 100644 --- a/db/migrate/20190711043653_add_missing_indices.mailboxer_engine.rb +++ b/db/migrate/20190720000557_add_missing_indices.mailboxer_engine.rb @@ -3,18 +3,18 @@ class AddMissingIndices < ActiveRecord::Migration[4.2] def change # We'll explicitly specify its name, as the auto-generated name is too long and exceeds 63 # characters limitation. - add_index :mailboxer_conversation_opt_outs, %i(unsubscriber_id unsubscriber_type), + add_index :mailboxer_conversation_opt_outs, [:unsubscriber_id, :unsubscriber_type], name: 'index_mailboxer_conversation_opt_outs_on_unsubscriber_id_type' add_index :mailboxer_conversation_opt_outs, :conversation_id add_index :mailboxer_notifications, :type - add_index :mailboxer_notifications, %i(sender_id sender_type) + add_index :mailboxer_notifications, [:sender_id, :sender_type] # We'll explicitly specify its name, as the auto-generated name is too long and exceeds 63 # characters limitation. - add_index :mailboxer_notifications, %i(notified_object_id notified_object_type), + add_index :mailboxer_notifications, [:notified_object_id, :notified_object_type], name: 'index_mailboxer_notifications_on_notified_object_id_and_type' - add_index :mailboxer_receipts, %i(receiver_id receiver_type) + add_index :mailboxer_receipts, [:receiver_id, :receiver_type] end end diff --git a/db/migrate/20190711043654_add_delivery_tracking_info_to_mailboxer_receipts.mailboxer_engine.rb b/db/migrate/20190720000558_add_delivery_tracking_info_to_mailboxer_receipts.mailboxer_engine.rb similarity index 100% rename from db/migrate/20190711043654_add_delivery_tracking_info_to_mailboxer_receipts.mailboxer_engine.rb rename to db/migrate/20190720000558_add_delivery_tracking_info_to_mailboxer_receipts.mailboxer_engine.rb diff --git a/db/migrate/20190720000625_notifications_to_mailboxer.rb b/db/migrate/20190720000625_notifications_to_mailboxer.rb new file mode 100644 index 000000000..235d3a000 --- /dev/null +++ b/db/migrate/20190720000625_notifications_to_mailboxer.rb @@ -0,0 +1,4 @@ +class NotificationsToMailboxer < ActiveRecord::Migration[5.2] + def change + end +end diff --git a/db/schema.rb b/db/schema.rb index 71f21ede5..a7cf22479 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: 2019_07_11_043654) do +ActiveRecord::Schema.define(version: 2019_07_20_000625) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -221,7 +221,6 @@ ActiveRecord::Schema.define(version: 2019_07_11_043654) do t.decimal "area" t.string "area_unit" t.integer "garden_type_id" - t.jsonb "layout" t.index ["garden_type_id"], name: "index_gardens_on_garden_type_id" t.index ["owner_id"], name: "index_gardens_on_owner_id" t.index ["slug"], name: "index_gardens_on_slug", unique: true @@ -394,7 +393,7 @@ ActiveRecord::Schema.define(version: 2019_07_11_043654) do t.integer "product_id" end - create_table "photographings", id: :serial, force: :cascade do |t| + create_table "photo_associations", id: :serial, force: :cascade do |t| t.integer "photo_id", null: false t.integer "photographable_id", null: false t.string "photographable_type", null: false @@ -417,6 +416,7 @@ ActiveRecord::Schema.define(version: 2019_07_11_043654) do t.string "link_url", null: false t.string "flickr_photo_id" t.datetime "date_taken" + t.integer "likes_count", default: 0 end create_table "photos_plantings", id: false, force: :cascade do |t| @@ -467,6 +467,7 @@ ActiveRecord::Schema.define(version: 2019_07_11_043654) do t.datetime "updated_at" t.string "slug" t.integer "forum_id" + t.integer "likes_count", default: 0 t.index ["created_at", "author_id"], name: "index_posts_on_created_at_and_author_id" t.index ["slug"], name: "index_posts_on_slug", unique: true end @@ -488,16 +489,6 @@ ActiveRecord::Schema.define(version: 2019_07_11_043654) do t.integer "creator_id" end - create_table "seed_trades", force: :cascade do |t| - t.bigint "seed_id" - t.bigint "member_id" - t.boolean "accepted" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["member_id"], name: "index_seed_trades_on_member_id" - t.index ["seed_id"], name: "index_seed_trades_on_seed_id" - end - create_table "seeds", id: :serial, force: :cascade do |t| t.integer "owner_id", null: false t.integer "crop_id", null: false @@ -519,32 +510,12 @@ ActiveRecord::Schema.define(version: 2019_07_11_043654) do t.index ["slug"], name: "index_seeds_on_slug", unique: true end - create_table "trades", force: :cascade do |t| - t.bigint "seed_id" - t.bigint "responded_seed_id" - t.bigint "requested_by_id" - t.boolean "accepted" - t.text "message" - t.text "address_for_delivery" - t.text "rejection_reason" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["requested_by_id"], name: "index_trades_on_requested_by_id" - t.index ["responded_seed_id"], name: "index_trades_on_responded_seed_id" - t.index ["seed_id"], name: "index_trades_on_seed_id" - end - add_foreign_key "harvests", "plantings" add_foreign_key "mailboxer_conversation_opt_outs", "mailboxer_conversations", column: "conversation_id", name: "mb_opt_outs_on_conversations_id" add_foreign_key "mailboxer_notifications", "mailboxer_conversations", column: "conversation_id", name: "notifications_on_conversation_id" add_foreign_key "mailboxer_receipts", "mailboxer_notifications", column: "notification_id", name: "receipts_on_notification_id" - add_foreign_key "photographings", "crops" - add_foreign_key "photographings", "photos" + add_foreign_key "photo_associations", "crops" + add_foreign_key "photo_associations", "photos" add_foreign_key "plantings", "seeds", column: "parent_seed_id", name: "parent_seed", on_delete: :nullify - add_foreign_key "seed_trades", "members" - add_foreign_key "seed_trades", "seeds" add_foreign_key "seeds", "plantings", column: "parent_planting_id", name: "parent_planting", on_delete: :nullify - add_foreign_key "trades", "members", column: "requested_by_id" - add_foreign_key "trades", "seeds" - add_foreign_key "trades", "seeds", column: "responded_seed_id" end diff --git a/lib/haml/filters/growstuff_markdown.rb b/lib/haml/filters/growstuff_markdown.rb index 4e7617c15..0a7c34000 100644 --- a/lib/haml/filters/growstuff_markdown.rb +++ b/lib/haml/filters/growstuff_markdown.rb @@ -54,7 +54,7 @@ module Haml::Filters # rubocop:disable Style/ClassAndModuleChildren def crop_link(crop, link_text) if crop - url = Rails.application.routes.url_helpers.crop_url(crop, host: HOST) + url = Rails.application.routes.url_helpers.crop_url(crop, only_path: true) "[#{link_text}](#{url})" else link_text diff --git a/spec/features/likeable_spec.rb b/spec/features/likeable_spec.rb index 0a39154b0..42aa03c6b 100644 --- a/spec/features/likeable_spec.rb +++ b/spec/features/likeable_spec.rb @@ -1,40 +1,97 @@ require 'rails_helper' describe 'Likeable', js: true do - let(:member) { FactoryBot.create(:member) } - let(:another_member) { FactoryBot.create(:london_member) } - let(:post) { FactoryBot.create(:post) } + let(:member) { FactoryBot.create(:member) } + let(:another_member) { FactoryBot.create(:london_member) } + let!(:post) { FactoryBot.create(:post, author: member) } + let!(:photo) { FactoryBot.create(:photo, owner: member) } context 'logged in member' do - before do - login_as member - visit post_path(post) + before { login_as member } + + describe 'photos' do + let(:like_count_class) { "#photo-#{photo.id} .like-count" } + + shared_examples 'photo can be liked' do + it 'can be liked' do + visit path + expect(page).to have_css(like_count_class, text: "0") + click_link '0', class: 'like-btn' + expect(page).to have_css(like_count_class, text: "1") + + # Reload page + visit path + expect(page).to have_css(like_count_class, text: "1") + expect(page).to have_link '1' + + click_link '1', class: 'like-btn' + expect(page).to have_css(like_count_class, text: "0") + end + + it 'displays correct number of likes' do + visit path + expect(page).to have_css(like_count_class, text: "0") + expect(page).to have_link '0' + click_link '0', class: 'like-btn' + expect(page).to have_css(like_count_class, text: "1") + + logout(member) + login_as(another_member) + visit path + + expect(page).to have_css(like_count_class, text: "1") + click_link '1', class: 'like-btn' + expect(page).to have_css(like_count_class, text: "2") + end + end + describe 'photos#index' do + let(:path) { photos_path } + include_examples 'photo can be liked' + end + describe 'photos#show' do + let(:path) { photo_path(photo) } + include_examples 'photo can be liked' + end + describe 'crops#show' do + let(:crop) { FactoryBot.create :crop } + let(:planting) { FactoryBot.create :planting, owner: member, crop: crop } + let(:path) { crop_path(crop) } + before { planting.photos << photo } + include_examples 'photo can be liked' + end end - it 'can be liked' do - expect(page).to have_link 'Like' - click_link 'Like' - expect(page).to have_content '1 like' + describe 'posts' do + let(:like_count_class) { "#post-#{post.id} .like-count" } + before { visit post_path(post) } + it 'can be liked' do + expect(page).to have_css(like_count_class, text: "0") + expect(page).to have_link 'Like' + click_link 'Like', class: 'like-btn' + expect(page).to have_css(like_count_class, text: "1") - visit post_path(post) + # Reload page + visit post_path(post) + expect(page).to have_css(like_count_class, text: "1") + expect(page).to have_link 'Unlike' - expect(page).to have_link 'Unlike' - click_link 'Unlike' - expect(page).to have_content '0 likes' - end + click_link 'Unlike', class: 'like-btn' + expect(page).to have_css(like_count_class, text: "0") + end - it 'displays correct number of likes' do - expect(page).to have_link 'Like' - click_link 'Like' - expect(page).to have_content '1 like' - logout(member) + it 'displays correct number of likes' do + expect(page).to have_link 'Like' + click_link 'Like', class: 'like-btn' + expect(page).to have_css(like_count_class, text: "1") - login_as(another_member) - visit post_path(post) + logout(member) + login_as(another_member) + visit post_path(post) - expect(page).to have_link 'Like' - click_link 'Like' - expect(page).to have_content '2 likes' + expect(page).to have_link 'Like' + click_link 'Like', class: 'like-btn' + expect(page).to have_css(like_count_class, text: "2") + end end end end diff --git a/spec/lib/haml/filters/growstuff_markdown_spec.rb b/spec/lib/haml/filters/growstuff_markdown_spec.rb index 597137f6d..cc27bd560 100644 --- a/spec/lib/haml/filters/growstuff_markdown_spec.rb +++ b/spec/lib/haml/filters/growstuff_markdown_spec.rb @@ -7,7 +7,7 @@ def input_link(name) end def output_link(crop, name = nil) - url = Rails.application.routes.url_helpers.crop_url(crop, host: Rails.application.config.host) + url = Rails.application.routes.url_helpers.crop_url(crop, only_path: true) return "#{name}" if name "#{crop.name}" @@ -62,7 +62,7 @@ describe 'Haml::Filters::Growstuff_Markdown' do end it "finds crops case insensitively" do - @crop = FactoryBot.create(:crop, name: 'tomato') + @crop = FactoryBot.create(:crop, name: 'tomato', slug: 'tomato') rendered = Haml::Filters::GrowstuffMarkdown.render(input_link('ToMaTo')) expect(rendered).to match(/#{output_link(@crop, 'ToMaTo')}/) end diff --git a/spec/models/crop_spec.rb b/spec/models/crop_spec.rb index fa642a540..ed17069c9 100644 --- a/spec/models/crop_spec.rb +++ b/spec/models/crop_spec.rb @@ -185,9 +185,9 @@ describe Crop do end it { expect(crop.photos.size).to eq 0 } - it { expect(crop.planting_photos.size).to eq 0 } - it { expect(crop.harvest_photos.size).to eq 0 } - it { expect(crop.seed_photos.size).to eq 0 } + it { expect(crop.photos.by_model(Planting).size).to eq 0 } + it { expect(crop.photos.by_model(Harvest).size).to eq 0 } + it { expect(crop.photos.by_model(Seed).size).to eq 0 } end describe 'finding all photos' do @@ -203,9 +203,9 @@ describe Crop do end it { expect(crop.photos.size).to eq 3 } - it { expect(crop.planting_photos.size).to eq 1 } - it { expect(crop.harvest_photos.size).to eq 1 } - it { expect(crop.seed_photos.size).to eq 1 } + it { expect(crop.photos.by_model(Planting).size).to eq 1 } + it { expect(crop.photos.by_model(Harvest).size).to eq 1 } + it { expect(crop.photos.by_model(Seed).size).to eq 1 } end end diff --git a/spec/models/harvest_spec.rb b/spec/models/harvest_spec.rb index 35cb9ba5f..168b2bcdc 100644 --- a/spec/models/harvest_spec.rb +++ b/spec/models/harvest_spec.rb @@ -237,6 +237,7 @@ describe Harvest do @planting = FactoryBot.create(:planting, crop: @harvest.crop) @photo = FactoryBot.create(:photo, owner: @planting.owner) @planting.photos << @photo + @harvest.update(planting: @planting) end it 'has a default photo' do diff --git a/spec/models/like_spec.rb b/spec/models/like_spec.rb index 363585aea..56f35bf1d 100644 --- a/spec/models/like_spec.rb +++ b/spec/models/like_spec.rb @@ -48,6 +48,12 @@ describe 'like' do expect(Like.all).not_to include like end + it 'destroys like if post no longer exists' do + like = Like.create(member: member, likeable: post) + post.destroy + expect(Like.all).not_to include like + end + it 'destroys like if member no longer exists' do like = Like.create(member: member, likeable: post) member.destroy diff --git a/spec/models/photo_spec.rb b/spec/models/photo_spec.rb index d71a6cd1c..1102253f8 100644 --- a/spec/models/photo_spec.rb +++ b/spec/models/photo_spec.rb @@ -2,18 +2,40 @@ require 'rails_helper' describe Photo do let(:photo) { FactoryBot.create(:photo, owner: member) } - let(:member) { FactoryBot.create(:member) } + let(:old_photo) { FactoryBot.create(:photo, owner: member, created_at: 1.year.ago, date_taken: 2.years.ago) } + let(:member) { FactoryBot.create(:member) } + + it_behaves_like "it is likeable" describe 'add/delete functionality' do let(:planting) { FactoryBot.create(:planting, owner: member) } + let(:seed) { FactoryBot.create(:seed, owner: member) } let(:harvest) { FactoryBot.create(:harvest, owner: member) } - let(:garden) { FactoryBot.create(:garden, owner: member) } + let(:post) { FactoryBot.create(:post, author: member) } + let(:garden) { FactoryBot.create(:garden, owner: member) } context "adds photos" do - it 'to a planting' do - planting.photos << photo - expect(planting.photos.size).to eq 1 - expect(planting.photos.first).to eq photo + describe 'to a planting' do + before { planting.photos << photo } + + it { expect(planting.photos.size).to eq 1 } + it { expect(planting.photos.first).to eq photo } + # there's only one photo, so that's the default + it { expect(planting.default_photo).to eq photo } + it { expect(planting.crop.default_photo).to eq photo } + + describe 'with a second older photo' do + # Add an old photo + before { planting.photos << old_photo } + it { expect(planting.default_photo).to eq photo } + it { expect(planting.crop.default_photo).to eq photo } + + describe 'and someone likes the old photo' do + before { FactoryBot.create :like, likeable: old_photo } + it { expect(planting.default_photo).to eq old_photo } + it { expect(planting.crop.default_photo).to eq old_photo } + end + end end it 'to a harvest' do @@ -22,11 +44,23 @@ describe Photo do expect(harvest.photos.first).to eq photo end + it 'to a seed' do + seed.photos << photo + expect(seed.photos.size).to eq 1 + expect(seed.photos.first).to eq photo + end + it 'to a garden' do garden.photos << photo expect(garden.photos.size).to eq 1 expect(garden.photos.first).to eq photo end + + it 'to a post' do + post.photos << photo + expect(post.photos.size).to eq 1 + expect(post.photos.first).to eq photo + end end context "removing photos" do