Merge pull request #327 from Growstuff/dev

Production push: Harvests
This commit is contained in:
Skud
2013-10-15 01:20:20 -07:00
52 changed files with 1252 additions and 15 deletions

View File

@@ -14,6 +14,8 @@ gem 'cancan' # for checking member privileges
gem 'gibbon' # for Mailchimp newsletter subscriptions
gem 'csv_shaper' # CSV export
# vendored activemerchant for testing- needed for bogus paypal
# gateway monkeypatch
gem 'activemerchant', '1.33.0',

View File

@@ -88,6 +88,8 @@ GEM
rest-client
simplecov (>= 0.7)
thor
csv_shaper (1.0.0)
activesupport (>= 3.0.0)
dalli (2.6.4)
debugger (1.6.1)
columnize (>= 0.3.1)
@@ -260,6 +262,7 @@ DEPENDENCIES
coffee-rails (~> 3.2.1)
compass-rails (~> 1.0.3)
coveralls
csv_shaper
dalli
debugger
devise

View File

@@ -0,0 +1,3 @@
# 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

@@ -10,9 +10,17 @@ class CropsController < ApplicationController
@crops = Crop.includes(:scientific_names, {:plantings => :photos}).paginate(:page => params[:page])
respond_to do |format|
format.html # index.html.haml
format.json { render json: @crops }
format.rss { render :layout => false }
format.html
format.json { render :json => @crops }
format.rss do
@crops = Crop.recent.includes(:scientific_names, :creator)
render :rss => @crops
end
format.csv do
@filename = "Growstuff-Crops-#{Time.zone.now.to_s(:number)}.csv"
@crops = Crop.includes(:scientific_names, :plantings, :seeds, :creator)
render :csv => @crops
end
end
end

View File

