Merge pull request #2139 from Growstuff/dev

Production release
This commit is contained in:
Brenda Wallace
2019-08-21 09:17:57 +12:00
committed by GitHub
136 changed files with 2024 additions and 2070 deletions

36
Gemfile
View File

@@ -106,7 +106,7 @@ gem 'rake', '>= 10.0.0'
gem "responders"
# allows soft delete. Used for members.
gem "paranoia", "~> 2.2"
gem 'discard', '~> 1.0'
gem 'xmlrpc' # fixes rake error - can be removed if not needed later
@@ -115,6 +115,9 @@ gem 'puma'
gem 'loofah', '>= 2.2.1'
gem 'rack-protection', '>= 2.0.1'
# Member to member messaging system
gem 'mailboxer'
group :production do
gem 'bonsai-elasticsearch-rails' # Integration with Bonsa-Elasticsearch on heroku
gem 'dalli'
@@ -132,25 +135,28 @@ group :development do
end
group :development, :test do
gem 'bullet' # performance tuning by finding unnecesary queries
gem 'byebug' # debugging
gem 'capybara' # integration tests
gem 'capybara-email' # integration tests for email
gem 'capybara-screenshot' # for test debugging
gem 'coveralls', require: false # coverage analysis
gem 'bullet' # performance tuning by finding unnecesary queries
gem 'byebug' # debugging
gem 'capybara' # integration tests
gem 'capybara-email' # integration tests for email
gem 'capybara-screenshot' # for test debugging
gem 'database_cleaner'
gem 'factory_bot_rails' # for creating test data
gem 'factory_bot_rails' # for creating test data
gem 'faker'
gem 'haml-i18n-extractor'
gem 'haml-rails' # HTML templating language
gem 'haml_lint', '>= 0.25.1' # Checks haml files for goodness
gem 'i18n-tasks' # adds tests for finding missing and unused translations
gem 'haml-rails' # HTML templating language
gem 'rspec-activemodel-mocks'
gem 'rspec-rails' # unit testing framework
gem 'rubocop', '~> 0.71'
gem 'rspec-rails' # unit testing framework
gem 'rubocop-rails'
gem 'rubocop-rspec'
gem 'webrat' # provides HTML matchers for view tests
gem 'webrat' # provides HTML matchers for view tests
# cli utils
gem 'coveralls', require: false # coverage analysis
gem 'haml-i18n-extractor', require: false
gem 'haml_lint', '>= 0.25.1', require: false # Checks haml files for goodness
gem 'i18n-tasks', require: false # adds tests for finding missing and unused translations
gem 'rspectre', require: false # finds unused code in specs
gem 'rubocop', '~> 0.71', require: false
end
group :test do

View File

@@ -2,6 +2,7 @@ GEM
remote: https://rubygems.org/
remote: https://rails-assets.org/
specs:
abstract_type (0.0.7)
actioncable (5.2.2.1)
actionpack (= 5.2.2.1)
nio4r (~> 2.0)
@@ -53,8 +54,15 @@ GEM
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
adamantium (0.2.0)
ice_nine (~> 0.11.0)
memoizable (~> 0.4.0)
addressable (2.6.0)
public_suffix (>= 2.0.2, < 4.0)
anima (0.3.1)
abstract_type (~> 0.0.7)
adamantium (~> 0.2)
equalizer (~> 0.0.11)
arel (9.0.0)
ast (2.4.0)
autoprefixer-rails (9.6.0)
@@ -100,6 +108,10 @@ GEM
capybara-screenshot (1.0.23)
capybara (>= 1.0, < 4)
launchy
carrierwave (1.3.1)
activemodel (>= 4.0.0)
activesupport (>= 4.0.0)
mime-types (>= 1.16)
chartkick (3.2.1)
childprocess (1.0.1)
rake (< 13.0)
@@ -126,6 +138,9 @@ GEM
sassc-rails (>= 2.0.0)
comfy_bootstrap_form (4.0.6)
rails (>= 5.0.0)
concord (0.1.5)
adamantium (~> 0.2.0)
equalizer (~> 0.0.9)
concurrent-ruby (1.1.5)
connection_pool (2.2.2)
coveralls (0.8.19)
@@ -139,13 +154,15 @@ GEM
activesupport (>= 3.0.0)
dalli (2.7.10)
database_cleaner (1.7.0)
devise (4.6.2)
devise (4.7.0)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0, < 6.0)
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
diff-lcs (1.3)
discard (1.1.0)
activerecord (>= 4.2, < 7)
docile (1.1.5)
elasticsearch (6.8.0)
elasticsearch-api (= 6.8.0)
@@ -160,6 +177,7 @@ GEM
elasticsearch-transport (6.8.0)
faraday
multi_json
equalizer (0.0.11)
erubi (1.8.0)
erubis (2.7.0)
excon (0.64.0)
@@ -205,7 +223,7 @@ GEM
haml (>= 4.0.6, < 6.0)
html2haml (>= 1.0.1)
railties (>= 5.1)
haml_lint (0.32.0)
haml_lint (0.33.0)
haml (>= 4.0, < 5.2)
rainbow
rake (>= 10, < 13)
@@ -238,6 +256,7 @@ GEM
rails-i18n
rainbow (>= 2.2.2, < 4.0)
terminal-table (>= 1.5.1)
ice_nine (0.11.2)
jaro_winkler (1.5.3)
jquery-rails (4.3.5)
rails-dom-testing (>= 1, < 3)
@@ -283,6 +302,9 @@ GEM
nokogiri (>= 1.5.9)
mail (2.7.1)
mini_mime (>= 0.1.1)
mailboxer (0.15.1)
carrierwave (>= 0.5.8)
rails (>= 5.0.0)
marcel (0.3.3)
mimemagic (~> 0.3.2)
material-sass (4.1.1)
@@ -291,6 +313,8 @@ GEM
material_icons (2.2.1)
railties (>= 3.2)
memcachier (0.0.2)
memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1)
method_source (0.9.2)
mime-types (3.2.2)
mime-types-data (~> 3.2015)
@@ -315,7 +339,7 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
oj (3.8.1)
oj (3.9.0)
omniauth (1.9.0)
hashie (>= 3.4.6, < 3.7.0)
rack (>= 1.6.2, < 3)
@@ -335,8 +359,6 @@ GEM
rack
orm_adapter (0.5.0)
parallel (1.17.0)
paranoia (2.4.2)
activerecord (>= 4.0, < 6.1)
parser (2.6.3.0)
ast (~> 2.4.0)
percy-capybara (4.0.2)
@@ -345,6 +367,7 @@ GEM
heroics (~> 0.0.25)
moneta (~> 1.0.0)
popper_js (1.14.5)
procto (0.0.3)
public_suffix (3.1.1)
puma (4.1.0)
nio4r (~> 2.0)
@@ -376,7 +399,7 @@ GEM
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.1.0)
rails-html-sanitizer (1.2.0)
loofah (~> 2.2, >= 2.2.2)
rails-i18n (5.1.3)
i18n (>= 0.7, < 2)
@@ -403,6 +426,10 @@ GEM
responders (3.0.0)
actionpack (>= 5.0)
railties (>= 5.0)
rspec (3.8.0)
rspec-core (~> 3.8.0)
rspec-expectations (~> 3.8.0)
rspec-mocks (~> 3.8.0)
rspec-activemodel-mocks (1.1.0)
activemodel (>= 3.0)
activesupport (>= 3.0)
@@ -424,6 +451,12 @@ GEM
rspec-mocks (~> 3.8.0)
rspec-support (~> 3.8.0)
rspec-support (3.8.2)
rspectre (0.0.1)
anima (~> 0.3)
concord (~> 0.1)
parser (~> 2.3)
rspec (~> 3.0)
unparser (~> 0.2)
rubocop (0.74.0)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
@@ -447,8 +480,8 @@ GEM
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
sass-rails (5.0.7)
railties (>= 4.0.0, < 6)
sass-rails (5.1.0)
railties (>= 5.2.0)
sass (~> 3.1)
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
@@ -509,6 +542,14 @@ GEM
kgio (~> 2.6)
raindrops (~> 0.7)
uniform_notifier (1.12.1)
unparser (0.4.5)
abstract_type (~> 0.0.7)
adamantium (~> 0.2.0)
concord (~> 0.1.5)
diff-lcs (~> 1.3)
equalizer (~> 0.0.9)
parser (~> 2.6.3)
procto (~> 0.0.2)
warden (1.2.8)
rack (>= 2.0.6)
webdrivers (4.1.2)
@@ -522,7 +563,7 @@ GEM
websocket-driver (0.7.1)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.4)
will_paginate (3.1.7)
will_paginate (3.1.8)
will_paginate-bootstrap4 (0.2.2)
will_paginate (~> 3.0, >= 3.0.0)
xmlrpc (0.3.0)
@@ -559,6 +600,7 @@ DEPENDENCIES
dalli
database_cleaner
devise
discard (~> 1.0)
elasticsearch (< 7.0.0)
factory_bot_rails
faker
@@ -584,6 +626,7 @@ DEPENDENCIES
letter_opener
listen
loofah (>= 2.2.1)
mailboxer
material-sass (= 4.1.1)
material_icons
memcachier
@@ -593,7 +636,6 @@ DEPENDENCIES
omniauth-facebook
omniauth-flickr (>= 0.0.15)
omniauth-twitter
paranoia (~> 2.2)
percy-capybara (~> 4.0.0)
pg (< 1.0.0)
platform-api
@@ -607,6 +649,7 @@ DEPENDENCIES
responders
rspec-activemodel-mocks
rspec-rails
rspectre
rubocop (~> 0.71)
rubocop-rails
rubocop-rspec

View File

@@ -15,3 +15,26 @@
padding-left: 1em;
width: 15em;
}
.member-chip {
background-color: lighten($green, 30%);
border-radius: 25px;
display: inline-block;
font-size: 1em;
height: 30px;
line-height: 30px;
padding: 0 25px;
a {
color: $black;
}
img {
border-radius: 50%;
float: left;
height: 30px;
margin: 0 10px 0 -25px;
width: 30px;
}
}

View File

@@ -3,17 +3,23 @@ module Admin
before_action :auth!
def index
@members = Member.order(:login_name).paginate(page: params[:page])
@members = Member.all
@members = @members.where("login_name ILIKE ?", "%#{search_term}%") unless search_term.nil?
@members = @members.order(:login_name).paginate(page: params[:page])
end
def destroy
@member = Member.find_by!(slug: params[:slug])
@member.destroy
@member.discard
redirect_to admin_members_path
end
private
def search_term
params[:q]
end
def auth!
authorize! :manage, :all
end

View File

@@ -0,0 +1,62 @@
class ConversationsController < ApplicationController
respond_to :html
before_action :authenticate_member!
before_action :set_box
before_action :check_current_subject_in_conversation, only: %i(show update destroy)
def index
@conversations = if @box.eql? "inbox"
mailbox.inbox
elsif @box.eql? "sent"
mailbox.sentbox
else
mailbox.trash
end.paginate(page: params[:page])
respond_with @conversations
end
def show
@receipts = mailbox.receipts_for(@conversation)
@receipts.mark_as_read
@participants = @conversation.participants
end
def update
@conversation.untrash(current_member)
redirect_to conversations_path(box: params[:box])
end
def destroy
@conversation = Mailboxer::Conversation.find(params[:id])
@conversation.move_to_trash(current_member)
redirect_to conversations_path(box: params[:box])
end
private
def mailbox
current_member.mailbox
end
def set_box
@boxes = {
'inbox' => { 'total' => mailbox.inbox.size, 'unread' => current_member.receipts.where(is_read: false).count },
'sent' => { 'total' => mailbox.sentbox.size, 'unread' => 0 },
'trash' => { 'total' => mailbox.trash.size, 'unread' => 0 }
}
@box = if params[:box].blank? || !@boxes.keys.include?(params[:box])
'inbox'
else
params[:box]
end
end
def check_current_subject_in_conversation
@conversation = Mailboxer::Conversation.find_by(id: params[:id])
return unless @conversation.nil? || !@conversation.is_participant?(current_member)
redirect_to conversations_path(box: box)
nil
end
end

View File

@@ -139,7 +139,7 @@ class CropsController < ApplicationController
def notify_wranglers
return if current_member.role? :crop_wrangler
Role.crop_wranglers.each do |w|
Role.crop_wranglers&.each do |w|
Notifier.new_crop_request(w, @crop).deliver_now!
end
end

View File

