mirror of
https://github.com/Growstuff/growstuff.git
synced 2026-05-26 09:50:08 -04:00
Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c08beb5291 | ||
|
|
269f3164b7 | ||
|
|
9d47b2b029 | ||
|
|
81e26aaf02 | ||
|
|
d62322b498 | ||
|
|
ebb2f90294 | ||
|
|
72922a160d | ||
|
|
0699716c19 | ||
|
|
4c35987cb7 | ||
|
|
54e0806f38 | ||
|
|
99828c7c01 | ||
|
|
25d2773464 | ||
|
|
233a30a045 | ||
|
|
01a7463ae8 | ||
|
|
8a446ecbfc | ||
|
|
efc60e6313 | ||
|
|
406b2dbf04 | ||
|
|
ac056b4eb5 | ||
|
|
2394f4d237 | ||
|
|
2a4fd57050 | ||
|
|
5c885d675e | ||
|
|
0b08691160 | ||
|
|
b1052a2dbf | ||
|
|
95a923ff5f | ||
|
|
c8e34b640e | ||
|
|
6f2b4b13cd | ||
|
|
970da53873 | ||
|
|
3aa2f0bd3b | ||
|
|
8c1514bc04 | ||
|
|
d2e39f58d5 | ||
|
|
3d3a3b669e | ||
|
|
8697e56323 | ||
|
|
472b2eb192 | ||
|
|
1698157d8d | ||
|
|
ff652ae234 | ||
|
|
25e9ef892d | ||
|
|
82149d4565 | ||
|
|
fcd3e36e36 | ||
|
|
f8e51f4b45 | ||
|
|
1c8b75b7cb | ||
|
|
700fba58e8 | ||
|
|
4f64698403 | ||
|
|
14551b6ec8 | ||
|
|
0f317f624e | ||
|
|
41e26a2c74 | ||
|
|
c4c659f159 | ||
|
|
14b16b0d8c | ||
|
|
697779e67c | ||
|
|
35ce8b84db | ||
|
|
ccbdfe51f6 | ||
|
|
ff57176d60 | ||
|
|
12700d6268 | ||
|
|
17fd6f61a5 |
16
Gemfile.lock
16
Gemfile.lock
@@ -58,7 +58,7 @@ GEM
|
||||
public_suffix (>= 2.0.2, < 4.0)
|
||||
arel (6.0.4)
|
||||
ast (2.3.0)
|
||||
autoprefixer-rails (7.2.2)
|
||||
autoprefixer-rails (7.2.3)
|
||||
execjs
|
||||
bcrypt (3.1.11)
|
||||
better_errors (2.2.0)
|
||||
@@ -174,7 +174,7 @@ GEM
|
||||
faraday
|
||||
multi_json
|
||||
erubis (2.7.0)
|
||||
excon (0.59.0)
|
||||
excon (0.60.0)
|
||||
execjs (2.7.0)
|
||||
factory_bot (4.8.2)
|
||||
activesupport (>= 3.0.0)
|
||||
@@ -297,7 +297,7 @@ GEM
|
||||
activerecord
|
||||
kaminari-core (= 1.1.1)
|
||||
kaminari-core (1.1.1)
|
||||
kgio (2.11.0)
|
||||
kgio (2.11.1)
|
||||
kramdown (1.16.2)
|
||||
launchy (2.4.3)
|
||||
addressable (~> 2.3)
|
||||
@@ -353,8 +353,8 @@ GEM
|
||||
omniauth-oauth (1.1.0)
|
||||
oauth
|
||||
omniauth (~> 1.0)
|
||||
omniauth-oauth2 (1.4.0)
|
||||
oauth2 (~> 1.0)
|
||||
omniauth-oauth2 (1.5.0)
|
||||
oauth2 (~> 1.1)
|
||||
omniauth (~> 1.2)
|
||||
omniauth-twitter (1.4.0)
|
||||
omniauth-oauth (~> 1.1)
|
||||
@@ -366,7 +366,7 @@ GEM
|
||||
cocaine (~> 0.5.5)
|
||||
mime-types
|
||||
mimemagic (~> 0.3.0)
|
||||
parallel (1.12.0)
|
||||
parallel (1.12.1)
|
||||
parser (2.4.0.2)
|
||||
ast (~> 2.3)
|
||||
pg (0.21.0)
|
||||
@@ -376,7 +376,7 @@ GEM
|
||||
moneta (~> 0.8.1)
|
||||
plupload-rails (1.2.1)
|
||||
rails (>= 3.1)
|
||||
poltergeist (1.16.0)
|
||||
poltergeist (1.17.0)
|
||||
capybara (~> 2.1)
|
||||
cliver (~> 0.3.1)
|
||||
websocket-driver (>= 0.2.0)
|
||||
@@ -475,7 +475,7 @@ GEM
|
||||
ruby_parser (3.10.1)
|
||||
sexp_processor (~> 4.9)
|
||||
rubyzip (1.2.1)
|
||||
sass (3.5.3)
|
||||
sass (3.5.4)
|
||||
sass-listen (~> 4.0.0)
|
||||
sass-listen (4.0.0)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
module Growstuff
|
||||
module Constants
|
||||
class PhotoModels
|
||||
PLANTING = { type: 'planting', class: 'Planting', relation: 'plantings' }.freeze
|
||||
HARVEST = { type: 'harvest', class: 'Harvest', relation: 'harvests' }.freeze
|
||||
GARDEN = { type: 'garden', class: 'Garden', relation: 'gardens' }.freeze
|
||||
SEED = { type: 'seed', class: 'Seed', relation: 'seeds' }.freeze
|
||||
|
||||
ALL = [PLANTING, HARVEST, GARDEN, SEED].freeze
|
||||
|
||||
def self.types
|
||||
ALL.map do |model|
|
||||
model[:type]
|
||||
end
|
||||
end
|
||||
|
||||
def self.relations
|
||||
ALL.map do |model|
|
||||
model[:relation]
|
||||
end
|
||||
end
|
||||
|
||||
def self.get_relation(object, type)
|
||||
relation = ALL.select do |model|
|
||||
model[:type] == type
|
||||
end[0][:relation]
|
||||
object.send(relation)
|
||||
end
|
||||
|
||||
def self.get_item(type)
|
||||
class_name = ALL.select do |model|
|
||||
model[:type] == type
|
||||
end[0][:class]
|
||||
class_name.constantize
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
14
app/controllers/admin/members_controller.rb
Normal file
14
app/controllers/admin/members_controller.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
module Admin
|
||||
class MembersController < ApplicationController
|
||||
before_action :auth!
|
||||
def index
|
||||
@members = Member.order(:login_name).paginate(page: params[:page])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def auth!
|
||||
authorize! :manage, :all
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,21 +1,23 @@
|
||||
class Admin::OrdersController < ApplicationController
|
||||
def index
|
||||
authorize! :manage, :all
|
||||
respond_to do |format|
|
||||
format.html # index.html.haml
|
||||
end
|
||||
end
|
||||
|
||||
def search
|
||||
authorize! :manage, :all
|
||||
@orders = Order.search(by: params[:search_by], for: params[:search_text])
|
||||
|
||||
if @orders.empty?
|
||||
flash[:alert] = "Couldn't find order with #{params[:search_by]} = #{params[:search_text]}"
|
||||
module Admin
|
||||
class OrdersController < ApplicationController
|
||||
def index
|
||||
authorize! :manage, :all
|
||||
respond_to do |format|
|
||||
format.html # index.html.haml
|
||||
end
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html # index.html.haml
|
||||
def search
|
||||
authorize! :manage, :all
|
||||
@orders = Order.search(by: params[:search_by], for: params[:search_text])
|
||||
|
||||
if @orders.empty?
|
||||
flash[:alert] = "Couldn't find order with #{params[:search_by]} = #{params[:search_text]}"
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html # index.html.haml
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,6 +18,7 @@ class HarvestsController < ApplicationController
|
||||
|
||||
def show
|
||||
@matching_plantings = matching_plantings if @harvest.owner == current_member
|
||||
@photos = @harvest.photos.order(created_at: :desc).paginate(page: params[:page])
|
||||
respond_with(@harvest)
|
||||
end
|
||||
|
||||
|
||||
@@ -3,11 +3,19 @@ class PhotoAssociationsController < ApplicationController
|
||||
respond_to :json, :html
|
||||
|
||||
def destroy
|
||||
raise "Photos not supported" unless Photo::PHOTO_CAPABLE.include? item_class
|
||||
@photo = Photo.find_by!(id: params[:photo_id], owner: current_member)
|
||||
collection = Growstuff::Constants::PhotoModels.get_relation(@photo, params[:type])
|
||||
item_class = Growstuff::Constants::PhotoModels.get_item(params[:type])
|
||||
@item = item_class.find_by!(id: params[:id], owner_id: current_member.id)
|
||||
collection.delete(@item)
|
||||
@item = Photographing.item(item_id, item_class)
|
||||
@item.photos.delete(@photo)
|
||||
# @photo.destroy_if_unused
|
||||
respond_with(@photo)
|
||||
end
|
||||
|
||||
def item_class
|
||||
params[:type].capitalize
|
||||
end
|
||||
|
||||
def item_id
|
||||
params[:id]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,7 +36,7 @@ class PhotosController < ApplicationController
|
||||
@photo = find_or_create_photo_from_flickr_photo
|
||||
@item = item_to_link_to
|
||||
raise "Could not find this #{type} owned by you" unless @item
|
||||
collection << @item unless collection.include?(@item)
|
||||
@item.photos << @photo unless @item.photos.include? @photo
|
||||
@photo.save! if @photo.present?
|
||||
end
|
||||
respond_with @photo
|
||||
@@ -74,21 +74,12 @@ class PhotosController < ApplicationController
|
||||
end
|
||||
|
||||
# Item with photos attached
|
||||
#
|
||||
def item_to_link_to
|
||||
raise "No item id provided" if item_id.nil?
|
||||
raise "No item type provided" if item_type.nil?
|
||||
raise "Missing or invalid type provided" unless photos_supported_on_type?(item_type)
|
||||
item_class = Growstuff::Constants::PhotoModels.get_item(item_type)
|
||||
item_class.find_by!(id: params[:id], owner_id: current_member.id)
|
||||
end
|
||||
|
||||
def collection
|
||||
Growstuff::Constants::PhotoModels.get_relation(@photo, item_type)
|
||||
end
|
||||
|
||||
def photos_supported_on_type?(_type)
|
||||
Growstuff::Constants::PhotoModels.types.include?(item_type)
|
||||
item_class = item_type.capitalize
|
||||
raise "Photos not supported" unless Photo::PHOTO_CAPABLE.include? item_class
|
||||
item_class.constantize.find_by!(id: params[:id], owner_id: current_member.id)
|
||||
end
|
||||
|
||||
#
|
||||
|
||||
@@ -31,6 +31,7 @@ class PlantingsController < ApplicationController
|
||||
@planting = Planting.includes(:owner, :crop, :garden, :photos)
|
||||
.friendly
|
||||
.find(params[:id])
|
||||
@photos = @planting.photos.order(created_at: :desc).includes(:owner).paginate(page: params[:page])
|
||||
respond_with @planting
|
||||
end
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ class SeedsController < ApplicationController
|
||||
# GET /seeds/1
|
||||
# GET /seeds/1.json
|
||||
def show
|
||||
@photos = @seed.photos.includes(:owner).order(created_at: :desc).paginate(page: params[:page])
|
||||
respond_with(@seed)
|
||||
end
|
||||
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
class Ability
|
||||
include CanCan::Ability
|
||||
|
||||
def initialize(member) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
||||
def initialize(member)
|
||||
anon_abilities(member)
|
||||
member_abilities(member) if member.present?
|
||||
admin_abilities(member) if member.present? && member.role?(:admin)
|
||||
end
|
||||
|
||||
def anon_abilities(_member)
|
||||
# See the wiki for details: https://github.com/ryanb/cancan/wiki/Defining-Abilities
|
||||
|
||||
# everyone can do these things, even non-logged in
|
||||
@@ -35,7 +41,9 @@ class Ability
|
||||
can :read, AlternateName do |an|
|
||||
an.crop.approved?
|
||||
end
|
||||
end
|
||||
|
||||
def member_abilities(member) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
||||
return unless member
|
||||
|
||||
# members can see even rejected or pending crops if they requested it
|
||||
@@ -126,7 +134,9 @@ class Ability
|
||||
|
||||
can :destroy, Follow
|
||||
cannot :destroy, Follow, followed_id: member.id # can't unfollow yourself
|
||||
end
|
||||
|
||||
def admin_abilities(member)
|
||||
return unless member.role? :admin
|
||||
|
||||
can :read, :all
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
require_relative '../../constants/photo_models.rb'
|
||||
module PhotoCapable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_and_belongs_to_many :photos # rubocop:disable Rails/HasAndBelongsToMany
|
||||
has_many :photos, through: :photographings, as: :photographable
|
||||
has_many :photographings, as: :photographable, dependent: :destroy
|
||||
|
||||
before_destroy :remove_from_list
|
||||
scope :has_photos, -> { includes(:photos).where.not(photos: { id: nil }) }
|
||||
end
|
||||
|
||||
def remove_from_list
|
||||
photolist = photos.to_a # save a temp copy of the photo list
|
||||
photos.clear # clear relationship b/w object and photo
|
||||
|
||||
photolist.each(&:destroy_if_unused)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,31 +1,19 @@
|
||||
require_relative '../constants/photo_models.rb'
|
||||
class Photo < ActiveRecord::Base
|
||||
belongs_to :owner, class_name: 'Member'
|
||||
|
||||
Growstuff::Constants::PhotoModels.relations.each do |relation|
|
||||
has_and_belongs_to_many relation.to_sym # rubocop:disable Rails/HasAndBelongsToMany
|
||||
end
|
||||
PHOTO_CAPABLE = %w(Garden Planting Harvest Seed).freeze
|
||||
|
||||
before_destroy { all_associations.clear }
|
||||
has_many :photographings, foreign_key: :photo_id, dependent: :destroy
|
||||
# creates a relationship for each assignee type
|
||||
PHOTO_CAPABLE.each do |type|
|
||||
has_many type.downcase.pluralize.to_s.to_sym,
|
||||
through: :photographings,
|
||||
source: :photographable,
|
||||
source_type: type
|
||||
end
|
||||
|
||||
default_scope { joins(:owner) } # Ensures the owner still exists
|
||||
|
||||
def associations?
|
||||
plantings.any? || harvests.any? || gardens.any? || seeds.any?
|
||||
end
|
||||
|
||||
def all_associations
|
||||
associations = []
|
||||
Growstuff::Constants::PhotoModels.relations.each do |association_name|
|
||||
associations << send(association_name.to_s).to_a
|
||||
end
|
||||
associations.flatten!
|
||||
end
|
||||
|
||||
def destroy_if_unused
|
||||
destroy if all_associations.empty?
|
||||
end
|
||||
|
||||
# This is split into a side-effect free method and a side-effecting method
|
||||
# for easier stubbing and testing.
|
||||
def flickr_metadata
|
||||
@@ -43,6 +31,14 @@ class Photo < ActiveRecord::Base
|
||||
}
|
||||
end
|
||||
|
||||
def associations?
|
||||
photographings.size.positive?
|
||||
end
|
||||
|
||||
def destroy_if_unused
|
||||
destroy unless associations?
|
||||
end
|
||||
|
||||
def calculate_title(info)
|
||||
if id && title # already has a title saved
|
||||
title
|
||||
|
||||
12
app/models/photographing.rb
Normal file
12
app/models/photographing.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
class Photographing < ActiveRecord::Base
|
||||
belongs_to :photo
|
||||
belongs_to :photographable, polymorphic: true
|
||||
|
||||
def self.item(item_id, item_type)
|
||||
find_by!(photographable_id: item_id, photographable_type: item_type).photographable
|
||||
end
|
||||
|
||||
def item
|
||||
find_by!(photographable_id: photographable_id, photographable_type: photographable_type).photographable
|
||||
end
|
||||
end
|
||||
@@ -26,6 +26,7 @@ class Planting < ActiveRecord::Base
|
||||
scope :finished, -> { where(finished: true) }
|
||||
scope :current, -> { where(finished: false) }
|
||||
scope :interesting, -> { has_photos.one_per_owner }
|
||||
scope :recent, -> { order(created_at: :desc) }
|
||||
scope :one_per_owner, lambda {
|
||||
joins("JOIN members m ON (m.id=plantings.owner_id)
|
||||
LEFT OUTER JOIN plantings p2
|
||||
|
||||
@@ -43,7 +43,7 @@ class Post < ActiveRecord::Base
|
||||
|
||||
# return posts sorted by recent activity
|
||||
def self.recently_active
|
||||
Post.all.sort do |a, b|
|
||||
Post.order(created_at: :desc).sort do |a, b|
|
||||
b.recent_activity <=> a.recent_activity
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,16 +2,28 @@
|
||||
|
||||
%h2 Manage
|
||||
|
||||
%ul#admin_links
|
||||
%li= link_to "Account types", account_types_path
|
||||
%li= link_to "Alternate names", alternate_names_path
|
||||
%li= link_to "Scientific names", scientific_names_path
|
||||
%li= link_to "Products", products_path
|
||||
%li= link_to "Roles", roles_path
|
||||
%li= link_to "Forums", forums_path
|
||||
%li= link_to "Newsletter subscribers", admin_newsletter_path
|
||||
%li= link_to "CMS", comfy_admin_cms_path
|
||||
.row
|
||||
.col-md-4
|
||||
%h2 Site admin
|
||||
%ul#site_admin
|
||||
%li= link_to "Account types", account_types_path
|
||||
%li= link_to "Products", products_path
|
||||
%li= link_to "Roles", roles_path
|
||||
%li= link_to "Forums", forums_path
|
||||
%li= link_to "CMS", comfy_admin_cms_path
|
||||
|
||||
%h2 Orders
|
||||
.col-md-4
|
||||
%h2 Crop data admin
|
||||
%ul
|
||||
%li= link_to "Alternate names", alternate_names_path
|
||||
%li= link_to "Scientific names", scientific_names_path
|
||||
.col-md-4
|
||||
%h2 Member admin
|
||||
%ul
|
||||
%li= link_to "Newsletter subscribers", admin_newsletter_path
|
||||
%li= link_to "Members", admin_members_path
|
||||
|
||||
= render "admin/orders/searchform"
|
||||
.row
|
||||
.col-md-12
|
||||
%h2 Orders
|
||||
= render "admin/orders/searchform"
|
||||
|
||||
15
app/views/admin/members/index.html.haml
Normal file
15
app/views/admin/members/index.html.haml
Normal file
@@ -0,0 +1,15 @@
|
||||
.pagination
|
||||
= page_entries_info @members
|
||||
= will_paginate @members
|
||||
|
||||
|
||||
%table.table.table-striped
|
||||
%tr
|
||||
%th Name
|
||||
%th Email
|
||||
%th
|
||||
%th
|
||||
- @members.each do |member|
|
||||
%tr
|
||||
%td= ember.login_name
|
||||
%td= member.email
|
||||
@@ -48,15 +48,4 @@
|
||||
:growstuff_markdown
|
||||
#{ @harvest.description != "" ? strip_tags(@harvest.description) : "No description given." }
|
||||
|
||||
- if !@harvest.photos.empty? || (can?(:edit, @harvest) && can?(:create, Photo))
|
||||
%h2 Pictures
|
||||
|
||||
%ul.thumbnails
|
||||
- @harvest.photos.each do |p|
|
||||
.col-md-2.six-across
|
||||
= render partial: 'photos/thumbnail', locals: { photo: p }
|
||||
- if can?(:create, Photo) && can?(:edit, @harvest)
|
||||
.col-md-2
|
||||
.thumbnail{ style: 'height: 220px' }
|
||||
%p{ style: 'text-align: center; padding-top: 50px' }
|
||||
= link_to "Add photo", new_photo_path(type: "harvest", id: @harvest.id), class: 'btn btn-primary'
|
||||
= render 'photos/item_photos', item: @harvest, type: 'harvest', photos: @photos
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
.col-md-4.hidden-xs
|
||||
- cache cache_key_for(Planting) do
|
||||
%h2= t('.recently_planted')
|
||||
= render partial: 'plantings/list', locals: { plantings: Planting.includes(:owner, :photos).interesting.first(6) }
|
||||
= render 'plantings/list', plantings: Planting.includes(:owner, :photos).interesting.recent.first(6)
|
||||
|
||||
.row
|
||||
.col-md-12
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
- posts = Post.limit(6)
|
||||
- if posts
|
||||
= render partial: "posts/summary", locals: { posts: posts, howmany: 6 }
|
||||
= render "posts/summary", posts: posts, howmany: 6
|
||||
|
||||
- cache cache_key_for(Forum) do
|
||||
- forums = Forum.all.order(:name)
|
||||
|
||||
16
app/views/photos/_item_photos.haml
Normal file
16
app/views/photos/_item_photos.haml
Normal file
@@ -0,0 +1,16 @@
|
||||
- if photos.size.positive? || (can?(:edit, item) && can?(:create, Photo))
|
||||
%h2 Photos
|
||||
- if photos.size.positive?
|
||||
.row
|
||||
.pagination
|
||||
= page_entries_info photos
|
||||
= will_paginate photos
|
||||
.row
|
||||
- photos.each do |photo|
|
||||
.col-md-2.six-across= render 'photos/thumbnail', photo: photo
|
||||
- if can?(:create, Photo) && can?(:edit, item)
|
||||
.col-md-2
|
||||
.thumbnail
|
||||
= link_to new_photo_path(type: type, id: item.id), class: 'btn btn-primary' do
|
||||
%span.glyphicon.glyphicon-camera{ title: "Add photo" }
|
||||
Add photo
|
||||
@@ -83,15 +83,4 @@
|
||||
:growstuff_markdown
|
||||
#{ @planting.description != "" ? strip_tags(@planting.description) : "No description given." }
|
||||
|
||||
- if !@planting.photos.empty? || (can?(:edit, @planting) && can?(:create, Photo))
|
||||
%h2 Photos
|
||||
|
||||
.row
|
||||
- @planting.photos.includes(:owner).each do |p|
|
||||
.col-md-2.six-across
|
||||
= render partial: 'photos/thumbnail', locals: { photo: p }
|
||||
- if can?(:create, Photo) && can?(:edit, @planting)
|
||||
.col-md-2
|
||||
.thumbnail{ style: 'height: 220px' }
|
||||
%p{ style: 'text-align: center; padding-top: 50px' }
|
||||
= link_to "Add photo", new_photo_path(type: "planting", id: @planting.id), class: 'btn btn-primary'
|
||||
= render 'photos/item_photos', item: @planting, type: 'planting', photos: @photos
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
= form_for @role do |f|
|
||||
- if @role.errors.any?
|
||||
#error_explanation
|
||||
%h2
|
||||
= pluralize(@role.errors.size, "error")
|
||||
prohibited this role from being saved:
|
||||
%ul
|
||||
- @role.errors.full_messages.each do |msg|
|
||||
%li= msg
|
||||
.row
|
||||
- if @role.errors.any?
|
||||
#error_explanation
|
||||
%h2
|
||||
= pluralize(@role.errors.size, "error")
|
||||
prohibited this role from being saved:
|
||||
%ul
|
||||
- @role.errors.full_messages.each do |msg|
|
||||
%li= msg
|
||||
|
||||
.field
|
||||
= f.label :name
|
||||
= f.text_field :name
|
||||
.col-md2= f.label :name
|
||||
.col-md10= f.text_field :name
|
||||
.field
|
||||
= f.label :description
|
||||
= f.text_area :description
|
||||
.col-md2= f.label :description
|
||||
.col-md10= f.text_area :description
|
||||
.actions
|
||||
= f.submit 'Save'
|
||||
= f.submit 'Save', class: 'btn btn-default'
|
||||
|
||||
@@ -2,6 +2,4 @@
|
||||
|
||||
= render 'form'
|
||||
|
||||
= link_to 'Show', @role
|
||||
\|
|
||||
= link_to 'Back', roles_path
|
||||
|
||||
@@ -3,17 +3,16 @@
|
||||
- if can? :create, Role
|
||||
%p= link_to 'New Role', new_role_path, class: 'btn btn-primary'
|
||||
|
||||
%table
|
||||
%table.table.table-striped
|
||||
%tr
|
||||
%th Name
|
||||
%th Description
|
||||
%th
|
||||
%th
|
||||
%th
|
||||
|
||||
- @roles.each do |role|
|
||||
%tr
|
||||
%td= link_to role.name, role
|
||||
%td= role.name
|
||||
%td= role.description
|
||||
- if can? :edit, role
|
||||
%td= link_to 'Edit', edit_role_path(role), class: 'btn btn-default btn-xs'
|
||||
|
||||
11
app/views/seeds/_actions.html.haml
Normal file
11
app/views/seeds/_actions.html.haml
Normal file
@@ -0,0 +1,11 @@
|
||||
- if can? :edit, @seed
|
||||
= link_to edit_seed_path(@seed), class: 'btn btn-default btn-xs' do
|
||||
%span.glyphicon.glyphicon-pencil{ title: "Edit" }
|
||||
Edit
|
||||
= link_to new_photo_path(id: seed.id, type: 'seed'), class: 'btn btn-default btn-xs' do
|
||||
%span.glyphicon.glyphicon-camera{ title: "Add photo" }
|
||||
Add photo
|
||||
- if can? :destroy, @seed
|
||||
= link_to @seed, method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-default btn-xs' do
|
||||
%span.glyphicon.glyphicon-trash{ title: "Delete" }
|
||||
Delete
|
||||
@@ -58,15 +58,11 @@
|
||||
subject: "Interested in your #{@seed.crop} seeds"),
|
||||
class: 'btn btn-primary'
|
||||
- else
|
||||
= render partial: 'shared/signin_signup', locals: { to: 'request seeds' }
|
||||
= render 'shared/signin_signup', to: 'request seeds'
|
||||
|
||||
- if can?(:edit, @seed) || can?(:destroy, @seed)
|
||||
%p
|
||||
- if can? :edit, @seed
|
||||
= link_to 'Edit', edit_seed_path(@seed), class: 'btn btn-default btn-xs'
|
||||
- if can? :destroy, @seed
|
||||
= link_to 'Delete', @seed, method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-default btn-xs'
|
||||
|
||||
= render 'actions', seed: @seed
|
||||
.col-md-6
|
||||
= render partial: "crops/index_card", locals: { crop: @seed.crop }
|
||||
- if @seed.owner.location
|
||||
@@ -79,3 +75,5 @@
|
||||
Or
|
||||
= link_to "purchase seeds via Ebay",
|
||||
crop_ebay_seeds_url(@seed.crop), target: "_blank", rel: "noopener noreferrer"
|
||||
|
||||
= render 'photos/item_photos', item: @seed, type: 'seed', photos: @photos
|
||||
|
||||
@@ -90,6 +90,9 @@ Growstuff::Application.routes.draw do
|
||||
get '/shop/:action' => 'shop#:action'
|
||||
|
||||
comfy_route :cms_admin, path: '/admin/cms'
|
||||
namespace :admin do
|
||||
resources :members
|
||||
end
|
||||
get '/admin/orders' => 'admin/orders#index'
|
||||
get '/admin/orders/:action' => 'admin/orders#:action'
|
||||
get '/admin' => 'admin#index'
|
||||
|
||||
54
db/migrate/20171129041341_create_photographings.rb
Normal file
54
db/migrate/20171129041341_create_photographings.rb
Normal file
@@ -0,0 +1,54 @@
|
||||
class CreatePhotographings < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :photographings do |t|
|
||||
t.integer :photo_id, null: false
|
||||
t.integer :photographable_id, null: false
|
||||
t.string :photographable_type, null: false
|
||||
t.timestamps null: false
|
||||
end
|
||||
|
||||
add_foreign_key :photographings, :photos
|
||||
|
||||
add_index :photographings, %i(photographable_id photographable_type photo_id),
|
||||
unique: true, name: 'items_to_photos_idx'
|
||||
add_index :photographings, %i(photographable_id photographable_type),
|
||||
name: 'photographable_idx'
|
||||
|
||||
migrate_data
|
||||
end
|
||||
|
||||
def migrate_data
|
||||
say "migrating garden photos"
|
||||
GardensPhoto.all.each do |g|
|
||||
Photographing.create! photo_id: g.photo_id, photographable_id: g.garden_id, photographable_type: 'Garden'
|
||||
end
|
||||
say "migrating planting photos"
|
||||
PhotosPlanting.all.each do |p|
|
||||
Photographing.create! photo_id: p.photo_id, photographable_id: p.planting_id, photographable_type: 'Planting'
|
||||
end
|
||||
say "migrating harvest photos"
|
||||
HarvestsPhoto.all.each do |h|
|
||||
Photographing.create! photo_id: h.photo_id, photographable_id: h.harvest_id, photographable_type: 'Harvest'
|
||||
end
|
||||
say "migrating seed photos"
|
||||
PhotosSeed.all.each do |s|
|
||||
Photographing.create! photo_id: s.photo_id, photographable_id: s.seed_id, photographable_type: 'Seed'
|
||||
end
|
||||
end
|
||||
class GardensPhoto < ActiveRecord::Base
|
||||
belongs_to :photo
|
||||
belongs_to :garden
|
||||
end
|
||||
class PhotosPlanting < ActiveRecord::Base
|
||||
belongs_to :photo
|
||||
belongs_to :planting
|
||||
end
|
||||
class HarvestsPhoto < ActiveRecord::Base
|
||||
belongs_to :photo
|
||||
belongs_to :harvest
|
||||
end
|
||||
class PhotosSeed < ActiveRecord::Base
|
||||
belongs_to :photo
|
||||
belongs_to :seed
|
||||
end
|
||||
end
|
||||
14
db/schema.rb
14
db/schema.rb
@@ -11,7 +11,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20171105011017) do
|
||||
ActiveRecord::Schema.define(version: 20171129041341) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
@@ -383,6 +383,17 @@ ActiveRecord::Schema.define(version: 20171105011017) do
|
||||
t.integer "product_id"
|
||||
end
|
||||
|
||||
create_table "photographings", force: :cascade do |t|
|
||||
t.integer "photo_id", null: false
|
||||
t.integer "photographable_id", null: false
|
||||
t.string "photographable_type", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
add_index "photographings", ["photographable_id", "photographable_type", "photo_id"], name: "items_to_photos_idx", unique: true, using: :btree
|
||||
add_index "photographings", ["photographable_id", "photographable_type"], name: "photographable_idx", using: :btree
|
||||
|
||||
create_table "photos", force: :cascade do |t|
|
||||
t.integer "owner_id", null: false
|
||||
t.string "thumbnail_url", null: false
|
||||
@@ -498,4 +509,5 @@ ActiveRecord::Schema.define(version: 20171105011017) do
|
||||
add_index "seeds", ["slug"], name: "index_seeds_on_slug", unique: true, using: :btree
|
||||
|
||||
add_foreign_key "harvests", "plantings"
|
||||
add_foreign_key "photographings", "photos"
|
||||
end
|
||||
|
||||
@@ -4,12 +4,12 @@ FactoryBot.define do
|
||||
factory :photo do
|
||||
owner
|
||||
flickr_photo_id 1
|
||||
title "Still life with chillies"
|
||||
title { Faker::HarryPotter.quote }
|
||||
license_name "CC-BY"
|
||||
license_url "http://example.com/license.html"
|
||||
thumbnail_url "http://example.com/thumb.jpg"
|
||||
fullsize_url "http://example.com/full.jpg"
|
||||
link_url "http://example.com/"
|
||||
thumbnail_url { "http://example.com/#{Faker::File.file_name}.jpg" }
|
||||
fullsize_url { "http://example.com/#{Faker::File.file_name}.jpg" }
|
||||
link_url { Faker::Internet.url }
|
||||
|
||||
factory :unlicensed_photo do
|
||||
license_name "All rights reserved"
|
||||
|
||||
@@ -13,7 +13,7 @@ feature "forums", js: true do
|
||||
visit root_path
|
||||
click_link "Admin"
|
||||
expect(current_path).to eq admin_path
|
||||
within 'ul#admin_links' do
|
||||
within 'ul#site_admin' do
|
||||
click_link "Forums"
|
||||
end
|
||||
expect(current_path).to eq forums_path
|
||||
@@ -25,7 +25,7 @@ feature "forums", js: true do
|
||||
click_link member.login_name
|
||||
click_link "Admin"
|
||||
expect(current_path).to eq admin_path
|
||||
within 'ul#admin_links' do
|
||||
within 'ul#site_admin' do
|
||||
click_link "Forums"
|
||||
end
|
||||
expect(current_path).to eq forums_path
|
||||
|
||||
@@ -2,27 +2,31 @@ require 'rails_helper'
|
||||
|
||||
feature "crop detail page", js: true do
|
||||
let(:member) { create :member }
|
||||
|
||||
let(:crop) { create :crop, plantings: [planting], harvests: [harvest] }
|
||||
let(:planting) { create :planting, owner: member, photos: [photo1, photo2] }
|
||||
let(:harvest) { create :harvest, owner: member, photos: [photo3, photo4] }
|
||||
let(:planting) { create :planting, owner: member }
|
||||
let(:harvest) { create :harvest, owner: member }
|
||||
|
||||
let(:photo1) do
|
||||
create(:photo, owner: member, title: 'photo 1',
|
||||
fullsize_url: 'photo1.jpg', thumbnail_url: 'thumb1.jpg')
|
||||
create(:photo, owner: member, title: 'photo 1', fullsize_url: 'photo1.jpg', thumbnail_url: 'thumb1.jpg')
|
||||
end
|
||||
let(:photo2) do
|
||||
create(:photo, owner: member, title: 'photo 2',
|
||||
fullsize_url: 'photo2.jpg', thumbnail_url: 'thumb2.jpg')
|
||||
create(:photo, owner: member, title: 'photo 2', fullsize_url: 'photo2.jpg', thumbnail_url: 'thumb2.jpg')
|
||||
end
|
||||
let(:photo3) do
|
||||
create(:photo, owner: member, title: 'photo 3',
|
||||
fullsize_url: 'photo3.jpg', thumbnail_url: 'thumb3.jpg')
|
||||
create(:photo, owner: member, title: 'photo 3', fullsize_url: 'photo3.jpg', thumbnail_url: 'thumb3.jpg')
|
||||
end
|
||||
let(:photo4) do
|
||||
create(:photo, owner: member, title: 'photo 4',
|
||||
fullsize_url: 'photo4.jpg', thumbnail_url: 'thumb4.jpg')
|
||||
create(:photo, owner: member, title: 'photo 4', fullsize_url: 'photo4.jpg', thumbnail_url: 'thumb4.jpg')
|
||||
end
|
||||
|
||||
before { visit crop_path(crop) }
|
||||
before do
|
||||
planting.photos << photo1
|
||||
planting.photos << photo2
|
||||
harvest.photos << photo3
|
||||
harvest.photos << photo4
|
||||
visit crop_path(crop)
|
||||
end
|
||||
subject { page }
|
||||
|
||||
shared_examples "shows photos" do
|
||||
|
||||
41
spec/features/seeds/seed_photos.rb
Normal file
41
spec/features/seeds/seed_photos.rb
Normal file
@@ -0,0 +1,41 @@
|
||||
require 'rails_helper'
|
||||
require 'custom_matchers'
|
||||
|
||||
feature "Seeds", :js do
|
||||
let(:member) { FactoryBot.create :member }
|
||||
let!(:seed) { FactoryBot.create :seed, owner: member }
|
||||
|
||||
subject do
|
||||
login_as member
|
||||
visit seed_path(seed)
|
||||
page
|
||||
end
|
||||
|
||||
it { is_expected.to have_content 'Add photo' }
|
||||
|
||||
# context 'no photos' do
|
||||
# it { is_expected.to have_content 'no photos' }
|
||||
# end
|
||||
context 'has one photo' do
|
||||
before { seed.photos = [photo] }
|
||||
let!(:photo) { FactoryBot.create :photo, title: 'hello photo' }
|
||||
it { is_expected.to have_xpath("//img[contains(@src,'#{photo.thumbnail_url}')]") }
|
||||
it { is_expected.to have_xpath("//a[contains(@href,'#{photo_path(photo)}')]") }
|
||||
end
|
||||
context 'has 50 photos' do
|
||||
before { seed.photos = photos }
|
||||
let!(:photos) { FactoryBot.create_list :photo, 50 }
|
||||
it "shows newest photo" do
|
||||
is_expected.to have_xpath("//img[contains(@src,'#{photos.last.thumbnail_url}')]")
|
||||
end
|
||||
it "links to newest photo" do
|
||||
is_expected.to have_xpath("//a[contains(@href,'#{photo_path(photos.last)}')]")
|
||||
end
|
||||
it "does not show oldest photo" do
|
||||
is_expected.not_to have_xpath("//img[contains(@src,'#{photos.first.thumbnail_url}')]")
|
||||
end
|
||||
it "does not link to oldest photo" do
|
||||
is_expected.not_to have_xpath("//a[contains(@href,'#{photo_path(photos.first)}')]")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -269,78 +269,55 @@ describe Crop do
|
||||
end
|
||||
|
||||
context 'interesting' do
|
||||
it 'lists interesting crops' do
|
||||
# first, a couple of candidate crops
|
||||
@crop1 = FactoryBot.create(:crop)
|
||||
@crop2 = FactoryBot.create(:crop)
|
||||
let(:photo) { FactoryBot.create :photo }
|
||||
# first, a couple of candidate crops
|
||||
let(:crop1) { FactoryBot.create(:crop) }
|
||||
let(:crop2) { FactoryBot.create(:crop) }
|
||||
|
||||
# they need 3+ plantings each to be interesting
|
||||
3.times do
|
||||
FactoryBot.create(:planting, crop: @crop1)
|
||||
end
|
||||
3.times do
|
||||
FactoryBot.create(:planting, crop: @crop2)
|
||||
let(:crop1_planting) { crop1.plantings.first }
|
||||
let(:crop2_planting) { crop2.plantings.first }
|
||||
|
||||
subject { Crop.interesting }
|
||||
|
||||
describe 'lists interesting crops' do
|
||||
before do
|
||||
# they need 3+ plantings each to be interesting
|
||||
FactoryBot.create_list(:planting, 3, crop: crop1)
|
||||
FactoryBot.create_list(:planting, 3, crop: crop2)
|
||||
# crops need 3+ photos to be interesting
|
||||
crop1_planting.photos = FactoryBot.create_list :photo, 3
|
||||
crop2_planting.photos = FactoryBot.create_list :photo, 3
|
||||
end
|
||||
|
||||
# crops need 3+ photos to be interesting
|
||||
@photo = FactoryBot.create(:photo)
|
||||
[@crop1, @crop2].each do |c|
|
||||
3.times do
|
||||
c.plantings.first.photos << @photo
|
||||
c.plantings.first.save
|
||||
end
|
||||
end
|
||||
|
||||
Crop.interesting.should include @crop1
|
||||
Crop.interesting.should include @crop2
|
||||
Crop.interesting.size.should == 2
|
||||
it { is_expected.to include crop1 }
|
||||
it { is_expected.to include crop2 }
|
||||
it { expect(subject.size).to eq 2 }
|
||||
end
|
||||
|
||||
it 'ignores crops without plantings' do
|
||||
# first, a couple of candidate crops
|
||||
@crop1 = FactoryBot.create(:crop)
|
||||
@crop2 = FactoryBot.create(:crop)
|
||||
|
||||
# only crop1 has plantings
|
||||
3.times do
|
||||
FactoryBot.create(:planting, crop: @crop1)
|
||||
describe 'crops without plantings are not interesting' do
|
||||
before do
|
||||
# only crop1 has plantings
|
||||
FactoryBot.create_list(:planting, 3, crop: crop1)
|
||||
# ... and photos
|
||||
crop1_planting.photos = FactoryBot.create_list(:photo, 3)
|
||||
end
|
||||
|
||||
# ... and photos
|
||||
@photo = FactoryBot.create(:photo)
|
||||
3.times do
|
||||
@crop1.plantings.first.photos << @photo
|
||||
@crop1.plantings.first.save
|
||||
end
|
||||
|
||||
Crop.interesting.should include @crop1
|
||||
Crop.interesting.should_not include @crop2
|
||||
Crop.interesting.size.should == 1
|
||||
it { is_expected.to include crop1 }
|
||||
it { is_expected.not_to include crop2 }
|
||||
it { expect(subject.size).to eq 1 }
|
||||
end
|
||||
|
||||
it 'ignores crops without photos' do
|
||||
# first, a couple of candidate crops
|
||||
@crop1 = FactoryBot.create(:crop)
|
||||
@crop2 = FactoryBot.create(:crop)
|
||||
describe 'crops without photos are not interesting' do
|
||||
before do
|
||||
# both crops have plantings
|
||||
FactoryBot.create_list(:planting, 3, crop: crop1)
|
||||
FactoryBot.create_list(:planting, 3, crop: crop2)
|
||||
|
||||
# both crops have plantings
|
||||
3.times do
|
||||
FactoryBot.create(:planting, crop: @crop1)
|
||||
# but only crop1 has photos
|
||||
crop1_planting.photos = FactoryBot.create_list(:photo, 3)
|
||||
end
|
||||
3.times do
|
||||
FactoryBot.create(:planting, crop: @crop2)
|
||||
end
|
||||
|
||||
# but only crop1 has photos
|
||||
@photo = FactoryBot.create(:photo)
|
||||
3.times do
|
||||
@crop1.plantings.first.photos << @photo
|
||||
@crop1.plantings.first.save
|
||||
end
|
||||
|
||||
Crop.interesting.should include @crop1
|
||||
Crop.interesting.should_not include @crop2
|
||||
Crop.interesting.size.should == 1
|
||||
it { is_expected.to include crop1 }
|
||||
it { is_expected.not_to include crop2 }
|
||||
it { expect(subject.size).to eq 1 }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -349,34 +326,20 @@ describe Crop do
|
||||
let(:pp2) { FactoryBot.create(:plant_part) }
|
||||
|
||||
context "harvests" do
|
||||
let(:h1) do
|
||||
FactoryBot.create(:harvest,
|
||||
crop: maize,
|
||||
plant_part: pp1)
|
||||
end
|
||||
|
||||
let(:h2) do
|
||||
FactoryBot.create(:harvest,
|
||||
crop: maize,
|
||||
plant_part: pp2)
|
||||
end
|
||||
|
||||
let(:h1) { FactoryBot.create(:harvest, crop: maize, plant_part: pp1) }
|
||||
let(:h2) { FactoryBot.create(:harvest, crop: maize, plant_part: pp2) }
|
||||
let!(:crop) { FactoryBot.create(:crop) }
|
||||
let!(:harvest) { FactoryBot.create(:harvest, crop: crop) }
|
||||
it "has harvests" do
|
||||
crop = FactoryBot.create(:crop)
|
||||
harvest = FactoryBot.create(:harvest, crop: crop)
|
||||
crop.harvests.should eq [harvest]
|
||||
expect(crop.harvests).to eq [harvest]
|
||||
end
|
||||
end
|
||||
|
||||
it "doesn't duplicate plant_parts" do
|
||||
@maize = FactoryBot.create(:maize)
|
||||
@pp1 = FactoryBot.create(:plant_part)
|
||||
@h1 = FactoryBot.create(:harvest,
|
||||
crop: @maize,
|
||||
plant_part: @pp1)
|
||||
@h2 = FactoryBot.create(:harvest,
|
||||
crop: @maize,
|
||||
plant_part: @pp1)
|
||||
@h1 = FactoryBot.create(:harvest, crop: @maize, plant_part: @pp1)
|
||||
@h2 = FactoryBot.create(:harvest, crop: @maize, plant_part: @pp1)
|
||||
@maize.plant_parts.should eq [@pp1]
|
||||
end
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ describe "harvests/show" do
|
||||
before do
|
||||
controller.stub(:current_user) { nil }
|
||||
assign(:harvest, harvest)
|
||||
assign(:photos, harvest.photos.paginate(page: 1))
|
||||
render
|
||||
end
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ describe "plantings/show" do
|
||||
|
||||
before(:each) do
|
||||
assign(:planting, planting)
|
||||
assign(:photos, planting.photos.paginate(page: 1))
|
||||
controller.stub(:current_user) { member }
|
||||
end
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ describe "seeds/show" do
|
||||
controller.stub(:current_user) { nil }
|
||||
@seed = FactoryBot.create(:seed)
|
||||
assign(:seed, @seed)
|
||||
assign(:photos, @seed.photos.paginate(page: 1))
|
||||
end
|
||||
|
||||
it "renders attributes in <p>" do
|
||||
|
||||
Reference in New Issue
Block a user