This commit is contained in:
Taylor Griffin
2014-10-16 06:39:10 +11:00
61 changed files with 674 additions and 149 deletions

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -69,6 +69,7 @@ class CropsController < ApplicationController
# GET /crops/1.json
def show
@crop = Crop.includes(:scientific_names, {:plantings => :photos}).find(params[:id])
@posts = @crop.posts.paginate(:page => params[:page])
respond_to do |format|
format.html # show.html.haml

View File

@@ -6,23 +6,21 @@ class HarvestsController < ApplicationController
# GET /harvests.json
def index
@owner = Member.find_by_slug(params[:owner])
@crop = Crop.find_by_slug(params[:crop])
if @owner
@harvests = @owner.harvests.includes(:owner, :crop).paginate(:page => params[:page])
@harvests = @owner.harvests.includes(:owner, :crop)
elsif @crop
@harvests = @crop.harvests.includes(:owner, :crop)
else
@harvests = Harvest.includes(:owner, :crop).paginate(:page => params[:page])
@harvests = Harvest.includes(:owner, :crop)
end
respond_to do |format|
format.html # index.html.erb
format.html { @harvests = @harvests.paginate(:page => params[:page]) }
format.json { render json: @harvests }
format.csv do
if @owner
@filename = "Growstuff-#{@owner}-Harvests-#{Time.zone.now.to_s(:number)}.csv"
@harvests = @owner.harvests.includes(:owner, :crop)
else
@filename = "Growstuff-Harvests-#{Time.zone.now.to_s(:number)}.csv"
@harvests = Harvest.includes(:owner, :crop)
end
specifics = (@owner ? "#{@owner.name}-" : @crop ? "#{@crop.name}-" : nil)
@filename = "Growstuff-#{specifics}Harvests-#{Time.zone.now.to_s(:number)}.csv"
render :csv => @harvests
end
end

View File

@@ -29,7 +29,8 @@ class PhotosController < ApplicationController
# GET /photos/new.json
def new
@photo = Photo.new
@planting_id = params[:planting_id]
@type = params[:type]
@id = params[:id]
page = params[:page] || 1
@@ -63,19 +64,35 @@ class PhotosController < ApplicationController
@photo.owner_id = current_member.id
@photo.set_flickr_metadata
if params[:planting_id]
planting = Planting.find_by_id(params[:planting_id])
if planting
if planting.owner.id == current_member.id
@photo.plantings << planting unless @photo.plantings.include?(planting)
# several models can have photos. we need to know what model and the id
# for the entry to attach the photo to
valid_models = ["planting", "harvest"]
if params[:type]
if valid_models.include?(params[:type])
if params[:id]
item = params[:type].camelcase.constantize.find_by_id(params[:id])
if item
if item.owner.id == current_member.id
# This syntax is weird, so just know that it means this:
# @photo.harvests << item unless @photo.harvests.include?(item)
# but with the correct many-to-many relationship automatically referenced
(@photo.send "#{params[:type]}s") << item unless (@photo.send "#{params[:type]}s").include?(item)
else
flash[:alert] = "You must own both the #{params[:type]} and the photo."
end
else
flash[:alert] = "Couldn't find #{params[:type]} to connect to photo."
end
else
flash[:alert] = "You must own both the planting and the photo."
flash[:alert] = "Missing id parameter"
end
else
flash[:alert] = "Couldn't find planting to connect to photo."
flash[:alert] = "Cannot attach photos to #{params[:type]}"
end
else
flash[:alert] = "Missing type parameter"
end
respond_to do |format|
if @photo.save
format.html { redirect_to @photo, notice: 'Photo was successfully added.' }

View File

@@ -7,24 +7,22 @@ class PlantingsController < ApplicationController
# GET /plantings.json
def index
@owner = Member.find_by_slug(params[:owner])
@crop = Crop.find_by_slug(params[:crop])
if @owner
@plantings = @owner.plantings.includes(:owner, :crop, :garden).paginate(:page => params[:page])
@plantings = @owner.plantings.includes(:owner, :crop, :garden)
elsif @crop
@plantings = @crop.plantings.includes(:owner, :crop, :garden)
else
@plantings = Planting.includes(:owner, :crop, :garden).paginate(:page => params[:page])
@plantings = Planting.includes(:owner, :crop, :garden)
end
respond_to do |format|
format.html # index.html.erb
format.html { @plantings = @plantings.paginate(:page => params[:page]) }
format.json { render json: @plantings }
format.rss { render :layout => false } #index.rss.builder
format.csv do
if @owner
@filename = "Growstuff-#{@owner}-Plantings-#{Time.zone.now.to_s(:number)}.csv"
@plantings = @owner.plantings.includes(:owner, :crop, :garden)
else
@filename = "Growstuff-Plantings-#{Time.zone.now.to_s(:number)}.csv"
@plantings = Planting.includes(:owner, :crop, :garden)
end
specifics = (@owner ? "#{@owner.name}-" : @crop ? "#{@crop.name}-" : nil)
@filename = "Growstuff-#{specifics}Plantings-#{Time.zone.now.to_s(:number)}.csv"
render :csv => @plantings
end
end

View File

@@ -1,2 +0,0 @@
module PlantPartsHelper
end

View File

@@ -1,2 +0,0 @@
module SeedsHelper
end

View File

@@ -17,6 +17,9 @@ class Crop < ActiveRecord::Base
belongs_to :parent, :class_name => 'Crop'
has_many :varieties, :class_name => 'Crop', :foreign_key => 'parent_id'
has_and_belongs_to_many :posts
before_destroy {|crop| crop.posts.clear}
default_scope order("lower(name) asc")
scope :recent, reorder("created_at desc")

