diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/admin.js.coffee +++ /dev/null @@ -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/ diff --git a/app/assets/javascripts/admin/orders.js.coffee b/app/assets/javascripts/admin/orders.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/admin/orders.js.coffee +++ /dev/null @@ -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/ diff --git a/app/assets/javascripts/comments.js.coffee b/app/assets/javascripts/comments.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/comments.js.coffee +++ /dev/null @@ -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/ diff --git a/app/assets/javascripts/forums.js.coffee b/app/assets/javascripts/forums.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/forums.js.coffee +++ /dev/null @@ -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/ diff --git a/app/assets/javascripts/gardens.js.coffee b/app/assets/javascripts/gardens.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/gardens.js.coffee +++ /dev/null @@ -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/ diff --git a/app/assets/javascripts/harvests.js.coffee b/app/assets/javascripts/harvests.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/harvests.js.coffee +++ /dev/null @@ -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/ diff --git a/app/assets/javascripts/home.js.coffee b/app/assets/javascripts/home.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/home.js.coffee +++ /dev/null @@ -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/ diff --git a/app/assets/javascripts/notifications.js.coffee b/app/assets/javascripts/notifications.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/notifications.js.coffee +++ /dev/null @@ -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/ diff --git a/app/assets/javascripts/order_items.js.coffee b/app/assets/javascripts/order_items.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/order_items.js.coffee +++ /dev/null @@ -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/ diff --git a/app/assets/javascripts/orders.js.coffee b/app/assets/javascripts/orders.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/orders.js.coffee +++ /dev/null @@ -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/ diff --git a/app/assets/javascripts/photos.js.coffee b/app/assets/javascripts/photos.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/photos.js.coffee +++ /dev/null @@ -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/ diff --git a/app/assets/javascripts/plant_parts.js.coffee b/app/assets/javascripts/plant_parts.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/plant_parts.js.coffee +++ /dev/null @@ -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/ diff --git a/app/assets/javascripts/products.js.coffee b/app/assets/javascripts/products.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/products.js.coffee +++ /dev/null @@ -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/ diff --git a/app/assets/javascripts/roles.js.coffee b/app/assets/javascripts/roles.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/roles.js.coffee +++ /dev/null @@ -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/ diff --git a/app/assets/javascripts/scientific_names.js.coffee b/app/assets/javascripts/scientific_names.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/scientific_names.js.coffee +++ /dev/null @@ -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/ diff --git a/app/assets/javascripts/updates.js.coffee b/app/assets/javascripts/updates.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/updates.js.coffee +++ /dev/null @@ -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/ diff --git a/app/controllers/crops_controller.rb b/app/controllers/crops_controller.rb index 1316350de..a30de159a 100644 --- a/app/controllers/crops_controller.rb +++ b/app/controllers/crops_controller.rb @@ -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 diff --git a/app/controllers/harvests_controller.rb b/app/controllers/harvests_controller.rb index 96e029ac3..524007d47 100644 --- a/app/controllers/harvests_controller.rb +++ b/app/controllers/harvests_controller.rb @@ -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 diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index 4962fc54e..45755db84 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -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.' } diff --git a/app/controllers/plantings_controller.rb b/app/controllers/plantings_controller.rb index 0d419d2f1..62eaf5b79 100644 --- a/app/controllers/plantings_controller.rb +++ b/app/controllers/plantings_controller.rb @@ -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 diff --git a/app/helpers/plant_parts_helper.rb b/app/helpers/plant_parts_helper.rb deleted file mode 100644 index b97b124e8..000000000 --- a/app/helpers/plant_parts_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module PlantPartsHelper -end diff --git a/app/helpers/seeds_helper.rb b/app/helpers/seeds_helper.rb deleted file mode 100644 index 46c697f02..000000000 --- a/app/helpers/seeds_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module SeedsHelper -end diff --git a/app/models/crop.rb b/app/models/crop.rb index 3efe8be57..387936d91 100644 --- a/app/models/crop.rb +++ b/app/models/crop.rb @@ -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") diff --git a/app/models/harvest.rb b/app/models/harvest.rb index a7851fdc8..68afb2ee5 100644 --- a/app/models/harvest.rb +++ b/app/models/harvest.rb @@ -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 diff --git a/app/models/photo.rb b/app/models/photo.rb index e65617c87..32679eeed 100644 --- a/app/models/photo.rb +++ b/app/models/photo.rb @@ -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 diff --git a/app/models/planting.rb b/app/models/planting.rb index e5955f04f..47aaf36e6 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -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) diff --git a/app/models/post.rb b/app/models/post.rb index 3a853c18a..7f72a4e57 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -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 diff --git a/app/views/crops/_harvests.html.haml b/app/views/crops/_harvests.html.haml index b8cb25447..d10f57d06 100644 --- a/app/views/crops/_harvests.html.haml +++ b/app/views/crops/_harvests.html.haml @@ -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" } diff --git a/app/views/crops/_plantings.html.haml b/app/views/crops/_plantings.html.haml new file mode 100644 index 000000000..72bce5608 --- /dev/null +++ b/app/views/crops/_plantings.html.haml @@ -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" } + diff --git a/app/views/crops/show.html.haml b/app/views/crops/show.html.haml index b79320698..670a0d438 100644 --- a/app/views/crops/show.html.haml +++ b/app/views/crops/show.html.haml @@ -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 } diff --git a/app/views/harvests/index.html.haml b/app/views/harvests/index.html.haml index 90bbb4638..f80ce0dd5 100644 --- a/app/views/harvests/index.html.haml +++ b/app/views/harvests/index.html.haml @@ -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 diff --git a/app/views/harvests/show.html.haml b/app/views/harvests/show.html.haml index 95897bd42..1cbd0d38e 100644 --- a/app/views/harvests/show.html.haml +++ b/app/views/harvests/show.html.haml @@ -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' diff --git a/app/views/photos/new.html.haml b/app/views/photos/new.html.haml index 365351fac..bc3eb35df 100644 --- a/app/views/photos/new.html.haml +++ b/app/views/photos/new.html.haml @@ -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 diff --git a/app/views/plantings/index.html.haml b/app/views/plantings/index.html.haml index 091974ef8..eb1e017dc 100644 --- a/app/views/plantings/index.html.haml +++ b/app/views/plantings/index.html.haml @@ -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 diff --git a/app/views/plantings/show.html.haml b/app/views/plantings/show.html.haml index 72154c0a3..366d089d7 100644 --- a/app/views/plantings/show.html.haml +++ b/app/views/plantings/show.html.haml @@ -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' diff --git a/config/application.rb b/config/application.rb index f1a5e9dd6..6bbe1fe7d 100644 --- a/config/application.rb +++ b/config/application.rb @@ -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 diff --git a/config/routes.rb b/config/routes.rb index 360ed2477..0d645a1be 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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' diff --git a/db/migrate/20140905001730_add_harvests_photos_table.rb b/db/migrate/20140905001730_add_harvests_photos_table.rb new file mode 100644 index 000000000..edacd061b --- /dev/null +++ b/db/migrate/20140905001730_add_harvests_photos_table.rb @@ -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 diff --git a/db/migrate/20140928044231_add_crops_posts_table.rb b/db/migrate/20140928044231_add_crops_posts_table.rb new file mode 100644 index 000000000..a8c06927b --- /dev/null +++ b/db/migrate/20140928044231_add_crops_posts_table.rb @@ -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 \ No newline at end of file diff --git a/db/migrate/20141002022459_create_index_harvest_photos.rb b/db/migrate/20141002022459_create_index_harvest_photos.rb new file mode 100644 index 000000000..75ef69d36 --- /dev/null +++ b/db/migrate/20141002022459_create_index_harvest_photos.rb @@ -0,0 +1,5 @@ +class CreateIndexHarvestPhotos < ActiveRecord::Migration + def change + add_index(:harvests_photos, [:harvest_id, :photo_id]) + end +end diff --git a/db/schema.rb b/db/schema.rb index af91a7a9d..617181402 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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 diff --git a/db/seeds/README.md b/db/seeds/README.md new file mode 100644 index 000000000..c3cfd1902 --- /dev/null +++ b/db/seeds/README.md @@ -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 diff --git a/db/seeds/crops-11-tomatoes.csv b/db/seeds/crops-11-tomatoes.csv new file mode 100644 index 000000000..eb28aaa79 --- /dev/null +++ b/db/seeds/crops-11-tomatoes.csv @@ -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, diff --git a/lib/haml/filters/growstuff_markdown.rb b/lib/haml/filters/growstuff_markdown.rb index 7069590e7..dba081e44 100644 --- a/lib/haml/filters/growstuff_markdown.rb +++ b/lib/haml/filters/growstuff_markdown.rb @@ -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 diff --git a/lib/tasks/growstuff.rake b/lib/tasks/growstuff.rake index 34739217b..f19bf1fed 100644 --- a/lib/tasks/growstuff.rake +++ b/lib/tasks/growstuff.rake @@ -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 diff --git a/script/deploy-tasks.sh b/script/deploy-tasks.sh index f861d27ed..451fe0032 100755 --- a/script/deploy-tasks.sh +++ b/script/deploy-tasks.sh @@ -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 diff --git a/spec/controllers/harvests_controller_spec.rb b/spec/controllers/harvests_controller_spec.rb index f2ecb8e32..38b0edf8c 100644 --- a/spec/controllers/harvests_controller_spec.rb +++ b/spec/controllers/harvests_controller_spec.rb @@ -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 diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb index a73981e4c..1c3b47780 100644 --- a/spec/controllers/photos_controller_spec.rb +++ b/spec/controllers/photos_controller_spec.rb @@ -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 diff --git a/spec/controllers/plantings_controller_spec.rb b/spec/controllers/plantings_controller_spec.rb index 7e6a75d22..4b3a518a5 100644 --- a/spec/controllers/plantings_controller_spec.rb +++ b/spec/controllers/plantings_controller_spec.rb @@ -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 diff --git a/spec/features/gardens.rb b/spec/features/gardens_spec.rb similarity index 100% rename from spec/features/gardens.rb rename to spec/features/gardens_spec.rb diff --git a/spec/features/signin.rb b/spec/features/signin_spec.rb similarity index 100% rename from spec/features/signin.rb rename to spec/features/signin_spec.rb diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb new file mode 100644 index 000000000..9962df359 --- /dev/null +++ b/spec/features/signup_spec.rb @@ -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 diff --git a/spec/migrations/give_each_user_a_garden.spec b/spec/migrations/give_each_user_a_garden.spec deleted file mode 100644 index 1c6972e54..000000000 --- a/spec/migrations/give_each_user_a_garden.spec +++ /dev/null @@ -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 diff --git a/spec/migrations/set_up_test_users.spec b/spec/migrations/set_up_test_users.spec deleted file mode 100644 index d1f69d3f6..000000000 --- a/spec/migrations/set_up_test_users.spec +++ /dev/null @@ -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 diff --git a/spec/models/crop_spec.rb b/spec/models/crop_spec.rb index 1741faf74..d9e3d4b0a 100644 --- a/spec/models/crop_spec.rb +++ b/spec/models/crop_spec.rb @@ -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 diff --git a/spec/models/harvest_spec.rb b/spec/models/harvest_spec.rb index ca0180ca1..66c83c93e 100644 --- a/spec/models/harvest_spec.rb +++ b/spec/models/harvest_spec.rb @@ -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 diff --git a/spec/models/photo_spec.rb b/spec/models/photo_spec.rb index 8ac067ab7..2a0b1d5ff 100644 --- a/spec/models/photo_spec.rb +++ b/spec/models/photo_spec.rb @@ -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. diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb index 441b9c6a9..30c19c500 100644 --- a/spec/models/post_spec.rb +++ b/spec/models/post_spec.rb @@ -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 diff --git a/spec/views/crops/show.html.haml_spec.rb b/spec/views/crops/show.html.haml_spec.rb index 6e8250b27..1ef461508 100644 --- a/spec/views/crops/show.html.haml_spec.rb +++ b/spec/views/crops/show.html.haml_spec.rb @@ -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 diff --git a/spec/views/harvests/index.html.haml_spec.rb b/spec/views/harvests/index.html.haml_spec.rb index 3a147e0b6..093a383a9 100644 --- a/spec/views/harvests/index.html.haml_spec.rb +++ b/spec/views/harvests/index.html.haml_spec.rb @@ -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 diff --git a/spec/views/plantings/index.html.haml_spec.rb b/spec/views/plantings/index.html.haml_spec.rb index 5ff890514..d46b4e003 100644 --- a/spec/views/plantings/index.html.haml_spec.rb +++ b/spec/views/plantings/index.html.haml_spec.rb @@ -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