@@ -0,0 +1,106 @@
class HarvestsController < ApplicationController
load_and_authorize_resource
# GET /harvests
# GET /harvests.json
def index
@owner = Member.find_by_slug(params[:owner])
if @owner
@harvests = @owner.harvests.includes(:owner, :crop).paginate(:page => params[:page])
else
@harvests = Harvest.includes(:owner, :crop).paginate(:page => params[:page])
end
respond_to do |format|
format.html # index.html.erb
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
render :csv => @harvests
end
end
end
# GET /harvests/1
# GET /harvests/1.json
def show
@harvest = Harvest.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: @harvest }
end
end
# GET /harvests/new
# GET /harvests/new.json
def new
@harvest = Harvest.new('harvested_at' => Date.today)
# using find_by_id here because it returns nil, unlike find
@crop = Crop.find_by_id(params[:crop_id]) || Crop.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: @harvest }
end
end
# GET /harvests/1/edit
def edit
@harvest = Harvest.find(params[:id])
end
# POST /harvests
# POST /harvests.json
def create
params[:harvest][:owner_id] = current_member.id
params[:harvested_at] = parse_date(params[:harvested_at])
@harvest = Harvest.new(params[:harvest])
respond_to do |format|
if @harvest.save
format.html { redirect_to @harvest, notice: 'Harvest was successfully created.' }
format.json { render json: @harvest, status: :created, location: @harvest }
else
format.html { render action: "new" }
format.json { render json: @harvest.errors, status: :unprocessable_entity }
end
end
end
# PUT /harvests/1
# PUT /harvests/1.json
def update
@harvest = Harvest.find(params[:id])
respond_to do |format|
if @harvest.update_attributes(params[:harvest])
format.html { redirect_to @harvest, notice: 'Harvest was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: @harvest.errors, status: :unprocessable_entity }
end
end
end
# DELETE /harvests/1
# DELETE /harvests/1.json
def destroy
@harvest = Harvest.find(params[:id])
@harvest.destroy
respond_to do |format|
format.html { redirect_to harvests_url }
format.json { head :no_content }
end
end
end

View File

@@ -17,6 +17,16 @@ class PlantingsController < ApplicationController
format.html # index.html.erb
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
render :csv => @plantings
end
end
end

View File

@@ -17,6 +17,16 @@ class SeedsController < ApplicationController
format.html # index.html.erb
format.json { render json: @seeds }
format.rss { render :layout => false } #index.rss.builder
format.csv do
if @owner
@filename = "Growstuff-#{@owner}-Seeds-#{Time.zone.now.to_s(:number)}.csv"
@seeds = @owner.seeds.includes(:owner, :crop)
else
@filename = "Growstuff-Seeds-#{Time.zone.now.to_s(:number)}.csv"
@seeds = Seed.includes(:owner, :crop)
end
render :csv => @seeds
end
end
end

View File

@@ -0,0 +1,40 @@
module HarvestsHelper
def display_quantity(harvest)
human_quantity = display_human_quantity(harvest)
weight = display_weight(harvest)
if human_quantity && weight
return "#{human_quantity}, weighing #{weight}"
elsif human_quantity
return human_quantity
elsif weight
return weight
else
return 'not specified'
end
end
def display_human_quantity(harvest)
if ! harvest.quantity.blank? && harvest.quantity > 0
if harvest.unit == 'individual' # just the number
number_to_human(harvest.quantity, :strip_insignificant_zeros => true)
elsif ! harvest.unit.blank? # pluralize anything else
return pluralize(number_to_human(harvest.quantity, :strip_insignificant_zeros => true), harvest.unit)
else
return "#{number_to_human(harvest.quantity, :strip_insignificant_zeros => true)} #{harvest.unit}"
end
else
return nil
end
end
def display_weight(harvest)
if ! harvest.weight_quantity.blank? && harvest.weight_quantity > 0
return "#{number_to_human(harvest.weight_quantity, :strip_insignificant_zeros => true)} #{harvest.weight_unit}"
else
return nil
end
end
end

View File

@@ -64,6 +64,10 @@ class Ability
can :update, Planting, :garden => { :owner_id => member.id }
can :destroy, Planting, :garden => { :owner_id => member.id }
can :create, Harvest
can :update, Harvest, :owner_id => member.id
can :destroy, Harvest, :owner_id => member.id
can :create, Photo
can :update, Photo, :owner_id => member.id
can :destroy, Photo, :owner_id => member.id

View File

@@ -7,6 +7,7 @@ class Crop < ActiveRecord::Base
has_many :plantings
has_many :photos, :through => :plantings
has_many :seeds
has_many :harvests
belongs_to :creator, :class_name => 'Member'
belongs_to :parent, :class_name => 'Crop'

69
app/models/harvest.rb Normal file
View File

@@ -0,0 +1,69 @@
class Harvest < ActiveRecord::Base
extend FriendlyId
friendly_id :harvest_slug, use: :slugged
attr_accessible :crop_id, :harvested_at, :description, :owner_id,
:quantity, :unit, :weight_quantity, :weight_unit, :slug
belongs_to :crop
belongs_to :owner, :class_name => 'Member'
validates :quantity,
:numericality => { :only_integer => false },
:allow_nil => true
UNITS_VALUES = {
"individual" => "individual",
"bunches" => "bunch",
"sprigs" => "sprig",
"handfuls" => "handful",
"litres" => "litre",
"pints" => "ping",
"quarts" => "quart",
"buckets" => "bucket",
"baskets" => "basket",
"bushels" => "bushel"
}
validates :unit, :inclusion => { :in => UNITS_VALUES.values,
:message => "%{value} is not a valid unit" },
:allow_nil => true,
:allow_blank => true
validates :weight_quantity,
:numericality => { :only_integer => false },
:allow_nil => true
WEIGHT_UNITS_VALUES = {
"kg" => "kg",
"lb" => "lb"
}
validates :weight_unit, :inclusion => { :in => WEIGHT_UNITS_VALUES.values,
:message => "%{value} is not a valid unit" },
:allow_nil => true,
:allow_blank => true
after_validation :cleanup_quantities
def cleanup_quantities
if quantity == 0
self.quantity = nil
end
if quantity.blank?
self.unit = nil
end
if weight_quantity == 0
self.weight_quantity = nil
end
if weight_quantity.blank?
self.weight_unit = nil
end
end
def harvest_slug
"#{owner.login_name}-#{crop}".downcase.gsub(' ', '-')
end
end

View File

@@ -10,6 +10,7 @@ class Member < ActiveRecord::Base
has_many :plantings, :foreign_key => 'owner_id'
has_many :seeds, :foreign_key => 'owner_id'
has_many :harvests, :foreign_key => 'owner_id'
has_and_belongs_to_many :roles

View File

@@ -0,0 +1,83 @@
csv.headers :id, :system_name,
:growstuff_url, :en_wikipedia_url,
:default_scientific_name,
:scientific_name_count,
:parent_crop_id, :parent_crop_name,
:plantings_count,
:seeds_count,
:recommended_sunniness,
:planted_in_sun,
:planted_in_semi_shade,
:planted_in_shade,
:plant_from_recommendation,
:planted_from_seed,
:planted_from_seedling,
:planted_from_cutting,
:planted_from_root_division,
:planted_from_runner,
:planted_from_bulb,
:planted_from_bare_root_plant,
:planted_from_advanced_plant,
:planted_from_graft,
:planted_from_layering,
:added_by_member_id,
:added_by_member_name,
:date_added,
:last_modified,
:license
@crops.each do |c|
csv.row c do |csv, crop|
csv.cells :id, :system_name, :en_wikipedia_url
csv.cell :growstuff_url, crop_url(c)
if c.scientific_names.any?
csv.cell :default_scientific_name, c.default_scientific_name
csv.cell :scientific_name_count, c.scientific_names.count
end
if c.parent
csv.cell :parent_crop_id, c.parent.id
csv.cell :parent_crop_name, c.parent.system_name
end
csv.cell :plantings_count || 0
csv.cell :seeds_count, c.seeds.count
sunniness = c.sunniness
sunniness_rec = sunniness.max_by{|k,v| v}
if sunniness_rec
csv.cell :recommended_sunniness, sunniness_rec[0]
end
csv.cell :planted_in_sun, sunniness['sun']
csv.cell :planted_in_semi_shade, sunniness['semi_shade']
csv.cell :planted_in_shade, sunniness['shade']
planted_from = c.planted_from
planted_from_rec = planted_from.max_by{|k,v| v}
if planted_from_rec
csv.cell :plant_from_recommendation, planted_from_rec[0]
end
csv.cell :planted_from_seed, planted_from['seed']
csv.cell :planted_from_seedling, planted_from['seedling']
csv.cell :planted_from_cutting, planted_from['cutting']
csv.cell :planted_from_root_division, planted_from['root division']
csv.cell :planted_from_runner, planted_from['runner']
csv.cell :planted_from_bulb, planted_from['bulb']
csv.cell :planted_from_bare_root_plant, planted_from['bare root plant']
csv.cell :planted_from_advanced_plant, planted_from['advanced plant']
csv.cell :planted_from_graft, planted_from['graft']
csv.cell :planted_from_layering, planted_from['layering']
csv.cell :added_by_member_id, c.creator.id
csv.cell :added_by_member_name, c.creator.to_s
csv.cell :date_added, c.created_at.to_s(:db)
csv.cell :last_modified, c.updated_at.to_s(:db)
csv.cell :license, "CC-BY-SA Growstuff http://growstuff.org/"
end
end

View File

@@ -22,3 +22,8 @@
= page_entries_info @crops, :model => "crops"
= will_paginate @crops
%ul.inline
%li The data on this page is available in the following formats:
%li= link_to "CSV", crops_path(:format => 'csv')
%li= link_to "JSON", crops_path(:format => 'json')

View File

@@ -22,6 +22,9 @@
- else
= render :partial => 'shared/signin_signup', :locals => { :to => 'plant this crop' }
- if can? :create, Harvest
= link_to "Harvest this", new_harvest_path(:crop_id => @crop.id), :class => 'btn btn-primary'
- if can? :create, Seed
= link_to 'Add seeds to stash', new_seed_path(:params => { :crop_id => @crop.id }), :class => 'btn btn-primary'

View File

@@ -0,0 +1,39 @@
= form_for(@harvest, :html => {:class => "form-horizontal"}) do |f|
- if @harvest.errors.any?
#error_explanation
%h2= "#{pluralize(@harvest.errors.count, "error")} prohibited this harvest from being saved:"
%ul
- @harvest.errors.full_messages.each do |msg|
%li= msg
.control-group
= f.label 'What did you harvest?', :class => 'control-label'
.controls
= collection_select(:harvest, :crop_id, Crop.all, :id, :system_name, :selected => @harvest.crop_id || @crop.id)
%span.help-inline
Can't find what you're looking for?
= link_to "Request new crops.", Growstuff::Application.config.new_crops_request_link
.control-group
= f.label 'When?', :class => 'control-label'
.controls= f.text_field :harvested_at, :value => @harvest.harvested_at ? @harvest.harvested_at.to_s(:ymd) : '', :class => 'add-datepicker'
.control-group
= f.label 'How many?', :class => 'control-label'
.controls
= f.number_field :quantity, :class => 'input-small'
= f.select(:unit, Harvest::UNITS_VALUES, {:include_blank => false}, :class => 'input-medium')
.control-group
= f.label 'Weighing:', :class => 'control-label'
.controls
= f.number_field :weight_quantity, :class => 'input-small'
= f.select(:weight_unit, Harvest::WEIGHT_UNITS_VALUES, {:include_blank => false}, :class => 'input-medium')
in total
.control-group
= f.label 'Notes', :class => 'control-label'
.controls= f.text_area :description, :rows => 6
.form-actions
= f.submit 'Save', :class => 'btn btn-primary'

View File

@@ -0,0 +1,7 @@
- content_for :title, "Editing harvest"
= render 'form'
= link_to 'Show', @harvest
\|
= link_to 'Back', harvests_path

View File

@@ -0,0 +1,41 @@
csv.headers :id,
:growstuff_url,
:owner_id,
:owner_name,
:crop_id,
:crop_name,
:quantity,
:unit,
:weight_quantity,
:weight_unit,
:date_harvested,
:description,
:date_added,
:last_modified,
:license
@harvests.each do |h|
csv.row h do |csv, harvest|
csv.cell :id
csv.cell :growstuff_url, harvest_url(h)
csv.cell :owner_id, h.owner.id
csv.cell :owner_name, h.owner.to_s
csv.cell :crop_id, h.crop.id
csv.cell :crop_name, h.crop.to_s
csv.cells :quantity, :unit, :weight_quantity, :weight_unit
csv.cell :date_harvested, h.created_at.to_s(:db)
csv.cell :description
csv.cell :date_added, h.created_at.to_s(:db)
csv.cell :last_modified, h.updated_at.to_s(:db)
csv.cell :license, "CC-BY-SA Growstuff http://growstuff.org/"
end
end

View File

@@ -0,0 +1,60 @@
- content_for :title, @owner ? "#{@owner}'s harvests" : "Everyone's harvests"
%p
#{Growstuff::Application.config.site_name} helps you track what you're
harvesting from your home garden and see how productive it is.
%p
- if can? :create, Harvest
- if @owner
%p
- if @owner == current_member
= link_to 'Add harvest', new_harvest_path, :class => 'btn btn-primary'
= link_to "View everyone's harvests", harvests_path, :class => 'btn'
- else # everyone's harvests
= link_to 'Add harvest', new_harvest_path, :class => 'btn btn-primary'
- if current_member
= link_to 'View your harvests', harvests_by_owner_path(:owner => current_member.slug), :class => 'btn'
- else
= render :partial => 'shared/signin_signup', :locals => { :to => 'track your harvests' }
%div.pagination
= page_entries_info @harvests, :model => "harvests"
= will_paginate @harvests
- if @harvests.length > 0
%table.table.table-striped
%tr
- unless @owner
%th Owner
%th Crop
%th Date
%th Quantity
%th Description
%th
- @harvests.each do |harvest|
%tr
- unless @owner
%td= link_to harvest.owner.login_name, harvest.owner
%td= link_to harvest.crop.system_name, harvest.crop
%td= harvest.harvested_at
%td
- if harvest.quantity
= display_quantity(harvest)
%td= harvest.description
%td= link_to 'Details', harvest, :class => 'btn btn-mini'
%div.pagination
= page_entries_info @harvests, :model => "harvests"
= will_paginate @harvests
%ul.inline
%li The data on this page is available in the following formats:
- if @owner
%li= link_to "CSV", harvests_by_owner_path(@owner, :format => 'csv')
%li= link_to "JSON", harvests_by_owner_path(@owner, :format => 'json')
- else
%li= link_to "CSV", harvests_path(:format => 'csv')
%li= link_to "JSON", harvests_path(:format => 'json')

View File

@@ -0,0 +1,3 @@
- content_for :title, "New Harvest"
= render 'form'

View File

@@ -0,0 +1,30 @@
=content_for :title, "#{@harvest.crop} harvested by #{@harvest.owner}"
.row-fluid
.span6
%p
%b Owner:
= link_to @harvest.owner, @harvest.owner
&mdash;
= link_to "view all #{@harvest.owner}'s harvests", harvests_by_owner_path(:owner => @harvest.owner.slug)
%p
%b Harvested:
= @harvest.harvested_at ? @harvest.harvested_at : "not specified"
%p
%b Quantity:
= display_quantity(@harvest)
- if can? :edit, @harvest or can? :destroy, @harvest
%p
- if can? :edit, @harvest
=link_to 'Edit', edit_harvest_path(@harvest), :class => 'btn btn-mini'
- if can? :destroy, @harvest
=link_to 'Delete', @harvest, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini'
.span6
= render :partial => "crops/index_card", :locals => { :crop => @harvest.crop}
%h2 Notes
:markdown
#{ @harvest.description != "" ? @harvest.description : "No description given." }

View File

@@ -7,13 +7,13 @@
= Growstuff::Application.config.site_name
= current_member
= render :partial => 'stats'
%ul.inline
%li{ :style => 'padding-left: 0px' }
%strong Quick links:
%li= link_to "Plant something", new_planting_path
%li= link_to "Add seeds", new_seed_path
%li= link_to "Post", new_post_path
%li= link_to "Edit profile", edit_member_registration_path
%p
.btn-group
= link_to "Plant", new_planting_path, :class => 'btn'
= link_to "Harvest", new_harvest_path, :class => 'btn'
= link_to "Add seeds", new_seed_path, :class => 'btn'
= link_to "Post", new_post_path, :class => 'btn'
= link_to "Edit profile", edit_member_registration_path, :class => 'btn'
- else
.visible-desktop.visible-tablet

View File

@@ -34,6 +34,7 @@
%li= link_to "Profile", member_path(current_member)
%li= link_to "Gardens", gardens_by_owner_path(:owner => current_member.slug)
%li= link_to "Plantings", plantings_by_owner_path(:owner => current_member.slug)
%li= link_to "Harvests", harvests_by_owner_path(:owner => current_member.slug)
%li= link_to "Seeds", seeds_by_owner_path(:owner => current_member.slug)
%li= link_to "Posts", posts_by_author_path(:author => current_member.slug)
%li= link_to "Account", orders_path

View File

@@ -0,0 +1,45 @@
csv.headers :id,
:growstuff_url,
:owner_id,
:owner_name,
:garden_id,
:garden_name,
:crop_id,
:crop_name,
:quantity,
:planted_from,
:sunniness,
:date_planted,
:description,
:date_added,
:last_modified,
:license
@plantings.each do |p|
csv.row p do |csv, planting|
csv.cell :id
csv.cell :growstuff_url, planting_url(p)
csv.cell :owner_id, p.owner.id
csv.cell :owner_name, p.owner.to_s
csv.cell :garden_id, p.garden.id
csv.cell :garden_name, p.garden.to_s
csv.cell :crop_id, p.crop.id
csv.cell :crop_name, p.crop.to_s
csv.cells :quantity, :planted_from, :sunniness
csv.cell :date_planted, p.planted_at ? p.planted_at.to_s(:db) : ''
csv.cell :description
csv.cell :date_added, p.created_at.to_s(:db)
csv.cell :last_modified, p.updated_at.to_s(:db)
csv.cell :license, "CC-BY-SA Growstuff http://growstuff.org/"
end
end

View File

@@ -51,3 +51,14 @@
%div.pagination
= page_entries_info @plantings, :model => "plantings"
= will_paginate @plantings
%ul.inline
%li The data on this page is available in the following formats:
- if @owner
%li= link_to "CSV", plantings_by_owner_path(@owner, :format => 'csv')
%li= link_to "JSON", plantings_by_owner_path(@owner, :format => 'json')
%li= link_to "RSS", plantings_by_owner_path(@owner, :format => 'rss')
- else
%li= link_to "CSV", plantings_path(:format => 'csv')
%li= link_to "JSON", plantings_path(:format => 'json')
%li= link_to "RSS", plantings_path(:format => 'rss')

View File

@@ -2,6 +2,11 @@
.row-fluid
.span6
%p
%b Owner:
= link_to @planting.owner, @planting.owner
&mdash;
= link_to "view all #{@planting.owner}'s plantings", plantings_by_owner_path(:owner => @planting.owner.slug)
%p
%b Planted:
= @planting.planted_at ? @planting.planted_at : "not specified"
@@ -41,9 +46,6 @@
:markdown
#{ @planting.description != "" ? @planting.description : "No description given." }
- if can? :edit, @planting
= link_to 'Edit', edit_planting_path(@planting), :class => 'btn btn-mini'
- if @planting.photos.count > 0 or (can? :edit, @planting and can? :create, Photo)
%h2 Pictures

View File

@@ -0,0 +1,47 @@
csv.headers :id,
:growstuff_url,
:owner_id,
:owner_name,
:crop_id,
:crop_name,
:quantity,
:plant_before,
:tradable_to,
:from_location,
:latitude,
:longitude,
:description,
:date_added,
:last_modified,
:license
@seeds.each do |s|
csv.row s do |csv, seed|
csv.cell :id
csv.cell :growstuff_url, seed_url(s)
csv.cell :owner_id, s.owner.id
csv.cell :owner_name, s.owner.to_s
csv.cell :crop_id, s.crop.id
csv.cell :crop_name, s.crop.to_s
csv.cell :quantity
csv.cell :plant_before, s.plant_before ? s.plant_before.to_s(:db) : ''
csv.cell :tradable_to
csv.cell :from_location, s.owner.location
csv.cell :latitude, s.owner.latitude
csv.cell :longitude, s.owner.longitude
csv.cell :description
csv.cell :date_added, s.created_at.to_s(:db)
csv.cell :last_modified, s.updated_at.to_s(:db)
csv.cell :license, "CC-BY-SA Growstuff http://growstuff.org/"
end
end

View File

@@ -56,3 +56,14 @@
%div.pagination
= page_entries_info @seeds, :model => "seeds"
= will_paginate @seeds
%ul.inline
%li The data on this page is available in the following formats:
- if @owner
%li= link_to "CSV", seeds_by_owner_path(@owner, :format => 'csv')
%li= link_to "JSON", seeds_by_owner_path(@owner, :format => 'json')
%li= link_to "RSS", seeds_by_owner_path(@owner, :format => 'rss')
- else
%li= link_to "CSV", seeds_path(:format => 'csv')
%li= link_to "JSON", seeds_path(:format => 'json')
%li= link_to "RSS", seeds_path(:format => 'rss')

View File

@@ -6,7 +6,7 @@
%b Owner:
= link_to @seed.owner, @seed.owner
&mdash;
= link_to "view all #{@seed.owner}'s seeds", seeds_path(:owner_id => @seed.owner.id)
= link_to "view all #{@seed.owner}'s seeds", seeds_by_owner_path(:owner => @seed.owner.slug)
%p
%b Quantity:
= @seed.quantity.blank? ? "not specified" : @seed.quantity

View File

@@ -16,6 +16,9 @@ Growstuff::Application.routes.draw do
resources :seeds
match '/seeds/owner/:owner' => 'seeds#index', :as => 'seeds_by_owner'
resources :harvests
match '/harvests/owner/:owner' => 'harvests#index', :as => 'harvests_by_owner'
resources :posts
match '/posts/author/:author' => 'posts#index', :as => 'posts_by_author'

View File

@@ -0,0 +1,14 @@
class CreateHarvests < ActiveRecord::Migration
def change
create_table :harvests do |t|
t.integer :crop_id, :null => false
t.integer :owner_id, :null => false
t.date :harvested_at
t.decimal :quantity
t.string :units
t.text :notes
t.timestamps
end
end
end

View File

@@ -0,0 +1,5 @@
class ChangeHarvestNotesToDescription < ActiveRecord::Migration
def change
rename_column :harvests, :notes, :description
end
end

View File

@@ -0,0 +1,5 @@
class ChangeHarvestUnitsToUnit < ActiveRecord::Migration
def change
rename_column :harvests, :units, :unit
end
end

View File

@@ -0,0 +1,5 @@
class AddSlugToHarvests < ActiveRecord::Migration
def change
add_column :harvests, :slug, :string
end
end

View File

@@ -0,0 +1,6 @@
class AddWeightToHarvests < ActiveRecord::Migration
def change
add_column :harvests, :weight_quantity, :decimal
add_column :harvests, :weight_unit, :string
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 => 20130913015118) do
ActiveRecord::Schema.define(:version => 20130925050304) do
create_table "account_types", :force => true do |t|
t.string "name", :null => false
@@ -87,6 +87,20 @@ ActiveRecord::Schema.define(:version => 20130913015118) do
add_index "gardens", ["owner_id"], :name => "index_gardens_on_user_id"
add_index "gardens", ["slug"], :name => "index_gardens_on_slug", :unique => true
create_table "harvests", :force => true do |t|
t.integer "crop_id", :null => false
t.integer "owner_id", :null => false
t.date "harvested_at"
t.decimal "quantity"
t.string "unit"
t.text "description"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "slug"
t.decimal "weight_quantity"
t.string "weight_unit"
end
create_table "members", :force => true do |t|
t.string "email", :default => "", :null => false
t.string "encrypted_password", :default => "", :null => false