View File

@@ -9,6 +9,17 @@ class Harvest < ActiveRecord::Base
belongs_to :owner, :class_name => 'Member'
belongs_to :plant_part
has_and_belongs_to_many :photos
before_destroy do |harvest|
photolist = harvest.photos.to_a # save a temp copy of the photo list
harvest.photos.clear # clear relationship b/w harvest and photo
photolist.each do |photo|
photo.destroy_if_unused
end
end
default_scope order('created_at DESC')
validates :crop, :presence => {:message => "must be present and exist in our database"}
@@ -72,4 +83,8 @@ class Harvest < ActiveRecord::Base
"#{owner.login_name}-#{crop}".downcase.gsub(' ', '-')
end
def default_photo
return photos.first
end
end

View File

@@ -4,10 +4,21 @@ class Photo < ActiveRecord::Base
belongs_to :owner, :class_name => 'Member'
has_and_belongs_to_many :plantings
before_destroy {|photo| photo.plantings.clear}
has_and_belongs_to_many :harvests
before_destroy do |photo|
photo.plantings.clear
photo.harvests.clear
end
default_scope order("created_at desc")
# remove photos that aren't used by anything
def destroy_if_unused
unless plantings.size > 0 and harvests.size > 0
self.destroy
end
end
# This is split into a side-effect free method and a side-effecting method
# for easier stubbing and testing.
def flickr_metadata

View File

@@ -11,7 +11,15 @@ class Planting < ActiveRecord::Base
belongs_to :crop, :counter_cache => true
has_and_belongs_to_many :photos
before_destroy {|planting| planting.photos.clear}
before_destroy do |planting|
photolist = planting.photos.to_a # save a temp copy of the photo list
planting.photos.clear # clear relationship b/w planting and photo
photolist.each do |photo|
photo.destroy_if_unused
end
end
default_scope order("created_at desc")
scope :finished, where(:finished => true)

View File

@@ -5,6 +5,9 @@ class Post < ActiveRecord::Base
belongs_to :author, :class_name => 'Member'
belongs_to :forum
has_many :comments, :dependent => :destroy
has_and_belongs_to_many :crops
before_destroy {|post| post.crops.clear}
after_save :update_crops_posts_association
# also has_many notifications, but kinda meaningless to get at them
# from this direction, so we won't set up an association for now.
@@ -39,4 +42,15 @@ class Post < ActiveRecord::Base
end
end
private
def update_crops_posts_association
self.crops.destroy_all
# look for crops mentioned in the post. eg. [tomato](crop)
self.body.scan(Haml::Filters::GrowstuffMarkdown::CROP_REGEX) do |m|
# find crop case-insensitively
crop = Crop.where('lower(name) = ?', $1.downcase).first
# create association
self.crops << crop if crop and not self.crops.include?(crop)
end
end
end

View File

@@ -4,16 +4,18 @@
Nobody has harvested this crop yet.
- else
%ul
- crop.harvests.each do |harvest|
- crop.harvests.take(3).each do |harvest|
%li
= link_to "#{harvest.owner} harvested #{display_quantity(harvest)}.", harvest_path(harvest)
= render :partial => 'members/location', :locals => { :member => harvest.owner }
%small
= distance_of_time_in_words(harvest.created_at, Time.zone.now)
ago.
%p.col-md-offset-1
= link_to "See all #{crop.name} harvests", harvests_by_crop_path(crop)
- if current_member
= link_to "Track your #{crop.name} harvests.", new_harvest_path()
%p.col-md-offset-1
= link_to "Track your #{crop.name} harvests.", new_harvest_path(:crop_id => crop.id)
- else
= render :partial => 'shared/signin_signup', :locals => { :to => "track your #{crop.name} harvests" }

View File

@@ -0,0 +1,21 @@
%h4 Plantings
- if crop.plantings.empty?
%p
Nobody has planted this crop yet.
- else
%ul
- crop.plantings.take(3).each do |planting|
%li
= link_to "#{planting.owner} planted #{planting.quantity} #{planting.planted_from}.", planting_path(planting)
= render :partial => 'members/location', :locals => { :member => planting.owner }
%small
= distance_of_time_in_words(planting.created_at, Time.zone.now)
ago.
%p.col-md-offset-1
= link_to "See all #{crop.name} plantings", plantings_by_crop_path(crop)
- if current_member
%p.col-md-offset-1
= link_to "Track your #{crop.name} plantings.", new_planting_path(:crop_id => crop.id)
- else
= render :partial => 'shared/signin_signup', :locals => { :to => "track your #{crop.name} plantings" }

View File

@@ -33,12 +33,18 @@
%div#cropmap
- if @crop.plantings.size > 0
%a{:name => 'posts'}
%div.pagination
= page_entries_info @posts, :model => "posts"
= will_paginate @posts, :params => {:anchor => "posts"}
%h2 All plantings
- unless @posts.empty?
- @posts.each do |post|
= render :partial => "posts/single", :locals => { :post => post, :subject => true }
- @crop.plantings.each do |p|
= render :partial => "plantings/thumbnail", :locals => { :planting => p, :title => 'owner' }
%div.pagination
= page_entries_info @posts, :model => "posts"
= will_paginate @posts, :params => {:anchor => "posts"}
.col-md-3
- if can? :edit, @crop or can? :destroy, @crop
@@ -76,5 +82,6 @@
%ul
%li= link_to 'Wikipedia (English)', @crop.en_wikipedia_url
= render :partial => 'plantings', :locals => { :crop => @crop }
= render :partial => 'harvests', :locals => { :crop => @crop }
= render :partial => 'find_seeds', :locals => { :crop => @crop }

