Merge remote-tracking branch 'upstream/dev' into mailbox

Conflicts:
	Gemfile.lock
	app/assets/stylesheets/application.scss
	db/schema.rb
This commit is contained in:
Brenda Wallace
2019-07-20 13:20:10 +12:00
48 changed files with 414 additions and 275 deletions

View File

@@ -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)

View File

@@ -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);
}
});
});

View File

@@ -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');
}
});
});

View File

@@ -0,0 +1,7 @@
.liked {
color: $red;
}
.like-btn {
color: $brown;
}

View File

@@ -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';

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -70,7 +70,7 @@ module IconsHelper
end
def like_icon
icon('fas', 'thumbs-up')
icon('fas', 'heart')
end
def sunniness_icon(sunniness)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -89,8 +89,4 @@ class Garden < ApplicationRecord
p.save
end
end
def default_photo
photos.order(created_at: :desc).first
end
end

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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/

View File

@@ -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}

View File

@@ -0,0 +1,4 @@
%span.badge.like-badge{class: (likeable.liked_by?(current_member)) ? 'liked' : ''}
= like_icon
&nbsp;
%span.like-count= likeable.likes_count

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
<script src="//static.getclicky.com/js" type="text/javascript"></script>
<script type="text/javascript">try{ clicky.init(100594260); }catch(e){}</script>

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,5 @@
class RenamePhotographings < ActiveRecord::Migration[5.2]
def change
rename_table :photographings, :photo_associations
end
end

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,4 @@
class NotificationsToMailboxer < ActiveRecord::Migration[5.2]
def change
end
end

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 "<a href=\"#{url}\">#{name}</a>" if name
"<a href=\"#{url}\">#{crop.name}</a>"
@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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