View File

@@ -0,0 +1,141 @@
require 'spec_helper'
describe HarvestsController do
login_member
def valid_attributes
{
:owner_id => subject.current_member.id,
:crop_id => FactoryGirl.create(:crop).id
}
end
describe "GET index" do
it "assigns all harvests as @harvests" do
harvest = Harvest.create! valid_attributes
get :index, {}
assigns(:harvests).should eq([harvest])
end
end
describe "GET show" do
it "assigns the requested harvest as @harvest" do
harvest = Harvest.create! valid_attributes
get :show, {:id => harvest.to_param}
assigns(:harvest).should eq(harvest)
end
end
describe "GET new" do
it "assigns a new harvest as @harvest" do
get :new, {}
assigns(:harvest).should be_a_new(Harvest)
end
end
describe "GET edit" do
it "assigns the requested harvest as @harvest" do
harvest = Harvest.create! valid_attributes
get :edit, {:id => harvest.to_param}
assigns(:harvest).should eq(harvest)
end
end
describe "POST create" do
describe "with valid params" do
it "creates a new Harvest" do
expect {
post :create, {:harvest => valid_attributes}
}.to change(Harvest, :count).by(1)
end
it "assigns a newly created harvest as @harvest" do
post :create, {:harvest => valid_attributes}
assigns(:harvest).should be_a(Harvest)
assigns(:harvest).should be_persisted
end
it "redirects to the created harvest" do
post :create, {:harvest => valid_attributes}
response.should redirect_to(Harvest.last)
end
end
describe "with invalid params" do
it "assigns a newly created but unsaved harvest as @harvest" do
# Trigger the behavior that occurs when invalid params are submitted
Harvest.any_instance.stub(:save).and_return(false)
post :create, {:harvest => { "crop_id" => "invalid value" }}
assigns(:harvest).should be_a_new(Harvest)
end
it "re-renders the 'new' template" do
# Trigger the behavior that occurs when invalid params are submitted
Harvest.any_instance.stub(:save).and_return(false)
post :create, {:harvest => { "crop_id" => "invalid value" }}
response.should render_template("new")
end
end
end
describe "PUT update" do
describe "with valid params" do
it "updates the requested harvest" do
harvest = Harvest.create! valid_attributes
# Assuming there are no other harvests in the database, this
# specifies that the Harvest created on the previous line
# receives the :update_attributes message with whatever params are
# submitted in the request.
Harvest.any_instance.should_receive(:update_attributes).with({ "crop_id" => "1" })
put :update, {:id => harvest.to_param, :harvest => { "crop_id" => "1" }}
end
it "assigns the requested harvest as @harvest" do
harvest = Harvest.create! valid_attributes
put :update, {:id => harvest.to_param, :harvest => valid_attributes}
assigns(:harvest).should eq(harvest)
end
it "redirects to the harvest" do
harvest = Harvest.create! valid_attributes
put :update, {:id => harvest.to_param, :harvest => valid_attributes}
response.should redirect_to(harvest)
end
end
describe "with invalid params" do
it "assigns the harvest as @harvest" do
harvest = Harvest.create! valid_attributes
# Trigger the behavior that occurs when invalid params are submitted
Harvest.any_instance.stub(:save).and_return(false)
put :update, {:id => harvest.to_param, :harvest => { "crop_id" => "invalid value" }}
assigns(:harvest).should eq(harvest)
end
it "re-renders the 'edit' template" do
harvest = Harvest.create! valid_attributes
# Trigger the behavior that occurs when invalid params are submitted
Harvest.any_instance.stub(:save).and_return(false)
put :update, {:id => harvest.to_param, :harvest => { "crop_id" => "invalid value" }}
response.should render_template("edit")
end
end
end
describe "DELETE destroy" do
it "destroys the requested harvest" do
harvest = Harvest.create! valid_attributes
expect {
delete :destroy, {:id => harvest.to_param}
}.to change(Harvest, :count).by(-1)
end
it "redirects to the harvests list" do
harvest = Harvest.create! valid_attributes
delete :destroy, {:id => harvest.to_param}
response.should redirect_to(harvests_url)
end
end
end