View File

@@ -1,4 +1,4 @@
- content_for :title, @owner ? "#{@owner}'s harvests" : "Everyone's harvests"
- content_for :title, @owner ? "#{@owner}'s harvests" : @crop ? "Everyone's #{@crop.name} harvests" : "Everyone's harvests"
%p
#{ENV['GROWSTUFF_SITE_NAME']} helps you track what you're

View File

@@ -34,3 +34,16 @@
:growstuff_markdown
#{ @harvest.description != "" ? @harvest.description : "No description given." }
- if @harvest.photos.count > 0 or (can? :edit, @harvest and 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 and 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'

View File

@@ -15,7 +15,8 @@
= form_tag(new_photo_path, :method => :get, :class => 'form-inline') do
= label_tag :set, "Choose a photo set:", :class => 'control-label'
= select_tag :set, options_for_select(@sets, @current_set), :class => 'input-large'
= hidden_field_tag :planting_id, @planting_id
= hidden_field_tag :type, @type
= hidden_field_tag :id, @id
= submit_tag "Search", :class => 'btn btn-primary'
%div.pagination
@@ -26,7 +27,7 @@
- @photos.each do |p|
.col-md-2.six-across
.thumbnail(style='height: 220px')
= link_to image_tag(FlickRaw.url_q(p), :alt => '', :class => 'img-rounded'), photos_path(:photo => { :flickr_photo_id => p.id }, :planting_id => @planting_id), :method => :post
= link_to image_tag(FlickRaw.url_q(p), :alt => '', :class => 'img-rounded'), photos_path(:photo => { :flickr_photo_id => p.id }, :type => @type, :id => @id), :method => :post
%p
=p.title

View File

@@ -1,4 +1,4 @@
- content_for :title, @owner ? "#{@owner}'s plantings" : "Everyone's plantings"
- content_for :title, @owner ? "#{@owner}'s plantings" : @crop ? "Everyone's #{@crop.name} plantings" : "Everyone's plantings"
%p
- if can? :create, Planting

View File

@@ -68,4 +68,4 @@
.col-md-2
.thumbnail(style='height: 220px')
%p{:style => 'text-align: center; padding-top: 50px'}
= link_to "Add photo", new_photo_path(:planting_id => @planting.id), :class => 'btn btn-primary'
= link_to "Add photo", new_photo_path(:type => "planting", :id => @planting.id), :class => 'btn btn-primary'

View File

@@ -67,8 +67,12 @@ module Growstuff
config.assets.initialize_on_precompile = true
config.generators do |g|
g.template_engine :haml
g.stylesheets false
g.template_engine :haml
g.view_specs false
g.controller_specs false
g.helper false
g.stylesheets false
g.javascripts false
end
config.action_mailer.delivery_method = :sendmail

View File

@@ -12,6 +12,7 @@ Growstuff::Application.routes.draw do
resources :plantings
match '/plantings/owner/:owner' => 'plantings#index', :as => 'plantings_by_owner'
match '/plantings/crop/:crop' => 'plantings#index', :as => 'plantings_by_crop'
resources :gardens
match '/gardens/owner/:owner' => 'gardens#index', :as => 'gardens_by_owner'
@@ -21,6 +22,7 @@ Growstuff::Application.routes.draw do
resources :harvests
match '/harvests/owner/:owner' => 'harvests#index', :as => 'harvests_by_owner'
match '/harvests/crop/:crop' => 'harvests#index', :as => 'harvests_by_crop'
resources :posts
match '/posts/author/:author' => 'posts#index', :as => 'posts_by_author'

View File

@@ -0,0 +1,8 @@
class AddHarvestsPhotosTable < ActiveRecord::Migration
def change
create_table :harvests_photos, :id => false do |t|
t.integer :photo_id
t.integer :harvest_id
end
end
end

View File

@@ -0,0 +1,10 @@
class AddCropsPostsTable < ActiveRecord::Migration
def change
create_table :crops_posts, :id => false do |t|
t.integer :crop_id
t.integer :post_id
end
add_index :crops_posts, [:crop_id, :post_id]
add_index :crops_posts, :crop_id
end
end

View File

@@ -0,0 +1,5 @@
class CreateIndexHarvestPhotos < ActiveRecord::Migration
def change
add_index(:harvests_photos, [:harvest_id, :photo_id])
end
end

View File

@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20140829230600) do
ActiveRecord::Schema.define(:version => 20141002022459) do
create_table "account_types", :force => true do |t|
t.string "name", :null => false
@@ -64,6 +64,14 @@ ActiveRecord::Schema.define(:version => 20140829230600) do
add_index "crops", ["name"], :name => "index_crops_on_name"
add_index "crops", ["slug"], :name => "index_crops_on_slug", :unique => true
create_table "crops_posts", :id => false, :force => true do |t|
t.integer "crop_id"
t.integer "post_id"
end
add_index "crops_posts", ["crop_id", "post_id"], :name => "index_crops_posts_on_crop_id_and_post_id"
add_index "crops_posts", ["crop_id"], :name => "index_crops_posts_on_crop_id"
create_table "forums", :force => true do |t|
t.string "name", :null => false
t.text "description", :null => false
@@ -108,6 +116,13 @@ ActiveRecord::Schema.define(:version => 20140829230600) do
t.integer "plant_part_id"
end
create_table "harvests_photos", :id => false, :force => true do |t|
t.integer "photo_id"
t.integer "harvest_id"
end
add_index "harvests_photos", ["harvest_id", "photo_id"], :name => "index_harvests_photos_on_harvest_id_and_photo_id"
create_table "members", :force => true do |t|
t.string "email", :default => "", :null => false
t.string "encrypted_password", :default => "", :null => false