@@ -14,7 +14,6 @@ class GardensController < ApplicationController
@gardens = @gardens.active unless @show_all
@gardens = @gardens.where(owner: @owner) if @owner.present?
@gardens = @gardens.joins(:owner).order(:name).paginate(page: params[:page])
@gardens = @gardens.includes(:owner, plantings: [:owner, crop: :parent])
respond_with(@gardens)
end

View File

@@ -13,7 +13,7 @@ class MembersController < ApplicationController
end
def show
@member = Member.confirmed.find_by!(slug: params[:slug])
@member = Member.confirmed.kept.find_by!(slug: params[:slug])
@twitter_auth = @member.auth('twitter')
@flickr_auth = @member.auth('flickr')
@facebook_auth = @member.auth('facebook')
@@ -89,6 +89,6 @@ class MembersController < ApplicationController
Member.recently_joined
else
Member.order(:login_name)
end.confirmed.paginate(page: params[:page])
end.kept.confirmed.paginate(page: params[:page])
end
end

View File

@@ -0,0 +1,38 @@
class MessagesController < ApplicationController
respond_to :html, :json
before_action :authenticate_member!
def index
redirect_to conversations_path(box: @box)
end
def show
if (@message = Message.find_by(id: params[:id])) && (@conversation = @message.conversation)
if @conversation.is_participant?(current_member)
redirect_to conversation_path(@conversation, box: @box, anchor: "message_" + @message.id.to_s)
return
end
end
redirect_to conversations_path(box: @box)
end
def new
return if params[:recipient_id].blank?
@recipient = Member.find_by(id: params[:recipient_id])
return if @recipient.nil?
end
def create
if params[:conversation_id].present?
@conversation = Mailboxer::Conversation.find(params[:conversation_id])
current_member.reply_to_conversation(@conversation, params[:body])
redirect_to conversation_path(@conversation)
else
recipient = Member.find(params[:recipient_id])
body = params[:body]
subject = params[:subject]
@conversation = current_member.send_message(recipient, body, subject)
redirect_to conversations_path(box: 'sentbox')
end
end
end

View File

@@ -1,65 +0,0 @@
class NotificationsController < ApplicationController
include NotificationsHelper
before_action :authenticate_member!
load_and_authorize_resource
respond_to :html
# GET /notifications
def index
@notifications = Notification.by_recipient(current_member).order(created_at: :desc).paginate(page: params[:page], per_page: 30)
end
# GET /notifications/1
def show
@notification.read = true
@notification.save
@reply_link = reply_link(@notification)
end
# GET /notifications/new
def new
@notification = Notification.new
@recipient = Member.find_by(id: params[:recipient_id])
@subject = params[:subject] || ""
end
# GET /notifications/1/reply
def reply
@notification = Notification.new
@sender_notification = Notification.find_by!(id: params[:notification_id], recipient: current_member)
@sender_notification.read = true
@sender_notification.save
@recipient = @sender_notification.sender
@subject = if @sender_notification.subject.start_with? 'Re: '
@sender_notification.subject
else
"Re: #{@sender_notification.subject}"
end
end
# DELETE /notifications/1
def destroy
@notification.destroy
redirect_to notifications_url
end
# POST /notifications
def create
params[:notification][:sender_id] = current_member.id
@notification = Notification.new(notification_params)
@recipient = Member.find_by(id: params[:notification][:recipient_id])
if @notification.save
redirect_to notifications_path, notice: 'Message was successfully sent.'
else
render action: "new"
end
end
private
def notification_params
params.require(:notification).permit(:sender_id, :recipient_id, :subject, :body, :post_id, :read)
end
end

View File

@@ -31,12 +31,7 @@ class PlantingsController < ApplicationController
end
def show
@planting = Planting.includes(:owner, :crop, :garden, :photos)
.friendly
.find(params[:id])
@photos = @planting.photos
.includes(:owner)
.order(date_taken: :desc)
@photos = @planting.photos.includes(:owner).order(date_taken: :desc)
respond_with @planting
end

View File

@@ -37,9 +37,11 @@ class RegistrationsController < Devise::RegistrationsController
end
def destroy
if @member.destroy_with_password(params.require(:member)[:current_password])
if @member.valid_password?(params.require(:member)[:current_password])
@member.discard
redirect_to root_path
else
@member.errors.add(:current_password, 'Incorrect password')
render "edit"
end
end

View File

@@ -1,11 +0,0 @@
module NotificationsHelper
def reply_link(notification)
if notification.post
# comment on the post in question
new_comment_url(post_id: notification.post.id)
else
# by default, reply link sends a PM in return
notification_reply_url(notification)
end
end
end

View File

@@ -1,5 +1,5 @@
class Notifier < ApplicationMailer
include NotificationsHelper
# include NotificationsHelper
default from: "Growstuff <#{ENV['GROWSTUFF_EMAIL']}>"
def verifier

View File

@@ -1,5 +1,5 @@
class Comment < ApplicationRecord
belongs_to :author, -> { with_deleted }, class_name: 'Member', inverse_of: :comments
belongs_to :author, class_name: 'Member', inverse_of: :comments
belongs_to :post
scope :post_order, -> { reorder("created_at ASC") } # for display on post page

View File

@@ -0,0 +1,51 @@
module MemberFlickr
extend ActiveSupport::Concern
included do # rubocop:disable Metrics/BlockLength
# Authenticates against Flickr and returns an object we can use for subsequent api calls
def flickr
if @flickr.nil?
flickr_auth = auth('flickr')
if flickr_auth
FlickRaw.api_key = ENV['GROWSTUFF_FLICKR_KEY']
FlickRaw.shared_secret = ENV['GROWSTUFF_FLICKR_SECRET']
@flickr = FlickRaw::Flickr.new
@flickr.access_token = flickr_auth.token
@flickr.access_secret = flickr_auth.secret
end
end
@flickr
end
# Fetches a collection of photos from Flickr
# Returns a [[page of photos], total] pair.
# Total is needed for pagination.
def flickr_photos(page_num = 1, set = nil)
result = if set
flickr.photosets.getPhotos(
photoset_id: set,
page: page_num,
per_page: 30
)
else
flickr.people.getPhotos(
user_id: 'me',
page: page_num,
per_page: 30
)
end
return [result.photo, result.total] if result
[[], 0]
end
# Returns a hash of Flickr photosets' ids and titles
def flickr_sets
sets = {}
flickr.photosets.getList.each do |p|
sets[p.title] = p.id
end
sets
end
end
end

View File

@@ -0,0 +1,46 @@
module MemberNewsletter
extend ActiveSupport::Concern
included do # rubocop:disable Metrics/BlockLength
after_save :update_newsletter_subscription
before_destroy :newsletter_unsubscribe
scope :wants_newsletter, -> { where(newsletter: true) }
def update_newsletter_subscription
return unless will_save_change_to_attribute?(:confirmed) || will_save_change_to_attribute?(:newsletter)
if newsletter
newsletter_subscribe if confirmed_just_now? || requested_newsletter_just_now?
elsif confirmed_at
newsletter_unsubscribe
end
end
def confirmed_just_now?
will_save_change_to_attribute?(:confirmed_at)
end
def requested_newsletter_just_now?
confirmed_at && will_save_change_to_attribute?(:newsletter)
end
def newsletter_subscribe(gibbon = Gibbon::API.new, testing = false)
return true if Rails.env.test? && !testing
gibbon.lists.subscribe(
id: Rails.application.config.newsletter_list_id,
email: { email: email },
merge_vars: { login_name: login_name },
double_optin: false # they already confirmed their email with us
)
end
def newsletter_unsubscribe(gibbon = Gibbon::API.new, testing = false)
return true if Rails.env.test? && !testing
gibbon.lists.unsubscribe(id: Rails.application.config.newsletter_list_id,
email: { email: email })
end
end
end

View File

@@ -4,5 +4,7 @@ module Ownable
included do
belongs_to :owner, class_name: 'Member', # rubocop:disable Rails/InverseOf
foreign_key: 'owner_id', counter_cache: true
default_scope { joins(:owner).merge(Member.kept) } # Ensures the owner still exists
end
end

View File