View File

@@ -0,0 +1,14 @@
# Read about factories at https://github.com/thoughtbot/factory_girl
FactoryGirl.define do
factory :harvest do
crop
owner
harvested_at "2013-09-17"
quantity "3"
unit "individual"
weight_quantity 6
weight_unit "kg"
description "A lovely harvest"
end
end

View File

@@ -0,0 +1,79 @@
require 'spec_helper'
describe HarvestsHelper do
describe "display_quantity" do
it "blank" do
harvest = FactoryGirl.create(:harvest,
:quantity => nil,
:weight_quantity => nil
)
result = helper.display_quantity(harvest)
result.should eq 'not specified'
end
it '3 individual' do
harvest = FactoryGirl.create(:harvest,
:quantity => 3,
:unit => 'individual',
:weight_quantity => nil
)
result = helper.display_quantity(harvest)
result.should eq '3'
end
it '1 bunch' do
harvest = FactoryGirl.create(:harvest,
:quantity => 1,
:unit => 'bunch',
:weight_quantity => nil
)
result = helper.display_quantity(harvest)
result.should eq '1 bunch'
end
it '3 bunches' do
harvest = FactoryGirl.create(:harvest,
:quantity => 3,
:unit => 'bunch',
:weight_quantity => nil
)
result = helper.display_quantity(harvest)
result.should eq '3 bunches'
end
it '3 kg' do
harvest = FactoryGirl.create(:harvest,
:quantity => nil,
:unit => nil,
:weight_quantity => 3,
:weight_unit => 'kg'
)
result = helper.display_quantity(harvest)
result.should eq '3 kg'
end
it '3 individual weighing 3 kg' do
harvest = FactoryGirl.create(:harvest,
:quantity => 3,
:unit => 'individual',
:weight_quantity => 3,
:weight_unit => 'kg'
)
result = helper.display_quantity(harvest)
result.should eq '3, weighing 3 kg'
end
it '3 bunches weighing 3 kg' do
harvest = FactoryGirl.create(:harvest,
:quantity => 3,
:unit => 'bunch',
:weight_quantity => 3,
:weight_unit => 'kg'
)
result = helper.display_quantity(harvest)
result.should eq '3 bunches, weighing 3 kg'
end
end
end