6
db/seeds/README.md Normal file
View File

@@ -0,0 +1,6 @@
The format for these crop seed files is CSV with the following fields:
Crop name
Scientific name
English Wikipedia URL
Parent crop name

View File

@@ -0,0 +1,41 @@
adoration tomato,,https://en.wikipedia.org/wiki/Adoration_%28Tomato%29,tomato,
alicante tomato,,https://en.wikipedia.org/wiki/Alicante_%28tomato%29,tomato,
Amish paste tomato,,http://en.wikipedia.org/wiki/Amish_Paste,tomato,
azoychka tomato,,https://en.wikipedia.org/wiki/Azoychka%28Tomato%29,tomato,
beefsteak tomato,,https://en.wikipedia.org/wiki/Beefsteak_(tomato),tomato,
better boy tomato,,https://en.wikipedia.org/wiki/Better_Boy,tomato,
big rainbow tomato,,https://en.wikipedia.org/wiki/Big_Rainbow_(Tomato),tomato,
Blaby special tomato,,https://en.wikipedia.org/wiki/Blaby_Special_(Tomato),tomato,
black krim tomato,,https://en.wikipedia.org/wiki/Black_Krim_%28tomato%29,tomato,
brandywine tomato,,https://en.wikipedia.org/wiki/Brandywine_(tomato),tomato,
campari tomato,,https://en.wikipedia.org/wiki/Campari_tomato,tomato,
Cherokee purple tomato,,https://en.wikipedia.org/wiki/Cherokee_purple,tomato,
currant tomato,solanum pimpinellifolium,https://en.wikipedia.org/wiki/Solanum_pimpinellifolium,
early girl tomato,,https://en.wikipedia.org/wiki/Early_Girl,tomato,
Fourth of July tomato,,https://en.wikipedia.org/wiki/Fourth_of_July_(tomato_variety),tomato,
garden peach tomato,,https://en.wikipedia.org/wiki/Garden_peach_tomato,tomato,
green zebra tomato,,https://en.wikipedia.org/wiki/Green_Zebra,tomato,
hillbilly tomato,,http://en.wikipedia.org/wiki/Hillbilly_(tomato),tomato,
jubilee tomato,,http://en.wikipedia.org/wiki/Jubilee_(tomato),tomato,
lillian's yellow tomato,,http://en.wikipedia.org/wiki/Lillian%27s_Yellow_(tomato),tomato,
Matt's wild cherry tomato,,http://en.wikipedia.org/wiki/Matt%27s_Wild_Cherry,tomato,
mortgage lifter tomato,,http://en.wikipedia.org/wiki/Mortgage_Lifter,tomato,
Mr. Stripey tomato,,http://en.wikipedia.org/wiki/Mr._Stripey,tomato,
Roma tomato,,http://en.wikipedia.org/wiki/Roma_tomato,tomato,
San Marzano tomato,,http://en.wikipedia.org/wiki/San_Marzano_tomato,tomato,
Santorini tomato,,http://en.wikipedia.org/wiki/Santorini_(tomato),tomato,
stupice tomato,,http://en.wikipedia.org/wiki/Super_Sweet_100,tomato,
tigerella tomato,,http://en.wikipedia.org/wiki/Tigerella,tomato,
tomaccio tomato,,http://en.wikipedia.org/wiki/Tomaccio_(tomato),tomato,
traveller tomato,,http://en.wikipedia.org/wiki/Traveller_(tomato),tomato
three sisters tomato,,http://en.wikipedia.org/wiki/Three_Sisters_(tomato),tomato,
Hanover tomato,,http://en.wikipedia.org/wiki/Hanover_tomato,tomato,
celebrity tomato,,http://en.wikipedia.org/wiki/Celebrity_(tomato),tomato,
tomberry,,http://en.wikipedia.org/wiki/Tomberry,tomato,
super sweet 100 tomato,,http://en.wikipedia.org/wiki/Super_Sweet_100,tomato,
marglobe tomato,,http://en.wikipedia.org/wiki/Marglobe,tomato,
grape tomato,,http://en.wikipedia.org/wiki/Grape_tomato,tomato,
cherry tomato,,http://en.wikipedia.org/wiki/Cherry_tomato,tomato,
Aunt Ruby's German green tomato,,http://en.wikipedia.org/wiki/Aunt_Ruby%27s_German_Green,tomato,
white queen tomato,,http://en.wikipedia.org/wiki/White_Queen_tomato,tomato,
pear tomato,,http://en.wikipedia.org/wiki/Pear_tomato,tomato,
1 adoration tomato https://en.wikipedia.org/wiki/Adoration_%28Tomato%29 tomato
2 alicante tomato https://en.wikipedia.org/wiki/Alicante_%28tomato%29 tomato
3 Amish paste tomato http://en.wikipedia.org/wiki/Amish_Paste tomato
4 azoychka tomato https://en.wikipedia.org/wiki/Azoychka%28Tomato%29 tomato
5 beefsteak tomato https://en.wikipedia.org/wiki/Beefsteak_(tomato) tomato
6 better boy tomato https://en.wikipedia.org/wiki/Better_Boy tomato
7 big rainbow tomato https://en.wikipedia.org/wiki/Big_Rainbow_(Tomato) tomato
8 Blaby special tomato https://en.wikipedia.org/wiki/Blaby_Special_(Tomato) tomato
9 black krim tomato https://en.wikipedia.org/wiki/Black_Krim_%28tomato%29 tomato
10 brandywine tomato https://en.wikipedia.org/wiki/Brandywine_(tomato) tomato
11 campari tomato https://en.wikipedia.org/wiki/Campari_tomato tomato
12 Cherokee purple tomato https://en.wikipedia.org/wiki/Cherokee_purple tomato
13 currant tomato solanum pimpinellifolium https://en.wikipedia.org/wiki/Solanum_pimpinellifolium
14 early girl tomato https://en.wikipedia.org/wiki/Early_Girl tomato
15 Fourth of July tomato https://en.wikipedia.org/wiki/Fourth_of_July_(tomato_variety) tomato
16 garden peach tomato https://en.wikipedia.org/wiki/Garden_peach_tomato tomato
17 green zebra tomato https://en.wikipedia.org/wiki/Green_Zebra tomato
18 hillbilly tomato http://en.wikipedia.org/wiki/Hillbilly_(tomato) tomato
19 jubilee tomato http://en.wikipedia.org/wiki/Jubilee_(tomato) tomato
20 lillian's yellow tomato http://en.wikipedia.org/wiki/Lillian%27s_Yellow_(tomato) tomato
21 Matt's wild cherry tomato http://en.wikipedia.org/wiki/Matt%27s_Wild_Cherry tomato
22 mortgage lifter tomato http://en.wikipedia.org/wiki/Mortgage_Lifter tomato
23 Mr. Stripey tomato http://en.wikipedia.org/wiki/Mr._Stripey tomato
24 Roma tomato http://en.wikipedia.org/wiki/Roma_tomato tomato
25 San Marzano tomato http://en.wikipedia.org/wiki/San_Marzano_tomato tomato
26 Santorini tomato http://en.wikipedia.org/wiki/Santorini_(tomato) tomato
27 stupice tomato http://en.wikipedia.org/wiki/Super_Sweet_100 tomato
28 tigerella tomato http://en.wikipedia.org/wiki/Tigerella tomato
29 tomaccio tomato http://en.wikipedia.org/wiki/Tomaccio_(tomato) tomato
30 traveller tomato http://en.wikipedia.org/wiki/Traveller_(tomato) tomato
31 three sisters tomato http://en.wikipedia.org/wiki/Three_Sisters_(tomato) tomato
32 Hanover tomato http://en.wikipedia.org/wiki/Hanover_tomato tomato
33 celebrity tomato http://en.wikipedia.org/wiki/Celebrity_(tomato) tomato
34 tomberry http://en.wikipedia.org/wiki/Tomberry tomato
35 super sweet 100 tomato http://en.wikipedia.org/wiki/Super_Sweet_100 tomato
36 marglobe tomato http://en.wikipedia.org/wiki/Marglobe tomato
37 grape tomato http://en.wikipedia.org/wiki/Grape_tomato tomato
38 cherry tomato http://en.wikipedia.org/wiki/Cherry_tomato tomato
39 Aunt Ruby's German green tomato http://en.wikipedia.org/wiki/Aunt_Ruby%27s_German_Green tomato
40 white queen tomato http://en.wikipedia.org/wiki/White_Queen_tomato tomato
41 pear tomato http://en.wikipedia.org/wiki/Pear_tomato tomato

