This commit is contained in:
Shiho Takagi
2015-02-12 21:09:42 +11:00
69 changed files with 1446 additions and 250 deletions

View File

@@ -52,4 +52,6 @@ submit the change with your pull request.
- Kevin Yang / [kevieyang](https://github.com/kevieyang)
- Justin Hamman / [juzham](https://github.com/juzham)
- Rocky Jaiswal / [rocky-jaiswal](https://github.com/rocky-jaiswal)
- Robert Landreaux / [robertlandreaux](https://github.com/robertlandreaux)
- Savant Krishna / [sksavant](https://github.com/sksavant)

View File

@@ -33,6 +33,8 @@ gem 'gibbon' # for Mailchimp newsletter subscriptions
gem 'csv_shaper' # CSV export
gem 'ruby-units' # for unit conversion
gem 'comfortable_mexican_sofa', '~> 1.12.0' # content management system
# vendored activemerchant for testing- needed for bogus paypal
# gateway monkeypatch
gem 'activemerchant', '1.33.0',

View File

@@ -33,6 +33,8 @@ GEM
activesupport (= 4.1.9)
builder (~> 3.1)
erubis (~> 2.7.0)
active_link_to (1.0.2)
actionpack
activemodel (4.1.9)
activesupport (= 4.1.9)
builder (~> 3.1)
@@ -48,6 +50,9 @@ GEM
tzinfo (~> 1.1)
addressable (2.3.6)
arel (5.0.1.20140414130214)
autoprefixer-rails (5.1.1)
execjs
json
bcrypt (3.1.9)
better_errors (2.0.0)
coderay (>= 1.0.0)
@@ -59,6 +64,10 @@ GEM
bonsai-elasticsearch-rails (0.0.4)
bootstrap-datepicker-rails (1.3.0.2)
railties (>= 3.0)
bootstrap-sass (3.3.3)
autoprefixer-rails (>= 5.0.0.1)
sass (>= 3.2.19)
bootstrap_form (2.2.0)
builder (3.2.2)
byebug (3.5.1)
columnize (~> 0.8)
@@ -74,7 +83,13 @@ GEM
capybara-email (2.4.0)
capybara (~> 2.4)
mail
climate_control (0.0.3)
activesupport (>= 3.0)
cliver (0.3.2)
cocaine (0.5.5)
climate_control (>= 0.0.3, < 1.0)
codemirror-rails (4.8)
railties (>= 3.0, < 5)
coderay (1.1.0)
coffee-rails (4.1.0)
coffee-script (>= 2.2.0)
@@ -84,6 +99,21 @@ GEM
execjs
coffee-script-source (1.8.0)
columnize (0.9.0)
comfortable_mexican_sofa (1.12.7)
active_link_to (>= 1.0.0)
bootstrap-sass (>= 3.2.0)
bootstrap_form (>= 2.2.0)
codemirror-rails (>= 3.0.0)
coffee-rails (>= 3.1.0)
haml-rails (>= 0.3.0)
jquery-rails (>= 3.0.0)
jquery-ui-rails (>= 5.0.0)
kramdown (>= 1.0.0)
paperclip (>= 4.0.0)
plupload-rails (>= 1.2.1)
rails (>= 4.0.0, < 5)
rails-i18n (>= 4.0.0)
sass-rails (>= 4.0.3)
commonjs (0.2.7)
coveralls (0.7.1)
multi_json (~> 1.3)
@@ -187,6 +217,7 @@ GEM
sprockets-rails
json (1.8.2)
kgio (2.9.2)
kramdown (1.5.0)
launchy (2.4.3)
addressable (~> 2.3)
leaflet-markercluster-rails (0.7.0)
@@ -229,7 +260,14 @@ GEM
multi_json (~> 1.3)
omniauth-oauth (~> 1.0)
orm_adapter (0.5.0)
paperclip (4.2.1)
activemodel (>= 3.0.0)
activesupport (>= 3.0.0)
cocaine (~> 0.5.3)
mime-types
pg (0.17.1)
plupload-rails (1.2.1)
rails (>= 3.1)
poltergeist (1.5.1)
capybara (~> 2.1)
cliver (~> 0.3.1)
@@ -254,6 +292,9 @@ GEM
bundler (>= 1.3.0, < 2.0)
railties (= 4.1.9)
sprockets-rails (~> 2.0)
rails-i18n (4.0.3)
i18n (~> 0.6)
railties (~> 4.0)
rails_12factor (0.0.3)
rails_serve_static_assets
rails_stdout_logging
@@ -367,6 +408,7 @@ DEPENDENCIES
capybara
capybara-email
coffee-rails (~> 4.1.0)
comfortable_mexican_sofa (~> 1.12.0)
coveralls
csv_shaper
dalli

View File

@@ -0,0 +1 @@
# Custom JS for the admin area

View File

@@ -0,0 +1 @@
// custom CSS for admin area

View File

@@ -9,10 +9,12 @@ class CropsController < ApplicationController
@sort = params[:sort]
if @sort == 'alpha'
# alphabetical order
@crops = Crop.includes(:scientific_names, {:plantings => :photos}).paginate(:page => params[:page])
@crops = Crop.includes(:scientific_names, {:plantings => :photos})
@paginated_crops = @crops.paginate(:page => params[:page])
else
# default to sorting by popularity
@crops = Crop.popular.includes(:scientific_names, {:plantings => :photos}).paginate(:page => params[:page])
@crops = Crop.popular.includes(:scientific_names, {:plantings => :photos})
@paginated_crops = @crops.paginate(:page => params[:page])
end
respond_to do |format|
@@ -32,7 +34,18 @@ class CropsController < ApplicationController
# GET /crops/wrangle
def wrangle
@crops = Crop.recent.paginate(:page => params[:page])
@approval_status = params[:approval_status]
case @approval_status
when "pending"
@crops = Crop.pending_approval
when "rejected"
@crops = Crop.rejected
else
@crops = Crop.recent
end
@crops = @crops.paginate(:page => params[:page])
@crop_wranglers = Role.crop_wranglers
respond_to do |format|
format.html
@@ -50,8 +63,9 @@ class CropsController < ApplicationController
# GET /crops/search
def search
@search = params[:search]
@all_matches = Crop.search(params[:search])
exact_match = Crop.find_by_name(params[:search])
@all_matches = Crop.search(@search)
@paginated_matches = @all_matches.paginate(:page => params[:page])
exact_match = Crop.find_by_name(@search)
if exact_match
@all_matches.delete(exact_match)
@all_matches.unshift(exact_match)
@@ -59,7 +73,7 @@ class CropsController < ApplicationController
respond_to do |format|
format.html
format.json { render :json => Crop.search(params[:term]) }
format.json { render :json => Crop.search(@search) }
end
end
@@ -95,17 +109,35 @@ class CropsController < ApplicationController
# GET /crops/1/edit
def edit
@crop = Crop.find(params[:id])
(3 - @crop.scientific_names.length).times do
@crop.scientific_names.build
end
end
# POST /crops
# POST /crops.json
def create
params[:crop][:creator_id] = current_member.id
@crop = Crop.new(crop_params)
if current_member.has_role? :crop_wrangler
@crop.creator = current_member
success_msg = "Crop was successfully created."
else
@crop.requester = current_member
@crop.approval_status = "pending"
success_msg = "Crop was successfully requested."
end
respond_to do |format|
if @crop.save
format.html { redirect_to @crop, notice: 'Crop was successfully created.' }
unless current_member.has_role? :crop_wrangler
Role.crop_wranglers.each do |w|
Notifier.new_crop_request(w, @crop).deliver!
end
end
format.html { redirect_to @crop, notice: success_msg }
format.json { render json: @crop, status: :created, location: @crop }
else
format.html { render action: "new" }
@@ -119,8 +151,18 @@ class CropsController < ApplicationController
def update
@crop = Crop.find(params[:id])
previous_status = @crop.approval_status
@crop.creator = current_member if previous_status == "pending"
respond_to do |format|
if @crop.update(crop_params)
if previous_status == "pending"
requester = @crop.requester
new_status = @crop.approval_status
Notifier.crop_request_approved(requester, @crop).deliver! if new_status == "approved"
Notifier.crop_request_rejected(requester, @crop).deliver! if new_status == "rejected"
end
format.html { redirect_to @crop, notice: 'Crop was successfully updated.' }
format.json { head :no_content }
else
@@ -145,6 +187,6 @@ class CropsController < ApplicationController
private
def crop_params
params.require(:crop).permit(:en_wikipedia_url, :name, :parent_id, :creator_id, :scientific_names_attributes => [:scientific_name])
params.require(:crop).permit(:en_wikipedia_url, :name, :parent_id, :creator_id, :approval_status, :request_notes, :reason_for_rejection, :rejection_notes, :scientific_names_attributes => [:scientific_name, :_destroy, :id])
end
end

View File

@@ -6,7 +6,12 @@ class MembersController < ApplicationController
after_action :expire_cache_fragments, :only => :create
def index
@members = Member.confirmed.paginate(:page => params[:page])
@sort = params[:sort]
if @sort == 'recently_joined'
@members = Member.confirmed.recently_joined.paginate(:page => params[:page])
else
@members = Member.confirmed.paginate(:page => params[:page])
end
respond_to do |format|
format.html # index.html.haml

View File

@@ -110,7 +110,9 @@ class SeedsController < ApplicationController
private
def seed_params
params.require(:seed).permit(:owner_id, :crop_id, :description, :quantity, :plant_before,
:tradable_to, :slug)
params.require(:seed).permit(
:owner_id, :crop_id, :description, :quantity, :plant_before,
:days_until_maturity_min, :days_until_maturity_max, :organic, :gmo,
:heirloom, :tradable_to, :slug)
end
end

View File

@@ -22,4 +22,19 @@ class Notifier < ActionMailer::Base
end
end
def new_crop_request(member, request)
@member, @request = member, request
mail(:to => @member.email, :subject => "#{@request.requester.login_name} has requested #{@request.name} as a new crop")
end
def crop_request_approved(member, crop)
@member, @crop = member, crop
mail(:to => @member.email, :subject => "#{crop.name.capitalize} has been approved")
end
def crop_request_rejected(member, crop)
@member, @crop = member, crop
mail(:to => @member.email, :subject => "#{crop.name.capitalize} has been rejected")
end
end

View File

@@ -21,7 +21,24 @@ class Ability
cannot :read, Account
cannot :read, AccountType
# nobody should be able to view unapproved crops unless they
# are wranglers or admins
cannot :read, Crop
can :read, Crop, :approval_status => "approved"
# scientific names should only be viewable if associated crop is approved
cannot :read, ScientificName
can :read, ScientificName do |sn|
sn.crop.approved?
end
# ... same for alternate names
cannot :read, AlternateName
can :read, AlternateName do |an|
an.crop.approved?
end
if member
# members can see even rejected or pending crops if they requested it
can :read, Crop, :requester_id => member.id
# managing your own user settings
can :update, Member, :id => member.id
@@ -45,6 +62,9 @@ class Ability
can :manage, AlternateName
end
# any member can create a crop provisionally
can :create, Crop
# can create & destroy their own authentications against other sites.
can :create, Authentication
can :destroy, Authentication, :member_id => member.id

View File

@@ -14,6 +14,7 @@ class Crop < ActiveRecord::Base
has_many :harvests
has_many :plant_parts, -> { uniq }, :through => :harvests
belongs_to :creator, :class_name => 'Member'
belongs_to :requester, :class_name => 'Member'
belongs_to :parent, :class_name => 'Crop'
has_many :varieties, :class_name => 'Crop', :foreign_key => 'parent_id'
@@ -21,17 +22,31 @@ class Crop < ActiveRecord::Base
before_destroy {|crop| crop.posts.clear}
default_scope { order("lower(name) asc") }
scope :recent, -> { reorder("created_at desc") }
scope :toplevel, -> { where(:parent_id => nil) }
scope :popular, -> { reorder("plantings_count desc, lower(name) asc") }
scope :randomized, -> { reorder('random()') } # ok on sqlite and psql, but not on mysql
scope :recent, -> { where(:approval_status => "approved").reorder("created_at desc") }
scope :toplevel, -> { where(:approval_status => "approved", :parent_id => nil) }
scope :popular, -> { where(:approval_status => "approved").reorder("plantings_count desc, lower(name) asc") }
scope :randomized, -> { where(:approval_status => "approved").reorder('random()') } # ok on sqlite and psql, but not on mysql
scope :pending_approval, -> { where(:approval_status => "pending") }
scope :rejected, -> { where(:approval_status => "rejected") }
## Wikipedia urls are only necessary when approving a crop
validates :en_wikipedia_url,
:format => {
:with => /\Ahttps?:\/\/en\.wikipedia\.org\/wiki/,
:message => 'is not a valid English Wikipedia URL'
}
},
:if => :approved?
## Reasons are only necessary when rejecting
validates :reason_for_rejection, :presence => true, :if => :rejected?
## This validation addresses a race condition
validate :approval_status_cannot_be_changed_again
validate :must_be_rejected_if_rejected_reasons_present
validate :must_have_meaningful_reason_for_rejection
####################################
# Elastic search configuration
include Elasticsearch::Model
@@ -81,13 +96,18 @@ class Crop < ActiveRecord::Base
include: {
scientific_names: { only: :scientific_name },
alternate_names: { only: :name }
})
})
end
# update the Elasticsearch index (only if we're using it in this
# environment)
def update_index(name_obj)
__elasticsearch__.index_document if ENV['GROWSTUFF_ELASTICSEARCH'] == "true"
if ENV["GROWSTUFF_ELASTICSEARCH"] == "true"
__elasticsearch__.index_document
end
end
####################################
# End Elasticsearch section
def to_s
return name
@@ -160,6 +180,26 @@ class Crop < ActiveRecord::Base
return true
end
def pending?
approval_status == "pending"
end
def approved?
approval_status == "approved"
end
def rejected?
approval_status == "rejected"
end
def approval_statuses
[ 'rejected', 'pending', 'approved' ]
end
def reasons_for_rejection
[ "already in database", "not edible", "not enough information", "other" ]
end
# Crop.interesting
# returns a list of interesting crops, for use on the homepage etc
def Crop.interesting
@@ -276,7 +316,31 @@ class Crop < ActiveRecord::Base
)
return response.records.to_a
else
where("name ILIKE ?", "%#{query}%")
where("name ILIKE ?", "%#{query}%").load
end
end
# Custom validations
def approval_status_cannot_be_changed_again
previous = previous_changes.include?(:approval_status) ? previous_changes.approval_status : {}
if previous.include?(:rejected) || previous.include?(:approved)
errors.add(:approval_status, "has already been set to #{approval_status}")
end
end
def must_be_rejected_if_rejected_reasons_present
unless rejected?
if reason_for_rejection.present? || rejection_notes.present?
errors.add(:approval_status, "must be rejected if a reason for rejection is present")
end
end
end
def must_have_meaningful_reason_for_rejection
if reason_for_rejection == "other" && rejection_notes.blank?
errors.add(:rejection_notes, "must be added if the reason for rejection is \"other\"")
end
end
end