View File

@@ -235,4 +235,12 @@ describe Crop do
end
context "harvests" do
it "has harvests" do
crop = FactoryGirl.create(:crop)
harvest = FactoryGirl.create(:harvest, :crop => crop)
crop.harvests.should eq [harvest]
end
end
end

120
spec/models/harvest_spec.rb Normal file
View File

@@ -0,0 +1,120 @@
require 'spec_helper'
describe Harvest do
it "has an owner" do
harvest = FactoryGirl.create(:harvest)
harvest.owner.should be_an_instance_of Member
end
it "has a crop" do
harvest = FactoryGirl.create(:harvest)
harvest.crop.should be_an_instance_of Crop
end
context 'quantity' do
it 'allows numeric quantities' do
@harvest = FactoryGirl.build(:harvest, :quantity => 33)
@harvest.should be_valid
end
it 'allows decimal quantities' do
@harvest = FactoryGirl.build(:harvest, :quantity => 3.3)
@harvest.should be_valid
end
it 'allows blank quantities' do
@harvest = FactoryGirl.build(:harvest, :quantity => '')
@harvest.should be_valid
end
it 'allows nil quantities' do
@harvest = FactoryGirl.build(:harvest, :quantity => nil)
@harvest.should be_valid
end
it 'cleans up zero quantities' do
@harvest = FactoryGirl.build(:harvest, :quantity => 0)
@harvest.quantity.should == 0
end
it "doesn't allow non-numeric quantities" do
@harvest = FactoryGirl.build(:harvest, :quantity => "99a")
@harvest.should_not be_valid
end
end
context 'units' do
Harvest::UNITS_VALUES.values.push(nil, '').each do |s|
it "#{s} should be a valid unit" do
@harvest = FactoryGirl.build(:harvest, :unit => s)
@harvest.should be_valid
end
end
it 'should refuse invalid unit values' do
@harvest = FactoryGirl.build(:harvest, :unit => 'not valid')
@harvest.should_not be_valid
@harvest.errors[:unit].should include("not valid is not a valid unit")
end
it 'sets unit to blank if quantity is blank' do
@harvest = FactoryGirl.build(:harvest, :quantity => '', :unit => 'individual')
@harvest.should be_valid
@harvest.unit.should eq nil
end
end
context 'weight quantity' do
it 'allows numeric weight quantities' do
@harvest = FactoryGirl.build(:harvest, :weight_quantity => 33)
@harvest.should be_valid
end
it 'allows decimal weight quantities' do
@harvest = FactoryGirl.build(:harvest, :weight_quantity => 3.3)
@harvest.should be_valid
end
it 'allows blank weight quantities' do
@harvest = FactoryGirl.build(:harvest, :weight_quantity => '')
@harvest.should be_valid
end
it 'allows nil weight quantities' do
@harvest = FactoryGirl.build(:harvest, :weight_quantity => nil)
@harvest.should be_valid
end
it 'cleans up zero quantities' do
@harvest = FactoryGirl.build(:harvest, :weight_quantity => 0)
@harvest.weight_quantity.should == 0
end
it "doesn't allow non-numeric weight quantities" do
@harvest = FactoryGirl.build(:harvest, :weight_quantity => "99a")
@harvest.should_not be_valid
end
end
context 'weight units' do
it 'all valid units should work' do
['kg', 'lb', nil, ''].each do |s|
@harvest = FactoryGirl.build(:harvest, :weight_unit => s)
@harvest.should be_valid
end
end
it 'should refuse invalid weight unit values' do
@harvest = FactoryGirl.build(:harvest, :weight_unit => 'not valid')
@harvest.should_not be_valid
@harvest.errors[:weight_unit].should include("not valid is not a valid unit")
end
it 'sets weight_unit to blank if quantity is blank' do
@harvest = FactoryGirl.build(:harvest, :weight_quantity => '', :weight_unit => 'kg')
@harvest.should be_valid
@harvest.weight_unit.should eq nil
end
end
end