View File

@@ -2,12 +2,13 @@ require 'bluecloth'
module Haml::Filters
module GrowstuffMarkdown
CROP_REGEX = /\[([^\[\]]+?)\]\(crop\)/
include Haml::Filters::Base
def render(text)
# turn [tomato](crop) into [tomato](http://growstuff.org/crops/tomato)
expanded = text.gsub(/\[([^\[\]]+?)\]\(crop\)/) do |m|
expanded = text.gsub(CROP_REGEX) do |m|
crop_str = $1
# find crop case-insensitively
crop = Crop.where('lower(name) = ?', crop_str.downcase).first

View File

@@ -30,6 +30,7 @@ namespace :growstuff do
CSV.foreach(@file) do |row|
Crop.create_from_csv(row)
end
Rails.cache.delete('full_crop_hierarchy')
puts "Finished loading crops"
end
@@ -249,13 +250,26 @@ namespace :growstuff do
desc "August 2014: fix ping to pint in database"
task :ping_to_pint => :environment do
Harvest.find_each do |h|
if h.unit == "ping"
if h.unit == "ping"
h.unit = "pint"
h.save
end
end
end
desc "October 2014: remove unused photos"
task :remove_unused_photos => :environment do
Photo.find_each do |p|
p.destroy_if_unused
end
end
desc "October 2014: generate crops_posts records for existing posts"
task :generate_crops_posts_records => :environment do
Post.find_each do |p|
p.save
end
end
end # end oneoff section
end

View File

@@ -10,8 +10,11 @@
# echo "YYYY-MM-DD - do something or other"
# rake growstuff:oneoff:something
echo "2013-07-18 - zero crop plantings_count"
rake growstuff:oneoff:zero_plantings_count
echo "2014-09-28 - upload tomatoes"
rake growstuff:import_crops file=db/seeds/crops-11-tomatoes.csv
echo "2014-08-10 - replace ping with pint in db"
rake growstuff:oneoff:ping_to_pint
echo "2014-10-02 - remove unused photos"
rake growstuff:oneoff:remove_unused_photos
echo "2014-10-05 - generate crops_posts records for existing posts"
rake growstuff:oneoff:generate_crops_posts_records

View File