View File

@@ -34,7 +34,9 @@ class Garden < ActiveRecord::Base
}
validates :area,
:numericality => { :only_integer => false, :greater_than_or_equal_to => 0 },
:numericality => {
:only_integer => false,
:greater_than_or_equal_to => 0 },
:allow_nil => true
AREA_UNITS_VALUES = {

View File

@@ -20,10 +20,14 @@ class Harvest < ActiveRecord::Base
default_scope { order('created_at DESC') }
validates :crop, :approved => true
validates :crop, :presence => {:message => "must be present and exist in our database"}
validates :quantity,
:numericality => { :only_integer => false },
:numericality => {
:only_integer => false,
:greater_than_or_equal_to => 0 },
:allow_nil => true
UNITS_VALUES = {

View File

@@ -32,6 +32,7 @@ class Member < ActiveRecord::Base
scope :confirmed, -> { where('confirmed_at IS NOT NULL') }
scope :located, -> { where("location <> '' and latitude IS NOT NULL and longitude IS NOT NULL") }
scope :recently_signed_in, -> { reorder('updated_at DESC') }
scope :recently_joined, -> { reorder("confirmed_at desc") }
scope :wants_newsletter, -> { where(:newsletter => true) }
has_many :follows, :class_name => "Follow", :foreign_key => "follower_id"

View File

@@ -30,10 +30,14 @@ class Planting < ActiveRecord::Base
default_scope { order("created_at desc") }
validates :crop_id, :presence => {:message => "must be present and exist in our database"}
validates :crop, :approved => true
validates :crop, :presence => {:message => "must be present and exist in our database"}
validates :quantity,
:numericality => { :only_integer => true },
:numericality => {
:only_integer => true,
:greater_than_or_equal_to => 0 },
:allow_nil => true
SUNNINESS_VALUES = %w(sun semi-shade shade)

View File

@@ -2,8 +2,11 @@ class Product < ActiveRecord::Base
has_and_belongs_to_many :orders
belongs_to :account_type
validates :paid_months, :numericality => { :only_integer => true,
:allow_nil => true }
validates :paid_months,
:numericality => {
:only_integer => true,
:greater_than_or_equal_to => 0 },
:allow_nil => true
def to_s
name

View File

@@ -7,9 +7,23 @@ class Seed < ActiveRecord::Base
default_scope { order("created_at desc") }
validates :crop, :approved => true
validates :crop, :presence => {:message => "must be present and exist in our database"}
validates :quantity,
:numericality => { :only_integer => true },
:numericality => {
:only_integer => true,
:greater_than_or_equal_to => 0 },
:allow_nil => true
validates :days_until_maturity_min,
:numericality => {
:only_integer => true,
:greater_than_or_equal_to => 0 },
:allow_nil => true
validates :days_until_maturity_max,
:numericality => {
:only_integer => true,
:greater_than_or_equal_to => 0 },
:allow_nil => true
scope :tradable, -> { where("tradable_to != 'nowhere'") }
@@ -20,6 +34,32 @@ class Seed < ActiveRecord::Base
:allow_nil => false,
:allow_blank => false
ORGANIC_VALUES = [
'certified organic',
'non-certified organic',
'conventional/non-organic',
'unknown']
validates :organic, :inclusion => { :in => ORGANIC_VALUES,
:message => "You must say whether the seeds are organic or not, or that you don't know" },
:allow_nil => false,
:allow_blank => false
GMO_VALUES = [
'certified GMO-free',
'non-certified GMO-free',
'GMO',
'unknown']
validates :gmo, :inclusion => { :in => GMO_VALUES,
:message => "You must say whether the seeds are genetically modified or not, or that you don't know" },
:allow_nil => false,
:allow_blank => false
HEIRLOOM_VALUES = %w(heirloom hybrid unknown)
validates :heirloom, :inclusion => { :in => HEIRLOOM_VALUES,
:message => "You must say whether the seeds are heirloom, hybrid, or unknown" },
:allow_nil => false,
:allow_blank => false
def tradable?
if self.tradable_to == 'nowhere'
return false

View File

@@ -0,0 +1,7 @@
class ApprovedValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless record.crop.try(:approved?)
record.errors[attribute] << (options[:message] || 'must be approved')
end
end
end

View File

@@ -8,6 +8,7 @@
%li= link_to "Roles", roles_path
%li= link_to "Forums", forums_path
%li= link_to "Newsletter subscribers", admin_newsletter_path
%li= link_to "CMS", comfy_admin_cms_path
%h2 Orders

View File

@@ -6,40 +6,87 @@
- @crop.errors.full_messages.each do |msg|
%li= msg
%p
%span.help-block
For detailed crop wrangling guidelines, please consult the
=link_to "crop wrangling guide", "http://wiki.growstuff.org/index.php/Crop_wrangling"
on the Growstuff wiki.
- if can? :wrangle, @crop
%p
%span.help-block
For detailed crop wrangling guidelines, please consult the
=link_to "crop wrangling guide", "http://wiki.growstuff.org/index.php/Crop_wrangling"
on the Growstuff wiki.
.form-group
= f.label :name, :class => 'control-label col-md-2'
.col-md-8
= f.text_field :name, :class => 'form-control'
%span.help-block Name in US English; singular; capitalize proper nouns only.
%span.help-block
- if can? :wrangle, @crop
Name in US English; singular; capitalize proper nouns only.
- else
Please provide the common name for the crop, in English, if you know it.
.form-group
= f.label :en_wikipedia_url, 'Wikipedia URL', :class => 'control-label col-md-2'
.col-md-8
= f.text_field :en_wikipedia_url, :class => 'form-control'
%span.help-block Link to this crop's page on the English language Wikipedia.
.form-group
= f.label :parent_id, 'Parent crop', :class => 'control-label col-md-2'
.col-md-8
= collection_select(:crop, :parent_id, Crop.all, :id, :name, {:include_blank => true}, :class => 'form-control')
%span.help-block Optional. For setting up crop hierarchies for varieties etc.
%p
%span.help-block
You may enter up to 3 scientific names for a crop. Most crops will have only one.
= f.fields_for :scientific_names do |sn|
%span.help-block
- if can? :wrangle, @crop
Link to this crop's page on the English language Wikipedia.
- else
Please provide a link to the crop's page on the English language Wikipedia. Crops without Wikipedia pages cannot be added to our database at this time.
- if can? :wrangle, @crop
.form-group
= sn.label :scientific_name, "Scientific name", :class => 'control-label col-md-2'
= f.label :parent_id, 'Parent crop', :class => 'control-label col-md-2'
.col-md-8
= sn.text_field :scientific_name, :class => 'form-control'
.col-md-2
- if sn.object && sn.object.persisted?
%label.checkbox
= sn.check_box :_destroy
= sn.label :_destroy, "Delete"
= collection_select(:crop, :parent_id, Crop.all, :id, :name, {:include_blank => true}, :class => 'form-control')
%span.help-block Optional. For setting up crop hierarchies for varieties etc.
%p
%span.help-block
You may enter up to 3 scientific names for a crop. Most crops will have only one.
= f.fields_for :scientific_names do |sn|
.form-group
= sn.label :scientific_name, "Scientific name", :class => 'control-label col-md-2'
.col-md-8
= sn.text_field :scientific_name, :class => 'form-control'
.col-md-2
- if sn.object && sn.object.persisted?
%label.checkbox
= sn.check_box :_destroy
= sn.label :_destroy, "Delete"
- unless @crop.new_record?
.form-group
= f.label :approval_status, 'Approval Status', :class=> 'control-label col-md-2'
.col-md-8
= f.select(:approval_status, @crop.approval_statuses, {}, {:class => 'form-control'})
.form-group
= f.label :reason_for_rejection, 'Reason for rejection', :class => 'control-label col-md-2'
.col-md-8
= f.select(:reason_for_rejection, @crop.reasons_for_rejection, {:include_blank => true}, {:class => 'form-control'})
%p
%span.help-block
Please provide additional notes why this crop request was rejected if the above reasons do not apply.
.form-group
= f.label :rejection_notes, 'Rejection Notes', :class => 'control-label col-md-2'
.col-md-8= f.text_area :rejection_notes, :rows => 6, :class => 'form-control'
- else
%p
%span.help-block
Provide any additional information that might help us assess your request.
.form-group
= f.label :request_notes, 'Comments', :class => 'control-label col-md-2'
.col-md-8= f.text_area :request_notes, :rows => 6, :class => 'form-control'
%p
%span.help-block
When you submit this form, your suggestion will be sent to our team of #{link_to 'volunteer crop wranglers', 'http://talk.growstuff.org/c/crop-wrangling'} for review. We'll let you know the outcome as soon as we can.
.form-group
.form-actions.col-md-offset-2.col-md-8
= f.submit 'Save', :class => 'btn btn-primary'

View File

@@ -1,4 +1,4 @@
- content_for :title, "Editing crop"
- content_for :title, "Review crop: #{@crop.name}"
%p
Added by

View File

@@ -1,5 +1,8 @@
- content_for :title, "Crops"
- content_for :title, "Browse crops"
- content_for :subtitle, "#{@crops.size} total"
- if can? :wrangle, Crop
= link_to 'Wrangle Crops', wrangle_crops_path, :class => 'btn btn-primary'
%p
#{ENV['GROWSTUFF_SITE_NAME']} tracks who's growing what, where.
View any crop page to see which of our members have planted it and find
@@ -12,11 +15,10 @@
= submit_tag "Show", :class => 'btn btn-primary'
%div.pagination
= page_entries_info @crops, :model => "crops"
= will_paginate @crops
= will_paginate @paginated_crops
.row
- @crops.each do |crop|
- @paginated_crops.each do |crop|
.col-md-2.six-across
= render :partial => "thumbnail", :locals => { :crop => crop }
@@ -25,8 +27,7 @@
= link_to 'New Crop', new_crop_path, {:class => 'btn btn-primary'}
%div.pagination
= page_entries_info @crops, :model => "crops"
= will_paginate @crops
= will_paginate @paginated_crops
%ul.list-inline

View File

@@ -1,3 +1,14 @@
- content_for :title, "New crop"
- content_for :title, (can?(:wrangle, @crop) ? "New crop" : "Suggest a crop")
- unless can? :wrangler, @crop
%p Thanks for taking the time to suggest a crop! Our crop database is managed by volunteers, and we appreciate your help. Here are some things to consider when suggesting a new crop:
%ul
%li First, you might want to #{link_to 'search our crops', crops_search_path} to make sure we don't have it already, perhaps under an alternate name.
%li The Growstuff database only contains edible crops. In future we hope to support other crops, but for now, if your suggestion is not edible we won't be able to add it.
%li At this time, we are only adding crops which have a Wikipedia page. If you want to add a specific variety of crop that doesn't have its own Wikipedia entry, please use the more general form of the crop instead and put the name of your variety in the notes/description.
= render 'form'

View File

@@ -1,4 +1,16 @@
- content_for :title, "Crops matching #{@search}"
- if @search
- content_for :title, "Crops matching \"#{@search}\""
- if @all_matches
- content_for :subtitle, "#{@all_matches.size} total"
- else
- content_for :title, "Crop search"
%div
= form_tag crops_search_path, :method => :get, :id => 'crop-search', :class => 'form-inline' do
.form-group
= label_tag :search, "Search crops:", :class => 'sr-only'
= text_field_tag 'search', nil, :class => 'search-query input-medium form-control', :placeholder => 'Search crops', :value => @search
= submit_tag "Search", :class => 'btn btn-primary'
- if @all_matches.empty?
%h2 No results found
@@ -9,10 +21,15 @@
= link_to "browsing our crop database", crops_path
instead.
- else
%div#all_matches
- else
%div.pagination
= will_paginate @paginated_matches
%div#paginated_matches
.row
- @all_matches.each do |c|
- @paginated_matches.each do |c|
.col-md-2.six-across
= render :partial => "thumbnail", :locals => { :crop => c }
%div.pagination
= will_paginate @paginated_matches

View File

@@ -1,14 +1,28 @@
- content_for :title, @crop.name
- content_for :subtitle, @crop.default_scientific_name
- content_for :buttonbar do
- if can? :create, Planting
= link_to "Plant this", new_planting_path(:crop_id => @crop.id), :class => 'btn btn-default'
- if can? :create, Harvest
= link_to "Harvest this", new_harvest_path(:crop_id => @crop.id), :class => 'btn btn-default'
- if @crop.pending?
.alert.alert-danger
%b This crop is currently pending approval.
%p This crop was requested by #{@crop.requester} #{time_ago_in_words(@crop.created_at)} ago.
- unless @crop.request_notes.blank?
%p
Request notes: #{@crop.request_notes}
- if can? :create, Seed
= link_to 'Add seeds to stash', new_seed_path(:params => { :crop_id => @crop.id }), :class => 'btn btn-default'
- if @crop.rejected?
.alert.alert-danger
%b This crop was rejected for the following reason: #{@crop.reason_for_rejection == "other" ? @crop.rejection_notes : @crop.reason_for_rejection}.
- if @crop.approved?
- content_for :buttonbar do
- if can? :create, Planting
= link_to "Plant this", new_planting_path(:crop_id => @crop.id), :class => 'btn btn-default'
- if can? :create, Harvest
= link_to "Harvest this", new_harvest_path(:crop_id => @crop.id), :class => 'btn btn-default'
- if can? :create, Seed
= link_to 'Add seeds to stash', new_seed_path(:params => { :crop_id => @crop.id }), :class => 'btn btn-default'
.row

View File

@@ -15,18 +15,38 @@
%li.crop_wrangler
= link_to crop_wrangler.login_name, crop_wrangler
%h2 Recently added crops
.tabbable
%ul.nav.nav-tabs
%li{:class => @approval_status.blank? ? 'active' : ''}
= link_to "Recently added", wrangle_crops_path
%li{:class => @approval_status == "pending" ? 'active' : ''}
= link_to "Pending approval", wrangle_crops_path(:approval_status => "pending")
%li{:class => @approval_status == "rejected" ? 'active' : ''}
= link_to "Rejected", wrangle_crops_path(:approval_status => "rejected")
%h2
- if @approval_status == "pending"
Requested Crops
- elsif @approval_status == "rejected"
Rejected Crops
- else
Recently added crops
%div.pagination
= page_entries_info @crops, :model => "crops"
= will_paginate @crops
%table.table.table-striped
%table{:class => "table table-striped", :id => @approval_status.blank? ? 'recently-added-crops' : "#{@approval_status}-crops"}
%tr
%th System name
%th English Wikipedia URL
%th Scientific names
%th Added by
%th Requested by
- if @approval_status == "rejected"
%th Rejected by
- if @approval_status != "rejected" && @approval_status != "pending"
%th Added by
%th When
- @crops.each do |c|
%tr
@@ -36,7 +56,9 @@
- c.scientific_names.each do |s|
= link_to s.scientific_name, s
%br/
%td= link_to c.creator, c.creator
%td= c.requester.present? ? (link_to c.requester, c.requester) : "N/A"
- unless @approval_status == "pending"
%td= c.creator.present? ? (link_to c.creator, c.creator) : "N/A"
%td
= distance_of_time_in_words(c.created_at, Time.zone.now)
ago.
@@ -44,3 +66,5 @@
%div.pagination
= page_entries_info @crops, :model => "crops"
= will_paginate @crops

View File

@@ -2,28 +2,8 @@
.container
.row
.col-md-4#about-growstuff
%ul
%li= link_to t('about'), "http://wiki.growstuff.org/index.php/About%20Growstuff"
%li= link_to t('our_values'), "http://wiki.growstuff.org/index.php/Values"
%li= link_to t('open_source'), "https://github.com/Growstuff/growstuff"
%li= link_to t('growstuff_team'), "http://wiki.growstuff.org/index.php/Team"
%li= link_to t('get_involved'), "http://wiki.growstuff.org/index.php/Get_involved"
!= cms_snippet_content(:footer1)
.col-md-4#policies
%ul
%li= link_to t('terms_of_service'), url_for(:controller => '/policy', :action => 'tos')
%li= link_to t('privacy_policy'), url_for(:controller => '/policy', :action => 'privacy')
%li= link_to t('data_use_policy'), url_for(:controller => '/policy', :action => 'api')
%li= link_to t('community_guidelines'), url_for(:controller => '/policy', :action => 'community')
!= cms_snippet_content(:footer2)
.col-md-4#contact
%ul
%li= link_to t('support_'), url_for(:controller => '/support')
%li= link_to t('contact'), url_for(:controller => '/about', :action => 'contact')
%p
= link_to('http://twitter.com/growstufforg', :target => "_blank") do
= image_tag("twitter_32.png", :alt => 'Twitter: @growstufforg')
&nbsp;
= link_to('https://www.facebook.com/Growstufforg', :target => "_blank") do
= image_tag("facebook_32.png", :alt => 'Facebook')
&nbsp;
= link_to('http://blog.growstuff.org/', :target => "_blank") do
= image_tag("blog_32.png", :alt => 'Growstuff Blog')
!= cms_snippet_content(:footer3)

View File

@@ -65,12 +65,14 @@
%li= link_to "Sign out", destroy_member_session_path, :method => :delete
- else
%li= link_to 'Sign in', new_member_session_path
%li= link_to 'Sign up', new_member_registration_path
%li= link_to 'Sign in', new_member_session_path, :id => 'navbar-signin'
%li= link_to 'Sign up', new_member_registration_path, :id => 'navbar-signup'
= form_tag crops_search_path, :method => :get, :class => 'navbar-form pull-right' do
= form_tag crops_search_path, :method => :get, :id => 'navbar-search', :class => 'navbar-form pull-right' do
.input
= label_tag :search, "Search crop database:", :class => 'sr-only'
= text_field_tag 'search', nil, :class => 'search-query input-medium form-control', :placeholder => 'Search crops'
= submit_tag "Search", :class => 'btn sr-only'
- # anchor tag for accessibility link to skip the navigation menu
%a{:name => 'skipnav'}

View File

@@ -1,5 +1,11 @@
= content_for :title, "#{ENV['GROWSTUFF_SITE_NAME']} members"
= form_tag(members_path, :method => :get, :class => 'form-inline', :role => 'form') do
.form-group
= label_tag :sort, "Sort by:", :class => 'sr-only'
= select_tag "sort", options_for_select({"Sort alphabetically" => 'alpha', "Sort by recently joined" => "recently_joined"}, @sort || 'alpha'), :class => 'form-control'
= submit_tag "Show", :class => 'btn btn-primary'
%div.pagination
= page_entries_info @members, :model => "members"
= will_paginate @members

View File

@@ -0,0 +1,6 @@
- site_name = ENV['GROWSTUFF_SITE_NAME']
%p
The #{site_name} team.
%br/
=link_to root_url, root_url

View File

@@ -0,0 +1,16 @@
- site_name = ENV['GROWSTUFF_SITE_NAME']
%p Hello #{@member.login_name},
%p
Your request for the new crop: #{link_to @crop.name, crop_url(@crop)} has been approved!
%ul
%li
= link_to "Plant #{@crop.name}", new_planting_url(crop_id: @crop.id)
%li
= link_to "Harvest #{@crop.name}", new_harvest_url(crop_id: @crop.id)
%li
= link_to "Stash seeds for #{@crop.name}", new_seed_url(crop_id: @crop.id)
= render :partial => 'signature'

View File

@@ -0,0 +1,9 @@
- site_name = ENV['GROWSTUFF_SITE_NAME']
%p Hello #{@member.login_name},
%p
Your request for the new crop: #{link_to @crop.name, crop_url(@crop)} has been rejected for the following reason.
= @crop.reason_for_rejection
= render :partial => 'signature'

View File

@@ -0,0 +1,21 @@
- site_name = ENV['GROWSTUFF_SITE_NAME']
%p Hello #{@member.login_name},
%p
#{@request.requester.login_name} has requested a new crop on #{site_name}.
%ul
%li Name: #{@request.name}
%li Wikipedia URL: #{@request.en_wikipedia_url.present? ? @request.en_wikipedia_url : "not specified"}
%p
As a crop wrangler, you can #{link_to "approve or reject this request", edit_crop_url(@request)}.
%p
Or, discuss this and other crop wrangling issues in our #{link_to "crop wrangling forum", "http://talk.growstuff.org/c/crop-wrangling"}.
%p
Thanks for your help!
= render :partial => 'signature'

View File

@@ -20,7 +20,4 @@
%br/
= link_to "Turn off these notifications", edit_member_registration_url
%p
The #{site_name} team.
%br/
=link_to root_url, root_url
= render :partial => 'signature'

View File

@@ -59,10 +59,7 @@
%h2
See you soon on #{site_name}!
%p
The #{site_name} team.
%br/
=link_to root_url, root_url
= render :partial => 'signature'
%hr/
%p

View File

@@ -0,0 +1,9 @@
- if seed.days_until_maturity_min.blank?
- if seed.days_until_maturity_max.blank?
unknown
- else
= seed.days_until_maturity_max.to_s
- elsif seed.days_until_maturity_max.blank?
= seed.days_until_maturity_min.to_s
- else
!= "#{seed.days_until_maturity_min}&ndash;#{seed.days_until_maturity_max}"

View File

@@ -21,6 +21,29 @@
= f.label :plant_before, 'Plant before:', :class => 'control-label col-md-2'
.col-md-2
= f.text_field :plant_before, :class => 'add-datepicker form-control', :value => @seed.plant_before ? @seed.plant_before.to_s(:ymd) : ''
.form-group
= f.label :days_until_maturity_min, 'Days until maturity:', :class => 'control-label col-md-2'
%fieldset
.col-md-2
= f.number_field :days_until_maturity_min, :class => 'form-control'
.col-md-1
= f.label :days_until_maturity_max, 'to', :class => 'control-label'
.col-md-2
= f.number_field :days_until_maturity_max, :class => 'form-control'
.col-md-1
= f.label :dummy, 'days', :class => 'control-label'
.form-group
= f.label :organic, 'Organic?', :class => 'control-label col-md-2'
.col-md-8
= f.select(:organic, Seed::ORGANIC_VALUES, {}, :class => 'form-control', :default => 'unknown')
.form-group
= f.label :gmo, 'GMO?', :class => 'control-label col-md-2'
.col-md-8
= f.select(:gmo, Seed::GMO_VALUES, {}, :class => 'form-control', :default => 'unknown')
.form-group
= f.label :heirloom, 'Heirloom?', :class => 'control-label col-md-2'
.col-md-8
= f.select(:heirloom, Seed::HEIRLOOM_VALUES, {}, :class => 'form-control', :default => 'unknown')
.form-group
= f.label :description, 'Description:', :class => 'control-label col-md-2'
.col-md-8

View File

@@ -13,6 +13,18 @@
%p
%b Plant before:
= @seed.plant_before.to_s
%p
%b Days until maturity:
= render :partial => 'days_until_maturity', :locals => { :seed => @seed }
%p
%b Organic?
= @seed.organic
%p
%b GMO?
= @seed.gmo
%p
%b Heirloom?
= @seed.heirloom
%p
%b Will trade:
= @seed.tradable_to

View File

@@ -0,0 +1,103 @@
# encoding: utf-8
ComfortableMexicanSofa.configure do |config|
# Title of the admin area
# config.cms_title = 'ComfortableMexicanSofa CMS Engine'
# Controller that is inherited from CmsAdmin::BaseController
# config.base_controller = 'ApplicationController'
# Module responsible for authentication. You can replace it with your own.
# It simply needs to have #authenticate method. See http_auth.rb for reference.
config.admin_auth = 'CmsDeviseAuth'
# Module responsible for authorization on admin side. It should have #authorize
# method that returns true or false based on params and loaded instance
# variables available for a given controller.
# config.admin_authorization = 'ComfyAdminAuthorization'
# Module responsible for public authentication. Similar to the above. You also
# will have access to @cms_site, @cms_layout, @cms_page so you can use them in
# your logic. Default module doesn't do anything.
# config.public_auth = 'ComfyPublicAuthentication'
# When arriving at /cms-admin you may chose to redirect to arbirtary path,
# for example '/cms-admin/users'
# config.admin_route_redirect = ''
# File uploads use Paperclip and can support filesystem or s3 uploads. Override
# the upload method and appropriate settings based on Paperclip. For S3 see:
# http://rdoc.info/gems/paperclip/2.3.8/Paperclip/Storage/S3, and for
# filesystem see: http://rdoc.info/gems/paperclip/2.3.8/Paperclip/Storage/Filesystem
# If you are using S3 and HTTPS, pass :s3_protocol => '' to have URLs that use the protocol of the page
# config.upload_file_options = {:url => '/system/:class/:id/:attachment/:style/:filename'}
# Sofa allows you to setup entire site from files. Database is updated with each
# request (if necessary). Please note that database entries are destroyed if there's
# no corresponding file. Fixtures are disabled by default.
# config.enable_fixtures = false
# Path where fixtures can be located.
# config.fixtures_path = File.expand_path('db/cms_fixtures', Rails.root)
# Importing fixtures into Database
# To load fixtures into the database just run this rake task:
# local: $ rake comfortable_mexican_sofa:fixtures:import FROM=example.local TO=localhost
# Heroku: $ heroku run rake comfortable_mexican_sofa:fixtures:import FROM=example.local TO=yourapp.herokuapp.com
# From indicates folder the fixtures are in and to is the Site hostname you have defined in the database.
# Exporting fixtures into Files
# If you need to dump database contents into fixture files run:
# local: $ rake comfortable_mexican_sofa:fixtures:export FROM=localhost TO=example.local
# Heroku: $ heroku run rake comfortable_mexican_sofa:fixtures:export FROM=yourapp.herokuapp.com TO=example.local
# This will create example.local folder and dump all content from example.com Site.
# Content for Layouts, Pages and Snippets has a revision history. You can revert
# a previous version using this system. You can control how many revisions per
# object you want to keep. Set it to 0 if you wish to turn this feature off.
# config.revisions_limit = 25
# Locale definitions. If you want to define your own locale merge
# {:locale => 'Locale Title'} with this.
# config.locales = {:en => 'English', :es => 'Español'}
# Admin interface will respect the locale of the site being managed. However you can
# force it to English by setting this to `:en`
# config.admin_locale = nil
# A class that is included as a sweeper to admin base controller if it's set
# config.admin_cache_sweeper = nil
# By default you cannot have irb code inside your layouts/pages/snippets.
# Generally this is to prevent putting something like this:
# <% User.delete_all %> but if you really want to allow it...
# config.allow_irb = false
# Whitelist of all helper methods that can be used via {{cms:helper}} tag. By default
# all helpers are allowed except `eval`, `send`, `call` and few others. Empty array
# will prevent rendering of all helpers.
# config.allowed_helpers = nil
# Whitelist of partials paths that can be used via {{cms:partial}} tag. All partials
# are accessible by default. Empty array will prevent rendering of all partials.
# config.allowed_partials = nil
# Site aliases, if you want to have aliases for your site. Good for harmonizing
# production env with dev/testing envs.
# e.g. config.hostname_aliases = {'host.com' => 'host.inv', 'host_a.com' => ['host.lvh.me', 'host.dev']}
# Default is nil (not used)
# config.hostname_aliases = nil
# Reveal partials that can be overwritten in the admin area.
# Default is false.
# config.reveal_cms_partials = false
end
module CmsDeviseAuth
def authenticate
unless current_member && current_member.has_role?(:admin)
redirect_to root_path, :alert => 'Permission denied. Please sign in as an admin user to use the CMS admin area.'
end
end
end

View File

@@ -1,5 +1,6 @@
Growstuff::Application.routes.draw do
resources :plant_parts
devise_for :members, :controllers => { :registrations => "registrations", :passwords => "passwords" }
@@ -84,6 +85,8 @@ Growstuff::Application.routes.draw do
get '/admin/newsletter' => 'admin#newsletter', :as => :admin_newsletter
get '/admin/:action' => 'admin#:action'
# CMS stuff -- must remain LAST
comfy_route :cms_admin, :path => '/cms/admin'
comfy_route :cms, :path => '/', :sitemap => false
end

View File

@@ -0,0 +1,9 @@
class AddPropertiesToSeeds < ActiveRecord::Migration
def change
add_column :seeds, :days_until_maturity_min, :integer
add_column :seeds, :days_until_maturity_max, :integer
add_column :seeds, :organic, :text, :default => 'unknown'
add_column :seeds, :gmo, :text, :default => 'unknown'
add_column :seeds, :heirloom, :text, :default => 'unknown'
end
end

View File

@@ -0,0 +1,6 @@
class AddRequesterToCrops < ActiveRecord::Migration
def change
add_column :crops, :requester_id, :integer
add_index :crops, :requester_id
end
end

View File

@@ -0,0 +1,140 @@
class CreateCms < ActiveRecord::Migration
def self.up
text_limit = case ActiveRecord::Base.connection.adapter_name
when 'PostgreSQL'
{ }
else
{ :limit => 16777215 }
end
# -- Sites --------------------------------------------------------------
create_table :comfy_cms_sites do |t|
t.string :label, :null => false
t.string :identifier, :null => false
t.string :hostname, :null => false
t.string :path
t.string :locale, :null => false, :default => 'en'
t.boolean :is_mirrored, :null => false, :default => false
end
add_index :comfy_cms_sites, :hostname
add_index :comfy_cms_sites, :is_mirrored
# -- Layouts ------------------------------------------------------------
create_table :comfy_cms_layouts do |t|
t.integer :site_id, :null => false
t.integer :parent_id
t.string :app_layout
t.string :label, :null => false
t.string :identifier, :null => false
t.text :content, text_limit
t.text :css, text_limit
t.text :js, text_limit
t.integer :position, :null => false, :default => 0
t.boolean :is_shared, :null => false, :default => false
t.timestamps
end
add_index :comfy_cms_layouts, [:parent_id, :position]
add_index :comfy_cms_layouts, [:site_id, :identifier], :unique => true
# -- Pages --------------------------------------------------------------
create_table :comfy_cms_pages do |t|
t.integer :site_id, :null => false
t.integer :layout_id
t.integer :parent_id
t.integer :target_page_id
t.string :label, :null => false
t.string :slug
t.string :full_path, :null => false
t.text :content_cache, text_limit
t.integer :position, :null => false, :default => 0
t.integer :children_count, :null => false, :default => 0
t.boolean :is_published, :null => false, :default => true
t.boolean :is_shared, :null => false, :default => false
t.timestamps
end
add_index :comfy_cms_pages, [:site_id, :full_path]
add_index :comfy_cms_pages, [:parent_id, :position]
# -- Page Blocks --------------------------------------------------------
create_table :comfy_cms_blocks do |t|
t.string :identifier, :null => false
t.text :content, text_limit
t.references :blockable, :polymorphic => true
t.timestamps
end
add_index :comfy_cms_blocks, [:identifier]
add_index :comfy_cms_blocks, [:blockable_id, :blockable_type]
# -- Snippets -----------------------------------------------------------
create_table :comfy_cms_snippets do |t|
t.integer :site_id, :null => false
t.string :label, :null => false
t.string :identifier, :null => false
t.text :content, text_limit
t.integer :position, :null => false, :default => 0
t.boolean :is_shared, :null => false, :default => false
t.timestamps
end
add_index :comfy_cms_snippets, [:site_id, :identifier], :unique => true
add_index :comfy_cms_snippets, [:site_id, :position]
# -- Files --------------------------------------------------------------
create_table :comfy_cms_files do |t|
t.integer :site_id, :null => false
t.integer :block_id
t.string :label, :null => false
t.string :file_file_name, :null => false
t.string :file_content_type, :null => false
t.integer :file_file_size, :null => false
t.string :description, :limit => 2048
t.integer :position, :null => false, :default => 0
t.timestamps
end
add_index :comfy_cms_files, [:site_id, :label]
add_index :comfy_cms_files, [:site_id, :file_file_name]
add_index :comfy_cms_files, [:site_id, :position]
add_index :comfy_cms_files, [:site_id, :block_id]
# -- Revisions -----------------------------------------------------------
create_table :comfy_cms_revisions, :force => true do |t|
t.string :record_type, :null => false
t.integer :record_id, :null => false
t.text :data, text_limit
t.datetime :created_at
end
add_index :comfy_cms_revisions, [:record_type, :record_id, :created_at],
:name => 'index_cms_revisions_on_rtype_and_rid_and_created_at'
# -- Categories ---------------------------------------------------------
create_table :comfy_cms_categories, :force => true do |t|
t.integer :site_id, :null => false
t.string :label, :null => false
t.string :categorized_type, :null => false
end
add_index :comfy_cms_categories, [:site_id, :categorized_type, :label], :unique => true,
:name => 'index_cms_categories_on_site_id_and_cat_type_and_label'
create_table :comfy_cms_categorizations, :force => true do |t|
t.integer :category_id, :null => false
t.string :categorized_type, :null => false
t.integer :categorized_id, :null => false
end
add_index :comfy_cms_categorizations, [:category_id, :categorized_type, :categorized_id], :unique => true,
:name => 'index_cms_categorizations_on_cat_id_and_catd_type_and_catd_id'
end
def self.down
drop_table :comfy_cms_sites
drop_table :comfy_cms_layouts
drop_table :comfy_cms_pages
drop_table :comfy_cms_snippets
drop_table :comfy_cms_blocks
drop_table :comfy_cms_files
drop_table :comfy_cms_revisions
drop_table :comfy_cms_categories
drop_table :comfy_cms_categorizations
end
end

View File

@@ -0,0 +1,5 @@
class AddApprovalStatusToCrops < ActiveRecord::Migration
def change
add_column :crops, :approval_status, :string, default: "approved"
end
end

View File

@@ -0,0 +1,5 @@
class AddReasonForRejectionToCrops < ActiveRecord::Migration
def change
add_column :crops, :reason_for_rejection, :text
end
end

View File

@@ -0,0 +1,5 @@
class AddRequestNotesToCrops < ActiveRecord::Migration
def change
add_column :crops, :request_notes, :text
end
end

View File

@@ -0,0 +1,5 @@
class AddRejectionNotesToCrops < ActiveRecord::Migration
def change
add_column :crops, :rejection_notes, :text
end
end

View File

@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150129034206) do
ActiveRecord::Schema.define(version: 20150209105410) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -20,16 +20,16 @@ ActiveRecord::Schema.define(version: 20150129034206) do
t.string "name", null: false
t.boolean "is_paid"
t.boolean "is_permanent_paid"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "accounts", force: true do |t|
t.integer "member_id", null: false
t.integer "account_type_id"
t.datetime "paid_until"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "alternate_names", force: true do |t|
@@ -46,33 +46,158 @@ ActiveRecord::Schema.define(version: 20150129034206) do
t.string "uid"
t.string "token"
t.string "secret"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.string "name"
end
add_index "authentications", ["member_id"], name: "index_authentications_on_member_id", using: :btree
create_table "comfy_cms_blocks", force: true do |t|
t.string "identifier", null: false
t.text "content"
t.integer "blockable_id"
t.string "blockable_type"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "comfy_cms_blocks", ["blockable_id", "blockable_type"], name: "index_comfy_cms_blocks_on_blockable_id_and_blockable_type", using: :btree
add_index "comfy_cms_blocks", ["identifier"], name: "index_comfy_cms_blocks_on_identifier", using: :btree
create_table "comfy_cms_categories", force: true do |t|
t.integer "site_id", null: false
t.string "label", null: false
t.string "categorized_type", null: false
end
add_index "comfy_cms_categories", ["site_id", "categorized_type", "label"], name: "index_cms_categories_on_site_id_and_cat_type_and_label", unique: true, using: :btree
create_table "comfy_cms_categorizations", force: true do |t|
t.integer "category_id", null: false
t.string "categorized_type", null: false
t.integer "categorized_id", null: false
end
add_index "comfy_cms_categorizations", ["category_id", "categorized_type", "categorized_id"], name: "index_cms_categorizations_on_cat_id_and_catd_type_and_catd_id", unique: true, using: :btree
create_table "comfy_cms_files", force: true do |t|
t.integer "site_id", null: false
t.integer "block_id"
t.string "label", null: false
t.string "file_file_name", null: false
t.string "file_content_type", null: false
t.integer "file_file_size", null: false
t.string "description", limit: 2048
t.integer "position", default: 0, null: false
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "comfy_cms_files", ["site_id", "block_id"], name: "index_comfy_cms_files_on_site_id_and_block_id", using: :btree
add_index "comfy_cms_files", ["site_id", "file_file_name"], name: "index_comfy_cms_files_on_site_id_and_file_file_name", using: :btree
add_index "comfy_cms_files", ["site_id", "label"], name: "index_comfy_cms_files_on_site_id_and_label", using: :btree
add_index "comfy_cms_files", ["site_id", "position"], name: "index_comfy_cms_files_on_site_id_and_position", using: :btree
create_table "comfy_cms_layouts", force: true do |t|
t.integer "site_id", null: false
t.integer "parent_id"
t.string "app_layout"
t.string "label", null: false
t.string "identifier", null: false
t.text "content"
t.text "css"
t.text "js"
t.integer "position", default: 0, null: false
t.boolean "is_shared", default: false, null: false
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "comfy_cms_layouts", ["parent_id", "position"], name: "index_comfy_cms_layouts_on_parent_id_and_position", using: :btree
add_index "comfy_cms_layouts", ["site_id", "identifier"], name: "index_comfy_cms_layouts_on_site_id_and_identifier", unique: true, using: :btree
create_table "comfy_cms_pages", force: true do |t|
t.integer "site_id", null: false
t.integer "layout_id"
t.integer "parent_id"
t.integer "target_page_id"
t.string "label", null: false
t.string "slug"
t.string "full_path", null: false
t.text "content_cache"
t.integer "position", default: 0, null: false
t.integer "children_count", default: 0, null: false
t.boolean "is_published", default: true, null: false
t.boolean "is_shared", default: false, null: false
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "comfy_cms_pages", ["parent_id", "position"], name: "index_comfy_cms_pages_on_parent_id_and_position", using: :btree
add_index "comfy_cms_pages", ["site_id", "full_path"], name: "index_comfy_cms_pages_on_site_id_and_full_path", using: :btree
create_table "comfy_cms_revisions", force: true do |t|
t.string "record_type", null: false
t.integer "record_id", null: false
t.text "data"
t.datetime "created_at"
end
add_index "comfy_cms_revisions", ["record_type", "record_id", "created_at"], name: "index_cms_revisions_on_rtype_and_rid_and_created_at", using: :btree
create_table "comfy_cms_sites", force: true do |t|
t.string "label", null: false
t.string "identifier", null: false
t.string "hostname", null: false
t.string "path"
t.string "locale", default: "en", null: false
t.boolean "is_mirrored", default: false, null: false
end
add_index "comfy_cms_sites", ["hostname"], name: "index_comfy_cms_sites_on_hostname", using: :btree
add_index "comfy_cms_sites", ["is_mirrored"], name: "index_comfy_cms_sites_on_is_mirrored", using: :btree
create_table "comfy_cms_snippets", force: true do |t|
t.integer "site_id", null: false
t.string "label", null: false
t.string "identifier", null: false
t.text "content"
t.integer "position", default: 0, null: false
t.boolean "is_shared", default: false, null: false
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "comfy_cms_snippets", ["site_id", "identifier"], name: "index_comfy_cms_snippets_on_site_id_and_identifier", unique: true, using: :btree
add_index "comfy_cms_snippets", ["site_id", "position"], name: "index_comfy_cms_snippets_on_site_id_and_position", using: :btree
create_table "comments", force: true do |t|
t.integer "post_id", null: false
t.integer "author_id", null: false
t.text "body", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "crops", force: true do |t|
t.string "name", null: false
t.string "name", null: false
t.string "en_wikipedia_url"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.string "slug"
t.integer "parent_id"
t.integer "plantings_count", default: 0
t.integer "plantings_count", default: 0
t.integer "creator_id"
t.integer "requester_id"
t.string "approval_status", default: "approved"
t.text "reason_for_rejection"
t.text "request_notes"
t.text "rejection_notes"
end
add_index "crops", ["name"], name: "index_crops_on_name", using: :btree
add_index "crops", ["requester_id"], name: "index_crops_on_requester_id", using: :btree
add_index "crops", ["slug"], name: "index_crops_on_slug", unique: true, using: :btree
create_table "crops_posts", id: false, force: true do |t|
@@ -94,8 +219,8 @@ ActiveRecord::Schema.define(version: 20150129034206) do
t.string "name", null: false
t.text "description", null: false
t.integer "owner_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.string "slug"
end
@@ -105,8 +230,8 @@ ActiveRecord::Schema.define(version: 20150129034206) do
t.string "name", null: false
t.integer "owner_id"
t.string "slug", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.text "description"
t.boolean "active", default: true
t.string "location"
@@ -116,7 +241,7 @@ ActiveRecord::Schema.define(version: 20150129034206) do
t.string "area_unit"
end
add_index "gardens", ["owner_id"], name: "index_gardens_on_user_id", using: :btree
add_index "gardens", ["owner_id"], name: "index_gardens_on_owner_id", using: :btree
add_index "gardens", ["slug"], name: "index_gardens_on_slug", unique: true, using: :btree
create_table "gardens_photos", id: false, force: true do |t|
@@ -133,8 +258,8 @@ ActiveRecord::Schema.define(version: 20150129034206) do
t.decimal "quantity"
t.string "unit"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.string "slug"
t.decimal "weight_quantity"
t.string "weight_unit"
@@ -167,8 +292,8 @@ ActiveRecord::Schema.define(version: 20150129034206) do
t.integer "failed_attempts", default: 0
t.string "unlock_token"
t.datetime "locked_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.string "login_name"
t.string "slug"
t.boolean "tos_agreement"
@@ -183,11 +308,11 @@ ActiveRecord::Schema.define(version: 20150129034206) do
t.boolean "send_planting_reminder", default: true
end
add_index "members", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
add_index "members", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "members", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
add_index "members", ["slug"], name: "index_users_on_slug", unique: true, using: :btree
add_index "members", ["unlock_token"], name: "index_users_on_unlock_token", unique: true, using: :btree
add_index "members", ["confirmation_token"], name: "index_members_on_confirmation_token", unique: true, using: :btree
add_index "members", ["email"], name: "index_members_on_email", unique: true, using: :btree
add_index "members", ["reset_password_token"], name: "index_members_on_reset_password_token", unique: true, using: :btree
add_index "members", ["slug"], name: "index_members_on_slug", unique: true, using: :btree
add_index "members", ["unlock_token"], name: "index_members_on_unlock_token", unique: true, using: :btree
create_table "members_roles", id: false, force: true do |t|
t.integer "member_id"
@@ -201,8 +326,8 @@ ActiveRecord::Schema.define(version: 20150129034206) do
t.text "body"
t.boolean "read", default: false
t.integer "post_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "order_items", force: true do |t|
@@ -210,13 +335,13 @@ ActiveRecord::Schema.define(version: 20150129034206) do
t.integer "product_id"
t.integer "price"
t.integer "quantity"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "orders", force: true do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.datetime "completed_at"
t.integer "member_id"
t.string "paypal_express_token"
@@ -233,8 +358,8 @@ ActiveRecord::Schema.define(version: 20150129034206) do
t.integer "owner_id", null: false
t.string "thumbnail_url", null: false
t.string "fullsize_url", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.string "title", null: false
t.string "license_name", null: false
t.string "license_url"
@@ -249,8 +374,8 @@ ActiveRecord::Schema.define(version: 20150129034206) do
create_table "plant_parts", force: true do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.string "slug"
end
@@ -260,8 +385,8 @@ ActiveRecord::Schema.define(version: 20150129034206) do
t.date "planted_at"
t.integer "quantity"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.string "slug"
t.string "sunniness"
t.string "planted_from"
@@ -276,21 +401,22 @@ ActiveRecord::Schema.define(version: 20150129034206) do
t.integer "author_id", null: false
t.string "subject", null: false
t.text "body", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.string "slug"
t.integer "forum_id"
t.integer "parent_id"
end
add_index "posts", ["created_at", "author_id"], name: "index_updates_on_created_at_and_user_id", using: :btree
add_index "posts", ["slug"], name: "index_updates_on_slug", unique: true, using: :btree
add_index "posts", ["created_at", "author_id"], name: "index_posts_on_created_at_and_author_id", using: :btree
add_index "posts", ["slug"], name: "index_posts_on_slug", unique: true, using: :btree
create_table "products", force: true do |t|
t.string "name", null: false
t.text "description", null: false
t.integer "min_price", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.integer "account_type_id"
t.integer "paid_months"
t.integer "recommended_price"
@@ -299,8 +425,8 @@ ActiveRecord::Schema.define(version: 20150129034206) do
create_table "roles", force: true do |t|
t.string "name", null: false
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.string "slug"
end
@@ -309,21 +435,26 @@ ActiveRecord::Schema.define(version: 20150129034206) do
create_table "scientific_names", force: true do |t|
t.string "scientific_name", null: false
t.integer "crop_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.integer "creator_id"
end
create_table "seeds", force: true do |t|
t.integer "owner_id", null: false
t.integer "crop_id", null: false
t.integer "owner_id", null: false
t.integer "crop_id", null: false
t.text "description"
t.integer "quantity"
t.date "plant_before"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "tradable_to", default: "nowhere"
t.datetime "created_at"
t.datetime "updated_at"
t.string "tradable_to", default: "nowhere"
t.string "slug"
t.integer "days_until_maturity_min"
t.integer "days_until_maturity_max"
t.text "organic", default: "unknown"
t.text "gmo", default: "unknown"
t.text "heirloom", default: "unknown"
end
add_index "seeds", ["slug"], name: "index_seeds_on_slug", unique: true, using: :btree

View File

@@ -319,6 +319,7 @@ namespace :growstuff do
task :populate_si_weight => :environment do
Harvest.find_each do |h|
h.set_si_weight
h.save
end
end

View File

@@ -7,7 +7,8 @@ describe CropsController do
def valid_attributes
{
:name => "Tomato",
:en_wikipedia_url => 'http://en.wikipedia.org/wiki/Tomato'
:en_wikipedia_url => 'http://en.wikipedia.org/wiki/Tomato',
:approval_status => 'approved'
}
end

View File

@@ -3,6 +3,7 @@ FactoryGirl.define do
factory :crop do
name "magic bean"
en_wikipedia_url "http://en.wikipedia.org/wiki/Magic_bean"
approval_status "approved"
creator
factory :tomato do
@@ -54,6 +55,21 @@ FactoryGirl.define do
name "Swiss chard"
end
#for testing crop request
factory :crop_request do
name "Ultra berry"
en_wikipedia_url ""
approval_status "pending"
association :requester, factory: :member
request_notes "Please approve this even though it's fake."
end
factory :rejected_crop do
name "Fail bean"
approval_status "rejected"
reason_for_rejection "Totally fake"
end
end
end

View File

@@ -8,6 +8,11 @@ FactoryGirl.define do
quantity 1
plant_before "2013-07-15"
tradable_to 'nowhere'
organic 'unknown'
gmo 'unknown'
heirloom 'unknown'
days_until_maturity_min nil
days_until_maturity_max nil
factory :tradable_seed do
tradable_to "locally"

37
spec/features/cms_spec.rb Normal file
View File

@@ -0,0 +1,37 @@
require 'spec_helper'
feature "cms admin" do
before(:each) do
@member = FactoryGirl.create(:member)
@admin_member = FactoryGirl.create(:admin_member)
end
scenario "can't view CMS admin if not signed in" do
visit comfy_admin_cms_path
current_path.should == root_path
page.should have_content("Please sign in as an admin user")
end
scenario "can't view CMS admin if not an admin member" do
# sign in as an ordinary member
visit root_path
click_link 'navbar-signin'
fill_in 'Login', :with => @member.email
fill_in 'Password', :with => @member.password
click_button 'Sign in'
visit comfy_admin_cms_path
current_path.should == root_path
page.should have_content("Please sign in as an admin user")
end
scenario "admin members can view CMS admin area" do
visit root_path
# now we sign in as an admin member
click_link 'navbar-signin'
fill_in 'Login', :with => @admin_member.email
fill_in 'Password', :with => @admin_member.password
click_button 'Sign in'
visit comfy_admin_cms_path
current_path.should match /#{comfy_admin_cms_path}/ # match any CMS admin page
end
end

View File

@@ -0,0 +1,19 @@
require 'rails_helper'
feature "browse crops" do
let(:tomato) { FactoryGirl.create(:tomato) }
let(:maize) { FactoryGirl.create(:maize) }
scenario "has a form for sorting by" do
visit crops_path
expect(page).to have_css "select#sort"
end
scenario "shows a list of crops" do
crop1 = tomato
visit crops_path
expect(page).to have_content crop1.name
end
end

View File

@@ -0,0 +1,22 @@
require 'rails_helper'
feature "crop search" do
scenario "search results show the search term in title" do
visit root_path
within "form#navbar-search" do
fill_in "search", with: "tomato"
click_button "Search"
end
expect(page).to have_css "h1", text: "Crops matching \"tomato\""
end
scenario "search page with no search term shows suitable title" do
visit crops_search_path
expect(page).to have_css "h1", text: "Crop search"
end
scenario "search page has a search form on it" do
visit crops_search_path
expect(page).to have_css "form#crop-search"
end
end

View File

@@ -5,6 +5,8 @@ feature "crop wranglers" do
let!(:crop_wranglers) { FactoryGirl.create_list(:crop_wrangling_member, 3) }
let(:wrangler){crop_wranglers.first}
let!(:crops) { FactoryGirl.create_list(:crop, 2) }
let!(:requested_crop) { FactoryGirl.create(:crop_request) }
let!(:rejected_crop) { FactoryGirl.create(:rejected_crop) }
background do
login_as(wrangler)
@@ -25,7 +27,7 @@ feature "crop wranglers" do
scenario "can see list of crops with extra detail of who created a crop" do
visit root_path
click_link 'Crop Wrangling'
within '.table' do
within '#recently-added-crops' do
expect(page).to have_content "#{crops.first.creator.login_name}"
end
end
@@ -48,6 +50,24 @@ feature "crop wranglers" do
expect(page).to have_content 'Crop was successfully created'
expect(page).to have_content 'planticus maximus'
end
scenario "View pending crops" do
visit wrangle_crops_path(:approval_status => "pending")
within "#pending-crops" do
click_link "Ultra berry"
end
expect(page).to have_content "This crop is currently pending approval."
expect(page).to have_content "Please approve this even though it's fake."
end
scenario "View rejected crops" do
visit wrangle_crops_path(:approval_status => "rejected")
within "#rejected-crops" do
click_link "Fail bean"
end
expect(page).to have_content "This crop was rejected for the following reason: Totally fake"
end
end

View File

@@ -0,0 +1,33 @@
require 'rails_helper'
feature "crop wrangling button" do
let(:crop_wrangler) { FactoryGirl.create(:crop_wrangling_member) }
context "crop wrangling button" do
background do
login_as(crop_wrangler)
visit crops_path
end
scenario "has a link to crop wrangling page" do
expect(page).to have_link "Wrangle Crops", :href => wrangle_crops_path
end
end
let(:member) { FactoryGirl.create(:member) }
context "crop wrangling button" do
background do
login_as(member)
visit crops_path
end
scenario "has no link to crop wrangling page" do
expect(page).to have_no_link "Wrangle Crops", :href => wrangle_crops_path
end
end
end

View File

@@ -0,0 +1,50 @@
require 'rails_helper'
feature "Requesting a new crop" do
context "As a regular member" do
let(:member) { FactoryGirl.create(:member) }
let!(:wrangler) { FactoryGirl.create(:crop_wrangling_member) }
before { login_as member }
scenario "Submit request" do
visit new_crop_path
fill_in "Name", with: "Couch potato"
fill_in "Comments", with: "Couch potatoes are real for real."
click_button "Save"
expect(page).to have_content "Crop was successfully requested."
end
end
context "As a crop wrangler" do
let(:wrangler) { FactoryGirl.create(:crop_wrangling_member) }
let!(:crop) { FactoryGirl.create(:crop_request) }
let!(:already_approved) { FactoryGirl.create(:crop) }
before { login_as wrangler }
scenario "Approve a request" do
visit edit_crop_path(crop)
select "approved", from: "Approval Status"
click_button "Save"
expect(page).to have_content "En wikipedia url is not a valid English Wikipedia URL"
fill_in "Wikipedia URL", with: "http://en.wikipedia.org/wiki/Aung_San_Suu_Kyi"
click_button "Save"
expect(page).to have_content "Crop was successfully updated."
end
scenario "Rejecting a crop" do
visit edit_crop_path(crop)
select "rejected", from: "Approval Status"
select "not edible", from: "Reason for rejection"
click_button "Save"
expect(page).to have_content "Crop was successfully updated."
end
end
end

View File

@@ -2,19 +2,11 @@ require 'rails_helper'
feature "footer" do
scenario "has three columns" do
scenario "footer is on home page" do
visit root_path
expect(page).to have_css 'footer #about-growstuff'
expect(page).to have_css 'footer #policies'
expect(page).to have_css 'footer #contact'
expect(page).to have_css 'footer'
end
# NB: not testing specific content in the footer since I'm going to put them
# in the CMS and they'll be variable.
scenario "contact page has Twitter link" do
visit root_path
click_link 'Contact'
page.should have_link '@growstufforg', :href => 'http://twitter.com/growstufforg'
end
end

View File

@@ -0,0 +1,35 @@
require 'rails_helper'
feature "members list" do
context "list all members" do
let! (:member1) { FactoryGirl.create(:member, :login_name => "Archaeopteryx", :confirmed_at => Time.zone.parse('2013-02-10')) }
let! (:member2) { FactoryGirl.create(:member, :login_name => "Zephyrosaurus", :confirmed_at => Time.zone.parse('2014-01-11')) }
let! (:member3) { FactoryGirl.create(:member, :login_name => "Testingname", :confirmed_at => Time.zone.parse('2014-05-09')) }
scenario "default alphabetical sort" do
visit members_path
expect(page).to have_css "#sort"
expect(page).to have_selector "form"
click_button('Show')
all_links = page.all("#maincontainer p")
expect(all_links.first).to have_text member1.login_name
expect(all_links.last).to have_text member2.login_name
end
scenario "recently joined sort" do
visit members_path
expect(page).to have_css "#sort"
expect(page).to have_selector "form"
select("recently", :from => 'sort')
click_button('Show')
all_links = page.all("#maincontainer p")
expect(all_links.first).to have_text member3.login_name
expect(all_links.last).to have_text member1.login_name
end
end
end

View File

@@ -5,6 +5,12 @@ feature "Planting reminder email", :js => true do
let(:member) { FactoryGirl.create(:member) }
let(:mail) { Notifier.planting_reminder(member) }
# Unfortunately, we can't use the default url options for ActionMailer as configured in
# test.rb, since this isn't a mailer spec.
def self.default_url_options
{ host: 'localhost', port: 8080 }
end
scenario "has a greeting" do
expect(mail).to have_content "Hello"
end
@@ -34,10 +40,8 @@ feature "Planting reminder email", :js => true do
scenario "lists plantings" do
expect(mail).to have_content "most recent plantings you've told us about"
expect(mail).to have_content @p1.to_s
expect(mail).to have_content @p2.to_s
# can't test for links to your plantings due to this weirdness:
# https://github.com/Skud/growstuff/commit/8e6a57c4429eac88ab934f422ab11bf16b0a7663
expect(mail).to have_link @p1.to_s, planting_url(@p1)
expect(mail).to have_link @p2.to_s, planting_url(@p2)
expect(mail).to have_content "keep your garden records up to date"
end
end
@@ -65,10 +69,8 @@ feature "Planting reminder email", :js => true do
scenario "lists harvests" do
expect(mail).to have_content "the last few things you harvested were"
expect(mail).to have_content @h1.to_s
expect(mail).to have_content @h2.to_s
# can't test for links to your harvests due to this weirdness:
# https://github.com/Skud/growstuff/commit/8e6a57c4429eac88ab934f422ab11bf16b0a7663
expect(mail).to have_link @h1.to_s, harvest_url(@h1)
expect(mail).to have_link @h2.to_s, harvest_url(@h2)
expect(mail).to have_content "Harvested anything else lately?"
end

View File

@@ -18,12 +18,23 @@ feature "Seeds", :js => true do
within "form#new_seed" do
fill_in "Quantity:", :with => 42
fill_in "Plant before:", :with => "2014-06-15"
fill_in "Days until maturity:", :with => 999
fill_in "to", :with => 1999
select "certified organic", :from => "Organic?"
select "non-certified GMO-free", :from => "GMO?"
select "heirloom", :from => "Heirloom?"
fill_in "Description", :with => "It's killer."
select "internationally", :from => "Will trade:"
click_button "Save"
end
expect(page).to have_content "Successfully added maize seed to your stash"
expect(page).to have_content "Quantity: 42"
expect(page).to have_content "Days until maturity: 9991999"
expect(page).to have_content "certified organic"
expect(page).to have_content "non-certified GMO-free"
expect(page).to have_content "Heirloom? heirloom"
expect(page).to have_content "It's killer."
end
scenario "Adding a seed from crop page" do

View File

@@ -45,5 +45,30 @@ feature "seeds" do
click_link 'Delete'
current_path.should eq seeds_path
end
scenario "view seeds with max and min days until maturity" do
seed = FactoryGirl.create(:seed, :days_until_maturity_min => 5, :days_until_maturity_max => 7)
visit seed_path(seed)
expect(page).to have_content "Days until maturity: 57"
end
scenario "view seeds with only max days until maturity" do
seed = FactoryGirl.create(:seed, :days_until_maturity_max => 7)
visit seed_path(seed)
expect(page).to have_content "Days until maturity: 7"
end
scenario "view seeds with only min days until maturity" do
seed = FactoryGirl.create(:seed, :days_until_maturity_min => 5)
visit seed_path(seed)
expect(page).to have_content "Days until maturity: 5"
end
scenario "view seeds with neither max nor min days until maturity" do
seed = FactoryGirl.create(:seed)
visit seed_path(seed)
expect(page).to have_content "Days until maturity: unknown"
end
end
end

View File

@@ -44,4 +44,84 @@ describe Notifier do
end
end
describe "new crop request" do
let(:member) { FactoryGirl.create(:crop_wrangling_member) }
let(:crop) { FactoryGirl.create(:crop_request) }
let(:mail) { Notifier.new_crop_request(member, crop) }
it 'sets the subject correctly' do
mail.subject.should == "#{crop.requester.login_name} has requested Ultra berry as a new crop"
end
it 'comes from noreply@growstuff.org' do
mail.from.should == ['noreply@growstuff.org']
end
it 'sends the mail to the recipient of the notification' do
mail.to.should == [member.email]
end
it 'includes the requested crop URL' do
mail.body.encoded.should match crop_url(crop)
end
end
describe "crop approved" do
let(:member) { FactoryGirl.create(:member) }
let(:crop) { FactoryGirl.create(:crop) }
let(:mail) { Notifier.crop_request_approved(member, crop) }
it 'sets the subject correctly' do
expect(mail.subject).to eq "Magic bean has been approved"
end
it 'comes from noreply@growstuff.org' do
expect(mail.from).to eq ['noreply@growstuff.org']
end
it 'sends the mail to the recipient of the notification' do
expect(mail.to).to eq [member.email]
end
it 'includes the approved crop URL' do
expect(mail.body.encoded).to match crop_url(crop)
end
it 'includes links to plant, harvest and stash seeds for the new crop' do
expect(mail.body.encoded).to match "#{new_planting_url}\\?crop_id=#{crop.id}"
expect(mail.body.encoded).to match "#{new_harvest_url}\\?crop_id=#{crop.id}"
expect(mail.body.encoded).to match "#{new_seed_url}\\?crop_id=#{crop.id}"
end
end
describe "crop rejected" do
let(:member) { FactoryGirl.create(:member) }
let(:crop) { FactoryGirl.create(:rejected_crop) }
let(:mail) { Notifier.crop_request_rejected(member, crop) }
it 'sets the subject correctly' do
expect(mail.subject).to eq "Fail bean has been rejected"
end
it 'comes from noreply@growstuff.org' do
expect(mail.from).to eq ['noreply@growstuff.org']
end
it 'sends the mail to the recipient of the notification' do
expect(mail.to).to eq [member.email]
end
it 'includes the rejected crop URL' do
expect(mail.body.encoded).to match crop_url(crop)
end
it 'includes the reason for rejection' do
expect(mail.body.encoded).to match "Totally fake"
end
end
end

View File

@@ -45,11 +45,14 @@ describe Ability do
context "standard member" do
it "can't manage crops" do
@ability.should_not be_able_to(:create, Crop)
@ability.should_not be_able_to(:update, @crop)
@ability.should_not be_able_to(:destroy, @crop)
end
it "can request crops" do
@ability.should be_able_to(:create, Crop)
end
it "can read crops" do
@ability.should be_able_to(:read, @crop)
end

View File

@@ -88,6 +88,49 @@ describe Seed do
end
end
context 'organic, gmo, heirloom' do
it 'all valid organic values should work' do
['certified organic', 'non-certified organic',
'conventional/non-organic', 'unknown'].each do |t|
@seed = FactoryGirl.build(:seed, :organic => t)
@seed.should be_valid
end
end
it 'all valid GMO values should work' do
['certified GMO-free', 'non-certified GMO-free',
'GMO', 'unknown'].each do |t|
@seed = FactoryGirl.build(:seed, :gmo => t)
@seed.should be_valid
end
end
it 'all valid heirloom values should work' do
%w(heirloom hybrid unknown).each do |t|
@seed = FactoryGirl.build(:seed, :heirloom => t)
@seed.should be_valid
end
end
it 'should refuse invalid organic/GMO/heirloom values' do
[:organic, :gmo, :heirloom].each do |field|
@seed = FactoryGirl.build(:seed, field => 'not valid')
@seed.should_not be_valid
@seed.errors[field].should_not be_empty
end
end
it 'should not allow nil or blank values' do
[:organic, :gmo, :heirloom].each do |field|
@seed = FactoryGirl.build(:seed, field => nil)
@seed.should_not be_valid
@seed = FactoryGirl.build(:seed, field => '')
@seed.should_not be_valid
end
end
end
context 'interesting' do
it 'lists interesting seeds' do

View File

@@ -25,7 +25,7 @@ require 'capybara'
require 'capybara/poltergeist'
Capybara.javascript_driver = :poltergeist
Capybara.app_host = 'http://localhost'
Capybara.server_port = 8080
Capybara.server_port = 8081
include Warden::Test::Helpers

View File

@@ -8,24 +8,11 @@ describe "crops/index" do
total_entries = 2
@tomato = FactoryGirl.create(:tomato)
@maize = FactoryGirl.create(:maize)
crops = WillPaginate::Collection.create(page, per_page, total_entries) do |pager|
assign(:crops, [@tomato, @maize])
paginated_crops = WillPaginate::Collection.create(page, per_page, total_entries) do |pager|
pager.replace([ @tomato, @maize ])
end
assign(:crops, crops)
end
it "has a form for sorting by" do
render
assert_select "form"
assert_select "select#sort"
assert_select "option[value=alpha]"
assert_select "option[value=popular]"
end
it "renders a list of crops" do
render
assert_select "a", :text => @maize.name
assert_select "a", :text => @tomato.name
assign(:paginated_crops, paginated_crops)
end
it "shows photos where available" do

View File

@@ -1,48 +0,0 @@
require 'rails_helper'
describe "crops/search" do
before(:each) do
controller.stub(:current_user) { nil }
end
context "has results" do
before :each do
@tomato = FactoryGirl.create(:tomato)
@roma = FactoryGirl.create(:crop, :name => 'Roma tomato', :parent => @tomato)
assign(:search, 'tomato')
assign(:all_matches, [@tomato, @roma])
render
end
it "shows exact matches" do
assert_select "div#all_matches" do
assert_select "a[href=#{crop_path(@tomato)}]"
end
end
it "shows partial matches" do
assert_select "div#all_matches" do
assert_select "a[href=#{crop_path(@roma)}]"
end
end
end
context "no results" do
before :each do
assign(:all_matches, [])
assign(:search, 'tomato')
render
end
it "tells you there are no matches" do
rendered.should have_content "No results found"
end
it "links to browse crops" do
assert_select "a", :href => crops_path
rendered.should have_content "Try browsing our crop database instead"
end
end
end