View File

@@ -373,4 +373,12 @@ describe 'member' do
end
end
context 'harvests' do
it 'has harvests' do
member = FactoryGirl.create(:member)
harvest = FactoryGirl.create(:harvest, :owner => member)
member.harvests.should eq [harvest]
end
end
end

View File

@@ -0,0 +1,11 @@
require 'spec_helper'
describe "Harvests" do
describe "GET /harvests" do
it "works! (now write some real specs)" do
# Run the generator again with the --webrat flag if you want to use webrat methods/matchers
get harvests_path
response.status.should be(200)
end
end
end

View File

@@ -0,0 +1,35 @@
require "spec_helper"
describe HarvestsController do
describe "routing" do
it "routes to #index" do
get("/harvests").should route_to("harvests#index")
end
it "routes to #new" do
get("/harvests/new").should route_to("harvests#new")
end
it "routes to #show" do
get("/harvests/1").should route_to("harvests#show", :id => "1")
end
it "routes to #edit" do
get("/harvests/1/edit").should route_to("harvests#edit", :id => "1")
end
it "routes to #create" do
post("/harvests").should route_to("harvests#create")
end
it "routes to #update" do
put("/harvests/1").should route_to("harvests#update", :id => "1")
end
it "routes to #destroy" do
delete("/harvests/1").should route_to("harvests#destroy", :id => "1")
end
end
end