@@ -12,10 +12,30 @@ describe HarvestsController do
end
describe "GET index" do
before do
@member1 = FactoryGirl.create(:member)
@member2 = FactoryGirl.create(:member)
@tomato = FactoryGirl.create(:tomato)
@maize = FactoryGirl.create(:maize)
@harvest1 = FactoryGirl.create(:harvest, :owner_id => @member1.id, :crop_id => @tomato.id)
@harvest2 = FactoryGirl.create(:harvest, :owner_id => @member2.id, :crop_id => @maize.id)
end
it "assigns all harvests as @harvests" do
harvest = Harvest.create! valid_attributes
get :index, {}
assigns(:harvests).should eq([harvest])
assigns(:harvests).should =~ [@harvest1, @harvest2]
end
it "picks up owner from params and shows owner's harvests only" do
get :index, {:owner => @member1.slug}
assigns(:owner).should eq @member1
assigns(:harvests).should eq [@harvest1]
end
it "picks up crop from params and shows the harvests for the crop only" do
get :index, {:crop => @maize.name}
assigns(:crop).should eq @maize
assigns(:harvests).should eq [@harvest2]
end
end

View File

@@ -37,8 +37,15 @@ describe PhotosController do
end
it "assigns a planting id" do
get :new, { :planting_id => 5 }
assigns(:planting_id).should eq "5"
get :new, { :type => "planting", :id => 5 }
assigns(:id).should eq "5"
assigns(:type).should eq "planting"
end
it "assigns a harvest id" do
get :new, { :type => "harvest", :id => 5 }
assigns(:id).should eq "5"
assigns(:type).should eq "harvest"
end
it "assigns the current set as @current_set" do
@@ -69,7 +76,8 @@ describe PhotosController do
planting = FactoryGirl.create(:planting, :garden => garden, :owner => member)
photo = FactoryGirl.create(:photo, :owner => member)
post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id },
:planting_id => planting.id }
:type => "planting",
:id => planting.id }
Photo.last.plantings.first.should eq planting
end
@@ -80,12 +88,39 @@ describe PhotosController do
planting = FactoryGirl.create(:planting, :garden => garden, :owner => member)
photo = FactoryGirl.create(:photo, :owner => member)
post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id },
:planting_id => planting.id }
:type => "planting",
:id => planting.id }
post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id },
:planting_id => planting.id }
:type => "planting",
:id => planting.id }
Photo.last.plantings.count.should eq 1
end
it "attaches the photo to a harvest" do
member = FactoryGirl.create(:member)
controller.stub(:current_member) { member }
harvest = FactoryGirl.create(:harvest, :owner => member)
photo = FactoryGirl.create(:photo, :owner => member)
post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id },
:type => "harvest",
:id => harvest.id }
Photo.last.harvests.first.should eq harvest
end
it "doesn't attach a photo to a harvest twice" do
member = FactoryGirl.create(:member)
controller.stub(:current_member) { member }
harvest = FactoryGirl.create(:harvest, :owner => member)
photo = FactoryGirl.create(:photo, :owner => member)
post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id },
:type => "harvest",
:id => harvest.id }
post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id },
:type => "harvest",
:id => harvest.id }
Photo.last.harvests.count.should eq 1
end
end
describe "for the second time" do
it "does not add a photo twice" do
@@ -106,9 +141,21 @@ describe PhotosController do
planting = FactoryGirl.create(:planting, :garden => garden, :owner => member)
photo = FactoryGirl.create(:photo, :owner => member)
post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id },
:planting_id => planting.id }
:type => "planting",
:id => planting.id }
Photo.last.plantings.first.should eq planting
end
it "creates the harvest/photo link" do
member = FactoryGirl.create(:member)
controller.stub(:current_member) { member }
harvest = FactoryGirl.create(:harvest, :owner => member)
photo = FactoryGirl.create(:photo, :owner => member)
post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id },
:type => "harvest",
:id => harvest.id }
Photo.last.harvests.first.should eq harvest
end
end
describe "with mismatched owners" do
@@ -117,9 +164,20 @@ describe PhotosController do
planting = FactoryGirl.create(:planting)
photo = FactoryGirl.create(:photo)
post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id },
:planting_id => planting.id }
:type => "planting",
:id => planting.id }
Photo.last.plantings.first.should_not eq planting
end
it "creates the harvest/photo link" do
# members will be auto-created, and different
harvest = FactoryGirl.create(:harvest)
photo = FactoryGirl.create(:photo)
post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id },
:type => "harvest",
:id => harvest.id }
Photo.last.harvests.first.should_not eq harvest
end
end
end
end

View File

@@ -12,10 +12,30 @@ describe PlantingsController do
end
describe "GET index" do
it "picks up owner from params" do
owner = FactoryGirl.create(:member)
get :index, {:owner => owner.slug}
assigns(:owner).should eq(owner)
before do
@member1 = FactoryGirl.create(:member)
@member2 = FactoryGirl.create(:member)
@tomato = FactoryGirl.create(:tomato)
@maize = FactoryGirl.create(:maize)
@planting1 = FactoryGirl.create(:planting, :crop => @tomato, :owner => @member1)
@planting2 = FactoryGirl.create(:planting, :crop => @maize, :owner => @member2)
end
it "assigns all plantings as @plantings" do
get :index, {}
assigns(:plantings).should =~ [@planting1, @planting2]
end
it "picks up owner from params and shows owner's plantings only" do
get :index, {:owner => @member1.slug}
assigns(:owner).should eq @member1
assigns(:plantings).should eq [@planting1]
end
it "picks up crop from params and shows the plantings for the crop only" do
get :index, {:crop => @maize.name}
assigns(:crop).should eq @maize
assigns(:plantings).should eq [@planting2]
end
end