@@ -17,7 +17,7 @@ class Crop < ApplicationRecord
has_many :harvests, dependent: :destroy
has_many :photo_associations, dependent: :destroy
has_many :photos, through: :photo_associations
has_many :plant_parts, -> { distinct.order("plant_parts.name") }, through: :harvests
has_many :plant_parts, -> { joins_members.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
belongs_to :parent, class_name: 'Crop', optional: true, inverse_of: :varieties
@@ -34,6 +34,7 @@ class Crop < ApplicationRecord
scope :rejected, -> { where(approval_status: "rejected") }
scope :interesting, -> { approved.has_photos }
scope :has_photos, -> { includes(:photos).where.not(photos: { id: nil }) }
scope :joins_members, -> { joins("INNER JOIN members ON members.id = harvests.owner_id") }
# Special scope to control if it's in the search index
scope :search_import, -> { approved }

View File

@@ -16,7 +16,6 @@ class Garden < ApplicationRecord
after_validation :empty_unwanted_geocodes
after_save :mark_inactive_garden_plantings_as_finished
default_scope { joins(:owner) } # Ensures owner exists
scope :active, -> { where(active: true) }
scope :inactive, -> { where(active: false) }
scope :order_by_name, -> { order(Arel.sql("lower(name) asc")) }
@@ -59,22 +58,6 @@ class Garden < ApplicationRecord
"#{owner.login_name}-#{name}".downcase.tr(' ', '-')
end
# featured plantings returns the most recent 4 plantings for a garden,
# choosing them so that no crop is repeated.
def featured_plantings
unique_plantings = []
seen_crops = []
plantings.includes(:garden, :crop, :owner, :harvests).order(created_at: :desc).each do |p|
unless seen_crops.include?(p.crop)
unique_plantings.push(p)
seen_crops.push(p.crop)
end
end
unique_plantings[0..3]
end
def to_s
name
end

View File

@@ -39,7 +39,6 @@ class Harvest < ApplicationRecord
##
## Scopes
default_scope { joins(:owner) } # Ensures owner exists
scope :interesting, -> { has_photos.one_per_owner }
scope :recent, -> { order(created_at: :desc) }
scope :one_per_owner, lambda {

View File

@@ -1,9 +1,11 @@
class Member < ApplicationRecord
acts_as_paranoid # implements soft deletion
before_destroy :newsletter_unsubscribe
include Discard::Model
acts_as_messageable # messages can be sent to this model
include Geocodable
extend FriendlyId
include MemberFlickr
include MemberNewsletter
extend FriendlyId
friendly_id :login_name, use: %i(slugged finders)
#
@@ -46,7 +48,6 @@ class Member < ApplicationRecord
scope :located, -> { where.not(location: '').where.not(latitude: nil).where.not(longitude: nil) }
scope :recently_signed_in, -> { reorder(updated_at: :desc) }
scope :recently_joined, -> { reorder(confirmed_at: :desc) }
scope :wants_newsletter, -> { where(newsletter: true) }
scope :interesting, -> { confirmed.located.recently_signed_in.has_plantings }
scope :has_plantings, -> { joins(:plantings).group("members.id") }
@@ -57,6 +58,11 @@ class Member < ApplicationRecord
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :lockable, :timeoutable, :omniauthable
# discarded (deleted) member cannot log in
def active_for_authentication?
super && !discarded?
end
# set up geocoding
geocoded_by :location
@@ -86,7 +92,6 @@ class Member < ApplicationRecord
# Triggers
after_validation :geocode
after_validation :empty_unwanted_geocodes
after_save :update_newsletter_subscription
# Give each new member a default garden
# we use find_or_create to avoid accidentally creating a second one,
@@ -103,13 +108,17 @@ class Member < ApplicationRecord
end
def to_s
login_name
discarded? ? 'deleted' : login_name
end
def to_param
slug
end
def mailboxer_email(_messageable)
send_notification_email ? email : false
end
def role?(role_sym)
roles.any? { |r| r.name.gsub(/\s+/, "_").underscore.to_sym == role_sym }
end
@@ -118,50 +127,8 @@ class Member < ApplicationRecord
authentications.find_by(provider: provider)
end
# Authenticates against Flickr and returns an object we can use for subsequent api calls
def flickr
if @flickr.nil?
flickr_auth = auth('flickr')
if flickr_auth
FlickRaw.api_key = ENV['GROWSTUFF_FLICKR_KEY']
FlickRaw.shared_secret = ENV['GROWSTUFF_FLICKR_SECRET']
@flickr = FlickRaw::Flickr.new
@flickr.access_token = flickr_auth.token
@flickr.access_secret = flickr_auth.secret
end
end
@flickr
end
# Fetches a collection of photos from Flickr
# Returns a [[page of photos], total] pair.
# Total is needed for pagination.
def flickr_photos(page_num = 1, set = nil)
result = if set
flickr.photosets.getPhotos(
photoset_id: set,
page: page_num,
per_page: 30
)
else
flickr.people.getPhotos(
user_id: 'me',
page: page_num,
per_page: 30
)
end
return [result.photo, result.total] if result
[[], 0]
end
# Returns a hash of Flickr photosets' ids and titles
def flickr_sets
sets = {}
flickr.photosets.getList.each do |p|
sets[p.title] = p.id
end
sets
def unread_count
receipts.where(is_read: false).count
end
def self.login_name_or_email(login)
@@ -181,42 +148,6 @@ class Member < ApplicationRecord
nearby_members
end
def update_newsletter_subscription
return unless will_save_change_to_attribute?(:confirmed) || will_save_change_to_attribute?(:newsletter)
if newsletter
newsletter_subscribe if confirmed_just_now? || requested_newsletter_just_now?
elsif confirmed_at
newsletter_unsubscribe
end
end
def confirmed_just_now?
will_save_change_to_attribute?(:confirmed_at)
end
def requested_newsletter_just_now?
confirmed_at && will_save_change_to_attribute?(:newsletter)
end
def newsletter_subscribe(gibbon = Gibbon::API.new, testing = false)
return true if Rails.env.test? && !testing
gibbon.lists.subscribe(
id: Rails.application.config.newsletter_list_id,
email: { email: email },
merge_vars: { login_name: login_name },
double_optin: false # they already confirmed their email with us
)
end
def newsletter_unsubscribe(gibbon = Gibbon::API.new, testing = false)
return true if Rails.env.test? && !testing
gibbon.lists.unsubscribe(id: Rails.application.config.newsletter_list_id,
email: { email: email })
end
def already_following?(member)
follows.exists?(followed_id: member.id)
end

View File

@@ -9,17 +9,15 @@ class Notification < ApplicationRecord
scope :by_recipient, ->(recipient) { where(recipient_id: recipient) }
before_create :replace_blank_subject
after_create :send_email
after_create :send_message
def self.unread_count
unread.size
def send_message
sender.send_message(recipient, body, subject) if recipient.send_notification_email
end
private
def replace_blank_subject
self.subject = "(no subject)" if subject.nil? || subject =~ /^\s*$/
end
def send_email
Notifier.notify(self).deliver_now! if recipient.send_notification_email
end
end

View File

@@ -15,7 +15,6 @@ class Photo < ApplicationRecord
source_type: type
end
default_scope { joins(:owner) } # Ensures the owner still exists
scope :by_crop, ->(crop) { joins(:photo_associations).where(photo_associations: { crop: crop }) }
scope :by_model, lambda { |model_name|
joins(:photo_associations).where(photo_associations: { photographable_type: model_name.to_s })

View File

@@ -3,10 +3,12 @@ class PlantPart < ApplicationRecord
friendly_id :name, use: %i(slugged finders)
has_many :harvests, dependent: :destroy
has_many :crops, -> { distinct }, through: :harvests
has_many :crops, -> { joins_members.distinct }, through: :harvests
validates :name, presence: true, uniqueness: true
scope :joins_members, -> { joins("INNER JOIN members ON members.id = harvests.owner_id") }
def to_s
name
end

View File

@@ -32,7 +32,6 @@ class Planting < ApplicationRecord
##
## Scopes
default_scope { joins(:owner) } # Ensures the owner still exists
scope :active, -> { where('finished <> true').where('finished_at IS NULL OR finished_at < ?', Time.zone.now) }
scope :interesting, -> { has_photos.one_per_owner.order(planted_at: :desc) }
scope :recent, -> { order(created_at: :desc) }
@@ -47,6 +46,7 @@ class Planting < ApplicationRecord
delegate :name, :en_wikipedia_url, :default_scientific_name, :plantings_count,
to: :crop, prefix: true
delegate :annual?, to: :crop
##
## Validations
validates :garden, presence: true

View File

@@ -19,7 +19,7 @@ class Post < ApplicationRecord
after_save :update_crops_posts_association
after_create :send_notification
default_scope { joins(:author) } # Ensures the owner still exists
default_scope { joins(:author).merge(Member.kept) } # Ensures the owner still exists
#
# Validations

View File

@@ -48,7 +48,7 @@ class Seed < ApplicationRecord
#
# Scopes
default_scope { joins(:owner) } # Ensure owner exists
default_scope { joins(:owner).merge(Member.kept) } # Ensure owner exists
scope :tradable, -> { where.not(tradable_to: 'nowhere') }
scope :interesting, -> { tradable.has_location }
scope :has_location, -> { joins(:owner).where.not("members.location": nil) }

View File

@@ -3,25 +3,41 @@
- content_for :breadcrumbs do
%li.breadcrumb-item.active= link_to 'Admin', admin_path
%h2 Manage
%h1 Manage #{ENV['GROWSTUFF_SITE_NAME']}
.row
.col-md-4
%h2 Site admin
%ul#site_admin
%li= link_to "Roles", roles_path
%li= link_to "Forums", forums_path
%li= link_to "CMS", comfy_admin_cms_path
%li= link_to t('.garden_types'), garden_types_path
.card
.card-header
%h2
= icon 'fas', 'tablet-alt'
Site admin
.card-body
%nav.nav#site_admin
%li= link_to "Roles", roles_path, class: 'nav-link'
%li= link_to "Forums", forums_path, class: 'nav-link'
%li= link_to "CMS", comfy_admin_cms_path, class: 'nav-link'
%li= link_to t('.garden_types'), garden_types_path, class: 'nav-link'
.col-md-4
%h2 Crop data admin
%ul
%li= link_to "Alternate names", alternate_names_path
%li= link_to "Scientific names", scientific_names_path
.card
.card-header
%h2
= icon 'fas', 'seedling'
Crop data admin
.card-body
%nav.nav#crop_admin
%li= link_to "Crop Wrangling", wrangle_crops_path, class: 'nav-link'
%li= link_to "Alternate names", alternate_names_path, class: 'nav-link'
%li= link_to "Scientific names", scientific_names_path, class: 'nav-link'
.col-md-4
%h2 Member admin
%ul
%li= link_to "Newsletter subscribers", admin_newsletter_path
%li= link_to "Members", admin_members_path
.card
.card-header
%h2
= icon 'fas', 'user'
Member admin
.card-body
%nav.nav#member_admin
%li= link_to "Newsletter subscribers", admin_newsletter_path, class: 'nav-link'
%li= link_to "Members", admin_members_path, class: 'nav-link'

View File

@@ -1,6 +1,12 @@
.pagination
= page_entries_info @members
= will_paginate @members
.row
.col-md-6
= bootstrap_form_tag url: admin_members_path, method: :get, layout: :horizontal do |f|
.input-group.md-form.form-sm
.input-group-prepend
%span.input-group-text= icon 'fas', 'user'
= f.text_field(:q, 'aria-label': 'search members', placeholder: 'search members', hide_label: true)
= f.submit 'search', class: 'btn btn-secondary'
%table.table.table-striped
%thead
@@ -11,10 +17,11 @@
- @members.each do |member|
%tr
%td= render 'members/tiny', member: member
%td
= link_to member, member
%td= member.login_name
%td= member.email
%td
= link_to admin_member_path(member), method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-light text-danger' do
= icon 'fas', 'ban'
Ban member
= page_entries_info @members
= will_paginate @members

View File

@@ -1,10 +1,11 @@
.card.comment
.card-body
.row
.col-md-1= render "members/tiny", member: comment.author
.col-md-11.border-left
.col
= render "members/tiny", member: comment.author
- if can?(:edit, comment) || can?(:destroy, comment)
.dropdown.float-right
%hr/
.dropdown
%button#comment-edit-button.btn.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", type: "button"} Actions
.dropdown-menu.dropdown-menu-xs{"aria-labelledby" => "comment-edit-button"}
- if can? :edit, comment
@@ -16,9 +17,10 @@
data: { confirm: 'Are you sure?' }, class: 'dropdown-item text-danger' do
= delete_icon
Delete
.col-md-9.border-left
.comment-meta.text-muted
Posted by
- if comment.author.deleted?
- if comment.author.discarded?
Member Deleted
- else
= link_to comment.author.login_name, member_path(comment.author)

View File

@@ -0,0 +1,57 @@
- content_for :title, "Inbox"
- content_for :breadcrumbs do
%li.breadcrumb-item= link_to 'Conversations', conversations_path
%li.breadcrumb-item.active= link_to @box, conversations_path(box: @box)
.row
.col-md-2
.col-md-10
%h1= @box
- unless @conversations.empty?
= will_paginate @conversations
.row
.col-md-2.list-group
- @boxes.each do |box_name, counts|
= link_to conversations_path(box: box_name), class: "nav-link list-group-item d-flex justify-content-between #{box_name == @box ? 'active' : ''} #{box_name}" do
- if counts['unread'].positive?
%span.badge.badge-info=counts['unread']
- else
%span
%span
= icon 'fas', box_name
= box_name
.col-md-10
.list-group
- @conversations.each do |conversation|
.list-group-item
.row
.col-md-1
- if conversation.receipts_for(current_member).last.is_unread?
%h1= icon 'far', 'envelope'
- else
%h1.text-muted= icon 'far', 'envelope-open'
.col-md-10
.text-right.float-right
- conversation.recipients.each do |member|
- if member != current_member
= render 'members/tiny', member: member
= link_to conversation_path(conversation) do
.conversation
%h5.mb-2.h5
- if conversation.receipts_for(current_member).last.is_unread?
%strong= conversation.subject
- else
= conversation.subject
%small
#{time_ago_in_words conversation.messages.last.created_at} ago
%span.text-muted= conversation.messages.last.created_at
= truncate(strip_tags(conversation.messages.last.body), length: 150, separator: ' ', omission: '... ')
.col-md-1
- if @box == 'trash'
= link_to conversation_path(conversation, box: @box), method: :put, class: 'restore' do
= icon 'fas', 'trash-restore'
- else
= link_to delete_icon, conversation_path(conversation, box: @box), method: :delete, class: 'delete text-danger'
- unless @conversations.empty?
= will_paginate @conversations

View File

@@ -0,0 +1,33 @@
- content_for :breadcrumbs do
%li.breadcrumb-item= link_to 'Conversations', conversations_path
%li.breadcrumb-item.active= link_to @conversation.subject, conversation_path(@conversation)
.row
.col-md-2
.col-md-10
%h1= @conversation.subject
.row
.col-md-2
.card
.card-header
%h6 Participants
%ul.list-group.list-group-flush
- @participants.each do |member|
%li.list-group-item= render 'members/tiny', member: member
.col-md-10
.card
%ul.list-group.list-group-flush
- @conversation.messages.order(:created_at).each do |message|
%li.list-group-item
.col-md-8.text-muted
= comment_icon
on #{message.created_at}
.col-md-4.text-right.float-right
= render 'members/tiny', member: message.sender
.col-12
%p.text-justify
:growstuff_markdown
#{ strip_tags(message.body) }
%li.list-group-item
= icon 'fas', 'reply'
= render 'messages/form', conversation: @conversation

View File

@@ -1,68 +1,60 @@
- content_for :title, "Crop Wrangling"
%h1 Crop Wrangling
%ul
%li= link_to "Full crop hierarchy", hierarchy_crops_path
%li= link_to "Add Crop", new_crop_path
%nav.nav
= link_to "Full crop hierarchy", hierarchy_crops_path, class: 'nav-link'
= link_to "Add Crop", new_crop_path, class: 'btn'
.crop_wranglers
%h2 Crop Wranglers:
%ul
- @crop_wranglers.each do |crop_wrangler|
%li.crop_wrangler
= link_to crop_wrangler.login_name, crop_wrangler
%section.crop_wranglers
%h2 Crop Wranglers
- @crop_wranglers.each do |crop_wrangler|
= render 'members/tiny', member: crop_wrangler
.tabbable
%ul.nav.nav-tabs
%li{ class: @approval_status.blank? ? 'active' : '' }
= link_to "Recently added", wrangle_crops_path
%li{ class: @approval_status == "pending" ? 'active' : '' }
= link_to "Pending approval", wrangle_crops_path(approval_status: "pending")
%li{ class: @approval_status == "rejected" ? 'active' : '' }
= link_to "Rejected", wrangle_crops_path(approval_status: "rejected")
%hr/
%h2
- if @approval_status == "pending"
Requested Crops
- elsif @approval_status == "rejected"
Rejected Crops
- else
Recently added crops
%section
%h2 Crops
%ul#myTab.nav.nav-tabs{role: "tablist"}
%li.nav-item
%a#home-tab.nav-link{ href: wrangle_crops_path, role: "tab", class: @approval_status.blank? ? 'active' : ''}
Recently added
%li.nav-item
%a#profile-tab.nav-link{ href: wrangle_crops_path(approval_status: "pending"), role: "tab", class: @approval_status == "pending" ? 'active' : ''}
Pending approval
%li.nav-item
%a#contact-tab.nav-link{ href: wrangle_crops_path(approval_status: "rejected"), role: "tab", class: @approval_status == "rejected" ? 'active' : ''} Rejected
.pagination
= page_entries_info @crops
= will_paginate @crops
%table{ class: "table table-striped",
id: @approval_status.blank? ? 'recently-added-crops' : "#{@approval_status}-crops" }
%tr
%th System name
%th English Wikipedia URL
%th Scientific names
%th Requested by
- if @approval_status == "rejected"
%th Rejected by
- if @approval_status != "rejected" && @approval_status != "pending"
%th Added by
%th When
- @crops.each do |c|
%table.table.table-striped.table-bordered.table-sm{id: @approval_status.blank? ? 'recently-added-crops' : "#{@approval_status}-crops" }
%tr
%td= link_to c.name, edit_crop_path(c)
%td= link_to c.en_wikipedia_url, c.en_wikipedia_url
%td
- c.scientific_names.each do |s|
= link_to s.name, s
%br/
%td= c.requester.present? ? (link_to c.requester, c.requester) : "N/A"
- unless @approval_status == "pending"
%td= c.creator.present? ? (link_to c.creator, c.creator) : "N/A"
%td
= distance_of_time_in_words(c.created_at, Time.zone.now)
ago.
%th System name
%th English Wikipedia URL
%th Scientific names
%th Requested by
- if @approval_status == "rejected"
%th Rejected by
- if @approval_status != "rejected" && @approval_status != "pending"
%th Added by
%th When
- @crops.each do |c|
%tr
%td
= link_to edit_crop_path(c) do
= icon 'fas', 'seedling'
= c.name
%td= link_to c.en_wikipedia_url, c.en_wikipedia_url
%td
- c.scientific_names.each do |s|
= link_to s.name, s
%br/
%td= c.requester.present? ? (link_to c.requester, c.requester) : "N/A"
- unless @approval_status == "pending"
%td= c.creator.present? ? (link_to c.creator, c.creator) : "N/A"
%td
= distance_of_time_in_words(c.created_at, Time.zone.now)
ago.
.pagination
= page_entries_info @crops
= will_paginate @crops

View File

@@ -13,18 +13,20 @@
- else
%li.breadcrumb-item.active= link_to 'Gardens', gardens_path
= link_to gardens_active_tickbox_path(@owner, @show_all) do
= check_box_tag 'active', 'all', @show_all
include in-active
.pagination
= page_entries_info @gardens
= will_paginate @gardens
%section.border-top
= link_to gardens_active_tickbox_path(@owner, @show_all), class: 'btn' do
= check_box_tag 'active', 'all', @show_all
include in-active
- if @gardens.empty?
%p There are no gardens to display.
- if can?(:create, Garden) && @owner == current_member
= link_to 'Add a garden', new_garden_path, class: 'btn btn-primary'
- else
.row
.col-12= page_entries_info @gardens
.col-12= will_paginate @gardens
- @gardens.each do |garden|
%section.border-top
.row
@@ -43,6 +45,6 @@
.col-6.col-md-4.col-lg-3= render 'plantings/card', planting: planting
.pagination
= page_entries_info @gardens
= will_paginate @gardens
.row
.col-12= page_entries_info @gardens
.col-12= will_paginate @gardens

View File

@@ -33,10 +33,11 @@
%span= display_quantity(@harvest)
- if @harvest.description.present?
%h2 Notes
:growstuff_markdown
#{ @harvest.description != "" ? strip_tags(@harvest.description) : "No description given." }
.col-md-4
.card
.card-body= render 'harvests/owner', harvest: @harvest
= render @harvest.crop
%hr/
- if @harvest.photos.size.positive?

View File

@@ -28,11 +28,11 @@
- if member_signed_in?
%li.nav-item.dropdown
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", href: "#", role: "button"}
- if current_member.notifications.unread_count.positive?
- if current_member.unread_count.positive?
= t('.your_stuff')
%span.badge.badge-info= current_member.notifications.unread_count
%span.badge.badge-info= current_member.unread_count
- else
= t('.current_memberlogin_name', current_memberlogin_name: current_member.login_name)
= t('.current_memberlogin_name', current_memberlogin_name: current_member)
.dropdown-menu{"aria-labelledby" => "navbarDropdown"}
= link_to member_path(current_member), class: 'dropdown-item' do
= t('.profile')
@@ -46,14 +46,14 @@
= t('.seeds')
= link_to t('.posts'), member_posts_path(current_member), class: 'dropdown-item'
- if current_member.notifications.unread_count.positive?
- if current_member.unread_count.positive?
.dropdown-divider
%strong
= link_to(notifications_path, class: 'dropdown-item') do
= link_to(conversations_path, class: 'dropdown-item') do
= t('.inbox')
%span.badge.badge-info= current_member.notifications.unread_count
%span.badge.badge-info= current_member.unread_count
- else
= link_to t('.inbox'), notifications_path, class: 'dropdown-item'
= link_to t('.inbox'), conversations_path, class: 'dropdown-item'
- if current_member.role?(:crop_wrangler) || current_member.role?(:admin)
%li.nav-item.dropdown

View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
</head>
<body>
<h1>You have a new message: <%= @subject %></h1>
<p>
You have received a new message:
</p>
<blockquote>
<p>
<%= raw @message.body %>
</p>
</blockquote>
<p>
Visit <%= link_to root_url, root_url %> and go to your inbox for more info.
</p>
</body>
</html>

View File

@@ -0,0 +1,10 @@
You have a new message: <%= @subject %>
===============================================
You have received a new message:
-----------------------------------------------
<%= @message.body.html_safe? ? @message.body : strip_tags(@message.body) %>
-----------------------------------------------
Visit <%= root_url %> and go to your inbox for more info.

View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
</head>
<body>
<h1>You have a new reply: <%= @subject %></h1>
<p>
You have received a new reply:
</p>
<blockquote>
<p>
<%= raw @message.body %>
</p>
</blockquote>
<p>
Visit <%= link_to root_url, root_url %> and go to your inbox for more info.
</p>
</body>
</html>

View File

@@ -0,0 +1,10 @@
You have a new reply: <%= @subject %>
===============================================
You have received a new reply:
-----------------------------------------------
<%= @message.body.html_safe? ? @message.body : strip_tags(@message.body) %>
-----------------------------------------------
Visit <%= root_url %> and go to your inbox for more info.

View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
</head>
<body>
<h1>You have a new notification: <%= @notification.subject.html_safe? ? @notification.subject : strip_tags(@notification.subject) %></h1>
<p>
You have received a new notification:
</p>
<blockquote>
<p>
<%= raw @notification.body %>
</p>
</blockquote>
<p>
Visit <%= link_to root_url,root_url %> and go to your notifications for more info.
</p>
</body>
</html>

View File

@@ -0,0 +1,10 @@
You have a new notification: <%= @notification.subject.html_safe? ? @notification.subject : strip_tags(@notification.subject) %>
===============================================
You have received a new notification:
-----------------------------------------------
<%= @notification.body.html_safe? ? @notification.body : strip_tags(@notification.body) %>
-----------------------------------------------
Visit <%= root_url %> and go to your notifications for more info.

View File

@@ -1,15 +0,0 @@
-# %p.btn-group
-# - if can? :update, @member
-# = link_to edit_member_registration_path, class: 'btn btn-default text-right' do
-# = edit_icon
-# = t('members.edit_profile')
-# - if can?(:create, Notification) && current_member != @member
-# = link_to 'Send message', new_notification_path(recipient_id: @member.id), class: 'btn btn-default'
-# - if current_member && current_member != @member # must be logged in, can't follow yourself
-# - follow = current_member.get_follow(@member)
-# - if !follow && can?(:create, Follow) # not already following
-# = link_to 'Follow', follows_path(followed: @member), method: :post, class: 'btn btn-default text-right'
-# - if follow && can?(:destroy, follow) # already following
-# = link_to 'Unfollow', follow_path(follow), method: :delete, class: 'btn btn-default text-right'

View File

@@ -19,10 +19,10 @@
%ul.nav.nav-justified.small
%li.nav-item.border-right
= link_to member_plantings_path(member) do
= localize_plural(member.plantings.active, Planting)
= localize_plural(member.plantings, Planting)
%li.nav-item.border-right
= link_to member_harvests_path(member) do
= localize_plural(member.harvests, Harvest)
%li.nav-item
= link_to member_seeds_path(member) do
= localize_plural(member.seeds.active, Seed)
= localize_plural(member.seeds, Seed)

View File

@@ -1 +1,10 @@
= image_tag(avatar_uri(member, 50), alt: '', class: 'img img-fluid avatar', height: 50)
.member-chip
- if member.discarded?
= icon 'fas', 'user-times'
= member
- else
= link_to member do
= image_tag(avatar_uri(member, 100), alt: '', height: 50, width: 50)
= member

View File

@@ -7,9 +7,6 @@
= tag("meta", property: "og:url", content: request.original_url)
= tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME'])
= render 'members/actions', member: @member
- content_for :member_rss_login_name, @member.login_name
- content_for :member_rss_slug, @member.slug
@@ -39,7 +36,7 @@
= t('members.edit_profile')
- if can?(:create, Notification) && current_member != @member
= link_to new_notification_path(recipient_id: @member.id), class: 'btn btn-block' do
= link_to new_message_path(recipient_id: @member.id), class: 'btn btn-block' do
= icon('fas', 'envelope')
Send message

View File

@@ -0,0 +1,17 @@
= bootstrap_form_tag url: '/messages' do |f|
- if @conversation.present?
= f.hidden_field :conversation_id, value: @conversation.id
- elsif @recipient.present?
%p
To
= link_to @recipient, @recipient
= render 'members/tiny', member: @recipient
= f.hidden_field :recipient_id, value: @recipient.id
= f.text_field :subject, value: @subject, class: 'form-control', maxlength: 255, required: true
= f.text_area :body, rows: 12, label: "Type your message here", required: true
%span.help-block= render partial: "shared/markdown_help"
.card-footer
= link_to 'cancel', conversations_path, class: 'btn'
.float-right= f.submit "Send", class: 'btn btn-primary'

View File

@@ -1,6 +1,6 @@
= content_for :title, "Send a message to #{@recipient}"
- content_for :breadcrumbs do
%li.breadcrumb-item= link_to 'Messages', notifications_path
%li.breadcrumb-item= link_to 'Messages', conversations_path
= render 'form'

View File

@@ -1,26 +0,0 @@
= bootstrap_form_for @notification do |f|
.card
.card-body
- if @notification.errors.any?
#error_explanation
%h2
= pluralize(@post.errors.size, "error")
prohibited this message from being sent:
%ul
- @notification.errors.full_messages.each do |msg|
%li= msg
.field
= f.hidden_field :recipient_id, value: @recipient.id
%p
To
= link_to @recipient, @recipient
= render 'members/tiny', member: @recipient
= f.text_field :subject, value: @subject, class: 'form-control', maxlength: 255
= f.text_area :body, rows: 12, label: "Type your message here"
%span.help-block= render partial: "shared/markdown_help"
.card-footer
= link_to 'cancel', notifications_path, class: 'btn'
.float-right= f.submit "Send", class: 'btn btn-primary'

View File

@@ -1,13 +0,0 @@
%p.text-muted
From
= link_to notification.sender, notification.sender
on
= notification.created_at
- if notification.post_id
in response to:
= link_to notification.post.subject, notification.post
.well
:growstuff_markdown
#{ strip_tags(notification.body) }

View File

@@ -1,38 +0,0 @@
- content_for :title, "Inbox"
- content_for :breadcrumbs do
%li.breadcrumb-item.active= link_to 'Messages', notifications_path
- if @notifications.empty?
.alert.alert-success{role: "alert"} You have no messages.
- else
= paginate @notifications
- @notifications.each do |n|
.card.message
.card-body
.row
.col-6.col-md-9
%h3= link_to n.subject, notification_path(n)
%p
- if n.read
= icon 'far', 'envelope-open'
- else
= icon 'far', 'envelope'
%strong unread
= n.created_at
.col-6.col-md-3.text-right
- if n.sender.present?
= link_to n.sender do
%h3
= render 'members/tiny', member: n.sender
= n.sender
= render 'members/follow_buttons', member: n.sender
- else
Member deleted
.card-footer
= link_to 'Read', n, class: 'btn btn-primary'
= link_to 'Reply', reply_link(n), class: 'btn btn-secondary'
= link_to n, method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-default btn-danger btn-xs' do
= delete_icon
= t('buttons.delete')
= paginate @notifications

View File

@@ -1,8 +0,0 @@
= content_for :title, "Send a message to #{@recipient}"
- content_for :breadcrumbs do
%li.breadcrumb-item= link_to 'Messages', notifications_path
.card
.card-body= render @sender_notification
= render 'form'

View File

@@ -1,24 +0,0 @@
= content_for :title, @notification.subject
- content_for :breadcrumbs do
%li.breadcrumb-item= link_to 'Messages', notifications_path
.card
.card-header
.card-title= @notification.subject
.float-right
- if @notification.sender.present?
= link_to @notification.sender do
= @notification.sender
= render 'members/tiny', member: @notification.sender
- else
Member deleted
.card-body
= render @notification
.card-footer
= link_to 'Reply', @reply_link, class: 'btn btn-primary'
= link_to @notification, method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-default btn-danger' do
= delete_icon
= t('buttons.delete')

View File

@@ -21,5 +21,4 @@
.card-body.text-center
%h4.card-title= link_to planting.crop, planting
.text-center= render 'plantings/badges', planting: planting
.card-footer
= render 'plantings/progress', planting: planting

View File

@@ -1,10 +1,8 @@
- if planting.planted_at.present?
planted #{planting.planted_at}
- if planting.percentage_grown.present?
- if planting.annual? && planting.percentage_grown.present?
.progress
.progress-bar.bg-success{"aria-valuemax" => "100", "aria-valuemin" => "0", "aria-valuenow" => planting.percentage_grown, :role => "progressbar", :style => "width: #{planting.percentage_grown}%; height: 25px"}
.float-left #{sprintf '%.0f', planting.percentage_grown}%
- unless planting.finish_predicted_at.blank?
.float-right= planting.finish_predicted_at
.float-right= planting.finish_predicted_at.strftime(Date::DATE_FORMATS[:ymd])

View File

@@ -13,25 +13,28 @@
- else
%li.breadcrumb-item.active= link_to 'Plantings', plantings_path
= link_to plantings_active_tickbox_path(@owner, @show_all) do
= check_box_tag 'active', 'all', @show_all
include in-active
%section.border-top
.btn-group
= link_to plantings_active_tickbox_path(@owner, @show_all), class: 'btn' do
= check_box_tag 'active', 'all', @show_all
include in-active
- if @owner
= link_to t('.view_owners_profile', owner: @owner), member_path(@owner)
- if @owner
= link_to t('.view_owners_profile', owner: @owner), member_path(@owner), class: 'btn'
.pagination
= page_entries_info @plantings
= render 'layouts/pagination', collection: @plantings
.row
.col-12= page_entries_info @plantings
.col-12= render 'layouts/pagination', collection: @plantings
.index-cards= render @plantings, full: true
.pagination
= page_entries_info @plantings
= render 'layouts/pagination', collection: @plantings
.row
.col-12= page_entries_info @plantings
.col-12= render 'layouts/pagination', collection: @plantings
%ul.list-inline
%li= t('.the_data_on_this_page_is_available_in_the_following_formats')
- ['csv', 'json', 'rss'].each do |format|
%li= link_to format.upcase,
(@owner ? member_plantings_path(@owner, format: format) : plantings_path(format: format))
%p= t('.the_data_on_this_page_is_available_in_the_following_formats')
%ul.list-group.list-group-horizontal
- ['csv', 'json', 'rss'].each do |format|
%li.list-group-item
= icon 'fas', format
= link_to format.upcase, (@owner ? member_plantings_path(@owner, format: format) : plantings_path(format: format))

View File

@@ -32,25 +32,24 @@
.row
.col-md-8.col-12
%section.blog-post
.card.post{ id: "post-#{@post.id}" }
.card-header
%h2.display-3.float-left
= @post.subject
= render 'posts/actions', post: @post
.card-body
.post-content= render 'posts/single', post: @post
.index-cards
- @post.photos.each do |photo|
= render 'photos/thumbnail', photo: photo
.card.post{ id: "post-#{@post.id}" }
.card-header
%h2.display-3.float-left
= @post.subject
= render 'posts/actions', post: @post
.card-body
.post-content= render 'posts/single', post: @post
.index-cards
- @post.photos.each do |photo|
= render 'photos/thumbnail', photo: photo
.card-footer
= render 'posts/likes', post: @post
.float-right
- if can? :create, Comment
= link_to new_comment_path(post_id: @post.id), class: 'btn' do
= icon 'fas', 'comment'
Comment
.card-footer
= render 'posts/likes', post: @post
.float-right
- if can? :create, Comment
= link_to new_comment_path(post_id: @post.id), class: 'btn' do
= icon 'fas', 'comment'
Comment
%secion.comments= render "comments", post: @post

View File

@@ -2,11 +2,12 @@
= link_to seed do
= image_tag(seed_image_path(seed, full_size: true), alt: seed, class: 'img-card')
.card-body
%h5= link_to seed, seed
.card-title= link_to seed.crop, seed
.card-footer
= render 'members/tiny', member: seed.owner
- if seed.tradable?
%span.badge.badge-pill.badge-secondary Will trade #{seed.tradable_to}
= link_to seed.owner do
.card-footer
%span.badge.badge-pill.badge-secondary
= truncate(seed.owner.location, length: 20, separator: ' ', omission: '... ')
= render 'members/tiny', member: seed.owner
%span.badge.badge-pill.badge-info
= icon 'fas', 'map'
Will trade #{seed.tradable_to}
%span.badge.badge-pill.badge-secondary
= truncate(seed.owner.location, length: 30, separator: ' ', omission: '... ')

View File

@@ -68,7 +68,7 @@
- if current_member
- if @seed.tradable? && current_member != @seed.owner
%p= link_to "Request seeds",
new_notification_path(recipient_id: @seed.owner.id,
new_message_path(recipient_id: @seed.owner.id,
subject: "Interested in your #{@seed.crop} seeds"),
class: 'btn btn-primary'
- else

View File

@@ -0,0 +1,21 @@
Mailboxer.setup do |config|
# Configures if your application uses or not email sending for Notifications and Messages
config.uses_emails = true
# Configures the default from for emails sent for Messages and Notifications
config.default_from = "no-reply@growstuff.org"
# Configures the methods needed by mailboxer
# config.email_method = :email
config.name_method = :login_name
config.notify_method = :notify
# Configures if you use or not a search engine and which one you are using
# Supported engines: [:solr,:sphinx,:pg_search]
# config.search_enabled = false
# config.search_engine = :solr
# Configures maximum length of the message subject and body
config.subject_max_length = 255
config.body_max_length = 32_000
end

View File

@@ -97,9 +97,8 @@ Rails.application.routes.draw do
get 'followers' => 'follows#followers'
end
resources :notifications do
get 'reply'
end
resources :messages
resources :conversations
resources :places, only: %i(index show), param: :place do
get 'search', on: :collection

View File

@@ -1,7 +1,7 @@
class SetPredictionData < ActiveRecord::Migration[4.2]
def up
say "Updating all plantings time to first harvest"
Planting.all.each(&:update_harvest_days!)
Planting.unscoped.all.each(&:update_harvest_days!)
say "Updating crop median time to first harvest, and lifespan"
Crop.all.each do |crop|
crop.update_lifespan_medians

View File

@@ -1,7 +1,7 @@
class FinishedBoolean < ActiveRecord::Migration[5.2]
def change
Planting.where('finished_at < now()').update_all(finished: true)
Planting.where(finished: nil).update_all(finished: false)
Planting.unscoped.where('finished_at < now()').update_all(finished: true)
Planting.unscoped.where(finished: nil).update_all(finished: false)
change_column_null :plantings, :finished, false
change_column_default :plantings, :finished, from: nil, to: false
end

View File

@@ -2,7 +2,7 @@ class AddHarvestsCount < ActiveRecord::Migration[5.2]
def change
add_column :plantings, :harvests_count, :integer, default: 0
Planting.reset_column_information
Planting.pluck(:id).each do |planting_id|
Planting.unscoped.pluck(:id).each do |planting_id|
Planting.reset_counters(planting_id, :harvests)
end
end

View File

@@ -0,0 +1,64 @@
# 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.timestamps 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.timestamps 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.timestamps 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,17 @@
# This migration comes from mailboxer_engine (originally 20131206080416)
class AddConversationOptout < ActiveRecord::Migration[4.2]
def self.up
create_table :mailboxer_conversation_opt_outs do |t|
t.references :unsubscriber, polymorphic: true
t.references :conversation
t.timestamps null: false
end
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"
drop_table :mailboxer_conversation_opt_outs
end
end

View File

@@ -0,0 +1,20 @@
# This migration comes from mailboxer_engine (originally 20131206080417)
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),
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)
# 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),
name: 'index_mailboxer_notifications_on_notified_object_id_and_type'
add_index :mailboxer_receipts, %i(receiver_id receiver_type)
end
end

View File

@@ -0,0 +1,8 @@
# This migration comes from mailboxer_engine (originally 20151103080417)
class AddDeliveryTrackingInfoToMailboxerReceipts < ActiveRecord::Migration[4.2]
def change
add_column :mailboxer_receipts, :is_delivered, :boolean, default: false
add_column :mailboxer_receipts, :delivery_method, :string
add_column :mailboxer_receipts, :message_id, :string
end
end

View File

@@ -0,0 +1,20 @@
class NotificationsToMailboxer < ActiveRecord::Migration[5.2]
def up
Mailboxer.setup do |config|
# turn off emails
config.uses_emails = false
end
Notification.find_in_batches.each do |group|
group.each do |n|
n.body = 'message has no body' if n.body.blank?
receipt = n.sender.send_message(n.recipient, n.body, n.subject)
# Copy over which messages are read
receipt.conversation.receipts.each(&:mark_as_read) if n.read
# copy over timestamps
receipt.conversation.messages.each do |msg|
msg.update!(created_at: n.created_at, updated_at: n.updated_at)
end
end
end
end
end

View File

@@ -0,0 +1,5 @@
class ParanoiaToDiscard < ActiveRecord::Migration[5.2]
def change
rename_column :members, :deleted_at, :discarded_at
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_12_234859) do
ActiveRecord::Schema.define(version: 2019_07_21_042146) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -268,6 +268,62 @@ ActiveRecord::Schema.define(version: 2019_07_12_234859) do
t.index ["member_id"], name: "index_likes_on_member_id"
end
create_table "mailboxer_conversation_opt_outs", id: :serial, force: :cascade do |t|
t.string "unsubscriber_type"
t.integer "unsubscriber_id"
t.integer "conversation_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["conversation_id"], name: "index_mailboxer_conversation_opt_outs_on_conversation_id"
t.index ["unsubscriber_id", "unsubscriber_type"], name: "index_mailboxer_conversation_opt_outs_on_unsubscriber_id_type"
end
create_table "mailboxer_conversations", id: :serial, force: :cascade do |t|
t.string "subject", default: ""
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "mailboxer_notifications", id: :serial, force: :cascade do |t|
t.string "type"
t.text "body"
t.string "subject", default: ""
t.string "sender_type"
t.integer "sender_id"
t.integer "conversation_id"
t.boolean "draft", default: false
t.string "notification_code"
t.string "notified_object_type"
t.integer "notified_object_id"
t.string "attachment"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "global", default: false
t.datetime "expires"
t.index ["conversation_id"], name: "index_mailboxer_notifications_on_conversation_id"
t.index ["notified_object_id", "notified_object_type"], name: "index_mailboxer_notifications_on_notified_object_id_and_type"
t.index ["notified_object_type", "notified_object_id"], name: "mailboxer_notifications_notified_object"
t.index ["sender_id", "sender_type"], name: "index_mailboxer_notifications_on_sender_id_and_sender_type"
t.index ["type"], name: "index_mailboxer_notifications_on_type"
end
create_table "mailboxer_receipts", id: :serial, force: :cascade do |t|
t.string "receiver_type"
t.integer "receiver_id"
t.integer "notification_id", null: false
t.boolean "is_read", default: false
t.boolean "trashed", default: false
t.boolean "deleted", default: false
t.string "mailbox_type", limit: 25
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "is_delivered", default: false
t.string "delivery_method"
t.string "message_id"
t.index ["notification_id"], name: "index_mailboxer_receipts_on_notification_id"
t.index ["receiver_id", "receiver_type"], name: "index_mailboxer_receipts_on_receiver_id_and_receiver_type"
end
create_table "median_functions", id: :serial, force: :cascade do |t|
end
@@ -307,11 +363,11 @@ ActiveRecord::Schema.define(version: 2019_07_12_234859) do
t.integer "gardens_count"
t.integer "harvests_count"
t.integer "seeds_count"
t.datetime "deleted_at"
t.datetime "discarded_at"
t.integer "photos_count"
t.integer "forums_count"
t.index ["confirmation_token"], name: "index_members_on_confirmation_token", unique: true
t.index ["deleted_at"], name: "index_members_on_deleted_at"
t.index ["discarded_at"], name: "index_members_on_discarded_at"
t.index ["email"], name: "index_members_on_email", unique: true
t.index ["reset_password_token"], name: "index_members_on_reset_password_token", unique: true
t.index ["slug"], name: "index_members_on_slug", unique: true
@@ -457,6 +513,9 @@ ActiveRecord::Schema.define(version: 2019_07_12_234859) do
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 "photo_associations", "crops"
add_foreign_key "photo_associations", "photos"
add_foreign_key "plantings", "seeds", column: "parent_seed_id", name: "parent_seed", on_delete: :nullify

View File

@@ -5,12 +5,6 @@ require 'rails_helper'
RSpec.describe Api::V1::PlantingsController, type: :controller do
subject { JSON.parse response.body }
let(:headers) do
{
'Accept' => 'application/vnd.api+json',
'Content-Type' => 'application/vnd.api+json'
}
end
let!(:member) { FactoryBot.create :member }
describe '#index' do

View File

@@ -2,7 +2,6 @@ require 'rails_helper'
describe Charts::GardensController do
include Devise::Test::ControllerHelpers
let(:valid_params) { { name: 'My second Garden' } }
let(:garden) { FactoryBot.create :garden }

View File

@@ -5,9 +5,6 @@ RSpec.describe GardenTypesController, type: :controller do
let(:valid_params) { { name: 'My second GardenType' } }
let(:garden_type) { FactoryBot.create :garden_type }
let(:member) { FactoryBot.create(:member) }
let(:admin_member) { FactoryBot.create(:admin) }
context "when not signed in" do
describe 'GET new' do
before { get :new, params: { id: garden_type.to_param } }

View File

@@ -4,7 +4,6 @@ describe LikesController do
let(:like) { FactoryBot.create :like, member: member }
let(:member) { FactoryBot.create(:member) }
let(:blogpost) { FactoryBot.create(:post) }
let(:mypost) { FactoryBot.create(:post, author: member) }
before { sign_in member }
@@ -22,7 +21,6 @@ describe LikesController do
end
describe "Liking your own post" do
let(:blogpost) { FactoryBot.create(:post, author: member) }
end
end

View File

@@ -1,75 +0,0 @@
require 'rails_helper'
describe NotificationsController do
login_member
def valid_attributes
{
"recipient_id" => subject.current_member.id,
"sender_id" => FactoryBot.create(:member).id,
"subject" => 'test'
}
end
describe "GET index" do
it "assigns all notifications as @notifications" do
notification = FactoryBot.create(:notification, recipient_id: subject.current_member.id)
get :index, params: {}
assigns(:notifications).should eq([notification])
end
end
describe "GET show" do
it "assigns the requested notification as @notification" do
notification = FactoryBot.create(:notification, recipient_id: subject.current_member.id)
get :show, params: { id: notification.to_param }
assigns(:notification).should eq(notification)
end
it "assigns the reply link for a post comment" do
notification = FactoryBot.create(:notification, recipient_id: subject.current_member.id)
get :show, params: { id: notification.to_param }
assigns(:reply_link).should_not be_nil
assigns(:reply_link).should eq new_comment_url(
post_id: notification.post.id
)
end
it "marks notifications as read" do
notification = FactoryBot.create(:notification, recipient_id: subject.current_member.id)
get :show, params: { id: notification.to_param }
# we need to fetch it from the db again, can't test against the old one
n = Notification.find(notification.id)
n.read.should eq true
end
end
describe "GET reply" do
it "marks notifications as read" do
notification = FactoryBot.create(:notification, recipient_id: subject.current_member.id)
get :reply, params: { notification_id: notification.to_param }
# we need to fetch it from the db again, can't test against the old one
n = Notification.find(notification.id)
n.read.should eq true
end
end
describe "GET new" do
it "assigns a recipient" do
@recipient = FactoryBot.create(:member)
get :new, params: { recipient_id: @recipient.id }
expect(assigns(:recipient)).to be_an_instance_of(Member)
end
end
describe "POST create" do
describe "with valid params" do
it "redirects to the recipient's profile" do
@recipient = FactoryBot.create(:member)
post :create, params: { notification: { recipient_id: @recipient.id, subject: 'foo' } }
response.should redirect_to(notifications_path)
end
end
end
end

View File

@@ -1,13 +1,8 @@
require 'rails_helper'
describe "forums", js: true do
context "as an admin user" do
let(:member) { create :admin_member }
before do
login_as member
end
context 'signed in admin' do
include_context 'signed in admin'
it "navigating to forum admin with js" do
visit admin_path
Percy.snapshot(page, name: 'Admin page')

View File

@@ -1,52 +1,54 @@
require 'rails_helper'
describe "forums", js: true do
context "as an admin user" do
let(:member) { create :admin_member }
let(:forum) { create :forum }
include_context 'signed in admin'
let(:forum) { create :forum }
describe "navigating to forum admin with js" do
before do
login_as member
end
it "navigating to forum admin with js" do
visit admin_path
within 'ul#site_admin' do
within 'nav#site_admin' do
click_link "Forums"
end
expect(current_path).to eq forums_path
expect(page).to have_link "New forum"
end
it { expect(current_path).to eq forums_path }
it { expect(page).to have_link "New forum" }
end
it "adding a forum" do
describe "adding a forum" do
before do
visit forums_path
click_link "New forum"
expect(current_path).to eq new_forum_path
fill_in 'Name', with: 'Discussion'
fill_in 'Description', with: "this is a new forum"
click_button 'Save'
expect(current_path).to eq forum_path(Forum.last)
expect(page).to have_content 'Forum was successfully created'
end
it { expect(current_path).to eq forum_path(Forum.last) }
it { expect(page).to have_content 'Forum was successfully created' }
end
it 'editing forum' do
describe 'editing forum' do
before do
visit forum_path forum
click_link 'Edit'
fill_in 'Name', with: 'Something else'
click_button 'Save'
forum.reload
expect(current_path).to eq forum_path(forum)
expect(page).to have_content 'Forum was successfully updated'
expect(page).to have_content 'Something else'
end
it { expect(current_path).to eq forum_path(forum) }
it { expect(page).to have_content 'Forum was successfully updated' }
it { expect(page).to have_content 'Something else' }
end
it 'deleting forum' do
describe 'deleting forum' do
before do
visit forum_path forum
accept_confirm do
click_link 'Delete'
end
expect(current_path).to eq forums_path
expect(page).to have_content 'Forum was successfully deleted'
end
it { expect(current_path).to eq forums_path }
it { expect(page).to have_content 'Forum was successfully deleted' }
end
end

View File

@@ -1,26 +1,25 @@
require 'rails_helper'
describe "cms admin" do
let(:member) { create :member }
let(:admin_member) { create :admin_member }
it "can't view CMS admin if not signed in" do
visit comfy_admin_cms_path
expect(current_path).to eq root_path
expect(page).to have_content "Please sign in as an admin user"
end
it "can't view CMS admin if not an admin member" do
# sign in as an ordinary member
login_as member
visit comfy_admin_cms_path
expect(current_path).to eq root_path
expect(page).to have_content "Please sign in as an admin user"
context 'signed in' do
include_context 'signed in member'
it "can't view CMS admin if not an admin member" do
visit comfy_admin_cms_path
expect(current_path).to eq root_path
expect(page).to have_content "Please sign in as an admin user"
end
end
it "admin members can view CMS admin area" do
login_as admin_member
visit comfy_admin_cms_path
expect(current_path).to match(/#{comfy_admin_cms_path}/) # match any CMS admin page
context 'admin' do
include_context 'signed in admin'
it "admin members can view CMS admin area" do
visit comfy_admin_cms_path
expect(current_path).to match(/#{comfy_admin_cms_path}/) # match any CMS admin page
end
end
end

View File

@@ -1,13 +1,11 @@
require 'rails_helper'
describe 'Commenting on a post' do
let(:member) { create :member }
let(:post) { create :post, author: member }
include_context 'signed in member'
let(:member) { create :member }
let(:post) { create :post, author: member }
before do
login_as member
visit new_comment_path post_id: post.id
end
before { visit new_comment_path post_id: post.id }
it "creating a comment" do
fill_in "comment_body", with: "This is a sample test for comment"

View File

@@ -0,0 +1,42 @@
require 'rails_helper'
describe "Conversations", :js do
let(:sender) { create :member }
let(:recipient) { create :member, login_name: 'beyonce' }
before do
sender.send_message(recipient, "this is the body", "something i want to say")
login_as recipient
end
describe "Read conversations list" do
before do
visit root_path
click_link 'Your Stuff'
click_link 'Inbox'
end
it { expect(page).to have_content 'something i want to say' }
it { Percy.snapshot(page, name: 'conversations#index') }
describe 'deleting' do
before do
# delete button
click_link class: 'delete'
end
describe 'view trash' do
before { click_link 'trash' }
it { expect(page).to have_content 'something i want to say' }
describe 'restore conversation' do
before { click_link class: 'restore' }
it { expect(page).not_to have_content 'something i want to say' }
describe 'conversation was restored' do
before { click_link 'inbox' }
it { expect(page).to have_content 'something i want to say' }
end
end
end
end
end
end

View File

@@ -0,0 +1,32 @@
require 'rails_helper'
describe "Conversations", :js do
let(:sender) { create :member }
let(:recipient) { create :member, login_name: 'beyonce' }
before do
sender.send_message(recipient, "this is the body", "something i want to say")
login_as recipient
end
describe 'view conversation thread' do
before do
visit root_path
click_link 'Your Stuff'
click_link 'Inbox'
click_link 'something i want to say'
end
it { expect(page).to have_content 'this is the body' }
it { expect(page).to have_link sender.login_name }
it { Percy.snapshot(page, name: 'conversations#show') }
describe 'Replying to the conversation' do
before do
fill_in :body, with: 'i like this too'
click_button 'Send'
end
it { expect(page).to have_content "i like this too" }
end
end
end

View File

@@ -4,26 +4,23 @@ describe "Alternate names", js: true do
let!(:alternate_eggplant) { create :alternate_eggplant }
let(:crop) { alternate_eggplant.crop }
it "Display alternate names on crop page" do
visit crop_path(alternate_eggplant.crop)
# expect(page.status_code).to equal 200
expect(page).to have_content alternate_eggplant.name
end
it "Index page for alternate names" do
visit alternate_names_path
expect(page).to have_content alternate_eggplant.name
end
context "User is a crop wrangler" do
let!(:crop_wranglers) { create_list :crop_wrangling_member, 3 }
let(:member) { crop_wranglers.first }
before do
login_as member
shared_examples 'show alt names' do
it "can see alternate names on crop page" do
visit crop_path(alternate_eggplant.crop)
# expect(page.status_code).to equal 200
expect(page).to have_content alternate_eggplant.name
end
it "Crop wranglers can edit alternate names" do
it "can see page for alternate names" do
visit alternate_names_path
expect(page).to have_content alternate_eggplant.name
end
end
shared_examples 'edit alt names' do
let!(:crop_wranglers) { create_list :crop_wrangling_member, 3 }
it "can edit alternate names" do
visit crop_path(crop)
# expect(page.status_code).to equal 200
expect(page).to have_content "CROP WRANGLER"
@@ -41,7 +38,7 @@ describe "Alternate names", js: true do
expect(page).to have_content 'Alternate name was successfully updated'
end
it "Crop wranglers can delete alternate names" do
it "can delete alternate names" do
visit crop_path(alternate_eggplant.crop)
click_link('aubergine', href: '#')
accept_confirm do
@@ -51,7 +48,7 @@ describe "Alternate names", js: true do
expect(page).to have_content 'Alternate name was successfully deleted'
end
it "Crop wranglers can add alternate names" do
it "can add alternate names" do
visit crop_path(crop)
expect(page).to have_link "Add",
href: new_alternate_name_path(crop_id: crop.id)
@@ -65,7 +62,7 @@ describe "Alternate names", js: true do
expect(page).to have_content 'Alternate name was successfully created'
end
it "The show-alternate-name page works" do
it "shows alternate-name page" do
visit alternate_name_path(alternate_eggplant)
# expect(page.status_code).to equal 200
expect(page).to have_content alternate_eggplant.crop.name
@@ -82,4 +79,25 @@ describe "Alternate names", js: true do
end
end
end
context 'Anonymous' do
include_examples 'show alt names'
end
context 'Signed in member' do
include_context 'signed in member'
include_examples 'show alt names'
end
context 'Crop wrangler' do
include_context 'signed in crop wrangler'
include_examples 'show alt names'
include_examples 'edit alt names'
end
context 'Admin' do
include_context 'signed in admin'
include_examples 'show alt names'
include_examples 'edit alt names'
end
end

View File

@@ -6,35 +6,51 @@ describe "browse crops" do
let!(:pending_crop) { FactoryBot.create :crop_request }
let!(:rejected_crop) { FactoryBot.create :rejected_crop }
it "has a form for sorting by" do
visit crops_path
expect(page).to have_css "select#sort"
end
it "shows a list of crops" do
crop1 = tomato
visit crops_path
expect(page).to have_content crop1.name
end
it "pending crops are not listed" do
visit crops_path
expect(page).not_to have_content pending_crop.name
end
it "rejected crops are not listed" do
visit crops_path
expect(page).not_to have_content rejected_crop.name
end
context "logged in and crop wrangler" do
before do
login_as FactoryBot.create(:crop_wrangling_member)
visit crops_path
shared_examples 'shows crops' do
before { visit crops_path }
it "has a form for sorting by" do
expect(page).to have_css "select#sort"
end
it "shows a list of crops" do
expect(page).to have_content tomato.name
end
it "pending crops are not listed" do
expect(page).not_to have_content pending_crop.name
end
it "rejected crops are not listed" do
expect(page).not_to have_content rejected_crop.name
end
end
shared_examples 'add new crop' do
it "shows a new crop link" do
expect(page).to have_link "Add New Crop"
end
end
context 'anon' do
include_examples 'shows crops'
it { expect(page).not_to have_link "Add New Crop" }
end
context 'member' do
include_context 'signed in member'
include_examples 'shows crops'
include_examples 'add new crop'
end
context 'wrangler' do
include_context 'signed in crop wrangler'
include_examples 'shows crops'
include_examples 'add new crop'
end
context 'admin' do
include_context 'signed in admin'
include_examples 'shows crops'
include_examples 'add new crop'
end
end

View File

@@ -1,35 +1,66 @@
require 'rails_helper'
describe "Crop - " do
let!(:crop_wrangler) { FactoryBot.create :crop_wrangling_member }
let!(:member) { FactoryBot.create :member }
before do
login_as member
visit new_crop_path
describe "Crop", js: true do
shared_examples 'fill in form' do
before do
visit new_crop_path
within "form#new_crop" do
fill_in "crop_name", with: "Philippine flower"
fill_in "en_wikipedia_url", with: "https://en.wikipedia.org/wiki/Jasminum_sambac"
click_button "add-sci_name-row"
fill_in "sci_name[1]", with: "Jasminum sambac 1"
fill_in "sci_name[2]", with: "Jasminum sambac 2"
fill_in "alt_name[1]", with: "Sampaguita"
click_button "add-alt_name-row"
click_button "add-alt_name-row"
fill_in "alt_name[2]", with: "Manol"
click_button "add-alt_name-row"
fill_in "alt_name[3]", with: "Jazmin"
fill_in "alt_name[4]", with: "Matsurika"
end
end
end
shared_examples 'request crop' do
describe "requesting a crop with multiple scientific and alternate name" do
include_examples 'fill in form'
before do
within "form#new_crop" do
fill_in "request_notes", with: "This is the Philippine national flower."
click_button "Save"
end
end
it { expect(page).to have_content 'crop was successfully created.' }
it { expect(page).to have_content "This crop is currently pending approval." }
it { expect(page).to have_content "Jasminum sambac 2" }
it { expect(page).to have_content "Matsurika" }
end
end
shared_examples 'create crop' do
describe "creating a crop with multiple scientific and alternate name" do
include_examples 'fill in form'
before do
click_button "Save"
end
it { expect(page).to have_content 'crop was successfully created.' }
it { expect(page).to have_content "Jasminum sambac 2" }
it { expect(page).to have_content "Matsurika" }
end
end
it "creating a crop with multiple scientific and alternate name", :js do
within "form#new_crop" do
fill_in "crop_name", with: "Philippine flower"
fill_in "en_wikipedia_url", with: "https://en.wikipedia.org/wiki/Jasminum_sambac"
click_button "add-sci_name-row"
fill_in "sci_name[1]", with: "Jasminum sambac 1"
fill_in "sci_name[2]", with: "Jasminum sambac 2"
fill_in "alt_name[1]", with: "Sampaguita"
click_button "add-alt_name-row"
click_button "add-alt_name-row"
fill_in "alt_name[2]", with: "Manol"
click_button "add-alt_name-row"
fill_in "alt_name[3]", with: "Jazmin"
fill_in "alt_name[4]", with: "Matsurika"
fill_in "request_notes", with: "This is the Philippine national flower."
click_button "Save"
end
expect(page).to have_content 'crop was successfully created.'
expect(page).to have_content "This crop is currently pending approval."
expect(page).to have_content "Jasminum sambac 2"
expect(page).to have_content "Matsurika"
context 'anon' do
before { visit new_crop_path }
it { expect(page).to have_content 'You need to sign in' }
end
context 'member' do
include_context 'signed in member'
include_examples 'request crop'
end
context 'crop wrangler' do
include_context 'signed in crop wrangler'
include_examples 'create crop'
end
context 'admin' do
include_context 'signed in admin'
include_examples 'create crop'
end
end

View File

@@ -20,13 +20,8 @@ describe "crop detail page", js: true do
end
end
context "signed in member" do
let(:member) { create :member }
before do
login_as(member)
end
context 'signed in' do
include_context "signed in member"
context "action buttons" do
before { subject }
@@ -78,25 +73,25 @@ describe "crop detail page", js: true do
end
context "seed quantity for a crop" do
let(:member) { create :member }
let(:seed) { create :seed, crop: crop, quantity: 20, owner: member }
let(:seed) { create :seed, crop: crop, quantity: 20 }
it "User not signed in" do
visit crop_path(seed.crop)
expect(page).not_to have_content "You have 20 seeds"
end
it "User signed in" do
login_as(member)
visit crop_path(seed.crop)
expect(page).to have_link "You have 20 seeds of this crop."
end
it "click link to your owned seeds" do
login_as(member)
visit crop_path(seed.crop)
click_link "You have 20 seeds of this crop."
expect(current_path).to eq member_seeds_path(member_slug: member.slug)
context 'signed in' do
include_context 'signed in member'
before { seed.update! owner: member }
it "User signed in" do
visit crop_path(seed.crop)
expect(page).to have_link "You have 20 seeds of this crop."
end
it "click link to your owned seeds" do
visit crop_path(seed.crop)
click_link "You have 20 seeds of this crop."
expect(current_path).to eq member_seeds_path(member_slug: member.slug)
end
end
end

View File

@@ -3,20 +3,20 @@ require 'rails_helper'
describe "crop detail page", js: true do
subject { page }
let!(:member) { FactoryBot.create :member }
let!(:owner_member) { FactoryBot.create :member }
let!(:crop) { FactoryBot.create :crop }
let!(:planting) { FactoryBot.create :planting, crop: crop, owner: member }
let!(:harvest) { FactoryBot.create :harvest, crop: crop, owner: member }
let!(:seed) { FactoryBot.create :seed, crop: crop, owner: member }
let!(:planting) { FactoryBot.create :planting, crop: crop, owner: owner_member }
let!(:harvest) { FactoryBot.create :harvest, crop: crop, owner: owner_member }
let!(:seed) { FactoryBot.create :seed, crop: crop, owner: owner_member }
let!(:photo1) { FactoryBot.create(:photo, owner: member) }
let!(:photo2) { FactoryBot.create(:photo, owner: member) }
let!(:photo3) { FactoryBot.create(:photo, owner: member) }
let!(:photo4) { FactoryBot.create(:photo, owner: member) }
let!(:photo5) { FactoryBot.create(:photo, owner: member) }
let!(:photo6) { FactoryBot.create(:photo, owner: member) }
let!(:photo1) { FactoryBot.create(:photo, owner: owner_member) }
let!(:photo2) { FactoryBot.create(:photo, owner: owner_member) }
let!(:photo3) { FactoryBot.create(:photo, owner: owner_member) }
let!(:photo4) { FactoryBot.create(:photo, owner: owner_member) }
let!(:photo5) { FactoryBot.create(:photo, owner: owner_member) }
let!(:photo6) { FactoryBot.create(:photo, owner: owner_member) }
before do
planting.photos << photo1
@@ -50,14 +50,13 @@ describe "crop detail page", js: true do
end
context "when signed in" do
before { login_as(FactoryBot.create(:member)) }
include_context 'signed in member'
include_examples "shows photos"
end
context "when signed in as photos owner" do
before { login_as(member) }
include_context 'signed in member'
let(:member) { owner_member }
include_examples "shows photos"
end

View File

@@ -2,21 +2,19 @@ require 'rails_helper'
describe "crop wranglers", js: true do
context "signed in wrangler" do
include_context 'signed in crop wrangler'
let!(:crop_wranglers) { create_list :crop_wrangling_member, 3 }
let(:wrangler) { crop_wranglers.first }
let!(:crops) { create_list :crop, 2 }
let!(:requested_crop) { create :crop_request }
let!(:rejected_crop) { create :rejected_crop }
before { login_as wrangler }
it "sees crop wranglers listed on the crop wrangler page" do
visit root_path
click_link 'Admin'
click_link 'Crop Wrangling'
within '.crop_wranglers' do
expect(page).to have_content 'Crop Wranglers:'
expect(page).to have_content 'Crop Wranglers'
crop_wranglers.each do |crop_wrangler|
expect(page).to have_link crop_wrangler.login_name, href: member_path(crop_wrangler)
end
@@ -67,22 +65,20 @@ describe "crop wranglers", js: true do
expect(page).to have_content "This crop was rejected for the following reason: Totally fake"
end
end
end
context "signed in non-wrangler" do
let!(:crop_wranglers) { create_list :crop_wrangling_member, 3 }
let(:member) { create :member }
context "signed in non-wrangler" do
include_context 'signed in member'
let!(:crop_wranglers) { create_list :crop_wrangling_member, 3 }
before { login_as member }
it "can't see wrangling page without js", js: false do
visit root_path
expect(page).not_to have_link "Crop Wrangling"
end
it "can't see wrangling page without js", js: false do
visit root_path
expect(page).not_to have_link "Crop Wrangling"
end
it "can't see wrangling page with js" do
visit root_path
click_link member.login_name
expect(page).not_to have_link "Crop Wrangling"
end
it "can't see wrangling page with js" do
visit root_path
click_link member.login_name
expect(page).not_to have_link "Crop Wrangling"
end
end

View File

@@ -1,28 +1,20 @@
require 'rails_helper'
describe "crop wrangling button" do
let(:crop_wrangler) { create :crop_wrangling_member }
let(:member) { create :member }
context "crop wrangling button" do
before do
login_as crop_wrangler
visit crops_path
end
it "has a link to crop wrangling page" do
expect(page).to have_link "Wrangle Crops", href: wrangle_crops_path
end
context 'not signed in' do
before { visit crops_path }
it { expect(page).to have_no_link "Wrangle Crops", href: wrangle_crops_path }
end
context "crop wrangling button" do
before do
login_as member
visit crops_path
end
context "signed in, but not a crop wrangler" do
include_context 'signed in member'
before { visit crops_path }
it { expect(page).to have_no_link "Wrangle Crops", href: wrangle_crops_path }
end
it "has no link to crop wrangling page" do
expect(page).to have_no_link "Wrangle Crops", href: wrangle_crops_path
end
context "signed in crop wrangler" do
include_context 'signed in crop wrangler'
before { visit crops_path }
it { expect(page).to have_link "Wrangle Crops", href: wrangle_crops_path }
end
end

View File

@@ -1,14 +1,10 @@
require 'rails_helper'
describe "Delete crop spec" do
context "As a crop wrangler" do
let(:wrangler) { FactoryBot.create :crop_wrangling_member }
shared_examples 'delete crop' do
let!(:pending_crop) { FactoryBot.create :crop_request }
let!(:approved_crop) { FactoryBot.create :crop }
before { login_as wrangler }
it "Delete approved crop" do
it "deletes approved crop" do
visit crop_path(approved_crop)
click_link 'Actions'
accept_confirm do
@@ -17,7 +13,7 @@ describe "Delete crop spec" do
expect(page).to have_content "crop was successfully destroyed"
end
it "Delete pending crop" do
it "deletes pending crop" do
visit crop_path(pending_crop)
click_link 'Actions'
accept_confirm do
@@ -26,4 +22,14 @@ describe "Delete crop spec" do
expect(page).to have_content "crop was successfully destroyed"
end
end
context "As a crop wrangler" do
include_context 'signed in crop wrangler'
include_examples 'delete crop'
end
context 'admin' do
include_context 'signed in admin'
include_examples 'delete crop'
end
end

View File

@@ -2,13 +2,7 @@ require 'rails_helper'
describe "Requesting a new crop" do
context "As a regular member" do
let(:member) { create :member }
let!(:wrangler) { create :crop_wrangling_member }
before do
login_as member
end
include_context 'signed in member'
it "Submit request" do
visit new_crop_path
fill_in "Name", with: "Couch potato"
@@ -20,12 +14,10 @@ describe "Requesting a new crop" do
end
context "As a crop wrangler" do
let(:wrangler) { create :crop_wrangling_member }
include_context 'signed in crop wrangler'
let!(:crop) { create :crop_request }
let!(:already_approved) { create :crop }
before { login_as wrangler }
it "Approve a request" do
visit edit_crop_path(crop)
select "approved", from: "Approval status"

View File

@@ -1,15 +1,14 @@
require 'rails_helper'
describe "Crop - " do
let(:member) { create :member }
describe "Requesting Crops" do
let!(:requested_crop) { create :crop, requester: member, approval_status: 'pending', name: 'puha for dinner' }
before do
login_as member
visit requested_crops_path
end
context 'signed in' do
include_context 'signed in member'
before { visit requested_crops_path }
it "creating a crop with multiple scientific and alternate name", :js do
expect(page).to have_content "puha for dinner"
it "creating a crop with multiple scientific and alternate name", :js do
expect(page).to have_content "puha for dinner"
end
end
end

View File

@@ -18,12 +18,8 @@ describe "Scientific names", js: true do
context "User is a crop wrangler" do
let!(:crop_wranglers) { create_list :crop_wrangling_member, 3 }
let(:member) { crop_wranglers.first }
before do
login_as(member)
end
include_context 'signed in crop wrangler'
it "Crop wranglers can edit scientific names" do
visit crop_path(crop)
# expect(page.status_code).to equal 200

View File

@@ -1,10 +1,7 @@
require 'rails_helper'
describe "browse crops" do
let(:tomato) { create :tomato }
let(:maize) { create :maize }
let(:pending_crop) { create :crop_request }
let(:rejected_crop) { create :rejected_crop }
let(:tomato) { create :tomato }
it "Show crop info" do
visit crop_path(tomato)

View File

@@ -5,9 +5,7 @@ describe "Gardens" do
context 'logged in' do
subject { page }
let(:member) { FactoryBot.create :member }
before { login_as member }
include_context 'signed in member'
let(:garden) { member.gardens.first }
let(:other_member_garden) { FactoryBot.create :garden }

View File

@@ -2,37 +2,35 @@ require 'rails_helper'
require 'custom_matchers'
describe "Gardens", :js do
let(:member) { FactoryBot.create :member }
context 'signed in' do
include_context 'signed in member'
before { visit new_garden_path }
before do
login_as member
visit new_garden_path
end
it "has the required fields help text" do
expect(page).to have_content "* denotes a required field"
end
it "has the required fields help text" do
expect(page).to have_content "* denotes a required field"
end
it "displays required and optional fields properly" do
expect(page).to have_selector ".required", text: "Name"
expect(page).to have_selector 'textarea#garden_description'
expect(page).to have_selector 'input#garden_location'
expect(page).to have_selector 'input#garden_area'
end
it "displays required and optional fields properly" do
expect(page).to have_selector ".required", text: "Name"
expect(page).to have_selector 'textarea#garden_description'
expect(page).to have_selector 'input#garden_location'
expect(page).to have_selector 'input#garden_area'
end
it "Create new garden" do
fill_in "Name", with: "New garden"
click_button "Save"
expect(page).to have_content "Garden was successfully created"
expect(page).to have_content "New garden"
end
it "Create new garden" do
fill_in "Name", with: "New garden"
click_button "Save"
expect(page).to have_content "Garden was successfully created"
expect(page).to have_content "New garden"
end
it "Refuse to create new garden with negative area" do
visit new_garden_path
fill_in "Name", with: "Negative Garden"
fill_in "Area", with: -5
click_button "Save"
expect(page).not_to have_content "Garden was successfully created"
expect(page).to have_content "Area must be greater than or equal to 0"
it "Refuse to create new garden with negative area" do
visit new_garden_path
fill_in "Name", with: "Negative Garden"
fill_in "Area", with: -5
click_button "Save"
expect(page).not_to have_content "Garden was successfully created"
expect(page).to have_content "Area must be greater than or equal to 0"
end
end
end

View File

@@ -1,121 +1,120 @@
require 'rails_helper'
describe "Planting a crop", js: true do
# name is aaa to ensure it is ordered first
let!(:garden) { create :garden, name: 'aaa' }
let!(:planting) { create :planting, garden: garden, owner: garden.owner, planted_at: Date.parse("2013-3-10") }
let!(:tomato) { create :tomato }
let!(:finished_planting) { create :finished_planting, owner: garden.owner, garden: garden, crop: tomato }
context 'signed in' do
include_context 'signed in member'
# name is aaa to ensure it is ordered first
let!(:garden) { create :garden, name: 'aaa', owner: member }
let!(:planting) { create :planting, garden: garden, owner: garden.owner, planted_at: Date.parse("2013-3-10") }
let!(:tomato) { create :tomato }
let!(:finished_planting) { create :finished_planting, owner: garden.owner, garden: garden, crop: tomato }
before do
login_as garden.owner
end
it "View gardens" do
visit gardens_path
expect(page).to have_content "Everyone's gardens"
within '.layout-actions' do
click_link "My gardens"
end
expect(page).to have_content "#{garden.owner.login_name}'s gardens"
within '.layout-actions' do
click_link "Everyone's gardens"
end
expect(page).to have_content "Everyone's gardens"
end
it "Marking a garden as inactive" do
visit garden_path(garden)
click_link 'Actions'
accept_confirm do
click_link "Mark as inactive"
end
expect(page).to have_content "Garden was successfully updated"
expect(page).to have_content "This garden is inactive"
click_link 'Actions'
expect(page).to have_content "Mark as active"
expect(page).not_to have_content "Mark as inactive"
end
it "List only active gardens" do
visit garden_path(garden)
click_link 'Actions'
accept_confirm do
click_link "Mark as inactive"
end
visit gardens_path
expect(page).not_to have_link garden_path(garden)
end
it "Create new garden" do
visit new_garden_path
fill_in "Name", with: "New garden"
click_button "Save"
expect(page).to have_content "Garden was successfully created"
expect(page).to have_content "New garden"
end
it "Refuse to create new garden with negative area" do
visit new_garden_path
fill_in "Name", with: "Negative Garden"
fill_in "Area", with: -5
click_button "Save"
expect(page).not_to have_content "Garden was successfully created"
expect(page).to have_content "Area must be greater than or equal to 0"
end
context "Clicking edit from the index page" do
before do
it "View gardens" do
visit gardens_path
expect(page).to have_content "Everyone's gardens"
within '.layout-actions' do
click_link "My gardens"
end
expect(page).to have_content "#{garden.owner.login_name}'s gardens"
within '.layout-actions' do
click_link "Everyone's gardens"
end
expect(page).to have_content "Everyone's gardens"
end
it "button on index to edit garden" do
first('a#garden-actions-button').click
click_link href: edit_garden_path(garden)
expect(page).to have_content 'Edit garden'
end
end
it "Edit garden" do
visit new_garden_path
fill_in "Name", with: "New garden"
click_button "Save"
click_link 'Actions'
within '.garden-actions' do
click_link 'Edit'
end
fill_in "Name", with: "Different name"
click_button "Save"
expect(page).to have_content "Garden was successfully updated"
expect(page).to have_content "Different name"
end
it "Delete garden" do
visit new_garden_path
fill_in "Name", with: "New garden"
click_button "Save"
visit garden_path(Garden.last)
click_link 'Actions'
accept_confirm do
click_link 'Delete'
end
expect(page).to have_content "Garden was successfully deleted"
expect(page).to have_content "#{garden.owner}'s gardens"
end
describe "Making a planting inactive from garden show" do
it do
it "Marking a garden as inactive" do
visit garden_path(garden)
click_link(class: 'planting-menu')
click_link "Mark as finished"
find(".datepicker-days td.day", text: "21").click
expect(page).to have_content 'Finished'
end
end
click_link 'Actions'
accept_confirm do
click_link "Mark as inactive"
end
expect(page).to have_content "Garden was successfully updated"
expect(page).to have_content "This garden is inactive"
it "List only active plantings on a garden" do
visit gardens_path
expect(page).not_to have_content finished_planting.crop_name
click_link 'Actions'
expect(page).to have_content "Mark as active"
expect(page).not_to have_content "Mark as inactive"
end
it "List only active gardens" do
visit garden_path(garden)
click_link 'Actions'
accept_confirm do
click_link "Mark as inactive"
end
visit gardens_path
expect(page).not_to have_link garden_path(garden)
end
it "Create new garden" do
visit new_garden_path
fill_in "Name", with: "New garden"
click_button "Save"
expect(page).to have_content "Garden was successfully created"
expect(page).to have_content "New garden"
end
it "Refuse to create new garden with negative area" do
visit new_garden_path
fill_in "Name", with: "Negative Garden"
fill_in "Area", with: -5
click_button "Save"
expect(page).not_to have_content "Garden was successfully created"
expect(page).to have_content "Area must be greater than or equal to 0"
end
context "Clicking edit from the index page" do
before do
visit gardens_path
end
it "button on index to edit garden" do
first('a#garden-actions-button').click
click_link href: edit_garden_path(garden)
expect(page).to have_content 'Edit garden'
end
end
it "Edit garden" do
visit new_garden_path
fill_in "Name", with: "New garden"
click_button "Save"
click_link 'Actions'
within '.garden-actions' do
click_link 'Edit'
end
fill_in "Name", with: "Different name"
click_button "Save"
expect(page).to have_content "Garden was successfully updated"
expect(page).to have_content "Different name"
end
it "Delete garden" do
visit new_garden_path
fill_in "Name", with: "New garden"
click_button "Save"
visit garden_path(Garden.last)
click_link 'Actions'
accept_confirm do
click_link 'Delete'
end
expect(page).to have_content "Garden was successfully deleted"
expect(page).to have_content "#{garden.owner}'s gardens"
end
describe "Making a planting inactive from garden show" do
it do
visit garden_path(garden)
click_link(class: 'planting-menu')
click_link "Mark as finished"
find(".datepicker-days td.day", text: "21").click
expect(page).to have_content 'Finished'
end
end
it "List only active plantings on a garden" do
visit gardens_path
expect(page).not_to have_content finished_planting.crop_name
end
end
end

View File

@@ -3,10 +3,9 @@ require 'custom_matchers'
describe "Gardens#index", :js do
context "Logged in as member" do
include_context 'signed in member'
let(:member) { FactoryBot.create :member, login_name: 'shadow' }
before { login_as member }
context "with 10 gardens" do
before do
FactoryBot.create_list :garden, 10, owner: member

View File

@@ -3,30 +3,30 @@ require 'rails_helper'
describe "browse harvests" do
subject { page }
let!(:member) { create :member }
let!(:harvest) { create :harvest, owner: member }
before { login_as member }
context 'signed in' do
include_context 'signed in member'
describe 'blank optional fields' do
let!(:harvest) { create :harvest, :no_description }
describe 'blank optional fields' do
let!(:harvest) { create :harvest, :no_description }
before { visit harvests_path }
before { visit harvests_path }
it 'read more' do
expect(subject).not_to have_link "Read more"
end
end
describe "filled in optional fields" do
let!(:harvest) { create :harvest, :long_description }
before do
visit harvests_path
it 'read more' do
expect(subject).not_to have_link "Read more"
end
end
it 'links to #show' do
expect(subject).to have_link harvest.crop.name, href: harvest_path(harvest)
describe "filled in optional fields" do
let!(:harvest) { create :harvest, :long_description }
before do
visit harvests_path
end
it 'links to #show' do
expect(subject).to have_link harvest.crop.name, href: harvest_path(harvest)
end
end
end
end

Some files were not shown because too many files have changed in this diff Show More