View File

@@ -45,4 +45,13 @@ describe "crops/index" do
rendered.should contain "New Crop"
end
end
context "downloads" do
it "offers a CSV download" do
render
rendered.should contain "The data on this page is available in the following formats:"
assert_select "a", :href => crops_path(:format => 'csv')
assert_select "a", :href => crops_path(:format => 'json')
end
end
end

View File

@@ -187,6 +187,10 @@ describe "crops/show" do
rendered.should contain "Plant this"
end
it "shows a harvest this button" do
rendered.should contain "Harvest this"
end
it "links to the right crop in the planting link" do
assert_select("a[href=#{new_planting_path}?crop_id=#{@crop.id}]")
end

View File

@@ -0,0 +1,19 @@
require 'spec_helper'
describe "harvests/edit" do
before(:each) do
assign(:harvest, FactoryGirl.create(:harvest))
render
end
it "renders new harvest form" do
assert_select "form", :action => harvests_path, :method => "post" do
assert_select "select#harvest_crop_id", :name => "harvest[crop_id]"
assert_select "input#harvest_quantity", :name => "harvest[quantity]"
assert_select "input#harvest_weight_quantity", :name => "harvest[quantity]"
assert_select "select#harvest_unit", :name => "harvest[unit]"
assert_select "select#harvest_weight_unit", :name => "harvest[unit]"
assert_select "textarea#harvest_description", :name => "harvest[description]"
end
end
end