View File

@@ -0,0 +1,52 @@
require 'spec_helper'
feature "signup" do
scenario "sign up for new account from top menubar" do
visit crops_path # something other than front page, which has multiple signup links
click_link 'Sign up'
fill_in 'Login name', with: 'person123'
fill_in 'Email', with: 'gardener@example.com'
fill_in 'Password', with: 'abc123'
fill_in 'Password confirmation', with: 'abc123'
check 'member_tos_agreement'
click_button 'Sign up'
page.has_content? 'A message with a confirmation link has been sent to your email address. Please open the link to activate your account.'
current_path.should eq root_path
end
scenario "sign up for new account with existing username" do
visit crops_path # something other than front page, which has multiple signup links
click_link 'Sign up'
fill_in 'Login name', with: 'person123'
fill_in 'Email', with: 'gardener@example.com'
fill_in 'Password', with: 'abc123'
fill_in 'Password confirmation', with: 'abc123'
check 'member_tos_agreement'
click_button 'Sign up'
page.has_content? 'A message with a confirmation link has been sent to your email address. Please open the link to activate your account.'
current_path.should eq root_path
first('.signup a').click # click the 'Sign up' button in the middle of the page
fill_in 'Login name', with: 'person123'
fill_in 'Email', with: 'gardener@example.com'
fill_in 'Password', with: 'abc123'
fill_in 'Password confirmation', with: 'abc123'
check 'member_tos_agreement'
click_button 'Sign up'
page.has_content? 'Login name has already been taken'
end
scenario "sign up for new account without accepting TOS" do
visit root_path
first('.signup a').click # click the 'Sign up' button in the middle of the page
fill_in 'Login name', with: 'person123'
fill_in 'Email', with: 'gardener@example.com'
fill_in 'Password', with: 'abc123'
fill_in 'Password confirmation', with: 'abc123'
# do not check 'member_tos_agreement'
click_button 'Sign up'
page.has_content? 'Tos agreement must be accepted'
current_path.should eq members_path
end
end

View File

@@ -1,12 +0,0 @@
require 'spec_helper'
describe 'new gardens' do
it "should have 'my garden' for each user" do
(1..3).each do |i|
@user = User.find_by_username("test#{i}")
@garden = Garden.find(:name => "My Garden", :user_id => @user.id)
@garden.should_exist
@garden.slug.should == "test#{i}-my-garden"
end
end
end

View File

@@ -1,10 +0,0 @@
require 'spec_helper'
describe 'test users' do
it 'should have 3 test users' do
(1..3).each do |i|
@user = User.find_by_username("test#{i}")
@user.email.should == "test#{i}@example.com"
end
end
end

View File

@@ -343,4 +343,23 @@ describe Crop do
end
end
context "crop-post association" do
let!(:tomato) { FactoryGirl.create(:tomato) }
let!(:maize) { FactoryGirl.create(:maize) }
let!(:post) { FactoryGirl.create(:post, :body => "[maize](crop)[tomato](crop)[tomato](crop)") }
describe "destroying a crop" do
before do
tomato.destroy
end
it "should delete the association between post and the crop(tomato)" do
expect(Post.find(post).crops).to eq [maize]
end
it "should not delete the posts" do
expect(Post.find(post)).to_not eq nil
end
end
end
end

View File

@@ -125,4 +125,32 @@ describe Harvest do
Harvest.all.should eq [@h2, @h1]
end
end
context 'photos' do
before(:each) do
@harvest = FactoryGirl.create(:harvest)
@photo = FactoryGirl.create(:photo)
@harvest.photos << @photo
end
it 'has a photo' do
@harvest.photos.first.should eq @photo
end
it 'deletes association with photos when photo is deleted' do
@photo.destroy
@harvest.reload
@harvest.photos.should be_empty
end
it 'has a default photo' do
@harvest.default_photo.should eq @photo
end
it 'chooses the most recent photo' do
@photo2 = FactoryGirl.create(:photo)
@harvest.photos << @photo2
@harvest.default_photo.should eq @photo2
end
end
end

View File

@@ -2,6 +2,82 @@ require 'spec_helper'
describe Photo do
describe 'add/delete functionality' do
let(:photo) { FactoryGirl.create(:photo) }
let(:planting) { FactoryGirl.create(:planting) }
let(:harvest) { FactoryGirl.create(:harvest) }
context "adds photos" do
it 'to a planting' do
planting.photos << photo
expect(planting.photos.count).to eq 1
expect(planting.photos.first).to eq photo
end
it 'to a harvest' do
harvest.photos << photo
expect(harvest.photos.count).to eq 1
expect(harvest.photos.first).to eq photo
end
end
context "removing photos" do
it 'from a planting' do
planting.photos << photo
photo.destroy
expect(planting.photos.count).to eq 0
end
it 'from a harvest' do
harvest.photos << photo
photo.destroy
expect(harvest.photos.count).to eq 0
end
it "automatically if unused" do
photo.destroy_if_unused
expect(lambda { photo.reload }).to raise_error ActiveRecord::RecordNotFound
end
it 'they are no longer used by plantings' do
planting.photos << photo
planting.destroy # photo is now no longer used by anything
expect(lambda { photo.reload }).to raise_error ActiveRecord::RecordNotFound
end
it 'they are no longer used by harvests' do
harvest.photos << photo
harvest.destroy # photo is now no longer used by anything
expect(lambda { photo.reload }).to raise_error ActiveRecord::RecordNotFound
end
it 'they are no longer used by anything' do
planting.photos << photo
harvest.photos << photo
expect(photo.plantings.size).to eq 1
expect(photo.harvests.size).to eq 1
planting.destroy # photo is still used by harvest
photo.reload
expect(photo).to be_an_instance_of Photo
expect(photo.plantings.size).to eq 0
expect(photo.harvests.size).to eq 1
harvest.destroy # photo is now no longer used by anything
expect(lambda { photo.reload }).to raise_error ActiveRecord::RecordNotFound
end
it 'does not occur when a photo is still in use' do
planting.photos << photo
harvest.photos << photo
planting.destroy # photo is still used by the harvest
expect(photo).to be_an_instance_of Photo
end
end # removing photos
end # add/delete functionality
describe 'flickr_metadata' do
# Any further tests led to us MOCKING ALL THE THINGS
# which was epistemologically unsatisfactory.

View File

@@ -95,4 +95,44 @@ describe Post do
end
end
context "crop-post association" do
let!(:tomato) { FactoryGirl.create(:tomato) }
let!(:maize) { FactoryGirl.create(:maize) }
let!(:chard) { FactoryGirl.create(:chard) }
let!(:post) { FactoryGirl.create(:post, :body => "[maize](crop)[tomato](crop)[tomato](crop)") }
it "should be generated" do
expect(tomato.posts).to eq [post]
expect(maize.posts).to eq [post]
end
it "should not duplicate" do
expect(post.crops) =~ [tomato, maize]
end
it "should be updated when post was modified" do
post.update_attributes(:body => "[chard](crop)")
expect(post.crops).to eq [chard]
expect(chard.posts).to eq [post]
expect(tomato.posts).to eq []
expect(maize.posts).to eq []
end
describe "destroying the post" do
before do
post.destroy
end
it "should delete the association" do
expect(Crop.find(tomato).posts).to eq []
expect(Crop.find(maize).posts).to eq []
end
it "should not delete the crops" do
expect(Crop.find(tomato)).to_not eq nil
expect(Crop.find(maize)).to_not eq nil
end
end
end
end

View File

@@ -7,6 +7,16 @@ describe "crops/show" do
:scientific_names => [ FactoryGirl.create(:zea_mays) ]
)
assign(:crop, @crop)
@author = FactoryGirl.create(:member)
page = 1
per_page = 2
total_entries = 2
@posts = WillPaginate::Collection.create(page, per_page, total_entries) do |pager|
pager.replace([
@post1 = FactoryGirl.create(:post, :author => @author, :body => "Post it!" ),
@post2 = FactoryGirl.create(:post, :author => @author, :body => "Done!" )
])
end
end
context 'photos' do
@@ -125,11 +135,10 @@ describe "crops/show" do
context "has plantings" do
before(:each) do
@owner = FactoryGirl.create(:member)
@garden = FactoryGirl.create(:garden, :owner => @owner)
@owner = FactoryGirl.create(:london_member)
@planting = FactoryGirl.create(:planting,
:garden => @garden,
:crop => @crop
:crop => @crop,
:owner => @owner
)
@crop.reload # to pick up latest plantings_count
end
@@ -137,16 +146,24 @@ describe "crops/show" do
it "links to people who are growing this crop" do
render
rendered.should contain @owner.login_name
rendered.should contain @garden.name
rendered.should contain @owner.location
end
end
it "shows photos where available" do
@photo = FactoryGirl.create(:photo)
@planting.photos << @photo
context "has posts" do
it "links to posts" do
render
assert_select "img", :src => @photo.thumbnail_url
@posts.each do |p|
rendered.should contain p.author.login_name
rendered.should contain p.subject
rendered.should contain p.body
end
end
it "contains two gravatar icons" do
render
assert_select "img", :src => /gravatar\.com\/avatar/, :count => 2
end
end
context 'varieties' do
@@ -189,9 +206,36 @@ describe "crops/show" do
rendered.should contain "Harvest this"
end
it "links to the right crop in the planting link" do
it "links to the right crop in the new planting link" do
assert_select("a[href=#{new_planting_path}?crop_id=#{@crop.id}]")
end
it "links to the right crop in the new harvest link" do
assert_select("a[href=#{new_harvest_path}?crop_id=#{@crop.id}]")
end
it { rendered.should contain "Nobody has planted this crop yet" }
it { rendered.should contain "Nobody has harvested this crop yet" }
context "should have a link to" do
before do
FactoryGirl.create(:planting, :crop => @crop)
FactoryGirl.create(:harvest, :crop => @crop)
@crop.reload
render
end
it "show all plantings by the crop link" do
assert_select("a[href=#{plantings_by_crop_path @crop}]")
end
it "show all harvests by the crop link" do
assert_select("a[href=#{harvests_by_crop_path @crop}]")
end
end
end
context "logged in and crop wrangler" do

View File

@@ -41,4 +41,17 @@ describe "harvests/index" do
assert_select "a", :href => harvests_path(:format => 'csv')
assert_select "a", :href => harvests_path(:format => 'json')
end
it "displays member's name in title" do
assign(:owner, @member)
render
view.content_for(:title).should contain @member.login_name
end
it "displays crop's name in title" do
assign(:crop, @tomato)
render
view.content_for(:title).should contain @tomato.name
end
end

View File

@@ -48,4 +48,15 @@ describe "plantings/index" do
assert_select "a", :href => plantings_path(:format => 'rss')
end
it "displays member's name in title" do
assign(:owner, @member)
render
view.content_for(:title).should contain @member.login_name
end
it "displays crop's name in title" do
assign(:crop, @tomato)
render
view.content_for(:title).should contain @tomato.name
end
end