View File

@@ -0,0 +1,41 @@
require 'spec_helper'
describe "harvests/index" do
before(:each) do
controller.stub(:current_user) { nil }
@member = FactoryGirl.create(:member)
@tomato = FactoryGirl.create(:tomato)
@maize = FactoryGirl.create(:maize)
page = 1
per_page = 2
total_entries = 2
harvests = WillPaginate::Collection.create(page, per_page, total_entries) do |pager|
pager.replace([
FactoryGirl.create(:harvest,
:crop => @tomato,
:owner => @member
),
FactoryGirl.create(:harvest,
:crop => @maize,
:owner => @member
)
])
end
assign(:harvests, harvests)
render
end
it "renders a list of harvests" do
render
assert_select "tr>td", :text => @member.login_name
assert_select "tr>td", :text => @tomato.system_name
assert_select "tr>td", :text => @maize.system_name
end
it "provides data links" do
render
rendered.should contain "The data on this page is available in the following formats:"
assert_select "a", :href => harvests_path(:format => 'csv')
assert_select "a", :href => harvests_path(:format => 'json')
end
end

View File

@@ -0,0 +1,19 @@
require 'spec_helper'
describe "harvests/new" do
before(:each) do
assign(:harvest, FactoryGirl.create(:harvest))
render
end
it "renders new harvest form" do
assert_select "form", :action => harvests_path, :method => "post" do
assert_select "select#harvest_crop_id", :name => "harvest[crop_id]"
assert_select "input#harvest_quantity", :name => "harvest[quantity]"
assert_select "input#harvest_weight_quantity", :name => "harvest[quantity]"
assert_select "select#harvest_unit", :name => "harvest[unit]"
assert_select "select#harvest_weight_unit", :name => "harvest[unit]"
assert_select "textarea#harvest_description", :name => "harvest[description]"
end
end
end

View File

@@ -0,0 +1,16 @@
require 'spec_helper'
describe "harvests/show" do
before(:each) do
controller.stub(:current_user) { nil }
@crop = FactoryGirl.create(:tomato)
@harvest = assign(:harvest, FactoryGirl.create(:harvest, :crop => @crop))
render
end
it "renders attributes" do
rendered.should contain @crop.system_name
rendered.should contain @harvest.harvested_at.to_s
end
end

View File

@@ -44,4 +44,12 @@ describe "plantings/index" do
rendered.should contain 'January 13, 2013'
end
it "provides data links" do
render
rendered.should contain "The data on this page is available in the following formats:"
assert_select "a", :href => plantings_path(:format => 'csv')
assert_select "a", :href => plantings_path(:format => 'json')
assert_select "a", :href => plantings_path(:format => 'rss')
end
end

View File

@@ -42,4 +42,12 @@ describe "seeds/index" do
assert_select 'a', :href => place_path(@owner.location)
end
end
it "provides data links" do
render
rendered.should contain "The data on this page is available in the following formats:"
assert_select "a", :href => seeds_path(:format => 'csv')
assert_select "a", :href => seeds_path(:format => 'json')
assert_select "a", :href => seeds_path(:format => 'rss')
end
end