From 9a09d8a0ae7dd45919edfb29baa358bbd0a43b88 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 14:12:36 -0400 Subject: [PATCH 001/288] Switch from twitter-bootstrap-rails gem to less-rails-bootstrap --- Gemfile | 4 +- Gemfile.lock | 15 +---- .../custom_bootstrap/custom_bootstrap.less | 65 +++++++++++++++++++ .../stylesheets/custom_bootstrap/mixins.less | 1 + .../custom_bootstrap/variables.less | 1 + 5 files changed, 71 insertions(+), 15 deletions(-) create mode 100644 app/assets/stylesheets/custom_bootstrap/custom_bootstrap.less create mode 100644 app/assets/stylesheets/custom_bootstrap/mixins.less create mode 100644 app/assets/stylesheets/custom_bootstrap/variables.less diff --git a/Gemfile b/Gemfile index b4ff013a0..3c41ee688 100644 --- a/Gemfile +++ b/Gemfile @@ -57,9 +57,7 @@ group :assets do gem "less", '~>2.3.2' gem "less-rails", '~> 2.3.3' # CSS framework - gem "twitter-bootstrap-rails", - :git => 'https://github.com/seyhunak/twitter-bootstrap-rails.git', - :ref => '2c7c52' + gem "less-rails-bootstrap" gem 'uglifier', '>= 1.0.3' # JavaScript compressor diff --git a/Gemfile.lock b/Gemfile.lock index e2bd35b31..2921a230f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,17 +5,6 @@ GIT specs: geocoder (1.1.8) -GIT - remote: https://github.com/seyhunak/twitter-bootstrap-rails.git - revision: 2c7c527c354d9068ce49346d4fd8389328d32ce6 - ref: 2c7c52 - specs: - twitter-bootstrap-rails (2.2.7) - actionpack (>= 3.1) - execjs - rails (>= 3.1) - railties (>= 3.1) - PATH remote: vendor/gems/active_utils-1.0.5 specs: @@ -162,6 +151,8 @@ GEM less-rails (2.3.3) actionpack (>= 3.1) less (~> 2.3.1) + less-rails-bootstrap (3.0.2) + less-rails (~> 2.3.1) letter_opener (1.2.0) launchy (~> 2.2) libv8 (3.16.14.3) @@ -312,6 +303,7 @@ DEPENDENCIES leaflet-rails less (~> 2.3.2) less-rails (~> 2.3.3) + less-rails-bootstrap letter_opener libv8 (= 3.16.14.3) memcachier @@ -327,7 +319,6 @@ DEPENDENCIES rspec-rails (~> 2.12.1) sass-rails (~> 3.2.3) therubyracer (~> 0.12) - twitter-bootstrap-rails! uglifier (>= 1.0.3) unicorn webrat diff --git a/app/assets/stylesheets/custom_bootstrap/custom_bootstrap.less b/app/assets/stylesheets/custom_bootstrap/custom_bootstrap.less new file mode 100644 index 000000000..effdaefb6 --- /dev/null +++ b/app/assets/stylesheets/custom_bootstrap/custom_bootstrap.less @@ -0,0 +1,65 @@ +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// !!! AUTOMATICALLY GENERATED FILE. DO NOT MODIFY !!! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +/*! + * Bootstrap v3.0.0 + * + * Copyright 2013 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world by @mdo and @fat. + */ + +// Core variables and mixins +@import "twitter/bootstrap/variables.less"; +@import "custom_bootstrap/variables.less"; // Modify this for custom colors, font-sizes, etc +@import "twitter/bootstrap/mixins.less"; +@import "custom_bootstrap/mixins.less"; // Modify this for custom mixins + +// Reset +@import "twitter/bootstrap/normalize.less"; +@import "twitter/bootstrap/print.less"; + +// Core CSS +@import "twitter/bootstrap/scaffolding.less"; +@import "twitter/bootstrap/type.less"; +@import "twitter/bootstrap/code.less"; +@import "twitter/bootstrap/grid.less"; +@import "twitter/bootstrap/tables.less"; +@import "twitter/bootstrap/forms.less"; +@import "twitter/bootstrap/buttons.less"; + +// Components +@import "twitter/bootstrap/component-animations.less"; +@import "twitter/bootstrap/glyphicons.less"; +@import "twitter/bootstrap/dropdowns.less"; +@import "twitter/bootstrap/button-groups.less"; +@import "twitter/bootstrap/input-groups.less"; +@import "twitter/bootstrap/navs.less"; +@import "twitter/bootstrap/navbar.less"; +@import "twitter/bootstrap/breadcrumbs.less"; +@import "twitter/bootstrap/pagination.less"; +@import "twitter/bootstrap/pager.less"; +@import "twitter/bootstrap/labels.less"; +@import "twitter/bootstrap/badges.less"; +@import "twitter/bootstrap/jumbotron.less"; +@import "twitter/bootstrap/thumbnails.less"; +@import "twitter/bootstrap/alerts.less"; +@import "twitter/bootstrap/progress-bars.less"; +@import "twitter/bootstrap/media.less"; +@import "twitter/bootstrap/list-group.less"; +@import "twitter/bootstrap/panels.less"; +@import "twitter/bootstrap/wells.less"; +@import "twitter/bootstrap/close.less"; + +// Components w/ JavaScript +@import "twitter/bootstrap/modals.less"; +@import "twitter/bootstrap/tooltip.less"; +@import "twitter/bootstrap/popovers.less"; +@import "twitter/bootstrap/carousel.less"; + +// Utility classes +@import "twitter/bootstrap/utilities.less"; +@import "twitter/bootstrap/responsive-utilities.less"; diff --git a/app/assets/stylesheets/custom_bootstrap/mixins.less b/app/assets/stylesheets/custom_bootstrap/mixins.less new file mode 100644 index 000000000..44c24bdb7 --- /dev/null +++ b/app/assets/stylesheets/custom_bootstrap/mixins.less @@ -0,0 +1 @@ +// Use this file to override Twitter Bootstrap mixins or define own mixins. diff --git a/app/assets/stylesheets/custom_bootstrap/variables.less b/app/assets/stylesheets/custom_bootstrap/variables.less new file mode 100644 index 000000000..8d9f63301 --- /dev/null +++ b/app/assets/stylesheets/custom_bootstrap/variables.less @@ -0,0 +1 @@ +// Use this file to override Twitter Bootstrap variables or define own variables. From 471ecbc45891f52832fdb0a526a3b413bdf75818 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 14:15:27 -0400 Subject: [PATCH 002/288] Require the new custom bootstrap the main stylesheet --- app/assets/stylesheets/application.css | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index c1cc5e005..425f61a76 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -8,6 +8,7 @@ *= require leaflet.markercluster *= require leaflet.markercluster.default *= require_tree . + *= require custom_bootstrap/custom_bootstrap */ From bc383592197357c0c582e20e3fd28c6fddc07356 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 14:23:35 -0400 Subject: [PATCH 003/288] Didn't need to specify including custom bootstrap - require_tree takes care of it --- app/assets/stylesheets/application.css | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 425f61a76..c1cc5e005 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -8,7 +8,6 @@ *= require leaflet.markercluster *= require leaflet.markercluster.default *= require_tree . - *= require custom_bootstrap/custom_bootstrap */ From e226ce406c262a71aa6491f7203a41ca83a24fad Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 15:11:21 -0400 Subject: [PATCH 004/288] Rename bootstrap_and_overrides file --- ..._overrides.css.less => overrides.css.less} | 67 ++----------------- 1 file changed, 4 insertions(+), 63 deletions(-) rename app/assets/stylesheets/{bootstrap_and_overrides.css.less => overrides.css.less} (63%) diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css.less b/app/assets/stylesheets/overrides.css.less similarity index 63% rename from app/assets/stylesheets/bootstrap_and_overrides.css.less rename to app/assets/stylesheets/overrides.css.less index 3879c9d7b..5c6a387a2 100644 --- a/app/assets/stylesheets/bootstrap_and_overrides.css.less +++ b/app/assets/stylesheets/overrides.css.less @@ -1,11 +1,11 @@ @import "twitter/bootstrap/bootstrap"; - +@import "custom_bootstrap/variables"; // this padding needs to be done before the responsive stuff is imported body { - padding-top: @navbarHeight + 15px; + padding-top: @navbar-height + 15px; } -@import "twitter/bootstrap/responsive"; +//@import "twitter/bootstrap/responsive"; // Set the correct sprite paths @iconSpritePath: asset-path("twitter/bootstrap/glyphicons-halflings"); @@ -19,70 +19,11 @@ body { @fontAwesomeSvgPath: asset-url("fontawesome-webfont.svg#fontawesomeregular"); // Font Awesome -@import "fontawesome/font-awesome"; +//@import "fontawesome/font-awesome"; // Glyphicons //@import "twitter/bootstrap/sprites.less"; -// Your custom LESS stylesheets goes here -// -// Since bootstrap was imported above you have access to its mixins which -// you may use and inherit here -// -// If you'd like to override bootstrap's own variables, you can do so here as well -// See http://twitter.github.com/bootstrap/customize.html#variables for their names and documentation -// -// Example: -// @linkColor: #ff0000; - -// Base colours - -@beige: #f3f1ee; -@brown: #413f3b; - -@green: #5f8e43; -@blue: #2f4365; -@red: #8e4d43; -@orange: #b2685c; -@yellow: #b2935c; - -@bodyBackground: @beige; -@textColor: @brown; - -@linkColor: @green; - -// Typography (with help from bootswatch.com's "readable" theme) -@import url('//fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic'); -@sansFontFamily: "Helvetica Neue", Helvetica, Arial, sans-serif; -@serifFontFamily: Georgia, "Times New Roman", Times, serif; -@monoFontFamily: Monaco, Menlo, Consolas, "Courier New", monospace; - -@baseFontSize: 15px; -@baseFontFamily: @serifFontFamily; -@baseLineHeight: @baseFontSize * 1.5; -@altFontFamily: @sansFontFamily; - -@headingsFontFamily: "Lora", Georgia, "Times New Roman", Times, serif; -@headingsFontWeight: bold; // instead of browser default, bold -@headingsColor: inherit; // empty to use BS default, @textColor - -// Hero unit -@heroUnitBackground: darken(@bodyBackground, 10%); - -// Nav bar -@navbarBackground: @brown; -@navbarBackgroundHighlight: @brown; -@navbarText: @beige; -@navbarLinkColor: darken(@beige, 20%); -@navbarLinkColorHover: @beige; -@navbarLinkColorActive: @beige; -@navbarBrandColor: lighten(@green, 20%); - -@dropdownBackground: lighten(@beige, 10%); -@dropdownLinkColor: @brown; -@dropdownLinkColorHover: @brown; -@dropdownLinkBackgroundHover: lighten(@green, 50%); - ul.inline > li.first { padding-left: 0px; From 088cd36d6f00e7bbd87f819e30e1d14df3e51391 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 15:11:35 -0400 Subject: [PATCH 005/288] Update variable names to bootstrap 3 versions --- .../custom_bootstrap/variables.less | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/app/assets/stylesheets/custom_bootstrap/variables.less b/app/assets/stylesheets/custom_bootstrap/variables.less index 8d9f63301..a1f6f6ad1 100644 --- a/app/assets/stylesheets/custom_bootstrap/variables.less +++ b/app/assets/stylesheets/custom_bootstrap/variables.less @@ -1 +1,49 @@ // Use this file to override Twitter Bootstrap variables or define own variables. + +// Base colours + +@beige: #f3f1ee; +@brown: #413f3b; + +@green: #5f8e43; +@blue: #2f4365; +@red: #8e4d43; +@orange: #b2685c; +@yellow: #b2935c; + +@body-bg: @beige; +@text-color: @brown; + +@link-color: @green; + +// Typography (with help from bootswatch.com's "readable" theme) +@import url('//fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic'); +@font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif; +@font-family-serif: Georgia, "Times New Roman", Times, serif; +@font-family-mono: Monaco, Menlo, Consolas, "Courier New", monospace; + +@font-size-base: 15px; +@font-family-base: @font-family-sans-serif; +@line-height-base: @font-size-base * 1.5; +@alt-font-family: @font-family-sans-serif; + +@headings-font-family: "Lora", Georgia, "Times New Roman", Times, serif; +@headings-font-weight: bold; // instead of browser default, bold +@headings-color: inherit; // empty to use BS default, @textColor + +// Hero unit +@hero-unit-background: darken(@body-bg, 10%); + +// Nav bar +@navbar-background: @brown; +@navbar-background-highlight: @brown; +@navbar-text: @beige; +@navbar-link-color: darken(@beige, 20%); +@navbar-link-color-hover: @beige; +@navbar-link-color-active: @beige; +@navbar-brand-color: lighten(@green, 20%); + +@dropdown-background: lighten(@beige, 10%); +@dropdown-link-color: @brown; +@dropdown-link-color-hover: @brown; +@dropdown-link-background-hover: lighten(@green, 50%); \ No newline at end of file From df9169c13d3b8df459832e6d52ad804cb4aa2c27 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 15:11:59 -0400 Subject: [PATCH 006/288] Get stylesheet includes working --- app/assets/stylesheets/application.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index c1cc5e005..507aa815e 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -7,7 +7,7 @@ *= require leaflet *= require leaflet.markercluster *= require leaflet.markercluster.default + *= require custom_bootstrap/custom_bootstrap + *= require overrides.css *= require_tree . */ - - From 8d0d01991b5698e9885dc39b03c8f4e50c8d3943 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 15:15:08 -0400 Subject: [PATCH 007/288] Bootstrap3 update: row classes --- app/views/comments/_single.html.haml | 2 +- app/views/crops/_index_card.html.haml | 2 +- app/views/crops/_photos.html.haml | 2 +- app/views/crops/show.html.haml | 2 +- app/views/gardens/show.html.haml | 2 +- app/views/harvests/show.html.haml | 2 +- app/views/home/_blurb.html.haml | 2 +- app/views/home/_crops.html.haml | 4 ++-- app/views/home/_members.html.haml | 2 +- app/views/home/index.html.haml | 4 ++-- app/views/layouts/application.html.haml | 2 +- app/views/members/_thumbnail.html.haml | 2 +- app/views/members/show.html.haml | 2 +- app/views/photos/new.html.haml | 2 +- app/views/photos/show.html.haml | 4 ++-- app/views/plantings/_list.html.haml | 2 +- app/views/plantings/_thumbnail.html.haml | 2 +- app/views/plantings/show.html.haml | 2 +- app/views/posts/_single.html.haml | 2 +- app/views/seeds/show.html.haml | 2 +- 20 files changed, 23 insertions(+), 23 deletions(-) diff --git a/app/views/comments/_single.html.haml b/app/views/comments/_single.html.haml index 6051590fa..6379a4ecd 100644 --- a/app/views/comments/_single.html.haml +++ b/app/views/comments/_single.html.haml @@ -1,6 +1,6 @@ .well .comment - .row-fluid + .row .span1 = render :partial => "members/avatar", :locals => { :member => comment.author } .span11 diff --git a/app/views/crops/_index_card.html.haml b/app/views/crops/_index_card.html.haml index 7fd64bd4f..fda786387 100644 --- a/app/views/crops/_index_card.html.haml +++ b/app/views/crops/_index_card.html.haml @@ -1,5 +1,5 @@ .well - .row-fluid + .row .span4 = link_to image_tag((crop.default_photo ? crop.default_photo.thumbnail_url : 'placeholder_150.png'), :alt => '', :class => 'img-rounded'), crop .span8 diff --git a/app/views/crops/_photos.html.haml b/app/views/crops/_photos.html.haml index 864560eb8..c6845bbba 100644 --- a/app/views/crops/_photos.html.haml +++ b/app/views/crops/_photos.html.haml @@ -1,4 +1,4 @@ -.row-fluid +.row - if !crop.photos.empty? %ul.thumbnails - crop.photos.first(3).each do |p| diff --git a/app/views/crops/show.html.haml b/app/views/crops/show.html.haml index c174f325e..d74fbbd79 100644 --- a/app/views/crops/show.html.haml +++ b/app/views/crops/show.html.haml @@ -1,6 +1,6 @@ - content_for :title, @crop.name -.row-fluid +.row .span9 %p - if can? :create, Planting diff --git a/app/views/gardens/show.html.haml b/app/views/gardens/show.html.haml index e06597ac5..25c17073e 100644 --- a/app/views/gardens/show.html.haml +++ b/app/views/gardens/show.html.haml @@ -1,6 +1,6 @@ =content_for :title, "#{@garden.owner}'s #{@garden}" -.row-fluid +.row .span9 - if ! @garden.active .alert.alert-notice diff --git a/app/views/harvests/show.html.haml b/app/views/harvests/show.html.haml index b3259f502..21b562408 100644 --- a/app/views/harvests/show.html.haml +++ b/app/views/harvests/show.html.haml @@ -1,6 +1,6 @@ =content_for :title, "#{@harvest.crop} harvested by #{@harvest.owner}" -.row-fluid +.row .span6 %p %b Owner: diff --git a/app/views/home/_blurb.html.haml b/app/views/home/_blurb.html.haml index 0655e2a66..9f256d498 100644 --- a/app/views/home/_blurb.html.haml +++ b/app/views/home/_blurb.html.haml @@ -2,7 +2,7 @@ %h1= ENV['GROWSTUFF_SITE_NAME'] -.row-fluid +.row .span8.info %p #{ENV['GROWSTUFF_SITE_NAME']} is a community of food gardeners. diff --git a/app/views/home/_crops.html.haml b/app/views/home/_crops.html.haml index be9b7f1e0..21cda0f88 100644 --- a/app/views/home/_crops.html.haml +++ b/app/views/home/_crops.html.haml @@ -1,4 +1,4 @@ -.row-fluid +.row .span6.hidden-phone - cache "interesting_crops", :expires_in => 1.day do %h2 Some of our crops @@ -11,7 +11,7 @@ %h2 Recently planted = render :partial => 'plantings/list', :locals => { :plantings => Planting.interesting.first(4) } -.row-fluid +.row .span12 - cache "recent_crops" do %p{ :style => 'margin-top: 11.25px' } diff --git a/app/views/home/_members.html.haml b/app/views/home/_members.html.haml index f7589b0de..86bf50d9c 100644 --- a/app/views/home/_members.html.haml +++ b/app/views/home/_members.html.haml @@ -4,7 +4,7 @@ - if members.present? %h2 Some of our members - .row-fluid + .row - members.each do |m| .span6.homepage-members = render :partial => "members/thumbnail", :locals => { :member => m } diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 5d06907cc..f30735eb5 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -1,4 +1,4 @@ -.row-fluid +.row .span12 - if member_signed_in? %h1 @@ -21,7 +21,7 @@ = render :partial => 'blurb' .visible-phone = render :partial => 'blurb' -.row-fluid +.row .span8.main = render :partial => 'crops' = render :partial => 'seeds' diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 01ba4cb55..2ae4385db 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -5,7 +5,7 @@ = render :partial => "layouts/header" .container - .row-fluid + .row .span12 - if content_for?(:title) %h1= yield(:title) diff --git a/app/views/members/_thumbnail.html.haml b/app/views/members/_thumbnail.html.haml index 7058941fa..d9ab14c0a 100644 --- a/app/views/members/_thumbnail.html.haml +++ b/app/views/members/_thumbnail.html.haml @@ -1,5 +1,5 @@ - cache "member_thumbnail_#{member.id}" do - .row-fluid + .row .member-thumbnail .span3 = render :partial => "members/image_with_popover", :locals => { :member => member } diff --git a/app/views/members/show.html.haml b/app/views/members/show.html.haml index b89b6028d..393c39af0 100644 --- a/app/views/members/show.html.haml +++ b/app/views/members/show.html.haml @@ -2,7 +2,7 @@ - content_for :member_rss_login_name, "#{@member.login_name}" - content_for :member_rss_slug, "#{@member.slug}" -.row-fluid +.row .span3 = render :partial => "members/avatar", :locals => { :member => @member } diff --git a/app/views/photos/new.html.haml b/app/views/photos/new.html.haml index 2db20b2ff..8d6a14d38 100644 --- a/app/views/photos/new.html.haml +++ b/app/views/photos/new.html.haml @@ -22,7 +22,7 @@ = will_paginate @photos %ul.thumbnails - .row-fluid + .row - @photos.each do |p| %li.span2.six-across .thumbnail(style='height: 220px') diff --git a/app/views/photos/show.html.haml b/app/views/photos/show.html.haml index 9ee1a22af..bd83caee6 100644 --- a/app/views/photos/show.html.haml +++ b/app/views/photos/show.html.haml @@ -1,6 +1,6 @@ -content_for :title, @photo.title -.row-fluid +.row .span6 %p %strong Posted by: @@ -25,7 +25,7 @@ - @photo.plantings.each do |p| %li= link_to p, p -.row-fluid +.row .span12 %p= image_tag(@photo.fullsize_url, :alt => @photo.title, :class => 'img-rounded') diff --git a/app/views/plantings/_list.html.haml b/app/views/plantings/_list.html.haml index 5374c815a..20cdece6f 100644 --- a/app/views/plantings/_list.html.haml +++ b/app/views/plantings/_list.html.haml @@ -1,6 +1,6 @@ - plantings.each do |p| - cache "plantings_listitem_#{p.id}" do - .row-fluid + .row .span2{:style => 'padding-bottom: 6px'} = render :partial => 'plantings/image_with_popover', :locals => { :planting => p } .span10 diff --git a/app/views/plantings/_thumbnail.html.haml b/app/views/plantings/_thumbnail.html.haml index f3b1c1211..f27ec9965 100644 --- a/app/views/plantings/_thumbnail.html.haml +++ b/app/views/plantings/_thumbnail.html.haml @@ -1,5 +1,5 @@ .well - .row-fluid + .row .span3 = link_to image_tag((planting.default_photo ? planting.default_photo.thumbnail_url : 'placeholder_150.png'), :alt => '', :class => 'img-rounded'), planting diff --git a/app/views/plantings/show.html.haml b/app/views/plantings/show.html.haml index cc35170dd..0494d2897 100644 --- a/app/views/plantings/show.html.haml +++ b/app/views/plantings/show.html.haml @@ -1,6 +1,6 @@ =content_for :title, "#{@planting.crop} in #{@planting.location}" -.row-fluid +.row .span6 %p %b Owner: diff --git a/app/views/posts/_single.html.haml b/app/views/posts/_single.html.haml index bd6122d0f..0c6ac3c2a 100644 --- a/app/views/posts/_single.html.haml +++ b/app/views/posts/_single.html.haml @@ -1,6 +1,6 @@ .well .post - .row-fluid + .row .span1 = render :partial => "members/avatar", :locals => { :member => post.author } .span11 diff --git a/app/views/seeds/show.html.haml b/app/views/seeds/show.html.haml index 47eac8a9e..1244749a6 100644 --- a/app/views/seeds/show.html.haml +++ b/app/views/seeds/show.html.haml @@ -1,6 +1,6 @@ - content_for :title, "#{@seed.owner}'s #{@seed.crop} seeds" -.row-fluid +.row .span6 %p %b Owner: From f0cd8df6e0904004c89e1201ce62e16dc3b85290 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 18:16:57 -0400 Subject: [PATCH 008/288] Update column classes --- app/views/comments/_single.html.haml | 4 ++-- app/views/crops/_index_card.html.haml | 4 ++-- app/views/crops/_photos.html.haml | 2 +- app/views/crops/index.html.haml | 2 +- app/views/crops/search.html.haml | 4 ++-- app/views/crops/show.html.haml | 4 ++-- app/views/gardens/show.html.haml | 4 ++-- app/views/harvests/show.html.haml | 4 ++-- app/views/home/_blurb.html.haml | 6 +++--- app/views/home/_crops.html.haml | 8 ++++---- app/views/home/_members.html.haml | 2 +- app/views/home/index.html.haml | 6 +++--- app/views/layouts/application.html.haml | 2 +- app/views/members/_thumbnail.html.haml | 4 ++-- app/views/members/index.html.haml | 2 +- app/views/members/nearby.html.haml | 2 +- app/views/members/show.html.haml | 4 ++-- app/views/photos/index.html.haml | 2 +- app/views/photos/new.html.haml | 2 +- app/views/photos/show.html.haml | 6 +++--- app/views/places/show.html.haml | 2 +- app/views/plantings/_list.html.haml | 4 ++-- app/views/plantings/_thumbnail.html.haml | 4 ++-- app/views/plantings/show.html.haml | 8 ++++---- app/views/posts/_single.html.haml | 4 ++-- app/views/seeds/show.html.haml | 4 ++-- 26 files changed, 50 insertions(+), 50 deletions(-) diff --git a/app/views/comments/_single.html.haml b/app/views/comments/_single.html.haml index 6379a4ecd..d2131b355 100644 --- a/app/views/comments/_single.html.haml +++ b/app/views/comments/_single.html.haml @@ -1,9 +1,9 @@ .well .comment .row - .span1 + .col-md-1 = render :partial => "members/avatar", :locals => { :member => comment.author } - .span11 + .col-md-11 .comment-meta Posted by = link_to comment.author.login_name, member_path(comment.author) diff --git a/app/views/crops/_index_card.html.haml b/app/views/crops/_index_card.html.haml index fda786387..a54898eb4 100644 --- a/app/views/crops/_index_card.html.haml +++ b/app/views/crops/_index_card.html.haml @@ -1,8 +1,8 @@ .well .row - .span4 + .col-md-4 = link_to image_tag((crop.default_photo ? crop.default_photo.thumbnail_url : 'placeholder_150.png'), :alt => '', :class => 'img-rounded'), crop - .span8 + .col-md-8 %h3{:style => 'padding-top: 0px; margin-top: 0px'} = link_to crop, crop diff --git a/app/views/crops/_photos.html.haml b/app/views/crops/_photos.html.haml index c6845bbba..5b80ea9c1 100644 --- a/app/views/crops/_photos.html.haml +++ b/app/views/crops/_photos.html.haml @@ -2,5 +2,5 @@ - if !crop.photos.empty? %ul.thumbnails - crop.photos.first(3).each do |p| - %li.span4 + %li.col-md-4 = render :partial => "photos/thumbnail", :locals => { :photo => p } diff --git a/app/views/crops/index.html.haml b/app/views/crops/index.html.haml index 2431bc9b6..182640a63 100644 --- a/app/views/crops/index.html.haml +++ b/app/views/crops/index.html.haml @@ -11,7 +11,7 @@ %ul.thumbnails - @crops.each do |crop| - %li.span2.six-across + %li.col-md-2.six-across = render :partial => "thumbnail", :locals => { :crop => crop } - if can? :create, Crop diff --git a/app/views/crops/search.html.haml b/app/views/crops/search.html.haml index 369d57fdd..2a49c56d3 100644 --- a/app/views/crops/search.html.haml +++ b/app/views/crops/search.html.haml @@ -14,7 +14,7 @@ %h2 Exact match %ul.thumbnails - %li.span2.six-across + %li.col-md-2.six-across = render :partial => "thumbnail", :locals => { :crop => @exact_match } - if ! @partial_matches.empty? @@ -22,5 +22,5 @@ %h2 Partial matches %ul.thumbnails - @partial_matches.each do |c| - %li.span2.six-across + %li.col-md-2.six-across = render :partial => "thumbnail", :locals => { :crop => c } diff --git a/app/views/crops/show.html.haml b/app/views/crops/show.html.haml index d74fbbd79..8a5e0f9ed 100644 --- a/app/views/crops/show.html.haml +++ b/app/views/crops/show.html.haml @@ -1,7 +1,7 @@ - content_for :title, @crop.name .row - .span9 + .col-md-9 %p - if can? :create, Planting = link_to "Plant this", new_planting_path(:crop_id => @crop.id), :class => 'btn btn-primary' @@ -40,7 +40,7 @@ - @crop.plantings.each do |p| = render :partial => "plantings/thumbnail", :locals => { :planting => p, :title => 'owner' } - .span3 + .col-md-3 - if can? :edit, @crop or can? :destroy, @crop %h4 Crop wrangling %p diff --git a/app/views/gardens/show.html.haml b/app/views/gardens/show.html.haml index 25c17073e..bfd696330 100644 --- a/app/views/gardens/show.html.haml +++ b/app/views/gardens/show.html.haml @@ -1,7 +1,7 @@ =content_for :title, "#{@garden.owner}'s #{@garden}" .row - .span9 + .col-md-9 - if ! @garden.active .alert.alert-notice NOTE: This garden is inactive. @@ -37,7 +37,7 @@ - @garden.plantings.each do |p| = render :partial => "plantings/thumbnail", :locals => { :planting => p } - .span3 + .col-md-3 %h4= "#{@garden.owner}'s gardens" %ul - @garden.owner.gardens.active.each do |othergarden| diff --git a/app/views/harvests/show.html.haml b/app/views/harvests/show.html.haml index 21b562408..a4ee0d3c9 100644 --- a/app/views/harvests/show.html.haml +++ b/app/views/harvests/show.html.haml @@ -1,7 +1,7 @@ =content_for :title, "#{@harvest.crop} harvested by #{@harvest.owner}" .row - .span6 + .col-md-6 %p %b Owner: = link_to @harvest.owner, @harvest.owner @@ -27,7 +27,7 @@ - if can? :destroy, @harvest =link_to 'Delete', @harvest, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini' - .span6 + .col-md-6 = render :partial => "crops/index_card", :locals => { :crop => @harvest.crop} %h2 Notes diff --git a/app/views/home/_blurb.html.haml b/app/views/home/_blurb.html.haml index 9f256d498..b3b1833c6 100644 --- a/app/views/home/_blurb.html.haml +++ b/app/views/home/_blurb.html.haml @@ -1,9 +1,9 @@ -.span12 +.col-md-12 %h1= ENV['GROWSTUFF_SITE_NAME'] .row - .span8.info + .col-md-8.info %p #{ENV['GROWSTUFF_SITE_NAME']} is a community of food gardeners. We're building an open source platform to help you learn about @@ -11,7 +11,7 @@ seeds and produce with other gardeners near you. = render :partial => 'stats' - .span4.signup + .col-md-4.signup %p Join now for your free garden journal, seed sharing, forums, and more. %p= link_to 'Sign up', new_member_registration_path, :class => 'btn btn-primary btn-large' %p diff --git a/app/views/home/_crops.html.haml b/app/views/home/_crops.html.haml index 21cda0f88..04c3b2d56 100644 --- a/app/views/home/_crops.html.haml +++ b/app/views/home/_crops.html.haml @@ -1,18 +1,18 @@ .row - .span6.hidden-phone + .col-md-6.hidden-phone - cache "interesting_crops", :expires_in => 1.day do %h2 Some of our crops - Crop.interesting.each do |c| - .span3{:style => 'margin:0px; padding: 3px'} + .col-md-3{:style => 'margin:0px; padding: 3px'} = render :partial => 'crops/image_with_popover', :locals => { :crop => c } - .span6 + .col-md-6 - cache "interesting_plantings" do %h2 Recently planted = render :partial => 'plantings/list', :locals => { :plantings => Planting.interesting.first(4) } .row - .span12 + .col-md-12 - cache "recent_crops" do %p{ :style => 'margin-top: 11.25px' } %strong diff --git a/app/views/home/_members.html.haml b/app/views/home/_members.html.haml index 86bf50d9c..adb7e5f28 100644 --- a/app/views/home/_members.html.haml +++ b/app/views/home/_members.html.haml @@ -6,7 +6,7 @@ .row - members.each do |m| - .span6.homepage-members + .col-md-6.homepage-members = render :partial => "members/thumbnail", :locals => { :member => m } %p.text-right diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index f30735eb5..b7f1bd276 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -1,5 +1,5 @@ .row - .span12 + .col-md-12 - if member_signed_in? %h1 Welcome to @@ -22,12 +22,12 @@ .visible-phone = render :partial => 'blurb' .row - .span8.main + .col-md-8.main = render :partial => 'crops' = render :partial => 'seeds' = render :partial => 'members' = render :partial => 'discuss' - .span4.sidebar + .col-md-4.sidebar = render :partial => 'keep_in_touch' = render :partial => 'open' diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 2ae4385db..4e19be59a 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -6,7 +6,7 @@ .container .row - .span12 + .col-md-12 - if content_for?(:title) %h1= yield(:title) - if notice diff --git a/app/views/members/_thumbnail.html.haml b/app/views/members/_thumbnail.html.haml index d9ab14c0a..bbb06f2f7 100644 --- a/app/views/members/_thumbnail.html.haml +++ b/app/views/members/_thumbnail.html.haml @@ -1,9 +1,9 @@ - cache "member_thumbnail_#{member.id}" do .row .member-thumbnail - .span3 + .col-md-3 = render :partial => "members/image_with_popover", :locals => { :member => member } - .span9 + .col-md-9 %p = link_to member.login_name, member - if ! member.location.blank? diff --git a/app/views/members/index.html.haml b/app/views/members/index.html.haml index 5bac7592b..0bb6f1f43 100644 --- a/app/views/members/index.html.haml +++ b/app/views/members/index.html.haml @@ -6,7 +6,7 @@ %ul.thumbnails - @members.each do |m| - %li.span4.three-across + %li.col-md-4.three-across = render :partial => "members/thumbnail", :locals => { :member => m } %div.pagination diff --git a/app/views/members/nearby.html.haml b/app/views/members/nearby.html.haml index c7eb3cdf0..98dc99cb1 100644 --- a/app/views/members/nearby.html.haml +++ b/app/views/members/nearby.html.haml @@ -19,7 +19,7 @@ %h3 Results found %ul.thumbnails - @nearby_members.each do |member| - %li.span4.three-across + %li.col-md-4.three-across = render :partial => "members/thumbnail", :locals => { :member => member } - elsif @location %h3 No results found diff --git a/app/views/members/show.html.haml b/app/views/members/show.html.haml index 393c39af0..b4e78ff58 100644 --- a/app/views/members/show.html.haml +++ b/app/views/members/show.html.haml @@ -3,7 +3,7 @@ - content_for :member_rss_slug, "#{@member.slug}" .row - .span3 + .col-md-3 = render :partial => "members/avatar", :locals => { :member => @member } -if can? :create, Notification and current_member != @member @@ -48,7 +48,7 @@ %br/ = link_to @member.location, place_path(@member.location) - .span9 + .col-md-9 %p - if can? :update, @member %p diff --git a/app/views/photos/index.html.haml b/app/views/photos/index.html.haml index ddf207906..e11a33416 100644 --- a/app/views/photos/index.html.haml +++ b/app/views/photos/index.html.haml @@ -8,7 +8,7 @@ %ul.thumbnails - @photos.each do |p| - %li.span2.six-across + %li.col-md-2.six-across .thumbnail(style='height: 220px') = link_to image_tag(p.thumbnail_url, :alt => p.title, :class => 'img-rounded'), p %p diff --git a/app/views/photos/new.html.haml b/app/views/photos/new.html.haml index 8d6a14d38..fb06bdeba 100644 --- a/app/views/photos/new.html.haml +++ b/app/views/photos/new.html.haml @@ -24,7 +24,7 @@ %ul.thumbnails .row - @photos.each do |p| - %li.span2.six-across + %li.col-md-2.six-across .thumbnail(style='height: 220px') = link_to image_tag(FlickRaw.url_q(p), :alt => '', :class => 'img-rounded'), photos_path(:photo => { :flickr_photo_id => p.id }, :planting_id => @planting_id), :method => :post %p diff --git a/app/views/photos/show.html.haml b/app/views/photos/show.html.haml index bd83caee6..1e0f8dd95 100644 --- a/app/views/photos/show.html.haml +++ b/app/views/photos/show.html.haml @@ -1,7 +1,7 @@ -content_for :title, @photo.title .row - .span6 + .col-md-6 %p %strong Posted by: = link_to @photo.owner, @photo.owner @@ -18,7 +18,7 @@ - if can? :destroy, @photo %p= link_to 'Delete Photo', @photo, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini' - .span6 + .col-md-6 - if @photo.plantings.count > 0 %p This photo depicts: %ul @@ -26,6 +26,6 @@ %li= link_to p, p .row - .span12 + .col-md-12 %p= image_tag(@photo.fullsize_url, :alt => @photo.title, :class => 'img-rounded') diff --git a/app/views/places/show.html.haml b/app/views/places/show.html.haml index 60629a7ad..27dbc7611 100644 --- a/app/views/places/show.html.haml +++ b/app/views/places/show.html.haml @@ -12,7 +12,7 @@ - if !@nearby_members.empty? %ul.thumbnails - @nearby_members.first(30).each do |member| - %li.span4.three-across + %li.col-md-4.three-across = render :partial => "members/thumbnail", :locals => { :member => member } - elsif @place %p No results found diff --git a/app/views/plantings/_list.html.haml b/app/views/plantings/_list.html.haml index 20cdece6f..3e62fffbc 100644 --- a/app/views/plantings/_list.html.haml +++ b/app/views/plantings/_list.html.haml @@ -1,9 +1,9 @@ - plantings.each do |p| - cache "plantings_listitem_#{p.id}" do .row - .span2{:style => 'padding-bottom: 6px'} + .col-md-2{:style => 'padding-bottom: 6px'} = render :partial => 'plantings/image_with_popover', :locals => { :planting => p } - .span10 + .col-md-10 = link_to p.crop, p.crop in = succeed "'s" do diff --git a/app/views/plantings/_thumbnail.html.haml b/app/views/plantings/_thumbnail.html.haml index f27ec9965..bc65def88 100644 --- a/app/views/plantings/_thumbnail.html.haml +++ b/app/views/plantings/_thumbnail.html.haml @@ -1,9 +1,9 @@ .well .row - .span3 + .col-md-3 = link_to image_tag((planting.default_photo ? planting.default_photo.thumbnail_url : 'placeholder_150.png'), :alt => '', :class => 'img-rounded'), planting - .span9 + .col-md-9 %h4 - if defined?(title) && title == 'owner' = link_to planting.owner, planting.owner diff --git a/app/views/plantings/show.html.haml b/app/views/plantings/show.html.haml index 0494d2897..7891eedbb 100644 --- a/app/views/plantings/show.html.haml +++ b/app/views/plantings/show.html.haml @@ -1,7 +1,7 @@ =content_for :title, "#{@planting.crop} in #{@planting.location}" .row - .span6 + .col-md-6 %p %b Owner: = link_to @planting.owner, @planting.owner @@ -38,7 +38,7 @@ - if can? :destroy, @planting =link_to 'Delete', @planting, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini' - .span6 + .col-md-6 = render :partial => "crops/index_card", :locals => { :crop => @planting.crop} %h2 Notes @@ -51,10 +51,10 @@ %ul.thumbnails - @planting.photos.each do |p| - %li.span2.six-across + %li.col-md-2.six-across = render :partial => 'photos/thumbnail', :locals => { :photo => p } - if can? :create, Photo and can? :edit, @planting - %li.span2 + %li.col-md-2 .thumbnail(style='height: 220px') %p{:style => 'text-align: center; padding-top: 50px'} = link_to "Add photo", new_photo_path(:planting_id => @planting.id), :class => 'btn btn-primary' diff --git a/app/views/posts/_single.html.haml b/app/views/posts/_single.html.haml index 0c6ac3c2a..5e15e039d 100644 --- a/app/views/posts/_single.html.haml +++ b/app/views/posts/_single.html.haml @@ -1,9 +1,9 @@ .well .post .row - .span1 + .col-md-1 = render :partial => "members/avatar", :locals => { :member => post.author } - .span11 + .col-md-11 - if defined?(subject) %h3= link_to strip_tags(post.subject), post diff --git a/app/views/seeds/show.html.haml b/app/views/seeds/show.html.haml index 1244749a6..1b07e7f10 100644 --- a/app/views/seeds/show.html.haml +++ b/app/views/seeds/show.html.haml @@ -1,7 +1,7 @@ - content_for :title, "#{@seed.owner}'s #{@seed.crop} seeds" .row - .span6 + .col-md-6 %p %b Owner: = link_to @seed.owner, @seed.owner @@ -43,5 +43,5 @@ - if can? :destroy, @seed =link_to 'Delete', @seed, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini' - .span6 + .col-md-6 = render :partial => "crops/index_card", :locals => { :crop => @seed.crop } From d7145b327f799cf5f8f24d10f319d05eee12eb84 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 18:19:22 -0400 Subject: [PATCH 009/288] Update .brand to bootstrap3 --- app/views/layouts/_header.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index 813179fb3..54342147b 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -5,7 +5,7 @@ %span.icon-bar %span.icon-bar %span.icon-bar - %a.brand(href=root_path)= ENV['GROWSTUFF_SITE_NAME'] + %a.navbar-brand(href=root_path)= ENV['GROWSTUFF_SITE_NAME'] .nav-collapse.collapse %ul.nav %li.dropdown< From e9b3c330225533c1e9731004b4ff47be02128117 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 18:21:04 -0400 Subject: [PATCH 010/288] Navbar centering style for bootstrap3 --- app/assets/stylesheets/overrides.css.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/overrides.css.less b/app/assets/stylesheets/overrides.css.less index 5c6a387a2..f9aac902c 100644 --- a/app/assets/stylesheets/overrides.css.less +++ b/app/assets/stylesheets/overrides.css.less @@ -158,7 +158,7 @@ li.crop-hierarchy { } // navbar centering -.navbar .nav { +.navbar-nav { float: none; display: inline-block; *display: inline; From 6cb89d1c758cdfa9c40762f0a8c895dcade46abb Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 18:22:01 -0400 Subject: [PATCH 011/288] correct navbar-nav class in header --- app/views/layouts/_header.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index 54342147b..50ca0568a 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -7,7 +7,7 @@ %span.icon-bar %a.navbar-brand(href=root_path)= ENV['GROWSTUFF_SITE_NAME'] .nav-collapse.collapse - %ul.nav + %ul.navbar-nav %li.dropdown< %a.dropdown-toggle{'data-toggle' => 'dropdown', :href => crops_path} Crops From bb458c17e89c4aeec2904b76696ba81a9d16a60a Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 18:23:53 -0400 Subject: [PATCH 012/288] another navbar-collapse instance --- app/views/layouts/_header.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index 50ca0568a..17534a4bf 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -1,12 +1,12 @@ .navbar.navbar-fixed-top .navbar-inner .container - %a.btn.btn-navbar(data-target=".nav-collapse" data-toggle="collapse") + %a.btn.btn-navbar(data-target=".navbar-collapse" data-toggle="collapse") %span.icon-bar %span.icon-bar %span.icon-bar %a.navbar-brand(href=root_path)= ENV['GROWSTUFF_SITE_NAME'] - .nav-collapse.collapse + .navbar-collapse.collapse %ul.navbar-nav %li.dropdown< %a.dropdown-toggle{'data-toggle' => 'dropdown', :href => crops_path} From bf217b161879c21a3e86d786997d1cee6cac44ad Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 18:25:02 -0400 Subject: [PATCH 013/288] correct navbar-btn --- app/views/layouts/_header.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index 17534a4bf..72846efb7 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -1,7 +1,7 @@ .navbar.navbar-fixed-top .navbar-inner .container - %a.btn.btn-navbar(data-target=".navbar-collapse" data-toggle="collapse") + %a.btn.navbar-btn(data-target=".navbar-collapse" data-toggle="collapse") %span.icon-bar %span.icon-bar %span.icon-bar From 6ec7b8f897406a15c338cf579d4f33dcca5409d4 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 18:27:48 -0400 Subject: [PATCH 014/288] Jumbotron classes to bootstrap3 --- app/assets/stylesheets/custom_bootstrap/variables.less | 2 +- app/assets/stylesheets/overrides.css.less | 6 +++--- app/views/home/index.html.haml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/custom_bootstrap/variables.less b/app/assets/stylesheets/custom_bootstrap/variables.less index a1f6f6ad1..f51baff25 100644 --- a/app/assets/stylesheets/custom_bootstrap/variables.less +++ b/app/assets/stylesheets/custom_bootstrap/variables.less @@ -32,7 +32,7 @@ @headings-color: inherit; // empty to use BS default, @textColor // Hero unit -@hero-unit-background: darken(@body-bg, 10%); +@jumbotron-bg: darken(@body-bg, 10%); // Nav bar @navbar-background: @brown; diff --git a/app/assets/stylesheets/overrides.css.less b/app/assets/stylesheets/overrides.css.less index f9aac902c..dedc863c5 100644 --- a/app/assets/stylesheets/overrides.css.less +++ b/app/assets/stylesheets/overrides.css.less @@ -58,18 +58,18 @@ h3 { } // let's condense the hero unit a little -.hero-unit { +.jumbotron { padding-top: 30px; padding-bottom: 30px; } // info under the main heading on homepage -.hero-unit .info { +.jumbotron .info { padding-top: 15px; } // signup widget on homepage -.hero-unit .signup { +.jumbotron .signup { background-color: lighten(@green, 40%); border: 1px solid lighten(@green, 20%); border-radius: 6px; diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index b7f1bd276..e9dc0ec8b 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -17,7 +17,7 @@ - else .visible-desktop.visible-tablet - .hero-unit + .jumbotron = render :partial => 'blurb' .visible-phone = render :partial => 'blurb' From 0d7fbc9b768db897965662a43a28061b7d45ee5c Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 18:33:19 -0400 Subject: [PATCH 015/288] update button class to bootstrap3 --- app/views/layouts/_header.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index 72846efb7..2d54504e7 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -1,7 +1,7 @@ .navbar.navbar-fixed-top .navbar-inner .container - %a.btn.navbar-btn(data-target=".navbar-collapse" data-toggle="collapse") + %a.btn.btn-default.navbar-btn(data-target=".navbar-collapse" data-toggle="collapse") %span.icon-bar %span.icon-bar %span.icon-bar From 76f2d003c5cd0fb1f0c1f3698cfe65b4ba3c4042 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 18:37:33 -0400 Subject: [PATCH 016/288] visible and hidden elements to bootstrap3 --- app/views/home/_crops.html.haml | 2 +- app/views/home/_members.html.haml | 2 +- app/views/home/_seeds.html.haml | 4 ++-- app/views/home/index.html.haml | 4 ++-- app/views/posts/_summary.html.haml | 8 ++++---- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/views/home/_crops.html.haml b/app/views/home/_crops.html.haml index 04c3b2d56..62b6d9e4a 100644 --- a/app/views/home/_crops.html.haml +++ b/app/views/home/_crops.html.haml @@ -1,5 +1,5 @@ .row - .col-md-6.hidden-phone + .col-md-6.hidden-xs - cache "interesting_crops", :expires_in => 1.day do %h2 Some of our crops - Crop.interesting.each do |c| diff --git a/app/views/home/_members.html.haml b/app/views/home/_members.html.haml index adb7e5f28..edb3b4171 100644 --- a/app/views/home/_members.html.haml +++ b/app/views/home/_members.html.haml @@ -1,5 +1,5 @@ - cache "interesting_members" do - .visible-desktop.visible-tablet + .visible-md.visible-sm - members = Member.interesting.first(6) - if members.present? %h2 Some of our members diff --git a/app/views/home/_seeds.html.haml b/app/views/home/_seeds.html.haml index 108efb73e..b37fe3e1d 100644 --- a/app/views/home/_seeds.html.haml +++ b/app/views/home/_seeds.html.haml @@ -10,7 +10,7 @@ %tr %th Owner %th Crop - %th.hidden-phone.hidden-tablet Description + %th.hidden-xs.hidden-sm Description %th Will trade to %th From location %th @@ -19,7 +19,7 @@ %tr %td= link_to seed.owner.login_name, seed.owner %td= link_to seed.crop.name, seed.crop - %td.hidden-phone.hidden-tablet= truncate(seed.description, :length => 40, :separator => ' ') + %td.hidden-xs.hidden-sm= truncate(seed.description, :length => 40, :separator => ' ') %td= seed.tradable? ? seed.tradable_to : '' %td - if seed.tradable? diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index e9dc0ec8b..f5ea0e8e8 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -16,10 +16,10 @@ = link_to "Edit profile", edit_member_registration_path, :class => 'btn' - else - .visible-desktop.visible-tablet + .visible-md.visible-sm .jumbotron = render :partial => 'blurb' - .visible-phone + .visible-xs = render :partial => 'blurb' .row .col-md-8.main diff --git a/app/views/posts/_summary.html.haml b/app/views/posts/_summary.html.haml index 69c2a3479..6e8e30895 100644 --- a/app/views/posts/_summary.html.haml +++ b/app/views/posts/_summary.html.haml @@ -3,18 +3,18 @@ %table.table.table-striped %tr %th Subject - %th.hidden-phone Posted by + %th.hidden-xs Posted by %th Most recent activity - %th.hidden-phone Comments + %th.hidden-xs Comments - posts.recently_active[0..howmany-1].each do |post| %tr %td = link_to truncate(strip_tags(post.subject), :length => 40, :separator => ' '), post - %td.hidden-phone + %td.hidden-xs =link_to post.author, post.author %td = distance_of_time_in_words(post.recent_activity, Time.zone.now) ago - %td.hidden-phone + %td.hidden-xs = post.comments.count.to_s From b7fbe60b8359d2a644e05abea035ee426fd4504d Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 18:45:56 -0400 Subject: [PATCH 017/288] Bootstrap3: form control groups --- app/views/crops/_form.html.haml | 6 ++--- app/views/devise/registrations/edit.html.haml | 22 +++++++++---------- app/views/devise/registrations/new.html.haml | 12 +++++----- app/views/devise/sessions/new.html.haml | 6 ++--- app/views/forums/_form.html.haml | 6 ++--- app/views/gardens/_form.html.haml | 10 ++++----- app/views/harvests/_form.html.haml | 10 ++++----- app/views/plantings/_form.html.haml | 14 ++++++------ app/views/products/_form.html.haml | 2 +- app/views/scientific_names/_form.html.haml | 4 ++-- app/views/seeds/_form.html.haml | 10 ++++----- 11 files changed, 51 insertions(+), 51 deletions(-) diff --git a/app/views/crops/_form.html.haml b/app/views/crops/_form.html.haml index 1ff75ce04..ec6a609b3 100644 --- a/app/views/crops/_form.html.haml +++ b/app/views/crops/_form.html.haml @@ -12,17 +12,17 @@ =link_to "crop wrangling guide", "http://wiki.growstuff.org/index.php/Crop_wrangling" on the Growstuff wiki. - .control-group + .form-group = f.label :name, :class => 'control-label' .controls = f.text_field :name %span.help-inline Name in US English; singular; capitalize proper nouns only. - .control-group + .form-group = f.label :en_wikipedia_url, 'Wikipedia URL', :class => 'control-label' .controls = f.text_field :en_wikipedia_url %span.help-inline Link to this crop's page on the English language Wikipedia. - .control-group + .form-group = f.label :parent_id, 'Parent crop', :class => 'control-label' .controls = collection_select(:crop, :parent_id, Crop.all, :id, :name, {:include_blank => true}) diff --git a/app/views/devise/registrations/edit.html.haml b/app/views/devise/registrations/edit.html.haml index 746cb55ba..5d577defd 100644 --- a/app/views/devise/registrations/edit.html.haml +++ b/app/views/devise/registrations/edit.html.haml @@ -5,23 +5,23 @@ %h2 Email settings - .control-group + .form-group = f.label :email, :class => 'control-label' .controls = f.email_field :email %span.help-inline If you change your email address you will have to reconfirm. - .control-group + .form-group .controls = f.check_box :show_email Show email publicly on your profile page - .control-group + .form-group .controls = f.check_box :send_notification_email Receive emailed copies of Inbox notifications. - .control-group + .form-group .controls = f.check_box :newsletter Subscribe to the #{ENV['GROWSTUFF_SITE_NAME']} newsletter @@ -29,7 +29,7 @@ = render :partial => 'newsletter_blurb' %h2 Profile details - .control-group + .form-group %label.control-label Profile picture .controls @@ -40,20 +40,20 @@ = succeed "." do = link_to 'gravatar.com', "http://gravatar.com/" - .control-group + .form-group =f.label :location, 'Your location', :class => 'control-label' .controls =f.text_field :location, :autocomplete => "off" %span.help-inline This will be displayed on a map. You can be as detailed or vague as you like. - .control-group + .form-group =f.label :bio, :class => 'control-label' .controls =f.text_area :bio, :rows => 6, :class => 'input-block-level' %h2 Linked accounts - .control-group + .form-group .controls %p = image_tag "twitter_32.png", :size => "32x32", :alt => 'Twitter logo' @@ -78,17 +78,17 @@ %p %span.help-block Leave blank if you don't want to change your password. - .control-group + .form-group = f.label :current_password, :class => 'control-label' .controls = f.password_field :current_password - .control-group + .form-group = f.label :password, "New password", :class => 'control-label' .controls = f.password_field :password, :autocomplete => "off" - .control-group + .form-group = f.label :password_confirmation, :class => 'control-label' .controls= f.password_field :password_confirmation diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml index 3d43274d6..e9eb59bd5 100644 --- a/app/views/devise/registrations/new.html.haml +++ b/app/views/devise/registrations/new.html.haml @@ -5,33 +5,33 @@ = form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => {:class => "form-horizontal"}) do |f| = devise_error_messages! - .control-group + .form-group = f.label :login_name, :class => "control-label" .controls = f.text_field :login_name %span.help-inline This is the name that will show on the website. - .control-group + .form-group = f.label :email, :class => "control-label" .controls = f.email_field :email %span.help-inline We'll use this address to contact you (we never spam!) - .control-group + .form-group = f.label :password, :class => "control-label" .controls= f.password_field :password - .control-group + .form-group = f.label :password_confirmation, :class => "control-label" .controls= f.password_field :password_confirmation - .control-group + .form-group .controls = f.check_box :tos_agreement I agree to the = succeed "." do = link_to 'Terms of Service', url_for(:action => 'tos', :controller => '/policy') - .control-group + .form-group .controls = f.check_box :newsletter, :checked => true Subscribe to the #{ENV['GROWSTUFF_SITE_NAME']} newsletter diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index ad691992a..bb3b0b8ff 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -3,16 +3,16 @@ = form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => {:class => "form-horizontal"}) do |f| = devise_error_messages! - .control-group + .form-group = f.label :login, :class => "control-label" .controls= f.text_field :login - .control-group + .form-group = f.label :password, :class => "control-label" .controls= f.password_field :password - if devise_mapping.rememberable? - .control-group + .form-group .controls = f.check_box :remember_me Remember me diff --git a/app/views/forums/_form.html.haml b/app/views/forums/_form.html.haml index fa3d26ba4..db70a8e04 100644 --- a/app/views/forums/_form.html.haml +++ b/app/views/forums/_form.html.haml @@ -6,13 +6,13 @@ - @forum.errors.full_messages.each do |msg| %li= msg - .control-group + .form-group = f.label :name, :class => 'control-label' .controls= f.text_field :name, :class => 'input-block-level' - .control-group + .form-group = f.label :description, :class => 'control-label' .controls= f.text_area :description, :rows => 6, :class => 'input-block-level' - .control-group + .form-group = f.label :owner_id, :class => 'control-label' .controls= collection_select(:forum, :owner_id, Member.all, :id, :login_name) .form-actions diff --git a/app/views/gardens/_form.html.haml b/app/views/gardens/_form.html.haml index 7776f0c23..7a1c5e59e 100644 --- a/app/views/gardens/_form.html.haml +++ b/app/views/gardens/_form.html.haml @@ -6,17 +6,17 @@ - @garden.errors.full_messages.each do |msg| %li= msg - .control-group + .form-group = f.label :name, :class => 'control-label' .controls = f.text_field :name - .control-group + .form-group = f.label :description, :class => 'control-label' .controls = f.text_area :description, :rows => 6 - .control-group + .form-group = f.label :location, :class => 'control-label' .controls = f.text_field :location, :value => @garden.location || current_member.location @@ -28,13 +28,13 @@ - else =link_to "Change your location.", edit_member_registration_path - .control-group + .form-group = f.label :area, :class => 'control-label' .controls = f.number_field :area, :class => 'input-small' = f.select(:area_unit, Garden::AREA_UNITS_VALUES, {:include_blank => false}, :class => 'input-medium') - .control-group + .form-group = f.label 'Active? ', :class => 'control-label' .controls = f.check_box :active diff --git a/app/views/harvests/_form.html.haml b/app/views/harvests/_form.html.haml index 181d0af54..f2a0b183c 100644 --- a/app/views/harvests/_form.html.haml +++ b/app/views/harvests/_form.html.haml @@ -6,7 +6,7 @@ - @harvest.errors.full_messages.each do |msg| %li= msg - .control-group + .form-group = f.label 'What did you harvest?', :class => 'control-label' .controls = collection_select(:harvest, :crop_id, Crop.all, :id, :name, :selected => @harvest.crop_id || @crop.id) @@ -15,11 +15,11 @@ Can't find what you're looking for? = link_to "Request new crops.", Growstuff::Application.config.new_crops_request_link - .control-group + .form-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 + .form-group = f.label 'How many?', :class => 'control-label' .controls -# Some browsers (eg Firefox for Android) assume "number" means @@ -28,13 +28,13 @@ = f.number_field :quantity, :class => 'input-small', :step => 'any' = f.select(:unit, Harvest::UNITS_VALUES, {:include_blank => false}, :class => 'input-medium') - .control-group + .form-group = f.label 'Weighing:', :class => 'control-label' .controls = f.number_field :weight_quantity, :class => 'input-small', :step => 'any' = f.select(:weight_unit, Harvest::WEIGHT_UNITS_VALUES, {:include_blank => false}, :class => 'input-medium') in total - .control-group + .form-group = f.label 'Notes', :class => 'control-label' .controls= f.text_area :description, :rows => 6 diff --git a/app/views/plantings/_form.html.haml b/app/views/plantings/_form.html.haml index 71056f7e7..6806a18fa 100644 --- a/app/views/plantings/_form.html.haml +++ b/app/views/plantings/_form.html.haml @@ -6,35 +6,35 @@ - @planting.errors.full_messages.each do |msg| %li= msg - .control-group + .form-group = f.label 'What did you plant?', :class => 'control-label' .controls = collection_select(:planting, :crop_id, Crop.all, :id, :name, :selected => @planting.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 + .form-group = f.label 'Where did you plant it?', :class => 'control-label' .controls = collection_select(:planting, :garden_id, Garden.active.where(:owner_id => current_member), :id, :name, :selected => @planting.garden_id || @garden.id) %span.help-inline = link_to "Add a garden.", new_garden_path - .control-group + .form-group = f.label 'When?', :class => 'control-label' .controls= f.text_field :planted_at, :value => @planting.planted_at ? @planting.planted_at.to_s(:ymd) : '', :class => 'add-datepicker' - .control-group + .form-group = f.label 'How many?', :class => 'control-label' .controls = f.number_field :quantity, :class => 'input-small' - .control-group + .form-group = f.label 'Planted from:', :class => 'control-label' .controls = f.select(:planted_from, Planting::PLANTED_FROM_VALUES, {:include_blank => true}) - .control-group + .form-group = f.label 'Sun or shade?', :class => 'control-label' .controls = f.select(:sunniness, Planting::SUNNINESS_VALUES, {:include_blank => true}) - .control-group + .form-group = f.label 'Tell us more about it', :class => 'control-label' .controls= f.text_area :description, :rows => 6 diff --git a/app/views/products/_form.html.haml b/app/views/products/_form.html.haml index 539e4b54c..291ece50a 100644 --- a/app/views/products/_form.html.haml +++ b/app/views/products/_form.html.haml @@ -25,6 +25,6 @@ .field = f.label :paid_months = f.text_field :paid_months - .control-group + .form-group .actions = f.submit 'Save' diff --git a/app/views/scientific_names/_form.html.haml b/app/views/scientific_names/_form.html.haml index 4ba12d586..2ee348e4a 100644 --- a/app/views/scientific_names/_form.html.haml +++ b/app/views/scientific_names/_form.html.haml @@ -12,11 +12,11 @@ =link_to "crop wrangling guide", "http://wiki.growstuff.org/index.php/Crop_wrangling" on the Growstuff wiki. - .control-group + .form-group = f.label :crop_id, :class => 'control-label' .controls = collection_select(:scientific_name, :crop_id, Crop.all, :id, :name, :selected => @scientific_name.crop_id || @crop.id) - .control-group + .form-group = f.label :scientific_name, :class => 'control-label' .controls = f.text_field :scientific_name diff --git a/app/views/seeds/_form.html.haml b/app/views/seeds/_form.html.haml index cb4ca579e..5e9270800 100644 --- a/app/views/seeds/_form.html.haml +++ b/app/views/seeds/_form.html.haml @@ -6,20 +6,20 @@ - @seed.errors.full_messages.each do |msg| %li= msg - .control-group + .form-group = f.label 'Crop:', :class => 'control-label' .controls= collection_select(:seed, :crop_id, Crop.all, :id, :name, :selected => @seed.crop_id || @crop.id) - .control-group + .form-group = f.label 'Quantity:', :class => 'control-label' .controls = f.number_field :quantity, :class => 'input-small' - .control-group + .form-group = f.label 'Plant before:', :class => 'control-label' .controls= f.text_field :plant_before, :value => @seed.plant_before ? @seed.plant_before.to_s(:ymd) : '', :class => 'add-datepicker' - .control-group + .form-group = f.label 'Description:', :class => 'control-label' .controls= f.text_area :description, :rows => 6 - .control-group + .form-group = f.label 'Will trade:', :class => 'control-label' .controls = f.select(:tradable_to, From 155d68bab1f816578f9fdd04b41e2d431298b79e Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 18:46:08 -0400 Subject: [PATCH 018/288] Bootstrap3: inline lists --- app/assets/stylesheets/overrides.css.less | 2 +- app/views/crops/index.html.haml | 2 +- app/views/harvests/index.html.haml | 2 +- app/views/home/_discuss.html.haml | 2 +- app/views/plantings/index.html.haml | 2 +- app/views/posts/_single.html.haml | 2 +- app/views/seeds/index.html.haml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/assets/stylesheets/overrides.css.less b/app/assets/stylesheets/overrides.css.less index dedc863c5..d977424db 100644 --- a/app/assets/stylesheets/overrides.css.less +++ b/app/assets/stylesheets/overrides.css.less @@ -25,7 +25,7 @@ body { //@import "twitter/bootstrap/sprites.less"; -ul.inline > li.first { +.list-inline > li.first { padding-left: 0px; } diff --git a/app/views/crops/index.html.haml b/app/views/crops/index.html.haml index 182640a63..5ea19bed6 100644 --- a/app/views/crops/index.html.haml +++ b/app/views/crops/index.html.haml @@ -23,7 +23,7 @@ = will_paginate @crops -%ul.inline +%ul.list-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') diff --git a/app/views/harvests/index.html.haml b/app/views/harvests/index.html.haml index bbc96c20e..ea08b0da8 100644 --- a/app/views/harvests/index.html.haml +++ b/app/views/harvests/index.html.haml @@ -52,7 +52,7 @@ = page_entries_info @harvests, :model => "harvests" = will_paginate @harvests - %ul.inline + %ul.list-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') diff --git a/app/views/home/_discuss.html.haml b/app/views/home/_discuss.html.haml index 0d3e3252a..7da71a54d 100644 --- a/app/views/home/_discuss.html.haml +++ b/app/views/home/_discuss.html.haml @@ -8,7 +8,7 @@ - cache "homepage_forums" do - forums = Forum.all - if forums - %ul.inline + %ul.list-inline %li %strong Forums: - forums.each do |f| diff --git a/app/views/plantings/index.html.haml b/app/views/plantings/index.html.haml index 7f98eb102..647810ea7 100644 --- a/app/views/plantings/index.html.haml +++ b/app/views/plantings/index.html.haml @@ -52,7 +52,7 @@ = page_entries_info @plantings, :model => "plantings" = will_paginate @plantings - %ul.inline + %ul.list-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') diff --git a/app/views/posts/_single.html.haml b/app/views/posts/_single.html.haml index 5e15e039d..8f5cc5507 100644 --- a/app/views/posts/_single.html.haml +++ b/app/views/posts/_single.html.haml @@ -23,7 +23,7 @@ - unless defined?(hide_comments) .post-comments - %ul.inline + %ul.list-inline %li.first= link_to pluralize(post.comments.count, "comment"), post_path(post, :anchor => 'comments') -if can? :create, Comment diff --git a/app/views/seeds/index.html.haml b/app/views/seeds/index.html.haml index cd317332a..3fff7aded 100644 --- a/app/views/seeds/index.html.haml +++ b/app/views/seeds/index.html.haml @@ -57,7 +57,7 @@ = page_entries_info @seeds, :model => "seeds" = will_paginate @seeds - %ul.inline + %ul.list-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') From 4436562dec8848363e2ac973b0bf457a8c0f39e6 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 19:05:51 -0400 Subject: [PATCH 019/288] Bootstrap3: fix vars for nav & dropdowns --- .../custom_bootstrap/variables.less | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/custom_bootstrap/variables.less b/app/assets/stylesheets/custom_bootstrap/variables.less index f51baff25..d01720ac5 100644 --- a/app/assets/stylesheets/custom_bootstrap/variables.less +++ b/app/assets/stylesheets/custom_bootstrap/variables.less @@ -35,15 +35,15 @@ @jumbotron-bg: darken(@body-bg, 10%); // Nav bar -@navbar-background: @brown; -@navbar-background-highlight: @brown; -@navbar-text: @beige; -@navbar-link-color: darken(@beige, 20%); -@navbar-link-color-hover: @beige; -@navbar-link-color-active: @beige; +@navbar-default-bg: @brown; +@navbar-default-bg-highlight: @brown; +@navbar-default-color: @beige; +@navbar-default-link-color: darken(@beige, 20%); +@navbar-default-link-hover-color: @beige; +@navbar-default-link-active-color: @beige; @navbar-brand-color: lighten(@green, 20%); -@dropdown-background: lighten(@beige, 10%); +@dropdown-bg: lighten(@beige, 10%); @dropdown-link-color: @brown; -@dropdown-link-color-hover: @brown; -@dropdown-link-background-hover: lighten(@green, 50%); \ No newline at end of file +@dropdown-link-hover-color: @brown; +@dropdown-link-hover-bg: lighten(@green, 50%); \ No newline at end of file From 4728de5dd81688131300692029b41f055bd1fe5a Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 19:27:06 -0400 Subject: [PATCH 020/288] Clean up header markup for Bootstrap 3 --- app/views/layouts/_header.html.haml | 115 ++++++++++++++-------------- 1 file changed, 58 insertions(+), 57 deletions(-) diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index 2d54504e7..6b749be3b 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -1,69 +1,70 @@ -.navbar.navbar-fixed-top - .navbar-inner - .container - %a.btn.btn-default.navbar-btn(data-target=".navbar-collapse" data-toggle="collapse") +.navbar.navbar-fixed-top(role="navigation") + .container-fluid + .navbar-header + %button.navbar-toggle(data-target="#navbar-collapse" data-toggle="collapse") + %span.sr-only Toggle Navigation %span.icon-bar %span.icon-bar %span.icon-bar %a.navbar-brand(href=root_path)= ENV['GROWSTUFF_SITE_NAME'] - .navbar-collapse.collapse - %ul.navbar-nav - %li.dropdown< - %a.dropdown-toggle{'data-toggle' => 'dropdown', :href => crops_path} - Crops - %b.caret - %ul.dropdown-menu - %li= link_to "Browse Crops", crops_path - %li= link_to "Seeds", seeds_path - %li= link_to "Plantings", plantings_path - %li= link_to "Harvests", harvests_path - %li.dropdown< - %a.dropdown-toggle{'data-toggle' => 'dropdown', :href => members_path} - Community - %b.caret - %ul.dropdown-menu - %li= link_to "Community Map", places_path - %li= link_to "Browse Members", members_path - %li= link_to "Posts", posts_path - %li= link_to "Forums", forums_path + .navbar-collapse.collapse#navbar-collapse + %ul.nav.navbar-nav + %li.dropdown< + %a.dropdown-toggle{'data-toggle' => 'dropdown', :href => crops_path} + Crops + %b.caret + %ul.dropdown-menu + %li= link_to "Browse Crops", crops_path + %li= link_to "Seeds", seeds_path + %li= link_to "Plantings", plantings_path + %li= link_to "Harvests", harvests_path + %li.dropdown< + %a.dropdown-toggle{'data-toggle' => 'dropdown', :href => members_path} + Community + %b.caret + %ul.dropdown-menu + %li= link_to "Community Map", places_path + %li= link_to "Browse Members", members_path + %li= link_to "Posts", posts_path + %li= link_to "Forums", forums_path - - if member_signed_in? - %li.dropdown< - %a.dropdown-toggle{'data-toggle' => 'dropdown', :href => root_path} + - if member_signed_in? + %li.dropdown< + %a.dropdown-toggle{'data-toggle' => 'dropdown', :href => root_path} + - if current_member.notifications.unread_count > 0 + Your Stuff (#{current_member.notifications.unread_count}) + - else + Your Stuff + %b.caret + %ul.dropdown-menu + %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 + %li - if current_member.notifications.unread_count > 0 - Your Stuff (#{current_member.notifications.unread_count}) + = link_to("Inbox (#{current_member.notifications.unread_count})", notifications_path) - else - Your Stuff - %b.caret - %ul.dropdown-menu - %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 - %li - - if current_member.notifications.unread_count > 0 - = link_to("Inbox (#{current_member.notifications.unread_count})", notifications_path) - - else - = link_to("Inbox", notifications_path) - - if current_member.has_role?(:crop_wrangler) || current_member.has_role?(:admin) - %li{:class => 'divider', :role => 'presentation'} - - if current_member.has_role?(:crop_wrangler) - %li= link_to "Crop Wrangling", wrangle_crops_path - - if current_member.has_role?(:admin) - %li= link_to "Admin", admin_path - %li= link_to "Support Growstuff", shop_path + = link_to("Inbox", notifications_path) + - if current_member.has_role?(:crop_wrangler) || current_member.has_role?(:admin) + %li{:class => 'divider', :role => 'presentation'} + - if current_member.has_role?(:crop_wrangler) + %li= link_to "Crop Wrangling", wrangle_crops_path + - if current_member.has_role?(:admin) + %li= link_to "Admin", admin_path + %li= link_to "Support Growstuff", shop_path - %li= link_to "Sign out", destroy_member_session_path, :method => :delete + %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 + - else + %li= link_to 'Sign in', new_member_session_path + %li= link_to 'Sign up', new_member_registration_path - = form_tag crops_search_path, :method => :get, :class => 'navbar-search pull-right' do - .input - = text_field_tag 'search', nil, :class => 'search-query input-medium', :placeholder => 'Search crops' + = form_tag crops_search_path, :method => :get, :class => 'navbar-search pull-right' do + .input + = text_field_tag 'search', nil, :class => 'search-query input-medium', :placeholder => 'Search crops' From 17c822f6f35c5452b9cb7b2c8d7e61af5e7c2f61 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 19:44:21 -0400 Subject: [PATCH 021/288] Unitless line-height --- app/assets/stylesheets/custom_bootstrap/variables.less | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/stylesheets/custom_bootstrap/variables.less b/app/assets/stylesheets/custom_bootstrap/variables.less index d01720ac5..0f72d41b5 100644 --- a/app/assets/stylesheets/custom_bootstrap/variables.less +++ b/app/assets/stylesheets/custom_bootstrap/variables.less @@ -13,7 +13,6 @@ @body-bg: @beige; @text-color: @brown; - @link-color: @green; // Typography (with help from bootswatch.com's "readable" theme) @@ -24,7 +23,7 @@ @font-size-base: 15px; @font-family-base: @font-family-sans-serif; -@line-height-base: @font-size-base * 1.5; +@line-height-base: 1.5; @alt-font-family: @font-family-sans-serif; @headings-font-family: "Lora", Georgia, "Times New Roman", Times, serif; From 0263e361c50441344128628726c3325f3e9b698f Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 20:06:31 -0400 Subject: [PATCH 022/288] Update footer markup to use bootstrap3 navbar classes --- app/views/layouts/_footer.html.haml | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/app/views/layouts/_footer.html.haml b/app/views/layouts/_footer.html.haml index b30d8e461..d9235ff25 100644 --- a/app/views/layouts/_footer.html.haml +++ b/app/views/layouts/_footer.html.haml @@ -1,11 +1,10 @@ -.navbar.navbar-bottom - .navbar-inner - .container.center - %ul.nav.text-center - %li= link_to "About", "http://wiki.growstuff.org/index.php/About%20Growstuff" - %li= link_to "Contact", url_for(:controller => '/about', :action => 'contact') - %li= link_to "Terms of Service", url_for(:controller => '/policy', :action => 'tos') - %li= link_to "Privacy Policy", url_for(:controller => %'/policy', :action => 'privacy') - %li= link_to "Community Guidelines", url_for(:controller => '/policy', :action => 'community') - %li= link_to "Support/FAQ", url_for(:controller => '/support') - %li= link_to "Open Source", "https://github.com/Growstuff/growstuff" +.navbar.navbar-default.navbar-bottom + .container + %ul.nav.navbar-nav.text-center + %li= link_to "About", "http://wiki.growstuff.org/index.php/About%20Growstuff" + %li= link_to "Contact", url_for(:controller => '/about', :action => 'contact') + %li= link_to "Terms of Service", url_for(:controller => '/policy', :action => 'tos') + %li= link_to "Privacy Policy", url_for(:controller => %'/policy', :action => 'privacy') + %li= link_to "Community Guidelines", url_for(:controller => '/policy', :action => 'community') + %li= link_to "Support/FAQ", url_for(:controller => '/support') + %li= link_to "Open Source", "https://github.com/Growstuff/growstuff" From 27062d253717c3e1a74fc8b7472f8c980d94cddf Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 20:07:20 -0400 Subject: [PATCH 023/288] Button group styling for homepage plant/harvest/etc actions --- app/views/home/index.html.haml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index f5ea0e8e8..8cb4ce07d 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -9,11 +9,11 @@ = render :partial => 'stats' %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' + = link_to "Plant", new_planting_path, :class => 'btn btn-default' + = link_to "Harvest", new_harvest_path, :class => 'btn btn-default' + = link_to "Add seeds", new_seed_path, :class => 'btn btn-default' + = link_to "Post", new_post_path, :class => 'btn btn-default' + = link_to "Edit profile", edit_member_registration_path, :class => 'btn btn-default' - else .visible-md.visible-sm From 84f663567945279b78df5f96dcfb13efd4c8f212 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 20:07:42 -0400 Subject: [PATCH 024/288] Continue cleaning up header styling --- .../custom_bootstrap/variables.less | 4 ++-- app/assets/stylesheets/overrides.css.less | 22 +------------------ app/views/layouts/_header.html.haml | 8 +++---- 3 files changed, 7 insertions(+), 27 deletions(-) diff --git a/app/assets/stylesheets/custom_bootstrap/variables.less b/app/assets/stylesheets/custom_bootstrap/variables.less index 0f72d41b5..f0743bb4c 100644 --- a/app/assets/stylesheets/custom_bootstrap/variables.less +++ b/app/assets/stylesheets/custom_bootstrap/variables.less @@ -22,7 +22,7 @@ @font-family-mono: Monaco, Menlo, Consolas, "Courier New", monospace; @font-size-base: 15px; -@font-family-base: @font-family-sans-serif; +@font-family-base: @font-family-serif; @line-height-base: 1.5; @alt-font-family: @font-family-sans-serif; @@ -40,7 +40,7 @@ @navbar-default-link-color: darken(@beige, 20%); @navbar-default-link-hover-color: @beige; @navbar-default-link-active-color: @beige; -@navbar-brand-color: lighten(@green, 20%); +@navbar-default-brand-color: lighten(@green, 20%); @dropdown-bg: lighten(@beige, 10%); @dropdown-link-color: @brown; diff --git a/app/assets/stylesheets/overrides.css.less b/app/assets/stylesheets/overrides.css.less index d977424db..3d051601b 100644 --- a/app/assets/stylesheets/overrides.css.less +++ b/app/assets/stylesheets/overrides.css.less @@ -149,10 +149,6 @@ li.crop-hierarchy { list-style-type: disc; } -.navbar-inner { - border-width: 0px !important; -} - .navbar-bottom { margin: 40px 0px 0px 0px !important; } @@ -177,22 +173,6 @@ li.crop-hierarchy { } } -.navbar-inner { - border: none !important; - background-image: none !important; - -webkit-border-radius: 0px; - -moz-border-radius: 0px; - border-radius: 0px; -} - -.navbar-bottom .navbar-inner { +.navbar-bottom .navbar { text-align: center; } - -.navbar-bottom .container{ - padding: 20px 0px 40px 0px; -} - -.navbar-search{ - padding-bottom: 7px; -} diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index 6b749be3b..568b08154 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -1,5 +1,5 @@ -.navbar.navbar-fixed-top(role="navigation") - .container-fluid +.navbar.navbar-default.navbar-fixed-top(role="navigation") + .container .navbar-header %button.navbar-toggle(data-target="#navbar-collapse" data-toggle="collapse") %span.sr-only Toggle Navigation @@ -65,6 +65,6 @@ %li= link_to 'Sign in', new_member_session_path %li= link_to 'Sign up', new_member_registration_path - = form_tag crops_search_path, :method => :get, :class => 'navbar-search pull-right' do + = form_tag crops_search_path, :method => :get, :class => 'navbar-form pull-right' do .input - = text_field_tag 'search', nil, :class => 'search-query input-medium', :placeholder => 'Search crops' + = text_field_tag 'search', nil, :class => 'search-query input-medium form-control', :placeholder => 'Search crops' From fdd3951ed77ffb076b9bbb368526c3ae97ac3b89 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 20:13:44 -0400 Subject: [PATCH 025/288] Green is the primary brand color --- app/assets/stylesheets/custom_bootstrap/variables.less | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/stylesheets/custom_bootstrap/variables.less b/app/assets/stylesheets/custom_bootstrap/variables.less index f0743bb4c..b4c9eeeb5 100644 --- a/app/assets/stylesheets/custom_bootstrap/variables.less +++ b/app/assets/stylesheets/custom_bootstrap/variables.less @@ -15,6 +15,8 @@ @text-color: @brown; @link-color: @green; +@brand-primary: @green; + // Typography (with help from bootswatch.com's "readable" theme) @import url('//fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic'); @font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif; From b374beae35362866729cb5f9501064ad888d34b6 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 20:20:58 -0400 Subject: [PATCH 026/288] Tweaks for bottom navbar --- app/assets/stylesheets/overrides.css.less | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/overrides.css.less b/app/assets/stylesheets/overrides.css.less index 3d051601b..6f7afe5c4 100644 --- a/app/assets/stylesheets/overrides.css.less +++ b/app/assets/stylesheets/overrides.css.less @@ -173,6 +173,7 @@ li.crop-hierarchy { } } -.navbar-bottom .navbar { +.navbar-bottom.navbar { text-align: center; + border-radius: 0; } From db9ef0bc8368f642de75407fcd5ef0493a3d81de Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 20:28:20 -0400 Subject: [PATCH 027/288] Full-width form elements --- app/views/comments/_form.html.haml | 2 +- app/views/devise/registrations/edit.html.haml | 2 +- app/views/forums/_form.html.haml | 4 ++-- app/views/notifications/_form.html.haml | 4 ++-- app/views/posts/_form.html.haml | 4 ++-- app/views/products/_form.html.haml | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/views/comments/_form.html.haml b/app/views/comments/_form.html.haml index 3e3f6f66b..bbb96d1a7 100644 --- a/app/views/comments/_form.html.haml +++ b/app/views/comments/_form.html.haml @@ -7,7 +7,7 @@ %li= msg .field - = f.text_area :body, :rows => 6, :class => 'input-block-level' + = f.text_area :body, :rows => 6, :class => 'form-control' %span.help-block = render :partial => "shared/markdown_help" .actions diff --git a/app/views/devise/registrations/edit.html.haml b/app/views/devise/registrations/edit.html.haml index 5d577defd..cb77568f4 100644 --- a/app/views/devise/registrations/edit.html.haml +++ b/app/views/devise/registrations/edit.html.haml @@ -49,7 +49,7 @@ .form-group =f.label :bio, :class => 'control-label' .controls - =f.text_area :bio, :rows => 6, :class => 'input-block-level' + =f.text_area :bio, :rows => 6, :class => 'form-control' %h2 Linked accounts diff --git a/app/views/forums/_form.html.haml b/app/views/forums/_form.html.haml index db70a8e04..503481187 100644 --- a/app/views/forums/_form.html.haml +++ b/app/views/forums/_form.html.haml @@ -8,10 +8,10 @@ .form-group = f.label :name, :class => 'control-label' - .controls= f.text_field :name, :class => 'input-block-level' + .controls= f.text_field :name, :class => 'form-control' .form-group = f.label :description, :class => 'control-label' - .controls= f.text_area :description, :rows => 6, :class => 'input-block-level' + .controls= f.text_area :description, :rows => 6, :class => 'form-control' .form-group = f.label :owner_id, :class => 'control-label' .controls= collection_select(:forum, :owner_id, Member.all, :id, :login_name) diff --git a/app/views/notifications/_form.html.haml b/app/views/notifications/_form.html.haml index babb75fd0..91115b15a 100644 --- a/app/views/notifications/_form.html.haml +++ b/app/views/notifications/_form.html.haml @@ -13,9 +13,9 @@ To: = link_to @recipient, @recipient = label_tag :notification, "Subject:" - = f.text_field :subject, :value => @subject, :class => 'input-block-level' + = f.text_field :subject, :value => @subject, :class => 'form-control' = label_tag :body, "Type your message here:" - = f.text_area :body, :rows => 12, :class => 'input-block-level' + = f.text_area :body, :rows => 12, :class => 'form-control' %span.help-block = render :partial => "shared/markdown_help" diff --git a/app/views/posts/_form.html.haml b/app/views/posts/_form.html.haml index d52a09b65..d08a577a5 100644 --- a/app/views/posts/_form.html.haml +++ b/app/views/posts/_form.html.haml @@ -8,12 +8,12 @@ = label_tag :post, "Subject" - = f.text_field :subject, :class => 'input-block-level' + = f.text_field :subject, :class => 'form-control' - if @post.forum || @forum = label_tag :body, "What's up?" - else = label_tag :body, "What's going on in your food garden?" - = f.text_area :body, :rows => 12, :class => 'input-block-level' + = f.text_area :body, :rows => 12, :class => 'form-control' %span.help-block = render :partial => "shared/markdown_help" diff --git a/app/views/products/_form.html.haml b/app/views/products/_form.html.haml index 291ece50a..f09bce0d8 100644 --- a/app/views/products/_form.html.haml +++ b/app/views/products/_form.html.haml @@ -8,10 +8,10 @@ .field = f.label :name - = f.text_field :name, :class => 'input-block-level' + = f.text_field :name, :class => 'form-control' .field = f.label :description - = f.text_area :description, :class => 'input-block-level' + = f.text_area :description, :class => 'form-control' .field = f.label :min_price, "Minimum price (in cents)" = f.text_field :min_price From 005a31c880f21669fdd2ec367c229a9fcc96a284 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 22:19:57 -0400 Subject: [PATCH 028/288] Use a hidden class for phones only, rather than multiple visible classes, on member block --- app/views/home/_members.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/home/_members.html.haml b/app/views/home/_members.html.haml index edb3b4171..6d3437302 100644 --- a/app/views/home/_members.html.haml +++ b/app/views/home/_members.html.haml @@ -1,5 +1,5 @@ - cache "interesting_members" do - .visible-md.visible-sm + .hidden-xs - members = Member.interesting.first(6) - if members.present? %h2 Some of our members From 2fbd596811b860f78541189e6edf1cf10d4b5059 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 22:25:21 -0400 Subject: [PATCH 029/288] Hidden class (not visible), to disable jumbotron class on home --- app/views/home/index.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 8cb4ce07d..57fb6473f 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -16,7 +16,7 @@ = link_to "Edit profile", edit_member_registration_path, :class => 'btn btn-default' - else - .visible-md.visible-sm + .hidden-xs .jumbotron = render :partial => 'blurb' .visible-xs From d910b5629a2cdbb6ba7eb8ab1b1998b6fd636c34 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 22:39:58 -0400 Subject: [PATCH 030/288] Make thumbnail avatar responsive --- app/views/members/_avatar.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/members/_avatar.html.haml b/app/views/members/_avatar.html.haml index 5750fb170..c56c8768a 100644 --- a/app/views/members/_avatar.html.haml +++ b/app/views/members/_avatar.html.haml @@ -5,5 +5,5 @@ :size => defined?(size) ? size : 150, | :default => :identicon }), | :alt => '', | - :class => 'img-rounded' ), | + :class => 'img-rounded img-responsive' ), | member_path(member) From 6503b2ec7ce760c9e67ef1c8f9e9870e1c4693c4 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Sun, 13 Jul 2014 23:54:37 -0400 Subject: [PATCH 031/288] Turn off caching in dev --- config/environments/development.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/environments/development.rb b/config/environments/development.rb index 1727e5203..7b25f588c 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -13,7 +13,7 @@ Growstuff::Application.configure do config.consider_all_requests_local = true # cache for testing/experimentation - turn off for normal dev use - config.action_controller.perform_caching = true + config.action_controller.perform_caching = false config.cache_store = :memory_store # Don't care if the mailer can't send From e93d4df3c0ce5721b21820a99dc63a394a99b68d Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Mon, 14 Jul 2014 00:02:53 -0400 Subject: [PATCH 032/288] Responsive thumbnails for members --- app/views/members/_image_with_popover.html.haml | 3 ++- app/views/members/index.html.haml | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/views/members/_image_with_popover.html.haml b/app/views/members/_image_with_popover.html.haml index 4332b0e51..4202f0f58 100644 --- a/app/views/members/_image_with_popover.html.haml +++ b/app/views/members/_image_with_popover.html.haml @@ -4,7 +4,8 @@ options = { | :size => defined?(size) ? size : 150, | :default => :identicon }), | - :alt => member.login_name, | + :alt => member.login_name, | + :class => 'img-responsive' | ), | member, | :rel => "popover", | diff --git a/app/views/members/index.html.haml b/app/views/members/index.html.haml index 0bb6f1f43..83a663d47 100644 --- a/app/views/members/index.html.haml +++ b/app/views/members/index.html.haml @@ -4,10 +4,11 @@ = page_entries_info @members, :model => "members" = will_paginate @members -%ul.thumbnails +.row - @members.each do |m| - %li.col-md-4.three-across - = render :partial => "members/thumbnail", :locals => { :member => m } + .col-md-4.three-across + .thumbnail + = render :partial => "members/thumbnail", :locals => { :member => m } %div.pagination = page_entries_info @members, :model => "members" From 48d3488b2dd1106576c4e1f386c1e9aa05c18a1c Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Mon, 14 Jul 2014 00:06:40 -0400 Subject: [PATCH 033/288] B3 thumbnails for crops --- app/views/crops/index.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/crops/index.html.haml b/app/views/crops/index.html.haml index 5ea19bed6..c7a5a7750 100644 --- a/app/views/crops/index.html.haml +++ b/app/views/crops/index.html.haml @@ -9,9 +9,9 @@ = page_entries_info @crops, :model => "crops" = will_paginate @crops -%ul.thumbnails +.row - @crops.each do |crop| - %li.col-md-2.six-across + .col-md-2.six-across = render :partial => "thumbnail", :locals => { :crop => crop } - if can? :create, Crop From 8004632b41b27a8c393c3c265bd617bbb176f6ee Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Mon, 14 Jul 2014 20:39:40 -0400 Subject: [PATCH 034/288] Thumbnail style on individual planting page --- app/views/plantings/show.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/plantings/show.html.haml b/app/views/plantings/show.html.haml index 7891eedbb..6d8f09b84 100644 --- a/app/views/plantings/show.html.haml +++ b/app/views/plantings/show.html.haml @@ -49,12 +49,12 @@ - if @planting.photos.count > 0 or (can? :edit, @planting and can? :create, Photo) %h2 Pictures - %ul.thumbnails + .row - @planting.photos.each do |p| - %li.col-md-2.six-across + .col-md-2.six-across = render :partial => 'photos/thumbnail', :locals => { :photo => p } - if can? :create, Photo and can? :edit, @planting - %li.col-md-2 + .col-md-2 .thumbnail(style='height: 220px') %p{:style => 'text-align: center; padding-top: 50px'} = link_to "Add photo", new_photo_path(:planting_id => @planting.id), :class => 'btn btn-primary' From 81a996b8cca0a26a992747ba00476a398d3e370d Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Mon, 14 Jul 2014 20:50:35 -0400 Subject: [PATCH 035/288] New markup for thumbnails --- app/views/crops/_photos.html.haml | 7 +++---- app/views/crops/search.html.haml | 8 ++++---- app/views/members/_thumbnail.html.haml | 2 +- app/views/members/nearby.html.haml | 4 ++-- app/views/photos/_thumbnail.html.haml | 2 +- app/views/photos/index.html.haml | 4 ++-- app/views/places/show.html.haml | 4 ++-- 7 files changed, 15 insertions(+), 16 deletions(-) diff --git a/app/views/crops/_photos.html.haml b/app/views/crops/_photos.html.haml index 5b80ea9c1..bc62ae216 100644 --- a/app/views/crops/_photos.html.haml +++ b/app/views/crops/_photos.html.haml @@ -1,6 +1,5 @@ .row - if !crop.photos.empty? - %ul.thumbnails - - crop.photos.first(3).each do |p| - %li.col-md-4 - = render :partial => "photos/thumbnail", :locals => { :photo => p } + - crop.photos.first(3).each do |p| + .col-md-4 + = render :partial => "photos/thumbnail", :locals => { :photo => p } diff --git a/app/views/crops/search.html.haml b/app/views/crops/search.html.haml index 2a49c56d3..e10ae6f76 100644 --- a/app/views/crops/search.html.haml +++ b/app/views/crops/search.html.haml @@ -13,14 +13,14 @@ %div#exact_match %h2 Exact match - %ul.thumbnails - %li.col-md-2.six-across + .row + .col-md-2.six-across = render :partial => "thumbnail", :locals => { :crop => @exact_match } - if ! @partial_matches.empty? %div#partial_matches %h2 Partial matches - %ul.thumbnails + .row - @partial_matches.each do |c| - %li.col-md-2.six-across + .col-md-2.six-across = render :partial => "thumbnail", :locals => { :crop => c } diff --git a/app/views/members/_thumbnail.html.haml b/app/views/members/_thumbnail.html.haml index bbb06f2f7..32c2e2242 100644 --- a/app/views/members/_thumbnail.html.haml +++ b/app/views/members/_thumbnail.html.haml @@ -1,6 +1,6 @@ - cache "member_thumbnail_#{member.id}" do .row - .member-thumbnail + .member-thumbnail.thumbnail .col-md-3 = render :partial => "members/image_with_popover", :locals => { :member => member } .col-md-9 diff --git a/app/views/members/nearby.html.haml b/app/views/members/nearby.html.haml index 98dc99cb1..59c7056f6 100644 --- a/app/views/members/nearby.html.haml +++ b/app/views/members/nearby.html.haml @@ -17,9 +17,9 @@ - if !@nearby_members.empty? %h3 Results found - %ul.thumbnails + .row - @nearby_members.each do |member| - %li.col-md-4.three-across + .col-md-4.three-across = render :partial => "members/thumbnail", :locals => { :member => member } - elsif @location %h3 No results found diff --git a/app/views/photos/_thumbnail.html.haml b/app/views/photos/_thumbnail.html.haml index b6dace133..fd331fd77 100644 --- a/app/views/photos/_thumbnail.html.haml +++ b/app/views/photos/_thumbnail.html.haml @@ -1,5 +1,5 @@ .thumbnail.crop-thumbnail - = link_to image_tag(photo.thumbnail_url, :alt => photo.title, :class => 'img-rounded'), photo + = link_to image_tag(photo.thumbnail_url, :alt => photo.title, :class => 'img-rounded img-responsive'), photo .text %p = link_to photo.title, photo diff --git a/app/views/photos/index.html.haml b/app/views/photos/index.html.haml index e11a33416..023c102e2 100644 --- a/app/views/photos/index.html.haml +++ b/app/views/photos/index.html.haml @@ -6,9 +6,9 @@ = page_entries_info @photos, :model => "photos" = will_paginate @photos -%ul.thumbnails +.row - @photos.each do |p| - %li.col-md-2.six-across + .col-md-2.six-across .thumbnail(style='height: 220px') = link_to image_tag(p.thumbnail_url, :alt => p.title, :class => 'img-rounded'), p %p diff --git a/app/views/places/show.html.haml b/app/views/places/show.html.haml index 27dbc7611..2feaf8965 100644 --- a/app/views/places/show.html.haml +++ b/app/views/places/show.html.haml @@ -10,9 +10,9 @@ %h3 Nearby members - if !@nearby_members.empty? - %ul.thumbnails + .row - @nearby_members.first(30).each do |member| - %li.col-md-4.three-across + .col-md-4.three-across = render :partial => "members/thumbnail", :locals => { :member => member } - elsif @place %p No results found From 98cbac8a70e933c4d55681e9ae84a521bc6a9597 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Mon, 14 Jul 2014 21:18:15 -0400 Subject: [PATCH 036/288] Remove an extra class on member thumbs --- app/views/members/_thumbnail.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/members/_thumbnail.html.haml b/app/views/members/_thumbnail.html.haml index 32c2e2242..bbb06f2f7 100644 --- a/app/views/members/_thumbnail.html.haml +++ b/app/views/members/_thumbnail.html.haml @@ -1,6 +1,6 @@ - cache "member_thumbnail_#{member.id}" do .row - .member-thumbnail.thumbnail + .member-thumbnail .col-md-3 = render :partial => "members/image_with_popover", :locals => { :member => member } .col-md-9 From 8da34308523b87ea60ab23202bcbf914c0c23b59 Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Mon, 14 Jul 2014 21:42:44 -0400 Subject: [PATCH 037/288] override some thumbnail styles --- app/assets/stylesheets/overrides.css.less | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/assets/stylesheets/overrides.css.less b/app/assets/stylesheets/overrides.css.less index 2005d9eed..0031ebecd 100644 --- a/app/assets/stylesheets/overrides.css.less +++ b/app/assets/stylesheets/overrides.css.less @@ -131,6 +131,17 @@ p.stats { } } +.member-thumbnail { + img { + height: 85px; + width: 85px; + max-width: 85px + } +} + +.thumbnail { + margin-bottom: 1.5em; +} li.crop-hierarchy { list-style-type: disc; From 863f1dfae734366c11aed0d2b4513acdd445d53b Mon Sep 17 00:00:00 2001 From: Amy Hendrix Date: Mon, 14 Jul 2014 22:19:07 -0400 Subject: [PATCH 038/288] Update planting form --- app/views/plantings/_form.html.haml | 44 ++++++++++++++--------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/app/views/plantings/_form.html.haml b/app/views/plantings/_form.html.haml index 6806a18fa..2decb9d68 100644 --- a/app/views/plantings/_form.html.haml +++ b/app/views/plantings/_form.html.haml @@ -1,4 +1,4 @@ -= form_for(@planting, :html => {:class => "form-horizontal"}) do |f| += form_for(@planting, :html => {:class => "form-horizontal", :role => "form"}) do |f| - if @planting.errors.any? #error_explanation %h2= "#{pluralize(@planting.errors.count, "error")} prohibited this planting from being saved:" @@ -7,36 +7,36 @@ %li= msg .form-group - = f.label 'What did you plant?', :class => 'control-label' - .controls - = collection_select(:planting, :crop_id, Crop.all, :id, :name, :selected => @planting.crop_id || @crop.id) + = f.label 'What did you plant?', :class => 'control-label col-md-2' + .col-md-4 + = collection_select(:planting, :crop_id, Crop.all, :id, :name, {:selected => @planting.crop_id || @crop.id}, {:class => 'form-control'}) %span.help-inline Can't find what you're looking for? = link_to "Request new crops.", Growstuff::Application.config.new_crops_request_link .form-group - = f.label 'Where did you plant it?', :class => 'control-label' - .controls - = collection_select(:planting, :garden_id, Garden.active.where(:owner_id => current_member), :id, :name, :selected => @planting.garden_id || @garden.id) + = f.label 'Where did you plant it?', :class => 'control-label col-md-2' + .col-md-4 + = collection_select(:planting, :garden_id, Garden.active.where(:owner_id => current_member), :id, :name, {:selected => @planting.garden_id || @garden.id}, {:class => 'form-control'}) %span.help-inline = link_to "Add a garden.", new_garden_path .form-group - = f.label 'When?', :class => 'control-label' - .controls= f.text_field :planted_at, :value => @planting.planted_at ? @planting.planted_at.to_s(:ymd) : '', :class => 'add-datepicker' + = f.label 'When?', :class => 'control-label col-md-2' + .col-md-3= f.text_field :planted_at, :value => @planting.planted_at ? @planting.planted_at.to_s(:ymd) : '', :class => 'add-datepicker form-control' .form-group - = f.label 'How many?', :class => 'control-label' - .controls - = f.number_field :quantity, :class => 'input-small' + = f.label 'How many?', :class => 'control-label col-md-2' + .col-md-2 + = f.number_field :quantity, :class => 'form-control' .form-group - = f.label 'Planted from:', :class => 'control-label' - .controls - = f.select(:planted_from, Planting::PLANTED_FROM_VALUES, {:include_blank => true}) + = f.label 'Planted from:', :class => 'control-label col-md-2' + .col-md-4 + = f.select(:planted_from, Planting::PLANTED_FROM_VALUES, {:include_blank => true}, :class => 'form-control') .form-group - = f.label 'Sun or shade?', :class => 'control-label' - .controls - = f.select(:sunniness, Planting::SUNNINESS_VALUES, {:include_blank => true}) + = f.label 'Sun or shade?', :class => 'control-label col-md-2' + .col-md-4 + = f.select(:sunniness, Planting::SUNNINESS_VALUES, {:include_blank => true}, :class => 'form-control') + .form-group + = f.label 'Tell us more about it', :class => 'control-label col-md-2' + .col-md-4= f.text_area :description, :rows => 6, :class => 'form-control' .form-group - = f.label 'Tell us more about it', :class => 'control-label' - .controls= f.text_area :description, :rows => 6 - .form-actions - = f.submit 'Save', :class => 'btn btn-primary' + = f.submit 'Save', :class => 'btn btn-primary col-md-offset-2' From a7d56d4f9261a0964d1d055e7945a4059da69976 Mon Sep 17 00:00:00 2001 From: Skud Date: Sun, 20 Jul 2014 17:27:17 +1000 Subject: [PATCH 039/288] Added stub code for sending regular (eg. weekly) emails --- Gemfile | 1 + Gemfile.lock | 9 +++++++++ app/controllers/members_controller.rb | 4 ++++ app/mailers/notifier.rb | 7 +++++++ app/models/ability.rb | 1 + app/views/members/send_email.html.haml | 2 ++ app/views/notifier/regular_email.html.haml | 22 ++++++++++++++++++++++ config/routes.rb | 2 +- spec/features/regular_email_spec.rb | 20 ++++++++++++++++++++ 9 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 app/views/members/send_email.html.haml create mode 100644 app/views/notifier/regular_email.html.haml create mode 100644 spec/features/regular_email_spec.rb diff --git a/Gemfile b/Gemfile index b4ff013a0..23dbbb978 100644 --- a/Gemfile +++ b/Gemfile @@ -124,4 +124,5 @@ group :development, :test do gem 'webrat' # provides HTML matchers for view tests gem 'factory_girl_rails', '~> 4.0' # for creating test data gem 'coveralls', require: false # coverage analysis + gem 'capybara' end diff --git a/Gemfile.lock b/Gemfile.lock index e2bd35b31..6095c5556 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -73,6 +73,12 @@ GEM railties (>= 3.0) builder (3.0.4) cancan (1.6.10) + capybara (2.3.0) + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) chunky_png (1.3.1) coderay (1.1.0) coffee-rails (3.2.2) @@ -277,6 +283,8 @@ GEM rack (>= 1.0) rack-test (>= 0.5.3) will_paginate (3.0.5) + xpath (2.0.0) + nokogiri (~> 1.3) PLATFORMS ruby @@ -290,6 +298,7 @@ DEPENDENCIES bootstrap-datepicker-rails bundler (>= 1.1.5) cancan + capybara coffee-rails (~> 3.2.1) compass-rails (~> 1.0.3) coveralls diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index ab3de0001..80c153ddc 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -32,4 +32,8 @@ class MembersController < ApplicationController end end + def send_email + Notifier.regular_email(current_member).deliver! + end + end diff --git a/app/mailers/notifier.rb b/app/mailers/notifier.rb index 579b2a8ad..7850d3623 100644 --- a/app/mailers/notifier.rb +++ b/app/mailers/notifier.rb @@ -9,4 +9,11 @@ class Notifier < ActionMailer::Base mail(:to => @notification.recipient.email, :subject => @notification.subject) end + + def regular_email(member) + @member = member + mail(:to => @member.email, + :subject => "This is your regular contact email") + end + end diff --git a/app/models/ability.rb b/app/models/ability.rb index e33f4a022..2c3764d9f 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -23,6 +23,7 @@ class Ability # managing your own user settings can :update, Member, :id => member.id + can :send_email, Member # can read/delete notifications that were sent to them can :read, Notification, :recipient_id => member.id diff --git a/app/views/members/send_email.html.haml b/app/views/members/send_email.html.haml new file mode 100644 index 000000000..e543ae1a2 --- /dev/null +++ b/app/views/members/send_email.html.haml @@ -0,0 +1,2 @@ +Sending email! + diff --git a/app/views/notifier/regular_email.html.haml b/app/views/notifier/regular_email.html.haml new file mode 100644 index 000000000..c32821a68 --- /dev/null +++ b/app/views/notifier/regular_email.html.haml @@ -0,0 +1,22 @@ +- site_name = ENV['GROWSTUFF_SITE_NAME'] +%p Hello #{@member.login_name}, + +%p + This is your regular weekly email. + +%p Stuff to include: + +%ul + %li Recent plantings (yours, people near you) + %li Recent harvests (yours, people near you) + %li Seeds available near you + %li New members near you + %li New discussion posts + +%p + = link_to "Turn off these notifications", edit_member_registration_url + +%p + The #{site_name} team. + %br/ + =link_to root_url, root_url diff --git a/config/routes.rb b/config/routes.rb index 9f066fa47..eb147a877 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,7 +2,7 @@ Growstuff::Application.routes.draw do resources :plant_parts - + get '/members/send_email' => 'members#send_email', :as => 'send_email' devise_for :members, :controllers => { :registrations => "registrations" } resources :members diff --git a/spec/features/regular_email_spec.rb b/spec/features/regular_email_spec.rb new file mode 100644 index 000000000..033c8b324 --- /dev/null +++ b/spec/features/regular_email_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +feature "regular email" do + before :each do + @member = FactoryGirl.create(:member) + visit root_path + click_link 'Sign in' + page.should have_content "Sign in" + fill_in 'Login', :with => @member.login_name + fill_in 'Password', :with => @member.password + click_button 'Sign in' + end + + scenario "sends email" do + expect { + # stub for while we're working on this. remove! + visit send_email_path + }.to change { ActionMailer::Base.deliveries.count }.by(1) + end +end From f4c2c2481d90d6c8db706e21bddff7b73b603d76 Mon Sep 17 00:00:00 2001 From: Skud Date: Sun, 20 Jul 2014 18:45:48 +1000 Subject: [PATCH 040/288] Include plantings/harvests in regular email --- app/mailers/notifier.rb | 4 + app/models/harvest.rb | 35 +++++++++ app/views/notifier/regular_email.html.haml | 38 ++++++++-- spec/models/harvest_spec.rb | 86 ++++++++++++++++++++++ 4 files changed, 155 insertions(+), 8 deletions(-) diff --git a/app/mailers/notifier.rb b/app/mailers/notifier.rb index 7850d3623..3cc2556b9 100644 --- a/app/mailers/notifier.rb +++ b/app/mailers/notifier.rb @@ -12,6 +12,10 @@ class Notifier < ActionMailer::Base def regular_email(member) @member = member + + @plantings = member.plantings.reorder.last(5) + @harvests = member.harvests.reorder.last(5) + mail(:to => @member.email, :subject => "This is your regular contact email") end diff --git a/app/models/harvest.rb b/app/models/harvest.rb index 3afaeb170..c58a53c30 100644 --- a/app/models/harvest.rb +++ b/app/models/harvest.rb @@ -1,4 +1,5 @@ class Harvest < ActiveRecord::Base + include ActionView::Helpers::NumberHelper extend FriendlyId friendly_id :harvest_slug, use: :slugged @@ -69,4 +70,38 @@ class Harvest < ActiveRecord::Base "#{owner.login_name}-#{crop}".downcase.gsub(' ', '-') end + # stringify as "beet in Skud's backyard" or similar + def to_s + # 50 individual apples, weighing 3lb + # 2 buckets of apricots, weighing 10kg + string = '' + if self.quantity + string += "#{number_to_human(self.quantity.to_s, :strip_insignificant_zeros => true)} " + if self.unit == 'individual' + string += 'individual ' + else + if self.quantity == 1 + string += "#{self.unit} of " + else + string += "#{self.unit.pluralize} of " + end + end + end + + if self.unit != 'individual' # buckets of apricot*s* + string += "#{self.crop.name.pluralize}" + elsif self.quantity == 1 + string += "#{self.crop.name}" + else + string += "#{self.crop.name.pluralize}" + end + + if self.weight_quantity + string += " weighing #{number_to_human(self.weight_quantity, :strip_insignificant_zeros => true)} #{self.weight_unit}" + end + + return string + + end + end diff --git a/app/views/notifier/regular_email.html.haml b/app/views/notifier/regular_email.html.haml index c32821a68..1e77fc195 100644 --- a/app/views/notifier/regular_email.html.haml +++ b/app/views/notifier/regular_email.html.haml @@ -1,19 +1,41 @@ - site_name = ENV['GROWSTUFF_SITE_NAME'] %p Hello #{@member.login_name}, -%p - This is your regular weekly email. +%h2 What's new in your garden? -%p Stuff to include: +Have you planted anything recently? The most recent plantings you've +told us about are: %ul - %li Recent plantings (yours, people near you) - %li Recent harvests (yours, people near you) - %li Seeds available near you - %li New members near you - %li New discussion posts +- @plantings.each do |p| + %li + = link_to p, planting_url(p) + planted + = distance_of_time_in_words(p.created_at, Time.zone.now) + ago. %p + Planted anything new? + = link_to "Track your plantings here.", new_planting_url + +%h2 Your recent harvests + +According to our records, the last few things you harvested were: + +%ul +- @harvests.each do |h| + %li + = link_to h, harvest_url(h) + harvested + = distance_of_time_in_words(h.created_at, Time.zone.now) + ago. + +%p + Harvested anything else lately? + = link_to "Track your harvests here.", new_harvest_url + +%p + Don't want to get these emails any more? = link_to "Turn off these notifications", edit_member_registration_url %p diff --git a/spec/models/harvest_spec.rb b/spec/models/harvest_spec.rb index a7da223aa..3555e390d 100644 --- a/spec/models/harvest_spec.rb +++ b/spec/models/harvest_spec.rb @@ -125,4 +125,90 @@ describe Harvest do Harvest.all.should eq [@h2, @h1] end end + + context "stringification" do + before :each do + @crop = FactoryGirl.create(:crop, :name => "apricot") + end + + it "apricots" do + @h = FactoryGirl.create(:harvest, :crop => @crop, + :quantity => nil, + :unit => nil, + :weight_quantity => nil, + :weight_unit => nil + ) + @h.to_s.should eq "apricots" + end + + it "1 individual apricot" do + @h = FactoryGirl.create(:harvest, :crop => @crop, + :quantity => 1, + :unit => 'individual', + :weight_quantity => nil, + :weight_unit => nil + ) + @h.to_s.should eq "1 individual apricot" + end + + it "10 individual apricots" do + @h = FactoryGirl.create(:harvest, :crop => @crop, + :quantity => 10, + :unit => 'individual', + :weight_quantity => nil, + :weight_unit => nil + ) + @h.to_s.should eq "10 individual apricots" + end + + it "1 bushel of apricots" do + @h = FactoryGirl.create(:harvest, :crop => @crop, + :quantity => 1, + :unit => 'bushel', + :weight_quantity => nil, + :weight_unit => nil + ) + @h.to_s.should eq "1 bushel of apricots" + end + + it "1.5 bushels of apricots" do + @h = FactoryGirl.create(:harvest, :crop => @crop, + :quantity => 1.5, + :unit => 'bushel', + :weight_quantity => nil, + :weight_unit => nil + ) + @h.to_s.should eq "1.5 bushels of apricots" + end + + it "10 bushels of apricots" do + @h = FactoryGirl.create(:harvest, :crop => @crop, + :quantity => 10, + :unit => 'bushel', + :weight_quantity => nil, + :weight_unit => nil + ) + @h.to_s.should eq "10 bushels of apricots" + end + + it "apricots weighing 1.2 kg" do + @h = FactoryGirl.create(:harvest, :crop => @crop, + :quantity => nil, + :unit => nil, + :weight_quantity => 1.2, + :weight_unit => 'kg' + ) + @h.to_s.should eq "apricots weighing 1.2 kg" + end + + it "10 bushels of apricots weighing 100 kg" do + @h = FactoryGirl.create(:harvest, :crop => @crop, + :quantity => 10, + :unit => 'bushel', + :weight_quantity => 100, + :weight_unit => 'kg') + @h.to_s.should eq "10 bushels of apricots weighing 100 kg" + end + + end end From 7c09188bb8066b4831ce253bc718c36417b9197d Mon Sep 17 00:00:00 2001 From: Skud Date: Sun, 20 Jul 2014 18:54:58 +1000 Subject: [PATCH 041/288] Renamed email to planting_reminder --- app/controllers/members_controller.rb | 4 ++-- app/mailers/notifier.rb | 4 ++-- app/models/ability.rb | 2 +- app/views/members/send_email.html.haml | 2 -- app/views/members/send_planting_reminder.html.haml | 8 ++++++++ ...egular_email.html.haml => planting_reminder.html.haml} | 0 config/routes.rb | 2 +- .../{regular_email_spec.rb => planting_reminder_spec.rb} | 4 ++-- 8 files changed, 16 insertions(+), 10 deletions(-) delete mode 100644 app/views/members/send_email.html.haml create mode 100644 app/views/members/send_planting_reminder.html.haml rename app/views/notifier/{regular_email.html.haml => planting_reminder.html.haml} (100%) rename spec/features/{regular_email_spec.rb => planting_reminder_spec.rb} (86%) diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index 80c153ddc..f1e00a37a 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -32,8 +32,8 @@ class MembersController < ApplicationController end end - def send_email - Notifier.regular_email(current_member).deliver! + def send_planting_reminder + Notifier.planting_reminder(current_member).deliver! end end diff --git a/app/mailers/notifier.rb b/app/mailers/notifier.rb index 3cc2556b9..637c3b2fe 100644 --- a/app/mailers/notifier.rb +++ b/app/mailers/notifier.rb @@ -10,14 +10,14 @@ class Notifier < ActionMailer::Base :subject => @notification.subject) end - def regular_email(member) + def planting_reminder(member) @member = member @plantings = member.plantings.reorder.last(5) @harvests = member.harvests.reorder.last(5) mail(:to => @member.email, - :subject => "This is your regular contact email") + :subject => "What have you planted lately?") end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 2c3764d9f..8e4e30fe6 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -23,7 +23,7 @@ class Ability # managing your own user settings can :update, Member, :id => member.id - can :send_email, Member + can :send_planting_reminder, Member # can read/delete notifications that were sent to them can :read, Notification, :recipient_id => member.id diff --git a/app/views/members/send_email.html.haml b/app/views/members/send_email.html.haml deleted file mode 100644 index e543ae1a2..000000000 --- a/app/views/members/send_email.html.haml +++ /dev/null @@ -1,2 +0,0 @@ -Sending email! - diff --git a/app/views/members/send_planting_reminder.html.haml b/app/views/members/send_planting_reminder.html.haml new file mode 100644 index 000000000..e83f4c36d --- /dev/null +++ b/app/views/members/send_planting_reminder.html.haml @@ -0,0 +1,8 @@ +- content_for :title, "Sent planting reminder" + +%p + We just sent you a planting reminder email. + +%p + = link_to "Back to your profile", edit_member_registration_path + diff --git a/app/views/notifier/regular_email.html.haml b/app/views/notifier/planting_reminder.html.haml similarity index 100% rename from app/views/notifier/regular_email.html.haml rename to app/views/notifier/planting_reminder.html.haml diff --git a/config/routes.rb b/config/routes.rb index eb147a877..09de6fe73 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,7 +2,7 @@ Growstuff::Application.routes.draw do resources :plant_parts - get '/members/send_email' => 'members#send_email', :as => 'send_email' + get '/members/send_planting_reminder' => 'members#send_planting_reminder', :as => 'send_planting_reminder' devise_for :members, :controllers => { :registrations => "registrations" } resources :members diff --git a/spec/features/regular_email_spec.rb b/spec/features/planting_reminder_spec.rb similarity index 86% rename from spec/features/regular_email_spec.rb rename to spec/features/planting_reminder_spec.rb index 033c8b324..763331442 100644 --- a/spec/features/regular_email_spec.rb +++ b/spec/features/planting_reminder_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature "regular email" do +feature "planting reminder" do before :each do @member = FactoryGirl.create(:member) visit root_path @@ -14,7 +14,7 @@ feature "regular email" do scenario "sends email" do expect { # stub for while we're working on this. remove! - visit send_email_path + visit send_planting_reminder_path }.to change { ActionMailer::Base.deliveries.count }.by(1) end end From 0294daf4238c5a853095875dfadec8b9d59250b8 Mon Sep 17 00:00:00 2001 From: Skud Date: Tue, 22 Jul 2014 12:01:49 +1000 Subject: [PATCH 042/288] s/btn-mini/btn-xs/ for bootstrap3 --- app/views/admin/orders/search.html.haml | 2 +- app/views/comments/_single.html.haml | 4 ++-- app/views/crops/show.html.haml | 10 +++++----- app/views/forums/show.html.haml | 2 +- app/views/gardens/index.html.haml | 2 +- app/views/gardens/show.html.haml | 6 +++--- app/views/harvests/index.html.haml | 2 +- app/views/harvests/show.html.haml | 4 ++-- app/views/home/_seeds.html.haml | 2 +- app/views/members/show.html.haml | 2 +- app/views/notifications/index.html.haml | 2 +- app/views/orders/index.html.haml | 2 +- app/views/photos/show.html.haml | 2 +- app/views/plant_parts/index.html.haml | 4 ++-- app/views/plant_parts/show.html.haml | 4 ++-- app/views/plantings/_thumbnail.html.haml | 4 ++-- app/views/plantings/index.html.haml | 2 +- app/views/plantings/show.html.haml | 4 ++-- app/views/posts/show.html.haml | 4 ++-- app/views/roles/index.html.haml | 4 ++-- app/views/roles/show.html.haml | 2 +- app/views/scientific_names/index.html.haml | 4 ++-- app/views/scientific_names/show.html.haml | 2 +- app/views/seeds/index.html.haml | 2 +- app/views/seeds/show.html.haml | 6 +++--- 25 files changed, 42 insertions(+), 42 deletions(-) diff --git a/app/views/admin/orders/search.html.haml b/app/views/admin/orders/search.html.haml index ab2516317..88824611a 100644 --- a/app/views/admin/orders/search.html.haml +++ b/app/views/admin/orders/search.html.haml @@ -36,4 +36,4 @@ @ = price_with_currency(o.price) %br/ - %td= link_to 'Details', order, :class => 'btn btn-mini' + %td= link_to 'Details', order, :class => 'btn btn-xs' diff --git a/app/views/comments/_single.html.haml b/app/views/comments/_single.html.haml index d2131b355..89ac4c37e 100644 --- a/app/views/comments/_single.html.haml +++ b/app/views/comments/_single.html.haml @@ -17,8 +17,8 @@ - if can? :edit, comment or can? :destroy, comment .comment-actions - if can? :edit, comment - = link_to 'Edit', edit_comment_path(comment), :class => 'btn btn-mini' + = link_to 'Edit', edit_comment_path(comment), :class => 'btn btn-xs' - if can? :destroy, comment = link_to 'Delete', comment, method: :delete, | - data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini' + data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' diff --git a/app/views/crops/show.html.haml b/app/views/crops/show.html.haml index 8a5e0f9ed..4c72e178c 100644 --- a/app/views/crops/show.html.haml +++ b/app/views/crops/show.html.haml @@ -49,9 +49,9 @@ %strong CROP WRANGLER %p - if can? :edit, @crop - = link_to 'Edit crop', edit_crop_path(@crop), { :class => 'btn btn-mini' } + = link_to 'Edit crop', edit_crop_path(@crop), { :class => 'btn btn-xs' } - if can? :destroy, @crop - = link_to 'Delete crop', @crop, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini' + = link_to 'Delete crop', @crop, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' %h4 Scientific names %ul @@ -59,12 +59,12 @@ %li = sn.scientific_name - if can? :edit, sn - = link_to 'Edit', edit_scientific_name_path(sn), { :class => 'btn btn-mini' } + = link_to 'Edit', edit_scientific_name_path(sn), { :class => 'btn btn-xs' } - if can? :destroy, sn - = link_to 'Delete', sn, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini' + = link_to 'Delete', sn, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' %p - if can? :edit, @crop - = link_to 'Add', new_scientific_name_path( :crop_id => @crop.id ), { :class => 'btn btn-mini' } + = link_to 'Add', new_scientific_name_path( :crop_id => @crop.id ), { :class => 'btn btn-xs' } = render :partial => 'varieties', :locals => { :crop => @crop } diff --git a/app/views/forums/show.html.haml b/app/views/forums/show.html.haml index 069ca4a17..95239b2a7 100644 --- a/app/views/forums/show.html.haml +++ b/app/views/forums/show.html.haml @@ -11,7 +11,7 @@ #{ strip_tags(@forum.description) } - if can? :edit, @forum - =link_to "Edit", edit_forum_path(@forum), :class => 'btn btn-mini' + =link_to "Edit", edit_forum_path(@forum), :class => 'btn btn-xs' %h2 Posts diff --git a/app/views/gardens/index.html.haml b/app/views/gardens/index.html.haml index 689ff9c02..fa2653e7a 100644 --- a/app/views/gardens/index.html.haml +++ b/app/views/gardens/index.html.haml @@ -58,7 +58,7 @@ planted on = p.planted_at - %td= link_to 'Details', garden, :class => 'btn btn-mini' + %td= link_to 'Details', garden, :class => 'btn btn-xs' %div.pagination = page_entries_info @gardens, :model => "gardens" diff --git a/app/views/gardens/show.html.haml b/app/views/gardens/show.html.haml index bfd696330..5640da8e8 100644 --- a/app/views/gardens/show.html.haml +++ b/app/views/gardens/show.html.haml @@ -24,10 +24,10 @@ :growstuff_markdown #{strip_tags @garden.description} - if can? :edit, @garden - = link_to 'Edit garden', edit_garden_path(@garden), :class => 'btn btn-mini' + = link_to 'Edit garden', edit_garden_path(@garden), :class => 'btn btn-xs' - if can? :destroy, @garden = link_to 'Delete garden', @garden, method: :delete, | - data: { confirm: 'All plantings associated with this garden will also be deleted. Are you sure?' }, :class => 'btn btn-mini' + data: { confirm: 'All plantings associated with this garden will also be deleted. Are you sure?' }, :class => 'btn btn-xs' %h3 What's planted here? @@ -57,4 +57,4 @@ = link_to "#{othergarden}", garden_path(othergarden) - if can? :create, @garden - = link_to 'Add New Garden', new_garden_path, :class => 'btn btn-mini' + = link_to 'Add New Garden', new_garden_path, :class => 'btn btn-xs' diff --git a/app/views/harvests/index.html.haml b/app/views/harvests/index.html.haml index ea08b0da8..cd2e9f3f5 100644 --- a/app/views/harvests/index.html.haml +++ b/app/views/harvests/index.html.haml @@ -46,7 +46,7 @@ %td= harvest.harvested_at %td= display_quantity(harvest) %td= harvest.description - %td= link_to 'Details', harvest, :class => 'btn btn-mini' + %td= link_to 'Details', harvest, :class => 'btn btn-xs' %div.pagination = page_entries_info @harvests, :model => "harvests" diff --git a/app/views/harvests/show.html.haml b/app/views/harvests/show.html.haml index a4ee0d3c9..53e10e071 100644 --- a/app/views/harvests/show.html.haml +++ b/app/views/harvests/show.html.haml @@ -23,9 +23,9 @@ - if can? :edit, @harvest or can? :destroy, @harvest %p - if can? :edit, @harvest - =link_to 'Edit', edit_harvest_path(@harvest), :class => 'btn btn-mini' + =link_to 'Edit', edit_harvest_path(@harvest), :class => 'btn btn-xs' - if can? :destroy, @harvest - =link_to 'Delete', @harvest, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini' + =link_to 'Delete', @harvest, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' .col-md-6 = render :partial => "crops/index_card", :locals => { :crop => @harvest.crop} diff --git a/app/views/home/_seeds.html.haml b/app/views/home/_seeds.html.haml index b37fe3e1d..e813040fa 100644 --- a/app/views/home/_seeds.html.haml +++ b/app/views/home/_seeds.html.haml @@ -24,7 +24,7 @@ %td - if seed.tradable? = seed.owner.location.blank? ? "unspecified" : truncate(seed.owner.location, :length => 25, :separator => ', ') - %td= link_to 'Details', seed, :class => 'btn btn-mini' + %td= link_to 'Details', seed, :class => 'btn btn-xs' %p.text-right = link_to "View all seeds »", seeds_path diff --git a/app/views/members/show.html.haml b/app/views/members/show.html.haml index b4e78ff58..acbcb8c34 100644 --- a/app/views/members/show.html.haml +++ b/app/views/members/show.html.haml @@ -21,7 +21,7 @@ account - if @member == current_member && !@member.is_paid? - = link_to "Upgrade", shop_path, :class => 'btn btn-primary btn-mini' + = link_to "Upgrade", shop_path, :class => 'btn btn-primary btn-xs' - if @twitter_auth || @flickr_auth || @member.show_email %h4 Contact diff --git a/app/views/notifications/index.html.haml b/app/views/notifications/index.html.haml index ace28ca92..e0d71b39f 100644 --- a/app/views/notifications/index.html.haml +++ b/app/views/notifications/index.html.haml @@ -27,6 +27,6 @@ - else %strong= n.created_at %td - = link_to 'Delete', n, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini' + = link_to 'Delete', n, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' - else You have no messages. diff --git a/app/views/orders/index.html.haml b/app/views/orders/index.html.haml index d20214bf4..5514e5a9a 100644 --- a/app/views/orders/index.html.haml +++ b/app/views/orders/index.html.haml @@ -36,7 +36,7 @@ @ = price_with_currency(o.price) %br/ - %td= link_to 'Details', order, :class => 'btn btn-mini' + %td= link_to 'Details', order, :class => 'btn btn-xs' - else %p You have not made any orders. You can place an order via our diff --git a/app/views/photos/show.html.haml b/app/views/photos/show.html.haml index 1e0f8dd95..1ef519c68 100644 --- a/app/views/photos/show.html.haml +++ b/app/views/photos/show.html.haml @@ -16,7 +16,7 @@ = link_to "View on Flickr", @photo.link_url - if can? :destroy, @photo - %p= link_to 'Delete Photo', @photo, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini' + %p= link_to 'Delete Photo', @photo, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' .col-md-6 - if @photo.plantings.count > 0 diff --git a/app/views/plant_parts/index.html.haml b/app/views/plant_parts/index.html.haml index d36f7142e..dc038aebc 100644 --- a/app/views/plant_parts/index.html.haml +++ b/app/views/plant_parts/index.html.haml @@ -15,9 +15,9 @@ %p - if can? :edit, plant_part - = link_to 'Edit', edit_plant_part_path(plant_part), :class => 'btn btn-mini' + = link_to 'Edit', edit_plant_part_path(plant_part), :class => 'btn btn-xs' - if can? :destroy, plant_part - = link_to 'Delete', plant_part, :method => :delete, :data => { :confirm => 'Are you sure?' }, :class => 'btn btn-mini' + = link_to 'Delete', plant_part, :method => :delete, :data => { :confirm => 'Are you sure?' }, :class => 'btn btn-xs' diff --git a/app/views/plant_parts/show.html.haml b/app/views/plant_parts/show.html.haml index 579307eb0..d0cd4088c 100644 --- a/app/views/plant_parts/show.html.haml +++ b/app/views/plant_parts/show.html.haml @@ -12,6 +12,6 @@ %p - if can? :edit, @plant_part - = link_to 'Edit', edit_plant_part_path(@plant_part), :class => 'btn btn-mini' + = link_to 'Edit', edit_plant_part_path(@plant_part), :class => 'btn btn-xs' - if can? :destroy, @plant_part - = link_to 'Delete', @plant_part, :method => :delete, :data => { :confirm => 'Are you sure?' }, :class => 'btn btn-mini' + = link_to 'Delete', @plant_part, :method => :delete, :data => { :confirm => 'Are you sure?' }, :class => 'btn btn-xs' diff --git a/app/views/plantings/_thumbnail.html.haml b/app/views/plantings/_thumbnail.html.haml index bc65def88..f9a5294ce 100644 --- a/app/views/plantings/_thumbnail.html.haml +++ b/app/views/plantings/_thumbnail.html.haml @@ -32,6 +32,6 @@ - if can? :edit, planting or can? :destroy, planting %p - if can? :edit, planting - =link_to 'Edit', edit_planting_path(planting), :class => 'btn btn-mini' + =link_to 'Edit', edit_planting_path(planting), :class => 'btn btn-xs' - if can? :destroy, planting - =link_to 'Delete', planting, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini' + =link_to 'Delete', planting, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' diff --git a/app/views/plantings/index.html.haml b/app/views/plantings/index.html.haml index 647810ea7..aaee4acd0 100644 --- a/app/views/plantings/index.html.haml +++ b/app/views/plantings/index.html.haml @@ -46,7 +46,7 @@ %td= planting.planted_at %td= planting.sunniness %td= planting.planted_from - %td= link_to 'Details', planting, :class => 'btn btn-mini' + %td= link_to 'Details', planting, :class => 'btn btn-xs' %div.pagination = page_entries_info @plantings, :model => "plantings" diff --git a/app/views/plantings/show.html.haml b/app/views/plantings/show.html.haml index 6d8f09b84..536cdb8d9 100644 --- a/app/views/plantings/show.html.haml +++ b/app/views/plantings/show.html.haml @@ -34,9 +34,9 @@ - if can? :edit, @planting or can? :destroy, @planting %p - if can? :edit, @planting - =link_to 'Edit', edit_planting_path(@planting), :class => 'btn btn-mini' + =link_to 'Edit', edit_planting_path(@planting), :class => 'btn btn-xs' - if can? :destroy, @planting - =link_to 'Delete', @planting, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini' + =link_to 'Delete', @planting, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' .col-md-6 = render :partial => "crops/index_card", :locals => { :crop => @planting.crop} diff --git a/app/views/posts/show.html.haml b/app/views/posts/show.html.haml index 607e9f1f7..7558e7285 100644 --- a/app/views/posts/show.html.haml +++ b/app/views/posts/show.html.haml @@ -5,10 +5,10 @@ - if can? :edit, @post or can? :destroy, @post .post-actions - if can? :edit, @post - = link_to 'Edit Post', edit_post_path(@post), :class => 'btn btn-mini' + = link_to 'Edit Post', edit_post_path(@post), :class => 'btn btn-xs' - if can? :destroy, @post = link_to 'Delete Post', @post, method: :delete, | - data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini' + data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' = render :partial => "comments", :locals => { :post => @post } diff --git a/app/views/roles/index.html.haml b/app/views/roles/index.html.haml index 1583f3ab2..e02c56f80 100644 --- a/app/views/roles/index.html.haml +++ b/app/views/roles/index.html.haml @@ -16,6 +16,6 @@ %td= link_to role.name, role %td= role.description - if can? :edit, role - %td= link_to 'Edit', edit_role_path(role), :class => 'btn btn-mini' + %td= link_to 'Edit', edit_role_path(role), :class => 'btn btn-xs' - if can? :destroy, role - %td= link_to 'Delete', role, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini' + %td= link_to 'Delete', role, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' diff --git a/app/views/roles/show.html.haml b/app/views/roles/show.html.haml index fc8edb76c..f27c5e4ae 100644 --- a/app/views/roles/show.html.haml +++ b/app/views/roles/show.html.haml @@ -7,6 +7,6 @@ %b Description: = @role.description -= link_to 'Edit', edit_role_path(@role), :class => 'btn btn-mini' += link_to 'Edit', edit_role_path(@role), :class => 'btn btn-xs' \| = link_to 'Back', roles_path diff --git a/app/views/scientific_names/index.html.haml b/app/views/scientific_names/index.html.haml index df688fe7e..2cd03b735 100644 --- a/app/views/scientific_names/index.html.haml +++ b/app/views/scientific_names/index.html.haml @@ -17,7 +17,7 @@ %td= link_to 'Show', scientific_name %td - if can? :edit, scientific_name - = link_to 'Edit', edit_scientific_name_path(scientific_name), :class => 'btn btn-mini' + = link_to 'Edit', edit_scientific_name_path(scientific_name), :class => 'btn btn-xs' %td - if can? :destroy, scientific_name - = link_to 'Delete', scientific_name, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini' + = link_to 'Delete', scientific_name, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' diff --git a/app/views/scientific_names/show.html.haml b/app/views/scientific_names/show.html.haml index d4ccc5575..0772ae9cb 100644 --- a/app/views/scientific_names/show.html.haml +++ b/app/views/scientific_names/show.html.haml @@ -7,6 +7,6 @@ %b Crop: = @scientific_name.crop_id -= link_to 'Edit', edit_scientific_name_path(@scientific_name), :class => 'btn btn-mini' += link_to 'Edit', edit_scientific_name_path(@scientific_name), :class => 'btn btn-xs' \| = link_to 'Back', scientific_names_path diff --git a/app/views/seeds/index.html.haml b/app/views/seeds/index.html.haml index 3fff7aded..6ea29227d 100644 --- a/app/views/seeds/index.html.haml +++ b/app/views/seeds/index.html.haml @@ -51,7 +51,7 @@ unspecified - else = link_to seed.owner.location, place_path(seed.owner.location) - %td= link_to 'Details', seed, :class => 'btn btn-mini' + %td= link_to 'Details', seed, :class => 'btn btn-xs' %div.pagination = page_entries_info @seeds, :model => "seeds" diff --git a/app/views/seeds/show.html.haml b/app/views/seeds/show.html.haml index 1b07e7f10..9364c9a7c 100644 --- a/app/views/seeds/show.html.haml +++ b/app/views/seeds/show.html.haml @@ -19,7 +19,7 @@ - if @seed.owner.location.blank? (from unspecified location) - if current_member == @seed.owner - = link_to "Set Location", edit_registration_path(current_member), :class => 'btn btn-mini' + = link_to "Set Location", edit_registration_path(current_member), :class => 'btn btn-xs' - else (from = succeed ")" do @@ -39,9 +39,9 @@ - if can? :edit, @seed or can? :destroy, @seed %p - if can? :edit, @seed - =link_to 'Edit', edit_seed_path(@seed), :class => 'btn btn-mini' + =link_to 'Edit', edit_seed_path(@seed), :class => 'btn btn-xs' - if can? :destroy, @seed - =link_to 'Delete', @seed, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini' + =link_to 'Delete', @seed, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' .col-md-6 = render :partial => "crops/index_card", :locals => { :crop => @seed.crop } From 117fd23beae1e19331076aecd7d98255ecf47069 Mon Sep 17 00:00:00 2001 From: Skud Date: Tue, 22 Jul 2014 12:06:28 +1000 Subject: [PATCH 043/288] add btn-default to all btn-xs --- app/views/admin/orders/search.html.haml | 2 +- app/views/comments/_single.html.haml | 4 ++-- app/views/crops/show.html.haml | 10 +++++----- app/views/forums/show.html.haml | 2 +- app/views/gardens/index.html.haml | 2 +- app/views/gardens/show.html.haml | 6 +++--- app/views/harvests/index.html.haml | 2 +- app/views/harvests/show.html.haml | 4 ++-- app/views/home/_seeds.html.haml | 2 +- app/views/notifications/index.html.haml | 2 +- app/views/orders/index.html.haml | 2 +- app/views/photos/show.html.haml | 2 +- app/views/plant_parts/index.html.haml | 4 ++-- app/views/plant_parts/show.html.haml | 4 ++-- app/views/plantings/_thumbnail.html.haml | 4 ++-- app/views/plantings/index.html.haml | 2 +- app/views/plantings/show.html.haml | 4 ++-- app/views/posts/show.html.haml | 4 ++-- app/views/roles/index.html.haml | 4 ++-- app/views/roles/show.html.haml | 2 +- app/views/scientific_names/index.html.haml | 4 ++-- app/views/scientific_names/show.html.haml | 2 +- app/views/seeds/index.html.haml | 2 +- app/views/seeds/show.html.haml | 6 +++--- 24 files changed, 41 insertions(+), 41 deletions(-) diff --git a/app/views/admin/orders/search.html.haml b/app/views/admin/orders/search.html.haml index 88824611a..022e97d87 100644 --- a/app/views/admin/orders/search.html.haml +++ b/app/views/admin/orders/search.html.haml @@ -36,4 +36,4 @@ @ = price_with_currency(o.price) %br/ - %td= link_to 'Details', order, :class => 'btn btn-xs' + %td= link_to 'Details', order, :class => 'btn btn-default btn-xs' diff --git a/app/views/comments/_single.html.haml b/app/views/comments/_single.html.haml index 89ac4c37e..ae9c503af 100644 --- a/app/views/comments/_single.html.haml +++ b/app/views/comments/_single.html.haml @@ -17,8 +17,8 @@ - if can? :edit, comment or can? :destroy, comment .comment-actions - if can? :edit, comment - = link_to 'Edit', edit_comment_path(comment), :class => 'btn btn-xs' + = link_to 'Edit', edit_comment_path(comment), :class => 'btn btn-default btn-xs' - if can? :destroy, comment = link_to 'Delete', comment, method: :delete, | - data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' + data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' diff --git a/app/views/crops/show.html.haml b/app/views/crops/show.html.haml index 4c72e178c..b79320698 100644 --- a/app/views/crops/show.html.haml +++ b/app/views/crops/show.html.haml @@ -49,9 +49,9 @@ %strong CROP WRANGLER %p - if can? :edit, @crop - = link_to 'Edit crop', edit_crop_path(@crop), { :class => 'btn btn-xs' } + = link_to 'Edit crop', edit_crop_path(@crop), { :class => 'btn btn-default btn-xs' } - if can? :destroy, @crop - = link_to 'Delete crop', @crop, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' + = link_to 'Delete crop', @crop, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' %h4 Scientific names %ul @@ -59,12 +59,12 @@ %li = sn.scientific_name - if can? :edit, sn - = link_to 'Edit', edit_scientific_name_path(sn), { :class => 'btn btn-xs' } + = link_to 'Edit', edit_scientific_name_path(sn), { :class => 'btn btn-default btn-xs' } - if can? :destroy, sn - = link_to 'Delete', sn, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' + = link_to 'Delete', sn, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' %p - if can? :edit, @crop - = link_to 'Add', new_scientific_name_path( :crop_id => @crop.id ), { :class => 'btn btn-xs' } + = link_to 'Add', new_scientific_name_path( :crop_id => @crop.id ), { :class => 'btn btn-default btn-xs' } = render :partial => 'varieties', :locals => { :crop => @crop } diff --git a/app/views/forums/show.html.haml b/app/views/forums/show.html.haml index 95239b2a7..617981814 100644 --- a/app/views/forums/show.html.haml +++ b/app/views/forums/show.html.haml @@ -11,7 +11,7 @@ #{ strip_tags(@forum.description) } - if can? :edit, @forum - =link_to "Edit", edit_forum_path(@forum), :class => 'btn btn-xs' + =link_to "Edit", edit_forum_path(@forum), :class => 'btn btn-default btn-xs' %h2 Posts diff --git a/app/views/gardens/index.html.haml b/app/views/gardens/index.html.haml index fa2653e7a..65f80c3af 100644 --- a/app/views/gardens/index.html.haml +++ b/app/views/gardens/index.html.haml @@ -58,7 +58,7 @@ planted on = p.planted_at - %td= link_to 'Details', garden, :class => 'btn btn-xs' + %td= link_to 'Details', garden, :class => 'btn btn-default btn-xs' %div.pagination = page_entries_info @gardens, :model => "gardens" diff --git a/app/views/gardens/show.html.haml b/app/views/gardens/show.html.haml index 5640da8e8..10fc78958 100644 --- a/app/views/gardens/show.html.haml +++ b/app/views/gardens/show.html.haml @@ -24,10 +24,10 @@ :growstuff_markdown #{strip_tags @garden.description} - if can? :edit, @garden - = link_to 'Edit garden', edit_garden_path(@garden), :class => 'btn btn-xs' + = link_to 'Edit garden', edit_garden_path(@garden), :class => 'btn btn-default btn-xs' - if can? :destroy, @garden = link_to 'Delete garden', @garden, method: :delete, | - data: { confirm: 'All plantings associated with this garden will also be deleted. Are you sure?' }, :class => 'btn btn-xs' + data: { confirm: 'All plantings associated with this garden will also be deleted. Are you sure?' }, :class => 'btn btn-default btn-xs' %h3 What's planted here? @@ -57,4 +57,4 @@ = link_to "#{othergarden}", garden_path(othergarden) - if can? :create, @garden - = link_to 'Add New Garden', new_garden_path, :class => 'btn btn-xs' + = link_to 'Add New Garden', new_garden_path, :class => 'btn btn-default btn-xs' diff --git a/app/views/harvests/index.html.haml b/app/views/harvests/index.html.haml index cd2e9f3f5..90bbb4638 100644 --- a/app/views/harvests/index.html.haml +++ b/app/views/harvests/index.html.haml @@ -46,7 +46,7 @@ %td= harvest.harvested_at %td= display_quantity(harvest) %td= harvest.description - %td= link_to 'Details', harvest, :class => 'btn btn-xs' + %td= link_to 'Details', harvest, :class => 'btn btn-default btn-xs' %div.pagination = page_entries_info @harvests, :model => "harvests" diff --git a/app/views/harvests/show.html.haml b/app/views/harvests/show.html.haml index 53e10e071..95897bd42 100644 --- a/app/views/harvests/show.html.haml +++ b/app/views/harvests/show.html.haml @@ -23,9 +23,9 @@ - if can? :edit, @harvest or can? :destroy, @harvest %p - if can? :edit, @harvest - =link_to 'Edit', edit_harvest_path(@harvest), :class => 'btn btn-xs' + =link_to 'Edit', edit_harvest_path(@harvest), :class => 'btn btn-default btn-xs' - if can? :destroy, @harvest - =link_to 'Delete', @harvest, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' + =link_to 'Delete', @harvest, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' .col-md-6 = render :partial => "crops/index_card", :locals => { :crop => @harvest.crop} diff --git a/app/views/home/_seeds.html.haml b/app/views/home/_seeds.html.haml index e813040fa..ccacd0bf4 100644 --- a/app/views/home/_seeds.html.haml +++ b/app/views/home/_seeds.html.haml @@ -24,7 +24,7 @@ %td - if seed.tradable? = seed.owner.location.blank? ? "unspecified" : truncate(seed.owner.location, :length => 25, :separator => ', ') - %td= link_to 'Details', seed, :class => 'btn btn-xs' + %td= link_to 'Details', seed, :class => 'btn btn-default btn-xs' %p.text-right = link_to "View all seeds »", seeds_path diff --git a/app/views/notifications/index.html.haml b/app/views/notifications/index.html.haml index e0d71b39f..1c7ec4489 100644 --- a/app/views/notifications/index.html.haml +++ b/app/views/notifications/index.html.haml @@ -27,6 +27,6 @@ - else %strong= n.created_at %td - = link_to 'Delete', n, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' + = link_to 'Delete', n, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' - else You have no messages. diff --git a/app/views/orders/index.html.haml b/app/views/orders/index.html.haml index 5514e5a9a..e48b38c3c 100644 --- a/app/views/orders/index.html.haml +++ b/app/views/orders/index.html.haml @@ -36,7 +36,7 @@ @ = price_with_currency(o.price) %br/ - %td= link_to 'Details', order, :class => 'btn btn-xs' + %td= link_to 'Details', order, :class => 'btn btn-default btn-xs' - else %p You have not made any orders. You can place an order via our diff --git a/app/views/photos/show.html.haml b/app/views/photos/show.html.haml index 1ef519c68..4d510df15 100644 --- a/app/views/photos/show.html.haml +++ b/app/views/photos/show.html.haml @@ -16,7 +16,7 @@ = link_to "View on Flickr", @photo.link_url - if can? :destroy, @photo - %p= link_to 'Delete Photo', @photo, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' + %p= link_to 'Delete Photo', @photo, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' .col-md-6 - if @photo.plantings.count > 0 diff --git a/app/views/plant_parts/index.html.haml b/app/views/plant_parts/index.html.haml index dc038aebc..9d7c19135 100644 --- a/app/views/plant_parts/index.html.haml +++ b/app/views/plant_parts/index.html.haml @@ -15,9 +15,9 @@ %p - if can? :edit, plant_part - = link_to 'Edit', edit_plant_part_path(plant_part), :class => 'btn btn-xs' + = link_to 'Edit', edit_plant_part_path(plant_part), :class => 'btn btn-default btn-xs' - if can? :destroy, plant_part - = link_to 'Delete', plant_part, :method => :delete, :data => { :confirm => 'Are you sure?' }, :class => 'btn btn-xs' + = link_to 'Delete', plant_part, :method => :delete, :data => { :confirm => 'Are you sure?' }, :class => 'btn btn-default btn-xs' diff --git a/app/views/plant_parts/show.html.haml b/app/views/plant_parts/show.html.haml index d0cd4088c..0af93e520 100644 --- a/app/views/plant_parts/show.html.haml +++ b/app/views/plant_parts/show.html.haml @@ -12,6 +12,6 @@ %p - if can? :edit, @plant_part - = link_to 'Edit', edit_plant_part_path(@plant_part), :class => 'btn btn-xs' + = link_to 'Edit', edit_plant_part_path(@plant_part), :class => 'btn btn-default btn-xs' - if can? :destroy, @plant_part - = link_to 'Delete', @plant_part, :method => :delete, :data => { :confirm => 'Are you sure?' }, :class => 'btn btn-xs' + = link_to 'Delete', @plant_part, :method => :delete, :data => { :confirm => 'Are you sure?' }, :class => 'btn btn-default btn-xs' diff --git a/app/views/plantings/_thumbnail.html.haml b/app/views/plantings/_thumbnail.html.haml index f9a5294ce..cdf011c38 100644 --- a/app/views/plantings/_thumbnail.html.haml +++ b/app/views/plantings/_thumbnail.html.haml @@ -32,6 +32,6 @@ - if can? :edit, planting or can? :destroy, planting %p - if can? :edit, planting - =link_to 'Edit', edit_planting_path(planting), :class => 'btn btn-xs' + =link_to 'Edit', edit_planting_path(planting), :class => 'btn btn-default btn-xs' - if can? :destroy, planting - =link_to 'Delete', planting, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' + =link_to 'Delete', planting, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' diff --git a/app/views/plantings/index.html.haml b/app/views/plantings/index.html.haml index aaee4acd0..9e38f2498 100644 --- a/app/views/plantings/index.html.haml +++ b/app/views/plantings/index.html.haml @@ -46,7 +46,7 @@ %td= planting.planted_at %td= planting.sunniness %td= planting.planted_from - %td= link_to 'Details', planting, :class => 'btn btn-xs' + %td= link_to 'Details', planting, :class => 'btn btn-default btn-xs' %div.pagination = page_entries_info @plantings, :model => "plantings" diff --git a/app/views/plantings/show.html.haml b/app/views/plantings/show.html.haml index 536cdb8d9..a6a713229 100644 --- a/app/views/plantings/show.html.haml +++ b/app/views/plantings/show.html.haml @@ -34,9 +34,9 @@ - if can? :edit, @planting or can? :destroy, @planting %p - if can? :edit, @planting - =link_to 'Edit', edit_planting_path(@planting), :class => 'btn btn-xs' + =link_to 'Edit', edit_planting_path(@planting), :class => 'btn btn-default btn-xs' - if can? :destroy, @planting - =link_to 'Delete', @planting, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' + =link_to 'Delete', @planting, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' .col-md-6 = render :partial => "crops/index_card", :locals => { :crop => @planting.crop} diff --git a/app/views/posts/show.html.haml b/app/views/posts/show.html.haml index 7558e7285..16421996b 100644 --- a/app/views/posts/show.html.haml +++ b/app/views/posts/show.html.haml @@ -5,10 +5,10 @@ - if can? :edit, @post or can? :destroy, @post .post-actions - if can? :edit, @post - = link_to 'Edit Post', edit_post_path(@post), :class => 'btn btn-xs' + = link_to 'Edit Post', edit_post_path(@post), :class => 'btn btn-default btn-xs' - if can? :destroy, @post = link_to 'Delete Post', @post, method: :delete, | - data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' + data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' = render :partial => "comments", :locals => { :post => @post } diff --git a/app/views/roles/index.html.haml b/app/views/roles/index.html.haml index e02c56f80..ae51b2d36 100644 --- a/app/views/roles/index.html.haml +++ b/app/views/roles/index.html.haml @@ -16,6 +16,6 @@ %td= link_to role.name, role %td= role.description - if can? :edit, role - %td= link_to 'Edit', edit_role_path(role), :class => 'btn btn-xs' + %td= link_to 'Edit', edit_role_path(role), :class => 'btn btn-default btn-xs' - if can? :destroy, role - %td= link_to 'Delete', role, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' + %td= link_to 'Delete', role, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' diff --git a/app/views/roles/show.html.haml b/app/views/roles/show.html.haml index f27c5e4ae..1814cbbca 100644 --- a/app/views/roles/show.html.haml +++ b/app/views/roles/show.html.haml @@ -7,6 +7,6 @@ %b Description: = @role.description -= link_to 'Edit', edit_role_path(@role), :class => 'btn btn-xs' += link_to 'Edit', edit_role_path(@role), :class => 'btn btn-default btn-xs' \| = link_to 'Back', roles_path diff --git a/app/views/scientific_names/index.html.haml b/app/views/scientific_names/index.html.haml index 2cd03b735..28cd84710 100644 --- a/app/views/scientific_names/index.html.haml +++ b/app/views/scientific_names/index.html.haml @@ -17,7 +17,7 @@ %td= link_to 'Show', scientific_name %td - if can? :edit, scientific_name - = link_to 'Edit', edit_scientific_name_path(scientific_name), :class => 'btn btn-xs' + = link_to 'Edit', edit_scientific_name_path(scientific_name), :class => 'btn btn-default btn-xs' %td - if can? :destroy, scientific_name - = link_to 'Delete', scientific_name, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' + = link_to 'Delete', scientific_name, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' diff --git a/app/views/scientific_names/show.html.haml b/app/views/scientific_names/show.html.haml index 0772ae9cb..23e3a3c47 100644 --- a/app/views/scientific_names/show.html.haml +++ b/app/views/scientific_names/show.html.haml @@ -7,6 +7,6 @@ %b Crop: = @scientific_name.crop_id -= link_to 'Edit', edit_scientific_name_path(@scientific_name), :class => 'btn btn-xs' += link_to 'Edit', edit_scientific_name_path(@scientific_name), :class => 'btn btn-default btn-xs' \| = link_to 'Back', scientific_names_path diff --git a/app/views/seeds/index.html.haml b/app/views/seeds/index.html.haml index 6ea29227d..faee35465 100644 --- a/app/views/seeds/index.html.haml +++ b/app/views/seeds/index.html.haml @@ -51,7 +51,7 @@ unspecified - else = link_to seed.owner.location, place_path(seed.owner.location) - %td= link_to 'Details', seed, :class => 'btn btn-xs' + %td= link_to 'Details', seed, :class => 'btn btn-default btn-xs' %div.pagination = page_entries_info @seeds, :model => "seeds" diff --git a/app/views/seeds/show.html.haml b/app/views/seeds/show.html.haml index 9364c9a7c..7db91c1a6 100644 --- a/app/views/seeds/show.html.haml +++ b/app/views/seeds/show.html.haml @@ -19,7 +19,7 @@ - if @seed.owner.location.blank? (from unspecified location) - if current_member == @seed.owner - = link_to "Set Location", edit_registration_path(current_member), :class => 'btn btn-xs' + = link_to "Set Location", edit_registration_path(current_member), :class => 'btn btn-default btn-xs' - else (from = succeed ")" do @@ -39,9 +39,9 @@ - if can? :edit, @seed or can? :destroy, @seed %p - if can? :edit, @seed - =link_to 'Edit', edit_seed_path(@seed), :class => 'btn btn-xs' + =link_to 'Edit', edit_seed_path(@seed), :class => 'btn btn-default btn-xs' - if can? :destroy, @seed - =link_to 'Delete', @seed, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-xs' + =link_to 'Delete', @seed, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' .col-md-6 = render :partial => "crops/index_card", :locals => { :crop => @seed.crop } From 4e9679b22ae592efa3c9645d3c682ee2605ca35b Mon Sep 17 00:00:00 2001 From: Skud Date: Tue, 22 Jul 2014 12:09:08 +1000 Subject: [PATCH 044/288] make sure all buttons are either btn-default or btn-primary --- app/views/gardens/index.html.haml | 4 ++-- app/views/notifications/show.html.haml | 2 +- app/views/orders/show.html.haml | 4 ++-- app/views/plantings/index.html.haml | 4 ++-- app/views/posts/index.html.haml | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/views/gardens/index.html.haml b/app/views/gardens/index.html.haml index 65f80c3af..2da035865 100644 --- a/app/views/gardens/index.html.haml +++ b/app/views/gardens/index.html.haml @@ -6,11 +6,11 @@ %p - if @owner == current_member = link_to 'Add a garden', new_garden_path, :class => 'btn btn-primary' - = link_to "View everyone's gardens", gardens_path, :class => 'btn' + = link_to "View everyone's gardens", gardens_path, :class => 'btn btn-default' - else # everyone's gardens = link_to 'Add a garden', new_garden_path, :class => 'btn btn-primary' - if current_member - = link_to 'View your gardens', gardens_by_owner_path(:owner => current_member.slug), :class => 'btn' + = link_to 'View your gardens', gardens_by_owner_path(:owner => current_member.slug), :class => 'btn btn-default' - else = render :partial => 'shared/signin_signup', :locals => { :to => 'add a new garden' } diff --git a/app/views/notifications/show.html.haml b/app/views/notifications/show.html.haml index ad161f9de..1926876a6 100644 --- a/app/views/notifications/show.html.haml +++ b/app/views/notifications/show.html.haml @@ -15,5 +15,5 @@ #{ strip_tags(@notification.body) } %p - =link_to 'Delete', @notification, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn' + =link_to 'Delete', @notification, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default' =link_to 'Reply', @reply_link, :class => 'btn btn-primary' diff --git a/app/views/orders/show.html.haml b/app/views/orders/show.html.haml index 39b648953..dbc635d4e 100644 --- a/app/views/orders/show.html.haml +++ b/app/views/orders/show.html.haml @@ -75,7 +75,7 @@ = submit_tag "Checkout with PayPal", :class => 'btn btn-primary' - if can? :destroy, @order = link_to 'Delete this order', @order, method: :delete, | - data: { confirm: 'Are you sure?' }, :class => 'btn' - = link_to "View other orders/order history", orders_path, :class => 'btn' + data: { confirm: 'Are you sure?' }, :class => 'btn btn-default' + = link_to "View other orders/order history", orders_path, :class => 'btn btn-default' %p diff --git a/app/views/plantings/index.html.haml b/app/views/plantings/index.html.haml index 9e38f2498..926e4a191 100644 --- a/app/views/plantings/index.html.haml +++ b/app/views/plantings/index.html.haml @@ -6,11 +6,11 @@ %p - if @owner == current_member = link_to 'Plant something', new_planting_path, :class => 'btn btn-primary' - = link_to "View everyone's plantings", plantings_path, :class => 'btn' + = link_to "View everyone's plantings", plantings_path, :class => 'btn btn-default' - else # everyone's plantings = link_to 'Plant something', new_planting_path, :class => 'btn btn-primary' - if current_member - = link_to 'View your plantings', plantings_by_owner_path(:owner => current_member.slug), :class => 'btn' + = link_to 'View your plantings', plantings_by_owner_path(:owner => current_member.slug), :class => 'btn btn-default' - else = render :partial => 'shared/signin_signup', :locals => { :to => "track what you've planted" } diff --git a/app/views/posts/index.html.haml b/app/views/posts/index.html.haml index 10bebe997..930dbdf84 100644 --- a/app/views/posts/index.html.haml +++ b/app/views/posts/index.html.haml @@ -6,11 +6,11 @@ %p - if @author == current_member = link_to 'Post something', new_post_path, :class => 'btn btn-primary' - = link_to "View everyone's posts", posts_path, :class => 'btn' + = link_to "View everyone's posts", posts_path, :class => 'btn btn-default' - else # everyone's posts = link_to 'Post something', new_post_path, :class => 'btn btn-primary' - if current_member - = link_to 'View your posts', posts_by_author_path(:author => current_member.slug), :class => 'btn' + = link_to 'View your posts', posts_by_author_path(:author => current_member.slug), :class => 'btn btn-default' - else = render :partial => 'shared/signin_signup', :locals => { :to => 'write a post' } From c0ea08ddc1cf40973388145e04450e146a29c0f7 Mon Sep 17 00:00:00 2001 From: Skud Date: Tue, 22 Jul 2014 12:11:33 +1000 Subject: [PATCH 045/288] s/btn-large/btn-lg/ for bootstrap 3 --- app/views/home/_blurb.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/home/_blurb.html.haml b/app/views/home/_blurb.html.haml index b3b1833c6..3e742b8d4 100644 --- a/app/views/home/_blurb.html.haml +++ b/app/views/home/_blurb.html.haml @@ -13,7 +13,7 @@ = render :partial => 'stats' .col-md-4.signup %p Join now for your free garden journal, seed sharing, forums, and more. - %p= link_to 'Sign up', new_member_registration_path, :class => 'btn btn-primary btn-large' + %p= link_to 'Sign up', new_member_registration_path, :class => 'btn btn-primary btn-lg' %p %small Or From bb0f88bc0bd65d32e3d8512d28381caf1b654038 Mon Sep 17 00:00:00 2001 From: Skud Date: Tue, 22 Jul 2014 14:26:22 +1000 Subject: [PATCH 046/288] Small amount of b3ification when merging dev into b3 branch --- app/views/crops/_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/crops/_form.html.haml b/app/views/crops/_form.html.haml index 3c89648be..8db5a5d8c 100644 --- a/app/views/crops/_form.html.haml +++ b/app/views/crops/_form.html.haml @@ -31,7 +31,7 @@ %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| - .control-group + .form-group = sn.label :scientific_name, "Scientific name", :class => 'control-label' .controls = sn.text_field :scientific_name From b940644ef30e6faa136523a57ffb89d608775e35 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sat, 26 Jul 2014 07:58:05 +1000 Subject: [PATCH 047/288] add capybara and selenium gems --- Gemfile | 2 ++ Gemfile.lock | 15 +++++++++++++++ spec/spec_helper.rb | 2 ++ 3 files changed, 19 insertions(+) diff --git a/Gemfile b/Gemfile index b4ff013a0..e026e07c0 100644 --- a/Gemfile +++ b/Gemfile @@ -123,5 +123,7 @@ group :development, :test do gem 'rspec-rails', '~> 2.12.1' # unit testing framework gem 'webrat' # provides HTML matchers for view tests gem 'factory_girl_rails', '~> 4.0' # for creating test data + gem 'capybara', '~> 2.4.1' # for feature tests + gem 'selenium', '~> 0.2.11' # for browser testing gem 'coveralls', require: false # coverage analysis end diff --git a/Gemfile.lock b/Gemfile.lock index e2bd35b31..4fc8df723 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -73,6 +73,12 @@ GEM railties (>= 3.0) builder (3.0.4) cancan (1.6.10) + capybara (2.4.1) + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) chunky_png (1.3.1) coderay (1.1.0) coffee-rails (3.2.2) @@ -146,6 +152,8 @@ GEM multi_json (~> 1.0) multi_xml (>= 0.5.2) i18n (0.6.1) + jar_wrapper (0.1.8) + zip journey (1.0.4) jquery-rails (3.1.0) railties (>= 3.0, < 5.0) @@ -241,6 +249,8 @@ GEM railties (~> 3.2.0) sass (>= 3.1.10) tilt (~> 1.3) + selenium (0.2.11) + jar_wrapper simplecov (0.8.2) docile (~> 1.1.0) multi_json @@ -277,6 +287,9 @@ GEM rack (>= 1.0) rack-test (>= 0.5.3) will_paginate (3.0.5) + xpath (2.0.0) + nokogiri (~> 1.3) + zip (2.0.2) PLATFORMS ruby @@ -290,6 +303,7 @@ DEPENDENCIES bootstrap-datepicker-rails bundler (>= 1.1.5) cancan + capybara (~> 2.4.1) coffee-rails (~> 3.2.1) compass-rails (~> 1.0.3) coveralls @@ -326,6 +340,7 @@ DEPENDENCIES rake (>= 10.0.0) rspec-rails (~> 2.12.1) sass-rails (~> 3.2.3) + selenium (~> 0.2.11) therubyracer (~> 0.12) twitter-bootstrap-rails! uglifier (>= 1.0.3) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8fa87587a..5c79f7332 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,6 +5,8 @@ require 'rspec/rails' require 'rspec/autorun' require 'coveralls' require 'simplecov' +require 'capybara' + SimpleCov.configure do add_filter 'spec/' end From 5df0c9c71adb13b25c18de13fcc52434a9c35655 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sat, 26 Jul 2014 08:36:48 +1000 Subject: [PATCH 048/288] sketch feature test for planting a crop --- Gemfile | 14 +++++++------- Gemfile.lock | 17 +++++++++++------ config/environments/test.rb | 2 +- spec/features/planting_a_crop_spec.rb | 19 +++++++++++++++++++ spec/spec_helper.rb | 2 ++ 5 files changed, 40 insertions(+), 14 deletions(-) create mode 100644 spec/features/planting_a_crop_spec.rb diff --git a/Gemfile b/Gemfile index e026e07c0..38a8f4ffa 100644 --- a/Gemfile +++ b/Gemfile @@ -119,11 +119,11 @@ gem 'omniauth-flickr', '>= 0.0.15' gem 'rake', '>= 10.0.0' group :development, :test do - gem 'haml-rails' # HTML templating language - gem 'rspec-rails', '~> 2.12.1' # unit testing framework - gem 'webrat' # provides HTML matchers for view tests - gem 'factory_girl_rails', '~> 4.0' # for creating test data - gem 'capybara', '~> 2.4.1' # for feature tests - gem 'selenium', '~> 0.2.11' # for browser testing - gem 'coveralls', require: false # coverage analysis + gem 'haml-rails' # HTML templating language + gem 'rspec-rails', '~> 2.12.1' # unit testing framework + gem 'webrat' # provides HTML matchers for view tests + gem 'factory_girl_rails', '~> 4.0' # for creating test data + gem 'capybara', '~> 2.4.1' # for feature tests + gem 'selenium-webdriver', '~> 2.42.0' # for browser testing + gem 'coveralls', require: false # coverage analysis end diff --git a/Gemfile.lock b/Gemfile.lock index 4fc8df723..15690d58c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -79,6 +79,8 @@ GEM rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (~> 2.0) + childprocess (0.5.3) + ffi (~> 1.0, >= 1.0.11) chunky_png (1.3.1) coderay (1.1.0) coffee-rails (3.2.2) @@ -126,6 +128,7 @@ GEM factory_girl_rails (4.4.1) factory_girl (~> 4.4.0) railties (>= 3.0.0) + ffi (1.9.3) figaro (0.7.0) bundler (~> 1.0) rails (>= 3, < 5) @@ -152,8 +155,6 @@ GEM multi_json (~> 1.0) multi_xml (>= 0.5.2) i18n (0.6.1) - jar_wrapper (0.1.8) - zip journey (1.0.4) jquery-rails (3.1.0) railties (>= 3.0, < 5.0) @@ -244,13 +245,17 @@ GEM rspec-core (~> 2.12.0) rspec-expectations (~> 2.12.0) rspec-mocks (~> 2.12.0) + rubyzip (1.1.6) sass (3.2.19) sass-rails (3.2.6) railties (~> 3.2.0) sass (>= 3.1.10) tilt (~> 1.3) - selenium (0.2.11) - jar_wrapper + selenium-webdriver (2.42.0) + childprocess (>= 0.5.0) + multi_json (~> 1.0) + rubyzip (~> 1.0) + websocket (~> 1.0.4) simplecov (0.8.2) docile (~> 1.1.0) multi_json @@ -286,10 +291,10 @@ GEM nokogiri (>= 1.2.0) rack (>= 1.0) rack-test (>= 0.5.3) + websocket (1.0.7) will_paginate (3.0.5) xpath (2.0.0) nokogiri (~> 1.3) - zip (2.0.2) PLATFORMS ruby @@ -340,7 +345,7 @@ DEPENDENCIES rake (>= 10.0.0) rspec-rails (~> 2.12.1) sass-rails (~> 3.2.3) - selenium (~> 0.2.11) + selenium-webdriver (~> 2.42.0) therubyracer (~> 0.12) twitter-bootstrap-rails! uglifier (>= 1.0.3) diff --git a/config/environments/test.rb b/config/environments/test.rb index be79ef8f6..6f49b5ef0 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -21,7 +21,7 @@ Growstuff::Application.configure do config.action_controller.perform_caching = false # Raise exceptions instead of rendering exception templates - config.action_dispatch.show_exceptions = false + config.action_dispatch.show_exceptions = true # Disable request forgery protection in test environment config.action_controller.allow_forgery_protection = false diff --git a/spec/features/planting_a_crop_spec.rb b/spec/features/planting_a_crop_spec.rb new file mode 100644 index 000000000..e3f328ac7 --- /dev/null +++ b/spec/features/planting_a_crop_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +feature "Planting a crop", :js => true do + let(:wrangler) { FactoryGirl.create(:member) } + let(:maize) { FactoryGirl.create(:maize) } + + background do + login_as(wrangler) + visit '/plantings/new' + end + + scenario "Typing in the crop name displays suggestions" do + within "form#new_planting" do + fill_in "What did you plant?", :with => "m" + expect(page).to have_content("maize") + end + end + +end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 5c79f7332..b2af2ab6e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -7,6 +7,8 @@ require 'coveralls' require 'simplecov' require 'capybara' +include Warden::Test::Helpers + SimpleCov.configure do add_filter 'spec/' end From 8a1dd9d171a8a6a1cdc1b12e701557f91b5f9068 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sat, 26 Jul 2014 20:16:22 +1000 Subject: [PATCH 049/288] implement autocomplete for crop name on new planting form --- Gemfile | 14 +++++------ Gemfile.lock | 29 ++++++++++------------ app/assets/javascripts/application.js | 3 +++ app/assets/javascripts/plantings.js.coffee | 19 ++++++++++++++ app/assets/stylesheets/application.css | 1 + app/controllers/crops_controller.rb | 4 ++- app/views/plantings/_form.html.haml | 3 ++- config/application.rb | 2 +- 8 files changed, 49 insertions(+), 26 deletions(-) diff --git a/Gemfile b/Gemfile index 38a8f4ffa..c88dee404 100644 --- a/Gemfile +++ b/Gemfile @@ -67,6 +67,8 @@ group :assets do end gem 'jquery-rails' +gem 'jquery-ui-rails' +gem 'js-routes' # provides access to Rails routes in Javascript gem 'flickraw' # To use ActiveModel has_secure_password @@ -119,11 +121,9 @@ gem 'omniauth-flickr', '>= 0.0.15' gem 'rake', '>= 10.0.0' group :development, :test do - gem 'haml-rails' # HTML templating language - gem 'rspec-rails', '~> 2.12.1' # unit testing framework - gem 'webrat' # provides HTML matchers for view tests - gem 'factory_girl_rails', '~> 4.0' # for creating test data - gem 'capybara', '~> 2.4.1' # for feature tests - gem 'selenium-webdriver', '~> 2.42.0' # for browser testing - gem 'coveralls', require: false # coverage analysis + gem 'haml-rails' # HTML templating language + gem 'rspec-rails', '~> 2.12.1' # unit testing framework + gem 'webrat' # provides HTML matchers for view tests + gem 'factory_girl_rails', '~> 4.0' # for creating test data + gem 'coveralls', require: false # coverage analysis end diff --git a/Gemfile.lock b/Gemfile.lock index 15690d58c..26caac25a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -79,8 +79,9 @@ GEM rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (~> 2.0) - childprocess (0.5.3) - ffi (~> 1.0, >= 1.0.11) + capybara-webkit (1.1.0) + capybara (~> 2.0, >= 2.0.2) + json chunky_png (1.3.1) coderay (1.1.0) coffee-rails (3.2.2) @@ -128,7 +129,6 @@ GEM factory_girl_rails (4.4.1) factory_girl (~> 4.4.0) railties (>= 3.0.0) - ffi (1.9.3) figaro (0.7.0) bundler (~> 1.0) rails (>= 3, < 5) @@ -159,6 +159,11 @@ GEM jquery-rails (3.1.0) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) + jquery-ui-rails (4.1.2) + railties (>= 3.1.0) + js-routes (0.9.8) + railties (>= 3.2) + sprockets-rails json (1.7.7) kgio (2.9.2) launchy (2.4.2) @@ -245,17 +250,11 @@ GEM rspec-core (~> 2.12.0) rspec-expectations (~> 2.12.0) rspec-mocks (~> 2.12.0) - rubyzip (1.1.6) sass (3.2.19) sass-rails (3.2.6) railties (~> 3.2.0) sass (>= 3.1.10) tilt (~> 1.3) - selenium-webdriver (2.42.0) - childprocess (>= 0.5.0) - multi_json (~> 1.0) - rubyzip (~> 1.0) - websocket (~> 1.0.4) simplecov (0.8.2) docile (~> 1.1.0) multi_json @@ -266,6 +265,8 @@ GEM multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) + sprockets-rails (0.0.1) + sprockets (>= 1.0.2) term-ansicolor (1.3.0) tins (~> 1.0) therubyracer (0.12.1) @@ -287,11 +288,6 @@ GEM raindrops (~> 0.7) warden (1.2.3) rack (>= 1.0) - webrat (0.7.3) - nokogiri (>= 1.2.0) - rack (>= 1.0) - rack-test (>= 0.5.3) - websocket (1.0.7) will_paginate (3.0.5) xpath (2.0.0) nokogiri (~> 1.3) @@ -309,6 +305,7 @@ DEPENDENCIES bundler (>= 1.1.5) cancan capybara (~> 2.4.1) + capybara-webkit coffee-rails (~> 3.2.1) compass-rails (~> 1.0.3) coveralls @@ -326,6 +323,8 @@ DEPENDENCIES haml haml-rails jquery-rails + jquery-ui-rails + js-routes json (~> 1.7.7) leaflet-markercluster-rails leaflet-rails @@ -345,10 +344,8 @@ DEPENDENCIES rake (>= 10.0.0) rspec-rails (~> 2.12.1) sass-rails (~> 3.2.3) - selenium-webdriver (~> 2.42.0) therubyracer (~> 0.12) twitter-bootstrap-rails! uglifier (>= 1.0.3) unicorn - webrat will_paginate (~> 3.0) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index cd84e8b20..beb65d620 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -12,8 +12,11 @@ // //= require leaflet //= require leaflet.markercluster +//= require js-routes //= require jquery //= require jquery_ujs +//= require jquery.ui.autocomplete //= require twitter/bootstrap //= require_tree . //= require bootstrap-datepicker + diff --git a/app/assets/javascripts/plantings.js.coffee b/app/assets/javascripts/plantings.js.coffee index ab5b73462..4d858f66b 100644 --- a/app/assets/javascripts/plantings.js.coffee +++ b/app/assets/javascripts/plantings.js.coffee @@ -4,3 +4,22 @@ jQuery -> $('.add-datepicker').datepicker('format' : 'yyyy-mm-dd') + +jQuery -> + if el = $( '#crop' ) + el.autocomplete + minLength: 1, + source: Routes.crops_search_path(), + focus: ( event, ui ) -> + el.val( ui.item.name ) + false + select: ( event, ui ) -> + el.val( ui.item.name ) + $( '#planting_crop_id' ).val( ui.item.id ) + false + .data( 'uiAutocomplete' )._renderItem = ( ul, item ) -> + $( '
  • ' ) + .data( 'item.autocomplete', item ) + .append( "#{item.name}" ) + .appendTo( ul ) + diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index c1cc5e005..1d44a0cbf 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -3,6 +3,7 @@ * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at * the top of the compiled file, but it's generally better to create a new file per style scope. *= require_self + *= require jquery.ui.autocomplete *= require bootstrap-datepicker *= require leaflet *= require leaflet.markercluster diff --git a/app/controllers/crops_controller.rb b/app/controllers/crops_controller.rb index ae38b2817..18261d332 100644 --- a/app/controllers/crops_controller.rb +++ b/app/controllers/crops_controller.rb @@ -57,8 +57,11 @@ class CropsController < ApplicationController # exclude exact match from partial match list @partial_matches.reject!{ |r| @exact_match && r.eql?(@exact_match) } + @starts_with = Crop.where("name LIKE :prefix", prefix: "#{params[:term]}%") + respond_to do |format| format.html + format.json { render :json => @starts_with } end end @@ -101,7 +104,6 @@ class CropsController < ApplicationController params[:crop][:creator_id] = current_member.id @crop = Crop.new(params[:crop]) - respond_to do |format| if @crop.save format.html { redirect_to @crop, notice: 'Crop was successfully created.' } diff --git a/app/views/plantings/_form.html.haml b/app/views/plantings/_form.html.haml index 71056f7e7..89bd5e115 100644 --- a/app/views/plantings/_form.html.haml +++ b/app/views/plantings/_form.html.haml @@ -9,7 +9,8 @@ .control-group = f.label 'What did you plant?', :class => 'control-label' .controls - = collection_select(:planting, :crop_id, Crop.all, :id, :name, :selected => @planting.crop_id || @crop.id) + = text_field_tag(:crop) + = f.hidden_field(: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 diff --git a/config/application.rb b/config/application.rb index bfa1fee4b..f1a5e9dd6 100644 --- a/config/application.rb +++ b/config/application.rb @@ -64,7 +64,7 @@ module Growstuff config.assets.version = '1.0' # Don't try to connect to the database when precompiling assets - config.assets.initialize_on_precompile = false + config.assets.initialize_on_precompile = true config.generators do |g| g.template_engine :haml From 29752e049eb5c9571a6bfd6d05773001171022af Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sat, 26 Jul 2014 20:19:46 +1000 Subject: [PATCH 050/288] remove stray reference to capybara --- spec/spec_helper.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b2af2ab6e..a71d9caf8 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,9 +5,6 @@ require 'rspec/rails' require 'rspec/autorun' require 'coveralls' require 'simplecov' -require 'capybara' - -include Warden::Test::Helpers SimpleCov.configure do add_filter 'spec/' From 1ee79c25f1fc6c840316ab0dac9ae061727d3f35 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sun, 27 Jul 2014 07:38:56 +1000 Subject: [PATCH 051/288] set up selenium test but encountering jQuery not defined error --- Gemfile | 12 +++++++----- Gemfile.lock | 18 +++++------------- spec/features/planting_a_crop_spec.rb | 8 ++++---- spec/spec_helper.rb | 3 +++ spec/support/feature_helpers.rb | 19 +++++++++++++++++++ 5 files changed, 38 insertions(+), 22 deletions(-) create mode 100644 spec/support/feature_helpers.rb diff --git a/Gemfile b/Gemfile index c88dee404..d14fe4c3b 100644 --- a/Gemfile +++ b/Gemfile @@ -121,9 +121,11 @@ gem 'omniauth-flickr', '>= 0.0.15' gem 'rake', '>= 10.0.0' group :development, :test do - gem 'haml-rails' # HTML templating language - gem 'rspec-rails', '~> 2.12.1' # unit testing framework - gem 'webrat' # provides HTML matchers for view tests - gem 'factory_girl_rails', '~> 4.0' # for creating test data - gem 'coveralls', require: false # coverage analysis + gem 'haml-rails' # HTML templating language + gem 'rspec-rails', '~> 2.12.1' # unit testing framework + gem 'webrat' # provides HTML matchers for view tests + gem 'capybara', '~> 2.4.1' # for feature tests + gem 'selenium-webdriver', '~> 2.42.0' # for testing in the browser + gem 'factory_girl_rails', '~> 4.0' # for creating test data + gem 'coveralls', require: false # coverage analysis end diff --git a/Gemfile.lock b/Gemfile.lock index 26caac25a..509b90ecb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -73,15 +73,6 @@ GEM railties (>= 3.0) builder (3.0.4) cancan (1.6.10) - capybara (2.4.1) - mime-types (>= 1.16) - nokogiri (>= 1.3.3) - rack (>= 1.0.0) - rack-test (>= 0.5.4) - xpath (~> 2.0) - capybara-webkit (1.1.0) - capybara (~> 2.0, >= 2.0.2) - json chunky_png (1.3.1) coderay (1.1.0) coffee-rails (3.2.2) @@ -288,9 +279,11 @@ GEM raindrops (~> 0.7) warden (1.2.3) rack (>= 1.0) + webrat (0.7.3) + nokogiri (>= 1.2.0) + rack (>= 1.0) + rack-test (>= 0.5.3) will_paginate (3.0.5) - xpath (2.0.0) - nokogiri (~> 1.3) PLATFORMS ruby @@ -304,8 +297,6 @@ DEPENDENCIES bootstrap-datepicker-rails bundler (>= 1.1.5) cancan - capybara (~> 2.4.1) - capybara-webkit coffee-rails (~> 3.2.1) compass-rails (~> 1.0.3) coveralls @@ -348,4 +339,5 @@ DEPENDENCIES twitter-bootstrap-rails! uglifier (>= 1.0.3) unicorn + webrat will_paginate (~> 3.0) diff --git a/spec/features/planting_a_crop_spec.rb b/spec/features/planting_a_crop_spec.rb index e3f328ac7..fe07853b5 100644 --- a/spec/features/planting_a_crop_spec.rb +++ b/spec/features/planting_a_crop_spec.rb @@ -1,17 +1,17 @@ require 'spec_helper' feature "Planting a crop", :js => true do - let(:wrangler) { FactoryGirl.create(:member) } - let(:maize) { FactoryGirl.create(:maize) } + let(:member) { FactoryGirl.create(:member) } + let!(:maize) { FactoryGirl.create(:maize) } background do - login_as(wrangler) + login_as(member) visit '/plantings/new' end scenario "Typing in the crop name displays suggestions" do within "form#new_planting" do - fill_in "What did you plant?", :with => "m" + fill_autocomplete 'crop', with: 'm' expect(page).to have_content("maize") end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a71d9caf8..b2af2ab6e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,6 +5,9 @@ require 'rspec/rails' require 'rspec/autorun' require 'coveralls' require 'simplecov' +require 'capybara' + +include Warden::Test::Helpers SimpleCov.configure do add_filter 'spec/' diff --git a/spec/support/feature_helpers.rb b/spec/support/feature_helpers.rb new file mode 100644 index 000000000..5429b3fc7 --- /dev/null +++ b/spec/support/feature_helpers.rb @@ -0,0 +1,19 @@ +module FeatureHelpers + + def fill_autocomplete(field, options = {}) + fill_in field, with: options[:with] + + page.execute_script %Q{ $('##{field}').trigger('focus') } + page.execute_script %Q{ $('##{field}').trigger('keydown') } + # selector = %Q{ul.ui-autocomplete li.ui-menu-item a:contains("#{options[:select]}")} + + # page.should have_selector('ul.ui-autocomplete li.ui-menu-item a') + # page.execute_script %Q{ $('#{selector}').trigger('mouseenter').click() } + end + + +end + +RSpec.configure do |config| + config.include FeatureHelpers, type: :feature +end \ No newline at end of file From 948aeb1289d7df2ae5193caa9c00c6a65ee1238e Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sun, 27 Jul 2014 21:04:52 +1000 Subject: [PATCH 052/288] working on getting Selenium tests to pass --- Gemfile | 2 + Gemfile.lock | 27 ++++++++ app/assets/javascripts/crops.js.erb | 80 ++++++++++++------------ app/assets/javascripts/places.js.erb | 90 +++++++++++++-------------- spec/features/planting_a_crop_spec.rb | 10 ++- spec/support/feature_helpers.rb | 6 +- 6 files changed, 127 insertions(+), 88 deletions(-) diff --git a/Gemfile b/Gemfile index d14fe4c3b..e2ee6ad8a 100644 --- a/Gemfile +++ b/Gemfile @@ -128,4 +128,6 @@ group :development, :test do gem 'selenium-webdriver', '~> 2.42.0' # for testing in the browser gem 'factory_girl_rails', '~> 4.0' # for creating test data gem 'coveralls', require: false # coverage analysis + + gem 'pry', '~> 0.10.0' # for debugging end diff --git a/Gemfile.lock b/Gemfile.lock index 509b90ecb..5d744da38 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -73,6 +73,14 @@ GEM railties (>= 3.0) builder (3.0.4) cancan (1.6.10) + capybara (2.4.1) + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) + childprocess (0.5.3) + ffi (~> 1.0, >= 1.0.11) chunky_png (1.3.1) coderay (1.1.0) coffee-rails (3.2.2) @@ -120,6 +128,7 @@ GEM factory_girl_rails (4.4.1) factory_girl (~> 4.4.0) railties (>= 3.0.0) + ffi (1.9.3) figaro (0.7.0) bundler (~> 1.0) rails (>= 3, < 5) @@ -174,6 +183,7 @@ GEM mime-types (~> 1.16) treetop (~> 1.4.8) memcachier (0.0.2) + method_source (0.8.2) mime-types (1.25.1) mini_portile (0.5.3) multi_json (1.9.3) @@ -196,6 +206,10 @@ GEM orm_adapter (0.5.0) pg (0.17.1) polyglot (0.3.4) + pry (0.10.0) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) rack (1.4.5) rack-cache (1.2) rack (>= 0.4) @@ -241,16 +255,23 @@ GEM rspec-core (~> 2.12.0) rspec-expectations (~> 2.12.0) rspec-mocks (~> 2.12.0) + rubyzip (1.1.6) sass (3.2.19) sass-rails (3.2.6) railties (~> 3.2.0) sass (>= 3.1.10) tilt (~> 1.3) + selenium-webdriver (2.42.0) + childprocess (>= 0.5.0) + multi_json (~> 1.0) + rubyzip (~> 1.0) + websocket (~> 1.0.4) simplecov (0.8.2) docile (~> 1.1.0) multi_json simplecov-html (~> 0.8.0) simplecov-html (0.8.0) + slop (3.6.0) sprockets (2.2.2) hike (~> 1.2) multi_json (~> 1.0) @@ -283,7 +304,10 @@ GEM nokogiri (>= 1.2.0) rack (>= 1.0) rack-test (>= 0.5.3) + websocket (1.0.7) will_paginate (3.0.5) + xpath (2.0.0) + nokogiri (~> 1.3) PLATFORMS ruby @@ -297,6 +321,7 @@ DEPENDENCIES bootstrap-datepicker-rails bundler (>= 1.1.5) cancan + capybara (~> 2.4.1) coffee-rails (~> 3.2.1) compass-rails (~> 1.0.3) coveralls @@ -329,12 +354,14 @@ DEPENDENCIES omniauth-flickr (>= 0.0.15) omniauth-twitter pg + pry (~> 0.10.0) rack (~> 1.4.5) rails (= 3.2.13) rails_12factor rake (>= 10.0.0) rspec-rails (~> 2.12.1) sass-rails (~> 3.2.3) + selenium-webdriver (~> 2.42.0) therubyracer (~> 0.12) twitter-bootstrap-rails! uglifier (>= 1.0.3) diff --git a/app/assets/javascripts/crops.js.erb b/app/assets/javascripts/crops.js.erb index 3a3837633..bd11cebeb 100644 --- a/app/assets/javascripts/crops.js.erb +++ b/app/assets/javascripts/crops.js.erb @@ -1,49 +1,49 @@ -if (document.getElementById("cropmap") !== null) { - mapbox_map_id = "<%= Growstuff::Application.config.mapbox_map_id %>"; - mapbox_base_url = "https://c.tiles.mapbox.com/v3/" + mapbox_map_id + "/{z}/{x}/{y}.png"; +// if (document.getElementById("cropmap") !== null) { +// mapbox_map_id = "<%= Growstuff::Application.config.mapbox_map_id %>"; +// mapbox_base_url = "https://c.tiles.mapbox.com/v3/" + mapbox_map_id + "/{z}/{x}/{y}.png"; - L.Icon.Default.imagePath = '/assets' +// L.Icon.Default.imagePath = '/assets' - cropmap = L.map('cropmap').setView([0.0, -0.0], 2); - showCropMap(cropmap); -} +// cropmap = L.map('cropmap').setView([0.0, -0.0], 2); +// showCropMap(cropmap); +// } -function showCropMap(cropmap) { +// function showCropMap(cropmap) { - L.tileLayer(mapbox_base_url, { - attribution: 'Map data © OpenStreetMap contributors under ODbL | Map imagery © Mapbox', - maxZoom: 18 - }).addTo(cropmap); - markers = new L.MarkerClusterGroup({showCoverageOnHover: false, maxClusterRadius: 20 }); +// L.tileLayer(mapbox_base_url, { +// attribution: 'Map data © OpenStreetMap contributors under ODbL | Map imagery © Mapbox', +// maxZoom: 18 +// }).addTo(cropmap); +// markers = new L.MarkerClusterGroup({showCoverageOnHover: false, maxClusterRadius: 20 }); - things_to_map = location.pathname + '.json'; - $.getJSON(things_to_map, function(crop) { - $.each(crop.plantings, function(i, planting) { - owner = planting.owner; - if (owner.latitude && owner.longitude) { - marker = new L.Marker(new L.LatLng(owner.latitude, owner.longitude)); +// things_to_map = location.pathname + '.json'; +// $.getJSON(things_to_map, function(crop) { +// $.each(crop.plantings, function(i, planting) { +// owner = planting.owner; +// if (owner.latitude && owner.longitude) { +// marker = new L.Marker(new L.LatLng(owner.latitude, owner.longitude)); - planting_url = "/plantings/" + planting.id; - planting_link = "" + owner.login_name + "'s " + crop.name + ""; +// planting_url = "/plantings/" + planting.id; +// planting_link = "" + owner.login_name + "'s " + crop.name + ""; - where = "

    " + owner.location + "

    "; +// where = "

    " + owner.location + "

    "; - details = "

    "; - if (planting.quantity) { - details = details + "Quantity: " + planting.quantity + "
    "; - } - if (planting.planted_from) { - details = details + "Planted from: " + planting.planted_from + "
    "; - } - if (planting.sunniness) { - details = details + "Planted in: " + planting.sunniness+ "
    "; - } - details = details + "

    "; - marker.bindPopup(planting_link + where + details).openPopup(); - markers.addLayer(marker); - } - }); - }); +// details = "

    "; +// if (planting.quantity) { +// details = details + "Quantity: " + planting.quantity + "
    "; +// } +// if (planting.planted_from) { +// details = details + "Planted from: " + planting.planted_from + "
    "; +// } +// if (planting.sunniness) { +// details = details + "Planted in: " + planting.sunniness+ "
    "; +// } +// details = details + "

    "; +// marker.bindPopup(planting_link + where + details).openPopup(); +// markers.addLayer(marker); +// } +// }); +// }); - cropmap.addLayer(markers); -} +// cropmap.addLayer(markers); +// } diff --git a/app/assets/javascripts/places.js.erb b/app/assets/javascripts/places.js.erb index f4758c561..d4767e594 100644 --- a/app/assets/javascripts/places.js.erb +++ b/app/assets/javascripts/places.js.erb @@ -1,50 +1,50 @@ -if (document.getElementById("placesmap") !== null) { - places_base_path = "/places"; - mapbox_map_id = "<%= Growstuff::Application.config.mapbox_map_id %>"; - mapbox_base_url = "https://c.tiles.mapbox.com/v3/" + mapbox_map_id + "/{z}/{x}/{y}.png"; - nominatim_base_url = 'http://nominatim.openstreetmap.org/search/'; - nominatim_user_agent_email = "<%= Growstuff::Application.config.user_agent_email %>"; +// if (document.getElementById("placesmap") !== null) { +// places_base_path = "/places"; +// mapbox_map_id = "<%= Growstuff::Application.config.mapbox_map_id %>"; +// mapbox_base_url = "https://c.tiles.mapbox.com/v3/" + mapbox_map_id + "/{z}/{x}/{y}.png"; +// nominatim_base_url = 'http://nominatim.openstreetmap.org/search/'; +// nominatim_user_agent_email = "<%= Growstuff::Application.config.user_agent_email %>"; - L.Icon.Default.imagePath = '/assets' +// L.Icon.Default.imagePath = '/assets' - if (location.pathname === places_base_path) { //places index page - placesmap = L.map('placesmap').setView([0.0, -0.0], 2); - showMap(placesmap); - } else { // specific place page - place = location.pathname.replace(places_base_path + "/", ''); - nominatim_query_url = nominatim_base_url + place; - nominatim_options = { - format: "json", - callback: "placeholder", - limit: 1, - email: nominatim_user_agent_email - }; - $.getJSON(nominatim_query_url, nominatim_options, function(data) { - placesmap = L.map('placesmap').setView([data[0].lat, data[0].lon], 5); - showMap(placesmap); - }) - } -} +// if (location.pathname === places_base_path) { //places index page +// placesmap = L.map('placesmap').setView([0.0, -0.0], 2); +// showMap(placesmap); +// } else { // specific place page +// place = location.pathname.replace(places_base_path + "/", ''); +// nominatim_query_url = nominatim_base_url + place; +// nominatim_options = { +// format: "json", +// callback: "placeholder", +// limit: 1, +// email: nominatim_user_agent_email +// }; +// $.getJSON(nominatim_query_url, nominatim_options, function(data) { +// placesmap = L.map('placesmap').setView([data[0].lat, data[0].lon], 5); +// showMap(placesmap); +// }) +// } +// } -function showMap(placesmap) { - L.tileLayer(mapbox_base_url, { - attribution: 'Map data © OpenStreetMap contributors under ODbL | Map imagery © Mapbox', - maxZoom: 18 - }).addTo(placesmap); - markers = new L.MarkerClusterGroup({showCoverageOnHover: false, maxClusterRadius: 20 }); +// function showMap(placesmap) { +// L.tileLayer(mapbox_base_url, { +// attribution: 'Map data © OpenStreetMap contributors under ODbL | Map imagery © Mapbox', +// maxZoom: 18 +// }).addTo(placesmap); +// markers = new L.MarkerClusterGroup({showCoverageOnHover: false, maxClusterRadius: 20 }); - things_to_map = location.pathname + '.json'; - $.getJSON(things_to_map, function(members) { - $.each(members, function(i, m) { - if (m.latitude && m.longitude) { - marker = new L.Marker(new L.LatLng(m.latitude, m.longitude)); - link = "

    " + m.login_name + "

    "; - where = "

    " + m.location + "

    "; - marker.bindPopup(link + where).openPopup(); - markers.addLayer(marker); - } - }); - }); +// things_to_map = location.pathname + '.json'; +// $.getJSON(things_to_map, function(members) { +// $.each(members, function(i, m) { +// if (m.latitude && m.longitude) { +// marker = new L.Marker(new L.LatLng(m.latitude, m.longitude)); +// link = "

    " + m.login_name + "

    "; +// where = "

    " + m.location + "

    "; +// marker.bindPopup(link + where).openPopup(); +// markers.addLayer(marker); +// } +// }); +// }); - placesmap.addLayer(markers); -} +// placesmap.addLayer(markers); +// } diff --git a/spec/features/planting_a_crop_spec.rb b/spec/features/planting_a_crop_spec.rb index fe07853b5..7f38a3eda 100644 --- a/spec/features/planting_a_crop_spec.rb +++ b/spec/features/planting_a_crop_spec.rb @@ -11,7 +11,15 @@ feature "Planting a crop", :js => true do scenario "Typing in the crop name displays suggestions" do within "form#new_planting" do - fill_autocomplete 'crop', with: 'm' + # fill_autocomplete 'crop', with: 'm' + fill_in 'crop', :with => "m" + + binding.pry + + sleep 1000 + + page.execute_script %Q{ $('#crop').trigger('focus') } + page.execute_script %Q{ $('#crop').trigger('keydown') } expect(page).to have_content("maize") end end diff --git a/spec/support/feature_helpers.rb b/spec/support/feature_helpers.rb index 5429b3fc7..16d7abdec 100644 --- a/spec/support/feature_helpers.rb +++ b/spec/support/feature_helpers.rb @@ -1,7 +1,9 @@ module FeatureHelpers def fill_autocomplete(field, options = {}) - fill_in field, with: options[:with] + fill_in field, :with => options[:with] + + sleep 3 page.execute_script %Q{ $('##{field}').trigger('focus') } page.execute_script %Q{ $('##{field}').trigger('keydown') } @@ -15,5 +17,5 @@ module FeatureHelpers end RSpec.configure do |config| - config.include FeatureHelpers, type: :feature + config.include FeatureHelpers, :type => :feature end \ No newline at end of file From ec060b9cc0331fe5a15d343a7bcb1e300d84c385 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sun, 27 Jul 2014 22:40:14 +1000 Subject: [PATCH 053/288] fix most view tests that broke after changing new planting form to auto-suggest crop name --- Gemfile.lock | 18 +++++------------- app/views/plantings/_form.html.haml | 2 +- spec/features/planting_a_crop_spec.rb | 19 ------------------- spec/views/plantings/edit.html.haml_spec.rb | 3 +-- spec/views/plantings/new.html.haml_spec.rb | 11 ++++++----- 5 files changed, 13 insertions(+), 40 deletions(-) delete mode 100644 spec/features/planting_a_crop_spec.rb diff --git a/Gemfile.lock b/Gemfile.lock index 26caac25a..509b90ecb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -73,15 +73,6 @@ GEM railties (>= 3.0) builder (3.0.4) cancan (1.6.10) - capybara (2.4.1) - mime-types (>= 1.16) - nokogiri (>= 1.3.3) - rack (>= 1.0.0) - rack-test (>= 0.5.4) - xpath (~> 2.0) - capybara-webkit (1.1.0) - capybara (~> 2.0, >= 2.0.2) - json chunky_png (1.3.1) coderay (1.1.0) coffee-rails (3.2.2) @@ -288,9 +279,11 @@ GEM raindrops (~> 0.7) warden (1.2.3) rack (>= 1.0) + webrat (0.7.3) + nokogiri (>= 1.2.0) + rack (>= 1.0) + rack-test (>= 0.5.3) will_paginate (3.0.5) - xpath (2.0.0) - nokogiri (~> 1.3) PLATFORMS ruby @@ -304,8 +297,6 @@ DEPENDENCIES bootstrap-datepicker-rails bundler (>= 1.1.5) cancan - capybara (~> 2.4.1) - capybara-webkit coffee-rails (~> 3.2.1) compass-rails (~> 1.0.3) coveralls @@ -348,4 +339,5 @@ DEPENDENCIES twitter-bootstrap-rails! uglifier (>= 1.0.3) unicorn + webrat will_paginate (~> 3.0) diff --git a/app/views/plantings/_form.html.haml b/app/views/plantings/_form.html.haml index 89bd5e115..19c680a02 100644 --- a/app/views/plantings/_form.html.haml +++ b/app/views/plantings/_form.html.haml @@ -9,7 +9,7 @@ .control-group = f.label 'What did you plant?', :class => 'control-label' .controls - = text_field_tag(:crop) + = text_field_tag(:crop, @planting.crop.nil? ? "" : @planting.crop.name) = f.hidden_field(:crop_id) %span.help-inline Can't find what you're looking for? diff --git a/spec/features/planting_a_crop_spec.rb b/spec/features/planting_a_crop_spec.rb deleted file mode 100644 index e3f328ac7..000000000 --- a/spec/features/planting_a_crop_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'spec_helper' - -feature "Planting a crop", :js => true do - let(:wrangler) { FactoryGirl.create(:member) } - let(:maize) { FactoryGirl.create(:maize) } - - background do - login_as(wrangler) - visit '/plantings/new' - end - - scenario "Typing in the crop name displays suggestions" do - within "form#new_planting" do - fill_in "What did you plant?", :with => "m" - expect(page).to have_content("maize") - end - end - -end \ No newline at end of file diff --git a/spec/views/plantings/edit.html.haml_spec.rb b/spec/views/plantings/edit.html.haml_spec.rb index bed679037..f80f6a0b4 100644 --- a/spec/views/plantings/edit.html.haml_spec.rb +++ b/spec/views/plantings/edit.html.haml_spec.rb @@ -44,8 +44,7 @@ describe "plantings/edit" do end it "chooses the right crop" do - assert_select "select#planting_crop_id", - :html => /option value="#{@tomato.id}" selected="selected"/ + assert_select "input#crop[value=?]", "tomato" end it "chooses the right garden" do diff --git a/spec/views/plantings/new.html.haml_spec.rb b/spec/views/plantings/new.html.haml_spec.rb index 3a945b06d..cd9a4d4eb 100644 --- a/spec/views/plantings/new.html.haml_spec.rb +++ b/spec/views/plantings/new.html.haml_spec.rb @@ -30,7 +30,8 @@ describe "plantings/new" do it "renders new planting form" do assert_select "form", :action => plantings_path, :method => "post" do assert_select "select#planting_garden_id", :name => "planting[garden_id]" - assert_select "select#planting_crop_id", :name => "planting[crop_id]" + assert_select "input#crop", :class => "ui-autocomplete-input" + assert_select "input#planting_crop_id", :name => "planting[crop_id]" assert_select "input#planting_quantity", :name => "planting[quantity]" assert_select "textarea#planting_description", :name => "planting[description]" assert_select "select#planting_sunniness", :name => "planting[sunniness]" @@ -43,10 +44,10 @@ describe "plantings/new" do assert_select "a[href=#{Growstuff::Application.config.new_crops_request_link}]", :text => "Request new crops." end - it "selects a crop given in a param" do - assert_select "select#planting_crop_id", - :html => /option value="#{@crop2.id}" selected="selected"/ - end + # it "selects a crop given in a param" do + # assert_select "select#planting_crop_id", + # :html => /option value="#{@crop2.id}" selected="selected"/ + # end it "selects a garden given in a param" do assert_select "select#planting_garden_id", From b28ce28703d50ef9045c27b735cf25d668144366 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Mon, 28 Jul 2014 00:12:40 +1000 Subject: [PATCH 054/288] replace mapbox_map_id and nominatim_user_agent_email with benign values if Rails env is test --- app/assets/javascripts/crops.js.erb | 80 ++++++++++++------------- app/assets/javascripts/places.js.erb | 90 ++++++++++++++-------------- 2 files changed, 85 insertions(+), 85 deletions(-) diff --git a/app/assets/javascripts/crops.js.erb b/app/assets/javascripts/crops.js.erb index bd11cebeb..fd9f2242e 100644 --- a/app/assets/javascripts/crops.js.erb +++ b/app/assets/javascripts/crops.js.erb @@ -1,49 +1,49 @@ -// if (document.getElementById("cropmap") !== null) { -// mapbox_map_id = "<%= Growstuff::Application.config.mapbox_map_id %>"; -// mapbox_base_url = "https://c.tiles.mapbox.com/v3/" + mapbox_map_id + "/{z}/{x}/{y}.png"; +if (document.getElementById("cropmap") !== null) { + mapbox_map_id = "<%= Rails.env == 'test' ? 0 : Growstuff::Application.config.mapbox_map_id %>"; + mapbox_base_url = "https://c.tiles.mapbox.com/v3/" + mapbox_map_id + "/{z}/{x}/{y}.png"; -// L.Icon.Default.imagePath = '/assets' + L.Icon.Default.imagePath = '/assets' -// cropmap = L.map('cropmap').setView([0.0, -0.0], 2); -// showCropMap(cropmap); -// } + cropmap = L.map('cropmap').setView([0.0, -0.0], 2); + showCropMap(cropmap); +} -// function showCropMap(cropmap) { +function showCropMap(cropmap) { -// L.tileLayer(mapbox_base_url, { -// attribution: 'Map data © OpenStreetMap contributors under ODbL | Map imagery © Mapbox', -// maxZoom: 18 -// }).addTo(cropmap); -// markers = new L.MarkerClusterGroup({showCoverageOnHover: false, maxClusterRadius: 20 }); + L.tileLayer(mapbox_base_url, { + attribution: 'Map data © OpenStreetMap contributors under ODbL | Map imagery © Mapbox', + maxZoom: 18 + }).addTo(cropmap); + markers = new L.MarkerClusterGroup({showCoverageOnHover: false, maxClusterRadius: 20 }); -// things_to_map = location.pathname + '.json'; -// $.getJSON(things_to_map, function(crop) { -// $.each(crop.plantings, function(i, planting) { -// owner = planting.owner; -// if (owner.latitude && owner.longitude) { -// marker = new L.Marker(new L.LatLng(owner.latitude, owner.longitude)); + things_to_map = location.pathname + '.json'; + $.getJSON(things_to_map, function(crop) { + $.each(crop.plantings, function(i, planting) { + owner = planting.owner; + if (owner.latitude && owner.longitude) { + marker = new L.Marker(new L.LatLng(owner.latitude, owner.longitude)); -// planting_url = "/plantings/" + planting.id; -// planting_link = "" + owner.login_name + "'s " + crop.name + ""; + planting_url = "/plantings/" + planting.id; + planting_link = "" + owner.login_name + "'s " + crop.name + ""; -// where = "

    " + owner.location + "

    "; + where = "

    " + owner.location + "

    "; -// details = "

    "; -// if (planting.quantity) { -// details = details + "Quantity: " + planting.quantity + "
    "; -// } -// if (planting.planted_from) { -// details = details + "Planted from: " + planting.planted_from + "
    "; -// } -// if (planting.sunniness) { -// details = details + "Planted in: " + planting.sunniness+ "
    "; -// } -// details = details + "

    "; -// marker.bindPopup(planting_link + where + details).openPopup(); -// markers.addLayer(marker); -// } -// }); -// }); + details = "

    "; + if (planting.quantity) { + details = details + "Quantity: " + planting.quantity + "
    "; + } + if (planting.planted_from) { + details = details + "Planted from: " + planting.planted_from + "
    "; + } + if (planting.sunniness) { + details = details + "Planted in: " + planting.sunniness+ "
    "; + } + details = details + "

    "; + marker.bindPopup(planting_link + where + details).openPopup(); + markers.addLayer(marker); + } + }); + }); -// cropmap.addLayer(markers); -// } + cropmap.addLayer(markers); +} diff --git a/app/assets/javascripts/places.js.erb b/app/assets/javascripts/places.js.erb index d4767e594..2dba78340 100644 --- a/app/assets/javascripts/places.js.erb +++ b/app/assets/javascripts/places.js.erb @@ -1,50 +1,50 @@ -// if (document.getElementById("placesmap") !== null) { -// places_base_path = "/places"; -// mapbox_map_id = "<%= Growstuff::Application.config.mapbox_map_id %>"; -// mapbox_base_url = "https://c.tiles.mapbox.com/v3/" + mapbox_map_id + "/{z}/{x}/{y}.png"; -// nominatim_base_url = 'http://nominatim.openstreetmap.org/search/'; -// nominatim_user_agent_email = "<%= Growstuff::Application.config.user_agent_email %>"; +if (document.getElementById("placesmap") !== null) { + places_base_path = "/places"; + mapbox_map_id = "<%= Rails.env == 'test' ? 0 : Growstuff::Application.config.mapbox_map_id %>"; + mapbox_base_url = "https://c.tiles.mapbox.com/v3/" + mapbox_map_id + "/{z}/{x}/{y}.png"; + nominatim_base_url = 'http://nominatim.openstreetmap.org/search/'; + nominatim_user_agent_email = "<%= Rails.env == 'test' ? 0 : Growstuff::Application.config.user_agent_email %>"; -// L.Icon.Default.imagePath = '/assets' + L.Icon.Default.imagePath = '/assets' -// if (location.pathname === places_base_path) { //places index page -// placesmap = L.map('placesmap').setView([0.0, -0.0], 2); -// showMap(placesmap); -// } else { // specific place page -// place = location.pathname.replace(places_base_path + "/", ''); -// nominatim_query_url = nominatim_base_url + place; -// nominatim_options = { -// format: "json", -// callback: "placeholder", -// limit: 1, -// email: nominatim_user_agent_email -// }; -// $.getJSON(nominatim_query_url, nominatim_options, function(data) { -// placesmap = L.map('placesmap').setView([data[0].lat, data[0].lon], 5); -// showMap(placesmap); -// }) -// } -// } + if (location.pathname === places_base_path) { //places index page + placesmap = L.map('placesmap').setView([0.0, -0.0], 2); + showMap(placesmap); + } else { // specific place page + place = location.pathname.replace(places_base_path + "/", ''); + nominatim_query_url = nominatim_base_url + place; + nominatim_options = { + format: "json", + callback: "placeholder", + limit: 1, + email: nominatim_user_agent_email + }; + $.getJSON(nominatim_query_url, nominatim_options, function(data) { + placesmap = L.map('placesmap').setView([data[0].lat, data[0].lon], 5); + showMap(placesmap); + }) + } +} -// function showMap(placesmap) { -// L.tileLayer(mapbox_base_url, { -// attribution: 'Map data © OpenStreetMap contributors under ODbL | Map imagery © Mapbox', -// maxZoom: 18 -// }).addTo(placesmap); -// markers = new L.MarkerClusterGroup({showCoverageOnHover: false, maxClusterRadius: 20 }); +function showMap(placesmap) { + L.tileLayer(mapbox_base_url, { + attribution: 'Map data © OpenStreetMap contributors under ODbL | Map imagery © Mapbox', + maxZoom: 18 + }).addTo(placesmap); + markers = new L.MarkerClusterGroup({showCoverageOnHover: false, maxClusterRadius: 20 }); -// things_to_map = location.pathname + '.json'; -// $.getJSON(things_to_map, function(members) { -// $.each(members, function(i, m) { -// if (m.latitude && m.longitude) { -// marker = new L.Marker(new L.LatLng(m.latitude, m.longitude)); -// link = "

    " + m.login_name + "

    "; -// where = "

    " + m.location + "

    "; -// marker.bindPopup(link + where).openPopup(); -// markers.addLayer(marker); -// } -// }); -// }); + things_to_map = location.pathname + '.json'; + $.getJSON(things_to_map, function(members) { + $.each(members, function(i, m) { + if (m.latitude && m.longitude) { + marker = new L.Marker(new L.LatLng(m.latitude, m.longitude)); + link = "

    " + m.login_name + "

    "; + where = "

    " + m.location + "

    "; + marker.bindPopup(link + where).openPopup(); + markers.addLayer(marker); + } + }); + }); -// placesmap.addLayer(markers); -// } + placesmap.addLayer(markers); +} From 9d6e787883d0d64da23c47ee5a599c19737a3954 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Mon, 28 Jul 2014 00:14:06 +1000 Subject: [PATCH 055/288] set up database_cleaner so js specs will use the same database connection as regular specs --- Gemfile | 1 + spec/support/database_cleaner.rb | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 spec/support/database_cleaner.rb diff --git a/Gemfile b/Gemfile index e2ee6ad8a..05d2295ab 100644 --- a/Gemfile +++ b/Gemfile @@ -124,6 +124,7 @@ group :development, :test do gem 'haml-rails' # HTML templating language gem 'rspec-rails', '~> 2.12.1' # unit testing framework gem 'webrat' # provides HTML matchers for view tests + gem 'database_cleaner', '~> 1.3.0' gem 'capybara', '~> 2.4.1' # for feature tests gem 'selenium-webdriver', '~> 2.42.0' # for testing in the browser gem 'factory_girl_rails', '~> 4.0' # for creating test data diff --git a/spec/support/database_cleaner.rb b/spec/support/database_cleaner.rb new file mode 100644 index 000000000..b3e908378 --- /dev/null +++ b/spec/support/database_cleaner.rb @@ -0,0 +1,23 @@ +RSpec.configure do |config| + + config.before(:suite) do + DatabaseCleaner.clean_with(:truncation) + end + + config.before(:each) do + DatabaseCleaner.strategy = :transaction + end + + config.before(:each, :js => true) do + DatabaseCleaner.strategy = :truncation + end + + config.before(:each) do + DatabaseCleaner.start + end + + config.after(:each) do + DatabaseCleaner.clean + end + +end \ No newline at end of file From a707d9a2ea955d0e718eb8ea8b37d393de202db5 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Mon, 28 Jul 2014 00:15:31 +1000 Subject: [PATCH 056/288] write feature spec that tests crop suggest functionality in new planting form --- Gemfile.lock | 2 ++ app/assets/javascripts/plantings.js.coffee | 2 ++ spec/features/planting_a_crop_spec.rb | 15 +++++---------- spec/spec_helper.rb | 2 +- spec/support/feature_helpers.rb | 9 ++++----- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5d744da38..70a252dc1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -107,6 +107,7 @@ GEM csv_shaper (1.0.0) activesupport (>= 3.0.0) dalli (2.7.0) + database_cleaner (1.3.0) debug_inspector (0.0.2) debugger (1.6.6) columnize (>= 0.3.1) @@ -327,6 +328,7 @@ DEPENDENCIES coveralls csv_shaper dalli + database_cleaner (~> 1.3.0) debugger devise (~> 3.0.0) factory_girl_rails (~> 4.0) diff --git a/app/assets/javascripts/plantings.js.coffee b/app/assets/javascripts/plantings.js.coffee index 4d858f66b..96318c04e 100644 --- a/app/assets/javascripts/plantings.js.coffee +++ b/app/assets/javascripts/plantings.js.coffee @@ -17,6 +17,8 @@ jQuery -> el.val( ui.item.name ) $( '#planting_crop_id' ).val( ui.item.id ) false + change: ( event, ui ) -> + console.log( event, ui ) .data( 'uiAutocomplete' )._renderItem = ( ul, item ) -> $( '
  • ' ) .data( 'item.autocomplete', item ) diff --git a/spec/features/planting_a_crop_spec.rb b/spec/features/planting_a_crop_spec.rb index 7f38a3eda..94e01c29a 100644 --- a/spec/features/planting_a_crop_spec.rb +++ b/spec/features/planting_a_crop_spec.rb @@ -11,17 +11,12 @@ feature "Planting a crop", :js => true do scenario "Typing in the crop name displays suggestions" do within "form#new_planting" do - # fill_autocomplete 'crop', with: 'm' - fill_in 'crop', :with => "m" - - binding.pry - - sleep 1000 - - page.execute_script %Q{ $('#crop').trigger('focus') } - page.execute_script %Q{ $('#crop').trigger('keydown') } - expect(page).to have_content("maize") + fill_autocomplete "crop", with: "m" end + + expect(page).to have_content("maize") + select_from_autocomplete("maize") + expect(page).to have_selector("input#planting_crop_id[value='#{maize.id}']", :visible => false) end end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b2af2ab6e..4e9bf8273 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -33,7 +33,7 @@ RSpec.configure do |config| # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false # instead of true. - config.use_transactional_fixtures = true + config.use_transactional_fixtures = false # If true, the base class of anonymous controllers will be inferred # automatically. This will be the default behavior in future versions of diff --git a/spec/support/feature_helpers.rb b/spec/support/feature_helpers.rb index 16d7abdec..97be826c1 100644 --- a/spec/support/feature_helpers.rb +++ b/spec/support/feature_helpers.rb @@ -3,14 +3,13 @@ module FeatureHelpers def fill_autocomplete(field, options = {}) fill_in field, :with => options[:with] - sleep 3 - page.execute_script %Q{ $('##{field}').trigger('focus') } page.execute_script %Q{ $('##{field}').trigger('keydown') } - # selector = %Q{ul.ui-autocomplete li.ui-menu-item a:contains("#{options[:select]}")} + end - # page.should have_selector('ul.ui-autocomplete li.ui-menu-item a') - # page.execute_script %Q{ $('#{selector}').trigger('mouseenter').click() } + def select_from_autocomplete(selection) + selector = %Q{ul.ui-autocomplete li.ui-menu-item a:contains("#{selection}")} + page.execute_script %Q{ $('#{selector}').trigger('mouseenter').click() } end From 012f37f36632e4882fcbff494e361ced7d30b35f Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Tue, 29 Jul 2014 08:42:35 +1000 Subject: [PATCH 057/288] set autoFocus to true for jquery autocomplete --- app/assets/javascripts/plantings.js.coffee | 3 +-- app/models/planting.rb | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/plantings.js.coffee b/app/assets/javascripts/plantings.js.coffee index 96318c04e..8e272dc99 100644 --- a/app/assets/javascripts/plantings.js.coffee +++ b/app/assets/javascripts/plantings.js.coffee @@ -17,8 +17,7 @@ jQuery -> el.val( ui.item.name ) $( '#planting_crop_id' ).val( ui.item.id ) false - change: ( event, ui ) -> - console.log( event, ui ) + autoFocus: true .data( 'uiAutocomplete' )._renderItem = ( ul, item ) -> $( '
  • ' ) .data( 'item.autocomplete', item ) diff --git a/app/models/planting.rb b/app/models/planting.rb index 57c95eb24..97bf96552 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -23,6 +23,8 @@ class Planting < ActiveRecord::Base default_scope order("created_at desc") + validates :crop_id, :presence => true + validates :quantity, :numericality => { :only_integer => true }, :allow_nil => true From 094d9c509aec2102e4b8b2ea3e93670922e6fd1c Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Tue, 29 Jul 2014 10:14:20 +1000 Subject: [PATCH 058/288] write feature spec for create planting functionality --- app/assets/javascripts/plantings.js.coffee | 1 + app/views/plantings/_form.html.haml | 14 ++++----- spec/features/planting_a_crop_spec.rb | 32 ++++++++++++++++++-- spec/support/feature_helpers.rb | 10 ++---- spec/views/plantings/_form.html.haml_spec.rb | 5 --- spec/views/plantings/new.html.haml_spec.rb | 5 --- 6 files changed, 39 insertions(+), 28 deletions(-) diff --git a/app/assets/javascripts/plantings.js.coffee b/app/assets/javascripts/plantings.js.coffee index 8e272dc99..e5b559bb7 100644 --- a/app/assets/javascripts/plantings.js.coffee +++ b/app/assets/javascripts/plantings.js.coffee @@ -12,6 +12,7 @@ jQuery -> source: Routes.crops_search_path(), focus: ( event, ui ) -> el.val( ui.item.name ) + $( '#planting_crop_id' ).val( ui.item.id ) false select: ( event, ui ) -> el.val( ui.item.name ) diff --git a/app/views/plantings/_form.html.haml b/app/views/plantings/_form.html.haml index 19c680a02..5da8bff30 100644 --- a/app/views/plantings/_form.html.haml +++ b/app/views/plantings/_form.html.haml @@ -7,7 +7,7 @@ %li= msg .control-group - = f.label 'What did you plant?', :class => 'control-label' + = f.label :crop, 'What did you plant?', :class => 'control-label' .controls = text_field_tag(:crop, @planting.crop.nil? ? "" : @planting.crop.name) = f.hidden_field(:crop_id) @@ -15,28 +15,28 @@ Can't find what you're looking for? = link_to "Request new crops.", Growstuff::Application.config.new_crops_request_link .control-group - = f.label 'Where did you plant it?', :class => 'control-label' + = f.label :garden_id, 'Where did you plant it?', :class => 'control-label' .controls = collection_select(:planting, :garden_id, Garden.active.where(:owner_id => current_member), :id, :name, :selected => @planting.garden_id || @garden.id) %span.help-inline = link_to "Add a garden.", new_garden_path .control-group - = f.label 'When?', :class => 'control-label' + = f.label :planted_at, 'When?', :class => 'control-label' .controls= f.text_field :planted_at, :value => @planting.planted_at ? @planting.planted_at.to_s(:ymd) : '', :class => 'add-datepicker' .control-group - = f.label 'How many?', :class => 'control-label' + = f.label :quantity, 'How many?', :class => 'control-label' .controls = f.number_field :quantity, :class => 'input-small' .control-group - = f.label 'Planted from:', :class => 'control-label' + = f.label :planted_from, 'Planted from:', :class => 'control-label' .controls = f.select(:planted_from, Planting::PLANTED_FROM_VALUES, {:include_blank => true}) .control-group - = f.label 'Sun or shade?', :class => 'control-label' + = f.label :sunniness, 'Sun or shade?', :class => 'control-label' .controls = f.select(:sunniness, Planting::SUNNINESS_VALUES, {:include_blank => true}) .control-group - = f.label 'Tell us more about it', :class => 'control-label' + = f.label :description, 'Tell us more about it', :class => 'control-label' .controls= f.text_area :description, :rows => 6 .form-actions diff --git a/spec/features/planting_a_crop_spec.rb b/spec/features/planting_a_crop_spec.rb index 94e01c29a..d5a03122b 100644 --- a/spec/features/planting_a_crop_spec.rb +++ b/spec/features/planting_a_crop_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' feature "Planting a crop", :js => true do let(:member) { FactoryGirl.create(:member) } let!(:maize) { FactoryGirl.create(:maize) } + let!(:popcorn) { FactoryGirl.create(:popcorn) } + let!(:pear) { FactoryGirl.create(:pear) } background do login_as(member) @@ -11,12 +13,36 @@ feature "Planting a crop", :js => true do scenario "Typing in the crop name displays suggestions" do within "form#new_planting" do - fill_autocomplete "crop", with: "m" + fill_autocomplete "crop", :with => "p" end + expect(page).to have_content("pear") + expect(page).to have_content("popcorn") + + within "form#new_planting" do + fill_autocomplete "crop", :with => "pe" + end + + expect(page).to have_content("pear") + expect(page).to_not have_content("popcorn") + expect(page).to have_selector("input#planting_crop_id[value='#{pear.id}']", :visible => false) + end + + scenario "Creating a new planting", :js => true do + within "form#new_planting" do + fill_autocomplete "crop", :with => "m" + end expect(page).to have_content("maize") - select_from_autocomplete("maize") - expect(page).to have_selector("input#planting_crop_id[value='#{maize.id}']", :visible => false) + within "form#new_planting" do + fill_in "When", :with => "2014-06-15" + fill_in "How many?", :with => 42 + select "cutting", :from => "Planted from:" + select "semi-shade", :from => "Sun or shade?" + fill_in "Tell us more about it", :with => "It's rad." + click_button "Save" + end + + expect(page).to have_content "Planting was successfully created" end end \ No newline at end of file diff --git a/spec/support/feature_helpers.rb b/spec/support/feature_helpers.rb index 97be826c1..465efae67 100644 --- a/spec/support/feature_helpers.rb +++ b/spec/support/feature_helpers.rb @@ -3,16 +3,10 @@ module FeatureHelpers def fill_autocomplete(field, options = {}) fill_in field, :with => options[:with] - page.execute_script %Q{ $('##{field}').trigger('focus') } - page.execute_script %Q{ $('##{field}').trigger('keydown') } + page.execute_script %Q{ $('##{field}').trigger('focus'); } + page.execute_script %Q{ $('##{field}').trigger('keydown'); } end - def select_from_autocomplete(selection) - selector = %Q{ul.ui-autocomplete li.ui-menu-item a:contains("#{selection}")} - page.execute_script %Q{ $('#{selector}').trigger('mouseenter').click() } - end - - end RSpec.configure do |config| diff --git a/spec/views/plantings/_form.html.haml_spec.rb b/spec/views/plantings/_form.html.haml_spec.rb index 8cd227cf1..69da79c35 100644 --- a/spec/views/plantings/_form.html.haml_spec.rb +++ b/spec/views/plantings/_form.html.haml_spec.rb @@ -21,10 +21,5 @@ describe "plantings/_form" do assert_select 'input#planting_planted_at[type=text][value=2013-03-01]' end - context "logged in" do - it "orders crops alphabetically" do - rendered.should =~ /#{@lowercase.name}.*#{@uppercase.name}/m - end - end end diff --git a/spec/views/plantings/new.html.haml_spec.rb b/spec/views/plantings/new.html.haml_spec.rb index cd9a4d4eb..61e81eb20 100644 --- a/spec/views/plantings/new.html.haml_spec.rb +++ b/spec/views/plantings/new.html.haml_spec.rb @@ -44,11 +44,6 @@ describe "plantings/new" do assert_select "a[href=#{Growstuff::Application.config.new_crops_request_link}]", :text => "Request new crops." end - # it "selects a crop given in a param" do - # assert_select "select#planting_crop_id", - # :html => /option value="#{@crop2.id}" selected="selected"/ - # end - it "selects a garden given in a param" do assert_select "select#planting_garden_id", :html => /option value="#{@garden_z.id}" selected="selected"/ From b4fdf970565eaf4451aba262c1a80f73983743ee Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Thu, 31 Jul 2014 14:48:35 +1000 Subject: [PATCH 059/288] switch to poltergeist-phantomjs from selenium for js webdriver --- Gemfile | 2 +- Gemfile.lock | 19 ++++++++----------- app/assets/javascripts/plantings.js.coffee | 11 ++++++----- spec/spec_helper.rb | 2 ++ 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Gemfile b/Gemfile index 05d2295ab..c45a5bab5 100644 --- a/Gemfile +++ b/Gemfile @@ -126,7 +126,7 @@ group :development, :test do gem 'webrat' # provides HTML matchers for view tests gem 'database_cleaner', '~> 1.3.0' gem 'capybara', '~> 2.4.1' # for feature tests - gem 'selenium-webdriver', '~> 2.42.0' # for testing in the browser + gem 'poltergeist', '~> 1.5.1' # for headless JS testing gem 'factory_girl_rails', '~> 4.0' # for creating test data gem 'coveralls', require: false # coverage analysis diff --git a/Gemfile.lock b/Gemfile.lock index 669d8f4d3..fd5b4d8fc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -79,9 +79,8 @@ GEM rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (~> 2.0) - childprocess (0.5.3) - ffi (~> 1.0, >= 1.0.11) chunky_png (1.3.1) + cliver (0.3.2) coderay (1.1.0) coffee-rails (3.2.2) coffee-script (>= 2.2.0) @@ -129,7 +128,6 @@ GEM factory_girl_rails (4.4.1) factory_girl (~> 4.4.0) railties (>= 3.0.0) - ffi (1.9.3) figaro (0.7.0) bundler (~> 1.0) rails (>= 3, < 5) @@ -207,6 +205,11 @@ GEM omniauth-oauth (~> 1.0) orm_adapter (0.5.0) pg (0.17.1) + poltergeist (1.5.1) + capybara (~> 2.1) + cliver (~> 0.3.1) + multi_json (~> 1.0) + websocket-driver (>= 0.2.0) polyglot (0.3.5) pry (0.10.0) coderay (~> 1.1.0) @@ -258,17 +261,11 @@ GEM rspec-core (~> 2.12.0) rspec-expectations (~> 2.12.0) rspec-mocks (~> 2.12.0) - rubyzip (1.1.6) sass (3.2.19) sass-rails (3.2.6) railties (~> 3.2.0) sass (>= 3.1.10) tilt (~> 1.3) - selenium-webdriver (2.42.0) - childprocess (>= 0.5.0) - multi_json (~> 1.0) - rubyzip (~> 1.0) - websocket (~> 1.0.4) simplecov (0.9.0) docile (~> 1.1.0) multi_json @@ -307,7 +304,7 @@ GEM nokogiri (>= 1.2.0) rack (>= 1.0) rack-test (>= 0.5.3) - websocket (1.0.7) + websocket-driver (0.3.4) will_paginate (3.0.7) xpath (2.0.0) nokogiri (~> 1.3) @@ -358,6 +355,7 @@ DEPENDENCIES omniauth-flickr (>= 0.0.15) omniauth-twitter pg + poltergeist (~> 1.5.1) pry (~> 0.10.0) rack (~> 1.4.5) rails (= 3.2.13) @@ -365,7 +363,6 @@ DEPENDENCIES rake (>= 10.0.0) rspec-rails (~> 2.12.1) sass-rails (~> 3.2.3) - selenium-webdriver (~> 2.42.0) therubyracer (~> 0.12) twitter-bootstrap-rails! uglifier (>= 1.0.3) diff --git a/app/assets/javascripts/plantings.js.coffee b/app/assets/javascripts/plantings.js.coffee index e5b559bb7..0bbf50e8d 100644 --- a/app/assets/javascripts/plantings.js.coffee +++ b/app/assets/javascripts/plantings.js.coffee @@ -19,9 +19,10 @@ jQuery -> $( '#planting_crop_id' ).val( ui.item.id ) false autoFocus: true - .data( 'uiAutocomplete' )._renderItem = ( ul, item ) -> - $( '
  • ' ) - .data( 'item.autocomplete', item ) - .append( "#{item.name}" ) - .appendTo( ul ) + if el.data( 'uiAutocomplete' ) + el.data( 'uiAutocomplete' )._renderItem = ( ul, item ) -> + $( '
  • ' ) + .data( 'item.autocomplete', item ) + .append( "#{item.name}" ) + .appendTo( ul ) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4e9bf8273..e80cc144e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -6,6 +6,8 @@ require 'rspec/autorun' require 'coveralls' require 'simplecov' require 'capybara' +require 'capybara/poltergeist' +Capybara.javascript_driver = :poltergeist include Warden::Test::Helpers From a49880d4da9b06f7ce14b54906bfcc3f896dae40 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Fri, 1 Aug 2014 19:19:27 +1000 Subject: [PATCH 060/288] make crop_suggest.js more abstract so it works for other resources, not just planting --- app/assets/javascripts/crop_suggest.js.coffee | 21 ++++++++++++++++++ app/assets/javascripts/plantings.js.coffee | 22 ------------------- app/views/harvests/_form.html.haml | 3 ++- app/views/plantings/_form.html.haml | 2 +- 4 files changed, 24 insertions(+), 24 deletions(-) create mode 100644 app/assets/javascripts/crop_suggest.js.coffee diff --git a/app/assets/javascripts/crop_suggest.js.coffee b/app/assets/javascripts/crop_suggest.js.coffee new file mode 100644 index 000000000..93802fc7c --- /dev/null +++ b/app/assets/javascripts/crop_suggest.js.coffee @@ -0,0 +1,21 @@ +jQuery -> + if el = $( '.crop-suggest' ) + el.autocomplete + minLength: 1, + source: Routes.crops_search_path(), + focus: ( event, ui ) -> + el.val( ui.item.name ) + $( "[id$='_crop_id']" ).val( ui.item.id ) + false + select: ( event, ui ) -> + el.val( ui.item.name ) + $( "[id$='_crop_id']" ).val( ui.item.id ) + false + autoFocus: true + if el.data( 'uiAutocomplete' ) + el.data( 'uiAutocomplete' )._renderItem = ( ul, item ) -> + $( '
  • ' ) + .data( 'item.autocomplete', item ) + .append( "#{item.name}" ) + .appendTo( ul ) + diff --git a/app/assets/javascripts/plantings.js.coffee b/app/assets/javascripts/plantings.js.coffee index 0bbf50e8d..ab5b73462 100644 --- a/app/assets/javascripts/plantings.js.coffee +++ b/app/assets/javascripts/plantings.js.coffee @@ -4,25 +4,3 @@ jQuery -> $('.add-datepicker').datepicker('format' : 'yyyy-mm-dd') - -jQuery -> - if el = $( '#crop' ) - el.autocomplete - minLength: 1, - source: Routes.crops_search_path(), - focus: ( event, ui ) -> - el.val( ui.item.name ) - $( '#planting_crop_id' ).val( ui.item.id ) - false - select: ( event, ui ) -> - el.val( ui.item.name ) - $( '#planting_crop_id' ).val( ui.item.id ) - false - autoFocus: true - if el.data( 'uiAutocomplete' ) - el.data( 'uiAutocomplete' )._renderItem = ( ul, item ) -> - $( '
  • ' ) - .data( 'item.autocomplete', item ) - .append( "#{item.name}" ) - .appendTo( ul ) - diff --git a/app/views/harvests/_form.html.haml b/app/views/harvests/_form.html.haml index 181d0af54..097d27caf 100644 --- a/app/views/harvests/_form.html.haml +++ b/app/views/harvests/_form.html.haml @@ -9,7 +9,8 @@ .control-group = f.label 'What did you harvest?', :class => 'control-label' .controls - = collection_select(:harvest, :crop_id, Crop.all, :id, :name, :selected => @harvest.crop_id || @crop.id) + = text_field_tag(:crop, @harvest.crop.nil? ? "" : @harvest.crop.name, :class => 'crop-suggest') + = f.hidden_field(:crop_id) = collection_select(:harvest, :plant_part_id, PlantPart.all, :id, :name, :selected => @harvest.plant_part_id) %span.help-block Can't find what you're looking for? diff --git a/app/views/plantings/_form.html.haml b/app/views/plantings/_form.html.haml index 5da8bff30..b7f58b2c7 100644 --- a/app/views/plantings/_form.html.haml +++ b/app/views/plantings/_form.html.haml @@ -9,7 +9,7 @@ .control-group = f.label :crop, 'What did you plant?', :class => 'control-label' .controls - = text_field_tag(:crop, @planting.crop.nil? ? "" : @planting.crop.name) + = text_field_tag(:crop, @planting.crop.nil? ? "" : @planting.crop.name, :class => 'crop-suggest') = f.hidden_field(:crop_id) %span.help-inline Can't find what you're looking for? From 63673112a756d8410c01bd6d96f7a2741ed6828e Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Fri, 1 Aug 2014 19:26:33 +1000 Subject: [PATCH 061/288] repair failing view tests after implementing crop-suggest on harvest --- spec/views/harvests/edit.html.haml_spec.rb | 3 ++- spec/views/harvests/new.html.haml_spec.rb | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/views/harvests/edit.html.haml_spec.rb b/spec/views/harvests/edit.html.haml_spec.rb index b2e4bc665..e47d3ad4a 100644 --- a/spec/views/harvests/edit.html.haml_spec.rb +++ b/spec/views/harvests/edit.html.haml_spec.rb @@ -8,7 +8,8 @@ describe "harvests/edit" do 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#crop", :class => "ui-autocomplete-input" + assert_select "input#harvest_crop_id", :name => "harvest[crop_id]" assert_select "select#harvest_plant_part_id", :name => "harvest[plant_part_id]" assert_select "input#harvest_quantity", :name => "harvest[quantity]" assert_select "input#harvest_weight_quantity", :name => "harvest[quantity]" diff --git a/spec/views/harvests/new.html.haml_spec.rb b/spec/views/harvests/new.html.haml_spec.rb index 3db5bb804..81de2671a 100644 --- a/spec/views/harvests/new.html.haml_spec.rb +++ b/spec/views/harvests/new.html.haml_spec.rb @@ -8,7 +8,8 @@ describe "harvests/new" do 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#crop", :class => "ui-autocomplete-input" + assert_select "input#harvest_crop_id", :name => "harvest[crop_id]" assert_select "select#harvest_plant_part_id", :name => "harvest[plant_part_id]" # some browsers interpret without a step as "integer" assert_select "input#harvest_quantity[step=any]", :name => "harvest[quantity]" From 8352807d5a2c22adf7ac759d9021ccb58a967b2a Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Fri, 1 Aug 2014 19:27:51 +1000 Subject: [PATCH 062/288] add presence validation for crop association on harvest --- app/models/harvest.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/harvest.rb b/app/models/harvest.rb index 3afaeb170..e17d46950 100644 --- a/app/models/harvest.rb +++ b/app/models/harvest.rb @@ -11,6 +11,8 @@ class Harvest < ActiveRecord::Base default_scope order('created_at DESC') + validates :crop, :presence => true + validates :quantity, :numericality => { :only_integer => false }, :allow_nil => true From 1bed42876cdfc150940b53f756c6120c0db9f60f Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Fri, 1 Aug 2014 19:30:30 +1000 Subject: [PATCH 063/288] write instructions for using crop_suggest.js in a view --- app/assets/javascripts/crop_suggest.js.coffee | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/assets/javascripts/crop_suggest.js.coffee b/app/assets/javascripts/crop_suggest.js.coffee index 93802fc7c..461b9b3e8 100644 --- a/app/assets/javascripts/crop_suggest.js.coffee +++ b/app/assets/javascripts/crop_suggest.js.coffee @@ -1,3 +1,10 @@ +# Uses JQuery's autocomplete to suggest a crop name in lieu of a +# preposterously long select dropdown. To implement add code to +# the view like this: +# +# = text_field_tag(:crop, @resource.crop.nil? ? "" : @resource.crop.name, :class => 'crop-suggest') +# = f.hidden_field(:crop_id) + jQuery -> if el = $( '.crop-suggest' ) el.autocomplete From 69162cf2287a12b62bd26db3fe4874f26f4b947e Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Fri, 1 Aug 2014 19:38:47 +1000 Subject: [PATCH 064/288] write feature specs for harvesting a crop --- app/views/harvests/_form.html.haml | 10 +++--- spec/features/harvesting_a_crop_spec.rb | 47 +++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 spec/features/harvesting_a_crop_spec.rb diff --git a/app/views/harvests/_form.html.haml b/app/views/harvests/_form.html.haml index 097d27caf..bd9fb94d6 100644 --- a/app/views/harvests/_form.html.haml +++ b/app/views/harvests/_form.html.haml @@ -7,7 +7,7 @@ %li= msg .control-group - = f.label 'What did you harvest?', :class => 'control-label' + = f.label :crop, 'What did you harvest?', :class => 'control-label' .controls = text_field_tag(:crop, @harvest.crop.nil? ? "" : @harvest.crop.name, :class => 'crop-suggest') = f.hidden_field(:crop_id) @@ -17,11 +17,11 @@ = link_to "Request new crops.", Growstuff::Application.config.new_crops_request_link .control-group - = f.label 'When?', :class => 'control-label' + = f.label :harvested_at, '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' + = f.label :quantity, 'How many?', :class => 'control-label' .controls -# Some browsers (eg Firefox for Android) assume "number" means -# "integer" unless you specify step="any": @@ -30,13 +30,13 @@ = f.select(:unit, Harvest::UNITS_VALUES, {:include_blank => false}, :class => 'input-medium') .control-group - = f.label 'Weighing:', :class => 'control-label' + = f.label :weight_quantity, 'Weighing?', :class => 'control-label' .controls = f.number_field :weight_quantity, :class => 'input-small', :step => 'any' = f.select(:weight_unit, Harvest::WEIGHT_UNITS_VALUES, {:include_blank => false}, :class => 'input-medium') in total .control-group - = f.label 'Notes', :class => 'control-label' + = f.label :description, 'Notes', :class => 'control-label' .controls= f.text_area :description, :rows => 6 .form-actions diff --git a/spec/features/harvesting_a_crop_spec.rb b/spec/features/harvesting_a_crop_spec.rb new file mode 100644 index 000000000..b0e296dde --- /dev/null +++ b/spec/features/harvesting_a_crop_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +feature "Harvesting a crop", :js => true do + let(:member) { FactoryGirl.create(:member) } + let!(:maize) { FactoryGirl.create(:maize) } + let!(:popcorn) { FactoryGirl.create(:popcorn) } + let!(:pear) { FactoryGirl.create(:pear) } + + background do + login_as(member) + visit '/harvests/new' + end + + scenario "Typing in the crop name displays suggestions" do + within "form#new_harvest" do + fill_autocomplete "crop", :with => "p" + end + + expect(page).to have_content("pear") + expect(page).to have_content("popcorn") + + within "form#new_harvest" do + fill_autocomplete "crop", :with => "pe" + end + + expect(page).to have_content("pear") + expect(page).to_not have_content("popcorn") + expect(page).to have_selector("input#harvest_crop_id[value='#{pear.id}']", :visible => false) + end + + scenario "Creating a new harvest", :js => true do + within "form#new_harvest" do + fill_autocomplete "crop", :with => "m" + end + expect(page).to have_content("maize") + within "form#new_harvest" do + fill_in "When?", :with => "2014-06-15" + fill_in "How many?", :with => 42 + fill_in "Weighing?", :with => 42 + fill_in "Notes", :with => "It's killer." + click_button "Save" + end + + expect(page).to have_content "Harvest was successfully created" + end + +end \ No newline at end of file From c282b3b2087438ac306d35c38d8e86e9b72480a3 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Fri, 1 Aug 2014 20:18:28 +1000 Subject: [PATCH 065/288] abstract crop suggest autocomplete tests into a shared example --- spec/features/harvesting_a_crop_spec.rb | 19 +-------------- spec/features/planting_a_crop_spec.rb | 19 +-------------- .../shared_examples/crop_suggest_spec.rb | 24 +++++++++++++++++++ spec/spec_helper.rb | 1 + 4 files changed, 27 insertions(+), 36 deletions(-) create mode 100644 spec/features/shared_examples/crop_suggest_spec.rb diff --git a/spec/features/harvesting_a_crop_spec.rb b/spec/features/harvesting_a_crop_spec.rb index b0e296dde..1da1771d0 100644 --- a/spec/features/harvesting_a_crop_spec.rb +++ b/spec/features/harvesting_a_crop_spec.rb @@ -3,30 +3,13 @@ require 'spec_helper' feature "Harvesting a crop", :js => true do let(:member) { FactoryGirl.create(:member) } let!(:maize) { FactoryGirl.create(:maize) } - let!(:popcorn) { FactoryGirl.create(:popcorn) } - let!(:pear) { FactoryGirl.create(:pear) } background do login_as(member) visit '/harvests/new' end - scenario "Typing in the crop name displays suggestions" do - within "form#new_harvest" do - fill_autocomplete "crop", :with => "p" - end - - expect(page).to have_content("pear") - expect(page).to have_content("popcorn") - - within "form#new_harvest" do - fill_autocomplete "crop", :with => "pe" - end - - expect(page).to have_content("pear") - expect(page).to_not have_content("popcorn") - expect(page).to have_selector("input#harvest_crop_id[value='#{pear.id}']", :visible => false) - end + it_behaves_like "crop suggest", "harvest" scenario "Creating a new harvest", :js => true do within "form#new_harvest" do diff --git a/spec/features/planting_a_crop_spec.rb b/spec/features/planting_a_crop_spec.rb index d5a03122b..d24a85dd3 100644 --- a/spec/features/planting_a_crop_spec.rb +++ b/spec/features/planting_a_crop_spec.rb @@ -3,30 +3,13 @@ require 'spec_helper' feature "Planting a crop", :js => true do let(:member) { FactoryGirl.create(:member) } let!(:maize) { FactoryGirl.create(:maize) } - let!(:popcorn) { FactoryGirl.create(:popcorn) } - let!(:pear) { FactoryGirl.create(:pear) } background do login_as(member) visit '/plantings/new' end - scenario "Typing in the crop name displays suggestions" do - within "form#new_planting" do - fill_autocomplete "crop", :with => "p" - end - - expect(page).to have_content("pear") - expect(page).to have_content("popcorn") - - within "form#new_planting" do - fill_autocomplete "crop", :with => "pe" - end - - expect(page).to have_content("pear") - expect(page).to_not have_content("popcorn") - expect(page).to have_selector("input#planting_crop_id[value='#{pear.id}']", :visible => false) - end + it_behaves_like "crop suggest", "planting" scenario "Creating a new planting", :js => true do within "form#new_planting" do diff --git a/spec/features/shared_examples/crop_suggest_spec.rb b/spec/features/shared_examples/crop_suggest_spec.rb new file mode 100644 index 000000000..33290460f --- /dev/null +++ b/spec/features/shared_examples/crop_suggest_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +shared_examples "crop suggest" do |resource| + let!(:popcorn) { FactoryGirl.create(:popcorn) } + let!(:pear) { FactoryGirl.create(:pear) } + + scenario "Typing in the crop name displays suggestions" do + within "form#new_#{resource}" do + fill_autocomplete "crop", :with => "p" + end + + expect(page).to have_content("pear") + expect(page).to have_content("popcorn") + + within "form#new_#{resource}" do + fill_autocomplete "crop", :with => "pe" + end + + expect(page).to have_content("pear") + expect(page).to_not have_content("popcorn") + expect(page).to have_selector("input##{resource}_crop_id[value='#{pear.id}']", :visible => false) + end + +end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e80cc144e..8ba405425 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -19,6 +19,7 @@ Coveralls.wear!('rails') # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} +Dir[Rails.root.join("spec/features/shared_examples/**/*.rb")].each {|f| require f} RSpec.configure do |config| # ## Mock Framework From 957fa3417d627f6211786ed4dd597e10c4d2e3a7 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Fri, 1 Aug 2014 20:27:26 +1000 Subject: [PATCH 066/288] remove exclusion of assets group gems --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7746d903e..805ca76f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ --- language: ruby env: GROWSTUFF_SITE_NAME="Growstuff (travis)" -bundler_args: --without development assets production staging +bundler_args: --without development production staging rvm: - 2.1.1 before_script: From 7aef1264ce7f3d4c27e6045602fb73a08ec377b3 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sat, 2 Aug 2014 20:44:19 +1000 Subject: [PATCH 067/288] fix edge case where user types in full name of crop and doesn't use autocomplete suggestions --- app/assets/javascripts/crop_suggest.js.coffee | 5 ++++- spec/features/harvesting_a_crop_spec.rb | 6 ++---- spec/features/planting_a_crop_spec.rb | 6 ++---- spec/features/shared_examples/crop_suggest_spec.rb | 3 +++ spec/support/feature_helpers.rb | 8 +++++++- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/crop_suggest.js.coffee b/app/assets/javascripts/crop_suggest.js.coffee index 461b9b3e8..43710d626 100644 --- a/app/assets/javascripts/crop_suggest.js.coffee +++ b/app/assets/javascripts/crop_suggest.js.coffee @@ -18,7 +18,10 @@ jQuery -> el.val( ui.item.name ) $( "[id$='_crop_id']" ).val( ui.item.id ) false - autoFocus: true + response: ( event, ui ) -> + for item in ui.content + if item.name == el.val() + $( "[id$='_crop_id']" ).val( item.id ) if el.data( 'uiAutocomplete' ) el.data( 'uiAutocomplete' )._renderItem = ( ul, item ) -> $( '
  • ' ) diff --git a/spec/features/harvesting_a_crop_spec.rb b/spec/features/harvesting_a_crop_spec.rb index 1da1771d0..33366187d 100644 --- a/spec/features/harvesting_a_crop_spec.rb +++ b/spec/features/harvesting_a_crop_spec.rb @@ -12,10 +12,8 @@ feature "Harvesting a crop", :js => true do it_behaves_like "crop suggest", "harvest" scenario "Creating a new harvest", :js => true do - within "form#new_harvest" do - fill_autocomplete "crop", :with => "m" - end - expect(page).to have_content("maize") + fill_autocomplete "crop", :with => "m" + select_from_autocomplete "maize" within "form#new_harvest" do fill_in "When?", :with => "2014-06-15" fill_in "How many?", :with => 42 diff --git a/spec/features/planting_a_crop_spec.rb b/spec/features/planting_a_crop_spec.rb index d24a85dd3..095ef9c13 100644 --- a/spec/features/planting_a_crop_spec.rb +++ b/spec/features/planting_a_crop_spec.rb @@ -12,10 +12,8 @@ feature "Planting a crop", :js => true do it_behaves_like "crop suggest", "planting" scenario "Creating a new planting", :js => true do - within "form#new_planting" do - fill_autocomplete "crop", :with => "m" - end - expect(page).to have_content("maize") + fill_autocomplete "crop", :with => "m" + select_from_autocomplete "maize" within "form#new_planting" do fill_in "When", :with => "2014-06-15" fill_in "How many?", :with => 42 diff --git a/spec/features/shared_examples/crop_suggest_spec.rb b/spec/features/shared_examples/crop_suggest_spec.rb index 33290460f..2c19c4742 100644 --- a/spec/features/shared_examples/crop_suggest_spec.rb +++ b/spec/features/shared_examples/crop_suggest_spec.rb @@ -18,6 +18,9 @@ shared_examples "crop suggest" do |resource| expect(page).to have_content("pear") expect(page).to_not have_content("popcorn") + + select_from_autocomplete("pear") + expect(page).to have_selector("input##{resource}_crop_id[value='#{pear.id}']", :visible => false) end diff --git a/spec/support/feature_helpers.rb b/spec/support/feature_helpers.rb index 465efae67..81b781d18 100644 --- a/spec/support/feature_helpers.rb +++ b/spec/support/feature_helpers.rb @@ -1,12 +1,18 @@ module FeatureHelpers - def fill_autocomplete(field, options = {}) + def fill_autocomplete(field, options={}) fill_in field, :with => options[:with] page.execute_script %Q{ $('##{field}').trigger('focus'); } page.execute_script %Q{ $('##{field}').trigger('keydown'); } end + def select_from_autocomplete(select) + page.should have_selector('ul.ui-autocomplete li.ui-menu-item a') + selector = %Q{ul.ui-autocomplete li.ui-menu-item a:contains("#{select}")} + page.execute_script %Q{ $('#{selector}').mouseenter().click() } + end + end RSpec.configure do |config| From e0d81f5693b085c052ca541bd7c345d9df58811a Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sat, 2 Aug 2014 22:14:10 +1000 Subject: [PATCH 068/288] improve and abstract auto_suggest api --- ...p_suggest.js.coffee => auto_suggest.js.coffee} | 15 ++++++++------- app/controllers/crops_controller.rb | 4 ++-- app/helpers/auto_suggest_helper.rb | 15 +++++++++++++++ app/views/harvests/_form.html.haml | 3 +-- app/views/plantings/_form.html.haml | 3 +-- .../features/shared_examples/crop_suggest_spec.rb | 2 +- 6 files changed, 28 insertions(+), 14 deletions(-) rename app/assets/javascripts/{crop_suggest.js.coffee => auto_suggest.js.coffee} (66%) create mode 100644 app/helpers/auto_suggest_helper.rb diff --git a/app/assets/javascripts/crop_suggest.js.coffee b/app/assets/javascripts/auto_suggest.js.coffee similarity index 66% rename from app/assets/javascripts/crop_suggest.js.coffee rename to app/assets/javascripts/auto_suggest.js.coffee index 43710d626..dac189db9 100644 --- a/app/assets/javascripts/crop_suggest.js.coffee +++ b/app/assets/javascripts/auto_suggest.js.coffee @@ -2,26 +2,27 @@ # preposterously long select dropdown. To implement add code to # the view like this: # -# = text_field_tag(:crop, @resource.crop.nil? ? "" : @resource.crop.name, :class => 'crop-suggest') -# = f.hidden_field(:crop_id) +# = auto_suggest @resource, :auto_suggest_source jQuery -> - if el = $( '.crop-suggest' ) + if el = $( '.auto-suggest' ) + el.autocomplete minLength: 1, - source: Routes.crops_search_path(), + source: el.attr( "data-source-url" ), focus: ( event, ui ) -> el.val( ui.item.name ) - $( "[id$='_crop_id']" ).val( ui.item.id ) + $( ".auto-suggest-id" ).val( ui.item.id ) false select: ( event, ui ) -> el.val( ui.item.name ) - $( "[id$='_crop_id']" ).val( ui.item.id ) + $( ".auto-suggest-id" ).val( ui.item.id ) false response: ( event, ui ) -> for item in ui.content if item.name == el.val() - $( "[id$='_crop_id']" ).val( item.id ) + $( ".auto-suggest-id" ).val( item.id ) + if el.data( 'uiAutocomplete' ) el.data( 'uiAutocomplete' )._renderItem = ( ul, item ) -> $( '
  • ' ) diff --git a/app/controllers/crops_controller.rb b/app/controllers/crops_controller.rb index 18261d332..f2c399ef8 100644 --- a/app/controllers/crops_controller.rb +++ b/app/controllers/crops_controller.rb @@ -57,11 +57,11 @@ class CropsController < ApplicationController # exclude exact match from partial match list @partial_matches.reject!{ |r| @exact_match && r.eql?(@exact_match) } - @starts_with = Crop.where("name LIKE :prefix", prefix: "#{params[:term]}%") + @fuzzy = Crop.search(params[:term]) respond_to do |format| format.html - format.json { render :json => @starts_with } + format.json { render :json => @fuzzy } end end diff --git a/app/helpers/auto_suggest_helper.rb b/app/helpers/auto_suggest_helper.rb new file mode 100644 index 000000000..dbecd431e --- /dev/null +++ b/app/helpers/auto_suggest_helper.rb @@ -0,0 +1,15 @@ +module AutoSuggestHelper + + def auto_suggest(resource, source) + default = resource.send(source).nil? ? "" : resource.send(source).name + + resource = resource.class.name.downcase + source_path = Rails.application.routes.url_helpers.send("#{source}s_search_path") + + %Q{ + + + }.html_safe + end + +end \ No newline at end of file diff --git a/app/views/harvests/_form.html.haml b/app/views/harvests/_form.html.haml index bd9fb94d6..08046ab9f 100644 --- a/app/views/harvests/_form.html.haml +++ b/app/views/harvests/_form.html.haml @@ -9,8 +9,7 @@ .control-group = f.label :crop, 'What did you harvest?', :class => 'control-label' .controls - = text_field_tag(:crop, @harvest.crop.nil? ? "" : @harvest.crop.name, :class => 'crop-suggest') - = f.hidden_field(:crop_id) + = auto_suggest @harvest, :crop = collection_select(:harvest, :plant_part_id, PlantPart.all, :id, :name, :selected => @harvest.plant_part_id) %span.help-block Can't find what you're looking for? diff --git a/app/views/plantings/_form.html.haml b/app/views/plantings/_form.html.haml index b7f58b2c7..e4fe6884b 100644 --- a/app/views/plantings/_form.html.haml +++ b/app/views/plantings/_form.html.haml @@ -9,8 +9,7 @@ .control-group = f.label :crop, 'What did you plant?', :class => 'control-label' .controls - = text_field_tag(:crop, @planting.crop.nil? ? "" : @planting.crop.name, :class => 'crop-suggest') - = f.hidden_field(:crop_id) + = auto_suggest @planting, :crop %span.help-inline Can't find what you're looking for? = link_to "Request new crops.", Growstuff::Application.config.new_crops_request_link diff --git a/spec/features/shared_examples/crop_suggest_spec.rb b/spec/features/shared_examples/crop_suggest_spec.rb index 2c19c4742..3cd50a928 100644 --- a/spec/features/shared_examples/crop_suggest_spec.rb +++ b/spec/features/shared_examples/crop_suggest_spec.rb @@ -13,7 +13,7 @@ shared_examples "crop suggest" do |resource| expect(page).to have_content("popcorn") within "form#new_#{resource}" do - fill_autocomplete "crop", :with => "pe" + fill_autocomplete "crop", :with => "pear" end expect(page).to have_content("pear") From f3c3d73ef1b92e1352ec973c4cad27048946d4be Mon Sep 17 00:00:00 2001 From: Mackenzie Morgan Date: Sun, 10 Aug 2014 19:49:10 -0400 Subject: [PATCH 069/288] updating test for harvest units to list the valid units and adding a task to correct misspelled pint harvests already in the database --- lib/tasks/growstuff.rake | 10 ++++++++++ script/deploy-tasks.sh | 3 +++ spec/models/harvest_spec.rb | 4 ++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/tasks/growstuff.rake b/lib/tasks/growstuff.rake index f4f7a0003..34739217b 100644 --- a/lib/tasks/growstuff.rake +++ b/lib/tasks/growstuff.rake @@ -246,6 +246,16 @@ namespace :growstuff do end end + desc "August 2014: fix ping to pint in database" + task :ping_to_pint => :environment do + Harvest.find_each do |h| + if h.unit == "ping" + h.unit = "pint" + h.save + end + end + end + end # end oneoff section end diff --git a/script/deploy-tasks.sh b/script/deploy-tasks.sh index c471b566e..f861d27ed 100755 --- a/script/deploy-tasks.sh +++ b/script/deploy-tasks.sh @@ -12,3 +12,6 @@ echo "2013-07-18 - zero crop plantings_count" rake growstuff:oneoff:zero_plantings_count + +echo "2014-08-10 - replace ping with pint in db" +rake growstuff:oneoff:ping_to_pint diff --git a/spec/models/harvest_spec.rb b/spec/models/harvest_spec.rb index 621a0d026..ca0180ca1 100644 --- a/spec/models/harvest_spec.rb +++ b/spec/models/harvest_spec.rb @@ -45,8 +45,8 @@ describe Harvest do end context 'units' do - Harvest::UNITS_VALUES.values.push(nil, '').each do |s| - it "#{s} should be a valid unit" do + it 'all valid units should work' do + ['individual','bunch','sprig','handful','litre','pint','quart','bucket','basket','bushel', nil, ''].each do |s| @harvest = FactoryGirl.build(:harvest, :unit => s) @harvest.should be_valid end From a684efcf2a6d01247cdb10a9adb6b2bfc99dcea1 Mon Sep 17 00:00:00 2001 From: Skud Date: Mon, 11 Aug 2014 13:02:08 +1000 Subject: [PATCH 070/288] b3ified some of the forms --- app/views/comments/_form.html.haml | 5 +-- app/views/comments/new.html.haml | 2 -- app/views/harvests/_form.html.haml | 49 ++++++++++++++++------------- app/views/plantings/_form.html.haml | 16 +++++----- app/views/posts/_form.html.haml | 22 +++++++------ app/views/seeds/_form.html.haml | 49 ++++++++++++++++------------- 6 files changed, 77 insertions(+), 66 deletions(-) diff --git a/app/views/comments/_form.html.haml b/app/views/comments/_form.html.haml index bbb96d1a7..5d13ec6b5 100644 --- a/app/views/comments/_form.html.haml +++ b/app/views/comments/_form.html.haml @@ -1,4 +1,4 @@ -= form_for @comment do |f| += form_for(@comment, :html => {:class => "form-horizontal", :role => "form"}) do |f| - if @comment.errors.any? #error_explanation %h2= "#{pluralize(@comment.errors.count, "error")} prohibited this comment from being saved:" @@ -6,7 +6,8 @@ - @comment.errors.full_messages.each do |msg| %li= msg - .field + .form-group + = f.label :body, "Your comment:" = f.text_area :body, :rows => 6, :class => 'form-control' %span.help-block = render :partial => "shared/markdown_help" diff --git a/app/views/comments/new.html.haml b/app/views/comments/new.html.haml index c01ee755f..ca74fb99b 100644 --- a/app/views/comments/new.html.haml +++ b/app/views/comments/new.html.haml @@ -4,6 +4,4 @@ = render :partial => "posts/comments", :locals => {:post => @post || @comment.post} -%p Your comment: - = render 'form' diff --git a/app/views/harvests/_form.html.haml b/app/views/harvests/_form.html.haml index f2a0b183c..3f712feb6 100644 --- a/app/views/harvests/_form.html.haml +++ b/app/views/harvests/_form.html.haml @@ -1,4 +1,4 @@ -= form_for(@harvest, :html => {:class => "form-horizontal"}) do |f| += form_for(@harvest, :html => {:class => "form-horizontal", :role => :form}) do |f| - if @harvest.errors.any? #error_explanation %h2= "#{pluralize(@harvest.errors.count, "error")} prohibited this harvest from being saved:" @@ -7,37 +7,42 @@ %li= msg .form-group - = f.label 'What did you harvest?', :class => 'control-label' - .controls - = collection_select(:harvest, :crop_id, Crop.all, :id, :name, :selected => @harvest.crop_id || @crop.id) - = collection_select(:harvest, :plant_part_id, PlantPart.all, :id, :name, :selected => @harvest.plant_part_id) - %span.help-block - Can't find what you're looking for? - = link_to "Request new crops.", Growstuff::Application.config.new_crops_request_link + = f.label 'What did you harvest?', :class => 'control-label col-md-2' + .col-md-4 + = collection_select(:harvest, :crop_id, Crop.all, :id, :name, { :selected => @harvest.crop_id || @crop.id }, { :class => 'form-control' }) + .col-md-4 + = collection_select(:harvest, :plant_part_id, PlantPart.all, :id, :name, { :selected => @harvest.plant_part_id }, { :class => 'form-control' }) + %span.help-block.col-md-8 + Can't find what you're looking for? + = link_to "Request new crops.", Growstuff::Application.config.new_crops_request_link .form-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' + = f.label 'When?', :class => 'control-label col-md-2' + .col-md-2 + = f.text_field :harvested_at, :value => @harvest.harvested_at ? @harvest.harvested_at.to_s(:ymd) : '', :class => 'add-datepicker form-control' .form-group - = f.label 'How many?', :class => 'control-label' - .controls + = f.label 'How many?', :class => 'control-label col-md-2' + .col-md-2 -# Some browsers (eg Firefox for Android) assume "number" means -# "integer" unless you specify step="any": -# http://blog.isotoma.com/2012/03/html5-input-typenumber-and-decimalsfloats-in-chrome/ - = f.number_field :quantity, :class => 'input-small', :step => 'any' - = f.select(:unit, Harvest::UNITS_VALUES, {:include_blank => false}, :class => 'input-medium') + = f.number_field :quantity, :class => 'input-small', :step => 'any', :class => 'form-control' + .col-md-2 + = f.select(:unit, Harvest::UNITS_VALUES, {:include_blank => false}, :class => 'input-medium form-control') .form-group - = f.label 'Weighing:', :class => 'control-label' - .controls - = f.number_field :weight_quantity, :class => 'input-small', :step => 'any' - = f.select(:weight_unit, Harvest::WEIGHT_UNITS_VALUES, {:include_blank => false}, :class => 'input-medium') - in total + = f.label 'Weighing (in total):', :class => 'control-label col-md-2' + .col-md-2 + = f.number_field :weight_quantity, :class => 'input-small', :step => 'any', :class => 'form-control' + .col-md-2 + = f.select(:weight_unit, Harvest::WEIGHT_UNITS_VALUES, {:include_blank => false}, :class => 'form-control') .form-group - = f.label 'Notes', :class => 'control-label' - .controls= f.text_area :description, :rows => 6 + = f.label 'Notes', :class => 'control-label col-md-2' + .col-md-8 + = f.text_area :description, :rows => 6, :class => 'form-control' - .form-actions + .form-group + .form-actions.col-md-offset-2.col-md-8 = f.submit 'Save', :class => 'btn btn-primary' diff --git a/app/views/plantings/_form.html.haml b/app/views/plantings/_form.html.haml index 2decb9d68..7d3f10a8f 100644 --- a/app/views/plantings/_form.html.haml +++ b/app/views/plantings/_form.html.haml @@ -8,35 +8,35 @@ .form-group = f.label 'What did you plant?', :class => 'control-label col-md-2' - .col-md-4 + .col-md-8 = collection_select(:planting, :crop_id, Crop.all, :id, :name, {:selected => @planting.crop_id || @crop.id}, {:class => 'form-control'}) %span.help-inline Can't find what you're looking for? = link_to "Request new crops.", Growstuff::Application.config.new_crops_request_link .form-group = f.label 'Where did you plant it?', :class => 'control-label col-md-2' - .col-md-4 + .col-md-8 = collection_select(:planting, :garden_id, Garden.active.where(:owner_id => current_member), :id, :name, {:selected => @planting.garden_id || @garden.id}, {:class => 'form-control'}) %span.help-inline = link_to "Add a garden.", new_garden_path .form-group = f.label 'When?', :class => 'control-label col-md-2' - .col-md-3= f.text_field :planted_at, :value => @planting.planted_at ? @planting.planted_at.to_s(:ymd) : '', :class => 'add-datepicker form-control' + .col-md-2= f.text_field :planted_at, :value => @planting.planted_at ? @planting.planted_at.to_s(:ymd) : '', :class => 'add-datepicker form-control' .form-group = f.label 'How many?', :class => 'control-label col-md-2' .col-md-2 = f.number_field :quantity, :class => 'form-control' .form-group = f.label 'Planted from:', :class => 'control-label col-md-2' - .col-md-4 + .col-md-8 = f.select(:planted_from, Planting::PLANTED_FROM_VALUES, {:include_blank => true}, :class => 'form-control') .form-group = f.label 'Sun or shade?', :class => 'control-label col-md-2' - .col-md-4 + .col-md-8 = f.select(:sunniness, Planting::SUNNINESS_VALUES, {:include_blank => true}, :class => 'form-control') .form-group = f.label 'Tell us more about it', :class => 'control-label col-md-2' - .col-md-4= f.text_area :description, :rows => 6, :class => 'form-control' + .col-md-8= f.text_area :description, :rows => 6, :class => 'form-control' .form-group - .form-actions - = f.submit 'Save', :class => 'btn btn-primary col-md-offset-2' + .form-actions.col-md-offset-2.col-md-8 + = f.submit 'Save', :class => 'btn btn-primary' diff --git a/app/views/posts/_form.html.haml b/app/views/posts/_form.html.haml index d08a577a5..3ae4e5172 100644 --- a/app/views/posts/_form.html.haml +++ b/app/views/posts/_form.html.haml @@ -1,4 +1,4 @@ -= form_for @post do |f| += form_for(@post, :html => {:role => "form"}) do |f| - if @post.errors.any? #error_explanation %h2= "#{pluralize(@post.errors.count, "error")} prohibited this post from being saved:" @@ -6,16 +6,18 @@ - @post.errors.full_messages.each do |msg| %li= msg + .form-group + = label_tag :post, "Subject", :class => 'control-label' + = f.text_field :subject, :class => 'form-control' - = label_tag :post, "Subject" - = f.text_field :subject, :class => 'form-control' - - if @post.forum || @forum - = label_tag :body, "What's up?" - - else - = label_tag :body, "What's going on in your food garden?" - = f.text_area :body, :rows => 12, :class => 'form-control' - %span.help-block - = render :partial => "shared/markdown_help" + .form-group + - if @post.forum || @forum + = label_tag :body, "What's up?", :class => 'control-label' + - else + = label_tag :body, "What's going on in your food garden?" + = f.text_area :body, :rows => 12, :class => 'form-control' + %span.help-block + = render :partial => "shared/markdown_help" - if @post.forum || @forum - forum = @post.forum || @forum diff --git a/app/views/seeds/_form.html.haml b/app/views/seeds/_form.html.haml index 5e9270800..1b327cdcc 100644 --- a/app/views/seeds/_form.html.haml +++ b/app/views/seeds/_form.html.haml @@ -1,4 +1,4 @@ -= form_for(@seed, :html => {:class => "form-horizontal"}) do |f| += form_for(@seed, :html => {:class => "form-horizontal", :role => "form"}) do |f| - if @seed.errors.any? #error_explanation %h2= "#{pluralize(@seed.errors.count, "error")} prohibited this seed from being saved:" @@ -7,23 +7,33 @@ %li= msg .form-group - = f.label 'Crop:', :class => 'control-label' - .controls= collection_select(:seed, :crop_id, Crop.all, :id, :name, :selected => @seed.crop_id || @crop.id) + = f.label 'Crop:', :class => 'control-label col-md-2' + .col-md-8 + = collection_select(:seed, :crop_id, Crop.all, :id, :name, { :selected => @seed.crop_id || @crop.id }, { :class => 'form-control' }) .form-group - = f.label 'Quantity:', :class => 'control-label' - .controls - = f.number_field :quantity, :class => 'input-small' + = f.label 'Quantity:', :class => 'control-label col-md-2' + .col-md-2 + = f.number_field :quantity, :class => 'form-control' .form-group - = f.label 'Plant before:', :class => 'control-label' - .controls= f.text_field :plant_before, :value => @seed.plant_before ? @seed.plant_before.to_s(:ymd) : '', :class => 'add-datepicker' + = f.label '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 'Description:', :class => 'control-label' - .controls= f.text_area :description, :rows => 6 + = f.label 'Description:', :class => 'control-label col-md-2' + .col-md-8 + = f.text_area :description, :rows => 6, :class => 'form-control' .form-group - = f.label 'Will trade:', :class => 'control-label' - .controls - = f.select(:tradable_to, - options_for_select(Seed::TRADABLE_TO_VALUES, :selected => @seed.tradable_to || 'nowhere')) + .col-md-offset-2.col-md-8 + %span.help-block + Are you interested in trading or swapping seeds with other + #{ENV['GROWSTUFF_SITE_NAME']} members? If you + list your seeds as available for trade, other members can + contact you to request seeds. You can list any conditions or + other information in the description, above. + .form-group + = f.label 'Will trade:', :class => 'control-label col-md-2' + .col-md-8 + = f.select(:tradable_to, Seed::TRADABLE_TO_VALUES, {}, :class => 'form-control') %span.help_inline - if current_member.location.blank? Don't forget to @@ -34,11 +44,6 @@ =succeed "." do = link_to current_member.location, place_path(current_member.location) =link_to "Change your location.", edit_member_registration_path - %span.help-block - Are you interested in trading or swapping seeds with other - #{ENV['GROWSTUFF_SITE_NAME']} members? If you - list your seeds as available for trade, other members can - contact you to request seeds. You can list any conditions or - other information in the description, above. - .form-actions - = f.submit 'Save', :class => 'btn btn-primary' + .form-group + .form-actions.col-md-offset-2.col-md-8 + = f.submit 'Save', :class => 'btn btn-primary' From 561b7c45ac01f5cbee0d84f5309ff0ae8b06393e Mon Sep 17 00:00:00 2001 From: Skud Date: Mon, 11 Aug 2014 14:42:00 +1000 Subject: [PATCH 071/288] b3ify and improve design of edit profile page --- Gemfile | 2 +- Gemfile.lock | 14 +-- app/controllers/registrations_controller.rb | 27 ++-- .../devise/registrations/_edit_apps.html.haml | 26 ++++ .../registrations/_edit_email.html.haml | 31 +++++ .../registrations/_edit_password.html.haml | 22 ++++ .../registrations/_edit_profile.html.haml | 30 +++++ app/views/devise/registrations/edit.html.haml | 117 ++++-------------- config/initializers/devise.rb | 2 + spec/views/devise/registrations/edit_spec.rb | 17 --- 10 files changed, 160 insertions(+), 128 deletions(-) create mode 100644 app/views/devise/registrations/_edit_apps.html.haml create mode 100644 app/views/devise/registrations/_edit_email.html.haml create mode 100644 app/views/devise/registrations/_edit_password.html.haml create mode 100644 app/views/devise/registrations/_edit_profile.html.haml diff --git a/Gemfile b/Gemfile index 3c41ee688..20fd19aab 100644 --- a/Gemfile +++ b/Gemfile @@ -93,7 +93,7 @@ gem 'bluecloth' gem 'will_paginate', '~> 3.0' # user signup/login/etc -gem 'devise', '~> 3.0.0' +gem 'devise', '~> 3.2.0' # nicely formatted URLs gem 'friendly_id', '~> 4.0.10' diff --git a/Gemfile.lock b/Gemfile.lock index 2921a230f..5fa52738a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -50,8 +50,6 @@ GEM addressable (2.3.6) arel (3.0.3) bcrypt (3.1.7) - bcrypt-ruby (3.1.5) - bcrypt (>= 3.1.3) better_errors (1.1.0) coderay (>= 1.0.0) erubis (>= 2.6.6) @@ -95,10 +93,11 @@ GEM debugger-ruby_core_source (~> 1.3.2) debugger-linecache (1.2.0) debugger-ruby_core_source (1.3.2) - devise (3.0.4) - bcrypt-ruby (~> 3.0) + devise (3.2.4) + bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 3.2.6, < 5) + thread_safe (~> 0.1) warden (~> 1.2.3) diff-lcs (1.1.3) docile (1.1.3) @@ -162,7 +161,7 @@ GEM memcachier (0.0.2) mime-types (1.25.1) mini_portile (0.5.3) - multi_json (1.9.3) + multi_json (1.10.1) multi_xml (0.5.5) newrelic_rpm (3.8.0.218) nokogiri (1.6.1) @@ -210,7 +209,7 @@ GEM rdoc (~> 3.4) thor (>= 0.14.6, < 2.0) raindrops (0.13.0) - rake (10.3.1) + rake (10.3.2) rdoc (3.12.2) json (~> 1.4) ref (1.0.5) @@ -248,6 +247,7 @@ GEM libv8 (~> 3.16.14.0) ref thor (0.19.1) + thread_safe (0.3.3) tilt (1.4.1) tins (1.1.0) treetop (1.4.15) @@ -287,7 +287,7 @@ DEPENDENCIES csv_shaper dalli debugger - devise (~> 3.0.0) + devise (~> 3.2.0) factory_girl_rails (~> 4.0) figaro flickraw diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 815457201..cbb300496 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -10,26 +10,37 @@ class RegistrationsController < Devise::RegistrationsController # we need this subclassed method so that Devise doesn't force people to # change their password every time they want to edit their settings. +# we also check that they give their current password to chang their password. # Code copied from # https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-edit-their-account-without-providing-a-password def update - # required for settings form to submit when password is left blank - if params[:member][:password].blank? - params[:member].delete("password") - params[:member].delete("password_confirmation") - params[:member].delete("current_password") - end @member = Member.find(current_member.id) - if @member.update_attributes(params[:member]) + successfully_updated = if needs_password?(@member, params) + @member.update_with_password(devise_parameter_sanitizer.sanitize(:account_update)) + else + # remove the virtual current_password attribute + # update_without_password doesn't know how to ignore it + params[:member].delete(:current_password) + @member.update_without_password(devise_parameter_sanitizer.sanitize(:account_update)) + end + + if successfully_updated set_flash_message :notice, :updated - # Sign in the member bypassing validation in case his password changed + # Sign in the member bypassing validation in case their password changed sign_in @member, :bypass => true redirect_to edit_member_registration_path else render "edit" end + end end + +# check if we need the current password to update fields +def needs_password?(member, params) + params[:member][:password].present? || + params[:member][:password_confirmation].present? +end diff --git a/app/views/devise/registrations/_edit_apps.html.haml b/app/views/devise/registrations/_edit_apps.html.haml new file mode 100644 index 000000000..df5d17c88 --- /dev/null +++ b/app/views/devise/registrations/_edit_apps.html.haml @@ -0,0 +1,26 @@ += form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put, :class => 'form-horizontal' }) do |f| + = devise_error_messages! + + .row + .col-md-12 + %p + = image_tag "twitter_32.png", :size => "32x32", :alt => 'Twitter logo' + - if @twitter_auth + You are connected to Twitter as + = succeed "." do + =link_to @twitter_auth.name, "http://twitter.com/#{@twitter_auth.name}" + = link_to "Disconnect", @twitter_auth, :confirm => "Are you sure you want to remove this connection?", :method => :delete, :class => "remove" + - else + =link_to 'Connect to Twitter', '/auth/twitter' + + .row + .col-md-12 + %p + = image_tag "flickr_32.png", :size => "32x32", :alt => 'Flickr logo' + - if @flickr_auth + You are connected to Flickr as + = succeed "." do + =link_to @flickr_auth.name, "http://flickr.com/photos/#{@flickr_auth.uid}" + = link_to "Disconnect", @flickr_auth, :confirm => "Are you sure you want to remove this connection?", :method => :delete, :class => "remove" + - else + =link_to 'Connect to Flickr', '/auth/flickr' diff --git a/app/views/devise/registrations/_edit_email.html.haml b/app/views/devise/registrations/_edit_email.html.haml new file mode 100644 index 000000000..61df13f91 --- /dev/null +++ b/app/views/devise/registrations/_edit_email.html.haml @@ -0,0 +1,31 @@ += form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put, :class => 'form-horizontal' }) do |f| + = devise_error_messages! + + .form-group + = f.label :email, :class => 'control-label col-md-2' + .col-md-8 + = f.email_field :email, :class => 'form-control' + %span.help-block If you change your email address you will have to reconfirm. + + .form-group + .col-md-offset-2.col-md-8 + = f.check_box :show_email + Show email publicly on your profile page + + .form-group + .col-md-offset-2.col-md-8 + = f.check_box :send_notification_email + Receive emailed copies of Inbox notifications. + + .form-group + .col-md-offset-2.col-md-8 + = f.check_box :newsletter + Subscribe to the #{ENV['GROWSTUFF_SITE_NAME']} newsletter + .help-block + = render :partial => 'newsletter_blurb' + + .form-group + .form-actions.col-md-offset-2.col-md-8 + = f.submit "Save", :class => 'btn btn-primary' + + =f.hidden_field(:tos_agreement, :value => true) diff --git a/app/views/devise/registrations/_edit_password.html.haml b/app/views/devise/registrations/_edit_password.html.haml new file mode 100644 index 000000000..c70edc96f --- /dev/null +++ b/app/views/devise/registrations/_edit_password.html.haml @@ -0,0 +1,22 @@ += form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put, :class => 'form-horizontal' }) do |f| + = devise_error_messages! + + .form-group + = f.label :current_password, :class => 'control-label col-md-2' + .col-md-4 + = f.password_field :current_password, :class => 'form-control' + + .form-group + = f.label :password, "New password", :class => 'control-label col-md-2' + .col-md-4 + = f.password_field :password, :autocomplete => "off", :class => 'form-control' + + .form-group + = f.label :password_confirmation, :class => 'control-label col-md-2' + .col-md-4= f.password_field :password_confirmation, :class => 'form-control' + + .form-group + .form-actions.col-md-offset-2.col-md-8 + = f.submit "Save", :class => 'btn btn-primary' + + =f.hidden_field(:tos_agreement, :value => true) diff --git a/app/views/devise/registrations/_edit_profile.html.haml b/app/views/devise/registrations/_edit_profile.html.haml new file mode 100644 index 000000000..fe33c6fdf --- /dev/null +++ b/app/views/devise/registrations/_edit_profile.html.haml @@ -0,0 +1,30 @@ += form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put, :class => 'form-horizontal' }) do |f| + = devise_error_messages! + + .form-group + =f.label :location, 'Your location', :class => 'control-label col-md-2' + .col-md-8 + =f.text_field :location, :autocomplete => "off", :class => 'form-control' + %span.help-block This will be displayed on a map. You can be as detailed or vague as you like. + + .form-group + =f.label :bio, :class => 'control-label col-md-2' + .col-md-8 + =f.text_area :bio, :rows => 6, :class => 'form-control' + + .form-group + %label.control-label.col-md-2 + Profile picture + .col-md-8 + = render :partial => "members/avatar", :locals => { :member => @member } + %p + %br/ + To change your profile picture, visit + = succeed "." do + = link_to 'gravatar.com', "http://gravatar.com/" + + .form-group + .form-actions.col-md-offset-2.col-md-8 + = f.submit "Save", :class => 'btn btn-primary' + + =f.hidden_field(:tos_agreement, :value => true) diff --git a/app/views/devise/registrations/edit.html.haml b/app/views/devise/registrations/edit.html.haml index cb77568f4..85ba205d9 100644 --- a/app/views/devise/registrations/edit.html.haml +++ b/app/views/devise/registrations/edit.html.haml @@ -1,98 +1,25 @@ - content_for :title, "Settings for #{current_member.login_name}" -= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put, :class => 'form-horizontal' }) do |f| - = devise_error_messages! +%ul.nav.nav-tabs{:role => 'tablist'} + %li.active + %a{:href => '#profile', :role => 'tab', 'data-toggle' => 'tab'} + Profile + %li + %a{:href => '#email', :role => 'tab', 'data-toggle' => 'tab'} + Email + %li + %a{:href => '#apps', :role => 'tab', 'data-toggle' => 'tab'} + Apps + %li + %a{:href => '#password', :role => 'tab', 'data-toggle' => 'tab'} + Password - %h2 Email settings - - .form-group - = f.label :email, :class => 'control-label' - .controls - = f.email_field :email - %span.help-inline If you change your email address you will have to reconfirm. - - .form-group - .controls - = f.check_box :show_email - Show email publicly on your profile page - - .form-group - .controls - = f.check_box :send_notification_email - Receive emailed copies of Inbox notifications. - - .form-group - .controls - = f.check_box :newsletter - Subscribe to the #{ENV['GROWSTUFF_SITE_NAME']} newsletter - .help-inline - = render :partial => 'newsletter_blurb' - - %h2 Profile details - .form-group - %label.control-label - Profile picture - .controls - = render :partial => "members/avatar", :locals => { :member => @member } - %p - %br/ - To change your profile picture, visit - = succeed "." do - = link_to 'gravatar.com', "http://gravatar.com/" - - .form-group - =f.label :location, 'Your location', :class => 'control-label' - .controls - =f.text_field :location, :autocomplete => "off" - %span.help-inline This will be displayed on a map. You can be as detailed or vague as you like. - - .form-group - =f.label :bio, :class => 'control-label' - .controls - =f.text_area :bio, :rows => 6, :class => 'form-control' - - %h2 Linked accounts - - .form-group - .controls - %p - = image_tag "twitter_32.png", :size => "32x32", :alt => 'Twitter logo' - - if @twitter_auth - You are connected to Twitter as - = succeed "." do - =link_to @twitter_auth.name, "http://twitter.com/#{@twitter_auth.name}" - = link_to "Disconnect", @twitter_auth, :confirm => "Are you sure you want to remove this connection?", :method => :delete, :class => "remove" - - else - =link_to 'Connect to Twitter', '/auth/twitter' - %p - = image_tag "flickr_32.png", :size => "32x32", :alt => 'Flickr logo' - - if @flickr_auth - You are connected to Flickr as - = succeed "." do - =link_to @flickr_auth.name, "http://flickr.com/photos/#{@flickr_auth.uid}" - = link_to "Disconnect", @flickr_auth, :confirm => "Are you sure you want to remove this connection?", :method => :delete, :class => "remove" - - else - =link_to 'Connect to Flickr', '/auth/flickr' - - %h2 Change password - %p - %span.help-block Leave blank if you don't want to change your password. - - .form-group - = f.label :current_password, :class => 'control-label' - .controls - = f.password_field :current_password - - .form-group - = f.label :password, "New password", :class => 'control-label' - .controls - = f.password_field :password, :autocomplete => "off" - - .form-group - = f.label :password_confirmation, :class => 'control-label' - .controls= f.password_field :password_confirmation - - .form-actions - = f.submit "Save", :class => 'btn btn-primary' - - =f.hidden_field(:tos_agreement, :value => true) +.tab-content + .tab-pane.active#profile + = render :partial => 'edit_profile' + .tab-pane#email + = render :partial => 'edit_email' + .tab-pane#apps + = render :partial => 'edit_apps' + .tab-pane#password + = render :partial => 'edit_password' diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 0e332098c..eb51fab96 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -6,6 +6,8 @@ Devise.setup do |config| # note that it will be overwritten if you use your own mailer class with default "from" parameter. config.mailer_sender = "Growstuff " + config.secret_key = ENV['RAILS_SECRET_TOKEN'] + # Configure the class responsible to send e-mails. # config.mailer = "Devise::Mailer" diff --git a/spec/views/devise/registrations/edit_spec.rb b/spec/views/devise/registrations/edit_spec.rb index cfd18c6de..f9c48ec1a 100644 --- a/spec/views/devise/registrations/edit_spec.rb +++ b/spec/views/devise/registrations/edit_spec.rb @@ -23,10 +23,6 @@ describe 'devise/registrations/edit.html.haml', :type => "view" do render end - it 'has a heading' do - assert_select "h2", "Email settings" - end - it 'has a checkbox for email notifications' do assert_select "input[id=member_send_notification_email][type=checkbox]" end @@ -41,10 +37,6 @@ describe 'devise/registrations/edit.html.haml', :type => "view" do render end - it 'has a heading' do - assert_select "h2", "Profile details" - end - it 'shows show_email checkbox' do assert_select "input[id=member_show_email][type=checkbox]" end @@ -67,10 +59,6 @@ describe 'devise/registrations/edit.html.haml', :type => "view" do end context 'other sites section' do - it 'has a heading' do - render - assert_select "h2", "Linked accounts" - end context 'not connected to twitter' do it 'has a link to connect' do @@ -114,11 +102,6 @@ describe 'devise/registrations/edit.html.haml', :type => "view" do end - it 'should have a password section' do - render - assert_select "h2", "Change password" - end - end end From f02f2ae77ee974b82fcbcce334b5a51728a1ff9c Mon Sep 17 00:00:00 2001 From: Skud Date: Mon, 11 Aug 2014 14:49:05 +1000 Subject: [PATCH 072/288] b3ify the signup/signin pages --- app/views/devise/registrations/new.html.haml | 31 +++++++++++--------- app/views/devise/sessions/new.html.haml | 22 ++++++++------ 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml index e9eb59bd5..268fc3e45 100644 --- a/app/views/devise/registrations/new.html.haml +++ b/app/views/devise/registrations/new.html.haml @@ -6,39 +6,42 @@ = devise_error_messages! .form-group - = f.label :login_name, :class => "control-label" - .controls - = f.text_field :login_name + = f.label :login_name, :class => "control-label col-md-2" + .col-md-8 + = f.text_field :login_name, :class => 'form-control' %span.help-inline This is the name that will show on the website. .form-group - = f.label :email, :class => "control-label" - .controls - = f.email_field :email + = f.label :email, :class => "control-label col-md-2" + .col-md-8 + = f.email_field :email, :class => 'form-control' %span.help-inline We'll use this address to contact you (we never spam!) .form-group - = f.label :password, :class => "control-label" - .controls= f.password_field :password + = f.label :password, :class => "control-label col-md-2" + .col-md-8= f.password_field :password, :class => 'form-control' .form-group - = f.label :password_confirmation, :class => "control-label" - .controls= f.password_field :password_confirmation + = f.label :password_confirmation, :class => "control-label col-md-2" + .col-md-8= f.password_field :password_confirmation, :class => 'form-control' .form-group - .controls + .col-md-offset-2.col-md-8 = f.check_box :tos_agreement I agree to the = succeed "." do = link_to 'Terms of Service', url_for(:action => 'tos', :controller => '/policy') .form-group - .controls + .col-md-offset-2.col-md-8 = f.check_box :newsletter, :checked => true Subscribe to the #{ENV['GROWSTUFF_SITE_NAME']} newsletter .help-inline = render :partial => 'newsletter_blurb' - .form-actions + .form-group + .form-actions.col-md-offset-2.col-md-8 = f.submit "Sign up", :class => 'btn btn-primary' -= render "devise/shared/links" + .form-group + .col-md-offset-2.col-md-8 + = render "devise/shared/links" diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index bb3b0b8ff..51d7b6845 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -4,21 +4,25 @@ = devise_error_messages! .form-group - = f.label :login, :class => "control-label" - .controls= f.text_field :login + = f.label :login, :class => "control-label col-md-2" + .col-md-8 + = f.text_field :login, :class => 'form-control' .form-group - = f.label :password, :class => "control-label" - .controls= f.password_field :password + = f.label :password, :class => "control-label col-md-2" + .col-md-8 + = f.password_field :password, :class => 'form-control' - if devise_mapping.rememberable? .form-group - .controls + .col-md-8.col-md-offset-2 = f.check_box :remember_me Remember me - .form-actions - = f.submit "Sign in", :class => 'btn btn-primary' + .form-group + .form-actions.col-md-8.col-md-offset-2 + = f.submit "Sign in", :class => 'btn btn-primary' - -= render "devise/shared/links" + .form-group + .col-md-8.col-md-offset-2 + = render "devise/shared/links" From 86699073199d09105ed3e2f9b50c97e675200011 Mon Sep 17 00:00:00 2001 From: Skud Date: Mon, 11 Aug 2014 14:55:43 +1000 Subject: [PATCH 073/288] add secret key to travis config (needed for devise update) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7746d903e..4b0fc1c55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ --- language: ruby -env: GROWSTUFF_SITE_NAME="Growstuff (travis)" +env: GROWSTUFF_SITE_NAME="Growstuff (travis)" RAILS_SECRET_KEY='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' bundler_args: --without development assets production staging rvm: - 2.1.1 From 25d19a25d3a4a36b07574f5188e1e83eb0edceaa Mon Sep 17 00:00:00 2001 From: Skud Date: Mon, 11 Aug 2014 15:00:46 +1000 Subject: [PATCH 074/288] err, make that secret token --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4b0fc1c55..825f9e960 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ --- language: ruby -env: GROWSTUFF_SITE_NAME="Growstuff (travis)" RAILS_SECRET_KEY='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' +env: GROWSTUFF_SITE_NAME="Growstuff (travis)" RAILS_SECRET_TOKEN='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' bundler_args: --without development assets production staging rvm: - 2.1.1 From ca41f86d93b9b60c95f1d04f8b0e6ec9cf5ba6a5 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sun, 17 Aug 2014 11:34:00 +1000 Subject: [PATCH 075/288] add validation for case when user input is not in the database --- app/assets/javascripts/auto_suggest.js.coffee | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/auto_suggest.js.coffee b/app/assets/javascripts/auto_suggest.js.coffee index dac189db9..9e76ad718 100644 --- a/app/assets/javascripts/auto_suggest.js.coffee +++ b/app/assets/javascripts/auto_suggest.js.coffee @@ -1,27 +1,34 @@ -# Uses JQuery's autocomplete to suggest a crop name in lieu of a +# Uses JQuery's autocomplete to make a suggestion in lieu of a # preposterously long select dropdown. To implement add code to # the view like this: # # = auto_suggest @resource, :auto_suggest_source +# +# You must also add a search method to the resource's controller. jQuery -> if el = $( '.auto-suggest' ) + id = $( '.auto-suggest-id' ) + submit = $( '[name="commit"]' ) + errorContainer = $( '' ).insertAfter(el) + el.autocomplete minLength: 1, - source: el.attr( "data-source-url" ), + source: el.attr( 'data-source-url' ), focus: ( event, ui ) -> el.val( ui.item.name ) - $( ".auto-suggest-id" ).val( ui.item.id ) + id.val( ui.item.id ) false select: ( event, ui ) -> el.val( ui.item.name ) - $( ".auto-suggest-id" ).val( ui.item.id ) + id.val( ui.item.id ) false response: ( event, ui ) -> + id.val( "" ) for item in ui.content if item.name == el.val() - $( ".auto-suggest-id" ).val( item.id ) + id.val( item.id ) if el.data( 'uiAutocomplete' ) el.data( 'uiAutocomplete' )._renderItem = ( ul, item ) -> @@ -30,3 +37,17 @@ jQuery -> .append( "#{item.name}" ) .appendTo( ul ) + submit.on( 'click', (e) -> + if el.val() != '' && id.val() == '' + e.preventDefault() + errorContainer.prepend( '

    That\'s not in our database.

    ' ) + el.css( 'background-color', '#ffe6e6' ) + $( 'html, body' ).animate({ + scrollTop: ( el.offset().top ) - 200 + }, 200) + ) + + el.focus( -> + el.css( 'background-color', '#fff' ) + errorContainer.find( 'p' ).remove() + ) From 793eecfcb7539e584a2161746abd1cdbdb2e9e39 Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 22 Aug 2014 10:25:40 +1000 Subject: [PATCH 076/288] Fixed typo --- app/controllers/registrations_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index cbb300496..50eccef0c 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -10,7 +10,7 @@ class RegistrationsController < Devise::RegistrationsController # we need this subclassed method so that Devise doesn't force people to # change their password every time they want to edit their settings. -# we also check that they give their current password to chang their password. +# we also check that they give their current password to change their password. # Code copied from # https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-edit-their-account-without-providing-a-password From ec25f2ab91e20291cfa9237e5b4c2ab269b46190 Mon Sep 17 00:00:00 2001 From: Marlena Compton Date: Sat, 9 Aug 2014 15:01:26 -0700 Subject: [PATCH 077/288] created list of crop wranglers on the crop wranglers page --- Gemfile | 1 + Gemfile.lock | 9 ++++++ .../javascripts/account_types.js.coffee | 3 -- app/controllers/crops_controller.rb | 9 +++++- app/models/role.rb | 4 +++ app/views/crops/wrangle.html.haml | 7 +++++ spec/controllers/crops_controller_spec.rb | 1 + spec/factories/member.rb | 1 + spec/factories/roles.rb | 2 +- spec/features/crop_wranglers_spec.rb | 30 +++++++++++++++++++ spec/models/role_spec.rb | 22 ++++++++++---- spec/views/crops/wrangle.html.haml_spec.rb | 1 + 12 files changed, 79 insertions(+), 11 deletions(-) delete mode 100644 app/assets/javascripts/account_types.js.coffee create mode 100644 spec/features/crop_wranglers_spec.rb diff --git a/Gemfile b/Gemfile index b4ff013a0..302669d18 100644 --- a/Gemfile +++ b/Gemfile @@ -124,4 +124,5 @@ group :development, :test do gem 'webrat' # provides HTML matchers for view tests gem 'factory_girl_rails', '~> 4.0' # for creating test data gem 'coveralls', require: false # coverage analysis + gem 'capybara' # integration tests end diff --git a/Gemfile.lock b/Gemfile.lock index e2bd35b31..6095c5556 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -73,6 +73,12 @@ GEM railties (>= 3.0) builder (3.0.4) cancan (1.6.10) + capybara (2.3.0) + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) chunky_png (1.3.1) coderay (1.1.0) coffee-rails (3.2.2) @@ -277,6 +283,8 @@ GEM rack (>= 1.0) rack-test (>= 0.5.3) will_paginate (3.0.5) + xpath (2.0.0) + nokogiri (~> 1.3) PLATFORMS ruby @@ -290,6 +298,7 @@ DEPENDENCIES bootstrap-datepicker-rails bundler (>= 1.1.5) cancan + capybara coffee-rails (~> 3.2.1) compass-rails (~> 1.0.3) coveralls diff --git a/app/assets/javascripts/account_types.js.coffee b/app/assets/javascripts/account_types.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/account_types.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/controllers/crops_controller.rb b/app/controllers/crops_controller.rb index ae38b2817..7cd37f576 100644 --- a/app/controllers/crops_controller.rb +++ b/app/controllers/crops_controller.rb @@ -31,10 +31,17 @@ class CropsController < ApplicationController end end + def wrangler_index + @crop_wranglers = Role.crop_wranglers + respond_to do |format| + format.html + end + end + # GET /crops/wrangle def wrangle @crops = Crop.recent.paginate(:page => params[:page]) - + @crop_wranglers = Role.crop_wranglers respond_to do |format| format.html end diff --git a/app/models/role.rb b/app/models/role.rb index f05ea0e3a..c959f3bd3 100644 --- a/app/models/role.rb +++ b/app/models/role.rb @@ -3,4 +3,8 @@ class Role < ActiveRecord::Base friendly_id :name, use: :slugged attr_accessible :description, :name, :members, :slug has_and_belongs_to_many :members + + def self.crop_wranglers + Role.where(slug: 'crop-wrangler').try(:first).try(:members) + end end diff --git a/app/views/crops/wrangle.html.haml b/app/views/crops/wrangle.html.haml index 3fb3ae381..b26f29fc9 100644 --- a/app/views/crops/wrangle.html.haml +++ b/app/views/crops/wrangle.html.haml @@ -8,6 +8,13 @@ %li= link_to "Full crop hierarchy", crops_hierarchy_path %li= link_to "Add Crop", new_crop_path +%div.crop_wranglers + %h2 Crop Wranglers: + %ul + - @crop_wranglers.each do |crop_wrangler| + %li.crop_wrangler + = link_to crop_wrangler.login_name, crop_wrangler + %h2 Recently added crops %div.pagination diff --git a/spec/controllers/crops_controller_spec.rb b/spec/controllers/crops_controller_spec.rb index dc2eee23d..997aa71a9 100644 --- a/spec/controllers/crops_controller_spec.rb +++ b/spec/controllers/crops_controller_spec.rb @@ -16,6 +16,7 @@ describe CropsController do get :wrangle response.should be_success response.should render_template("crops/wrangle") + expect(assigns[:crop_wranglers]).to eq(Role.crop_wranglers) end end diff --git a/spec/factories/member.rb b/spec/factories/member.rb index b3d2782a7..bb4523a33 100644 --- a/spec/factories/member.rb +++ b/spec/factories/member.rb @@ -60,6 +60,7 @@ FactoryGirl.define do factory :crop_wrangling_member do roles { [ FactoryGirl.create(:crop_wrangler) ] } + sequence(:login_name) {|n| "wrangler#{n}"} end factory :invalid_member_shortname do diff --git a/spec/factories/roles.rb b/spec/factories/roles.rb index ace5b57b1..e169cf86e 100644 --- a/spec/factories/roles.rb +++ b/spec/factories/roles.rb @@ -10,7 +10,7 @@ FactoryGirl.define do end factory :crop_wrangler do - name "crop wrangler" + name "Crop Wrangler" description "they wrangle crops" end end diff --git a/spec/features/crop_wranglers_spec.rb b/spec/features/crop_wranglers_spec.rb new file mode 100644 index 000000000..bf5957d0b --- /dev/null +++ b/spec/features/crop_wranglers_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +feature "crop wranglers" do + context "signed in user" do + let(:crop_wrangler_role) { FactoryGirl.create :crop_wrangler } + let!(:crop_wranglers) { FactoryGirl.create_list(:crop_wrangling_member, 3, roles: [crop_wrangler_role]) } + let(:user){crop_wranglers.first} + before :each do + visit root_path + click_link 'Sign in' + fill_in 'Login', with: user.login_name + fill_in 'Password', with: user.password + click_button 'Sign in' + page.should have_content user.login_name + end + + scenario "crop wranglers are listed on the crop wrangler page" do + click_link 'Crop Wrangling' + + within '.crop_wranglers' do + expect(page).to have_content 'Crop Wranglers:' + crop_wranglers.each_with_index do |crop_wrangler, index| + link = find(".crop_wrangler:nth-child(#{index + 1}) a") + expect(link.text).to eq(crop_wrangler.login_name) + expect(link['href']).to eq(member_path(crop_wrangler)) + end + end + end + end +end diff --git a/spec/models/role_spec.rb b/spec/models/role_spec.rb index 8250e064d..68001c358 100644 --- a/spec/models/role_spec.rb +++ b/spec/models/role_spec.rb @@ -1,17 +1,27 @@ require 'spec_helper' describe Role do - before(:each) do - @member = FactoryGirl.create(:member) - @role = FactoryGirl.create(:role) - @role.members << @member + let(:member) { FactoryGirl.create(:member) } + + subject do + role = FactoryGirl.create(:role, name: 'Crop Wrangler') + role.members << member + role end it 'has members' do - @role.members.first.should eq @member + subject.members.first.should eq member end it 'has a slug' do - @role.slug.should eq 'moderator' + subject.slug.should eq 'crop-wrangler' + end + + describe '.crop_wranglers' do + let(:crop_wrangler_role) { FactoryGirl.create :crop_wrangler } + let!(:crop_wranglers) { FactoryGirl.create_list(:crop_wrangling_member, 3, roles: [crop_wrangler_role]) } + it 'return the crop wranglers that are members of that role' do + expect(Role.crop_wranglers).to eq(crop_wranglers) + end end end diff --git a/spec/views/crops/wrangle.html.haml_spec.rb b/spec/views/crops/wrangle.html.haml_spec.rb index e12c23d10..a4adde063 100644 --- a/spec/views/crops/wrangle.html.haml_spec.rb +++ b/spec/views/crops/wrangle.html.haml_spec.rb @@ -13,6 +13,7 @@ describe "crops/wrangle" do pager.replace([ @tomato, @maize ]) end assign(:crops, crops) + assign(:crop_wranglers, Role.crop_wranglers) end it 'contains handy links for wranglers' do From 38b92071227773b7fdd9a2c6870c574c4a850b22 Mon Sep 17 00:00:00 2001 From: Marlena Compton Date: Sun, 17 Aug 2014 16:43:45 -0700 Subject: [PATCH 078/288] remove extra method --- app/controllers/crops_controller.rb | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/controllers/crops_controller.rb b/app/controllers/crops_controller.rb index 7cd37f576..6e0137216 100644 --- a/app/controllers/crops_controller.rb +++ b/app/controllers/crops_controller.rb @@ -31,13 +31,6 @@ class CropsController < ApplicationController end end - def wrangler_index - @crop_wranglers = Role.crop_wranglers - respond_to do |format| - format.html - end - end - # GET /crops/wrangle def wrangle @crops = Crop.recent.paginate(:page => params[:page]) From 13e015b441f4b1b71489825daccc3312b34b6e77 Mon Sep 17 00:00:00 2001 From: Marlena Compton Date: Thu, 21 Aug 2014 19:30:01 -0700 Subject: [PATCH 079/288] fixed role factory to not create extra roles when new members --- spec/factories/roles.rb | 2 +- spec/features/crop_wranglers_spec.rb | 3 +-- spec/models/role_spec.rb | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/spec/factories/roles.rb b/spec/factories/roles.rb index e169cf86e..dc4aa3b9c 100644 --- a/spec/factories/roles.rb +++ b/spec/factories/roles.rb @@ -4,6 +4,7 @@ FactoryGirl.define do factory :role do name "Moderator" description "These people moderate the forums" + initialize_with { Role.find_or_create_by_name(name) } factory :admin do name "admin" @@ -14,5 +15,4 @@ FactoryGirl.define do description "they wrangle crops" end end - end diff --git a/spec/features/crop_wranglers_spec.rb b/spec/features/crop_wranglers_spec.rb index bf5957d0b..88dd277b2 100644 --- a/spec/features/crop_wranglers_spec.rb +++ b/spec/features/crop_wranglers_spec.rb @@ -2,8 +2,7 @@ require 'spec_helper' feature "crop wranglers" do context "signed in user" do - let(:crop_wrangler_role) { FactoryGirl.create :crop_wrangler } - let!(:crop_wranglers) { FactoryGirl.create_list(:crop_wrangling_member, 3, roles: [crop_wrangler_role]) } + let!(:crop_wranglers) { FactoryGirl.create_list(:crop_wrangling_member, 3) } let(:user){crop_wranglers.first} before :each do visit root_path diff --git a/spec/models/role_spec.rb b/spec/models/role_spec.rb index 68001c358..b6b5d6426 100644 --- a/spec/models/role_spec.rb +++ b/spec/models/role_spec.rb @@ -18,8 +18,7 @@ describe Role do end describe '.crop_wranglers' do - let(:crop_wrangler_role) { FactoryGirl.create :crop_wrangler } - let!(:crop_wranglers) { FactoryGirl.create_list(:crop_wrangling_member, 3, roles: [crop_wrangler_role]) } + let!(:crop_wranglers) { FactoryGirl.create_list(:crop_wrangling_member, 3) } it 'return the crop wranglers that are members of that role' do expect(Role.crop_wranglers).to eq(crop_wranglers) end From 36199054c048bfd0991c39bec34943f3ccea8396 Mon Sep 17 00:00:00 2001 From: Marlena Compton Date: Thu, 21 Aug 2014 20:06:05 -0700 Subject: [PATCH 080/288] changed method so that you can now call crop_wranglers and admins. meta-programming ftw --- app/models/role.rb | 9 +++++++-- spec/models/role_spec.rb | 7 +++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/models/role.rb b/app/models/role.rb index c959f3bd3..284fdd0d8 100644 --- a/app/models/role.rb +++ b/app/models/role.rb @@ -4,7 +4,12 @@ class Role < ActiveRecord::Base attr_accessible :description, :name, :members, :slug has_and_belongs_to_many :members - def self.crop_wranglers - Role.where(slug: 'crop-wrangler').try(:first).try(:members) + class << self + [:crop_wranglers, :admins].each do |method| + define_method method do + slug = method.to_s.singularize.dasherize + Role.where(slug: slug).try(:first).try(:members) + end + end end end diff --git a/spec/models/role_spec.rb b/spec/models/role_spec.rb index b6b5d6426..7618872c6 100644 --- a/spec/models/role_spec.rb +++ b/spec/models/role_spec.rb @@ -23,4 +23,11 @@ describe Role do expect(Role.crop_wranglers).to eq(crop_wranglers) end end + + describe '.admins' do + let!(:admins) { FactoryGirl.create_list(:admin_member, 3) } + it 'return the members that have the role of admin' do + expect(Role.admins).to eq(admins) + end + end end From 9c5f7753cab93e65a7c3e526e2c59968e85ea553 Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 22 Aug 2014 14:09:12 +1000 Subject: [PATCH 081/288] Show coverage locally and send to coveralls --- Gemfile | 1 + Gemfile.lock | 9 +++++++ spec/features/seeds_spec.rb | 51 +++++++++++++++++++++++++++++++++++++ spec/spec_helper.rb | 24 ++++++++++++----- 4 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 spec/features/seeds_spec.rb diff --git a/Gemfile b/Gemfile index 3c41ee688..4a5263a53 100644 --- a/Gemfile +++ b/Gemfile @@ -121,5 +121,6 @@ group :development, :test do gem 'rspec-rails', '~> 2.12.1' # unit testing framework gem 'webrat' # provides HTML matchers for view tests gem 'factory_girl_rails', '~> 4.0' # for creating test data + gem 'capybara' gem 'coveralls', require: false # coverage analysis end diff --git a/Gemfile.lock b/Gemfile.lock index 2921a230f..64d97afdd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -62,6 +62,12 @@ GEM railties (>= 3.0) builder (3.0.4) cancan (1.6.10) + capybara (2.4.1) + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) chunky_png (1.3.1) coderay (1.1.0) coffee-rails (3.2.2) @@ -268,6 +274,8 @@ GEM rack (>= 1.0) rack-test (>= 0.5.3) will_paginate (3.0.5) + xpath (2.0.0) + nokogiri (~> 1.3) PLATFORMS ruby @@ -281,6 +289,7 @@ DEPENDENCIES bootstrap-datepicker-rails bundler (>= 1.1.5) cancan + capybara coffee-rails (~> 3.2.1) compass-rails (~> 1.0.3) coveralls diff --git a/spec/features/seeds_spec.rb b/spec/features/seeds_spec.rb new file mode 100644 index 000000000..216c23877 --- /dev/null +++ b/spec/features/seeds_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +feature "seeds" do + context "signed in user" do + before(:each) do + @crop = FactoryGirl.create(:crop) + @member = FactoryGirl.create(:member) + visit root_path + click_link 'Sign in' + fill_in 'Login', :with => @member.login_name + fill_in 'Password', :with => @member.password + click_button 'Sign in' + end + + scenario "button on front page to add seeds" do + visit root_path + click_link "Add seeds" + current_path.should eq new_seed_path + page.should have_content 'Add seeds' + end + + scenario "add seeds" do + visit new_seed_path + page.should have_content 'Add seeds' + select @crop.name, :from => 'seed_crop_id' + fill_in 'seed_quantity', :with => 3 + fill_in 'seed_plant_before', :with => '2020-01-01' + fill_in 'seed_description', :with => "these are some seeds I harvested" + select "nowhere", :from => 'seed_tradable_to' + click_button 'Save' + current_path.should eq seed_path(Seed.last) + end + + scenario "edit seeds" do + seed = FactoryGirl.create(:seed, :owner => @member) + visit seed_path(seed) + click_link 'Edit' + current_path.should eq edit_seed_path(seed) + fill_in 'seed_quantity', :with => seed.quantity * 2 + click_button 'Save' + current_path.should eq seed_path(seed) + end + + scenario "delete seeds" do + seed = FactoryGirl.create(:seed, :owner => @member) + visit seed_path(seed) + click_link 'Delete' + current_path.should eq seeds_path + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8fa87587a..23acf340a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,14 +1,26 @@ # This file is copied to spec/ when you run 'rails generate rspec:install' ENV["RAILS_ENV"] ||= 'test' + +require 'simplecov' +require 'coveralls' + +# output coverage locally AND send it to coveralls +SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ + SimpleCov::Formatter::HTMLFormatter, + Coveralls::SimpleCov::Formatter +] + +# fail if changes make overall coverage drop +SimpleCov.refuse_coverage_drop + +SimpleCov.start :rails do + add_filter 'spec/' + add_filter 'vendor/' +end + require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' require 'rspec/autorun' -require 'coveralls' -require 'simplecov' -SimpleCov.configure do - add_filter 'spec/' -end -Coveralls.wear!('rails') # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. From 1250206c5eedc044472494803f544b21b0128fce Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 22 Aug 2014 14:44:00 +1000 Subject: [PATCH 082/288] Ongoing coverage improvements --- app/controllers/account_types_controller.rb | 2 +- app/controllers/forums_controller.rb | 2 +- app/views/account_types/show.html.haml | 2 + app/views/admin/index.html.haml | 2 +- app/views/forums/index.html.haml | 4 ++ app/views/forums/show.html.haml | 2 + spec/features/admin/account_types_spec.rb | 50 ++++++++++++++++++ spec/features/admin/forums_spec.rb | 56 +++++++++++++++++++++ spec/spec_helper.rb | 4 +- spec/views/forums/index.html.haml_spec.rb | 2 + 10 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 spec/features/admin/account_types_spec.rb create mode 100644 spec/features/admin/forums_spec.rb diff --git a/app/controllers/account_types_controller.rb b/app/controllers/account_types_controller.rb index 68c7f750d..9095beb37 100644 --- a/app/controllers/account_types_controller.rb +++ b/app/controllers/account_types_controller.rb @@ -64,7 +64,7 @@ class AccountTypesController < ApplicationController @account_type.destroy respond_to do |format| - format.html { redirect_to account_types_url } + format.html { redirect_to account_types_url, notice: 'Account type was successfully deleted.' } end end end diff --git a/app/controllers/forums_controller.rb b/app/controllers/forums_controller.rb index c37dc4224..b77939111 100644 --- a/app/controllers/forums_controller.rb +++ b/app/controllers/forums_controller.rb @@ -80,7 +80,7 @@ class ForumsController < ApplicationController @forum.destroy respond_to do |format| - format.html { redirect_to forums_url } + format.html { redirect_to forums_url, notice: 'Forum was successfully deleted' } format.json { head :no_content } end end diff --git a/app/views/account_types/show.html.haml b/app/views/account_types/show.html.haml index cb8993a27..1bd40e690 100644 --- a/app/views/account_types/show.html.haml +++ b/app/views/account_types/show.html.haml @@ -12,4 +12,6 @@ = link_to 'Edit', edit_account_type_path(@account_type) \| += link_to 'Delete', @account_type, :method => :delete, :data => { :confirm => 'Are you sure?' } +\| = link_to 'Back', account_types_path diff --git a/app/views/admin/index.html.haml b/app/views/admin/index.html.haml index b9f814df8..18f0a043e 100644 --- a/app/views/admin/index.html.haml +++ b/app/views/admin/index.html.haml @@ -2,7 +2,7 @@ %h2 Manage -%ul +%ul#admin_links %li= link_to "Account types", account_types_path %li= link_to "Products", products_path %li= link_to "Roles", roles_path diff --git a/app/views/forums/index.html.haml b/app/views/forums/index.html.haml index 160238709..3737d3f6f 100644 --- a/app/views/forums/index.html.haml +++ b/app/views/forums/index.html.haml @@ -1,5 +1,9 @@ - content_for :title, "Forums" +- if can? :create, Forum + %p + = link_to "New forum", new_forum_path, :class => 'btn btn-default' + - @forums.each do |forum| %h2= forum %p diff --git a/app/views/forums/show.html.haml b/app/views/forums/show.html.haml index 617981814..de839a651 100644 --- a/app/views/forums/show.html.haml +++ b/app/views/forums/show.html.haml @@ -12,6 +12,8 @@ - if can? :edit, @forum =link_to "Edit", edit_forum_path(@forum), :class => 'btn btn-default btn-xs' +- if can? :delete, @forum + = link_to 'Delete', @forum, :method => :delete, :data => { :confirm => 'Are you sure?' } %h2 Posts diff --git a/spec/features/admin/account_types_spec.rb b/spec/features/admin/account_types_spec.rb new file mode 100644 index 000000000..0ec005dd2 --- /dev/null +++ b/spec/features/admin/account_types_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +feature "account types" do + context "admin user" do + before(:each) do + @member = FactoryGirl.create(:admin_member) + visit root_path + click_link 'Sign in' + fill_in 'Login', :with => @member.login_name + fill_in 'Password', :with => @member.password + click_button 'Sign in' + end + + scenario "navigating to account type admin" do + visit root_path + click_link "Admin" + current_path.should eq admin_path + click_link "Account types" + current_path.should eq account_types_path + end + + scenario "adding an account type" do + visit account_types_path + click_link "New Account type" + current_path.should eq new_account_type_path + fill_in 'Name', :with => 'Guest' + click_button 'Save' + current_path.should eq account_type_path(AccountType.last) + page.should have_content 'Account type was successfully created' + end + + scenario 'editing account type' do + a = FactoryGirl.create(:account_type) + visit account_type_path(a) + click_link 'Edit' + fill_in 'Name', :with => 'Something else' + click_button 'Save' + current_path.should eq account_type_path(a) + page.should have_content 'Account type was successfully updated' + end + + scenario 'deleting account type' do + a = FactoryGirl.create(:account_type) + visit account_type_path(a) + click_link 'Delete' + current_path.should eq account_types_path + page.should have_content 'Account type was successfully deleted' + end + end +end diff --git a/spec/features/admin/forums_spec.rb b/spec/features/admin/forums_spec.rb new file mode 100644 index 000000000..a5c6f4fb1 --- /dev/null +++ b/spec/features/admin/forums_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +feature "forums" do + context "admin user" do + before(:each) do + @member = FactoryGirl.create(:admin_member) + visit root_path + click_link 'Sign in' + fill_in 'Login', :with => @member.login_name + fill_in 'Password', :with => @member.password + click_button 'Sign in' + end + + scenario "navigating to forum admin" do + visit root_path + click_link "Admin" + current_path.should eq admin_path + within 'ul#admin_links' do + click_link "Forums" + end + current_path.should eq forums_path + page.should have_content "New forum" + end + + scenario "adding a forum" do + visit forums_path + click_link "New forum" + current_path.should eq new_forum_path + fill_in 'Name', :with => 'Discussion' + fill_in 'Description', :with => "this is a new forum" + click_button 'Save' + current_path.should eq forum_path(Forum.last) + page.should have_content 'Forum was successfully created' + end + + scenario 'editing forum' do + f = FactoryGirl.create(:forum) + visit forum_path(f) + click_link 'Edit' + fill_in 'Name', :with => 'Something else' + click_button 'Save' + f.reload + current_path.should eq forum_path(f) + page.should have_content 'Forum was successfully updated' + page.should have_content 'Something else' + end + + scenario 'deleting forum' do + f = FactoryGirl.create(:forum) + visit forum_path(f) + click_link 'Delete' + current_path.should eq forums_path + page.should have_content 'Forum was successfully deleted' + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 23acf340a..a520377ad 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -10,8 +10,8 @@ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ Coveralls::SimpleCov::Formatter ] -# fail if changes make overall coverage drop -SimpleCov.refuse_coverage_drop +# fail if there's a significant test coverage drop +SimpleCov.maximum_coverage_drop 1 SimpleCov.start :rails do add_filter 'spec/' diff --git a/spec/views/forums/index.html.haml_spec.rb b/spec/views/forums/index.html.haml_spec.rb index 4d6d11805..d2046de48 100644 --- a/spec/views/forums/index.html.haml_spec.rb +++ b/spec/views/forums/index.html.haml_spec.rb @@ -2,6 +2,8 @@ require 'spec_helper' describe "forums/index" do before(:each) do + @admin = FactoryGirl.create(:admin_member) + controller.stub(:current_user) { @admin } @forum1 = FactoryGirl.create(:forum) @forum2 = FactoryGirl.create(:forum) assign(:forums, [ @forum1, @forum2 ]) From d038ac6d8af05bd784ed669b18e60322024940fe Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sun, 24 Aug 2014 10:53:24 +1000 Subject: [PATCH 083/288] test that input value is not affected by auto suggest --- app/assets/javascripts/auto_suggest.js.coffee | 1 + spec/features/shared_examples/crop_suggest_spec.rb | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/app/assets/javascripts/auto_suggest.js.coffee b/app/assets/javascripts/auto_suggest.js.coffee index 9e76ad718..c57a53643 100644 --- a/app/assets/javascripts/auto_suggest.js.coffee +++ b/app/assets/javascripts/auto_suggest.js.coffee @@ -16,6 +16,7 @@ jQuery -> el.autocomplete minLength: 1, source: el.attr( 'data-source-url' ), + autoFocus: true, focus: ( event, ui ) -> el.val( ui.item.name ) id.val( ui.item.id ) diff --git a/spec/features/shared_examples/crop_suggest_spec.rb b/spec/features/shared_examples/crop_suggest_spec.rb index 3cd50a928..cf4146920 100644 --- a/spec/features/shared_examples/crop_suggest_spec.rb +++ b/spec/features/shared_examples/crop_suggest_spec.rb @@ -24,4 +24,13 @@ shared_examples "crop suggest" do |resource| expect(page).to have_selector("input##{resource}_crop_id[value='#{pear.id}']", :visible => false) end + scenario "Typing and pausing does not affect input" do + within "form#new_#{resource}" do + fill_autocomplete "crop", :with => "p" + end + + expect(page).to have_content("pear") + expect(find_field("crop").value).to eq("p") + end + end \ No newline at end of file From fd651de7ea5957b07b5694b61ce112bf09dac6c1 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sun, 24 Aug 2014 12:02:53 +1000 Subject: [PATCH 084/288] implement auto suggest on new seed page --- app/assets/javascripts/auto_suggest.js.coffee | 1 - app/views/seeds/_form.html.haml | 13 +++++---- spec/features/adding_a_seed_spec.rb | 28 +++++++++++++++++++ spec/features/harvesting_a_crop_spec.rb | 6 ++-- spec/features/planting_a_crop_spec.rb | 6 ++-- .../shared_examples/crop_suggest_spec.rb | 20 +++++++++++++ 6 files changed, 61 insertions(+), 13 deletions(-) create mode 100644 spec/features/adding_a_seed_spec.rb diff --git a/app/assets/javascripts/auto_suggest.js.coffee b/app/assets/javascripts/auto_suggest.js.coffee index c57a53643..9e76ad718 100644 --- a/app/assets/javascripts/auto_suggest.js.coffee +++ b/app/assets/javascripts/auto_suggest.js.coffee @@ -16,7 +16,6 @@ jQuery -> el.autocomplete minLength: 1, source: el.attr( 'data-source-url' ), - autoFocus: true, focus: ( event, ui ) -> el.val( ui.item.name ) id.val( ui.item.id ) diff --git a/app/views/seeds/_form.html.haml b/app/views/seeds/_form.html.haml index cb4ca579e..6200320a9 100644 --- a/app/views/seeds/_form.html.haml +++ b/app/views/seeds/_form.html.haml @@ -7,20 +7,21 @@ %li= msg .control-group - = f.label 'Crop:', :class => 'control-label' - .controls= collection_select(:seed, :crop_id, Crop.all, :id, :name, :selected => @seed.crop_id || @crop.id) + = f.label :crop, 'Crop:', :class => 'control-label' + .controls + = auto_suggest @seed, :crop .control-group - = f.label 'Quantity:', :class => 'control-label' + = f.label :quantity, 'Quantity:', :class => 'control-label' .controls = f.number_field :quantity, :class => 'input-small' .control-group - = f.label 'Plant before:', :class => 'control-label' + = f.label :plant_before, 'Plant before:', :class => 'control-label' .controls= f.text_field :plant_before, :value => @seed.plant_before ? @seed.plant_before.to_s(:ymd) : '', :class => 'add-datepicker' .control-group - = f.label 'Description:', :class => 'control-label' + = f.label :description, 'Description:', :class => 'control-label' .controls= f.text_area :description, :rows => 6 .control-group - = f.label 'Will trade:', :class => 'control-label' + = f.label :tradable_to, 'Will trade:', :class => 'control-label' .controls = f.select(:tradable_to, options_for_select(Seed::TRADABLE_TO_VALUES, :selected => @seed.tradable_to || 'nowhere')) diff --git a/spec/features/adding_a_seed_spec.rb b/spec/features/adding_a_seed_spec.rb new file mode 100644 index 000000000..018531ec5 --- /dev/null +++ b/spec/features/adding_a_seed_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +feature "Harvesting a crop", :js => true do + let(:member) { FactoryGirl.create(:member) } + let!(:maize) { FactoryGirl.create(:maize) } + + background do + login_as(member) + visit '/seeds/new' + end + + it_behaves_like "crop suggest", "seed", "crop" + + scenario "Adding a new seed", :js => true do + fill_autocomplete "crop", :with => "m" + select_from_autocomplete "maize" + within "form#new_seed" do + fill_in "Quantity:", :with => 42 + fill_in "Plant before:", :with => "2014-06-15" + 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" + end + +end \ No newline at end of file diff --git a/spec/features/harvesting_a_crop_spec.rb b/spec/features/harvesting_a_crop_spec.rb index 33366187d..46404edd2 100644 --- a/spec/features/harvesting_a_crop_spec.rb +++ b/spec/features/harvesting_a_crop_spec.rb @@ -1,15 +1,15 @@ require 'spec_helper' feature "Harvesting a crop", :js => true do - let(:member) { FactoryGirl.create(:member) } - let!(:maize) { FactoryGirl.create(:maize) } + let(:member) { FactoryGirl.create(:member) } + let!(:maize) { FactoryGirl.create(:maize) } background do login_as(member) visit '/harvests/new' end - it_behaves_like "crop suggest", "harvest" + it_behaves_like "crop suggest", "harvest", "crop" scenario "Creating a new harvest", :js => true do fill_autocomplete "crop", :with => "m" diff --git a/spec/features/planting_a_crop_spec.rb b/spec/features/planting_a_crop_spec.rb index 095ef9c13..ad87a35ca 100644 --- a/spec/features/planting_a_crop_spec.rb +++ b/spec/features/planting_a_crop_spec.rb @@ -1,15 +1,15 @@ require 'spec_helper' feature "Planting a crop", :js => true do - let(:member) { FactoryGirl.create(:member) } - let!(:maize) { FactoryGirl.create(:maize) } + let(:member) { FactoryGirl.create(:member) } + let!(:maize) { FactoryGirl.create(:maize) } background do login_as(member) visit '/plantings/new' end - it_behaves_like "crop suggest", "planting" + it_behaves_like "crop suggest", "planting", "crop" scenario "Creating a new planting", :js => true do fill_autocomplete "crop", :with => "m" diff --git a/spec/features/shared_examples/crop_suggest_spec.rb b/spec/features/shared_examples/crop_suggest_spec.rb index cf4146920..a0edd5322 100644 --- a/spec/features/shared_examples/crop_suggest_spec.rb +++ b/spec/features/shared_examples/crop_suggest_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' shared_examples "crop suggest" do |resource| let!(:popcorn) { FactoryGirl.create(:popcorn) } let!(:pear) { FactoryGirl.create(:pear) } + let!(:tomato) { FactoryGirl.create(:tomato) } + let!(:roma) { FactoryGirl.create(:roma) } scenario "Typing in the crop name displays suggestions" do within "form#new_#{resource}" do @@ -33,4 +35,22 @@ shared_examples "crop suggest" do |resource| expect(find_field("crop").value).to eq("p") end + scenario "Searching for a crop casts a wide net on results" do + within "form#new_#{resource}" do + fill_autocomplete "crop", :with => "to" + end + + expect(page).to have_content("tomato") + expect(page).to have_content("roma tomato") + end + + scenario "Submitting a crop that doesn't exist in the database produces a meaningful error" do + within "form#new_#{resource}" do + fill_autocomplete "crop", :with => "Ryan Gosling" + click_button "Save" + end + + expect(page).to have_content("That's not in our database.") + end + end \ No newline at end of file From 2a51f66d888930826002114a2932971281c775df Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sun, 24 Aug 2014 12:11:10 +1000 Subject: [PATCH 085/288] fix seed view tests --- spec/views/seeds/edit.html.haml_spec.rb | 3 ++- spec/views/seeds/new.html.haml_spec.rb | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/views/seeds/edit.html.haml_spec.rb b/spec/views/seeds/edit.html.haml_spec.rb index f338dc46c..03b2064cf 100644 --- a/spec/views/seeds/edit.html.haml_spec.rb +++ b/spec/views/seeds/edit.html.haml_spec.rb @@ -13,7 +13,8 @@ describe "seeds/edit" do # Run the generator again with the --webrat flag if you want to use webrat matchers assert_select "form", :action => seeds_path(@seed), :method => "post" do - assert_select "select#seed_crop_id", :name => "seed[crop_id]" + assert_select "input#crop", :class => "ui-autocomplete-input" + assert_select "input#seed_crop_id", :name => "seed[crop_id]" assert_select "textarea#seed_description", :name => "seed[description]" assert_select "input#seed_quantity", :name => "seed[quantity]" assert_select "select#seed_tradable_to", :name => "seed[tradable_to]" diff --git a/spec/views/seeds/new.html.haml_spec.rb b/spec/views/seeds/new.html.haml_spec.rb index d9807277b..a88713181 100644 --- a/spec/views/seeds/new.html.haml_spec.rb +++ b/spec/views/seeds/new.html.haml_spec.rb @@ -12,7 +12,8 @@ describe "seeds/new" do it "renders new seed form" do render assert_select "form", :action => seeds_path, :method => "post" do - assert_select "select#seed_crop_id", :name => "seed[crop_id]" + assert_select "input#crop", :class => "ui-autocomplete-input" + assert_select "input#seed_crop_id", :name => "seed[crop_id]" assert_select "textarea#seed_description", :name => "seed[description]" assert_select "input#seed_quantity", :name => "seed[quantity]" assert_select "select#seed_tradable_to", :name => "seed[tradable_to]" From a5c2c0bb5c7ccb5850a8c79cf98fcf06bf805ce8 Mon Sep 17 00:00:00 2001 From: Skud Date: Mon, 25 Aug 2014 13:41:15 +1000 Subject: [PATCH 086/288] Fixed twitter link on contact page --- Gemfile.lock | 67 ++++++++++++++++--------------- app/views/about/contact.html.haml | 2 +- spec/features/footer_spec.rb | 9 +++++ 3 files changed, 45 insertions(+), 33 deletions(-) create mode 100644 spec/features/footer_spec.rb diff --git a/Gemfile.lock b/Gemfile.lock index 3f5152440..382a18460 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -52,9 +52,10 @@ GEM bcrypt (3.1.7) bcrypt-ruby (3.1.5) bcrypt (>= 3.1.3) - better_errors (1.1.0) + better_errors (2.0.0) coderay (>= 1.0.0) erubis (>= 2.6.6) + rack (>= 0.9.0) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) bluecloth (2.2.0) @@ -62,7 +63,7 @@ GEM railties (>= 3.0) builder (3.0.4) cancan (1.6.10) - capybara (2.3.0) + capybara (2.4.1) mime-types (>= 1.16) nokogiri (>= 1.3.3) rack (>= 1.0.0) @@ -73,19 +74,19 @@ GEM coffee-rails (3.2.2) coffee-script (>= 2.2.0) railties (~> 3.2.0) - coffee-script (2.2.0) + coffee-script (2.3.0) coffee-script-source execjs - coffee-script-source (1.7.0) + coffee-script-source (1.7.1) columnize (0.8.9) commonjs (0.2.7) - compass (0.12.6) + compass (0.12.7) chunky_png (~> 1.2) fssm (>= 0.2.7) sass (~> 3.2.19) compass-rails (1.0.3) compass (>= 0.12.2, < 0.14) - coveralls (0.7.0) + coveralls (0.7.1) multi_json (~> 1.3) rest-client simplecov (>= 0.7) @@ -93,23 +94,23 @@ GEM thor csv_shaper (1.0.0) activesupport (>= 3.0.0) - dalli (2.7.0) + dalli (2.7.2) debug_inspector (0.0.2) - debugger (1.6.6) + debugger (1.6.8) columnize (>= 0.3.1) debugger-linecache (~> 1.2.0) - debugger-ruby_core_source (~> 1.3.2) + debugger-ruby_core_source (~> 1.3.5) debugger-linecache (1.2.0) - debugger-ruby_core_source (1.3.2) + debugger-ruby_core_source (1.3.5) devise (3.0.4) bcrypt-ruby (~> 3.0) orm_adapter (~> 0.1) railties (>= 3.2.6, < 5) warden (~> 1.2.3) diff-lcs (1.1.3) - docile (1.1.3) + docile (1.1.5) erubis (2.7.0) - execjs (2.0.2) + execjs (2.2.1) factory_girl (4.4.0) activesupport (>= 3.0.0) factory_girl_rails (4.4.1) @@ -122,7 +123,7 @@ GEM friendly_id (4.0.10.1) activerecord (>= 3.0, < 4.0) fssm (0.2.10) - gibbon (1.1.2) + gibbon (1.1.3) httparty multi_json (>= 1.3.4) gravatar-ultimate (2.0.0) @@ -135,14 +136,14 @@ GEM activesupport (>= 3.1, < 4.1) haml (>= 3.1, < 4.1) railties (>= 3.1, < 4.1) - hashie (2.1.1) + hashie (3.2.0) hike (1.2.3) httparty (0.11.0) multi_json (~> 1.0) multi_xml (>= 0.5.2) i18n (0.6.1) journey (1.0.4) - jquery-rails (3.1.0) + jquery-rails (3.1.1) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) json (1.7.7) @@ -151,7 +152,7 @@ GEM addressable (~> 2.3) leaflet-markercluster-rails (0.7.0) railties (>= 3.1) - leaflet-rails (0.7.2) + leaflet-rails (0.7.3) less (2.3.3) commonjs (~> 0.2.6) less-rails (2.3.3) @@ -167,15 +168,16 @@ GEM treetop (~> 1.4.8) memcachier (0.0.2) mime-types (1.25.1) - mini_portile (0.5.3) - multi_json (1.9.3) + mini_portile (0.6.0) + multi_json (1.10.1) multi_xml (0.5.5) - newrelic_rpm (3.8.0.218) - nokogiri (1.6.1) - mini_portile (~> 0.5.0) + netrc (0.7.7) + newrelic_rpm (3.9.2.239) + nokogiri (1.6.3.1) + mini_portile (= 0.6.0) oauth (0.4.7) - omniauth (1.2.1) - hashie (>= 1.2, < 3) + omniauth (1.2.2) + hashie (>= 1.2, < 4) rack (~> 1.0) omniauth-flickr (0.0.15) omniauth-oauth (~> 1.0) @@ -187,7 +189,7 @@ GEM omniauth-oauth (~> 1.0) orm_adapter (0.5.0) pg (0.17.1) - polyglot (0.3.4) + polyglot (0.3.5) rack (1.4.5) rack-cache (1.2) rack (>= 0.4) @@ -216,12 +218,13 @@ GEM rdoc (~> 3.4) thor (>= 0.14.6, < 2.0) raindrops (0.13.0) - rake (10.3.1) + rake (10.3.2) rdoc (3.12.2) json (~> 1.4) ref (1.0.5) - rest-client (1.6.7) - mime-types (>= 1.16) + rest-client (1.7.2) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) rspec-core (2.12.2) rspec-expectations (2.12.1) diff-lcs (~> 1.1.3) @@ -238,7 +241,7 @@ GEM railties (~> 3.2.0) sass (>= 3.1.10) tilt (~> 1.3) - simplecov (0.8.2) + simplecov (0.9.0) docile (~> 1.1.0) multi_json simplecov-html (~> 0.8.0) @@ -255,15 +258,15 @@ GEM ref thor (0.19.1) tilt (1.4.1) - tins (1.1.0) + tins (1.3.2) treetop (1.4.15) polyglot polyglot (>= 0.3.1) - tzinfo (0.3.39) + tzinfo (0.3.41) uglifier (2.2.1) execjs (>= 0.3.0) multi_json (~> 1.0, >= 1.0.2) - unicorn (4.8.2) + unicorn (4.8.3) kgio (~> 2.6) rack raindrops (~> 0.7) @@ -273,7 +276,7 @@ GEM nokogiri (>= 1.2.0) rack (>= 1.0) rack-test (>= 0.5.3) - will_paginate (3.0.5) + will_paginate (3.0.7) xpath (2.0.0) nokogiri (~> 1.3) diff --git a/app/views/about/contact.html.haml b/app/views/about/contact.html.haml index c4de94d5c..2398bea36 100644 --- a/app/views/about/contact.html.haml +++ b/app/views/about/contact.html.haml @@ -14,4 +14,4 @@ %dd= link_to 'media@growstuff.org', 'mailto:media@growstuff.org' %dl %dt Twitter - %dd= link_to '@Growstuff', 'http:/twitter.com/Growstuff' + %dd= link_to '@growstufforg', 'http://twitter.com/growstufforg' diff --git a/spec/features/footer_spec.rb b/spec/features/footer_spec.rb new file mode 100644 index 000000000..aa8449b15 --- /dev/null +++ b/spec/features/footer_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +feature "footer" do + 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 From 1103e2719feb32a44c5a6cbfa5d1bc14040ccc4c Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 27 Aug 2014 18:42:42 +1000 Subject: [PATCH 087/288] Fixed up label with link to field it relates to --- app/views/gardens/_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/gardens/_form.html.haml b/app/views/gardens/_form.html.haml index 7a1c5e59e..d6925a205 100644 --- a/app/views/gardens/_form.html.haml +++ b/app/views/gardens/_form.html.haml @@ -35,7 +35,7 @@ = f.select(:area_unit, Garden::AREA_UNITS_VALUES, {:include_blank => false}, :class => 'input-medium') .form-group - = f.label 'Active? ', :class => 'control-label' + = f.label :active, 'Active? ', :class => 'control-label' .controls = f.check_box :active %span.help-inline From f6c2873f979dcda78a83a40b74d84a51ba6455cd Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 27 Aug 2014 18:43:44 +1000 Subject: [PATCH 088/288] Add page content test suggested by tygriffin --- spec/features/admin/account_types_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/features/admin/account_types_spec.rb b/spec/features/admin/account_types_spec.rb index 0ec005dd2..2f8cda09f 100644 --- a/spec/features/admin/account_types_spec.rb +++ b/spec/features/admin/account_types_spec.rb @@ -37,6 +37,7 @@ feature "account types" do click_button 'Save' current_path.should eq account_type_path(a) page.should have_content 'Account type was successfully updated' + page.should have_content 'Something else' end scenario 'deleting account type' do From 501fda3fc9ae5d66e23143ce74fb8ba4bd97e55e Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 27 Aug 2014 19:02:06 +1000 Subject: [PATCH 089/288] b3ified a few of the lesser-used forms --- app/views/crops/_form.html.haml | 38 ++++++++++---------- app/views/forums/_form.html.haml | 22 +++++++----- app/views/gardens/_form.html.haml | 42 +++++++++++----------- app/views/scientific_names/_form.html.haml | 19 +++++----- 4 files changed, 64 insertions(+), 57 deletions(-) diff --git a/app/views/crops/_form.html.haml b/app/views/crops/_form.html.haml index 8db5a5d8c..0c8fc8c45 100644 --- a/app/views/crops/_form.html.haml +++ b/app/views/crops/_form.html.haml @@ -1,4 +1,4 @@ -= form_for @crop, :html => {:class => 'form-horizontal'} do |f| += form_for @crop, :html => {:class => 'form-horizontal', :role => "form"} do |f| - if @crop.errors.any? #error_explanation %h3= "#{pluralize(@crop.errors.count, "error")} prohibited this crop from being saved:" @@ -13,31 +13,33 @@ on the Growstuff wiki. .form-group - = f.label :name, :class => 'control-label' - .controls - = f.text_field :name - %span.help-inline Name in US English; singular; capitalize proper nouns only. + = 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. .form-group - = f.label :en_wikipedia_url, 'Wikipedia URL', :class => 'control-label' - .controls - = f.text_field :en_wikipedia_url - %span.help-inline Link to this crop's page on the English language Wikipedia. + = 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' - .controls - = collection_select(:crop, :parent_id, Crop.all, :id, :name, {:include_blank => true}) - %span.help-inline Optional. For setting up crop hierarchies for varieties etc. + = 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| .form-group - = sn.label :scientific_name, "Scientific name", :class => 'control-label' - .controls - = sn.text_field :scientific_name + = 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" - .form-actions - = f.submit 'Save', :class => 'btn btn-primary' + .form-group + .form-actions.col-md-offset-2.col-md-8 + = f.submit 'Save', :class => 'btn btn-primary' diff --git a/app/views/forums/_form.html.haml b/app/views/forums/_form.html.haml index 503481187..8168f30cd 100644 --- a/app/views/forums/_form.html.haml +++ b/app/views/forums/_form.html.haml @@ -1,4 +1,4 @@ -= form_for @forum, :html => { :class => 'form-horizontal' } do |f| += form_for @forum, :html => { :class => 'form-horizontal', :role => "form" } do |f| - if @forum.errors.any? #error_explanation %h2= "#{pluralize(@forum.errors.count, "error")} prohibited this forum from being saved:" @@ -7,13 +7,17 @@ %li= msg .form-group - = f.label :name, :class => 'control-label' - .controls= f.text_field :name, :class => 'form-control' + = f.label :name, :class => 'control-label col-md-2' + .col-md-8 + = f.text_field :name, :class => 'form-control' .form-group - = f.label :description, :class => 'control-label' - .controls= f.text_area :description, :rows => 6, :class => 'form-control' + = f.label :description, :class => 'control-label col-md-2' + .col-md-8 + = f.text_area :description, :rows => 6, :class => 'form-control' .form-group - = f.label :owner_id, :class => 'control-label' - .controls= collection_select(:forum, :owner_id, Member.all, :id, :login_name) - .form-actions - = f.submit 'Save', :class => 'btn btn-primary' + = f.label :owner_id, :class => 'control-label col-md-2' + .col-md-8 + = collection_select(:forum, :owner_id, Member.all, :id, :login_name, {}, :class => 'form-control') + .form-group + .form-actions.col-md-offset-2.col-md-8 + = f.submit 'Save', :class => 'btn btn-primary' diff --git a/app/views/gardens/_form.html.haml b/app/views/gardens/_form.html.haml index 7a1c5e59e..6313762c4 100644 --- a/app/views/gardens/_form.html.haml +++ b/app/views/gardens/_form.html.haml @@ -1,4 +1,4 @@ -= form_for(@garden, :html => {:class => "form-horizontal"}) do |f| += form_for(@garden, :html => {:class => "form-horizontal", :role => "form"}) do |f| - if @garden.errors.any? #error_explanation %h2= "#{pluralize(@garden.errors.count, "error")} prohibited this garden from being saved:" @@ -7,19 +7,19 @@ %li= msg .form-group - = f.label :name, :class => 'control-label' - .controls - = f.text_field :name + = f.label :name, :class => 'control-label col-md-2' + .col-md-8 + = f.text_field :name, :class => 'form-control' .form-group - = f.label :description, :class => 'control-label' - .controls - = f.text_area :description, :rows => 6 + = f.label :description, :class => 'control-label col-md-2' + .col-md-8 + = f.text_area :description, :rows => 6, :class => 'form-control' .form-group - = f.label :location, :class => 'control-label' - .controls - = f.text_field :location, :value => @garden.location || current_member.location + = f.label :location, :class => 'control-label col-md-2' + .col-md-8 + = f.text_field :location, :value => @garden.location || current_member.location, :class => 'form-control' %span.help-block If you have a location set in your profile, it will be used when you create a new garden. @@ -29,18 +29,18 @@ =link_to "Change your location.", edit_member_registration_path .form-group - = f.label :area, :class => 'control-label' - .controls - = f.number_field :area, :class => 'input-small' - = f.select(:area_unit, Garden::AREA_UNITS_VALUES, {:include_blank => false}, :class => 'input-medium') + = f.label :area, :class => 'control-label col-md-2' + .col-md-2 + = f.number_field :area, :class => 'input-small form-control' + .col-md-2 + = f.select(:area_unit, Garden::AREA_UNITS_VALUES, {:include_blank => false}, :class => 'form-control') .form-group - = f.label 'Active? ', :class => 'control-label' - .controls + = f.label 'Active? ', :class => 'control-label col-md-2' + .col-md-8 = f.check_box :active - %span.help-inline - You can mark a garden as inactive if you no longer use it. + You can mark a garden as inactive if you no longer use it. - - .form-actions - = f.submit 'Save Garden', :class => 'btn btn-primary' + .form-group + .form-actions.col-md-offset-2.col-md-8 + = f.submit 'Save Garden', :class => 'btn btn-primary' diff --git a/app/views/scientific_names/_form.html.haml b/app/views/scientific_names/_form.html.haml index 2ee348e4a..e8d08978a 100644 --- a/app/views/scientific_names/_form.html.haml +++ b/app/views/scientific_names/_form.html.haml @@ -1,4 +1,4 @@ -= form_for @scientific_name, :html => {:class => 'form-horizontal'} do |f| += form_for @scientific_name, :html => {:class => 'form-horizontal', :role => "form"} do |f| - if @scientific_name.errors.any? #error_explanation %h2= "#{pluralize(@scientific_name.errors.count, "error")} prohibited this scientific_name from being saved:" @@ -13,12 +13,13 @@ on the Growstuff wiki. .form-group - = f.label :crop_id, :class => 'control-label' - .controls - = collection_select(:scientific_name, :crop_id, Crop.all, :id, :name, :selected => @scientific_name.crop_id || @crop.id) + = f.label :crop_id, :class => 'control-label col-md-2' + .col-md-8 + = collection_select(:scientific_name, :crop_id, Crop.all, :id, :name, { :selected => @scientific_name.crop_id || @crop.id }, :class => 'form-control') .form-group - = f.label :scientific_name, :class => 'control-label' - .controls - = f.text_field :scientific_name - .form-actions - = f.submit 'Save', :class => 'btn btn-primary' + = f.label :scientific_name, :class => 'control-label col-md-2' + .col-md-8 + = f.text_field :scientific_name, :class => 'form-control' + .form-group + .form-actions.col-md-offset-2.col-md-8 + = f.submit 'Save', :class => 'btn btn-primary' From e5b15fae1aa4f5c17c0171b296e4d9165503da64 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 27 Aug 2014 19:02:49 +1000 Subject: [PATCH 090/288] got rid of ugly scaffolding 'back' links --- app/views/forums/edit.html.haml | 4 ---- app/views/forums/new.html.haml | 2 -- app/views/gardens/new.html.haml | 2 -- 3 files changed, 8 deletions(-) diff --git a/app/views/forums/edit.html.haml b/app/views/forums/edit.html.haml index f334a2092..c23c87878 100644 --- a/app/views/forums/edit.html.haml +++ b/app/views/forums/edit.html.haml @@ -1,7 +1,3 @@ - content_for :title, "Editing forum" = render 'form' - -= link_to 'Show', @forum -\| -= link_to 'Back', forums_path diff --git a/app/views/forums/new.html.haml b/app/views/forums/new.html.haml index 59e6ef782..b570c218f 100644 --- a/app/views/forums/new.html.haml +++ b/app/views/forums/new.html.haml @@ -1,5 +1,3 @@ - content_for :title, "New Forum" = render 'form' - -= link_to 'Back', forums_path diff --git a/app/views/gardens/new.html.haml b/app/views/gardens/new.html.haml index 7d7b7ce19..3e7ee5f67 100644 --- a/app/views/gardens/new.html.haml +++ b/app/views/gardens/new.html.haml @@ -1,5 +1,3 @@ %h1 New garden = render 'form' - -= link_to 'Back', gardens_path From c68b029d50b6a254a74fb5f4d40dc3a0fccaab06 Mon Sep 17 00:00:00 2001 From: Skud Date: Thu, 28 Aug 2014 10:16:31 +1000 Subject: [PATCH 091/288] Switch to match_array to get around spuriously failing tests --- spec/models/role_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/role_spec.rb b/spec/models/role_spec.rb index 7618872c6..5a74d6165 100644 --- a/spec/models/role_spec.rb +++ b/spec/models/role_spec.rb @@ -20,14 +20,14 @@ describe Role do describe '.crop_wranglers' do let!(:crop_wranglers) { FactoryGirl.create_list(:crop_wrangling_member, 3) } it 'return the crop wranglers that are members of that role' do - expect(Role.crop_wranglers).to eq(crop_wranglers) + expect(Role.crop_wranglers).to match_array(crop_wranglers) end end describe '.admins' do let!(:admins) { FactoryGirl.create_list(:admin_member, 3) } it 'return the members that have the role of admin' do - expect(Role.admins).to eq(admins) + expect(Role.admins).to match_array(admins) end end end From 848b583d9461b047d7fded4d7cdc0e048e5bd083 Mon Sep 17 00:00:00 2001 From: Skud Date: Thu, 28 Aug 2014 11:46:36 +1000 Subject: [PATCH 092/288] Tweak formatting of images with popovers (used mostly on homepage) --- app/assets/stylesheets/overrides.css.less | 5 +++++ app/views/crops/_image_with_popover.html.haml | 5 +++-- app/views/members/_image_with_popover.html.haml | 2 +- app/views/plantings/_image_with_popover.html.haml | 3 ++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/overrides.css.less b/app/assets/stylesheets/overrides.css.less index 846069012..969d0750a 100644 --- a/app/assets/stylesheets/overrides.css.less +++ b/app/assets/stylesheets/overrides.css.less @@ -176,6 +176,11 @@ footer .navbar .nav { border-radius: 0; } +.crop-image, .member-image { + width: 100%; + height: 100%; +} + // Overrides applying only to mobile view. This must be at the end of the overrides file. @media only screen and (max-width: 767px) { diff --git a/app/views/crops/_image_with_popover.html.haml b/app/views/crops/_image_with_popover.html.haml index 96bcd2ec1..46e1b4ee8 100644 --- a/app/views/crops/_image_with_popover.html.haml +++ b/app/views/crops/_image_with_popover.html.haml @@ -2,11 +2,12 @@ = link_to | image_tag( | crop.default_photo ? crop.default_photo.thumbnail_url : 'placeholder_150.png', | - :alt => crop.name | + :alt => crop.name, | + :class => 'image-responsive crop-image' | ), | crop, | :rel => "popover", | 'data-trigger' => 'hover', | 'data-title' => crop.name, | 'data-content' => "#{ render :partial => 'crops/popover', :locals => { :crop => crop } }", | - 'data-html' => true + 'data-html' => true | diff --git a/app/views/members/_image_with_popover.html.haml b/app/views/members/_image_with_popover.html.haml index 4202f0f58..fb9caddfc 100644 --- a/app/views/members/_image_with_popover.html.haml +++ b/app/views/members/_image_with_popover.html.haml @@ -5,7 +5,7 @@ :size => defined?(size) ? size : 150, | :default => :identicon }), | :alt => member.login_name, | - :class => 'img-responsive' | + :class => 'img-responsive member-image' | ), | member, | :rel => "popover", | diff --git a/app/views/plantings/_image_with_popover.html.haml b/app/views/plantings/_image_with_popover.html.haml index 5eef8610e..01d39128e 100644 --- a/app/views/plantings/_image_with_popover.html.haml +++ b/app/views/plantings/_image_with_popover.html.haml @@ -2,7 +2,8 @@ = link_to | image_tag( | planting.photos.present? ? planting.photos.first.thumbnail_url : 'placeholder_150.png', | - :alt => planting.to_s | + :alt => planting.to_s, | + :class => 'image-responsive crop-image' | ), | planting, | :rel => "popover", | From 55c2fceffbbb235dae3f2e2f7a0a946466746f96 Mon Sep 17 00:00:00 2001 From: Skud Date: Thu, 28 Aug 2014 12:09:40 +1000 Subject: [PATCH 093/288] changed layout of planting list on homepage ... as b3 seems to have more gutter space between columns --- app/views/plantings/_list.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/plantings/_list.html.haml b/app/views/plantings/_list.html.haml index 3e62fffbc..0efc1be37 100644 --- a/app/views/plantings/_list.html.haml +++ b/app/views/plantings/_list.html.haml @@ -1,9 +1,9 @@ - plantings.each do |p| - cache "plantings_listitem_#{p.id}" do .row - .col-md-2{:style => 'padding-bottom: 6px'} + .col-md-3{:style => 'padding-bottom: 6px'} = render :partial => 'plantings/image_with_popover', :locals => { :planting => p } - .col-md-10 + .col-md-9 = link_to p.crop, p.crop in = succeed "'s" do From 4e0d62b28d4ecb3b55ff3c742f27f3cd2d096863 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Fri, 29 Aug 2014 06:44:48 +1000 Subject: [PATCH 094/288] remove client side validation --- app/assets/javascripts/auto_suggest.js.coffee | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/app/assets/javascripts/auto_suggest.js.coffee b/app/assets/javascripts/auto_suggest.js.coffee index 9e76ad718..8720b1978 100644 --- a/app/assets/javascripts/auto_suggest.js.coffee +++ b/app/assets/javascripts/auto_suggest.js.coffee @@ -10,8 +10,6 @@ jQuery -> if el = $( '.auto-suggest' ) id = $( '.auto-suggest-id' ) - submit = $( '[name="commit"]' ) - errorContainer = $( '' ).insertAfter(el) el.autocomplete minLength: 1, @@ -36,18 +34,3 @@ jQuery -> .data( 'item.autocomplete', item ) .append( "#{item.name}" ) .appendTo( ul ) - - submit.on( 'click', (e) -> - if el.val() != '' && id.val() == '' - e.preventDefault() - errorContainer.prepend( '

    That\'s not in our database.

    ' ) - el.css( 'background-color', '#ffe6e6' ) - $( 'html, body' ).animate({ - scrollTop: ( el.offset().top ) - 200 - }, 200) - ) - - el.focus( -> - el.css( 'background-color', '#fff' ) - errorContainer.find( 'p' ).remove() - ) From cd6fc7e8043aa46561c17cf1b7babaafaf35efb1 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Fri, 29 Aug 2014 06:45:11 +1000 Subject: [PATCH 095/288] add noscript tag --- app/helpers/auto_suggest_helper.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/helpers/auto_suggest_helper.rb b/app/helpers/auto_suggest_helper.rb index dbecd431e..b562ad1d7 100644 --- a/app/helpers/auto_suggest_helper.rb +++ b/app/helpers/auto_suggest_helper.rb @@ -8,6 +8,7 @@ module AutoSuggestHelper %Q{ + }.html_safe end From 62e1c3bb9382bdb18e0044c17b63c5aea8e54ce1 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Fri, 29 Aug 2014 06:45:37 +1000 Subject: [PATCH 096/288] add server side validation --- app/models/crop.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/crop.rb b/app/models/crop.rb index 3efe8be57..584f5be9e 100644 --- a/app/models/crop.rb +++ b/app/models/crop.rb @@ -24,6 +24,7 @@ class Crop < ActiveRecord::Base scope :popular, reorder("plantings_count desc, lower(name) asc") scope :randomized, reorder('random()') # ok on sqlite and psql, but not on mysql + validates :presence => {:message => "must be present and exist in our database"} validates :en_wikipedia_url, :format => { :with => /^https?:\/\/en\.wikipedia\.org\/wiki/, From 550cb4b2654d567dffafe8b531a39da850f332b5 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Fri, 29 Aug 2014 06:56:02 +1000 Subject: [PATCH 097/288] put validation in the correct place this time --- app/models/crop.rb | 1 - app/models/harvest.rb | 2 +- app/models/planting.rb | 2 +- app/models/seed.rb | 3 ++- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/crop.rb b/app/models/crop.rb index 584f5be9e..3efe8be57 100644 --- a/app/models/crop.rb +++ b/app/models/crop.rb @@ -24,7 +24,6 @@ class Crop < ActiveRecord::Base scope :popular, reorder("plantings_count desc, lower(name) asc") scope :randomized, reorder('random()') # ok on sqlite and psql, but not on mysql - validates :presence => {:message => "must be present and exist in our database"} validates :en_wikipedia_url, :format => { :with => /^https?:\/\/en\.wikipedia\.org\/wiki/, diff --git a/app/models/harvest.rb b/app/models/harvest.rb index e17d46950..a0f8f8f5d 100644 --- a/app/models/harvest.rb +++ b/app/models/harvest.rb @@ -11,7 +11,7 @@ class Harvest < ActiveRecord::Base default_scope order('created_at DESC') - validates :crop, :presence => true + validates :crop, :presence => {:message => "must be present and exist in our database"} validates :quantity, :numericality => { :only_integer => false }, diff --git a/app/models/planting.rb b/app/models/planting.rb index 97bf96552..b848d3490 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -23,7 +23,7 @@ class Planting < ActiveRecord::Base default_scope order("created_at desc") - validates :crop_id, :presence => true + validates :crop_id, :presence => {:message => "must be present and exist in our database"} validates :quantity, :numericality => { :only_integer => true }, diff --git a/app/models/seed.rb b/app/models/seed.rb index 54868c579..9ff32c6d3 100644 --- a/app/models/seed.rb +++ b/app/models/seed.rb @@ -2,7 +2,7 @@ class Seed < ActiveRecord::Base extend FriendlyId friendly_id :seed_slug, use: :slugged - attr_accessible :owner_id, :crop_id, :description, :quantity, :plant_before, + attr_accessible :owner_id, :crop_id, :description, :quantity, :plant_before, :tradable_to, :slug belongs_to :crop @@ -10,6 +10,7 @@ class Seed < ActiveRecord::Base default_scope order("created_at desc") + validates :crop, :presence => {:message => "must be present and exist in our database"} validates :quantity, :numericality => { :only_integer => true }, :allow_nil => true From 6bb74039a7334e73813896026c99dea800daf531 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Fri, 29 Aug 2014 07:31:02 +1000 Subject: [PATCH 098/288] fix slug bug --- app/models/seed.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/seed.rb b/app/models/seed.rb index 9ff32c6d3..38cd3b714 100644 --- a/app/models/seed.rb +++ b/app/models/seed.rb @@ -56,6 +56,6 @@ class Seed < ActiveRecord::Base end def seed_slug - "#{owner.login_name}-#{crop.name}".downcase.gsub(' ', '-') + "#{owner.login_name}-#{crop}".downcase.gsub(' ', '-') end end From d8c7aba725d1339ba82127f1b472207735fb9ecd Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Fri, 29 Aug 2014 07:31:32 +1000 Subject: [PATCH 099/288] pass options hash into auto_suggest helper to allow setting class --- app/helpers/auto_suggest_helper.rb | 4 ++-- app/views/harvests/_form.html.haml | 2 +- app/views/plantings/_form.html.haml | 2 +- app/views/seeds/_form.html.haml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/helpers/auto_suggest_helper.rb b/app/helpers/auto_suggest_helper.rb index b562ad1d7..70bbd893d 100644 --- a/app/helpers/auto_suggest_helper.rb +++ b/app/helpers/auto_suggest_helper.rb @@ -1,13 +1,13 @@ module AutoSuggestHelper - def auto_suggest(resource, source) + def auto_suggest(resource, source, options={}) default = resource.send(source).nil? ? "" : resource.send(source).name resource = resource.class.name.downcase source_path = Rails.application.routes.url_helpers.send("#{source}s_search_path") %Q{ - + }.html_safe diff --git a/app/views/harvests/_form.html.haml b/app/views/harvests/_form.html.haml index 734df848c..f10b5497c 100644 --- a/app/views/harvests/_form.html.haml +++ b/app/views/harvests/_form.html.haml @@ -9,7 +9,7 @@ .form-group = f.label :crop, 'What did you harvest?', :class => 'control-label col-md-2' .col-md-4 - = auto_suggest @harvest, :crop + = auto_suggest @harvest, :crop, :class => 'form-control col-md-2' .col-md-4 = collection_select(:harvest, :plant_part_id, PlantPart.all, :id, :name, { :selected => @harvest.plant_part_id }, { :class => 'form-control' }) %span.help-block.col-md-8 diff --git a/app/views/plantings/_form.html.haml b/app/views/plantings/_form.html.haml index e0550faf4..2946a1d6d 100644 --- a/app/views/plantings/_form.html.haml +++ b/app/views/plantings/_form.html.haml @@ -9,7 +9,7 @@ .form-group = f.label :crop, 'What did you plant?', :class => 'control-label col-md-2' .col-md-8 - = auto_suggest @planting, :crop + = auto_suggest @planting, :crop, :class => 'form-control' %span.help-inline Can't find what you're looking for? = link_to "Request new crops.", Growstuff::Application.config.new_crops_request_link diff --git a/app/views/seeds/_form.html.haml b/app/views/seeds/_form.html.haml index 08a252501..6a32344c5 100644 --- a/app/views/seeds/_form.html.haml +++ b/app/views/seeds/_form.html.haml @@ -9,7 +9,7 @@ .form-group = f.label :crop, 'Crop:', :class => 'control-label col-md-2' .col-md-8 - = auto_suggest @seed, :crop + = auto_suggest @seed, :crop, :class => 'form-control' .form-group = f.label :quantity, 'Quantity:', :class => 'control-label col-md-2' .col-md-2 From 7fa05d81e97d7bd3b3393c678634a2a38944b284 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Fri, 29 Aug 2014 07:38:55 +1000 Subject: [PATCH 100/288] fix tests to match new implementation of of auto_suggest --- app/views/plantings/_form.html.haml | 2 +- app/views/seeds/_form.html.haml | 2 +- spec/features/harvesting_a_crop_spec.rb | 2 +- spec/features/shared_examples/crop_suggest_spec.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/plantings/_form.html.haml b/app/views/plantings/_form.html.haml index 2946a1d6d..277fe2cb9 100644 --- a/app/views/plantings/_form.html.haml +++ b/app/views/plantings/_form.html.haml @@ -23,7 +23,7 @@ = f.label :planted_at, 'When?', :class => 'control-label col-md-2' .col-md-2= f.text_field :planted_at, :value => @planting.planted_at ? @planting.planted_at.to_s(:ymd) : '', :class => 'add-datepicker form-control' .form-group - = f.label :qantity, 'How many?', :class => 'control-label col-md-2' + = f.label :quantity, 'How many?', :class => 'control-label col-md-2' .col-md-2 = f.number_field :quantity, :class => 'form-control' .form-group diff --git a/app/views/seeds/_form.html.haml b/app/views/seeds/_form.html.haml index 6a32344c5..b81f1b46d 100644 --- a/app/views/seeds/_form.html.haml +++ b/app/views/seeds/_form.html.haml @@ -19,7 +19,7 @@ .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 :desciption, 'Description:', :class => 'control-label col-md-2' + = f.label :description, 'Description:', :class => 'control-label col-md-2' .col-md-8 = f.text_area :description, :rows => 6, :class => 'form-control' .form-group diff --git a/spec/features/harvesting_a_crop_spec.rb b/spec/features/harvesting_a_crop_spec.rb index 46404edd2..0645cf045 100644 --- a/spec/features/harvesting_a_crop_spec.rb +++ b/spec/features/harvesting_a_crop_spec.rb @@ -17,7 +17,7 @@ feature "Harvesting a crop", :js => true do within "form#new_harvest" do fill_in "When?", :with => "2014-06-15" fill_in "How many?", :with => 42 - fill_in "Weighing?", :with => 42 + fill_in "Weighing (in total):", :with => 42 fill_in "Notes", :with => "It's killer." click_button "Save" end diff --git a/spec/features/shared_examples/crop_suggest_spec.rb b/spec/features/shared_examples/crop_suggest_spec.rb index a0edd5322..a63acb4b6 100644 --- a/spec/features/shared_examples/crop_suggest_spec.rb +++ b/spec/features/shared_examples/crop_suggest_spec.rb @@ -50,7 +50,7 @@ shared_examples "crop suggest" do |resource| click_button "Save" end - expect(page).to have_content("That's not in our database.") + expect(page).to have_content("Crop must be present and exist in our database") end end \ No newline at end of file From 83d3c369c26e85e64f3224f35e2044f0d871e842 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Fri, 29 Aug 2014 08:42:43 +1000 Subject: [PATCH 101/288] style noscript warning --- app/helpers/auto_suggest_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/auto_suggest_helper.rb b/app/helpers/auto_suggest_helper.rb index 70bbd893d..e81664c83 100644 --- a/app/helpers/auto_suggest_helper.rb +++ b/app/helpers/auto_suggest_helper.rb @@ -8,7 +8,7 @@ module AutoSuggestHelper %Q{ - + }.html_safe end From 3beae6f500826755e29b787473e89e16dca8125b Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Fri, 29 Aug 2014 08:43:02 +1000 Subject: [PATCH 102/288] add request crop link to seeds --- app/views/seeds/_form.html.haml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/views/seeds/_form.html.haml b/app/views/seeds/_form.html.haml index b81f1b46d..e38849811 100644 --- a/app/views/seeds/_form.html.haml +++ b/app/views/seeds/_form.html.haml @@ -10,6 +10,9 @@ = f.label :crop, 'Crop:', :class => 'control-label col-md-2' .col-md-8 = auto_suggest @seed, :crop, :class => 'form-control' + %span.help-inline + Can't find what you're looking for? + = link_to "Request new crops.", Growstuff::Application.config.new_crops_request_link .form-group = f.label :quantity, 'Quantity:', :class => 'control-label col-md-2' .col-md-2 From e3627f6dcd84cdffab2ebcb3dcb1a148fd688336 Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 29 Aug 2014 10:02:56 +1000 Subject: [PATCH 103/288] Fixed devise forms to make them b3ish and pretty --- app/views/devise/confirmations/new.html.haml | 12 ++++++---- app/views/devise/passwords/edit.html.haml | 23 ++++++++++---------- app/views/devise/passwords/new.html.haml | 20 ++++++++--------- app/views/devise/unlocks/new.html.haml | 16 +++++++------- 4 files changed, 38 insertions(+), 33 deletions(-) diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml index d190140cd..c7ebdd604 100644 --- a/app/views/devise/confirmations/new.html.haml +++ b/app/views/devise/confirmations/new.html.haml @@ -1,12 +1,16 @@ - content_for :title, "Resend confirmation instructions" -= form_for(resource, :as => resource_name, :url => confirmation_path(resource_name), :html => { :method => :post }) do |f| += form_for(resource, :as => resource_name, :url => confirmation_path(resource_name), :html => { :method => :post, :class => 'form-horizontal', :role => 'form' }) do |f| = devise_error_messages! %p Enter either your login name or your email address to resend the confirmation email. - = f.label :login - = f.text_field :login - = f.submit "Resend confirmation instructions" + .form-group + = f.label :login, :class => 'control-label col-md-2' + .col-md-8 + = f.text_field :login, :class => 'form-control' + .form-group + .form-actions.col-md-offset-2.col-md-8 + = f.submit "Resend confirmation instructions", :class => 'btn btn-primary' = render "devise/shared/links" diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml index 2860c6b22..8e31924e5 100644 --- a/app/views/devise/passwords/edit.html.haml +++ b/app/views/devise/passwords/edit.html.haml @@ -1,20 +1,21 @@ - content_for :title, "Change your password" -= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put }) do |f| += form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put, :class => 'form-horizontal', :role => 'form' }) do |f| = devise_error_messages! = f.hidden_field :reset_password_token - %div - = f.label :password, "New password" - %br - = f.password_field :password + .form-group + = f.label :password, "New password", :class => 'control-label col-md-2' + .col-md-8 + = f.password_field :password, :class => 'form-control' - %div - = f.label :password_confirmation, "Confirm new password" - %br - = f.password_field :password_confirmation + .form-group + = f.label :password_confirmation, "Confirm new password", :class => 'control-label col-md-2' + .col-md-8 + = f.password_field :password_confirmation, :class => 'form-control' - %div - = f.submit "Change my password" + .form-group + .form-actions.col-md-offset-2.col-md-8 + = f.submit "Change my password", :class => 'btn btn-primary' = render "devise/shared/links" diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml index 2027975d0..b32607760 100644 --- a/app/views/devise/passwords/new.html.haml +++ b/app/views/devise/passwords/new.html.haml @@ -1,14 +1,14 @@ - content_for :title, "Forgot your password?" -= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post }) do |f| - = devise_error_messages! += form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post, :class => 'form-horizontal', :role => 'form' }) do |f| + = devise_error_messages! - %div - = f.label :login - %br - = f.text_field :login + .form-group + = f.label :login, :class => 'control-label col-md-2' + .col-md-8 + = f.text_field :login, :class => 'form-control' + .form-group + .form-actions.col-md-offset-2.col-md-8 + = f.submit "Send me reset password instructions", :class => 'btn btn-primary' - %div - = f.submit "Send me reset password instructions" - -= render "devise/shared/links" += render "devise/shared/links" diff --git a/app/views/devise/unlocks/new.html.haml b/app/views/devise/unlocks/new.html.haml index f545cd4e7..77d726806 100644 --- a/app/views/devise/unlocks/new.html.haml +++ b/app/views/devise/unlocks/new.html.haml @@ -1,14 +1,14 @@ - content_for :title, "Resend unlock instructions" -= form_for(resource, :as => resource_name, :url => unlock_path(resource_name), :html => { :method => :post }) do |f| += form_for(resource, :as => resource_name, :url => unlock_path(resource_name), :html => { :method => :post, :class => 'form-horizontal', :role => 'form' }) do |f| = devise_error_messages! - %div - = f.label :email - %br - = f.email_field :email - - %div - = f.submit "Resend unlock instructions" + .form-group + = f.label :email, :class => 'control-label col-md-2' + .col-md-8 + = f.email_field :email, :class => 'form-control' + .form-group + .form-actions.col-md-offset-2.col-md-8 + = f.submit "Resend unlock instructions", :class => 'btn btn-primary' = render "devise/shared/links" From 30d436cb5daebcc04cca9104746a009946965953 Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 29 Aug 2014 10:19:37 +1000 Subject: [PATCH 104/288] Fixed inline form on places page to make it b3-ish --- app/views/places/show.html.haml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/views/places/show.html.haml b/app/views/places/show.html.haml index 2feaf8965..b34846b39 100644 --- a/app/views/places/show.html.haml +++ b/app/views/places/show.html.haml @@ -1,9 +1,11 @@ -content_for :title, "#{ENV['GROWSTUFF_SITE_NAME']} members near #{@place}" -= form_tag(search_places_path, :method => :get, :class => 'form-inline') do - = label_tag :place, "Change location:", :class => 'control-label' - = text_field_tag :new_place +%form{:action => search_places_path, :method => :get, :class => 'form-inline', :role => 'form'} + .form-group + = label_tag :new_place, "Change location:", :class => 'sr-only' + = text_field_tag :new_place, '', :class => 'form-control', :placeholder => "New location..." = submit_tag "Search", :class => 'btn btn-primary' +%br/ %div#placesmap{ :style => "height:300px"} From 8a5d1f2e5e30f112f210a664cd3ba9f4d54c3f9f Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 29 Aug 2014 10:38:35 +1000 Subject: [PATCH 105/288] Fixed crop wrangler tests to not be order dependent --- spec/features/crop_wranglers_spec.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/spec/features/crop_wranglers_spec.rb b/spec/features/crop_wranglers_spec.rb index 88dd277b2..033aca5f2 100644 --- a/spec/features/crop_wranglers_spec.rb +++ b/spec/features/crop_wranglers_spec.rb @@ -18,10 +18,8 @@ feature "crop wranglers" do within '.crop_wranglers' do expect(page).to have_content 'Crop Wranglers:' - crop_wranglers.each_with_index do |crop_wrangler, index| - link = find(".crop_wrangler:nth-child(#{index + 1}) a") - expect(link.text).to eq(crop_wrangler.login_name) - expect(link['href']).to eq(member_path(crop_wrangler)) + crop_wranglers.each do |crop_wrangler| + page.should have_link crop_wrangler.login_name, :href => member_path(crop_wrangler) end end end From b07d9d8db0c47482ca170348df8ae9154551db5f Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 30 Aug 2014 09:11:19 +1000 Subject: [PATCH 106/288] Added finished fields for plantings --- app/models/planting.rb | 3 ++- .../20140829230600_add_finished_to_planting.rb | 6 ++++++ db/schema.rb | 12 +++++++----- spec/factories/planting.rb | 5 +++++ spec/models/planting_spec.rb | 6 ++++++ 5 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 db/migrate/20140829230600_add_finished_to_planting.rb diff --git a/app/models/planting.rb b/app/models/planting.rb index b848d3490..afeeb2137 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -3,7 +3,8 @@ class Planting < ActiveRecord::Base friendly_id :planting_slug, use: :slugged attr_accessible :crop_id, :description, :garden_id, :planted_at, - :quantity, :sunniness, :planted_from, :owner_id + :quantity, :sunniness, :planted_from, :owner_id, :finished, + :finished_at belongs_to :garden belongs_to :owner, :class_name => 'Member', :counter_cache => true diff --git a/db/migrate/20140829230600_add_finished_to_planting.rb b/db/migrate/20140829230600_add_finished_to_planting.rb new file mode 100644 index 000000000..8be30756a --- /dev/null +++ b/db/migrate/20140829230600_add_finished_to_planting.rb @@ -0,0 +1,6 @@ +class AddFinishedToPlanting < ActiveRecord::Migration + def change + add_column :plantings, :finished, :boolean, :default => false + add_column :plantings, :finished_at, :date + end +end diff --git a/db/schema.rb b/db/schema.rb index 489a9e6b6..29271f5e6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20140718075753) do +ActiveRecord::Schema.define(:version => 20140829230600) do create_table "account_types", :force => true do |t| t.string "name", :null => false @@ -213,17 +213,19 @@ ActiveRecord::Schema.define(:version => 20140718075753) do end create_table "plantings", :force => true do |t| - t.integer "garden_id", :null => false - t.integer "crop_id", :null => false + t.integer "garden_id", :null => false + t.integer "crop_id", :null => false 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", :null => false + t.datetime "updated_at", :null => false t.string "slug" t.string "sunniness" t.string "planted_from" t.integer "owner_id" + t.boolean "finished", :default => false + t.date "finished_at" end add_index "plantings", ["slug"], :name => "index_plantings_on_slug", :unique => true diff --git a/spec/factories/planting.rb b/spec/factories/planting.rb index 6bfe8554c..5d229d545 100644 --- a/spec/factories/planting.rb +++ b/spec/factories/planting.rb @@ -30,5 +30,10 @@ FactoryGirl.define do factory :shady_planting do sunniness 'shade' end + + factory :finished_planting do + finished true + finished_at '2014-08-30' + end end end diff --git a/spec/models/planting_spec.rb b/spec/models/planting_spec.rb index eae16cbe5..65e7b890e 100644 --- a/spec/models/planting_spec.rb +++ b/spec/models/planting_spec.rb @@ -209,6 +209,12 @@ describe Planting do Planting.interesting.should_not include @planting1 end + it 'has finished fields' do + @planting = FactoryGirl.create(:finished_planting) + @planting.finished.should be true + @planting.finished_at.should be_an_instance_of Date + end + end end From e7dd50c3f18abbbad3fd2bbc81ea3efee5aef18a Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 30 Aug 2014 09:28:02 +1000 Subject: [PATCH 107/288] Added scopes for finished and current plantings --- app/models/planting.rb | 2 ++ spec/models/planting_spec.rb | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/app/models/planting.rb b/app/models/planting.rb index afeeb2137..270a827d2 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -14,6 +14,8 @@ class Planting < ActiveRecord::Base before_destroy {|planting| planting.photos.clear} default_scope order("created_at desc") + scope :finished, where(:finished => true) + scope :current, where(:finished => false) delegate :name, :en_wikipedia_url, diff --git a/spec/models/planting_spec.rb b/spec/models/planting_spec.rb index 65e7b890e..13b910d22 100644 --- a/spec/models/planting_spec.rb +++ b/spec/models/planting_spec.rb @@ -215,6 +215,20 @@ describe Planting do @planting.finished_at.should be_an_instance_of Date end + it 'has finished scope' do + @p = FactoryGirl.create(:planting) + @f = FactoryGirl.create(:finished_planting) + Planting.finished.should include @f + Planting.finished.should_not include @p + end + + it 'has current scope' do + @p = FactoryGirl.create(:planting) + @f = FactoryGirl.create(:finished_planting) + Planting.current.should include @p + Planting.current.should_not include @f + end + end end From 9f9ef10b6ab7e34ab811b43e474818ba98f3bfdc Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 30 Aug 2014 09:52:26 +1000 Subject: [PATCH 108/288] Added finished fields to planting form and display --- app/views/plantings/_form.html.haml | 10 ++++++++++ app/views/plantings/show.html.haml | 9 +++++++++ spec/features/planting_a_crop_spec.rb | 25 ++++++++++++++++++++++++- 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/app/views/plantings/_form.html.haml b/app/views/plantings/_form.html.haml index 277fe2cb9..23db0f3d5 100644 --- a/app/views/plantings/_form.html.haml +++ b/app/views/plantings/_form.html.haml @@ -37,6 +37,16 @@ .form-group = f.label :description, 'Tell us more about it', :class => 'control-label col-md-2' .col-md-8= f.text_area :description, :rows => 6, :class => 'form-control' + .form-group + = f.label :finished, 'Mark as finished', :class => 'control-label col-md-2' + .col-md-8 + = f.check_box :finished + %span.help-block + A planting is finished when you've harvested all of the crop, or it dies, or it's otherwise no longer growing in your garden. + .form-group + = f.label :finished_at, 'Finished date', { :class => 'control-label col-md-2' } + .col-md-2 + = f.text_field :finished_at, :value => @planting.finished_at ? @planting.finished_at.to_s(:ymd) : '', :class => 'add-datepicker form-control' .form-group .form-actions.col-md-offset-2.col-md-8 = f.submit 'Save', :class => 'btn btn-primary' diff --git a/app/views/plantings/show.html.haml b/app/views/plantings/show.html.haml index a6a713229..3dea2e74c 100644 --- a/app/views/plantings/show.html.haml +++ b/app/views/plantings/show.html.haml @@ -10,6 +10,15 @@ %p %b Planted: = @planting.planted_at ? @planting.planted_at : "not specified" + - if @planting.finished + %p + %b Finished: + - if @planting.finished_at + = @planting.finished_at + - else + Yes (no date specified) + %p + %b %p %b Where: =link_to "#{@planting.owner}'s", @planting.owner diff --git a/spec/features/planting_a_crop_spec.rb b/spec/features/planting_a_crop_spec.rb index ad87a35ca..52226aeee 100644 --- a/spec/features/planting_a_crop_spec.rb +++ b/spec/features/planting_a_crop_spec.rb @@ -26,4 +26,27 @@ feature "Planting a crop", :js => true do expect(page).to have_content "Planting was successfully created" end -end \ No newline at end of file + scenario "Marking a planting as finished", :js => true do + fill_autocomplete "crop", :with => "m" + select_from_autocomplete "maize" + within "form#new_planting" do + check 'Mark as finished' + fill_in "Finished date", :with => '2014-08-30' + click_button "Save" + end + expect(page).to have_content "Planting was successfully created" + expect(page).to have_content "Finished: August 30, 2014" + end + + scenario "Marking a planting as finished without a date", :js => true do + fill_autocomplete "crop", :with => "m" + select_from_autocomplete "maize" + within "form#new_planting" do + check 'Mark as finished' + click_button "Save" + end + expect(page).to have_content "Planting was successfully created" + expect(page).to have_content "Finished: Yes (no date specified)" + end + +end From facba1515471822b771fe672f8ac5e030ad62b9c Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 30 Aug 2014 10:14:11 +1000 Subject: [PATCH 109/288] Added a button on the planting details page to mark as finished --- app/controllers/plantings_controller.rb | 2 ++ app/views/plantings/show.html.haml | 14 ++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/controllers/plantings_controller.rb b/app/controllers/plantings_controller.rb index 0d419d2f1..477e9bf13 100644 --- a/app/controllers/plantings_controller.rb +++ b/app/controllers/plantings_controller.rb @@ -86,8 +86,10 @@ class PlantingsController < ApplicationController # PUT /plantings/1 # PUT /plantings/1.json def update + puts "I'm in the controller!" @planting = Planting.find(params[:id]) params[:planted_at] = parse_date(params[:planted_at]) + puts params.to_yaml respond_to do |format| if @planting.update_attributes(params[:planting]) diff --git a/app/views/plantings/show.html.haml b/app/views/plantings/show.html.haml index 3dea2e74c..72154c0a3 100644 --- a/app/views/plantings/show.html.haml +++ b/app/views/plantings/show.html.haml @@ -10,15 +10,15 @@ %p %b Planted: = @planting.planted_at ? @planting.planted_at : "not specified" + - if @planting.finished %p %b Finished: - - if @planting.finished_at - = @planting.finished_at - - else - Yes (no date specified) - %p - %b + - if @planting.finished_at + = @planting.finished_at + - else + Yes (no date specified) + %p %b Where: =link_to "#{@planting.owner}'s", @planting.owner @@ -44,6 +44,8 @@ %p - if can? :edit, @planting =link_to 'Edit', edit_planting_path(@planting), :class => 'btn btn-default btn-xs' + - if ! @planting.finished + = link_to "Mark as finished", planting_path(@planting, :planting => {:finished => 1}), :method => :put, :class => 'btn btn-default btn-xs' - if can? :destroy, @planting =link_to 'Delete', @planting, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' From 480e67bf2fa9ee6d1cf7a157fac97e051140454a Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sat, 30 Aug 2014 10:20:29 +1000 Subject: [PATCH 110/288] add default id to hidden input in auto suggest view helper to make edit work --- app/helpers/auto_suggest_helper.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/helpers/auto_suggest_helper.rb b/app/helpers/auto_suggest_helper.rb index e81664c83..0905e5257 100644 --- a/app/helpers/auto_suggest_helper.rb +++ b/app/helpers/auto_suggest_helper.rb @@ -1,15 +1,17 @@ module AutoSuggestHelper def auto_suggest(resource, source, options={}) - default = resource.send(source).nil? ? "" : resource.send(source).name + default = resource.send(source) + default_name = default.name || "" + default_id = default.id || "" resource = resource.class.name.downcase source_path = Rails.application.routes.url_helpers.send("#{source}s_search_path") %Q{ - + - + }.html_safe end From 170d6b474ece62e3292fdc9335241b9c9b4e04b2 Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 30 Aug 2014 10:41:58 +1000 Subject: [PATCH 111/288] Updated garden page with "finished" plantings Also added a similar "mark as inactive" button for the garden itself and rearranged some things on the page. --- app/controllers/plantings_controller.rb | 2 +- app/views/gardens/show.html.haml | 75 +++++++++++++++--------- app/views/plantings/_thumbnail.html.haml | 12 +++- spec/features/gardens.rb | 19 ++++++ 4 files changed, 78 insertions(+), 30 deletions(-) create mode 100644 spec/features/gardens.rb diff --git a/app/controllers/plantings_controller.rb b/app/controllers/plantings_controller.rb index 477e9bf13..7b760c7bf 100644 --- a/app/controllers/plantings_controller.rb +++ b/app/controllers/plantings_controller.rb @@ -93,7 +93,7 @@ class PlantingsController < ApplicationController respond_to do |format| if @planting.update_attributes(params[:planting]) - format.html { redirect_to @planting, notice: 'Planting was successfully updated.' } + format.html { redirect_to request.referer, notice: 'Planting was successfully updated.' } format.json { head :no_content } else format.html { render action: "edit" } diff --git a/app/views/gardens/show.html.haml b/app/views/gardens/show.html.haml index 10fc78958..b1e5f42da 100644 --- a/app/views/gardens/show.html.haml +++ b/app/views/gardens/show.html.haml @@ -2,12 +2,48 @@ .row .col-md-9 + + - if can? :edit, @garden or can? :delete, @garden + %p.btn-group + - if can? :edit, @garden + - if @garden.active + = link_to "Plant something", new_planting_path(:garden_id => @garden.id), :class => 'btn btn-primary' + = link_to "Mark as inactive", garden_path(@garden, :garden => {:active => 0}), :method => :put, :class => 'btn btn-default' + - else + = link_to "Mark as active", garden_path(@garden, :garden => {:active => 1}), :method => :put, :class => 'btn btn-default' + = link_to 'Edit garden', edit_garden_path(@garden), :class => 'btn btn-default' + - if can? :destroy, @garden + = link_to 'Delete garden', @garden, method: :delete, | + data: { confirm: 'All plantings associated with this garden will also be deleted. Are you sure?' }, :class => 'btn btn-default' + - if ! @garden.active - .alert.alert-notice - NOTE: This garden is inactive. + .alert.alert-warning + This garden is inactive. - if can? :edit, @garden = link_to 'Set it to active', edit_garden_path(@garden) to plant something in this garden. + + %div + :growstuff_markdown + #{strip_tags @garden.description} + + %h3 + What's planted here? + + - if @garden.plantings.current.count > 0 + - @garden.plantings.current.each do |p| + = render :partial => "plantings/thumbnail", :locals => { :planting => p } + - else + %p + Nothing is currently planted here. + + - if @garden.plantings.finished.count > 0 + %h3 Previously planted in this garden + - @garden.plantings.finished.each do |p| + = render :partial => "plantings/thumbnail", :locals => { :planting => p } + + .col-md-3 + %h4 About this garden %p %strong Owner: = link_to @garden.owner, @garden.owner @@ -20,24 +56,6 @@ %strong Area: = pluralize(@garden.area, @garden.area_unit) - %div - :growstuff_markdown - #{strip_tags @garden.description} - - if can? :edit, @garden - = link_to 'Edit garden', edit_garden_path(@garden), :class => 'btn btn-default btn-xs' - - if can? :destroy, @garden - = link_to 'Delete garden', @garden, method: :delete, | - data: { confirm: 'All plantings associated with this garden will also be deleted. Are you sure?' }, :class => 'btn btn-default btn-xs' - - %h3 - What's planted here? - - if can? :edit, @garden and @garden.active - = link_to "Plant something", new_planting_path(:garden_id => @garden.id), :class => 'btn btn-primary' - - - @garden.plantings.each do |p| - = render :partial => "plantings/thumbnail", :locals => { :planting => p } - - .col-md-3 %h4= "#{@garden.owner}'s gardens" %ul - @garden.owner.gardens.active.each do |othergarden| @@ -47,14 +65,15 @@ - else = link_to "#{othergarden}", garden_path(othergarden) - %h4= "Inactive gardens" - %ul - - @garden.owner.gardens.inactive.each do |othergarden| - %li - - if @garden == othergarden - = @garden - - else - = link_to "#{othergarden}", garden_path(othergarden) + - if @garden.owner.gardens.inactive.count > 0 + %h4= "Inactive gardens" + %ul + - @garden.owner.gardens.inactive.each do |othergarden| + %li + - if @garden == othergarden + = @garden + - else + = link_to "#{othergarden}", garden_path(othergarden) - if can? :create, @garden = link_to 'Add New Garden', new_garden_path, :class => 'btn btn-default btn-xs' diff --git a/app/views/plantings/_thumbnail.html.haml b/app/views/plantings/_thumbnail.html.haml index cdf011c38..c8588abf5 100644 --- a/app/views/plantings/_thumbnail.html.haml +++ b/app/views/plantings/_thumbnail.html.haml @@ -2,7 +2,7 @@ .row .col-md-3 = link_to image_tag((planting.default_photo ? planting.default_photo.thumbnail_url : 'placeholder_150.png'), :alt => '', :class => 'img-rounded'), planting - + .col-md-9 %h4 - if defined?(title) && title == 'owner' @@ -17,6 +17,14 @@ in = link_to planting.location, planting.garden + - if planting.finished + %p + Finished: + - if planting.finished_at + = planting.finished_at + - else + Yes (no date specified) + %p - if planting.quantity Quantity: @@ -33,5 +41,7 @@ %p - if can? :edit, planting =link_to 'Edit', edit_planting_path(planting), :class => 'btn btn-default btn-xs' + - if ! planting.finished + = link_to "Mark as finished", planting_path(planting, :planting => {:finished => 1}), :method => :put, :class => 'btn btn-default btn-xs' - if can? :destroy, planting =link_to 'Delete', planting, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' diff --git a/spec/features/gardens.rb b/spec/features/gardens.rb new file mode 100644 index 000000000..fff4d214b --- /dev/null +++ b/spec/features/gardens.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +feature "Planting a crop", :js => true do + let!(:garden) { FactoryGirl.create(:garden) } + + background do + login_as(garden.owner) + visit garden_path(garden) + end + + scenario "Marking a garden as inactive" do + click_link "Mark as inactive" + expect(page).to have_content "Garden was successfully updated" + expect(page).to have_content "This garden is inactive" + expect(page).to have_content "Mark as active" + expect(page).not_to have_content "Mark as inactive" + end + +end From af2d0e22ee15301a3c17e39f6ee26e2fcf9dc36f Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 30 Aug 2014 10:51:33 +1000 Subject: [PATCH 112/288] Automatically set plantings to finished when a garden is marked inactive --- app/models/garden.rb | 12 ++++++++++++ app/views/gardens/show.html.haml | 4 +++- spec/models/garden_spec.rb | 18 ++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/app/models/garden.rb b/app/models/garden.rb index cece4dfab..7f64994cc 100644 --- a/app/models/garden.rb +++ b/app/models/garden.rb @@ -14,6 +14,7 @@ class Garden < ActiveRecord::Base geocoded_by :location after_validation :geocode after_validation :empty_unwanted_geocodes + after_save :mark_inactive_garden_plantings_as_finished default_scope order("lower(name) asc") scope :active, where(:active => true) @@ -74,4 +75,15 @@ class Garden < ActiveRecord::Base name end + # When you mark a garden as inactive, all the plantings in it should be + # marked as finished. This automates that. + def mark_inactive_garden_plantings_as_finished + if (active == false) + plantings.current.each do |p| + p.finished = true + p.save + end + end + end + end diff --git a/app/views/gardens/show.html.haml b/app/views/gardens/show.html.haml index b1e5f42da..a0630d3a8 100644 --- a/app/views/gardens/show.html.haml +++ b/app/views/gardens/show.html.haml @@ -8,7 +8,9 @@ - if can? :edit, @garden - if @garden.active = link_to "Plant something", new_planting_path(:garden_id => @garden.id), :class => 'btn btn-primary' - = link_to "Mark as inactive", garden_path(@garden, :garden => {:active => 0}), :method => :put, :class => 'btn btn-default' + = link_to "Mark as inactive", garden_path(@garden, :garden => {:active => 0}), | + :method => :put, :class => 'btn btn-default', | + data: { confirm: 'All plantings associated with this garden will be marked as finished. Are you sure?' } - else = link_to "Mark as active", garden_path(@garden, :garden => {:active => 1}), :method => :put, :class => 'btn btn-default' = link_to 'Edit garden', edit_garden_path(@garden), :class => 'btn btn-default' diff --git a/spec/models/garden_spec.rb b/spec/models/garden_spec.rb index 43319daa6..e3a2e3690 100644 --- a/spec/models/garden_spec.rb +++ b/spec/models/garden_spec.rb @@ -165,4 +165,22 @@ describe Garden do end end + it "marks plantings as finished when garden is inactive" do + garden = FactoryGirl.create(:garden) + p1 = FactoryGirl.create(:planting, :garden => garden) + p2 = FactoryGirl.create(:planting, :garden => garden) + + p1.finished.should eq false + p2.finished.should eq false + + garden.active = false + garden.save + + p1.reload + p1.finished.should eq true + p2.reload + p2.finished.should eq true + + end + end From 46e20f0562bafa939ca1a3e888bad745ed252bcf Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 30 Aug 2014 11:04:31 +1000 Subject: [PATCH 113/288] Add finished planting data (and buttons) to plantings index --- app/controllers/plantings_controller.rb | 2 +- app/views/plantings/index.html.haml | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/app/controllers/plantings_controller.rb b/app/controllers/plantings_controller.rb index 7b760c7bf..477e9bf13 100644 --- a/app/controllers/plantings_controller.rb +++ b/app/controllers/plantings_controller.rb @@ -93,7 +93,7 @@ class PlantingsController < ApplicationController respond_to do |format| if @planting.update_attributes(params[:planting]) - format.html { redirect_to request.referer, notice: 'Planting was successfully updated.' } + format.html { redirect_to @planting, notice: 'Planting was successfully updated.' } format.json { head :no_content } else format.html { render action: "edit" } diff --git a/app/views/plantings/index.html.haml b/app/views/plantings/index.html.haml index 926e4a191..091974ef8 100644 --- a/app/views/plantings/index.html.haml +++ b/app/views/plantings/index.html.haml @@ -26,9 +26,9 @@ %th Owner %th Crop %th Garden - %th Description %th Quantity %th Planted on + %th Finished %th Sun/shade? %th Planted from %th @@ -39,14 +39,23 @@ %td= link_to planting.owner.login_name, planting.owner %td= link_to planting.crop.name, planting.crop %td= link_to planting.garden.name, planting.garden - %td - :growstuff_markdown - #{ strip_tags planting.description } %td= planting.quantity %td= planting.planted_at + %td + - if planting.finished and planting.finished_at + = planting.planted_at + - elsif planting.finished + Yes (no date specified) %td= planting.sunniness %td= planting.planted_from - %td= link_to 'Details', planting, :class => 'btn btn-default btn-xs' + %td + = link_to 'Details', planting, :class => 'btn btn-default btn-xs' + - if can? :edit, planting + =link_to 'Edit', edit_planting_path(planting), :class => 'btn btn-default btn-xs' + - if ! planting.finished + = link_to "Mark as finished", planting_path(planting, :planting => {:finished => 1}), :method => :put, :class => 'btn btn-default btn-xs' + - if can? :destroy, planting + =link_to 'Delete', planting, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' %div.pagination = page_entries_info @plantings, :model => "plantings" From 4ebc184188755b6fd5e0059f57b0e6a289abc855 Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 30 Aug 2014 11:04:31 +1000 Subject: [PATCH 114/288] Add finished planting data (and buttons) to plantings index I also removed description from the table as it was getting way too full --- app/views/plantings/index.csv.shaper | 5 +++++ spec/views/plantings/index.html.haml_spec.rb | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/views/plantings/index.csv.shaper b/app/views/plantings/index.csv.shaper index becb6d4d8..5bec91a4b 100644 --- a/app/views/plantings/index.csv.shaper +++ b/app/views/plantings/index.csv.shaper @@ -10,6 +10,8 @@ csv.headers :id, :planted_from, :sunniness, :date_planted, + :finished, + :date_finished, :description, :date_added, :last_modified, @@ -34,6 +36,9 @@ csv.headers :id, csv.cell :date_planted, p.planted_at ? p.planted_at.to_s(:db) : '' + csv.cell :finished + csv.cell :date_finished, p.finished_at ? p.finished_at.to_s(:db) : '' + csv.cell :description csv.cell :date_added, p.created_at.to_s(:db) diff --git a/spec/views/plantings/index.html.haml_spec.rb b/spec/views/plantings/index.html.haml_spec.rb index fb3d9d57d..5ff890514 100644 --- a/spec/views/plantings/index.html.haml_spec.rb +++ b/spec/views/plantings/index.html.haml_spec.rb @@ -36,10 +36,6 @@ describe "plantings/index" do rendered.should contain @garden.name end - it "shows descriptions where they exist" do - rendered.should contain "This is a" - end - it "displays planting time" do rendered.should contain 'January 13, 2013' end From fbeafd4299619f073d8092fb2371ea67236b9f6e Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 30 Aug 2014 11:28:52 +1000 Subject: [PATCH 115/288] Improving test coverage for gardens --- app/controllers/gardens_controller.rb | 2 +- spec/features/gardens.rb | 40 ++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/app/controllers/gardens_controller.rb b/app/controllers/gardens_controller.rb index e77c22a1b..570dcdfca 100644 --- a/app/controllers/gardens_controller.rb +++ b/app/controllers/gardens_controller.rb @@ -85,7 +85,7 @@ class GardensController < ApplicationController @garden.destroy respond_to do |format| - format.html { redirect_to @garden.owner, notice: 'Garden was successfully deleted.' } + format.html { redirect_to gardens_by_owner_path(:owner => @garden.owner), notice: 'Garden was successfully deleted.' } format.json { head :no_content } end end diff --git a/spec/features/gardens.rb b/spec/features/gardens.rb index fff4d214b..1b803a41e 100644 --- a/spec/features/gardens.rb +++ b/spec/features/gardens.rb @@ -5,10 +5,19 @@ feature "Planting a crop", :js => true do background do login_as(garden.owner) - visit garden_path(garden) + end + + scenario "View gardens" do + visit gardens_path + expect(page).to have_content "Everyone's gardens" + click_link "View your gardens" + expect(page).to have_content "#{garden.owner.login_name}'s gardens" + click_link "View everyone's gardens" + expect(page).to have_content "Everyone's gardens" end scenario "Marking a garden as inactive" do + visit garden_path(garden) click_link "Mark as inactive" expect(page).to have_content "Garden was successfully updated" expect(page).to have_content "This garden is inactive" @@ -16,4 +25,33 @@ feature "Planting a crop", :js => true do expect(page).not_to have_content "Mark as inactive" end + scenario "Create new garden" do + visit new_garden_path + fill_in "Name", :with => "New garden" + click_button "Save" + expect(page).to have_content "Garden was successfully created" + expect(page).to have_content "New garden" + end + + scenario "Edit garden" do + visit new_garden_path + fill_in "Name", :with => "New garden" + click_button "Save" + click_link "Edit garden" + fill_in "Name", :with => "Different name" + click_button "Save" + expect(page).to have_content "Garden was successfully updated" + expect(page).to have_content "Different name" + end + + scenario "Delete garden" do + visit new_garden_path + fill_in "Name", :with => "New garden" + click_button "Save" + visit garden_path(Garden.last) + click_link "Delete garden" + expect(page).to have_content "Garden was successfully deleted" + expect(page).to have_content "#{garden.owner}'s gardens" + end + end From 3fbae6a5eedd329fb0c83c8e0a9647030a377679 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sat, 30 Aug 2014 12:14:36 +1000 Subject: [PATCH 116/288] write regression spec for resource id bug when editing resource --- app/helpers/auto_suggest_helper.rb | 4 ++-- db/schema.rb | 3 ++- spec/features/harvesting_a_crop_spec.rb | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/app/helpers/auto_suggest_helper.rb b/app/helpers/auto_suggest_helper.rb index 0905e5257..f6a5bcdb7 100644 --- a/app/helpers/auto_suggest_helper.rb +++ b/app/helpers/auto_suggest_helper.rb @@ -2,8 +2,8 @@ module AutoSuggestHelper def auto_suggest(resource, source, options={}) default = resource.send(source) - default_name = default.name || "" - default_id = default.id || "" + default_name = default.try(:name) + default_id = default.try(:id) resource = resource.class.name.downcase source_path = Rails.application.routes.url_helpers.send("#{source}s_search_path") diff --git a/db/schema.rb b/db/schema.rb index 489a9e6b6..4c5ee003b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20140718075753) do +ActiveRecord::Schema.define(:version => 20140720085713) do create_table "account_types", :force => true do |t| t.string "name", :null => false @@ -139,6 +139,7 @@ ActiveRecord::Schema.define(:version => 20140718075753) do t.text "bio" t.integer "plantings_count" t.boolean "newsletter" + t.boolean "send_planting_reminder", :default => true end add_index "members", ["confirmation_token"], :name => "index_users_on_confirmation_token", :unique => true diff --git a/spec/features/harvesting_a_crop_spec.rb b/spec/features/harvesting_a_crop_spec.rb index 0645cf045..31087656f 100644 --- a/spec/features/harvesting_a_crop_spec.rb +++ b/spec/features/harvesting_a_crop_spec.rb @@ -25,4 +25,22 @@ feature "Harvesting a crop", :js => true do expect(page).to have_content "Harvest was successfully created" end + context "Editing a harvest" do + let(:existing_harvest) { FactoryGirl.create(:harvest, :crop => maize, :owner => member) } + + background do + visit harvest_path(existing_harvest) + click_link "Edit" + end + + scenario "Saving wihout edits" do + # Check that the autosuggest helper properly fills inputs with + # existing resource's data + click_button "Save" + expect(page).to have_content "Harvest was successfully updated" + expect(page).to have_content "maize" + end + + end + end \ No newline at end of file From 082663c885686e16bdba083e649cb9e3e5315996 Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 22 Aug 2014 14:09:12 +1000 Subject: [PATCH 117/288] Show coverage locally and send to coveralls --- spec/features/seeds_spec.rb | 51 +++++++++++++++++++++++++++++++++++++ spec/spec_helper.rb | 25 +++++++++++++----- 2 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 spec/features/seeds_spec.rb diff --git a/spec/features/seeds_spec.rb b/spec/features/seeds_spec.rb new file mode 100644 index 000000000..216c23877 --- /dev/null +++ b/spec/features/seeds_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +feature "seeds" do + context "signed in user" do + before(:each) do + @crop = FactoryGirl.create(:crop) + @member = FactoryGirl.create(:member) + visit root_path + click_link 'Sign in' + fill_in 'Login', :with => @member.login_name + fill_in 'Password', :with => @member.password + click_button 'Sign in' + end + + scenario "button on front page to add seeds" do + visit root_path + click_link "Add seeds" + current_path.should eq new_seed_path + page.should have_content 'Add seeds' + end + + scenario "add seeds" do + visit new_seed_path + page.should have_content 'Add seeds' + select @crop.name, :from => 'seed_crop_id' + fill_in 'seed_quantity', :with => 3 + fill_in 'seed_plant_before', :with => '2020-01-01' + fill_in 'seed_description', :with => "these are some seeds I harvested" + select "nowhere", :from => 'seed_tradable_to' + click_button 'Save' + current_path.should eq seed_path(Seed.last) + end + + scenario "edit seeds" do + seed = FactoryGirl.create(:seed, :owner => @member) + visit seed_path(seed) + click_link 'Edit' + current_path.should eq edit_seed_path(seed) + fill_in 'seed_quantity', :with => seed.quantity * 2 + click_button 'Save' + current_path.should eq seed_path(seed) + end + + scenario "delete seeds" do + seed = FactoryGirl.create(:seed, :owner => @member) + visit seed_path(seed) + click_link 'Delete' + current_path.should eq seeds_path + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8ba405425..2bf837ef2 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,20 +1,33 @@ # This file is copied to spec/ when you run 'rails generate rspec:install' ENV["RAILS_ENV"] ||= 'test' -require File.expand_path("../../config/environment", __FILE__) -require 'rspec/rails' -require 'rspec/autorun' -require 'coveralls' -require 'simplecov' + require 'capybara' require 'capybara/poltergeist' Capybara.javascript_driver = :poltergeist include Warden::Test::Helpers +require 'simplecov' SimpleCov.configure do +require 'coveralls' + +# output coverage locally AND send it to coveralls +SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ + SimpleCov::Formatter::HTMLFormatter, + Coveralls::SimpleCov::Formatter +] + +# fail if changes make overall coverage drop +SimpleCov.refuse_coverage_drop + +SimpleCov.start :rails do add_filter 'spec/' + add_filter 'vendor/' end -Coveralls.wear!('rails') + +require File.expand_path("../../config/environment", __FILE__) +require 'rspec/rails' +require 'rspec/autorun' # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. From d55d2c64094c67126ed31cb0be5afa081922e59e Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 22 Aug 2014 14:44:00 +1000 Subject: [PATCH 118/288] Ongoing coverage improvements --- app/controllers/account_types_controller.rb | 2 +- app/controllers/forums_controller.rb | 2 +- app/views/account_types/show.html.haml | 2 + app/views/admin/index.html.haml | 2 +- app/views/forums/index.html.haml | 4 ++ app/views/forums/show.html.haml | 2 + spec/features/admin/account_types_spec.rb | 50 ++++++++++++++++++ spec/features/admin/forums_spec.rb | 56 +++++++++++++++++++++ spec/spec_helper.rb | 4 +- spec/views/forums/index.html.haml_spec.rb | 2 + 10 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 spec/features/admin/account_types_spec.rb create mode 100644 spec/features/admin/forums_spec.rb diff --git a/app/controllers/account_types_controller.rb b/app/controllers/account_types_controller.rb index 68c7f750d..9095beb37 100644 --- a/app/controllers/account_types_controller.rb +++ b/app/controllers/account_types_controller.rb @@ -64,7 +64,7 @@ class AccountTypesController < ApplicationController @account_type.destroy respond_to do |format| - format.html { redirect_to account_types_url } + format.html { redirect_to account_types_url, notice: 'Account type was successfully deleted.' } end end end diff --git a/app/controllers/forums_controller.rb b/app/controllers/forums_controller.rb index c37dc4224..b77939111 100644 --- a/app/controllers/forums_controller.rb +++ b/app/controllers/forums_controller.rb @@ -80,7 +80,7 @@ class ForumsController < ApplicationController @forum.destroy respond_to do |format| - format.html { redirect_to forums_url } + format.html { redirect_to forums_url, notice: 'Forum was successfully deleted' } format.json { head :no_content } end end diff --git a/app/views/account_types/show.html.haml b/app/views/account_types/show.html.haml index cb8993a27..1bd40e690 100644 --- a/app/views/account_types/show.html.haml +++ b/app/views/account_types/show.html.haml @@ -12,4 +12,6 @@ = link_to 'Edit', edit_account_type_path(@account_type) \| += link_to 'Delete', @account_type, :method => :delete, :data => { :confirm => 'Are you sure?' } +\| = link_to 'Back', account_types_path diff --git a/app/views/admin/index.html.haml b/app/views/admin/index.html.haml index b9f814df8..18f0a043e 100644 --- a/app/views/admin/index.html.haml +++ b/app/views/admin/index.html.haml @@ -2,7 +2,7 @@ %h2 Manage -%ul +%ul#admin_links %li= link_to "Account types", account_types_path %li= link_to "Products", products_path %li= link_to "Roles", roles_path diff --git a/app/views/forums/index.html.haml b/app/views/forums/index.html.haml index 160238709..3737d3f6f 100644 --- a/app/views/forums/index.html.haml +++ b/app/views/forums/index.html.haml @@ -1,5 +1,9 @@ - content_for :title, "Forums" +- if can? :create, Forum + %p + = link_to "New forum", new_forum_path, :class => 'btn btn-default' + - @forums.each do |forum| %h2= forum %p diff --git a/app/views/forums/show.html.haml b/app/views/forums/show.html.haml index 617981814..de839a651 100644 --- a/app/views/forums/show.html.haml +++ b/app/views/forums/show.html.haml @@ -12,6 +12,8 @@ - if can? :edit, @forum =link_to "Edit", edit_forum_path(@forum), :class => 'btn btn-default btn-xs' +- if can? :delete, @forum + = link_to 'Delete', @forum, :method => :delete, :data => { :confirm => 'Are you sure?' } %h2 Posts diff --git a/spec/features/admin/account_types_spec.rb b/spec/features/admin/account_types_spec.rb new file mode 100644 index 000000000..0ec005dd2 --- /dev/null +++ b/spec/features/admin/account_types_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +feature "account types" do + context "admin user" do + before(:each) do + @member = FactoryGirl.create(:admin_member) + visit root_path + click_link 'Sign in' + fill_in 'Login', :with => @member.login_name + fill_in 'Password', :with => @member.password + click_button 'Sign in' + end + + scenario "navigating to account type admin" do + visit root_path + click_link "Admin" + current_path.should eq admin_path + click_link "Account types" + current_path.should eq account_types_path + end + + scenario "adding an account type" do + visit account_types_path + click_link "New Account type" + current_path.should eq new_account_type_path + fill_in 'Name', :with => 'Guest' + click_button 'Save' + current_path.should eq account_type_path(AccountType.last) + page.should have_content 'Account type was successfully created' + end + + scenario 'editing account type' do + a = FactoryGirl.create(:account_type) + visit account_type_path(a) + click_link 'Edit' + fill_in 'Name', :with => 'Something else' + click_button 'Save' + current_path.should eq account_type_path(a) + page.should have_content 'Account type was successfully updated' + end + + scenario 'deleting account type' do + a = FactoryGirl.create(:account_type) + visit account_type_path(a) + click_link 'Delete' + current_path.should eq account_types_path + page.should have_content 'Account type was successfully deleted' + end + end +end diff --git a/spec/features/admin/forums_spec.rb b/spec/features/admin/forums_spec.rb new file mode 100644 index 000000000..a5c6f4fb1 --- /dev/null +++ b/spec/features/admin/forums_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +feature "forums" do + context "admin user" do + before(:each) do + @member = FactoryGirl.create(:admin_member) + visit root_path + click_link 'Sign in' + fill_in 'Login', :with => @member.login_name + fill_in 'Password', :with => @member.password + click_button 'Sign in' + end + + scenario "navigating to forum admin" do + visit root_path + click_link "Admin" + current_path.should eq admin_path + within 'ul#admin_links' do + click_link "Forums" + end + current_path.should eq forums_path + page.should have_content "New forum" + end + + scenario "adding a forum" do + visit forums_path + click_link "New forum" + current_path.should eq new_forum_path + fill_in 'Name', :with => 'Discussion' + fill_in 'Description', :with => "this is a new forum" + click_button 'Save' + current_path.should eq forum_path(Forum.last) + page.should have_content 'Forum was successfully created' + end + + scenario 'editing forum' do + f = FactoryGirl.create(:forum) + visit forum_path(f) + click_link 'Edit' + fill_in 'Name', :with => 'Something else' + click_button 'Save' + f.reload + current_path.should eq forum_path(f) + page.should have_content 'Forum was successfully updated' + page.should have_content 'Something else' + end + + scenario 'deleting forum' do + f = FactoryGirl.create(:forum) + visit forum_path(f) + click_link 'Delete' + current_path.should eq forums_path + page.should have_content 'Forum was successfully deleted' + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2bf837ef2..2b1ddc0d5 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -17,8 +17,8 @@ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ Coveralls::SimpleCov::Formatter ] -# fail if changes make overall coverage drop -SimpleCov.refuse_coverage_drop +# fail if there's a significant test coverage drop +SimpleCov.maximum_coverage_drop 1 SimpleCov.start :rails do add_filter 'spec/' diff --git a/spec/views/forums/index.html.haml_spec.rb b/spec/views/forums/index.html.haml_spec.rb index 4d6d11805..d2046de48 100644 --- a/spec/views/forums/index.html.haml_spec.rb +++ b/spec/views/forums/index.html.haml_spec.rb @@ -2,6 +2,8 @@ require 'spec_helper' describe "forums/index" do before(:each) do + @admin = FactoryGirl.create(:admin_member) + controller.stub(:current_user) { @admin } @forum1 = FactoryGirl.create(:forum) @forum2 = FactoryGirl.create(:forum) assign(:forums, [ @forum1, @forum2 ]) From d991765edb20bfb0d0581cb0923f27f5e00d86d5 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 27 Aug 2014 18:42:42 +1000 Subject: [PATCH 119/288] Fixed up label with link to field it relates to --- app/views/gardens/_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/gardens/_form.html.haml b/app/views/gardens/_form.html.haml index 6313762c4..dc0c2c6a1 100644 --- a/app/views/gardens/_form.html.haml +++ b/app/views/gardens/_form.html.haml @@ -36,7 +36,7 @@ = f.select(:area_unit, Garden::AREA_UNITS_VALUES, {:include_blank => false}, :class => 'form-control') .form-group - = f.label 'Active? ', :class => 'control-label col-md-2' + = f.label :active, 'Active? ', :class => 'control-label col-md-2' .col-md-8 = f.check_box :active You can mark a garden as inactive if you no longer use it. From d35639d7cb1bda849ea471d5f817830fcaae29de Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 27 Aug 2014 18:43:44 +1000 Subject: [PATCH 120/288] Add page content test suggested by tygriffin --- spec/features/admin/account_types_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/features/admin/account_types_spec.rb b/spec/features/admin/account_types_spec.rb index 0ec005dd2..2f8cda09f 100644 --- a/spec/features/admin/account_types_spec.rb +++ b/spec/features/admin/account_types_spec.rb @@ -37,6 +37,7 @@ feature "account types" do click_button 'Save' current_path.should eq account_type_path(a) page.should have_content 'Account type was successfully updated' + page.should have_content 'Something else' end scenario 'deleting account type' do From b0378fab69a06022a794e6d6dac550361b312951 Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 30 Aug 2014 17:30:24 +1000 Subject: [PATCH 121/288] Cleaned up tests a little bit --- .../{ => harvests}/harvesting_a_crop_spec.rb | 0 .../{ => plantings}/planting_a_crop_spec.rb | 0 .../adding_seeds_spec.rb} | 2 +- .../{seeds_spec.rb => seeds/misc_seeds_spec.rb} | 14 ++------------ spec/spec_helper.rb | 17 ++++++++--------- 5 files changed, 11 insertions(+), 22 deletions(-) rename spec/features/{ => harvests}/harvesting_a_crop_spec.rb (100%) rename spec/features/{ => plantings}/planting_a_crop_spec.rb (100%) rename spec/features/{adding_a_seed_spec.rb => seeds/adding_seeds_spec.rb} (99%) rename spec/features/{seeds_spec.rb => seeds/misc_seeds_spec.rb} (66%) diff --git a/spec/features/harvesting_a_crop_spec.rb b/spec/features/harvests/harvesting_a_crop_spec.rb similarity index 100% rename from spec/features/harvesting_a_crop_spec.rb rename to spec/features/harvests/harvesting_a_crop_spec.rb diff --git a/spec/features/planting_a_crop_spec.rb b/spec/features/plantings/planting_a_crop_spec.rb similarity index 100% rename from spec/features/planting_a_crop_spec.rb rename to spec/features/plantings/planting_a_crop_spec.rb diff --git a/spec/features/adding_a_seed_spec.rb b/spec/features/seeds/adding_seeds_spec.rb similarity index 99% rename from spec/features/adding_a_seed_spec.rb rename to spec/features/seeds/adding_seeds_spec.rb index 018531ec5..196fa7b47 100644 --- a/spec/features/adding_a_seed_spec.rb +++ b/spec/features/seeds/adding_seeds_spec.rb @@ -25,4 +25,4 @@ feature "Harvesting a crop", :js => true do expect(page).to have_content "Successfully added maize seed to your stash" end -end \ No newline at end of file +end diff --git a/spec/features/seeds_spec.rb b/spec/features/seeds/misc_seeds_spec.rb similarity index 66% rename from spec/features/seeds_spec.rb rename to spec/features/seeds/misc_seeds_spec.rb index 216c23877..e72e3b68f 100644 --- a/spec/features/seeds_spec.rb +++ b/spec/features/seeds/misc_seeds_spec.rb @@ -19,24 +19,14 @@ feature "seeds" do page.should have_content 'Add seeds' end - scenario "add seeds" do - visit new_seed_path - page.should have_content 'Add seeds' - select @crop.name, :from => 'seed_crop_id' - fill_in 'seed_quantity', :with => 3 - fill_in 'seed_plant_before', :with => '2020-01-01' - fill_in 'seed_description', :with => "these are some seeds I harvested" - select "nowhere", :from => 'seed_tradable_to' - click_button 'Save' - current_path.should eq seed_path(Seed.last) - end + # actually adding seeds is in spec/features/seeds_new_spec.rb scenario "edit seeds" do seed = FactoryGirl.create(:seed, :owner => @member) visit seed_path(seed) click_link 'Edit' current_path.should eq edit_seed_path(seed) - fill_in 'seed_quantity', :with => seed.quantity * 2 + fill_in 'Quantity:', :with => seed.quantity * 2 click_button 'Save' current_path.should eq seed_path(seed) end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2b1ddc0d5..9af45a394 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,14 +1,11 @@ # This file is copied to spec/ when you run 'rails generate rspec:install' ENV["RAILS_ENV"] ||= 'test' -require 'capybara' -require 'capybara/poltergeist' -Capybara.javascript_driver = :poltergeist - -include Warden::Test::Helpers +require File.expand_path("../../config/environment", __FILE__) +require 'rspec/rails' +require 'rspec/autorun' require 'simplecov' -SimpleCov.configure do require 'coveralls' # output coverage locally AND send it to coveralls @@ -25,9 +22,11 @@ SimpleCov.start :rails do add_filter 'vendor/' end -require File.expand_path("../../config/environment", __FILE__) -require 'rspec/rails' -require 'rspec/autorun' +require 'capybara' +require 'capybara/poltergeist' +Capybara.javascript_driver = :poltergeist + +include Warden::Test::Helpers # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. From 597e8784137160a6dc204a9961337be68f65c207 Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 30 Aug 2014 17:40:16 +1000 Subject: [PATCH 122/288] Removed spurious seeds tests (moved to subdir) --- spec/features/seeds_spec.rb | 51 ------------------------------------- 1 file changed, 51 deletions(-) delete mode 100644 spec/features/seeds_spec.rb diff --git a/spec/features/seeds_spec.rb b/spec/features/seeds_spec.rb deleted file mode 100644 index 216c23877..000000000 --- a/spec/features/seeds_spec.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'spec_helper' - -feature "seeds" do - context "signed in user" do - before(:each) do - @crop = FactoryGirl.create(:crop) - @member = FactoryGirl.create(:member) - visit root_path - click_link 'Sign in' - fill_in 'Login', :with => @member.login_name - fill_in 'Password', :with => @member.password - click_button 'Sign in' - end - - scenario "button on front page to add seeds" do - visit root_path - click_link "Add seeds" - current_path.should eq new_seed_path - page.should have_content 'Add seeds' - end - - scenario "add seeds" do - visit new_seed_path - page.should have_content 'Add seeds' - select @crop.name, :from => 'seed_crop_id' - fill_in 'seed_quantity', :with => 3 - fill_in 'seed_plant_before', :with => '2020-01-01' - fill_in 'seed_description', :with => "these are some seeds I harvested" - select "nowhere", :from => 'seed_tradable_to' - click_button 'Save' - current_path.should eq seed_path(Seed.last) - end - - scenario "edit seeds" do - seed = FactoryGirl.create(:seed, :owner => @member) - visit seed_path(seed) - click_link 'Edit' - current_path.should eq edit_seed_path(seed) - fill_in 'seed_quantity', :with => seed.quantity * 2 - click_button 'Save' - current_path.should eq seed_path(seed) - end - - scenario "delete seeds" do - seed = FactoryGirl.create(:seed, :owner => @member) - visit seed_path(seed) - click_link 'Delete' - current_path.should eq seeds_path - end - end -end From e7d9ebe37a00f5b5337cdb1ed3aaaebcb5bbaac4 Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 30 Aug 2014 18:05:11 +1000 Subject: [PATCH 123/288] b3ify 'sort by' dropdown on crops page --- app/views/crops/index.html.haml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/views/crops/index.html.haml b/app/views/crops/index.html.haml index f3cbce84e..1b5663c25 100644 --- a/app/views/crops/index.html.haml +++ b/app/views/crops/index.html.haml @@ -5,9 +5,10 @@ View any crop page to see which of our members have planted it and find information on how to grow it yourself. -= form_tag(crops_path, :method => :get, :class => 'form-inline') do - = label_tag :sort, "Sort by:", :class => 'control-label' - = select_tag "sort", options_for_select({"Popularity" => 'popular', "Alphabetical" => 'alpha'}, @sort || 'popular') += form_tag(crops_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 by popularity" => 'popular', "Sort alphabetically" => 'alpha'}, @sort || 'popular'), :class => 'form-control' = submit_tag "Show", :class => 'btn btn-primary' %div.pagination From 99ffa3775e9a4c2ef6a1c617fd5f68cf0510170b Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 30 Aug 2014 18:38:16 +1000 Subject: [PATCH 124/288] Remove puts used for debugging --- app/controllers/plantings_controller.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/controllers/plantings_controller.rb b/app/controllers/plantings_controller.rb index 477e9bf13..0d419d2f1 100644 --- a/app/controllers/plantings_controller.rb +++ b/app/controllers/plantings_controller.rb @@ -86,10 +86,8 @@ class PlantingsController < ApplicationController # PUT /plantings/1 # PUT /plantings/1.json def update - puts "I'm in the controller!" @planting = Planting.find(params[:id]) params[:planted_at] = parse_date(params[:planted_at]) - puts params.to_yaml respond_to do |format| if @planting.update_attributes(params[:planting]) From 474b56cf6b00dc679e1cca7cc500603c41e94d50 Mon Sep 17 00:00:00 2001 From: Skud Date: Sun, 31 Aug 2014 17:54:07 +1000 Subject: [PATCH 125/288] Updated devise tokens in emails for devise 3.2 --- app/views/devise/mailer/confirmation_instructions.html.haml | 2 +- app/views/devise/mailer/reset_password_instructions.html.haml | 2 +- app/views/devise/mailer/unlock_instructions.html.haml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/devise/mailer/confirmation_instructions.html.haml b/app/views/devise/mailer/confirmation_instructions.html.haml index 6a82d732a..d910951f6 100644 --- a/app/views/devise/mailer/confirmation_instructions.html.haml +++ b/app/views/devise/mailer/confirmation_instructions.html.haml @@ -5,7 +5,7 @@ Your account on #{site_name} has been created. You just need to confirm your email address through the link below: -%p= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) +%p= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @token) %p Once you're confirmed, you can sign in with your login name diff --git a/app/views/devise/mailer/reset_password_instructions.html.haml b/app/views/devise/mailer/reset_password_instructions.html.haml index d8efdd93e..4a80b1065 100644 --- a/app/views/devise/mailer/reset_password_instructions.html.haml +++ b/app/views/devise/mailer/reset_password_instructions.html.haml @@ -5,7 +5,7 @@ Someone has requested a link to reset your password on #{site_name}. We presume this was you, in which case you can do so through this link: -%p= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @resource.reset_password_token) +%p= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @token) %p If it wasn't you, then someone's made a typo or has been messing diff --git a/app/views/devise/mailer/unlock_instructions.html.haml b/app/views/devise/mailer/unlock_instructions.html.haml index cb6580971..971a00d59 100644 --- a/app/views/devise/mailer/unlock_instructions.html.haml +++ b/app/views/devise/mailer/unlock_instructions.html.haml @@ -8,7 +8,7 @@ forgotten your password. In either case, use the link below to unlock your account: -%p= link_to 'Unlock my account', unlock_url(@resource, :unlock_token => @resource.unlock_token) +%p= link_to 'Unlock my account', unlock_url(@resource, :unlock_token => @token) %p If you have actually forgotten your password, you can From 965a3bf2a1815238f7591446893bcac39f6b1f1f Mon Sep 17 00:00:00 2001 From: Skud Date: Sun, 31 Aug 2014 18:23:08 +1000 Subject: [PATCH 126/288] Modernise redirection after signup to use devise's stored_location_for --- app/controllers/application_controller.rb | 14 ++++++++----- config/locales/devise.en.yml | 2 +- spec/features/signin.rb | 24 +++++++++++++++++++++++ 3 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 spec/features/signin.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 556f811e6..0d07cb866 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -6,15 +6,19 @@ class ApplicationController < ActionController::Base after_filter :store_location def store_location - # store last url - this is needed for post-login redirect to whatever the user last visited. - if (request.fullpath != new_member_session_path && \ - !request.xhr?) # don't store ajax calls - session[:previous_url] = request.fullpath + if (request.path != "/members/sign_in" && + request.path != "/members/sign_up" && + request.path != "/members/password/new" && + request.path != "/members/password/edit" && + request.path != "/members/confirmation" && + request.path != "/members/sign_out" && + !request.xhr?) + store_location_for(:member, request.fullpath) end end def after_sign_in_path_for(resource) - session[:previous_url] || root_path + stored_location_for(:member) || root_path end # tweak CanCan defaults because we don't have a "current_user" method diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml index 55bbf9e81..669c8cd38 100644 --- a/config/locales/devise.en.yml +++ b/config/locales/devise.en.yml @@ -38,7 +38,7 @@ en: confirmations: send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.' send_paranoid_instructions: 'If your email address exists in our database, you will receive an email with instructions about how to confirm your account in a few minutes.' - confirmed: 'Your account was successfully confirmed. You are now signed in.' + confirmed: 'Your account was successfully confirmed.' registrations: signed_up: 'Welcome! You have signed up successfully.' signed_up_but_unconfirmed: 'A message with a confirmation link has been sent to your email address. Please open the link to activate your account.' diff --git a/spec/features/signin.rb b/spec/features/signin.rb new file mode 100644 index 000000000..b4a5eb7bb --- /dev/null +++ b/spec/features/signin.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +feature "signin" do + let(:member){FactoryGirl.create(:member)} + + scenario "redirect to previous page after signin" do + visit crops_path # some random page + click_link 'Sign in' + fill_in 'Login', with: member.login_name + fill_in 'Password', with: member.password + click_button 'Sign in' + current_path.should eq crops_path + end + + scenario "don't redirect to devise pages after signin" do + visit new_member_registration_path # devise signup page + click_link 'Sign in' + fill_in 'Login', with: member.login_name + fill_in 'Password', with: member.password + click_button 'Sign in' + current_path.should eq root_path + end + +end From a8a34d8dc8fac4bdbe99b6387346af3c1b58fb7e Mon Sep 17 00:00:00 2001 From: Skud Date: Sun, 31 Aug 2014 18:28:50 +1000 Subject: [PATCH 127/288] s/user/member/g in crop wrangler tests We don't use the term "user" in Growstuff -- this should ideally have been caught in code review but I missed it :-/ --- spec/features/crop_wranglers_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/features/crop_wranglers_spec.rb b/spec/features/crop_wranglers_spec.rb index 033aca5f2..80475bd18 100644 --- a/spec/features/crop_wranglers_spec.rb +++ b/spec/features/crop_wranglers_spec.rb @@ -1,16 +1,16 @@ require 'spec_helper' feature "crop wranglers" do - context "signed in user" do + context "signed in member" do let!(:crop_wranglers) { FactoryGirl.create_list(:crop_wrangling_member, 3) } - let(:user){crop_wranglers.first} + let(:member){crop_wranglers.first} before :each do visit root_path click_link 'Sign in' - fill_in 'Login', with: user.login_name - fill_in 'Password', with: user.password + fill_in 'Login', with: member.login_name + fill_in 'Password', with: member.password click_button 'Sign in' - page.should have_content user.login_name + page.should have_content member.login_name end scenario "crop wranglers are listed on the crop wrangler page" do From c4bd4c178de8cb8e2016b1438335234b56c340c5 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Mon, 1 Sep 2014 20:40:16 +1000 Subject: [PATCH 128/288] add a little offset on crop index card to prevent info from smooshing against pic --- app/views/crops/_index_card.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/crops/_index_card.html.haml b/app/views/crops/_index_card.html.haml index a54898eb4..52e2dbc5f 100644 --- a/app/views/crops/_index_card.html.haml +++ b/app/views/crops/_index_card.html.haml @@ -2,7 +2,7 @@ .row .col-md-4 = link_to image_tag((crop.default_photo ? crop.default_photo.thumbnail_url : 'placeholder_150.png'), :alt => '', :class => 'img-rounded'), crop - .col-md-8 + .col-md-7.col-md-offset-1 %h3{:style => 'padding-top: 0px; margin-top: 0px'} = link_to crop, crop From 0af550b6e7fa192ac9bd04ddf64727f1ec5f1c55 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Mon, 1 Sep 2014 20:45:45 +1000 Subject: [PATCH 129/288] make top nav collapse threshold larger to prevent it from becoming too big at some browser sizes --- app/assets/stylesheets/custom_bootstrap/variables.less | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/stylesheets/custom_bootstrap/variables.less b/app/assets/stylesheets/custom_bootstrap/variables.less index b4c9eeeb5..9bb1c6a23 100644 --- a/app/assets/stylesheets/custom_bootstrap/variables.less +++ b/app/assets/stylesheets/custom_bootstrap/variables.less @@ -44,6 +44,9 @@ @navbar-default-link-active-color: @beige; @navbar-default-brand-color: lighten(@green, 20%); +// Top nav collapse threshold +@grid-float-breakpoint: 1000px; + @dropdown-bg: lighten(@beige, 10%); @dropdown-link-color: @brown; @dropdown-link-hover-color: @brown; From c9cd82d7fd2372ff05c10405bb31509047745af1 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Mon, 1 Sep 2014 20:53:48 +1000 Subject: [PATCH 130/288] increase z-index of autosuggest dropdown --- app/assets/stylesheets/overrides.css.less | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/assets/stylesheets/overrides.css.less b/app/assets/stylesheets/overrides.css.less index 969d0750a..b15b701ba 100644 --- a/app/assets/stylesheets/overrides.css.less +++ b/app/assets/stylesheets/overrides.css.less @@ -198,3 +198,9 @@ footer .navbar .nav { display: block; } } + +// Autosuggest + +.ui-autocomplete { + z-index: @zindex-tooltip; +} From 703de076477cb3aac5b20c5beadc598f55e74f1c Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Mon, 1 Sep 2014 20:55:54 +1000 Subject: [PATCH 131/288] add crop image class to prevent smooshing again neighboring div --- app/views/crops/_index_card.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/crops/_index_card.html.haml b/app/views/crops/_index_card.html.haml index 52e2dbc5f..89e5f4e65 100644 --- a/app/views/crops/_index_card.html.haml +++ b/app/views/crops/_index_card.html.haml @@ -1,8 +1,8 @@ .well .row .col-md-4 - = link_to image_tag((crop.default_photo ? crop.default_photo.thumbnail_url : 'placeholder_150.png'), :alt => '', :class => 'img-rounded'), crop - .col-md-7.col-md-offset-1 + = link_to image_tag((crop.default_photo ? crop.default_photo.thumbnail_url : 'placeholder_150.png'), :alt => '', :class => 'img-rounded crop-image'), crop + .col-md-7 %h3{:style => 'padding-top: 0px; margin-top: 0px'} = link_to crop, crop From 7518fbec1c5cb62cc2dc5a364080ed62fecc0fb9 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Mon, 1 Sep 2014 20:58:48 +1000 Subject: [PATCH 132/288] add styles for alerts --- app/views/layouts/application.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 4e19be59a..aa674d95a 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -13,7 +13,7 @@ .alert.alert-success = notice - if alert - .alert + .alert.alert-warning = alert = yield From 0687625049519b779b25afc2b74bdc02ef78e9da Mon Sep 17 00:00:00 2001 From: Skud Date: Mon, 1 Sep 2014 22:30:08 +1000 Subject: [PATCH 133/288] Quick and dirty fix for layout of settings tabs --- app/views/devise/registrations/_edit_apps.html.haml | 1 + app/views/devise/registrations/_edit_email.html.haml | 1 + app/views/devise/registrations/_edit_password.html.haml | 1 + app/views/devise/registrations/_edit_profile.html.haml | 1 + 4 files changed, 4 insertions(+) diff --git a/app/views/devise/registrations/_edit_apps.html.haml b/app/views/devise/registrations/_edit_apps.html.haml index df5d17c88..6445c0bc0 100644 --- a/app/views/devise/registrations/_edit_apps.html.haml +++ b/app/views/devise/registrations/_edit_apps.html.haml @@ -1,4 +1,5 @@ = form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put, :class => 'form-horizontal' }) do |f| + %br/ = devise_error_messages! .row diff --git a/app/views/devise/registrations/_edit_email.html.haml b/app/views/devise/registrations/_edit_email.html.haml index 61df13f91..ca0db1131 100644 --- a/app/views/devise/registrations/_edit_email.html.haml +++ b/app/views/devise/registrations/_edit_email.html.haml @@ -1,4 +1,5 @@ = form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put, :class => 'form-horizontal' }) do |f| + %br/ = devise_error_messages! .form-group diff --git a/app/views/devise/registrations/_edit_password.html.haml b/app/views/devise/registrations/_edit_password.html.haml index c70edc96f..3e660ab98 100644 --- a/app/views/devise/registrations/_edit_password.html.haml +++ b/app/views/devise/registrations/_edit_password.html.haml @@ -1,4 +1,5 @@ = form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put, :class => 'form-horizontal' }) do |f| + %br/ = devise_error_messages! .form-group diff --git a/app/views/devise/registrations/_edit_profile.html.haml b/app/views/devise/registrations/_edit_profile.html.haml index fe33c6fdf..aa3ad0ced 100644 --- a/app/views/devise/registrations/_edit_profile.html.haml +++ b/app/views/devise/registrations/_edit_profile.html.haml @@ -1,4 +1,5 @@ = form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put, :class => 'form-horizontal' }) do |f| + %br/ = devise_error_messages! .form-group From 1224bd0ebba9065c1496f2384ee97217369c9dbf Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Wed, 3 Sep 2014 07:28:27 +1000 Subject: [PATCH 134/288] import original variables into custom --- app/assets/stylesheets/custom_bootstrap/variables.less | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/custom_bootstrap/variables.less b/app/assets/stylesheets/custom_bootstrap/variables.less index 9bb1c6a23..37d3a1904 100644 --- a/app/assets/stylesheets/custom_bootstrap/variables.less +++ b/app/assets/stylesheets/custom_bootstrap/variables.less @@ -1,5 +1,8 @@ // Use this file to override Twitter Bootstrap variables or define own variables. +// Import original variables so they can be used in overrides +@import "twitter/bootstrap/variables.less"; + // Base colours @beige: #f3f1ee; @@ -45,7 +48,7 @@ @navbar-default-brand-color: lighten(@green, 20%); // Top nav collapse threshold -@grid-float-breakpoint: 1000px; +@grid-float-breakpoint: @screen-md; @dropdown-bg: lighten(@beige, 10%); @dropdown-link-color: @brown; From 1fc100eca459295d7ae07671b22288c6d1725d2b Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Wed, 3 Sep 2014 07:52:45 +1000 Subject: [PATCH 135/288] update gem and finesse signup widget --- Gemfile | 7 +++---- Gemfile.lock | 18 +++++++++--------- .../custom_bootstrap/custom_bootstrap.less | 15 +++------------ .../custom_bootstrap/variables.less | 2 +- app/assets/stylesheets/overrides.css.less | 2 +- 5 files changed, 17 insertions(+), 27 deletions(-) diff --git a/Gemfile b/Gemfile index 7e96469c8..e47403546 100644 --- a/Gemfile +++ b/Gemfile @@ -54,10 +54,10 @@ group :assets do gem 'libv8', '3.16.14.3' # Another CSS preprocessor, used for Bootstrap overrides - gem "less", '~>2.3.2' - gem "less-rails", '~> 2.3.3' + gem "less", '~>2.5.0' + gem "less-rails", '~> 2.5.0' # CSS framework - gem "less-rails-bootstrap" + gem "less-rails-bootstrap", '~> 3.2.0' gem 'uglifier', '>= 1.0.3' # JavaScript compressor @@ -124,7 +124,6 @@ group :development, :test do gem 'database_cleaner', '~> 1.3.0' gem 'webrat' # provides HTML matchers for view tests gem 'factory_girl_rails', '~> 4.0' # for creating test data - gem 'capybara' gem 'coveralls', require: false # coverage analysis gem 'capybara' # integration tests gem 'poltergeist', '~> 1.5.1' # for headless JS testing diff --git a/Gemfile.lock b/Gemfile.lock index 11efdb043..7829a34de 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -159,13 +159,13 @@ GEM leaflet-markercluster-rails (0.7.0) railties (>= 3.1) leaflet-rails (0.7.3) - less (2.3.3) - commonjs (~> 0.2.6) - less-rails (2.3.3) + less (2.5.1) + commonjs (~> 0.2.7) + less-rails (2.5.0) actionpack (>= 3.1) - less (~> 2.3.1) - less-rails-bootstrap (3.0.2) - less-rails (~> 2.3.1) + less (~> 2.5.0) + less-rails-bootstrap (3.2.0) + less-rails (~> 2.5.0) letter_opener (1.2.0) launchy (~> 2.2) libv8 (3.16.14.3) @@ -331,9 +331,9 @@ DEPENDENCIES json (~> 1.7.7) leaflet-markercluster-rails leaflet-rails - less (~> 2.3.2) - less-rails (~> 2.3.3) - less-rails-bootstrap + less (~> 2.5.0) + less-rails (~> 2.5.0) + less-rails-bootstrap (~> 3.2.0) letter_opener libv8 (= 3.16.14.3) memcachier diff --git a/app/assets/stylesheets/custom_bootstrap/custom_bootstrap.less b/app/assets/stylesheets/custom_bootstrap/custom_bootstrap.less index effdaefb6..d9c85016c 100644 --- a/app/assets/stylesheets/custom_bootstrap/custom_bootstrap.less +++ b/app/assets/stylesheets/custom_bootstrap/custom_bootstrap.less @@ -2,25 +2,16 @@ // !!! AUTOMATICALLY GENERATED FILE. DO NOT MODIFY !!! // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -/*! - * Bootstrap v3.0.0 - * - * Copyright 2013 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world by @mdo and @fat. - */ - // Core variables and mixins @import "twitter/bootstrap/variables.less"; @import "custom_bootstrap/variables.less"; // Modify this for custom colors, font-sizes, etc @import "twitter/bootstrap/mixins.less"; @import "custom_bootstrap/mixins.less"; // Modify this for custom mixins -// Reset +// Reset and dependencies @import "twitter/bootstrap/normalize.less"; @import "twitter/bootstrap/print.less"; +@import "twitter/bootstrap/glyphicons.less"; // Core CSS @import "twitter/bootstrap/scaffolding.less"; @@ -33,7 +24,6 @@ // Components @import "twitter/bootstrap/component-animations.less"; -@import "twitter/bootstrap/glyphicons.less"; @import "twitter/bootstrap/dropdowns.less"; @import "twitter/bootstrap/button-groups.less"; @import "twitter/bootstrap/input-groups.less"; @@ -51,6 +41,7 @@ @import "twitter/bootstrap/media.less"; @import "twitter/bootstrap/list-group.less"; @import "twitter/bootstrap/panels.less"; +@import "twitter/bootstrap/responsive-embed.less"; @import "twitter/bootstrap/wells.less"; @import "twitter/bootstrap/close.less"; diff --git a/app/assets/stylesheets/custom_bootstrap/variables.less b/app/assets/stylesheets/custom_bootstrap/variables.less index 37d3a1904..9601a8f0a 100644 --- a/app/assets/stylesheets/custom_bootstrap/variables.less +++ b/app/assets/stylesheets/custom_bootstrap/variables.less @@ -48,7 +48,7 @@ @navbar-default-brand-color: lighten(@green, 20%); // Top nav collapse threshold -@grid-float-breakpoint: @screen-md; +@grid-float-breakpoint: @screen-md-min; @dropdown-bg: lighten(@beige, 10%); @dropdown-link-color: @brown; diff --git a/app/assets/stylesheets/overrides.css.less b/app/assets/stylesheets/overrides.css.less index b15b701ba..ab9de14d1 100644 --- a/app/assets/stylesheets/overrides.css.less +++ b/app/assets/stylesheets/overrides.css.less @@ -75,7 +75,7 @@ h3 { border-radius: 6px; padding: 15px; text-align: center; - line-height: 110%; + line-height: 200%; } // stats shown on homepage. eg. "999 members..." From 982572d2bdf01a11cb81333eb6312764f00aec26 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Wed, 3 Sep 2014 08:02:53 +1000 Subject: [PATCH 136/288] slight change to width of index card --- app/views/crops/_index_card.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/crops/_index_card.html.haml b/app/views/crops/_index_card.html.haml index 89e5f4e65..df706401b 100644 --- a/app/views/crops/_index_card.html.haml +++ b/app/views/crops/_index_card.html.haml @@ -2,7 +2,7 @@ .row .col-md-4 = link_to image_tag((crop.default_photo ? crop.default_photo.thumbnail_url : 'placeholder_150.png'), :alt => '', :class => 'img-rounded crop-image'), crop - .col-md-7 + .col-md-8 %h3{:style => 'padding-top: 0px; margin-top: 0px'} = link_to crop, crop From 326248797e82f95a16b53edc64a2891e53662bb1 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 3 Sep 2014 09:00:55 +1000 Subject: [PATCH 137/288] Updated readme and contributors --- CONTRIBUTORS.md | 1 + README.md | 31 +++++++++++++++++++++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 811c7f1ad..9e3de1fb4 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -42,3 +42,4 @@ submit the change with your pull request. - Amelia Greenhall / [ameliagreenhall](https://github.com/ameliagreenhall) - Barb Natali / [barbnatali](https://github.com/barbnatali) - Taylor Griffin / [tygriffin](https://github.com/tygriffin) +- Marlena Compton / [Marlena](https://github.com/marlena) diff --git a/README.md b/README.md index 40911c0e8..b4c49cc9e 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,32 @@ Welcome to the Growstuff project. -Growstuff is an open source/open data project to create a website for food gardeners. +Growstuff is an open source/open data project to create a website for +food gardeners. We crowdsource information on what our members are +growing and harvesting, aggregate it, and make it available as open data +via our API. -You can find most of our documentation at: http://wiki.growstuff.org/ +Growstuff was founded in 2012 and has been built by dozens of +[contributors](CONTRIBUTORS.md). We are an inclusive, welcoming project, and +encourage participation from people of all backgrounds and skill levels. -Our development uses Extreme Programming practices, which means we pair on all our work. Feel free to fork our code and explore, but if you would like to contribute back to us, -please read http://wiki.growstuff.org/index.php/Development_process_overview before sending us a pull request. +## Important links + +* [Wiki](http://wiki.growstuff.org/) (general documentation) +* [Task tracker](http://tracker.growstuff.org/) (recent and upcoming work, bugs, etc) +* [Discussion forums](http://wiki.growstuff.org/index.php/Discussion_forums) (mailing lists, IRC, etc) + +## For developers + +* Start by looking at our [task tracker](http://tracker.growstuff.org/) for something to work on. +* Drop in to one of our [discussion forums](http://wiki.growstuff.org/index.php/Discussion_forums) to talk to our team about it. +* To set up your development environment, see [Getting started](http://wiki.growstuff.org/index.php/Development/Getting_Started). +* We encourage [pair programming](http://wiki.growstuff.org/index.php/Pairing), especially for newer developers. +* You may also be interested in our [API](http://wiki.growstuff.org/index.php/API). + +## Contact + +For more information about this project, contact [info@growstuff.org](mailto:info@growstuff.org). + +You can also contact us on [Twitter](http://twitter.com/growstufforg/) or +[Facebook](https://www.facebook.com/pages/Growstuff/1531133417099494). From 35470acb86d0724f7a1c75cb1aedc343bcec7e14 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 3 Sep 2014 09:06:59 +1000 Subject: [PATCH 138/288] updated CONTRIBUTING.md with more up to date info --- CONTRIBUTING.md | 42 ++++++++++-------------------------------- 1 file changed, 10 insertions(+), 32 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 156c83eba..4b582e92a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,35 +1,13 @@ -Thanks for contributing to Growstuff! We have different contribution -guidelines depending on whether your change is a small one (a one-line -bugfix or similar) or a larger one. +Thanks for contributing to Growstuff! -## Small changes +Please include the following information in your pull request: -Send us a pull request! We will get one of our pairs of coders to -review it. - -If you are interested in becoming a more regular contributor or working -on larger features, please see the [Growstuff -wiki](http://wiki.growstuff.org/) for more information. - -## Larger changes - -Growstuff does pair programming (two coders working together) for all -new features and other significant changes. This means that if you -submit a pull request and weren't working in a pair, we're unlikely to -merge it into the project as-is. - -If you would like to work on any larger change, we would appreciate it -if you would get in touch with us, preferably via our [mailing -list](http://lists.growstuff.org/mailman/listinfo/discuss), and talk to -us about it first. We'll try and hook you up with a partner so you can -work as a pair, either in person or remotely depending on where you are. -The [Growstuff wiki](http://wiki.growstuff.org/) has lots more -information on our dev process, to get you started if you would like to -join us. - -If you submit a larger change without working in a pair, we will treat -your work as an experimental "spike" and get one of our pairs of -programmers to look over it and maybe use what you've done as the basis -for re-implementing it using our processes. **We'd much rather work -with you, so please talk to us first!** +* A link to the [Pivotal Tracker](http://tracker.growstuff.org/) task to which it relates. +* Your code should follow our [Coding style guide](http://wiki.growstuff.org/index.php/Coding_style_guide) +* Make sure you have automated tests for your work, where possible. +* Add your name (and that of your pair partner, if any) to [CONTRIBUTORS.md](CONTRIBUTORS.md). +If you would like to discuss your work before submitting a pull request, +please join any of our [Discussion +forums](http://wiki.growstuff.org/index.php/Discussion_forums), where +our dev team will be happy to help you. From 591b8e03d6ee6ce544c598c49d37657801324b6a Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 3 Sep 2014 09:08:18 +1000 Subject: [PATCH 139/288] we don't always work in pairs, just often --- CONTRIBUTORS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 9e3de1fb4..e12ea4142 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,5 +1,5 @@ This is a list of contributors to Growstuff's codebase. We maintain -this list because we work in pairs, but Github only knows about the +this list because we often work in pairs, but Github only knows about the person who actually does the commits. This gives credit to both members of the pair. From cdbc4611c946dd4288d015bb70e05567753765c6 Mon Sep 17 00:00:00 2001 From: Skud Date: Thu, 4 Sep 2014 17:48:00 +1000 Subject: [PATCH 140/288] Fix up crop photo wrapping mess --- app/views/crops/_photos.html.haml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/crops/_photos.html.haml b/app/views/crops/_photos.html.haml index bc62ae216..d7284078f 100644 --- a/app/views/crops/_photos.html.haml +++ b/app/views/crops/_photos.html.haml @@ -1,5 +1,5 @@ .row -- if !crop.photos.empty? - - crop.photos.first(3).each do |p| - .col-md-4 - = render :partial => "photos/thumbnail", :locals => { :photo => p } + - if !crop.photos.empty? + - crop.photos.first(3).each do |p| + .col-md-4 + = render :partial => "photos/thumbnail", :locals => { :photo => p } From 579f79760b310924bb2d85612e1fe4dcd21596e1 Mon Sep 17 00:00:00 2001 From: Skud Date: Thu, 4 Sep 2014 17:51:13 +1000 Subject: [PATCH 141/288] remove bullet points from new photo page --- app/views/photos/new.html.haml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/app/views/photos/new.html.haml b/app/views/photos/new.html.haml index fb06bdeba..32ea66915 100644 --- a/app/views/photos/new.html.haml +++ b/app/views/photos/new.html.haml @@ -21,14 +21,13 @@ = page_entries_info @photos, :model => "photos" = will_paginate @photos - %ul.thumbnails - .row - - @photos.each do |p| - %li.col-md-2.six-across - .thumbnail(style='height: 220px') - = link_to image_tag(FlickRaw.url_q(p), :alt => '', :class => 'img-rounded'), photos_path(:photo => { :flickr_photo_id => p.id }, :planting_id => @planting_id), :method => :post - %p - =p.title + .row + - @photos.each do |p| + .col-md-2.six-across + .thumbnail(style='height: 220px') + = link_to image_tag(FlickRaw.url_q(p), :alt => '', :class => 'img-rounded'), photos_path(:photo => { :flickr_photo_id => p.id }, :planting_id => @planting_id), :method => :post + %p + =p.title - else .alert From baf8b91a4e3dbf20fcd836016a3b9f1b26077206 Mon Sep 17 00:00:00 2001 From: Skud Date: Thu, 4 Sep 2014 18:05:10 +1000 Subject: [PATCH 142/288] Removed fairly pointless test for markup --- spec/views/photos/new.html.haml_spec.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/spec/views/photos/new.html.haml_spec.rb b/spec/views/photos/new.html.haml_spec.rb index 5ab726a49..c3e47a6b8 100644 --- a/spec/views/photos/new.html.haml_spec.rb +++ b/spec/views/photos/new.html.haml_spec.rb @@ -14,11 +14,6 @@ describe "photos/new" do assign(:flickr_auth, FactoryGirl.create(:flickr_authentication, :member => @member)) end - it "shows a list of photos" do - render - assert_select "ul.thumbnails" - end - context "user has no photosets" do it "doesn't show a dropdown with sets from Flickr" do assert_select "select#set", false From 4f4aad76266536e1c79cef476d5c3906b9c83df9 Mon Sep 17 00:00:00 2001 From: Skud Date: Thu, 4 Sep 2014 18:14:49 +1000 Subject: [PATCH 143/288] Added pear logo to top nav --- app/assets/images/growstuff-brand.png | Bin 0 -> 8246 bytes .../stylesheets/custom_bootstrap/variables.less | 10 ++++------ app/assets/stylesheets/overrides.css.less | 5 +++++ app/views/layouts/_header.html.haml | 3 ++- 4 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 app/assets/images/growstuff-brand.png diff --git a/app/assets/images/growstuff-brand.png b/app/assets/images/growstuff-brand.png new file mode 100644 index 0000000000000000000000000000000000000000..6e9f8729f9f89bb11b907c2dc4c704054b19b001 GIT binary patch literal 8246 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 "200x50", :alt => ENV['GROWSTUFF_SITE_NAME']) .navbar-collapse.collapse#navbar-collapse %ul.nav.navbar-nav %li.dropdown< From 0e0a13bbd181834137c50a27854921fe3acc324a Mon Sep 17 00:00:00 2001 From: Skud Date: Thu, 4 Sep 2014 18:31:13 +1000 Subject: [PATCH 144/288] Made sure all headings are sans-serif --- app/assets/stylesheets/custom_bootstrap/variables.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/custom_bootstrap/variables.less b/app/assets/stylesheets/custom_bootstrap/variables.less index 0a2963d82..b6e75669c 100644 --- a/app/assets/stylesheets/custom_bootstrap/variables.less +++ b/app/assets/stylesheets/custom_bootstrap/variables.less @@ -29,7 +29,7 @@ @line-height-base: 1.5; @alt-font-family: @font-family-serif; -@headings-font-family: "Lora", Georgia, "Times New Roman", Times, serif; +@headings-font-family: @font-family-sans-serif; @headings-font-weight: bold; // instead of browser default, bold @headings-color: inherit; // empty to use BS default, @textColor From 7184b04397b6ffc9fbadb54d4fe7a5e151b22ca4 Mon Sep 17 00:00:00 2001 From: Skud Date: Thu, 4 Sep 2014 18:37:27 +1000 Subject: [PATCH 145/288] Added favicon --- app/assets/images/favicon.ico | Bin 0 -> 920 bytes app/views/layouts/_meta.html.haml | 1 + 2 files changed, 1 insertion(+) create mode 100644 app/assets/images/favicon.ico diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..aff560bd810f54bdbe153bc6c2ac25856018da0a GIT binary patch literal 920 zcmZQzU}Ruo5D;Jh(h3ZRm>3w;7#M(jCLo`YVFQr;0w~rQ;OEZECB+5g@_Kr>1c8)* zFb5lul>huck%57!)6>N<#G^iwXShYl8N#3Vr-3u0UcO7`QYxTNCde#zYB^QKDBN}J9zA`CY8T|Z;<@J5Fu{_R~ zYu?@aUOVsp|MS0@7(QA#q)gRMNH0%6Z1*)V14fcR2e* z&F;GXzxYw~I+0945D1vteqMXjAtxe?kTzJ*`-wxH|%8&KD;E%<#LyQU8U91 zn?D)W=|=8Z|NdRs61BRS+%DU_HYGSWFML1b?~e5S z@29P8zLtBcvVWSE`=>0{EjfGoqOG-|Thg{Kx|?6Z+8=XnKhuSEvP!m^%#{cKtlL*D znb7OFWtCvS)rj2d=QEZSeziPs_SAWvo-g`)|L&Lia6w0>NaC^KyfxGAe3#QO^_Qz= z2xHC>m=vsWVUhRlO+1|0JFDL{ZLSG*NNl{g@1*OyL*K=-53#HMZkZLzc&)Ta!HYxt z>?7X==1CEv7cMO5t74a&v}9?mZ}S(QA73VxGhFg|IAhtO$&07yM}KP73U<=rQ1JS& zw|nNT?kLtDSN}7WiCX=<@JLeTd(^BcGKtGIl+L%$c>A;M_pzxNEA21XCmLJ-|8I9- z-Xd1#wtb5Z%?yiqv!&UY;Y3){X8T;p=jYyk?z;+1n+%?=elF{r5}GtX$rXqdfEbjr Oae@E;|KpOPt2hAaa$C0m literal 0 HcmV?d00001 diff --git a/app/views/layouts/_meta.html.haml b/app/views/layouts/_meta.html.haml index 3c28d0cbe..83207b31b 100644 --- a/app/views/layouts/_meta.html.haml +++ b/app/views/layouts/_meta.html.haml @@ -7,6 +7,7 @@ = auto_discovery_link_tag(:rss, { :controller => "/posts", :format => "rss" }, { :title => "#{ ENV['GROWSTUFF_SITE_NAME'] } - Recent posts from all members" }) = auto_discovery_link_tag(:rss, { :controller => "/crops", :format => "rss" }, { :title => "#{ ENV['GROWSTUFF_SITE_NAME'] } - Recently added crops" }) = auto_discovery_link_tag(:rss, { :controller => "/plantings", :format => "rss" }, { :title => "#{ ENV['GROWSTUFF_SITE_NAME'] } - Recent plantings from all members" }) + = favicon_link_tag '/assets/favicon.ico' %title = content_for?(:title) ? yield(:title) + " - #{ ENV['GROWSTUFF_SITE_NAME']} " : ENV['GROWSTUFF_SITE_NAME'] From 7543221f738bacb11ed8b7470c1a91e99ce37e13 Mon Sep 17 00:00:00 2001 From: Skud Date: Thu, 4 Sep 2014 18:40:51 +1000 Subject: [PATCH 146/288] Added 'skip navigation menu' link for screenreaders --- app/views/layouts/_header.html.haml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index 8642b6aac..d68f6dc73 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -1,3 +1,5 @@ +.sr-only + =link_to "Skip navigation menu", "#skipnav" .navbar.navbar-default.navbar-fixed-top(role="navigation") .container .navbar-header @@ -69,3 +71,6 @@ = form_tag crops_search_path, :method => :get, :class => 'navbar-form pull-right' do .input = text_field_tag 'search', nil, :class => 'search-query input-medium form-control', :placeholder => 'Search crops' + +- # anchor tag for accessibility link to skip the navigation menu +%a{:name => 'skipnav'} From 495f4194e7adc2840e8e6535c418b330cbb95abf Mon Sep 17 00:00:00 2001 From: Skud Date: Thu, 4 Sep 2014 19:02:38 +1000 Subject: [PATCH 147/288] Fixed test for branding in navbar --- spec/views/layouts/_header_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/views/layouts/_header_spec.rb b/spec/views/layouts/_header_spec.rb index 8aa6c7d0f..2274b25b5 100644 --- a/spec/views/layouts/_header_spec.rb +++ b/spec/views/layouts/_header_spec.rb @@ -7,8 +7,8 @@ describe 'layouts/_header.html.haml', :type => "view" do render end - it 'shows the title' do - rendered.should contain ENV['GROWSTUFF_SITE_NAME'] + it 'shows the brand logo in the navbar' do + assert_select("img[src='/assets/growstuff-brand.png']", :href => root_path) end it 'should have signup/signin links' do From a3213a99e82fd159c45c9e0be0f799850d33f2e0 Mon Sep 17 00:00:00 2001 From: Skud Date: Thu, 4 Sep 2014 19:02:58 +1000 Subject: [PATCH 148/288] Added apple icon for iOS users --- .../growstuff-apple-touch-icon-precomposed.png | Bin 0 -> 10621 bytes app/views/layouts/_meta.html.haml | 9 +++------ 2 files changed, 3 insertions(+), 6 deletions(-) create mode 100644 app/assets/images/growstuff-apple-touch-icon-precomposed.png diff --git a/app/assets/images/growstuff-apple-touch-icon-precomposed.png b/app/assets/images/growstuff-apple-touch-icon-precomposed.png new file mode 100644 index 0000000000000000000000000000000000000000..67e397952a9920438055be4d624f9015f2a85706 GIT binary patch literal 10621 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 literal 0 HcmV?d00001 diff --git a/app/views/layouts/_meta.html.haml b/app/views/layouts/_meta.html.haml index 83207b31b..c5313fb8d 100644 --- a/app/views/layouts/_meta.html.haml +++ b/app/views/layouts/_meta.html.haml @@ -7,7 +7,6 @@ = auto_discovery_link_tag(:rss, { :controller => "/posts", :format => "rss" }, { :title => "#{ ENV['GROWSTUFF_SITE_NAME'] } - Recent posts from all members" }) = auto_discovery_link_tag(:rss, { :controller => "/crops", :format => "rss" }, { :title => "#{ ENV['GROWSTUFF_SITE_NAME'] } - Recently added crops" }) = auto_discovery_link_tag(:rss, { :controller => "/plantings", :format => "rss" }, { :title => "#{ ENV['GROWSTUFF_SITE_NAME'] } - Recent plantings from all members" }) - = favicon_link_tag '/assets/favicon.ico' %title = content_for?(:title) ? yield(:title) + " - #{ ENV['GROWSTUFF_SITE_NAME']} " : ENV['GROWSTUFF_SITE_NAME'] @@ -16,8 +15,6 @@ /[if lt IE 9] = javascript_include_tag "http://html5shim.googlecode.com/svn/trunk/html5.js" = stylesheet_link_tag "application", :media => "all" - %link(href="images/apple-touch-icon-144x144.png" rel="apple-touch-icon-precomposed" sizes="144x144") - %link(href="images/apple-touch-icon-114x114.png" rel="apple-touch-icon-precomposed" sizes="114x114") - %link(href="images/apple-touch-icon-72x72.png" rel="apple-touch-icon-precomposed" sizes="72x72") - %link(href="images/apple-touch-icon.png" rel="apple-touch-icon-precomposed") - %link(href="images/favicon.ico" rel="shortcut icon") + + %link(href="/assets/growstuff-apple-touch-icon-precomposed.png" rel="apple-touch-icon-precomposed") + = favicon_link_tag '/assets/favicon.ico' From 4caf7b9b28853684094ea1b453ba866b0910b041 Mon Sep 17 00:00:00 2001 From: catfriend Date: Fri, 5 Sep 2014 15:30:34 -0700 Subject: [PATCH 149/288] added placeholder text per 78178814 --- app/helpers/auto_suggest_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/auto_suggest_helper.rb b/app/helpers/auto_suggest_helper.rb index f6a5bcdb7..532386b1d 100644 --- a/app/helpers/auto_suggest_helper.rb +++ b/app/helpers/auto_suggest_helper.rb @@ -9,7 +9,7 @@ module AutoSuggestHelper source_path = Rails.application.routes.url_helpers.send("#{source}s_search_path") %Q{ - + }.html_safe From b0ee83bc3ec5b924714d24437a9fdf7bdefc2a07 Mon Sep 17 00:00:00 2001 From: catfriend Date: Fri, 5 Sep 2014 16:23:33 -0700 Subject: [PATCH 150/288] added test for adding placeholder text --- spec/features/shared_examples/crop_suggest_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/features/shared_examples/crop_suggest_spec.rb b/spec/features/shared_examples/crop_suggest_spec.rb index a63acb4b6..499bab891 100644 --- a/spec/features/shared_examples/crop_suggest_spec.rb +++ b/spec/features/shared_examples/crop_suggest_spec.rb @@ -6,6 +6,10 @@ shared_examples "crop suggest" do |resource| let!(:tomato) { FactoryGirl.create(:tomato) } let!(:roma) { FactoryGirl.create(:roma) } + scenario "See text in crop auto suggest field" do + expect(page).to have_selector("input[placeholder='e.g. lettuce']") + end + scenario "Typing in the crop name displays suggestions" do within "form#new_#{resource}" do fill_autocomplete "crop", :with => "p" From 885ffab012417bd4665584d6fcc8d6af59782b60 Mon Sep 17 00:00:00 2001 From: catfriend Date: Fri, 5 Sep 2014 20:36:58 -0700 Subject: [PATCH 151/288] added contributor info --- CONTRIBUTORS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index e12ea4142..5bd5cc601 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -43,3 +43,5 @@ submit the change with your pull request. - Barb Natali / [barbnatali](https://github.com/barbnatali) - Taylor Griffin / [tygriffin](https://github.com/tygriffin) - Marlena Compton / [Marlena](https://github.com/marlena) +- Elizabeth A. Kari / [catfriend](https://github.com/catfriend) +- Cheri Allen / [cherimarie](https://github.com/cherimarie) From 00b24890f4b7394aceaa8f8260fe942fd067b044 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Tue, 9 Sep 2014 21:27:21 +1000 Subject: [PATCH 152/288] accept a default value in options hash of autosuggest helper --- app/assets/javascripts/auto_suggest.js.coffee | 1 + app/helpers/auto_suggest_helper.rb | 12 ++++++++---- app/views/plantings/_form.html.haml | 2 +- db/schema.rb | 3 +-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/auto_suggest.js.coffee b/app/assets/javascripts/auto_suggest.js.coffee index 8720b1978..9923a1e62 100644 --- a/app/assets/javascripts/auto_suggest.js.coffee +++ b/app/assets/javascripts/auto_suggest.js.coffee @@ -7,6 +7,7 @@ # You must also add a search method to the resource's controller. jQuery -> + if el = $( '.auto-suggest' ) id = $( '.auto-suggest-id' ) diff --git a/app/helpers/auto_suggest_helper.rb b/app/helpers/auto_suggest_helper.rb index 532386b1d..3b133e8d9 100644 --- a/app/helpers/auto_suggest_helper.rb +++ b/app/helpers/auto_suggest_helper.rb @@ -1,15 +1,19 @@ module AutoSuggestHelper def auto_suggest(resource, source, options={}) - default = resource.send(source) - default_name = default.try(:name) - default_id = default.try(:id) + if options[:default].blank? + default = resource.send(source) + default_id = default.try(:id) + else + default = options[:default] + default_id = options[:default].try(:id) + end resource = resource.class.name.downcase source_path = Rails.application.routes.url_helpers.send("#{source}s_search_path") %Q{ - + }.html_safe diff --git a/app/views/plantings/_form.html.haml b/app/views/plantings/_form.html.haml index 277fe2cb9..fd0b00b34 100644 --- a/app/views/plantings/_form.html.haml +++ b/app/views/plantings/_form.html.haml @@ -9,7 +9,7 @@ .form-group = f.label :crop, 'What did you plant?', :class => 'control-label col-md-2' .col-md-8 - = auto_suggest @planting, :crop, :class => 'form-control' + = auto_suggest @planting, :crop, :class => 'form-control', :default => @crop %span.help-inline Can't find what you're looking for? = link_to "Request new crops.", Growstuff::Application.config.new_crops_request_link diff --git a/db/schema.rb b/db/schema.rb index 4c5ee003b..489a9e6b6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20140720085713) do +ActiveRecord::Schema.define(:version => 20140718075753) do create_table "account_types", :force => true do |t| t.string "name", :null => false @@ -139,7 +139,6 @@ ActiveRecord::Schema.define(:version => 20140720085713) do t.text "bio" t.integer "plantings_count" t.boolean "newsletter" - t.boolean "send_planting_reminder", :default => true end add_index "members", ["confirmation_token"], :name => "index_users_on_confirmation_token", :unique => true From 138904bd8274fa4b31fe483bdf090205d1c2665f Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Tue, 9 Sep 2014 21:52:50 +1000 Subject: [PATCH 153/288] write regression test for creating plantings, harvests and seeds form crop page --- app/helpers/auto_suggest_helper.rb | 8 ++++---- app/views/harvests/_form.html.haml | 2 +- app/views/seeds/_form.html.haml | 2 +- spec/features/harvests/harvesting_a_crop_spec.rb | 12 ++++++++++++ spec/features/plantings/planting_a_crop_spec.rb | 14 +++++++++++++- spec/features/seeds/adding_seeds_spec.rb | 12 ++++++++++++ 6 files changed, 43 insertions(+), 7 deletions(-) diff --git a/app/helpers/auto_suggest_helper.rb b/app/helpers/auto_suggest_helper.rb index 3b133e8d9..3b6cb3ce8 100644 --- a/app/helpers/auto_suggest_helper.rb +++ b/app/helpers/auto_suggest_helper.rb @@ -1,12 +1,12 @@ module AutoSuggestHelper def auto_suggest(resource, source, options={}) - if options[:default].blank? - default = resource.send(source) - default_id = default.try(:id) - else + if options[:default] && !options[:default].new_record? default = options[:default] default_id = options[:default].try(:id) + else + default = resource.send(source) + default_id = default.try(:id) end resource = resource.class.name.downcase diff --git a/app/views/harvests/_form.html.haml b/app/views/harvests/_form.html.haml index f10b5497c..83aa71d08 100644 --- a/app/views/harvests/_form.html.haml +++ b/app/views/harvests/_form.html.haml @@ -9,7 +9,7 @@ .form-group = f.label :crop, 'What did you harvest?', :class => 'control-label col-md-2' .col-md-4 - = auto_suggest @harvest, :crop, :class => 'form-control col-md-2' + = auto_suggest @harvest, :crop, :class => 'form-control col-md-2', :default => @crop .col-md-4 = collection_select(:harvest, :plant_part_id, PlantPart.all, :id, :name, { :selected => @harvest.plant_part_id }, { :class => 'form-control' }) %span.help-block.col-md-8 diff --git a/app/views/seeds/_form.html.haml b/app/views/seeds/_form.html.haml index e38849811..642a5de01 100644 --- a/app/views/seeds/_form.html.haml +++ b/app/views/seeds/_form.html.haml @@ -9,7 +9,7 @@ .form-group = f.label :crop, 'Crop:', :class => 'control-label col-md-2' .col-md-8 - = auto_suggest @seed, :crop, :class => 'form-control' + = auto_suggest @seed, :crop, :class => 'form-control', :default => @crop %span.help-inline Can't find what you're looking for? = link_to "Request new crops.", Growstuff::Application.config.new_crops_request_link diff --git a/spec/features/harvests/harvesting_a_crop_spec.rb b/spec/features/harvests/harvesting_a_crop_spec.rb index 31087656f..7a366309a 100644 --- a/spec/features/harvests/harvesting_a_crop_spec.rb +++ b/spec/features/harvests/harvesting_a_crop_spec.rb @@ -25,6 +25,18 @@ feature "Harvesting a crop", :js => true do expect(page).to have_content "Harvest was successfully created" end + scenario "Harvesting from crop page" do + visit "/crops/maize" + click_link "Harvest this" + within "form#new_harvest" do + expect(page).to have_selector "input[value='maize']" + click_button "Save" + end + + expect(page).to have_content "Harvest was successfully created" + expect(page).to have_content "maize" + end + context "Editing a harvest" do let(:existing_harvest) { FactoryGirl.create(:harvest, :crop => maize, :owner => member) } diff --git a/spec/features/plantings/planting_a_crop_spec.rb b/spec/features/plantings/planting_a_crop_spec.rb index ad87a35ca..23800717a 100644 --- a/spec/features/plantings/planting_a_crop_spec.rb +++ b/spec/features/plantings/planting_a_crop_spec.rb @@ -11,7 +11,7 @@ feature "Planting a crop", :js => true do it_behaves_like "crop suggest", "planting", "crop" - scenario "Creating a new planting", :js => true do + scenario "Creating a new planting" do fill_autocomplete "crop", :with => "m" select_from_autocomplete "maize" within "form#new_planting" do @@ -26,4 +26,16 @@ feature "Planting a crop", :js => true do expect(page).to have_content "Planting was successfully created" end + scenario "Planting from crop page" do + visit "/crops/maize" + click_link "Plant this" + within "form#new_planting" do + expect(page).to have_selector "input[value='maize']" + click_button "Save" + end + + expect(page).to have_content "Planting was successfully created" + expect(page).to have_content "maize" + end + end \ No newline at end of file diff --git a/spec/features/seeds/adding_seeds_spec.rb b/spec/features/seeds/adding_seeds_spec.rb index 196fa7b47..7df57b95a 100644 --- a/spec/features/seeds/adding_seeds_spec.rb +++ b/spec/features/seeds/adding_seeds_spec.rb @@ -25,4 +25,16 @@ feature "Harvesting a crop", :js => true do expect(page).to have_content "Successfully added maize seed to your stash" end + scenario "Adding a seed from crop page" do + visit "/crops/maize" + click_link "Add seeds to stash" + within "form#new_seed" do + expect(page).to have_selector "input[value='maize']" + click_button "Save" + end + + expect(page).to have_content "Successfully added maize seed to your stash" + expect(page).to have_content "maize" + end + end From e0485ade1d8f7ea721da2374c6f7916255354dce Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 10 Sep 2014 08:06:07 +1000 Subject: [PATCH 154/288] Added test to make sure the wrong plantings aren't finished --- spec/models/garden_spec.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/spec/models/garden_spec.rb b/spec/models/garden_spec.rb index e3a2e3690..8bf355cd0 100644 --- a/spec/models/garden_spec.rb +++ b/spec/models/garden_spec.rb @@ -180,7 +180,25 @@ describe Garden do p1.finished.should eq true p2.reload p2.finished.should eq true + end + it "doesn't mark the wrong plantings as finished" do + g1 = FactoryGirl.create(:garden) + g2 = FactoryGirl.create(:garden) + p1 = FactoryGirl.create(:planting, :garden => g1) + p2 = FactoryGirl.create(:planting, :garden => g2) + + # mark the garden as inactive + g1.active = false + g1.save + + # plantings in that garden should be "finished" + p1.reload + p1.finished.should eq true + + # plantings in other gardens should not be. + p2.reload + p2.finished.should eq false end end From 7d54ab347fbc8bf5c367bd0b8f150b0b7077b546 Mon Sep 17 00:00:00 2001 From: Mackenzie Morgan Date: Tue, 9 Sep 2014 19:55:40 -0400 Subject: [PATCH 155/288] Adding photo functionality to harvests --- app/controllers/photos_controller.rb | 41 ++++++++---- app/models/harvest.rb | 7 ++ app/models/photo.rb | 6 +- app/views/harvests/show.html.haml | 13 ++++ app/views/photos/new.html.haml | 2 +- app/views/plantings/show.html.haml | 2 +- config/initializers/devise.rb | 3 +- db/schema.rb | 7 +- spec/controllers/photos_controller_spec.rb | 74 +++++++++++++++++++--- spec/models/harvest_spec.rb | 28 ++++++++ 10 files changed, 158 insertions(+), 25 deletions(-) diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index 4962fc54e..8932ebae8 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -29,7 +29,8 @@ class PhotosController < ApplicationController # GET /photos/new.json def new @photo = Photo.new - @planting_id = params[:planting_id] + @type = params[:type] + @id = params[:id] page = params[:page] || 1 @@ -63,17 +64,33 @@ class PhotosController < ApplicationController @photo.owner_id = current_member.id @photo.set_flickr_metadata - if params[:planting_id] - planting = Planting.find_by_id(params[:planting_id]) - if planting - if planting.owner.id == current_member.id - @photo.plantings << planting unless @photo.plantings.include?(planting) - else - flash[:alert] = "You must own both the planting and the photo." - end - else - flash[:alert] = "Couldn't find planting to connect to photo." - end + # several models can have photos. we need to know what model and the id + # for the entry to attach the photo to + valid_models = ["planting", "harvest"] + if params[:type] + if valid_models.include?(params[:type]) + if params[:id] + item = params[:type].camelcase.constantize.find_by_id(params[:id]) + if item + if item.owner.id == current_member.id + # This syntax is weird, so just know that it means this: + # @photo.harvests << item unless @photo.harvests.include?(item) + # but with the correct many-to-many relationship automatically referenced + (@photo.send "#{params[:type]}s") << item unless (@photo.send "#{params[:type]}s").include?(item) + else + flash[:alert] = "You must own both the #{params[:type]} and the photo." + end + else + flash[:alert] = "Couldn't find #{params[:type]} to connect to photo." + end + else + flash[:alert] = "Missing id parameter" + end + else + flash[:alert] = "Cannot attach photos to #{params[:type]}" + end + else + flash[:alert] = "Missing type parameter" end respond_to do |format| diff --git a/app/models/harvest.rb b/app/models/harvest.rb index a7851fdc8..8cc54e043 100644 --- a/app/models/harvest.rb +++ b/app/models/harvest.rb @@ -9,6 +9,9 @@ class Harvest < ActiveRecord::Base belongs_to :owner, :class_name => 'Member' belongs_to :plant_part + has_and_belongs_to_many :photos + before_destroy {|harvest| harvest.photos.clear} + default_scope order('created_at DESC') validates :crop, :presence => {:message => "must be present and exist in our database"} @@ -72,4 +75,8 @@ class Harvest < ActiveRecord::Base "#{owner.login_name}-#{crop}".downcase.gsub(' ', '-') end + def default_photo + return photos.first + end + end diff --git a/app/models/photo.rb b/app/models/photo.rb index e65617c87..aa5a624d6 100644 --- a/app/models/photo.rb +++ b/app/models/photo.rb @@ -4,7 +4,11 @@ class Photo < ActiveRecord::Base belongs_to :owner, :class_name => 'Member' has_and_belongs_to_many :plantings - before_destroy {|photo| photo.plantings.clear} + has_and_belongs_to_many :harvests + before_destroy do |photo| + photo.plantings.clear + photo.harvests.clear + end default_scope order("created_at desc") diff --git a/app/views/harvests/show.html.haml b/app/views/harvests/show.html.haml index 95897bd42..fe0690f1e 100644 --- a/app/views/harvests/show.html.haml +++ b/app/views/harvests/show.html.haml @@ -34,3 +34,16 @@ :growstuff_markdown #{ @harvest.description != "" ? @harvest.description : "No description given." } + +- if @harvest.photos.count > 0 or (can? :edit, @harvest and can? :create, Photo) + %h2 Pictures + + %ul.thumbnails + - @harvest.photos.each do |p| + %li.span2.six-across + = render :partial => 'photos/thumbnail', :locals => { :photo => p } + - if can? :create, Photo and can? :edit, @harvest + %li.span2 + .thumbnail(style='height: 220px') + %p{:style => 'text-align: center; padding-top: 50px'} + = link_to "Add photo", new_photo_path(:type => "harvest", :id => @harvest.id), :class => 'btn btn-primary' diff --git a/app/views/photos/new.html.haml b/app/views/photos/new.html.haml index 32ea66915..965ff28e4 100644 --- a/app/views/photos/new.html.haml +++ b/app/views/photos/new.html.haml @@ -25,7 +25,7 @@ - @photos.each do |p| .col-md-2.six-across .thumbnail(style='height: 220px') - = link_to image_tag(FlickRaw.url_q(p), :alt => '', :class => 'img-rounded'), photos_path(:photo => { :flickr_photo_id => p.id }, :planting_id => @planting_id), :method => :post + = link_to image_tag(FlickRaw.url_q(p), :alt => '', :class => 'img-rounded'), photos_path(:photo => { :flickr_photo_id => p.id }, :type => @type, :id => @id), :method => :post %p =p.title diff --git a/app/views/plantings/show.html.haml b/app/views/plantings/show.html.haml index a6a713229..a5f8c8b8e 100644 --- a/app/views/plantings/show.html.haml +++ b/app/views/plantings/show.html.haml @@ -57,4 +57,4 @@ .col-md-2 .thumbnail(style='height: 220px') %p{:style => 'text-align: center; padding-top: 50px'} - = link_to "Add photo", new_photo_path(:planting_id => @planting.id), :class => 'btn btn-primary' + = link_to "Add photo", new_photo_path(:type => "planting", :id => @planting.id), :class => 'btn btn-primary' diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index eb51fab96..92d55dfaa 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -6,7 +6,8 @@ Devise.setup do |config| # note that it will be overwritten if you use your own mailer class with default "from" parameter. config.mailer_sender = "Growstuff " - config.secret_key = ENV['RAILS_SECRET_TOKEN'] + #config.secret_key = ENV['RAILS_SECRET_TOKEN'] + config.secret_key = '6ccb49df7c02ce710c37ec3eadd81ec65e732708bd859ce6076f42593f0cff186b7c35be2fd195f46c9e923296ee07bf309eb33866fe1ea7d9ffeb88f367489c' # Configure the class responsible to send e-mails. # config.mailer = "Devise::Mailer" diff --git a/db/schema.rb b/db/schema.rb index 4c5ee003b..4ac5f92c7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20140720085713) do +ActiveRecord::Schema.define(:version => 20140905001730) do create_table "account_types", :force => true do |t| t.string "name", :null => false @@ -108,6 +108,11 @@ ActiveRecord::Schema.define(:version => 20140720085713) do t.integer "plant_part_id" end + create_table "harvests_photos", :id => false, :force => true do |t| + t.integer "photo_id" + t.integer "harvest_id" + end + create_table "members", :force => true do |t| t.string "email", :default => "", :null => false t.string "encrypted_password", :default => "", :null => false diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb index a73981e4c..4370e626f 100644 --- a/spec/controllers/photos_controller_spec.rb +++ b/spec/controllers/photos_controller_spec.rb @@ -37,8 +37,15 @@ describe PhotosController do end it "assigns a planting id" do - get :new, { :planting_id => 5 } - assigns(:planting_id).should eq "5" + get :new, { :type => "planting", :id => 5 } + assigns(:id).should eq "5" + assigns(:type).should eq "planting" + end + + it "assigns a harvest id" do + get :new, { :type => "harvest", :id => 5 } + assigns(:id).should eq "5" + assigns(:type).should eq "harvest" end it "assigns the current set as @current_set" do @@ -69,7 +76,8 @@ describe PhotosController do planting = FactoryGirl.create(:planting, :garden => garden, :owner => member) photo = FactoryGirl.create(:photo, :owner => member) post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, - :planting_id => planting.id } + :type => "planting", + :id => planting.id } Photo.last.plantings.first.should eq planting end @@ -80,12 +88,39 @@ describe PhotosController do planting = FactoryGirl.create(:planting, :garden => garden, :owner => member) photo = FactoryGirl.create(:photo, :owner => member) post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, - :planting_id => planting.id } + :type => "planting", + :id => planting.id } post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, - :planting_id => planting.id } + :type => "planting", + :id => planting.id } Photo.last.plantings.count.should eq 1 end - end + + it "attaches the photo to a harvest" do + member = FactoryGirl.create(:member) + controller.stub(:current_member) { member } + harvest = FactoryGirl.create(:harvest, :owner => member) + photo = FactoryGirl.create(:photo, :owner => member) + post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, + :type => "harvest", + :id => harvest.id } + Photo.last.harvests.first.should eq harvest + end + + it "doesn't attach a photo to a harvest twice" do + member = FactoryGirl.create(:member) + controller.stub(:current_member) { member } + harvest = FactoryGirl.create(:harvest, :owner => member) + photo = FactoryGirl.create(:photo, :owner => member) + post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, + :type => "harvest", + :id => harvest.id } + post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, + :type => "harvest", + :id => harvest.id } + Photo.last.harvests.count.should eq 1 + end + end describe "for the second time" do it "does not add a photo twice" do @@ -106,9 +141,21 @@ describe PhotosController do planting = FactoryGirl.create(:planting, :garden => garden, :owner => member) photo = FactoryGirl.create(:photo, :owner => member) post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, - :planting_id => planting.id } + :type => "planting", + :id => planting.id } Photo.last.plantings.first.should eq planting end + + it "creates the harvest/photo link" do + member = FactoryGirl.create(:member) + controller.stub(:current_member) { member } + harvest = FactoryGirl.create(:harvest, :owner => member) + photo = FactoryGirl.create(:photo, :owner => member) + post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, + :type => "harvest", + :id => harvest.id } + Photo.last.harvests.first.should eq harvest + end end describe "with mismatched owners" do @@ -117,9 +164,20 @@ describe PhotosController do planting = FactoryGirl.create(:planting) photo = FactoryGirl.create(:photo) post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, - :planting_id => planting.id } + :type => "planting", + :id => planting.id } Photo.last.plantings.first.should_not eq planting end + + it "creates the harvest/photo link" do + # members will be auto-created, and different + harvest = FactoryGirl.create(:harvest) + photo = FactoryGirl.create(:photo) + post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, + :type => "harvest", + :id => harvest.id } + Photo.last.harvests.first.should_not eq harvest + end end end end diff --git a/spec/models/harvest_spec.rb b/spec/models/harvest_spec.rb index ca0180ca1..a5b5cabf7 100644 --- a/spec/models/harvest_spec.rb +++ b/spec/models/harvest_spec.rb @@ -125,4 +125,32 @@ describe Harvest do Harvest.all.should eq [@h2, @h1] end end + + context 'photos' do + before(:each) do + @harvest = FactoryGirl.create(:harvest) + @photo = FactoryGirl.create(:photo) + @harvest.photos << @photo + end + + it 'has a photo' do + @harvest.photos.first.should eq @photo + end + + it 'deletes association with photos when photo is deleted' do + @photo.destroy + @harvest.reload + @harvest.photos.should be_empty + end + + it 'has a default photo' do + @harvest.default_photo.should eq @photo + end + + it 'chooses the most recent photo' do + @photo2 = FactoryGirl.create(:photo) + @harvest.photos << @photo2 + @harvest.default_photo.should eq @photo2 + end + end end From 2b7b5bad6378edb66a0b9307c3c33b89c10f4b4e Mon Sep 17 00:00:00 2001 From: Mackenzie Morgan Date: Tue, 9 Sep 2014 20:21:16 -0400 Subject: [PATCH 156/288] including all the new files needed for harvest photos, this time --- .../harvests/_image_with_popover.html.haml | 12 ++++++ app/views/harvests/_thumbnail.html.haml | 37 +++++++++++++++++++ ...0140905001730_add_harvests_photos_table.rb | 8 ++++ 3 files changed, 57 insertions(+) create mode 100644 app/views/harvests/_image_with_popover.html.haml create mode 100644 app/views/harvests/_thumbnail.html.haml create mode 100644 db/migrate/20140905001730_add_harvests_photos_table.rb diff --git a/app/views/harvests/_image_with_popover.html.haml b/app/views/harvests/_image_with_popover.html.haml new file mode 100644 index 000000000..5eef8610e --- /dev/null +++ b/app/views/harvests/_image_with_popover.html.haml @@ -0,0 +1,12 @@ +- cache "planting_image_#{planting.id}" do + = link_to | + image_tag( | + planting.photos.present? ? planting.photos.first.thumbnail_url : 'placeholder_150.png', | + :alt => planting.to_s | + ), | + planting, | + :rel => "popover", | + 'data-trigger' => 'hover', | + 'data-title' => planting.to_s, | + 'data-content' => "#{ render :partial => 'plantings/popover', :locals => { :planting => planting } }", | + 'data-html' => true diff --git a/app/views/harvests/_thumbnail.html.haml b/app/views/harvests/_thumbnail.html.haml new file mode 100644 index 000000000..f3b1c1211 --- /dev/null +++ b/app/views/harvests/_thumbnail.html.haml @@ -0,0 +1,37 @@ +.well + .row-fluid + .span3 + = link_to image_tag((planting.default_photo ? planting.default_photo.thumbnail_url : 'placeholder_150.png'), :alt => '', :class => 'img-rounded'), planting + + .span9 + %h4 + - if defined?(title) && title == 'owner' + = link_to planting.owner, planting.owner + - else + = link_to planting.crop.name, planting + + %p + Planted + - if planting.planted_at + = planting.planted_at + in + = link_to planting.location, planting.garden + + %p + - if planting.quantity + Quantity: + = planting.quantity + - else +   + + - if planting.description && ! defined?(hide_description) + %div + :growstuff_markdown + #{ planting.description } + + - if can? :edit, planting or can? :destroy, planting + %p + - if can? :edit, planting + =link_to 'Edit', edit_planting_path(planting), :class => 'btn btn-mini' + - if can? :destroy, planting + =link_to 'Delete', planting, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini' diff --git a/db/migrate/20140905001730_add_harvests_photos_table.rb b/db/migrate/20140905001730_add_harvests_photos_table.rb new file mode 100644 index 000000000..edacd061b --- /dev/null +++ b/db/migrate/20140905001730_add_harvests_photos_table.rb @@ -0,0 +1,8 @@ +class AddHarvestsPhotosTable < ActiveRecord::Migration + def change + create_table :harvests_photos, :id => false do |t| + t.integer :photo_id + t.integer :harvest_id + end + end +end From ed537e583baa711f64e48eb2d744a54e1cfbfc89 Mon Sep 17 00:00:00 2001 From: Mackenzie Morgan Date: Tue, 9 Sep 2014 20:31:14 -0400 Subject: [PATCH 157/288] whitespace fixes and um oops no there shouldn't be a secret key in that config file --- app/controllers/photos_controller.rb | 48 +++++++++++----------- app/models/photo.rb | 4 +- config/initializers/devise.rb | 3 +- spec/controllers/photos_controller_spec.rb | 28 ++++++------- 4 files changed, 41 insertions(+), 42 deletions(-) diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index 8932ebae8..f4004ad73 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -64,32 +64,32 @@ class PhotosController < ApplicationController @photo.owner_id = current_member.id @photo.set_flickr_metadata - # several models can have photos. we need to know what model and the id - # for the entry to attach the photo to - valid_models = ["planting", "harvest"] + # several models can have photos. we need to know what model and the id + # for the entry to attach the photo to + valid_models = ["planting", "harvest"] if params[:type] if valid_models.include?(params[:type]) - if params[:id] - item = params[:type].camelcase.constantize.find_by_id(params[:id]) - if item - if item.owner.id == current_member.id - # This syntax is weird, so just know that it means this: - # @photo.harvests << item unless @photo.harvests.include?(item) - # but with the correct many-to-many relationship automatically referenced - (@photo.send "#{params[:type]}s") << item unless (@photo.send "#{params[:type]}s").include?(item) - else - flash[:alert] = "You must own both the #{params[:type]} and the photo." - end - else - flash[:alert] = "Couldn't find #{params[:type]} to connect to photo." - end - else - flash[:alert] = "Missing id parameter" - end - else - flash[:alert] = "Cannot attach photos to #{params[:type]}" - end - else + if params[:id] + item = params[:type].camelcase.constantize.find_by_id(params[:id]) + if item + if item.owner.id == current_member.id + # This syntax is weird, so just know that it means this: + # @photo.harvests << item unless @photo.harvests.include?(item) + # but with the correct many-to-many relationship automatically referenced + (@photo.send "#{params[:type]}s") << item unless (@photo.send "#{params[:type]}s").include?(item) + else + flash[:alert] = "You must own both the #{params[:type]} and the photo." + end + else + flash[:alert] = "Couldn't find #{params[:type]} to connect to photo." + end + else + flash[:alert] = "Missing id parameter" + end + else + flash[:alert] = "Cannot attach photos to #{params[:type]}" + end + else flash[:alert] = "Missing type parameter" end diff --git a/app/models/photo.rb b/app/models/photo.rb index aa5a624d6..4f0a0160f 100644 --- a/app/models/photo.rb +++ b/app/models/photo.rb @@ -6,8 +6,8 @@ class Photo < ActiveRecord::Base has_and_belongs_to_many :plantings has_and_belongs_to_many :harvests before_destroy do |photo| - photo.plantings.clear - photo.harvests.clear + photo.plantings.clear + photo.harvests.clear end default_scope order("created_at desc") diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 92d55dfaa..eb51fab96 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -6,8 +6,7 @@ Devise.setup do |config| # note that it will be overwritten if you use your own mailer class with default "from" parameter. config.mailer_sender = "Growstuff " - #config.secret_key = ENV['RAILS_SECRET_TOKEN'] - config.secret_key = '6ccb49df7c02ce710c37ec3eadd81ec65e732708bd859ce6076f42593f0cff186b7c35be2fd195f46c9e923296ee07bf309eb33866fe1ea7d9ffeb88f367489c' + config.secret_key = ENV['RAILS_SECRET_TOKEN'] # Configure the class responsible to send e-mails. # config.mailer = "Devise::Mailer" diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb index 4370e626f..1c3b47780 100644 --- a/spec/controllers/photos_controller_spec.rb +++ b/spec/controllers/photos_controller_spec.rb @@ -39,13 +39,13 @@ describe PhotosController do it "assigns a planting id" do get :new, { :type => "planting", :id => 5 } assigns(:id).should eq "5" - assigns(:type).should eq "planting" + assigns(:type).should eq "planting" end it "assigns a harvest id" do get :new, { :type => "harvest", :id => 5 } assigns(:id).should eq "5" - assigns(:type).should eq "harvest" + assigns(:type).should eq "harvest" end it "assigns the current set as @current_set" do @@ -76,7 +76,7 @@ describe PhotosController do planting = FactoryGirl.create(:planting, :garden => garden, :owner => member) photo = FactoryGirl.create(:photo, :owner => member) post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, - :type => "planting", + :type => "planting", :id => planting.id } Photo.last.plantings.first.should eq planting end @@ -88,10 +88,10 @@ describe PhotosController do planting = FactoryGirl.create(:planting, :garden => garden, :owner => member) photo = FactoryGirl.create(:photo, :owner => member) post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, - :type => "planting", + :type => "planting", :id => planting.id } post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, - :type => "planting", + :type => "planting", :id => planting.id } Photo.last.plantings.count.should eq 1 end @@ -102,7 +102,7 @@ describe PhotosController do harvest = FactoryGirl.create(:harvest, :owner => member) photo = FactoryGirl.create(:photo, :owner => member) post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, - :type => "harvest", + :type => "harvest", :id => harvest.id } Photo.last.harvests.first.should eq harvest end @@ -113,14 +113,14 @@ describe PhotosController do harvest = FactoryGirl.create(:harvest, :owner => member) photo = FactoryGirl.create(:photo, :owner => member) post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, - :type => "harvest", + :type => "harvest", :id => harvest.id } post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, - :type => "harvest", + :type => "harvest", :id => harvest.id } Photo.last.harvests.count.should eq 1 - end - end + end + end describe "for the second time" do it "does not add a photo twice" do @@ -141,7 +141,7 @@ describe PhotosController do planting = FactoryGirl.create(:planting, :garden => garden, :owner => member) photo = FactoryGirl.create(:photo, :owner => member) post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, - :type => "planting", + :type => "planting", :id => planting.id } Photo.last.plantings.first.should eq planting end @@ -152,7 +152,7 @@ describe PhotosController do harvest = FactoryGirl.create(:harvest, :owner => member) photo = FactoryGirl.create(:photo, :owner => member) post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, - :type => "harvest", + :type => "harvest", :id => harvest.id } Photo.last.harvests.first.should eq harvest end @@ -164,7 +164,7 @@ describe PhotosController do planting = FactoryGirl.create(:planting) photo = FactoryGirl.create(:photo) post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, - :type => "planting", + :type => "planting", :id => planting.id } Photo.last.plantings.first.should_not eq planting end @@ -174,7 +174,7 @@ describe PhotosController do harvest = FactoryGirl.create(:harvest) photo = FactoryGirl.create(:photo) post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, - :type => "harvest", + :type => "harvest", :id => harvest.id } Photo.last.harvests.first.should_not eq harvest end From 550f3c53265e80185486298079fdb7bebde63fc0 Mon Sep 17 00:00:00 2001 From: Mackenzie Morgan Date: Tue, 9 Sep 2014 22:18:43 -0400 Subject: [PATCH 158/288] removing unneeded photo-related files --- .../harvests/_image_with_popover.html.haml | 12 ------ app/views/harvests/_thumbnail.html.haml | 37 ------------------- 2 files changed, 49 deletions(-) delete mode 100644 app/views/harvests/_image_with_popover.html.haml delete mode 100644 app/views/harvests/_thumbnail.html.haml diff --git a/app/views/harvests/_image_with_popover.html.haml b/app/views/harvests/_image_with_popover.html.haml deleted file mode 100644 index 5eef8610e..000000000 --- a/app/views/harvests/_image_with_popover.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -- cache "planting_image_#{planting.id}" do - = link_to | - image_tag( | - planting.photos.present? ? planting.photos.first.thumbnail_url : 'placeholder_150.png', | - :alt => planting.to_s | - ), | - planting, | - :rel => "popover", | - 'data-trigger' => 'hover', | - 'data-title' => planting.to_s, | - 'data-content' => "#{ render :partial => 'plantings/popover', :locals => { :planting => planting } }", | - 'data-html' => true diff --git a/app/views/harvests/_thumbnail.html.haml b/app/views/harvests/_thumbnail.html.haml deleted file mode 100644 index f3b1c1211..000000000 --- a/app/views/harvests/_thumbnail.html.haml +++ /dev/null @@ -1,37 +0,0 @@ -.well - .row-fluid - .span3 - = link_to image_tag((planting.default_photo ? planting.default_photo.thumbnail_url : 'placeholder_150.png'), :alt => '', :class => 'img-rounded'), planting - - .span9 - %h4 - - if defined?(title) && title == 'owner' - = link_to planting.owner, planting.owner - - else - = link_to planting.crop.name, planting - - %p - Planted - - if planting.planted_at - = planting.planted_at - in - = link_to planting.location, planting.garden - - %p - - if planting.quantity - Quantity: - = planting.quantity - - else -   - - - if planting.description && ! defined?(hide_description) - %div - :growstuff_markdown - #{ planting.description } - - - if can? :edit, planting or can? :destroy, planting - %p - - if can? :edit, planting - =link_to 'Edit', edit_planting_path(planting), :class => 'btn btn-mini' - - if can? :destroy, planting - =link_to 'Delete', planting, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini' From 03235f3952cb893f099081f7fb88d1e93c225d11 Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 12 Sep 2014 07:43:41 +1000 Subject: [PATCH 159/288] Add warning about plantings being marked as finished --- app/views/gardens/_form.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/gardens/_form.html.haml b/app/views/gardens/_form.html.haml index 6313762c4..8a19edb2c 100644 --- a/app/views/gardens/_form.html.haml +++ b/app/views/gardens/_form.html.haml @@ -39,7 +39,8 @@ = f.label 'Active? ', :class => 'control-label col-md-2' .col-md-8 = f.check_box :active - You can mark a garden as inactive if you no longer use it. + You can mark a garden as inactive if you no longer use it. Note: + this will mark all plantings in the garden as "finished". .form-group .form-actions.col-md-offset-2.col-md-8 From 85b5ac12cad18f7293342a23bab69892da895c04 Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 12 Sep 2014 07:44:07 +1000 Subject: [PATCH 160/288] added validation for finished_at (must be after planted_at) --- app/models/planting.rb | 8 ++++++++ spec/factories/planting.rb | 1 + spec/models/planting_spec.rb | 14 ++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/app/models/planting.rb b/app/models/planting.rb index 270a827d2..9585d0914 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -56,6 +56,14 @@ class Planting < ActiveRecord::Base :allow_nil => true, :allow_blank => true + validate :finished_must_be_after_planted + + # check that any finished_at date occurs after planted_at + def finished_must_be_after_planted + return unless planted_at && finished_at # only check if we have both + errors.add(:finished_at, "must be after the planting date") unless planted_at < finished_at + end + def planting_slug "#{owner.login_name}-#{garden}-#{crop}".downcase.gsub(' ', '-') end diff --git a/spec/factories/planting.rb b/spec/factories/planting.rb index 5d229d545..60b4842e8 100644 --- a/spec/factories/planting.rb +++ b/spec/factories/planting.rb @@ -33,6 +33,7 @@ FactoryGirl.define do factory :finished_planting do finished true + planted_at '2014-07-30' finished_at '2014-08-30' end end diff --git a/spec/models/planting_spec.rb b/spec/models/planting_spec.rb index 13b910d22..6a93d3750 100644 --- a/spec/models/planting_spec.rb +++ b/spec/models/planting_spec.rb @@ -229,6 +229,20 @@ describe Planting do Planting.current.should_not include @f end + context "finished date validation" do + it 'requires finished date after planting date' do + @f = FactoryGirl.build(:finished_planting, :planted_at => + '2014-01-01', :finished_at => '2013-01-01') + @f.should_not be_valid + end + + it 'allows just the finished date' do + @f = FactoryGirl.build(:finished_planting, :finished_at => '2013-01-01') + @f.should be_valid + end + + end + end end From fa9c0294932655f9d7f4fa4d6ed4a5abedb2eb7b Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 12 Sep 2014 08:09:20 +1000 Subject: [PATCH 161/288] added sitewide crowdfunding banner --- app/assets/stylesheets/overrides.css.less | 33 ++++++++++++++++++----- app/views/layouts/_crowdfunding.html.haml | 7 +++++ app/views/layouts/application.html.haml | 1 + 3 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 app/views/layouts/_crowdfunding.html.haml diff --git a/app/assets/stylesheets/overrides.css.less b/app/assets/stylesheets/overrides.css.less index 9d26c042a..7b9952c5d 100644 --- a/app/assets/stylesheets/overrides.css.less +++ b/app/assets/stylesheets/overrides.css.less @@ -2,7 +2,10 @@ @import "custom_bootstrap/variables"; // this padding needs to be done before the responsive stuff is imported body { - padding-top: @navbar-height + 15px; + // modifying this for our promotional banner. can be replaced after if + // needed. + // padding-top: @navbar-height + 15px; + padding-top: @navbar-height; } //@import "twitter/bootstrap/responsive"; @@ -186,6 +189,28 @@ footer .navbar .nav { height: 100%; } +// Autosuggest + +.ui-autocomplete { + z-index: @zindex-tooltip; +} + +// Crowdfunding campaign, Sep-Oct 2014 + +.crowdfunding-banner { + text-align: center; + font-weight: bold; + background-color: lighten(@green, 30%); + margin-top: 0px; + margin-bottom: 5px; + padding: 15px; +} + +.crowdfunding-banner a { + color: @brown; + text-decoration: underline; +} + // Overrides applying only to mobile view. This must be at the end of the overrides file. @media only screen and (max-width: 767px) { @@ -203,9 +228,3 @@ footer .navbar .nav { display: block; } } - -// Autosuggest - -.ui-autocomplete { - z-index: @zindex-tooltip; -} diff --git a/app/views/layouts/_crowdfunding.html.haml b/app/views/layouts/_crowdfunding.html.haml new file mode 100644 index 000000000..63a27b4a8 --- /dev/null +++ b/app/views/layouts/_crowdfunding.html.haml @@ -0,0 +1,7 @@ +- daysleft = Date.new(2014, 10, 22) - Date.today # end of campaign is the 21st, this gives a number that matches what IGG uses +- if daysleft > 0 + .crowdfunding-banner + Help us share free growing information with the world. + = link_to "Support our crowdfunding campaign.", "https://www.indiegogo.com/projects/growstuff/x/6079859" + There are only #{daysleft.to_i} days left. + diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index aa674d95a..7ff38d7c2 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -3,6 +3,7 @@ = render :partial => "layouts/meta" %body = render :partial => "layouts/header" + = render :partial => "layouts/crowdfunding" .container .row From 374987488fada6277be2caf64fd4840828e6b714 Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 12 Sep 2014 08:30:44 +1000 Subject: [PATCH 162/288] Fixed broken tests. --- app/models/planting.rb | 2 +- spec/features/planting_a_crop_spec.rb | 1 + spec/models/planting_spec.rb | 6 +++++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/models/planting.rb b/app/models/planting.rb index 9585d0914..e5955f04f 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -60,7 +60,7 @@ class Planting < ActiveRecord::Base # check that any finished_at date occurs after planted_at def finished_must_be_after_planted - return unless planted_at && finished_at # only check if we have both + return unless planted_at and finished_at # only check if we have both errors.add(:finished_at, "must be after the planting date") unless planted_at < finished_at end diff --git a/spec/features/planting_a_crop_spec.rb b/spec/features/planting_a_crop_spec.rb index 52226aeee..12d0fc82a 100644 --- a/spec/features/planting_a_crop_spec.rb +++ b/spec/features/planting_a_crop_spec.rb @@ -30,6 +30,7 @@ feature "Planting a crop", :js => true do fill_autocomplete "crop", :with => "m" select_from_autocomplete "maize" within "form#new_planting" do + fill_in "When?", :with => '2014-07-01' check 'Mark as finished' fill_in "Finished date", :with => '2014-08-30' click_button "Save" diff --git a/spec/models/planting_spec.rb b/spec/models/planting_spec.rb index 6a93d3750..5248e0b0a 100644 --- a/spec/models/planting_spec.rb +++ b/spec/models/planting_spec.rb @@ -236,8 +236,12 @@ describe Planting do @f.should_not be_valid end + it 'allows just the planted date' do + @f = FactoryGirl.build(:planting, :planted_at => '2013-01-01', :finished_at => nil) + @f.should be_valid + end it 'allows just the finished date' do - @f = FactoryGirl.build(:finished_planting, :finished_at => '2013-01-01') + @f = FactoryGirl.build(:planting, :finished_at => '2013-01-01', :planted_at => nil) @f.should be_valid end From 46eaf3743782e95b45c7cd2f0cce214d5f15042a Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 12 Sep 2014 09:10:13 +1000 Subject: [PATCH 163/288] Fixed mailing list link to point to Talk instead --- app/views/home/_open.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/home/_open.html.haml b/app/views/home/_open.html.haml index 419233c1b..0cc44700e 100644 --- a/app/views/home/_open.html.haml +++ b/app/views/home/_open.html.haml @@ -33,8 +33,8 @@ We believe in collaboration, and work closely with our members and the wider food-growing community. Our team includes volunteers from all walks of life - and all skills levels. To get involved, - = link_to 'join our discussion mailing list', 'http://lists.growstuff.org/mailman/listinfo/discuss' + and all skill levels. To get involved, visit + = link_to 'Growstuff Talk', 'http://talk.growstuff.org/' or find more information on the = succeed "." do = link_to 'Growstuff Wiki', 'http://wiki.growstuff.org/' From 2bbccfc44da1d8cb070c9cad5e9099915fb0ba18 Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 12 Sep 2014 12:30:04 +1000 Subject: [PATCH 164/288] Added tests for planting reminder email --- spec/mailers/notifier_spec.rb | 39 ++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/spec/mailers/notifier_spec.rb b/spec/mailers/notifier_spec.rb index 3b4aa3ddb..6ade0fb78 100644 --- a/spec/mailers/notifier_spec.rb +++ b/spec/mailers/notifier_spec.rb @@ -2,22 +2,45 @@ require "spec_helper" describe Notifier do - describe "notify" do - before(:each) do - @notification = FactoryGirl.create(:notification) - @mail = Notifier.notify(@notification) - end + describe "notifications" do + let(:notification) { FactoryGirl.create(:notification) } + let(:mail) { Notifier.notify(notification) } it 'sets the subject correctly' do - @mail.subject.should == @notification.subject + mail.subject.should == notification.subject end it 'comes from noreply@growstuff.org' do - @mail.from.should == ['noreply@growstuff.org'] + mail.from.should == ['noreply@growstuff.org'] end it 'sends the mail to the recipient of the notification' do - @mail.to.should == [@notification.recipient.email] + mail.to.should == [notification.recipient.email] + end + end + + describe "planting reminders" do + let(:member) { FactoryGirl.create(:member) } + let(:mail) { Notifier.planting_reminder(member) } + + it 'sets the subject correctly' do + mail.subject.should == "What have you planted lately?" + 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 new planting URL' do + mail.body.encoded.should match new_planting_path + end + + it 'includes the new harvest URL' do + mail.body.encoded.should match new_harvest_path end end From 09bef3f9d8bb889551aff44c5cf94e17382f2db5 Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 12 Sep 2014 12:30:27 +1000 Subject: [PATCH 165/288] Added options for if you didn't plant/harvest anything --- .../notifier/planting_reminder.html.haml | 74 ++++++++++++------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/app/views/notifier/planting_reminder.html.haml b/app/views/notifier/planting_reminder.html.haml index 1e77fc195..98ca52fa9 100644 --- a/app/views/notifier/planting_reminder.html.haml +++ b/app/views/notifier/planting_reminder.html.haml @@ -3,40 +3,60 @@ %h2 What's new in your garden? -Have you planted anything recently? The most recent plantings you've -told us about are: - -%ul -- @plantings.each do |p| - %li - = link_to p, planting_url(p) - planted - = distance_of_time_in_words(p.created_at, Time.zone.now) - ago. - %p - Planted anything new? - = link_to "Track your plantings here.", new_planting_url + Have you planted anything recently? + +- if @member.plantings.size == 0 + %p + #{ENV['GROWSTUFF_SITE_NAME']} lets you track what food you're growing + in your garden. + %p + = link_to "Get started now.", new_planting_url + +- else + %p + The most recent plantings you've told us about are: + + %ul + - @plantings.each do |p| + %li + = link_to p, planting_url(p) + planted + = distance_of_time_in_words(p.created_at, Time.zone.now) + ago. + + %p + Planted anything new? + = link_to "Track your plantings here.", new_planting_url %h2 Your recent harvests -According to our records, the last few things you harvested were: +- if @member.harvests.size == 0 + %p + With #{ENV['GROWSTUFF_SITE_NAME']} you can keep track of what you + harvest from your garden. -%ul -- @harvests.each do |h| - %li - = link_to h, harvest_url(h) - harvested - = distance_of_time_in_words(h.created_at, Time.zone.now) - ago. + %p + = link_to "Get started now.", new_harvest_url + +- else + According to our records, the last few things you harvested were: + + %ul + - @harvests.each do |h| + %li + = link_to h, harvest_url(h) + harvested + = distance_of_time_in_words(h.created_at, Time.zone.now) + ago. + + %p + Harvested anything else lately? + = link_to "Track your harvests here.", new_harvest_url %p - Harvested anything else lately? - = link_to "Track your harvests here.", new_harvest_url - -%p - Don't want to get these emails any more? - = link_to "Turn off these notifications", edit_member_registration_url +Don't want to get these emails any more? += link_to "Turn off these notifications", edit_member_registration_url %p The #{site_name} team. From 3cf2a50a34ffac3e4917b3c8de4c0dd7d102e730 Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 12 Sep 2014 13:20:41 +1000 Subject: [PATCH 166/288] modified Planting.interesting to have some options re: howmany and photos --- app/models/planting.rb | 8 ++-- spec/models/planting_spec.rb | 77 ++++++++++++++++++++++++------------ 2 files changed, 55 insertions(+), 30 deletions(-) diff --git a/app/models/planting.rb b/app/models/planting.rb index b848d3490..78dcc82b2 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -78,15 +78,15 @@ class Planting < ActiveRecord::Base # return a list of interesting plantings, for the homepage etc. # we can't do this via a scope (as far as we know) so sadly we have to # do it this way. - def Planting.interesting - howmany = 12 # max amount to collect - + def Planting.interesting(howmany=12, require_photo=true) interesting_plantings = Array.new seen_owners = Hash.new(false) # keep track of which owners we've seen already Planting.all.each do |p| break if interesting_plantings.count == howmany # got enough yet? - next unless p.interesting? # skip those that don't have photos + if require_photo + next unless p.photos.present? # skip those without photos, if required + end next if seen_owners[p.owner] # skip if we already have one from this owner seen_owners[p.owner] = true # we've seen this owner interesting_plantings.push(p) diff --git a/spec/models/planting_spec.rb b/spec/models/planting_spec.rb index eae16cbe5..f791bf5af 100644 --- a/spec/models/planting_spec.rb +++ b/spec/models/planting_spec.rb @@ -153,7 +153,7 @@ describe Planting do end end - context 'interesting crops' do + context 'interesting plantings' do it 'picks up interesting plantings' do # plantings have members created implicitly for them # each member is different, hence these are all interesting @@ -177,38 +177,63 @@ describe Planting do ] end - it 'ignores plantings without photos' do - # first, an interesting planting - @planting = FactoryGirl.create(:planting) - @planting.photos << FactoryGirl.create(:photo) - @planting.save + context "default arguments" do + it 'ignores plantings without photos' do + # first, an interesting planting + @planting = FactoryGirl.create(:planting) + @planting.photos << FactoryGirl.create(:photo) + @planting.save - # this one doesn't have a photo - @boring_planting = FactoryGirl.create(:planting) + # this one doesn't have a photo + @boring_planting = FactoryGirl.create(:planting) - Planting.interesting.should include @planting - Planting.interesting.should_not include @boring_planting + Planting.interesting.should include @planting + Planting.interesting.should_not include @boring_planting + end + + it 'ignores plantings with the same owner' do + # this planting is older + @planting1 = FactoryGirl.create(:planting, :created_at => 1.day.ago) + @planting1.photos << FactoryGirl.create(:photo) + @planting1.save + + # this one is newer, and has the same owner, through the garden + @planting2 = FactoryGirl.create(:planting, + :created_at => 1.minute.ago, + :owner_id => @planting1.owner.id + ) + @planting2.photos << FactoryGirl.create(:photo) + @planting2.save + + # result: the newer one is interesting, the older one isn't + Planting.interesting.should include @planting2 + Planting.interesting.should_not include @planting1 + end end - it 'ignores plantings with the same owner' do - # this planting is older - @planting1 = FactoryGirl.create(:planting, :created_at => 1.day.ago) - @planting1.photos << FactoryGirl.create(:photo) - @planting1.save + context "with require_photo = false" do + it "returns plantings without photos" do + # first, a planting with a photo + @planting = FactoryGirl.create(:planting) + @planting.photos << FactoryGirl.create(:photo) + @planting.save - # this one is newer, and has the same owner, through the garden - @planting2 = FactoryGirl.create(:planting, - :created_at => 1.minute.ago, - :owner_id => @planting1.owner.id - ) - @planting2.photos << FactoryGirl.create(:photo) - @planting2.save + # this one doesn't have a photo + @boring_planting = FactoryGirl.create(:planting) - # result: the newer one is interesting, the older one isn't - Planting.interesting.should include @planting2 - Planting.interesting.should_not include @planting1 + interesting = Planting.interesting(10, false) + interesting.should include @planting + interesting.should include @boring_planting + end end - end + context "with howmany argument" do + it "only returns the number asked for" do + @plantings = FactoryGirl.create_list(:planting, 10) + Planting.interesting(3, false).length.should eq 3 + end + end + + end # interesting plantings end From eeb2bf9938192925951530d108692bcab6b632b3 Mon Sep 17 00:00:00 2001 From: Shiho Takagi Date: Wed, 17 Sep 2014 21:38:43 +1000 Subject: [PATCH 167/288] added planting_id param to Flickr set search (new_photo_path form). --- app/views/photos/new.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/photos/new.html.haml b/app/views/photos/new.html.haml index 32ea66915..365351fac 100644 --- a/app/views/photos/new.html.haml +++ b/app/views/photos/new.html.haml @@ -15,6 +15,7 @@ = form_tag(new_photo_path, :method => :get, :class => 'form-inline') do = label_tag :set, "Choose a photo set:", :class => 'control-label' = select_tag :set, options_for_select(@sets, @current_set), :class => 'input-large' + = hidden_field_tag :planting_id, @planting_id = submit_tag "Search", :class => 'btn btn-primary' %div.pagination From f355c393f006c596c3187a879960269f770dc547 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Thu, 18 Sep 2014 06:15:59 +1000 Subject: [PATCH 168/288] basic setup --- app/controllers/application_controller.rb | 10 ++ app/views/home/_blurb.html.haml | 5 +- config/initializers/locale.rb | 2 + config/locales/en.yml | 1 + config/locales/jp.yml | 2 + config/routes.rb | 137 +++++++++++----------- db/schema.rb | 1 + 7 files changed, 87 insertions(+), 71 deletions(-) create mode 100644 config/initializers/locale.rb create mode 100644 config/locales/jp.yml diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 0d07cb866..29f5adb35 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -4,6 +4,7 @@ class ApplicationController < ActionController::Base include ApplicationHelper after_filter :store_location + before_filter :set_locale def store_location if (request.path != "/members/sign_in" && @@ -32,5 +33,14 @@ class ApplicationController < ActionController::Base rescue_from CanCan::AccessDenied do |exception| redirect_to request.referer || root_url, :alert => exception.message end + + def set_locale + I18n.locale = params[:locale] || I18n.default_locale + end + + def default_url_options(options={}) + logger.debug "default_url_options is passed options: #{options.inspect}\n" + { locale: I18n.locale } + end end diff --git a/app/views/home/_blurb.html.haml b/app/views/home/_blurb.html.haml index 3e742b8d4..ba3fd072f 100644 --- a/app/views/home/_blurb.html.haml +++ b/app/views/home/_blurb.html.haml @@ -5,10 +5,7 @@ .row .col-md-8.info %p - #{ENV['GROWSTUFF_SITE_NAME']} is a community of food gardeners. - We're building an open source platform to help you learn about - growing food, track what you plant and harvest, and swap - seeds and produce with other gardeners near you. + #{ENV['GROWSTUFF_SITE_NAME']} #{t 'blurb'} = render :partial => 'stats' .col-md-4.signup diff --git a/config/initializers/locale.rb b/config/initializers/locale.rb new file mode 100644 index 000000000..ef55314c4 --- /dev/null +++ b/config/initializers/locale.rb @@ -0,0 +1,2 @@ +I18n.load_path += Dir[Rails.root.join('config', 'locales', '*.{rb,yml}')] + \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index 7a30c72fe..4d47f61ab 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -13,3 +13,4 @@ en: all: "Please sign in or sign up to create a %{subject}." manage: all: "Not authorized to %{action} %{subject}." + blurb: "is a community of food gardeners. We're building an open source platform to help you learn about growing food, track what you plant and harvest, and swap seeds and produce with other gardeners near you." \ No newline at end of file diff --git a/config/locales/jp.yml b/config/locales/jp.yml new file mode 100644 index 000000000..89dcea14a --- /dev/null +++ b/config/locales/jp.yml @@ -0,0 +1,2 @@ +jp: + blurb: "はガーデナーのコミュニティーです。" diff --git a/config/routes.rb b/config/routes.rb index 360ed2477..01600b7ed 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,78 +1,81 @@ Growstuff::Application.routes.draw do - resources :plant_parts - - - devise_for :members, :controllers => { :registrations => "registrations", :passwords => "passwords" } - resources :members - - resources :photos - - resources :authentications - - resources :plantings - match '/plantings/owner/:owner' => 'plantings#index', :as => 'plantings_by_owner' - - resources :gardens - match '/gardens/owner/:owner' => 'gardens#index', :as => 'gardens_by_owner' - - 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' - - resources :scientific_names - - match 'crops/wrangle' => 'crops#wrangle', :as => 'wrangle_crops' - match 'crops/hierarchy' => 'crops#hierarchy', :as => 'crops_hierarchy' - match 'crops/search' => 'crops#search', :as => 'crops_search' - resources :crops - - resources :comments - resources :roles - resources :forums - resources :notifications - - get '/places' => 'places#index' - get '/places/search' => 'places#search', :as => 'search_places' - get '/places/:place' => 'places#show', :as => 'place' - - # everything for paid accounts etc - resources :account_types - resources :accounts - resources :orders - match 'orders/:id/checkout' => 'orders#checkout', :as => 'checkout_order' - match 'orders/:id/complete' => 'orders#complete', :as => 'complete_order' - match 'orders/:id/cancel' => 'orders#cancel', :as => 'cancel_order' - - resources :order_items - resources :products - - get "home/index" + get "/:locale" => 'home#index' root :to => 'home#index' - match 'auth/:provider/callback' => 'authentications#create' + scope '/:locale' do + + resources :plant_parts + + devise_for :members, :controllers => { :registrations => "registrations", :passwords => "passwords" } + resources :members + + resources :photos + + resources :authentications + + resources :plantings + match '/plantings/owner/:owner' => 'plantings#index', :as => 'plantings_by_owner' + + resources :gardens + match '/gardens/owner/:owner' => 'gardens#index', :as => 'gardens_by_owner' + + 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' + + resources :scientific_names + + match 'crops/wrangle' => 'crops#wrangle', :as => 'wrangle_crops' + match 'crops/hierarchy' => 'crops#hierarchy', :as => 'crops_hierarchy' + match 'crops/search' => 'crops#search', :as => 'crops_search' + resources :crops + + resources :comments + resources :roles + resources :forums + resources :notifications + + get '/places' => 'places#index' + get '/places/search' => 'places#search', :as => 'search_places' + get '/places/:place' => 'places#show', :as => 'place' + + # everything for paid accounts etc + resources :account_types + resources :accounts + resources :orders + match 'orders/:id/checkout' => 'orders#checkout', :as => 'checkout_order' + match 'orders/:id/complete' => 'orders#complete', :as => 'complete_order' + match 'orders/:id/cancel' => 'orders#cancel', :as => 'cancel_order' + + resources :order_items + resources :products + + match 'auth/:provider/callback' => 'authentications#create' - match '/policy/:action' => 'policy#:action' + match '/policy/:action' => 'policy#:action' - match '/support' => 'support#index' - match '/support/:action' => 'support#:action' + match '/support' => 'support#index' + match '/support/:action' => 'support#:action' - match '/about' => 'about#index' - match '/about/:action' => 'about#:action' + match '/about' => 'about#index' + match '/about/:action' => 'about#:action' - match '/shop' => 'shop#index' - match '/shop/:action' => 'shop#:action' + match '/shop' => 'shop#index' + match '/shop/:action' => 'shop#:action' - match '/admin/orders' => 'admin/orders#index' - match '/admin/orders/:action' => 'admin/orders#:action' - match '/admin' => 'admin#index' - match '/admin/newsletter' => 'admin#newsletter', :as => :admin_newsletter - match '/admin/:action' => 'admin#:action' + match '/admin/orders' => 'admin/orders#index' + match '/admin/orders/:action' => 'admin/orders#:action' + match '/admin' => 'admin#index' + match '/admin/newsletter' => 'admin#newsletter', :as => :admin_newsletter + match '/admin/:action' => 'admin#:action' -end + end + +end \ No newline at end of file diff --git a/db/schema.rb b/db/schema.rb index 29271f5e6..af91a7a9d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -139,6 +139,7 @@ ActiveRecord::Schema.define(:version => 20140829230600) do t.text "bio" t.integer "plantings_count" t.boolean "newsletter" + t.boolean "send_planting_reminder", :default => true end add_index "members", ["confirmation_token"], :name => "index_users_on_confirmation_token", :unique => true From 87df8661c7c6829549ef2e4bb5dc71d4af4527d2 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Thu, 18 Sep 2014 06:24:59 +1000 Subject: [PATCH 169/288] raise exception on missing translations in test and dev environment --- config/initializers/locale.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/config/initializers/locale.rb b/config/initializers/locale.rb index ef55314c4..eb4ed56e7 100644 --- a/config/initializers/locale.rb +++ b/config/initializers/locale.rb @@ -1,2 +1,8 @@ I18n.load_path += Dir[Rails.root.join('config', 'locales', '*.{rb,yml}')] + +if Rails.env.development? || Rails.env.test? + I18n.exception_handler = lambda do |exception, locale, key, options| + raise "Missing translation: #{key}" + end +end \ No newline at end of file From f8ee9d05894100a2b4041e23b5d154110e902f67 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Thu, 18 Sep 2014 07:14:35 +1000 Subject: [PATCH 170/288] don't scope routes to locale to make url prettier because it breaks tests like whoa --- Gemfile | 1 + Gemfile.lock | 18 +++++ config/i18n-tasks.yml | 90 ++++++++++++++++++++++ config/initializers/locale.rb | 1 + config/routes.rb | 137 +++++++++++++++++----------------- spec/i18n_spec.rb | 18 +++++ 6 files changed, 195 insertions(+), 70 deletions(-) create mode 100644 config/i18n-tasks.yml create mode 100644 spec/i18n_spec.rb diff --git a/Gemfile b/Gemfile index e47403546..fd8184894 100644 --- a/Gemfile +++ b/Gemfile @@ -127,4 +127,5 @@ group :development, :test do gem 'coveralls', require: false # coverage analysis gem 'capybara' # integration tests gem 'poltergeist', '~> 1.5.1' # for headless JS testing + gem 'i18n-tasks' # adds tests for finding missing and unused translations end diff --git a/Gemfile.lock b/Gemfile.lock index 7829a34de..d8ccbfcb2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -110,6 +110,10 @@ GEM warden (~> 1.2.3) diff-lcs (1.1.3) docile (1.1.5) + easy_translate (0.5.0) + json + thread + thread_safe erubis (2.7.0) execjs (2.2.1) factory_girl (4.4.0) @@ -138,11 +142,21 @@ GEM haml (>= 3.1, < 4.1) railties (>= 3.1, < 4.1) hashie (3.3.1) + highline (1.6.21) hike (1.2.3) httparty (0.11.0) multi_json (~> 1.0) multi_xml (>= 0.5.2) i18n (0.6.1) + i18n-tasks (0.7.6) + activesupport + easy_translate (>= 0.5.0) + erubis + highline + i18n + slop (>= 3.5.0) + term-ansicolor + terminal-table journey (1.0.4) jquery-rails (3.1.1) railties (>= 3.0, < 5.0) @@ -257,6 +271,7 @@ GEM multi_json simplecov-html (~> 0.8.0) simplecov-html (0.8.0) + slop (3.6.0) sprockets (2.2.2) hike (~> 1.2) multi_json (~> 1.0) @@ -266,10 +281,12 @@ GEM sprockets (>= 1.0.2) term-ansicolor (1.3.0) tins (~> 1.0) + terminal-table (1.4.5) therubyracer (0.12.1) libv8 (~> 3.16.14.0) ref thor (0.19.1) + thread (0.1.4) thread_safe (0.3.4) tilt (1.4.1) tins (1.3.2) @@ -325,6 +342,7 @@ DEPENDENCIES gravatar-ultimate haml haml-rails + i18n-tasks jquery-rails jquery-ui-rails js-routes diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml new file mode 100644 index 000000000..e1a1c35d9 --- /dev/null +++ b/config/i18n-tasks.yml @@ -0,0 +1,90 @@ +# i18n-tasks finds and manages missing and unused translations https://github.com/glebm/i18n-tasks + +base_locale: en +## i18n-tasks detects locales automatically from the existing locale files +## uncomment to set locales explicitly +# locales: [en, es, fr] + +## i18n-tasks report locale, default: en, available: en, ru +# internal_locale: ru + +# Read and write locale data +data: + ## by default, translation data are read from the file system, or you can provide a custom data adapter + # adapter: I18n::Tasks::Data::FileSystem + + # Locale files to read from + read: + - config/locales/%{locale}.yml + # - config/locales/*.%{locale}.yml + # - config/locales/**/*.%{locale}.yml + + # key => file routes, matched top to bottom + write: + ## E.g., write devise and simple form keys to their respective files + # - ['{devise, simple_form}.*', 'config/locales/\1.%{locale}.yml'] + # Catch-all + - config/locales/%{locale}.yml + # `i18n-tasks normalize -p` will force move the keys according to these rules + + # YAML / JSON serializer options, passed to load / dump / parse / serialize + yaml: + write: + # do not wrap lines at 80 characters + line_width: -1 + json: + write: + # pretty print JSON + indent: ' ' + space: ' ' + object_nl: "\n" + array_nl: "\n" + +# Find translate calls +search: + ## Default scanner finds t() and I18n.t() calls + # scanner: I18n::Tasks::Scanners::PatternWithScopeScanner + + ## Paths to search in, passed to File.find + paths: + - app/ + + ## Root for resolving relative keys (default) + # relative_roots: + # - app/views + + ## File.fnmatch patterns to exclude from search (default) + # exclude: ["*.jpg", "*.png", "*.gif", "*.svg", "*.ico", "*.eot", "*.ttf", "*.woff", "*.pdf"] + + ## Or, File.fnmatch patterns to include + # include: ["*.rb", "*.html.slim"] + + ## Lines starting with # or / are ignored by default + # ignore_lines: + # - "^\\s*[#/](?!\\si18n-tasks-use)" + +## Google Translate +# translation: +# # Get an API key and set billing info at https://code.google.com/apis/console to use Google Translate +# api_key: "AbC-dEf5" + +## Consider these keys not missing +# ignore_missing: +# - pagination.views.* + +## Consider these keys used +# ignore_unused: +# - 'simple_form.{yes,no}' +# - 'simple_form.{placeholders,hints,labels}.*' +# - 'simple_form.{error_notification,required}.:' + +## Exclude these keys from `i18n-tasks eq-base' report +# ignore_eq_base: +# all: +# - common.ok +# fr,es: +# - common.brand + +## Exclude these keys from all of the reports +# ignore: +# - kaminari.* diff --git a/config/initializers/locale.rb b/config/initializers/locale.rb index eb4ed56e7..21c2733cb 100644 --- a/config/initializers/locale.rb +++ b/config/initializers/locale.rb @@ -1,4 +1,5 @@ I18n.load_path += Dir[Rails.root.join('config', 'locales', '*.{rb,yml}')] +I18n.default_locale = :en if Rails.env.development? || Rails.env.test? I18n.exception_handler = lambda do |exception, locale, key, options| diff --git a/config/routes.rb b/config/routes.rb index 01600b7ed..360ed2477 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,81 +1,78 @@ Growstuff::Application.routes.draw do - get "/:locale" => 'home#index' + resources :plant_parts + + + devise_for :members, :controllers => { :registrations => "registrations", :passwords => "passwords" } + resources :members + + resources :photos + + resources :authentications + + resources :plantings + match '/plantings/owner/:owner' => 'plantings#index', :as => 'plantings_by_owner' + + resources :gardens + match '/gardens/owner/:owner' => 'gardens#index', :as => 'gardens_by_owner' + + 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' + + resources :scientific_names + + match 'crops/wrangle' => 'crops#wrangle', :as => 'wrangle_crops' + match 'crops/hierarchy' => 'crops#hierarchy', :as => 'crops_hierarchy' + match 'crops/search' => 'crops#search', :as => 'crops_search' + resources :crops + + resources :comments + resources :roles + resources :forums + resources :notifications + + get '/places' => 'places#index' + get '/places/search' => 'places#search', :as => 'search_places' + get '/places/:place' => 'places#show', :as => 'place' + + # everything for paid accounts etc + resources :account_types + resources :accounts + resources :orders + match 'orders/:id/checkout' => 'orders#checkout', :as => 'checkout_order' + match 'orders/:id/complete' => 'orders#complete', :as => 'complete_order' + match 'orders/:id/cancel' => 'orders#cancel', :as => 'cancel_order' + + resources :order_items + resources :products + + get "home/index" root :to => 'home#index' - scope '/:locale' do - - resources :plant_parts - - devise_for :members, :controllers => { :registrations => "registrations", :passwords => "passwords" } - resources :members - - resources :photos - - resources :authentications - - resources :plantings - match '/plantings/owner/:owner' => 'plantings#index', :as => 'plantings_by_owner' - - resources :gardens - match '/gardens/owner/:owner' => 'gardens#index', :as => 'gardens_by_owner' - - 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' - - resources :scientific_names - - match 'crops/wrangle' => 'crops#wrangle', :as => 'wrangle_crops' - match 'crops/hierarchy' => 'crops#hierarchy', :as => 'crops_hierarchy' - match 'crops/search' => 'crops#search', :as => 'crops_search' - resources :crops - - resources :comments - resources :roles - resources :forums - resources :notifications - - get '/places' => 'places#index' - get '/places/search' => 'places#search', :as => 'search_places' - get '/places/:place' => 'places#show', :as => 'place' - - # everything for paid accounts etc - resources :account_types - resources :accounts - resources :orders - match 'orders/:id/checkout' => 'orders#checkout', :as => 'checkout_order' - match 'orders/:id/complete' => 'orders#complete', :as => 'complete_order' - match 'orders/:id/cancel' => 'orders#cancel', :as => 'cancel_order' - - resources :order_items - resources :products - - match 'auth/:provider/callback' => 'authentications#create' + match 'auth/:provider/callback' => 'authentications#create' - match '/policy/:action' => 'policy#:action' + match '/policy/:action' => 'policy#:action' - match '/support' => 'support#index' - match '/support/:action' => 'support#:action' + match '/support' => 'support#index' + match '/support/:action' => 'support#:action' - match '/about' => 'about#index' - match '/about/:action' => 'about#:action' + match '/about' => 'about#index' + match '/about/:action' => 'about#:action' - match '/shop' => 'shop#index' - match '/shop/:action' => 'shop#:action' + match '/shop' => 'shop#index' + match '/shop/:action' => 'shop#:action' - match '/admin/orders' => 'admin/orders#index' - match '/admin/orders/:action' => 'admin/orders#:action' - match '/admin' => 'admin#index' - match '/admin/newsletter' => 'admin#newsletter', :as => :admin_newsletter - match '/admin/:action' => 'admin#:action' + match '/admin/orders' => 'admin/orders#index' + match '/admin/orders/:action' => 'admin/orders#:action' + match '/admin' => 'admin#index' + match '/admin/newsletter' => 'admin#newsletter', :as => :admin_newsletter + match '/admin/:action' => 'admin#:action' - end - -end \ No newline at end of file +end diff --git a/spec/i18n_spec.rb b/spec/i18n_spec.rb new file mode 100644 index 000000000..0475c9fcc --- /dev/null +++ b/spec/i18n_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' +require 'i18n/tasks' + +describe 'I18n' do + let(:i18n) { I18n::Tasks::BaseTask.new } + let(:missing_keys) { i18n.missing_keys } + let(:unused_keys) { i18n.unused_keys } + + it 'does not have missing keys' do + expect(missing_keys).to be_empty, + "Missing #{missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them" + end + + it 'does not have unused keys' do + expect(i18n.unused_keys).to be_empty, + "#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them" + end +end From dd75f2aef909ab533f7c8c93db591f16ebc4778d Mon Sep 17 00:00:00 2001 From: Shiho Takagi Date: Thu, 18 Sep 2014 14:18:18 +1000 Subject: [PATCH 171/288] updated regex for crop in markdown --- lib/haml/filters/growstuff_markdown.rb | 2 +- spec/lib/haml/filters/growstuff_markdown_spec.rb | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/haml/filters/growstuff_markdown.rb b/lib/haml/filters/growstuff_markdown.rb index 35333a256..7069590e7 100644 --- a/lib/haml/filters/growstuff_markdown.rb +++ b/lib/haml/filters/growstuff_markdown.rb @@ -7,7 +7,7 @@ module Haml::Filters def render(text) # turn [tomato](crop) into [tomato](http://growstuff.org/crops/tomato) - expanded = text.gsub(/\[(.*?)\]\(crop\)/) do |m| + expanded = text.gsub(/\[([^\[\]]+?)\]\(crop\)/) do |m| crop_str = $1 # find crop case-insensitively crop = Crop.where('lower(name) = ?', crop_str.downcase).first diff --git a/spec/lib/haml/filters/growstuff_markdown_spec.rb b/spec/lib/haml/filters/growstuff_markdown_spec.rb index 566740307..cc6a2af54 100644 --- a/spec/lib/haml/filters/growstuff_markdown_spec.rb +++ b/spec/lib/haml/filters/growstuff_markdown_spec.rb @@ -52,4 +52,14 @@ describe 'Haml::Filters::Growstuff_Markdown' do rendered.should match /#{output_link(@crop, 'ToMaTo')}/ end + + it "doesn't convert when it's not followed by '(crop)'" do + tomato = FactoryGirl.create(:tomato) + maize = FactoryGirl.create(:maize) + string = "[maize](http://example.com) #{input_link(tomato)}" + rendered = Haml::Filters::GrowstuffMarkdown.render(string) + rendered.should match /#{output_link(tomato)}/ + rendered.should_not match /#{output_link(maize)}/ + rendered.should match "maize" + end end From 16f4d2f80e105f18881238fa42baa69bdd25d612 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Fri, 19 Sep 2014 07:17:08 +1000 Subject: [PATCH 172/288] fix spec to check href with newly added locale query param --- app/controllers/application_controller.rb | 2 +- config/locales/{jp.yml => ja.yml} | 2 +- spec/features/crop_wranglers_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename config/locales/{jp.yml => ja.yml} (94%) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 29f5adb35..91fc81413 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -38,8 +38,8 @@ class ApplicationController < ActionController::Base I18n.locale = params[:locale] || I18n.default_locale end + # Adds locale query parameter to every path / url helper def default_url_options(options={}) - logger.debug "default_url_options is passed options: #{options.inspect}\n" { locale: I18n.locale } end diff --git a/config/locales/jp.yml b/config/locales/ja.yml similarity index 94% rename from config/locales/jp.yml rename to config/locales/ja.yml index 89dcea14a..c213277b9 100644 --- a/config/locales/jp.yml +++ b/config/locales/ja.yml @@ -1,2 +1,2 @@ -jp: +ja: blurb: "はガーデナーのコミュニティーです。" diff --git a/spec/features/crop_wranglers_spec.rb b/spec/features/crop_wranglers_spec.rb index 80475bd18..327be7e51 100644 --- a/spec/features/crop_wranglers_spec.rb +++ b/spec/features/crop_wranglers_spec.rb @@ -19,7 +19,7 @@ feature "crop wranglers" do within '.crop_wranglers' do expect(page).to have_content 'Crop Wranglers:' crop_wranglers.each do |crop_wrangler| - page.should have_link crop_wrangler.login_name, :href => member_path(crop_wrangler) + page.should have_link crop_wrangler.login_name, :href => member_path(crop_wrangler, {locale: 'en'}) end end end From 983fa8ede04a81aa25d5426dd07b49850f22674d Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Fri, 19 Sep 2014 07:29:04 +1000 Subject: [PATCH 173/288] abstract strings out of blurb partial --- app/views/home/_blurb.html.haml | 11 +++++------ config/locales/en.yml | 9 ++++++++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/views/home/_blurb.html.haml b/app/views/home/_blurb.html.haml index ba3fd072f..fe06dad9c 100644 --- a/app/views/home/_blurb.html.haml +++ b/app/views/home/_blurb.html.haml @@ -5,15 +5,14 @@ .row .col-md-8.info %p - #{ENV['GROWSTUFF_SITE_NAME']} #{t 'blurb'} + #{ENV['GROWSTUFF_SITE_NAME']} #{t('.intro')} = render :partial => 'stats' .col-md-4.signup - %p Join now for your free garden journal, seed sharing, forums, and more. - %p= link_to 'Sign up', new_member_registration_path, :class => 'btn btn-primary btn-lg' + %p= t('.perks') + %p= link_to "#{t('.sign_up')}", new_member_registration_path, :class => 'btn btn-primary btn-lg' %p %small - Or - = link_to "sign in", new_member_session_path - if you already have an account. + = t('.already') + = link_to "#{t('.sign_in')}", new_member_session_path diff --git a/config/locales/en.yml b/config/locales/en.yml index 4d47f61ab..591ddd0e8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -13,4 +13,11 @@ en: all: "Please sign in or sign up to create a %{subject}." manage: all: "Not authorized to %{action} %{subject}." - blurb: "is a community of food gardeners. We're building an open source platform to help you learn about growing food, track what you plant and harvest, and swap seeds and produce with other gardeners near you." \ No newline at end of file + + home: + blurb: + intro: "is a community of food gardeners. We're building an open source platform to help you learn about growing food, track what you plant and harvest, and swap seeds and produce with other gardeners near you." + perks: "Join now for your free garden journal, seed sharing, forums, and more." + sign_up: "Sign up" + sign_in: "Sign in" + already: "Already have an account?" \ No newline at end of file From aa028db2dc649d90a0f2090da1904ad35bbe7c6d Mon Sep 17 00:00:00 2001 From: Shiho Takagi Date: Wed, 17 Sep 2014 21:38:43 +1000 Subject: [PATCH 174/288] added planting_id param to Flickr set search (new_photo_path form). --- CONTRIBUTORS.md | 1 + app/views/photos/new.html.haml | 1 + 2 files changed, 2 insertions(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 5bd5cc601..79dfdcaa1 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -45,3 +45,4 @@ submit the change with your pull request. - Marlena Compton / [Marlena](https://github.com/marlena) - Elizabeth A. Kari / [catfriend](https://github.com/catfriend) - Cheri Allen / [cherimarie](https://github.com/cherimarie) +- Shiho Takagi / [oshiho3](https://github.com/oshiho3) diff --git a/app/views/photos/new.html.haml b/app/views/photos/new.html.haml index 32ea66915..365351fac 100644 --- a/app/views/photos/new.html.haml +++ b/app/views/photos/new.html.haml @@ -15,6 +15,7 @@ = form_tag(new_photo_path, :method => :get, :class => 'form-inline') do = label_tag :set, "Choose a photo set:", :class => 'control-label' = select_tag :set, options_for_select(@sets, @current_set), :class => 'input-large' + = hidden_field_tag :planting_id, @planting_id = submit_tag "Search", :class => 'btn btn-primary' %div.pagination From 60354264d6643a6d268cb87efb83ade72444517a Mon Sep 17 00:00:00 2001 From: Shiho Takagi Date: Fri, 19 Sep 2014 16:00:25 +1000 Subject: [PATCH 175/288] Included PT bug# to its test description. Test using the case from the bug --- spec/lib/haml/filters/growstuff_markdown_spec.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/spec/lib/haml/filters/growstuff_markdown_spec.rb b/spec/lib/haml/filters/growstuff_markdown_spec.rb index cc6a2af54..718645322 100644 --- a/spec/lib/haml/filters/growstuff_markdown_spec.rb +++ b/spec/lib/haml/filters/growstuff_markdown_spec.rb @@ -52,14 +52,12 @@ describe 'Haml::Filters::Growstuff_Markdown' do rendered.should match /#{output_link(@crop, 'ToMaTo')}/ end - - it "doesn't convert when it's not followed by '(crop)'" do + it "fixes PT bug #78615258 (Markdown rendering bug with URLs and crops in same text)" do tomato = FactoryGirl.create(:tomato) - maize = FactoryGirl.create(:maize) - string = "[maize](http://example.com) #{input_link(tomato)}" + string = "[test](http://example.com) [tomato](crop)" rendered = Haml::Filters::GrowstuffMarkdown.render(string) rendered.should match /#{output_link(tomato)}/ - rendered.should_not match /#{output_link(maize)}/ - rendered.should match "maize" + rendered.should match "test" end + end From e3f590ede5d1cfbf2f0839a039c7f8f52931c6c1 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sat, 20 Sep 2014 21:47:18 +1000 Subject: [PATCH 176/288] abstract strings in home views into locale file --- app/views/home/_blurb.html.haml | 8 ++- app/views/home/_crops.html.haml | 8 +-- app/views/home/_discuss.html.haml | 6 +-- app/views/home/_keep_in_touch.html.haml | 13 ++--- app/views/home/_members.html.haml | 4 +- app/views/home/_open.html.haml | 40 ++++---------- app/views/home/_seeds.html.haml | 18 +++---- app/views/home/_stats.html.haml | 11 ++-- app/views/home/index.html.haml | 17 +++--- config/locales/en.yml | 72 +++++++++++++++++++++++-- 10 files changed, 114 insertions(+), 83 deletions(-) diff --git a/app/views/home/_blurb.html.haml b/app/views/home/_blurb.html.haml index fe06dad9c..3e85b4496 100644 --- a/app/views/home/_blurb.html.haml +++ b/app/views/home/_blurb.html.haml @@ -4,15 +4,13 @@ .row .col-md-8.info - %p - #{ENV['GROWSTUFF_SITE_NAME']} #{t('.intro')} + %p= t('.intro', site_name: ENV['GROWSTUFF_SITE_NAME']) = render :partial => 'stats' .col-md-4.signup %p= t('.perks') - %p= link_to "#{t('.sign_up')}", new_member_registration_path, :class => 'btn btn-primary btn-lg' + %p= link_to(t('.sign_up'), new_member_registration_path, :class => 'btn btn-primary btn-lg') %p %small - = t('.already') - = link_to "#{t('.sign_in')}", new_member_session_path + = t('.already_html', sign_in: link_to(t('.sign_in_linktext'), new_member_session_path)) diff --git a/app/views/home/_crops.html.haml b/app/views/home/_crops.html.haml index 62b6d9e4a..ce3c775ce 100644 --- a/app/views/home/_crops.html.haml +++ b/app/views/home/_crops.html.haml @@ -1,14 +1,14 @@ .row .col-md-6.hidden-xs - cache "interesting_crops", :expires_in => 1.day do - %h2 Some of our crops + %h2= t('.our_crops') - Crop.interesting.each do |c| .col-md-3{:style => 'margin:0px; padding: 3px'} = render :partial => 'crops/image_with_popover', :locals => { :crop => c } .col-md-6 - cache "interesting_plantings" do - %h2 Recently planted + %h2= t('.recently_planted') = render :partial => 'plantings/list', :locals => { :plantings => Planting.interesting.first(4) } .row @@ -16,8 +16,8 @@ - cache "recent_crops" do %p{ :style => 'margin-top: 11.25px' } %strong - Recently added crops: + #{t('.recently_added')}: != Crop.recent.limit(12).map {|c| link_to(c, c) }.join(", ") %p.text-right - =link_to "View all crops »", crops_path + =link_to "#{t('.view_all')} »", crops_path diff --git a/app/views/home/_discuss.html.haml b/app/views/home/_discuss.html.haml index 7da71a54d..22b9972b9 100644 --- a/app/views/home/_discuss.html.haml +++ b/app/views/home/_discuss.html.haml @@ -1,4 +1,4 @@ -%h2 Discussion +%h2= t('.discussion') - cache "recent_posts" do - posts = Post.limit(6) @@ -10,9 +10,9 @@ - if forums %ul.list-inline %li - %strong Forums: + %strong #{t('.forums')}: - forums.each do |f| %li= link_to f.name, f %p.text-right - = link_to "View all posts »", posts_path + = link_to "#{t('.view_all')} »", posts_path diff --git a/app/views/home/_keep_in_touch.html.haml b/app/views/home/_keep_in_touch.html.haml index 34365352b..5bc821887 100644 --- a/app/views/home/_keep_in_touch.html.haml +++ b/app/views/home/_keep_in_touch.html.haml @@ -1,17 +1,14 @@ -%h2 Keep in touch +%h2= t('.keep_in_touch') %p =image_tag("twitter_32.png", :alt => '') - Follow - =link_to '@growstufforg', 'http://twitter.com/growstufforg' - on Twitter. + = t('.twitter_html', link: link_to(t('.twitter_linktext'), 'http://twitter.com/growstufforg')) %p =image_tag("blog_32.png", :alt => '') - Subscribe to the - =link_to 'Growstuff Blog', 'http://blog.growstuff.org/' + = t('.blog_html', link: link_to(t('.blog_linktext'), 'http://blog.growstuff.org/')) %p =image_tag("email_32.png", :alt => '') - Subscribe to our - =link_to 'email newsletter', 'http://blog.growstuff.org/newsletter' + = t('.newsletter_html', link: link_to(t('.newsletter_linktext'), 'http://blog.growstuff.org/newsletter')) + diff --git a/app/views/home/_members.html.haml b/app/views/home/_members.html.haml index 6d3437302..25cb0867b 100644 --- a/app/views/home/_members.html.haml +++ b/app/views/home/_members.html.haml @@ -2,7 +2,7 @@ .hidden-xs - members = Member.interesting.first(6) - if members.present? - %h2 Some of our members + %h2= t('.title') .row - members.each do |m| @@ -10,4 +10,4 @@ = render :partial => "members/thumbnail", :locals => { :member => m } %p.text-right - = link_to "View all members »", members_path + = link_to "#{t('.view_all')} »", members_path diff --git a/app/views/home/_open.html.haml b/app/views/home/_open.html.haml index 0cc44700e..1eeb2d847 100644 --- a/app/views/home/_open.html.haml +++ b/app/views/home/_open.html.haml @@ -1,43 +1,21 @@ -%h2 Open Source +%h2= t('.open_source_title') %p - #{ENV['GROWSTUFF_SITE_NAME']} is open source software, - which means that we share this website's code for free with our - community and the world. We believe that openness, - sustainability, and social good go hand in hand. You can read more - about - =link_to "why Growstuff is open source", "http://blog.growstuff.org/2013/02/20/why-growstuff-is-open-source/" - or check out our code on - =succeed '.' do - = link_to 'Github', 'http://github.com/Growstuff/growstuff' + #{ENV['GROWSTUFF_SITE_NAME']} #{t('.open_source_body_html', why: link_to(t('.why_linktext'), "http://blog.growstuff.org/2013/02/20/why-growstuff-is-open-source/"), github: link_to(t('.github_linktext'), 'http://github.com/Growstuff/growstuff'))} -%h2 Open Data and APIs + +%h2= t('.open_data_title') %p - We're building a database - of crops, planting advice, seed sources, and other information - that anyone can use for free, under a - = succeed '.' do - = link_to 'Creative Commons license', 'http://creativecommons.org/licenses/by-sa/3.0/' - You can use this data for research, to build apps, or for any - purpose at all. Read more about our + = t('.open_data_body_html', creative_commons_link: link_to(t('.creative_commons_linktext'), 'http://creativecommons.org/licenses/by-sa/3.0/'), wiki_link: link_to(t('.wiki_linktext'), 'http://wiki.growstuff.org/index.php/Open_data'), api_docs_link: link_to(t('.api_docs_linktext'), 'http://wiki.growstuff.org/index.php/API')) - = link_to 'open data', 'http://wiki.growstuff.org/index.php/Open_data' - and - = succeed '.' do - = link_to 'API documentation', 'http://wiki.growstuff.org/index.php/API' -%h2 Get Involved + +%h2= t('.get_involved_title') %p - We believe in collaboration, and work closely with our members - and the wider food-growing community. - Our team includes volunteers from all walks of life - and all skill levels. To get involved, visit - = link_to 'Growstuff Talk', 'http://talk.growstuff.org/' - or find more information on the - = succeed "." do - = link_to 'Growstuff Wiki', 'http://wiki.growstuff.org/' + = t('.get_involved_body_html', talk_link: link_to(t('.talk_linktext'), 'http://talk.growstuff.org/'), wiki_link: link_to(t('.wiki_linktext'), 'http://wiki.growstuff.org/')) + %h2 Support Growstuff diff --git a/app/views/home/_seeds.html.haml b/app/views/home/_seeds.html.haml index ccacd0bf4..52d996373 100644 --- a/app/views/home/_seeds.html.haml +++ b/app/views/home/_seeds.html.haml @@ -1,18 +1,18 @@ - seeds = Seed.interesting.first(6) - if seeds.present? - %h2 Seeds available to trade + %h2= t('.title') - cache "interesting_seeds" do - if seeds.length > 0 %table.table.table-striped %tr - %th Owner - %th Crop - %th.hidden-xs.hidden-sm Description - %th Will trade to - %th From location + %th= t('.owner') + %th= t('.crop') + %th.hidden-xs.hidden-sm= t('.description') + %th= t('.trade_to') + %th= t('.from') %th - seeds.each do |seed| @@ -23,8 +23,8 @@ %td= seed.tradable? ? seed.tradable_to : '' %td - if seed.tradable? - = seed.owner.location.blank? ? "unspecified" : truncate(seed.owner.location, :length => 25, :separator => ', ') - %td= link_to 'Details', seed, :class => 'btn btn-default btn-xs' + = seed.owner.location.blank? ? t('.unspecified') : truncate(seed.owner.location, :length => 25, :separator => ', ') + %td= link_to t('.details'), seed, :class => 'btn btn-default btn-xs' %p.text-right - = link_to "View all seeds »", seeds_path + = link_to "#{t('.view_all')} »", seeds_path diff --git a/app/views/home/_stats.html.haml b/app/views/home/_stats.html.haml index 93c16327d..78ab2c0bc 100644 --- a/app/views/home/_stats.html.haml +++ b/app/views/home/_stats.html.haml @@ -1,10 +1,5 @@ - cache("homepage_stats") do %p.stats - So far, - = link_to "#{Member.confirmed.count.to_i} members", members_path - have planted - = link_to "#{Crop.count.to_i} crops", crops_path - = link_to "#{Planting.count.to_i} times", plantings_path - in - = succeed "." do - = link_to "#{Garden.count.to_i} gardens", gardens_path + = t('.message_html', member: link_to(t('.member_linktext', count: Member.confirmed.count.to_i), members_path), number_crops: link_to(t('.number_crops_linktext', count: Crop.count.to_i), crops_path), number_plantings: link_to(t('.number_plantings_linktext', count: Planting.count.to_i), plantings_path), number_gardens: link_to(t('.number_gardens_linktext', count: Garden.count.to_i), gardens_path)) + + diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 57fb6473f..f36a29aab 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -1,19 +1,16 @@ .row .col-md-12 - if member_signed_in? - %h1 - Welcome to - = succeed "," do - = ENV['GROWSTUFF_SITE_NAME'] - = current_member + %h1= t('.welcome', site_name: ENV['GROWSTUFF_SITE_NAME'], member_name: current_member) + = render :partial => 'stats' %p .btn-group - = link_to "Plant", new_planting_path, :class => 'btn btn-default' - = link_to "Harvest", new_harvest_path, :class => 'btn btn-default' - = link_to "Add seeds", new_seed_path, :class => 'btn btn-default' - = link_to "Post", new_post_path, :class => 'btn btn-default' - = link_to "Edit profile", edit_member_registration_path, :class => 'btn btn-default' + = link_to t('.plant'), new_planting_path, :class => 'btn btn-default' + = link_to t('.harvest'), new_harvest_path, :class => 'btn btn-default' + = link_to t('.add_seeds'), new_seed_path, :class => 'btn btn-default' + = link_to t('.post'), new_post_path, :class => 'btn btn-default' + = link_to t('.edit_profile'), edit_member_registration_path, :class => 'btn btn-default' - else .hidden-xs diff --git a/config/locales/en.yml b/config/locales/en.yml index 591ddd0e8..9b650e582 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -16,8 +16,74 @@ en: home: blurb: - intro: "is a community of food gardeners. We're building an open source platform to help you learn about growing food, track what you plant and harvest, and swap seeds and produce with other gardeners near you." + intro: "%{site_name} is a community of food gardeners. We're building an open source platform to help you learn about growing food, track what you plant and harvest, and swap seeds and produce with other gardeners near you." perks: "Join now for your free garden journal, seed sharing, forums, and more." sign_up: "Sign up" - sign_in: "Sign in" - already: "Already have an account?" \ No newline at end of file + already_html: "Or %{sign_in} if you already have an account" + sign_in_linktext: "sign in" + + crops: + our_crops: "Some of our crops" + recently_planted: "Recently planted" + recently_added: "Recently added crops" + view_all: "View all crops" + + discuss: + discussion: "Discussion" + forums: "Forums" + view_all: "View all posts" + + keep_in_touch: + keep_in_touch: "Keep in touch" + twitter_html: "Follow %{link} on Twitter" + twitter_linktext: "@growstufforg" + blog_html: "Subscribe to the %{link}" + blog_linktext: "Growstuff Blog" + newsletter_html: "Subscribe to our %{link}" + newsletter_linktext: "email newsletter" + + members: + title: "Some of our members" + view_all: "View all members" + + open: + open_source_title: "Open Source" + open_source_body_html: "is open source software, which means that we share this website's code for free with our community and the world. We believe that openness, sustainability, and social good go hand in hand. You can read more about %{why} or check out our code on %{github}." + why_linktext: "why Growstuff is open source" + github_linktext: "Github" + open_data_title: "Open Data and APIs" + open_data_body_html: "We're building a database of crops, planting advice, seed sources, and other information that anyone can use for free, under a %{creative_commons_link}. You can use this data for research, to build apps, or for any purpose at all. Read more about our %{wiki_link} and %{api_docs_link}." + creative_commons_linktext: "Creative Commons license" + wiki_linktext: "open data" + api_docs_linktext: "API documentation" + get_involved_title: "Get Involved" + get_involved_body_html: "We believe in collaboration, and work closely with our members and the wider food-growing community. Our team includes volunteers from all walks of life and all skill levels. To get involved, visit %{talk_link} or find more information on the %{wiki_link}." + talk_linktext: "Growstuff Talk" + wiki_linktext: "Growstuff Wiki" + + seeds: + title: "Seeds available to trade" + owner: "Owner" + crop: "Crop" + description: "Description" + trade_to: "Will trade to" + from: "From location" + unspecified: "unspecified" + details: "Details" + view_all: "View all seeds" + + stats: + message_html: "So far, %{member} have planted %{number_crops} %{number_plantings} in %{number_gardens}." + member_linktext: "%{count} members" + number_crops_linktext: "%{count} crops" + number_plantings_linktext: "%{count} times" + number_gardens_linktext: "%{count} gardens" + + index: + welcome: "Welcome to %{site_name}, %{member_name}" + plant: "Plant" + harvest: "Harvest" + add_seeds: "Add seeds" + post: "Post" + edit_profile: "Edit profile" + From 659a68cf3971b6894f7f557d7f00921722c98a6a Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sat, 20 Sep 2014 21:49:30 +1000 Subject: [PATCH 177/288] remove missing translations spec in favor of running dedicated task --- spec/i18n_spec.rb | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 spec/i18n_spec.rb diff --git a/spec/i18n_spec.rb b/spec/i18n_spec.rb deleted file mode 100644 index 0475c9fcc..000000000 --- a/spec/i18n_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'spec_helper' -require 'i18n/tasks' - -describe 'I18n' do - let(:i18n) { I18n::Tasks::BaseTask.new } - let(:missing_keys) { i18n.missing_keys } - let(:unused_keys) { i18n.unused_keys } - - it 'does not have missing keys' do - expect(missing_keys).to be_empty, - "Missing #{missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them" - end - - it 'does not have unused keys' do - expect(i18n.unused_keys).to be_empty, - "#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them" - end -end From e3a6742574dde7b905356203a1c23ef7623f0fae Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sat, 20 Sep 2014 21:56:14 +1000 Subject: [PATCH 178/288] add sample japanese locale file to test changing languages --- config/locales/ja.yml | 74 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/config/locales/ja.yml b/config/locales/ja.yml index c213277b9..6c7d63b8f 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -1,2 +1,74 @@ ja: - blurb: "はガーデナーのコミュニティーです。" + home: + blurb: + intro: "%{site_name}はガーデナーのコミュニティです。" + perks: "翻訳中" + sign_up: "翻訳中" + already_html: "翻訳中" + sign_in_linktext: "翻訳中" + + crops: + our_crops: "翻訳中" + recently_planted: "翻訳中" + recently_added: "翻訳中" + view_all: "翻訳中" + + discuss: + discussion: "翻訳中" + forums: "翻訳中" + view_all: "翻訳中" + + keep_in_touch: + keep_in_touch: "翻訳中" + twitter_html: "翻訳中" + twitter_linktext: "翻訳中" + blog_html: "翻訳中" + blog_linktext: "翻訳中" + newsletter_html: "翻訳中" + newsletter_linktext: "翻訳中" + + members: + title: "翻訳中" + view_all: "翻訳中" + + open: + open_source_title: "翻訳中" + open_source_body_html: "翻訳中" + why_linktext: "翻訳中" + github_linktext: "翻訳中" + open_data_title: "翻訳中" + open_data_body_html: "翻訳中" + creative_commons_linktext: "翻訳中" + wiki_linktext: "翻訳中" + api_docs_linktext: "翻訳中" + get_involved_title: "翻訳中" + get_involved_body_html: "翻訳中" + talk_linktext: "翻訳中" + wiki_linktext: "翻訳中" + + seeds: + title: "翻訳中" + owner: "翻訳中" + crop: "翻訳中" + description: "翻訳中" + trade_to: "翻訳中" + from: "翻訳中" + unspecified: "翻訳中" + details: "翻訳中" + view_all: "翻訳中" + + stats: + message_html: "翻訳中" + member_linktext: "翻訳中" + number_crops_linktext: "翻訳中" + number_plantings_linktext: "翻訳中" + number_gardens_linktext: "翻訳中" + + index: + welcome: "翻訳中" + plant: "翻訳中" + harvest: "翻訳中" + add_seeds: "翻訳中" + post: "翻訳中" + edit_profile: "翻訳中" + From 93cd067fff99086cfb7d4d99ce9b3f67e2fa770a Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sat, 20 Sep 2014 21:59:05 +1000 Subject: [PATCH 179/288] add two contributors --- CONTRIBUTORS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 5bd5cc601..8473e9f2a 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -45,3 +45,5 @@ submit the change with your pull request. - Marlena Compton / [Marlena](https://github.com/marlena) - Elizabeth A. Kari / [catfriend](https://github.com/catfriend) - Cheri Allen / [cherimarie](https://github.com/cherimarie) +- Shiho Takagi / [oshiho3](https:://github.com/oshiho3) +- Maki Sugita / [macckii](https:://github.com/macckii) From 1acba65a8b01f74292eca50ac038e2701fa6a085 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sun, 21 Sep 2014 10:22:46 +1000 Subject: [PATCH 180/288] clean up haml a bit --- app/views/home/_open.html.haml | 11 ++++++++--- app/views/home/_stats.html.haml | 5 ++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/views/home/_open.html.haml b/app/views/home/_open.html.haml index 1eeb2d847..f53c32675 100644 --- a/app/views/home/_open.html.haml +++ b/app/views/home/_open.html.haml @@ -1,20 +1,25 @@ %h2= t('.open_source_title') %p - #{ENV['GROWSTUFF_SITE_NAME']} #{t('.open_source_body_html', why: link_to(t('.why_linktext'), "http://blog.growstuff.org/2013/02/20/why-growstuff-is-open-source/"), github: link_to(t('.github_linktext'), 'http://github.com/Growstuff/growstuff'))} + = ENV['GROWSTUFF_SITE_NAME'] + = t('.open_source_body_html', why: link_to(t('.why_linktext'), 'http://blog.growstuff.org/2013/02/20/why-growstuff-is-open-source/'), + github: link_to(t('.github_linktext'), 'http://github.com/Growstuff/growstuff') ) %h2= t('.open_data_title') %p - = t('.open_data_body_html', creative_commons_link: link_to(t('.creative_commons_linktext'), 'http://creativecommons.org/licenses/by-sa/3.0/'), wiki_link: link_to(t('.wiki_linktext'), 'http://wiki.growstuff.org/index.php/Open_data'), api_docs_link: link_to(t('.api_docs_linktext'), 'http://wiki.growstuff.org/index.php/API')) + = t('.open_data_body_html', creative_commons_link: link_to(t('.creative_commons_linktext'), 'http://creativecommons.org/licenses/by-sa/3.0/'), + wiki_link: link_to(t('.wiki_linktext'), 'http://wiki.growstuff.org/index.php/Open_data'), + api_docs_link: link_to(t('.api_docs_linktext'), 'http://wiki.growstuff.org/index.php/API')) %h2= t('.get_involved_title') %p - = t('.get_involved_body_html', talk_link: link_to(t('.talk_linktext'), 'http://talk.growstuff.org/'), wiki_link: link_to(t('.wiki_linktext'), 'http://wiki.growstuff.org/')) + = t('.get_involved_body_html', talk_link: link_to(t('.talk_linktext'), 'http://talk.growstuff.org/'), + wiki_link: link_to(t('.wiki_linktext'), 'http://wiki.growstuff.org/')) %h2 Support Growstuff diff --git a/app/views/home/_stats.html.haml b/app/views/home/_stats.html.haml index 78ab2c0bc..e13f74719 100644 --- a/app/views/home/_stats.html.haml +++ b/app/views/home/_stats.html.haml @@ -1,5 +1,8 @@ - cache("homepage_stats") do %p.stats - = t('.message_html', member: link_to(t('.member_linktext', count: Member.confirmed.count.to_i), members_path), number_crops: link_to(t('.number_crops_linktext', count: Crop.count.to_i), crops_path), number_plantings: link_to(t('.number_plantings_linktext', count: Planting.count.to_i), plantings_path), number_gardens: link_to(t('.number_gardens_linktext', count: Garden.count.to_i), gardens_path)) + = t('.message_html', { member: link_to(t('.member_linktext', count: Member.confirmed.count.to_i), members_path), + number_crops: link_to(t('.number_crops_linktext', count: Crop.count.to_i), crops_path), + number_plantings: link_to(t('.number_plantings_linktext', count: Planting.count.to_i), plantings_path), + number_gardens: link_to(t('.number_gardens_linktext', count: Garden.count.to_i), gardens_path) }) From d4622540219da913ab90fd95fd78f22c3d36fc35 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sun, 21 Sep 2014 10:26:30 +1000 Subject: [PATCH 181/288] add missing translation --- app/views/home/_open.html.haml | 10 +++------- config/locales/en.yml | 4 ++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/views/home/_open.html.haml b/app/views/home/_open.html.haml index f53c32675..b14bfa83b 100644 --- a/app/views/home/_open.html.haml +++ b/app/views/home/_open.html.haml @@ -22,12 +22,8 @@ wiki_link: link_to(t('.wiki_linktext'), 'http://wiki.growstuff.org/')) -%h2 Support Growstuff +%h2= t('.support_title') %p - Growstuff is independent, - =succeed "," do - =link_to 'ad-free', 'http://wiki.growstuff.org/index.php/Why_no_ads%3F' - and we have no outside investment. You can support our work by - =succeed "." do - =link_to 'buying a paid account', shop_path + = t('.support_body_html', ad_free: link_to(t('.ad_free_linktext'), 'http://wiki.growstuff.org/index.php/Why_no_ads%3F'), + buy_account: link_to(t('.buy_account_linktext'), shop_path)) diff --git a/config/locales/en.yml b/config/locales/en.yml index 9b650e582..b32bd931f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -60,6 +60,10 @@ en: get_involved_body_html: "We believe in collaboration, and work closely with our members and the wider food-growing community. Our team includes volunteers from all walks of life and all skill levels. To get involved, visit %{talk_link} or find more information on the %{wiki_link}." talk_linktext: "Growstuff Talk" wiki_linktext: "Growstuff Wiki" + support_title: "Support Growstuff" + support_body_html: "Growstuff is independent, %{ad_free} and we have no outside investment. You can support our work by %{buy_account}." + ad_free_linktext: "ad-free" + buy_account_linktext: "buying a paid account" seeds: title: "Seeds available to trade" From 5e48c4392ab80040d53cf941cf7b5f7568fef2a7 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Wed, 24 Sep 2014 18:29:59 +1000 Subject: [PATCH 182/288] move site name into localization --- app/views/home/_open.html.haml | 4 ++-- config/locales/en.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/home/_open.html.haml b/app/views/home/_open.html.haml index b14bfa83b..fa7c00f57 100644 --- a/app/views/home/_open.html.haml +++ b/app/views/home/_open.html.haml @@ -1,9 +1,9 @@ %h2= t('.open_source_title') %p - = ENV['GROWSTUFF_SITE_NAME'] = t('.open_source_body_html', why: link_to(t('.why_linktext'), 'http://blog.growstuff.org/2013/02/20/why-growstuff-is-open-source/'), - github: link_to(t('.github_linktext'), 'http://github.com/Growstuff/growstuff') ) + github: link_to(t('.github_linktext'), 'http://github.com/Growstuff/growstuff'), + site_name: ENV['GROWSTUFF_SITE_NAME'] ) %h2= t('.open_data_title') diff --git a/config/locales/en.yml b/config/locales/en.yml index b32bd931f..39bb974ff 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -48,7 +48,7 @@ en: open: open_source_title: "Open Source" - open_source_body_html: "is open source software, which means that we share this website's code for free with our community and the world. We believe that openness, sustainability, and social good go hand in hand. You can read more about %{why} or check out our code on %{github}." + open_source_body_html: "%{site_name} is open source software, which means that we share this website's code for free with our community and the world. We believe that openness, sustainability, and social good go hand in hand. You can read more about %{why} or check out our code on %{github}." why_linktext: "why Growstuff is open source" github_linktext: "Github" open_data_title: "Open Data and APIs" From 984503480f074fcdb9218cd9458efec983845878 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Thu, 25 Sep 2014 07:00:11 +1000 Subject: [PATCH 183/288] remove necessity to add locale to url if current locale is English --- app/controllers/application_controller.rb | 6 +++++- spec/features/crop_wranglers_spec.rb | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 91fc81413..8fbc8ba95 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -40,7 +40,11 @@ class ApplicationController < ActionController::Base # Adds locale query parameter to every path / url helper def default_url_options(options={}) - { locale: I18n.locale } + if I18n.locale == :en + {} + else + { locale: I18n.locale } + end end end diff --git a/spec/features/crop_wranglers_spec.rb b/spec/features/crop_wranglers_spec.rb index 327be7e51..80475bd18 100644 --- a/spec/features/crop_wranglers_spec.rb +++ b/spec/features/crop_wranglers_spec.rb @@ -19,7 +19,7 @@ feature "crop wranglers" do within '.crop_wranglers' do expect(page).to have_content 'Crop Wranglers:' crop_wranglers.each do |crop_wrangler| - page.should have_link crop_wrangler.login_name, :href => member_path(crop_wrangler, {locale: 'en'}) + page.should have_link crop_wrangler.login_name, :href => member_path(crop_wrangler) end end end From 8ab4f885f11db1ec53fb568b728b2abc49b0c79d Mon Sep 17 00:00:00 2001 From: Wendy Smoak Date: Fri, 26 Sep 2014 10:32:06 -0400 Subject: [PATCH 184/288] Renamed gardens.rb to gardens_spec.rb so that these tests will run. --- spec/features/{gardens.rb => gardens_spec.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename spec/features/{gardens.rb => gardens_spec.rb} (100%) diff --git a/spec/features/gardens.rb b/spec/features/gardens_spec.rb similarity index 100% rename from spec/features/gardens.rb rename to spec/features/gardens_spec.rb From 25abcdc9236aea1efaaad8683b0f246faffe91f8 Mon Sep 17 00:00:00 2001 From: Skud Date: Sun, 28 Sep 2014 13:50:55 +1000 Subject: [PATCH 185/288] Upload tomato varieties to crop database --- db/seeds/README.md | 6 +++++ db/seeds/crops-11-tomatoes.csv | 41 ++++++++++++++++++++++++++++++++++ script/deploy-tasks.sh | 7 ++---- 3 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 db/seeds/README.md create mode 100644 db/seeds/crops-11-tomatoes.csv diff --git a/db/seeds/README.md b/db/seeds/README.md new file mode 100644 index 000000000..c3cfd1902 --- /dev/null +++ b/db/seeds/README.md @@ -0,0 +1,6 @@ +The format for these crop seed files is CSV with the following fields: + +Crop name +Scientific name +English Wikipedia URL +Parent crop name diff --git a/db/seeds/crops-11-tomatoes.csv b/db/seeds/crops-11-tomatoes.csv new file mode 100644 index 000000000..eb28aaa79 --- /dev/null +++ b/db/seeds/crops-11-tomatoes.csv @@ -0,0 +1,41 @@ +adoration tomato,,https://en.wikipedia.org/wiki/Adoration_%28Tomato%29,tomato, +alicante tomato,,https://en.wikipedia.org/wiki/Alicante_%28tomato%29,tomato, +Amish paste tomato,,http://en.wikipedia.org/wiki/Amish_Paste,tomato, +azoychka tomato,,https://en.wikipedia.org/wiki/Azoychka%28Tomato%29,tomato, +beefsteak tomato,,https://en.wikipedia.org/wiki/Beefsteak_(tomato),tomato, +better boy tomato,,https://en.wikipedia.org/wiki/Better_Boy,tomato, +big rainbow tomato,,https://en.wikipedia.org/wiki/Big_Rainbow_(Tomato),tomato, +Blaby special tomato,,https://en.wikipedia.org/wiki/Blaby_Special_(Tomato),tomato, +black krim tomato,,https://en.wikipedia.org/wiki/Black_Krim_%28tomato%29,tomato, +brandywine tomato,,https://en.wikipedia.org/wiki/Brandywine_(tomato),tomato, +campari tomato,,https://en.wikipedia.org/wiki/Campari_tomato,tomato, +Cherokee purple tomato,,https://en.wikipedia.org/wiki/Cherokee_purple,tomato, +currant tomato,solanum pimpinellifolium,https://en.wikipedia.org/wiki/Solanum_pimpinellifolium, +early girl tomato,,https://en.wikipedia.org/wiki/Early_Girl,tomato, +Fourth of July tomato,,https://en.wikipedia.org/wiki/Fourth_of_July_(tomato_variety),tomato, +garden peach tomato,,https://en.wikipedia.org/wiki/Garden_peach_tomato,tomato, +green zebra tomato,,https://en.wikipedia.org/wiki/Green_Zebra,tomato, +hillbilly tomato,,http://en.wikipedia.org/wiki/Hillbilly_(tomato),tomato, +jubilee tomato,,http://en.wikipedia.org/wiki/Jubilee_(tomato),tomato, +lillian's yellow tomato,,http://en.wikipedia.org/wiki/Lillian%27s_Yellow_(tomato),tomato, +Matt's wild cherry tomato,,http://en.wikipedia.org/wiki/Matt%27s_Wild_Cherry,tomato, +mortgage lifter tomato,,http://en.wikipedia.org/wiki/Mortgage_Lifter,tomato, +Mr. Stripey tomato,,http://en.wikipedia.org/wiki/Mr._Stripey,tomato, +Roma tomato,,http://en.wikipedia.org/wiki/Roma_tomato,tomato, +San Marzano tomato,,http://en.wikipedia.org/wiki/San_Marzano_tomato,tomato, +Santorini tomato,,http://en.wikipedia.org/wiki/Santorini_(tomato),tomato, +stupice tomato,,http://en.wikipedia.org/wiki/Super_Sweet_100,tomato, +tigerella tomato,,http://en.wikipedia.org/wiki/Tigerella,tomato, +tomaccio tomato,,http://en.wikipedia.org/wiki/Tomaccio_(tomato),tomato, +traveller tomato,,http://en.wikipedia.org/wiki/Traveller_(tomato),tomato +three sisters tomato,,http://en.wikipedia.org/wiki/Three_Sisters_(tomato),tomato, +Hanover tomato,,http://en.wikipedia.org/wiki/Hanover_tomato,tomato, +celebrity tomato,,http://en.wikipedia.org/wiki/Celebrity_(tomato),tomato, +tomberry,,http://en.wikipedia.org/wiki/Tomberry,tomato, +super sweet 100 tomato,,http://en.wikipedia.org/wiki/Super_Sweet_100,tomato, +marglobe tomato,,http://en.wikipedia.org/wiki/Marglobe,tomato, +grape tomato,,http://en.wikipedia.org/wiki/Grape_tomato,tomato, +cherry tomato,,http://en.wikipedia.org/wiki/Cherry_tomato,tomato, +Aunt Ruby's German green tomato,,http://en.wikipedia.org/wiki/Aunt_Ruby%27s_German_Green,tomato, +white queen tomato,,http://en.wikipedia.org/wiki/White_Queen_tomato,tomato, +pear tomato,,http://en.wikipedia.org/wiki/Pear_tomato,tomato, diff --git a/script/deploy-tasks.sh b/script/deploy-tasks.sh index f861d27ed..ac4068b26 100755 --- a/script/deploy-tasks.sh +++ b/script/deploy-tasks.sh @@ -10,8 +10,5 @@ # echo "YYYY-MM-DD - do something or other" # rake growstuff:oneoff:something -echo "2013-07-18 - zero crop plantings_count" -rake growstuff:oneoff:zero_plantings_count - -echo "2014-08-10 - replace ping with pint in db" -rake growstuff:oneoff:ping_to_pint +echo "2014-09-28 - upload tomatoes" +rake growstuff:import_crops file=db/seeds/crops-11-tomatoes.csv From 1b1f1d33e01cfd85d9519b63728697691a547d0c Mon Sep 17 00:00:00 2001 From: Skud Date: Sun, 28 Sep 2014 14:35:32 +1000 Subject: [PATCH 186/288] Add planting reminder checkbox to email settings --- app/views/devise/registrations/_edit_email.html.haml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/views/devise/registrations/_edit_email.html.haml b/app/views/devise/registrations/_edit_email.html.haml index 61df13f91..252263703 100644 --- a/app/views/devise/registrations/_edit_email.html.haml +++ b/app/views/devise/registrations/_edit_email.html.haml @@ -10,12 +10,17 @@ .form-group .col-md-offset-2.col-md-8 = f.check_box :show_email - Show email publicly on your profile page + Show email publicly on your profile page. .form-group .col-md-offset-2.col-md-8 = f.check_box :send_notification_email - Receive emailed copies of Inbox notifications. + Receive emailed copies of Inbox notifications (eg. private messages). + + .form-group + .col-md-offset-2.col-md-8 + = f.check_box :send_planting_reminder + Receive regular reminders to track your planting and harvesting. .form-group .col-md-offset-2.col-md-8 From e011b54cbac18b6744d16bf197c75d60e35f6b97 Mon Sep 17 00:00:00 2001 From: Skud Date: Sun, 28 Sep 2014 14:35:54 +1000 Subject: [PATCH 187/288] Nicer phrasing for the no-plantings use case --- .../notifier/planting_reminder.html.haml | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/app/views/notifier/planting_reminder.html.haml b/app/views/notifier/planting_reminder.html.haml index 98ca52fa9..eb7489184 100644 --- a/app/views/notifier/planting_reminder.html.haml +++ b/app/views/notifier/planting_reminder.html.haml @@ -4,14 +4,14 @@ %h2 What's new in your garden? %p - Have you planted anything recently? - if @member.plantings.size == 0 %p #{ENV['GROWSTUFF_SITE_NAME']} lets you track what food you're growing - in your garden. + in your garden and see what other people near you are planting too. %p - = link_to "Get started now.", new_planting_url + = link_to "Get started now", new_planting_url + by planting your first crop. - else %p @@ -26,18 +26,20 @@ ago. %p - Planted anything new? - = link_to "Track your plantings here.", new_planting_url + = link_to "Plant something new", new_planting_url + to keep your garden records up to date. %h2 Your recent harvests - if @member.harvests.size == 0 %p - With #{ENV['GROWSTUFF_SITE_NAME']} you can keep track of what you - harvest from your garden. + #{ENV['GROWSTUFF_SITE_NAME']} helps you keep track of what you + harvest from your garden. Record what food you've grown + and see what other people near you are harvesting, too. %p - = link_to "Get started now.", new_harvest_url + = link_to "Get started now", new_harvest_url + by tracking your first harvest. - else According to our records, the last few things you harvested were: @@ -54,11 +56,16 @@ Harvested anything else lately? = link_to "Track your harvests here.", new_harvest_url -%p -Don't want to get these emails any more? -= link_to "Turn off these notifications", edit_member_registration_url +%h2 + See you soon on #{ENV['GROWSTUFF_SITE_NAME']}! %p The #{site_name} team. %br/ =link_to root_url, root_url + +%hr/ +%p + Don't want to get these emails any more? + = link_to "Turn off these notifications", edit_member_registration_url + From 529c98f5dc6120225dde51ebf47f66b8da530bc9 Mon Sep 17 00:00:00 2001 From: Skud Date: Sun, 28 Sep 2014 15:28:53 +1000 Subject: [PATCH 188/288] Move actual sending of planting reminder to rake task --- app/controllers/members_controller.rb | 3 --- app/models/ability.rb | 1 - config/routes.rb | 1 - lib/tasks/growstuff.rake | 20 ++++++++++++++++++++ 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index f1e00a37a..fdc297740 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -32,8 +32,5 @@ class MembersController < ApplicationController end end - def send_planting_reminder - Notifier.planting_reminder(current_member).deliver! - end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 8e4e30fe6..e33f4a022 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -23,7 +23,6 @@ class Ability # managing your own user settings can :update, Member, :id => member.id - can :send_planting_reminder, Member # can read/delete notifications that were sent to them can :read, Notification, :recipient_id => member.id diff --git a/config/routes.rb b/config/routes.rb index 4bc93c2fb..00e31fec8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,7 +2,6 @@ Growstuff::Application.routes.draw do resources :plant_parts - get '/members/send_planting_reminder' => 'members#send_planting_reminder', :as => 'send_planting_reminder' devise_for :members, :controllers => { :registrations => "registrations", :passwords => "passwords" } resources :members diff --git a/lib/tasks/growstuff.rake b/lib/tasks/growstuff.rake index 34739217b..8ae3b8659 100644 --- a/lib/tasks/growstuff.rake +++ b/lib/tasks/growstuff.rake @@ -34,6 +34,26 @@ namespace :growstuff do end + desc "Send planting reminder email" + # usage: rake growstuff:send_planting_reminder + + task :send_planting_reminder => :environment do + # Heroku scheduler only lets us run things daily, so this checks + # whether it's the right day to actually do the deed. + # Note that Heroku scheduler runs on UTC. + # We'd like to send on Wednesday mornings, US time, which will be + # very early Wednesday morning UTC. + send_on_day = 3 # wednesday + every_n_weeks = 2 # send fortnightly + + if Date.today.cwday == send_on_day and Date.today.cw_week % every_n_weeks == 0 + puts "We're going to send email because it's the right day" + Member.find_each do |m| + Notifier.planting_reminder(m).deliver! + end + end + end + desc "Depopulate Null Island" # this fixes up anyone who has erroneously wound up with a 0,0 lat/long task :depopulate_null_island => :environment do From d8b75eaa66d0f29e5c5d7f56bab8d47d5b8d8e94 Mon Sep 17 00:00:00 2001 From: Skud Date: Sun, 28 Sep 2014 15:55:55 +1000 Subject: [PATCH 189/288] Fixed up db migration and tests for planting reminders --- ...0140928085713_add_send_planting_reminder_to_member.rb} | 0 db/schema.rb | 3 ++- spec/features/planting_reminder_spec.rb | 8 +------- 3 files changed, 3 insertions(+), 8 deletions(-) rename db/migrate/{20140720085713_add_send_planting_reminder_to_member.rb => 20140928085713_add_send_planting_reminder_to_member.rb} (100%) diff --git a/db/migrate/20140720085713_add_send_planting_reminder_to_member.rb b/db/migrate/20140928085713_add_send_planting_reminder_to_member.rb similarity index 100% rename from db/migrate/20140720085713_add_send_planting_reminder_to_member.rb rename to db/migrate/20140928085713_add_send_planting_reminder_to_member.rb diff --git a/db/schema.rb b/db/schema.rb index 29271f5e6..839017c46 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20140829230600) do +ActiveRecord::Schema.define(:version => 20140928085713) do create_table "account_types", :force => true do |t| t.string "name", :null => false @@ -139,6 +139,7 @@ ActiveRecord::Schema.define(:version => 20140829230600) do t.text "bio" t.integer "plantings_count" t.boolean "newsletter" + t.boolean "send_planting_reminder", :default => true end add_index "members", ["confirmation_token"], :name => "index_users_on_confirmation_token", :unique => true diff --git a/spec/features/planting_reminder_spec.rb b/spec/features/planting_reminder_spec.rb index 763331442..9979ddd99 100644 --- a/spec/features/planting_reminder_spec.rb +++ b/spec/features/planting_reminder_spec.rb @@ -3,18 +3,12 @@ require 'spec_helper' feature "planting reminder" do before :each do @member = FactoryGirl.create(:member) - visit root_path - click_link 'Sign in' - page.should have_content "Sign in" - fill_in 'Login', :with => @member.login_name - fill_in 'Password', :with => @member.password - click_button 'Sign in' end scenario "sends email" do expect { # stub for while we're working on this. remove! - visit send_planting_reminder_path + Notifier.planting_reminder(@member).deliver! }.to change { ActionMailer::Base.deliveries.count }.by(1) end end From 05400a081fa008a1e6c62c01db9d2adeb36ebc89 Mon Sep 17 00:00:00 2001 From: Skud Date: Sun, 28 Sep 2014 16:02:02 +1000 Subject: [PATCH 190/288] tiny bit of whitespace cleanup --- app/controllers/members_controller.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index fdc297740..ab3de0001 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -32,5 +32,4 @@ class MembersController < ApplicationController end end - end From baeb5d352586f83c3ddd18c425dbe5fe3e75591d Mon Sep 17 00:00:00 2001 From: Skud Date: Sun, 28 Sep 2014 16:03:58 +1000 Subject: [PATCH 191/288] Remove old planting reminder sending stuff --- app/views/members/send_planting_reminder.html.haml | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 app/views/members/send_planting_reminder.html.haml diff --git a/app/views/members/send_planting_reminder.html.haml b/app/views/members/send_planting_reminder.html.haml deleted file mode 100644 index 1be4469e7..000000000 --- a/app/views/members/send_planting_reminder.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -- content_for :title, "Planting reminder email (beta)" - -- if current_member.send_planting_reminder - %p - We just sent you a planting reminder email. -- else - %p - Your profile settings say not to send you this email, so we haven't. - -%p - = link_to "Back to your profile", edit_member_registration_path - From d10a62d336405c88418247ec56f960becd346764 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sun, 28 Sep 2014 21:32:15 +1000 Subject: [PATCH 192/288] set locale from subdomain --- app/controllers/application_controller.rb | 21 +++++++++++++-------- config/locales/ja.yml | 4 ++++ spec/features/signup_spec.rb | 13 +++++++++++++ 3 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 spec/features/signup_spec.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 8fbc8ba95..56db2c730 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -35,16 +35,21 @@ class ApplicationController < ActionController::Base end def set_locale - I18n.locale = params[:locale] || I18n.default_locale + I18n.locale = extract_locale_tld || I18n.default_locale + end + + def extract_locale_tld + parsed_locale = request.subdomains.first + I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil end # Adds locale query parameter to every path / url helper - def default_url_options(options={}) - if I18n.locale == :en - {} - else - { locale: I18n.locale } - end - end + # def default_url_options(options={}) + # if I18n.locale == :en + # {} + # else + # { locale: I18n.locale } + # end + # end end diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 6c7d63b8f..6ca6eaf88 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -45,6 +45,10 @@ ja: get_involved_body_html: "翻訳中" talk_linktext: "翻訳中" wiki_linktext: "翻訳中" + support_title: "翻訳中" + support_body_html: "翻訳中" + ad_free_linktext: "翻訳中" + buy_account_linktext: "翻訳中" seeds: title: "翻訳中" diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb new file mode 100644 index 000000000..d3be7e17f --- /dev/null +++ b/spec/features/signup_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +feature "signup" do + + scenario "signup" do + visit root_path + first('.signup a').click + + expect(current_path).to eq(new_member_registration_path) + end + + +end From f9dc17c5875633594079fd6d4bea64521e65463f Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sun, 28 Sep 2014 21:34:26 +1000 Subject: [PATCH 193/288] no longer raise exception on missing g translation in dev / test envs --- config/initializers/locale.rb | 5 ----- config/locales/ja.yml | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/config/initializers/locale.rb b/config/initializers/locale.rb index 21c2733cb..fb251b800 100644 --- a/config/initializers/locale.rb +++ b/config/initializers/locale.rb @@ -1,9 +1,4 @@ I18n.load_path += Dir[Rails.root.join('config', 'locales', '*.{rb,yml}')] I18n.default_locale = :en -if Rails.env.development? || Rails.env.test? - I18n.exception_handler = lambda do |exception, locale, key, options| - raise "Missing translation: #{key}" - end -end \ No newline at end of file diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 6ca6eaf88..1f2fe3247 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -48,7 +48,7 @@ ja: support_title: "翻訳中" support_body_html: "翻訳中" ad_free_linktext: "翻訳中" - buy_account_linktext: "翻訳中" + seeds: title: "翻訳中" From a7da4b9ce41ac5e1ccec0723d456085778e8d0f5 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sun, 28 Sep 2014 21:38:28 +1000 Subject: [PATCH 194/288] no longer necessary to pass locale quuery param in every url --- app/controllers/application_controller.rb | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 56db2c730..1b27f01fc 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -43,13 +43,4 @@ class ApplicationController < ActionController::Base I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil end - # Adds locale query parameter to every path / url helper - # def default_url_options(options={}) - # if I18n.locale == :en - # {} - # else - # { locale: I18n.locale } - # end - # end - end From 87b6780220071860be7eef24f677fe971f32bfa0 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sun, 28 Sep 2014 21:57:05 +1000 Subject: [PATCH 195/288] stop generating helpers, constroller specs, view specs, and javascripts --- app/assets/javascripts/admin.js.coffee | 3 --- app/assets/javascripts/admin/orders.js.coffee | 3 --- app/assets/javascripts/comments.js.coffee | 3 --- app/assets/javascripts/forums.js.coffee | 3 --- app/assets/javascripts/gardens.js.coffee | 3 --- app/assets/javascripts/harvests.js.coffee | 3 --- app/assets/javascripts/home.js.coffee | 3 --- app/assets/javascripts/notifications.js.coffee | 3 --- app/assets/javascripts/order_items.js.coffee | 3 --- app/assets/javascripts/orders.js.coffee | 3 --- app/assets/javascripts/photos.js.coffee | 3 --- app/assets/javascripts/plant_parts.js.coffee | 3 --- app/assets/javascripts/products.js.coffee | 3 --- app/assets/javascripts/roles.js.coffee | 3 --- app/assets/javascripts/scientific_names.js.coffee | 3 --- app/assets/javascripts/updates.js.coffee | 3 --- app/helpers/plant_parts_helper.rb | 2 -- app/helpers/seeds_helper.rb | 2 -- config/application.rb | 8 ++++++-- 19 files changed, 6 insertions(+), 54 deletions(-) delete mode 100644 app/assets/javascripts/admin.js.coffee delete mode 100644 app/assets/javascripts/admin/orders.js.coffee delete mode 100644 app/assets/javascripts/comments.js.coffee delete mode 100644 app/assets/javascripts/forums.js.coffee delete mode 100644 app/assets/javascripts/gardens.js.coffee delete mode 100644 app/assets/javascripts/harvests.js.coffee delete mode 100644 app/assets/javascripts/home.js.coffee delete mode 100644 app/assets/javascripts/notifications.js.coffee delete mode 100644 app/assets/javascripts/order_items.js.coffee delete mode 100644 app/assets/javascripts/orders.js.coffee delete mode 100644 app/assets/javascripts/photos.js.coffee delete mode 100644 app/assets/javascripts/plant_parts.js.coffee delete mode 100644 app/assets/javascripts/products.js.coffee delete mode 100644 app/assets/javascripts/roles.js.coffee delete mode 100644 app/assets/javascripts/scientific_names.js.coffee delete mode 100644 app/assets/javascripts/updates.js.coffee delete mode 100644 app/helpers/plant_parts_helper.rb delete mode 100644 app/helpers/seeds_helper.rb diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/admin.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/javascripts/admin/orders.js.coffee b/app/assets/javascripts/admin/orders.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/admin/orders.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/javascripts/comments.js.coffee b/app/assets/javascripts/comments.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/comments.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/javascripts/forums.js.coffee b/app/assets/javascripts/forums.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/forums.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/javascripts/gardens.js.coffee b/app/assets/javascripts/gardens.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/gardens.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/javascripts/harvests.js.coffee b/app/assets/javascripts/harvests.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/harvests.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/javascripts/home.js.coffee b/app/assets/javascripts/home.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/home.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/javascripts/notifications.js.coffee b/app/assets/javascripts/notifications.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/notifications.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/javascripts/order_items.js.coffee b/app/assets/javascripts/order_items.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/order_items.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/javascripts/orders.js.coffee b/app/assets/javascripts/orders.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/orders.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/javascripts/photos.js.coffee b/app/assets/javascripts/photos.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/photos.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/javascripts/plant_parts.js.coffee b/app/assets/javascripts/plant_parts.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/plant_parts.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/javascripts/products.js.coffee b/app/assets/javascripts/products.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/products.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/javascripts/roles.js.coffee b/app/assets/javascripts/roles.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/roles.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/javascripts/scientific_names.js.coffee b/app/assets/javascripts/scientific_names.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/scientific_names.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/javascripts/updates.js.coffee b/app/assets/javascripts/updates.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/updates.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/helpers/plant_parts_helper.rb b/app/helpers/plant_parts_helper.rb deleted file mode 100644 index b97b124e8..000000000 --- a/app/helpers/plant_parts_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module PlantPartsHelper -end diff --git a/app/helpers/seeds_helper.rb b/app/helpers/seeds_helper.rb deleted file mode 100644 index 46c697f02..000000000 --- a/app/helpers/seeds_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module SeedsHelper -end diff --git a/config/application.rb b/config/application.rb index f1a5e9dd6..6bbe1fe7d 100644 --- a/config/application.rb +++ b/config/application.rb @@ -67,8 +67,12 @@ module Growstuff config.assets.initialize_on_precompile = true config.generators do |g| - g.template_engine :haml - g.stylesheets false + g.template_engine :haml + g.view_specs false + g.controller_specs false + g.helper false + g.stylesheets false + g.javascripts false end config.action_mailer.delivery_method = :sendmail From aebd9e1d4e8a95a65c33ea0f03de5f701fddae85 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sun, 28 Sep 2014 22:03:50 +1000 Subject: [PATCH 196/288] remove a file that accidentally found its way in from another unrelated branch --- spec/features/signup_spec.rb | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 spec/features/signup_spec.rb diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb deleted file mode 100644 index d3be7e17f..000000000 --- a/spec/features/signup_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'spec_helper' - -feature "signup" do - - scenario "signup" do - visit root_path - first('.signup a').click - - expect(current_path).to eq(new_member_registration_path) - end - - -end From 65c46c334b097816cdc92348a8a390680c4e5a7f Mon Sep 17 00:00:00 2001 From: Skud Date: Mon, 29 Sep 2014 09:18:28 +1000 Subject: [PATCH 197/288] Delete unused photos Make sure the models are setup so that if a photo is not used for anything, it's removed from the system. Also wrote a rake task (which should be run on deploy) to remove older unused photos. --- app/models/harvest.rb | 10 +++++- app/models/photo.rb | 9 ++++- app/models/planting.rb | 10 +++++- db/schema.rb | 4 ++- lib/tasks/growstuff.rake | 9 ++++- spec/models/photo_spec.rb | 76 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 113 insertions(+), 5 deletions(-) diff --git a/app/models/harvest.rb b/app/models/harvest.rb index 8cc54e043..68afb2ee5 100644 --- a/app/models/harvest.rb +++ b/app/models/harvest.rb @@ -10,7 +10,15 @@ class Harvest < ActiveRecord::Base belongs_to :plant_part has_and_belongs_to_many :photos - before_destroy {|harvest| harvest.photos.clear} + + before_destroy do |harvest| + photolist = harvest.photos.to_a # save a temp copy of the photo list + harvest.photos.clear # clear relationship b/w harvest and photo + + photolist.each do |photo| + photo.destroy_if_unused + end + end default_scope order('created_at DESC') diff --git a/app/models/photo.rb b/app/models/photo.rb index 4f0a0160f..32679eeed 100644 --- a/app/models/photo.rb +++ b/app/models/photo.rb @@ -5,13 +5,20 @@ class Photo < ActiveRecord::Base has_and_belongs_to_many :plantings has_and_belongs_to_many :harvests - before_destroy do |photo| + before_destroy do |photo| photo.plantings.clear photo.harvests.clear end default_scope order("created_at desc") + # remove photos that aren't used by anything + def destroy_if_unused + unless plantings.size > 0 and harvests.size > 0 + self.destroy + end + end + # This is split into a side-effect free method and a side-effecting method # for easier stubbing and testing. def flickr_metadata diff --git a/app/models/planting.rb b/app/models/planting.rb index 270a827d2..8d0a5227a 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -11,7 +11,15 @@ class Planting < ActiveRecord::Base belongs_to :crop, :counter_cache => true has_and_belongs_to_many :photos - before_destroy {|planting| planting.photos.clear} + + before_destroy do |planting| + photolist = planting.photos.to_a # save a temp copy of the photo list + planting.photos.clear # clear relationship b/w planting and photo + + photolist.each do |photo| + photo.destroy_if_unused + end + end default_scope order("created_at desc") scope :finished, where(:finished => true) diff --git a/db/schema.rb b/db/schema.rb index 5ad7d3192..3790e9340 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20140909001730) do +ActiveRecord::Schema.define(:version => 20140928085713) do create_table "account_types", :force => true do |t| t.string "name", :null => false @@ -144,6 +144,7 @@ ActiveRecord::Schema.define(:version => 20140909001730) do t.text "bio" t.integer "plantings_count" t.boolean "newsletter" + t.boolean "send_planting_reminder", :default => true end add_index "members", ["confirmation_token"], :name => "index_users_on_confirmation_token", :unique => true @@ -243,6 +244,7 @@ ActiveRecord::Schema.define(:version => 20140909001730) do t.datetime "updated_at", :null => false 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" diff --git a/lib/tasks/growstuff.rake b/lib/tasks/growstuff.rake index 34739217b..0ae08a699 100644 --- a/lib/tasks/growstuff.rake +++ b/lib/tasks/growstuff.rake @@ -249,13 +249,20 @@ namespace :growstuff do desc "August 2014: fix ping to pint in database" task :ping_to_pint => :environment do Harvest.find_each do |h| - if h.unit == "ping" + if h.unit == "ping" h.unit = "pint" h.save end end end + desc "October 2014: remove unused photos" + task :remove_unused_photos => :environment do + Photo.find_each do |p| + p.destroy_if_unused + end + end + end # end oneoff section end diff --git a/spec/models/photo_spec.rb b/spec/models/photo_spec.rb index 8ac067ab7..2a0b1d5ff 100644 --- a/spec/models/photo_spec.rb +++ b/spec/models/photo_spec.rb @@ -2,6 +2,82 @@ require 'spec_helper' describe Photo do + describe 'add/delete functionality' do + let(:photo) { FactoryGirl.create(:photo) } + let(:planting) { FactoryGirl.create(:planting) } + let(:harvest) { FactoryGirl.create(:harvest) } + + context "adds photos" do + it 'to a planting' do + planting.photos << photo + expect(planting.photos.count).to eq 1 + expect(planting.photos.first).to eq photo + end + + it 'to a harvest' do + harvest.photos << photo + expect(harvest.photos.count).to eq 1 + expect(harvest.photos.first).to eq photo + end + end + + context "removing photos" do + it 'from a planting' do + planting.photos << photo + photo.destroy + expect(planting.photos.count).to eq 0 + end + + it 'from a harvest' do + harvest.photos << photo + photo.destroy + expect(harvest.photos.count).to eq 0 + end + + it "automatically if unused" do + photo.destroy_if_unused + expect(lambda { photo.reload }).to raise_error ActiveRecord::RecordNotFound + end + + it 'they are no longer used by plantings' do + planting.photos << photo + planting.destroy # photo is now no longer used by anything + expect(lambda { photo.reload }).to raise_error ActiveRecord::RecordNotFound + end + + it 'they are no longer used by harvests' do + harvest.photos << photo + harvest.destroy # photo is now no longer used by anything + expect(lambda { photo.reload }).to raise_error ActiveRecord::RecordNotFound + end + + it 'they are no longer used by anything' do + planting.photos << photo + harvest.photos << photo + expect(photo.plantings.size).to eq 1 + expect(photo.harvests.size).to eq 1 + + planting.destroy # photo is still used by harvest + photo.reload + expect(photo).to be_an_instance_of Photo + expect(photo.plantings.size).to eq 0 + expect(photo.harvests.size).to eq 1 + + harvest.destroy # photo is now no longer used by anything + expect(lambda { photo.reload }).to raise_error ActiveRecord::RecordNotFound + end + + it 'does not occur when a photo is still in use' do + planting.photos << photo + harvest.photos << photo + planting.destroy # photo is still used by the harvest + expect(photo).to be_an_instance_of Photo + end + + end # removing photos + + end # add/delete functionality + describe 'flickr_metadata' do # Any further tests led to us MOCKING ALL THE THINGS # which was epistemologically unsatisfactory. From e2571e92615bab1da1b38b816e47ab154018dd0c Mon Sep 17 00:00:00 2001 From: Skud Date: Tue, 30 Sep 2014 22:37:19 +1000 Subject: [PATCH 198/288] Replaced ENV vars with site_name --- app/views/notifier/planting_reminder.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/notifier/planting_reminder.html.haml b/app/views/notifier/planting_reminder.html.haml index eb7489184..7edf063b5 100644 --- a/app/views/notifier/planting_reminder.html.haml +++ b/app/views/notifier/planting_reminder.html.haml @@ -7,7 +7,7 @@ - if @member.plantings.size == 0 %p - #{ENV['GROWSTUFF_SITE_NAME']} lets you track what food you're growing + #{site_name} lets you track what food you're growing in your garden and see what other people near you are planting too. %p = link_to "Get started now", new_planting_url @@ -33,7 +33,7 @@ - if @member.harvests.size == 0 %p - #{ENV['GROWSTUFF_SITE_NAME']} helps you keep track of what you + #{site_name} helps you keep track of what you harvest from your garden. Record what food you've grown and see what other people near you are harvesting, too. @@ -57,7 +57,7 @@ = link_to "Track your harvests here.", new_harvest_url %h2 - See you soon on #{ENV['GROWSTUFF_SITE_NAME']}! + See you soon on #{site_name}! %p The #{site_name} team. From 98da785d8a52e4272fb405558f759ce34235a894 Mon Sep 17 00:00:00 2001 From: Skud Date: Tue, 30 Sep 2014 22:37:58 +1000 Subject: [PATCH 199/288] Removed spurious debugging puts --- lib/tasks/growstuff.rake | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/tasks/growstuff.rake b/lib/tasks/growstuff.rake index 8ae3b8659..6009e33c6 100644 --- a/lib/tasks/growstuff.rake +++ b/lib/tasks/growstuff.rake @@ -47,7 +47,6 @@ namespace :growstuff do every_n_weeks = 2 # send fortnightly if Date.today.cwday == send_on_day and Date.today.cw_week % every_n_weeks == 0 - puts "We're going to send email because it's the right day" Member.find_each do |m| Notifier.planting_reminder(m).deliver! end From 97699e5e6aff32803c7e5aea6243c2e37a4c3984 Mon Sep 17 00:00:00 2001 From: Skud Date: Tue, 30 Sep 2014 22:38:24 +1000 Subject: [PATCH 200/288] Removed feature test that doesn't really test anything featurelike --- spec/features/planting_reminder_spec.rb | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 spec/features/planting_reminder_spec.rb diff --git a/spec/features/planting_reminder_spec.rb b/spec/features/planting_reminder_spec.rb deleted file mode 100644 index 9979ddd99..000000000 --- a/spec/features/planting_reminder_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'spec_helper' - -feature "planting reminder" do - before :each do - @member = FactoryGirl.create(:member) - end - - scenario "sends email" do - expect { - # stub for while we're working on this. remove! - Notifier.planting_reminder(@member).deliver! - }.to change { ActionMailer::Base.deliveries.count }.by(1) - end -end From 8497782847f491ebee838c75115e23995c098d5b Mon Sep 17 00:00:00 2001 From: Skud Date: Tue, 30 Sep 2014 23:02:06 +1000 Subject: [PATCH 201/288] Clear crop hierarchy view cache after uploading crops The crop_sweeper only acts in response to things that happen in the controller. Since this works directly with the model, we need to clear the cache fragment manually. --- lib/tasks/growstuff.rake | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/tasks/growstuff.rake b/lib/tasks/growstuff.rake index 34739217b..65a418201 100644 --- a/lib/tasks/growstuff.rake +++ b/lib/tasks/growstuff.rake @@ -30,6 +30,7 @@ namespace :growstuff do CSV.foreach(@file) do |row| Crop.create_from_csv(row) end + Rails.cache.delete('full_crop_hierarchy') puts "Finished loading crops" end From 9a9eeecb46b8c2e3fba4ff7d33ce05674eee2ce8 Mon Sep 17 00:00:00 2001 From: Skud Date: Thu, 2 Oct 2014 10:09:21 +1000 Subject: [PATCH 202/288] Removing tests for migrations These were never run anyway (they don't have _spec.rb in their names) and were obviously written when we had no idea what we were doing. They're full of syntax errors and all sorts of stuff that just won't work. The best thing is to remove them so they don't confuse people! --- spec/migrations/give_each_user_a_garden.spec | 12 ------------ spec/migrations/set_up_test_users.spec | 10 ---------- 2 files changed, 22 deletions(-) delete mode 100644 spec/migrations/give_each_user_a_garden.spec delete mode 100644 spec/migrations/set_up_test_users.spec diff --git a/spec/migrations/give_each_user_a_garden.spec b/spec/migrations/give_each_user_a_garden.spec deleted file mode 100644 index 1c6972e54..000000000 --- a/spec/migrations/give_each_user_a_garden.spec +++ /dev/null @@ -1,12 +0,0 @@ -require 'spec_helper' - -describe 'new gardens' do - it "should have 'my garden' for each user" do - (1..3).each do |i| - @user = User.find_by_username("test#{i}") - @garden = Garden.find(:name => "My Garden", :user_id => @user.id) - @garden.should_exist - @garden.slug.should == "test#{i}-my-garden" - end - end -end diff --git a/spec/migrations/set_up_test_users.spec b/spec/migrations/set_up_test_users.spec deleted file mode 100644 index d1f69d3f6..000000000 --- a/spec/migrations/set_up_test_users.spec +++ /dev/null @@ -1,10 +0,0 @@ -require 'spec_helper' - -describe 'test users' do - it 'should have 3 test users' do - (1..3).each do |i| - @user = User.find_by_username("test#{i}") - @user.email.should == "test#{i}@example.com" - end - end -end From 0ad217c9a264cdeb9899362ad9eb098eaf8defa4 Mon Sep 17 00:00:00 2001 From: Mackenzie Morgan Date: Wed, 1 Oct 2014 20:16:53 -0400 Subject: [PATCH 203/288] add index to harvest_photo --- db/migrate/20140905001730_add_harvests_photos_table.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/db/migrate/20140905001730_add_harvests_photos_table.rb b/db/migrate/20140905001730_add_harvests_photos_table.rb index edacd061b..299d6653e 100644 --- a/db/migrate/20140905001730_add_harvests_photos_table.rb +++ b/db/migrate/20140905001730_add_harvests_photos_table.rb @@ -5,4 +5,6 @@ class AddHarvestsPhotosTable < ActiveRecord::Migration t.integer :harvest_id end end + + add_index(:harvests_photos, [:harvest_id, :photo_id]) end From a29d11a07c24d5da516cc35f7993c584982317ad Mon Sep 17 00:00:00 2001 From: Mackenzie Morgan Date: Wed, 1 Oct 2014 20:17:11 -0400 Subject: [PATCH 204/288] refactor error handling on photo upload to be easier to read --- app/controllers/photos_controller.rb | 50 ++++++++++++++-------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index f4004ad73..9c53e9168 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -67,31 +67,31 @@ class PhotosController < ApplicationController # several models can have photos. we need to know what model and the id # for the entry to attach the photo to valid_models = ["planting", "harvest"] - if params[:type] - if valid_models.include?(params[:type]) - if params[:id] - item = params[:type].camelcase.constantize.find_by_id(params[:id]) - if item - if item.owner.id == current_member.id - # This syntax is weird, so just know that it means this: - # @photo.harvests << item unless @photo.harvests.include?(item) - # but with the correct many-to-many relationship automatically referenced - (@photo.send "#{params[:type]}s") << item unless (@photo.send "#{params[:type]}s").include?(item) - else - flash[:alert] = "You must own both the #{params[:type]} and the photo." - end - else - flash[:alert] = "Couldn't find #{params[:type]} to connect to photo." - end - else - flash[:alert] = "Missing id parameter" - end - else - flash[:alert] = "Cannot attach photos to #{params[:type]}" - end - else - flash[:alert] = "Missing type parameter" - end + if ! params[:type] + flash[:alert] = "Missing type parameter" + return 1 + end + if ! valid_models.include?(params[:type]) + flash[:alert] = "Cannot attach photos to #{params[:type]}" + return 1 + end + if ! params[:id] + flash[:alert] = "Missing id parameter" + return 1 + end + item = params[:type].camelcase.constantize.find_by_id(params[:id]) + if ! item + flash[:alert] = "Couldn't find #{params[:type]} to connect to photo." + return 1 + end + if item.owner.id != current_member.id + flash[:alert] = "You must own both the #{params[:type]} and the photo." + return 1 + end + # This syntax is weird, so just know that it means this: + # @photo.harvests << item unless @photo.harvests.include?(item) + # but with the correct many-to-many relationship automatically referenced + (@photo.send "#{params[:type]}s") << item unless (@photo.send "#{params[:type]}s").include?(item) respond_to do |format| if @photo.save From 557d07d2f1093009806e3eec323517911288b941 Mon Sep 17 00:00:00 2001 From: Wendy Smoak Date: Wed, 1 Oct 2014 20:24:15 -0400 Subject: [PATCH 205/288] Rename Sign in tests and add Sign up tests. --- spec/features/{signin.rb => signin_spec.rb} | 0 spec/features/signup_spec.rb | 52 +++++++++++++++++++++ 2 files changed, 52 insertions(+) rename spec/features/{signin.rb => signin_spec.rb} (100%) create mode 100644 spec/features/signup_spec.rb diff --git a/spec/features/signin.rb b/spec/features/signin_spec.rb similarity index 100% rename from spec/features/signin.rb rename to spec/features/signin_spec.rb diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb new file mode 100644 index 000000000..9962df359 --- /dev/null +++ b/spec/features/signup_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +feature "signup" do + + scenario "sign up for new account from top menubar" do + visit crops_path # something other than front page, which has multiple signup links + click_link 'Sign up' + fill_in 'Login name', with: 'person123' + fill_in 'Email', with: 'gardener@example.com' + fill_in 'Password', with: 'abc123' + fill_in 'Password confirmation', with: 'abc123' + check 'member_tos_agreement' + click_button 'Sign up' + page.has_content? 'A message with a confirmation link has been sent to your email address. Please open the link to activate your account.' + current_path.should eq root_path + end + + scenario "sign up for new account with existing username" do + visit crops_path # something other than front page, which has multiple signup links + click_link 'Sign up' + fill_in 'Login name', with: 'person123' + fill_in 'Email', with: 'gardener@example.com' + fill_in 'Password', with: 'abc123' + fill_in 'Password confirmation', with: 'abc123' + check 'member_tos_agreement' + click_button 'Sign up' + page.has_content? 'A message with a confirmation link has been sent to your email address. Please open the link to activate your account.' + current_path.should eq root_path + first('.signup a').click # click the 'Sign up' button in the middle of the page + fill_in 'Login name', with: 'person123' + fill_in 'Email', with: 'gardener@example.com' + fill_in 'Password', with: 'abc123' + fill_in 'Password confirmation', with: 'abc123' + check 'member_tos_agreement' + click_button 'Sign up' + page.has_content? 'Login name has already been taken' + end + + scenario "sign up for new account without accepting TOS" do + visit root_path + first('.signup a').click # click the 'Sign up' button in the middle of the page + fill_in 'Login name', with: 'person123' + fill_in 'Email', with: 'gardener@example.com' + fill_in 'Password', with: 'abc123' + fill_in 'Password confirmation', with: 'abc123' + # do not check 'member_tos_agreement' + click_button 'Sign up' + page.has_content? 'Tos agreement must be accepted' + current_path.should eq members_path + end + +end From 57abfa04b38f23d15bed82572ce44319279b7edb Mon Sep 17 00:00:00 2001 From: Mackenzie Morgan Date: Wed, 1 Oct 2014 21:46:15 -0400 Subject: [PATCH 206/288] fix whitespace --- app/controllers/photos_controller.rb | 58 ++++++++++++++-------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index 9c53e9168..a22a3c2f1 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -64,35 +64,35 @@ class PhotosController < ApplicationController @photo.owner_id = current_member.id @photo.set_flickr_metadata - # several models can have photos. we need to know what model and the id - # for the entry to attach the photo to - valid_models = ["planting", "harvest"] - if ! params[:type] - flash[:alert] = "Missing type parameter" - return 1 - end - if ! valid_models.include?(params[:type]) - flash[:alert] = "Cannot attach photos to #{params[:type]}" - return 1 - end - if ! params[:id] - flash[:alert] = "Missing id parameter" - return 1 - end - item = params[:type].camelcase.constantize.find_by_id(params[:id]) - if ! item - flash[:alert] = "Couldn't find #{params[:type]} to connect to photo." - return 1 - end - if item.owner.id != current_member.id - flash[:alert] = "You must own both the #{params[:type]} and the photo." - return 1 - end - # This syntax is weird, so just know that it means this: - # @photo.harvests << item unless @photo.harvests.include?(item) - # but with the correct many-to-many relationship automatically referenced - (@photo.send "#{params[:type]}s") << item unless (@photo.send "#{params[:type]}s").include?(item) - + # several models can have photos. we need to know what model and the id + # for the entry to attach the photo to + valid_models = ["planting", "harvest"] + if ! params[:type] + flash[:alert] = "Missing type parameter" + return 1 + end + if ! valid_models.include?(params[:type]) + flash[:alert] = "Cannot attach photos to #{params[:type]}" + return 1 + end + if ! params[:id] + flash[:alert] = "Missing id parameter" + return 1 + end + item = params[:type].camelcase.constantize.find_by_id(params[:id]) + if ! item + flash[:alert] = "Couldn't find #{params[:type]} to connect to photo." + return 1 + end + if item.owner.id != current_member.id + flash[:alert] = "You must own both the #{params[:type]} and the photo." + return 1 + end + # This syntax is weird, so just know that it means this: + # @photo.harvests << item unless @photo.harvests.include?(item) + # but with the correct many-to-many relationship automatically referenced + (@photo.send "#{params[:type]}s") << item unless (@photo.send "#{params[:type]}s").include?(item) + respond_to do |format| if @photo.save format.html { redirect_to @photo, notice: 'Photo was successfully added.' } From fcda8742d8049bac9bcfb46f82b00f79c54c4f69 Mon Sep 17 00:00:00 2001 From: Mackenzie Morgan Date: Wed, 1 Oct 2014 22:52:52 -0400 Subject: [PATCH 207/288] go back to the nested error handling in the photos controller and add a migration for indexing harvests_photos --- app/controllers/photos_controller.rb | 48 +++++++++---------- ...0140905001730_add_harvests_photos_table.rb | 2 - ...41002022459_create_index_harvest_photos.rb | 5 ++ db/schema.rb | 6 +-- 4 files changed, 32 insertions(+), 29 deletions(-) create mode 100644 db/migrate/20141002022459_create_index_harvest_photos.rb diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index a22a3c2f1..1bb9ea4cf 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -67,31 +67,31 @@ class PhotosController < ApplicationController # several models can have photos. we need to know what model and the id # for the entry to attach the photo to valid_models = ["planting", "harvest"] - if ! params[:type] + if params[:type] + if valid_models.include?(params[:type]) + if params[:id] + item = params[:type].camelcase.constantize.find_by_id(params[:id]) + if item + if item.owner.id == current_member.id + # This syntax is weird, so just know that it means this: + # @photo.harvests << item unless @photo.harvests.include?(item) + # but with the correct many-to-many relationship automatically referenced + (@photo.send "#{params[:type]}s") << item unless (@photo.send "#{params[:type]}s").include?(item) + else + flash[:alert] = "You must own both the #{params[:type]} and the photo." + end + else + flash[:alert] = "Couldn't find #{params[:type]} to connect to photo." + end + else + flash[:alert] = "Missing id parameter" + end + else + flash[:alert] = "Cannot attach photos to #{params[:type]}" + end + else flash[:alert] = "Missing type parameter" - return 1 - end - if ! valid_models.include?(params[:type]) - flash[:alert] = "Cannot attach photos to #{params[:type]}" - return 1 - end - if ! params[:id] - flash[:alert] = "Missing id parameter" - return 1 - end - item = params[:type].camelcase.constantize.find_by_id(params[:id]) - if ! item - flash[:alert] = "Couldn't find #{params[:type]} to connect to photo." - return 1 - end - if item.owner.id != current_member.id - flash[:alert] = "You must own both the #{params[:type]} and the photo." - return 1 - end - # This syntax is weird, so just know that it means this: - # @photo.harvests << item unless @photo.harvests.include?(item) - # but with the correct many-to-many relationship automatically referenced - (@photo.send "#{params[:type]}s") << item unless (@photo.send "#{params[:type]}s").include?(item) + end respond_to do |format| if @photo.save diff --git a/db/migrate/20140905001730_add_harvests_photos_table.rb b/db/migrate/20140905001730_add_harvests_photos_table.rb index 299d6653e..edacd061b 100644 --- a/db/migrate/20140905001730_add_harvests_photos_table.rb +++ b/db/migrate/20140905001730_add_harvests_photos_table.rb @@ -5,6 +5,4 @@ class AddHarvestsPhotosTable < ActiveRecord::Migration t.integer :harvest_id end end - - add_index(:harvests_photos, [:harvest_id, :photo_id]) end diff --git a/db/migrate/20141002022459_create_index_harvest_photos.rb b/db/migrate/20141002022459_create_index_harvest_photos.rb new file mode 100644 index 000000000..b3bd4317a --- /dev/null +++ b/db/migrate/20141002022459_create_index_harvest_photos.rb @@ -0,0 +1,5 @@ +class CreateIndexHarvestPhotos < ActiveRecord::Migration + def change + add_index(:harvests_photos, [:harvest_id, :photo_id]) + end +end diff --git a/db/schema.rb b/db/schema.rb index 3790e9340..31f3b4487 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20140928085713) do +ActiveRecord::Schema.define(:version => 20141002022459) do create_table "account_types", :force => true do |t| t.string "name", :null => false @@ -113,6 +113,8 @@ ActiveRecord::Schema.define(:version => 20140928085713) do t.integer "harvest_id" end + add_index "harvests_photos", ["harvest_id", "photo_id"], :name => "index_harvests_photos_on_harvest_id_and_photo_id" + create_table "members", :force => true do |t| t.string "email", :default => "", :null => false t.string "encrypted_password", :default => "", :null => false @@ -144,7 +146,6 @@ ActiveRecord::Schema.define(:version => 20140928085713) do t.text "bio" t.integer "plantings_count" t.boolean "newsletter" - t.boolean "send_planting_reminder", :default => true end add_index "members", ["confirmation_token"], :name => "index_users_on_confirmation_token", :unique => true @@ -244,7 +245,6 @@ ActiveRecord::Schema.define(:version => 20140928085713) do t.datetime "updated_at", :null => false 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" From 48ad561b76c4128928650c8bde26dc6109383278 Mon Sep 17 00:00:00 2001 From: Mackenzie Morgan Date: Wed, 1 Oct 2014 23:58:54 -0400 Subject: [PATCH 208/288] convert tabs to spaces --- app/controllers/photos_controller.rb | 2 +- ...41002022459_create_index_harvest_photos.rb | 2 +- spec/models/harvest_spec.rb | 52 +++++++++---------- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index 1bb9ea4cf..45755db84 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -91,7 +91,7 @@ class PhotosController < ApplicationController end else flash[:alert] = "Missing type parameter" - end + end respond_to do |format| if @photo.save diff --git a/db/migrate/20141002022459_create_index_harvest_photos.rb b/db/migrate/20141002022459_create_index_harvest_photos.rb index b3bd4317a..75ef69d36 100644 --- a/db/migrate/20141002022459_create_index_harvest_photos.rb +++ b/db/migrate/20141002022459_create_index_harvest_photos.rb @@ -1,5 +1,5 @@ class CreateIndexHarvestPhotos < ActiveRecord::Migration def change - add_index(:harvests_photos, [:harvest_id, :photo_id]) + add_index(:harvests_photos, [:harvest_id, :photo_id]) end end diff --git a/spec/models/harvest_spec.rb b/spec/models/harvest_spec.rb index a5b5cabf7..66c83c93e 100644 --- a/spec/models/harvest_spec.rb +++ b/spec/models/harvest_spec.rb @@ -127,30 +127,30 @@ describe Harvest do end context 'photos' do - before(:each) do - @harvest = FactoryGirl.create(:harvest) - @photo = FactoryGirl.create(:photo) - @harvest.photos << @photo - end - - it 'has a photo' do - @harvest.photos.first.should eq @photo - end - - it 'deletes association with photos when photo is deleted' do - @photo.destroy - @harvest.reload - @harvest.photos.should be_empty - end - - it 'has a default photo' do - @harvest.default_photo.should eq @photo - end - - it 'chooses the most recent photo' do - @photo2 = FactoryGirl.create(:photo) - @harvest.photos << @photo2 - @harvest.default_photo.should eq @photo2 - end - end + before(:each) do + @harvest = FactoryGirl.create(:harvest) + @photo = FactoryGirl.create(:photo) + @harvest.photos << @photo + end + + it 'has a photo' do + @harvest.photos.first.should eq @photo + end + + it 'deletes association with photos when photo is deleted' do + @photo.destroy + @harvest.reload + @harvest.photos.should be_empty + end + + it 'has a default photo' do + @harvest.default_photo.should eq @photo + end + + it 'chooses the most recent photo' do + @photo2 = FactoryGirl.create(:photo) + @harvest.photos << @photo2 + @harvest.default_photo.should eq @photo2 + end + end end From e002cb2d35dcd209fb462f4d8bed4b81cdcef9ec Mon Sep 17 00:00:00 2001 From: Skud Date: Thu, 2 Oct 2014 17:48:19 +1000 Subject: [PATCH 209/288] added deploy task to remove unused photos --- script/deploy-tasks.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/script/deploy-tasks.sh b/script/deploy-tasks.sh index ac4068b26..3c6dfb909 100755 --- a/script/deploy-tasks.sh +++ b/script/deploy-tasks.sh @@ -12,3 +12,6 @@ echo "2014-09-28 - upload tomatoes" rake growstuff:import_crops file=db/seeds/crops-11-tomatoes.csv + +echo "2014-10-02 - remove unused photos" +rake growstuff:oneoff:remove_unused_photos From a2f2508f0ddd9080fcb793da0ca1415c4df6de7d Mon Sep 17 00:00:00 2001 From: Mackenzie Morgan Date: Thu, 2 Oct 2014 09:38:02 -0400 Subject: [PATCH 210/288] don't lose the thing we're attaching the photo to when we change sets. This is a modification of oshiho3's previous fix. --- app/views/photos/new.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/photos/new.html.haml b/app/views/photos/new.html.haml index f0503a6a6..bc3eb35df 100644 --- a/app/views/photos/new.html.haml +++ b/app/views/photos/new.html.haml @@ -15,7 +15,8 @@ = form_tag(new_photo_path, :method => :get, :class => 'form-inline') do = label_tag :set, "Choose a photo set:", :class => 'control-label' = select_tag :set, options_for_select(@sets, @current_set), :class => 'input-large' - = hidden_field_tag :planting_id, @planting_id + = hidden_field_tag :type, @type + = hidden_field_tag :id, @id = submit_tag "Search", :class => 'btn btn-primary' %div.pagination From 14d039ee018b4d877df10c17285d7a0f471fecd4 Mon Sep 17 00:00:00 2001 From: Shiho Takagi Date: Mon, 29 Sep 2014 22:37:03 +1000 Subject: [PATCH 211/288] Added crops-posts association as well as updated GUI for crop show --- app/controllers/crops_controller.rb | 1 + app/controllers/harvests_controller.rb | 18 +++--- app/controllers/plantings_controller.rb | 18 +++--- app/models/crop.rb | 3 + app/models/post.rb | 14 ++++ app/views/crops/_harvests.html.haml | 8 ++- app/views/crops/_plantings.html.haml | 21 ++++++ app/views/crops/show.html.haml | 14 ++-- app/views/harvests/index.html.haml | 2 +- app/views/plantings/index.html.haml | 2 +- config/routes.rb | 2 + .../20140928044231_add_crops_posts_table.rb | 10 +++ db/schema.rb | 10 ++- lib/haml/filters/growstuff_markdown.rb | 3 +- lib/tasks/growstuff.rake | 7 ++ spec/controllers/harvests_controller_spec.rb | 24 ++++++- spec/controllers/plantings_controller_spec.rb | 28 ++++++-- spec/models/crop_spec.rb | 18 ++++++ spec/models/post_spec.rb | 37 +++++++++++ spec/views/crops/show.html.haml_spec.rb | 64 ++++++++++++++++--- spec/views/harvests/index.html.haml_spec.rb | 13 ++++ spec/views/plantings/index.html.haml_spec.rb | 11 ++++ 22 files changed, 281 insertions(+), 47 deletions(-) create mode 100644 app/views/crops/_plantings.html.haml create mode 100644 db/migrate/20140928044231_add_crops_posts_table.rb diff --git a/app/controllers/crops_controller.rb b/app/controllers/crops_controller.rb index 1316350de..a30de159a 100644 --- a/app/controllers/crops_controller.rb +++ b/app/controllers/crops_controller.rb @@ -69,6 +69,7 @@ class CropsController < ApplicationController # GET /crops/1.json def show @crop = Crop.includes(:scientific_names, {:plantings => :photos}).find(params[:id]) + @posts = @crop.posts.paginate(:page => params[:page]) respond_to do |format| format.html # show.html.haml diff --git a/app/controllers/harvests_controller.rb b/app/controllers/harvests_controller.rb index 96e029ac3..524007d47 100644 --- a/app/controllers/harvests_controller.rb +++ b/app/controllers/harvests_controller.rb @@ -6,23 +6,21 @@ class HarvestsController < ApplicationController # GET /harvests.json def index @owner = Member.find_by_slug(params[:owner]) + @crop = Crop.find_by_slug(params[:crop]) if @owner - @harvests = @owner.harvests.includes(:owner, :crop).paginate(:page => params[:page]) + @harvests = @owner.harvests.includes(:owner, :crop) + elsif @crop + @harvests = @crop.harvests.includes(:owner, :crop) else - @harvests = Harvest.includes(:owner, :crop).paginate(:page => params[:page]) + @harvests = Harvest.includes(:owner, :crop) end respond_to do |format| - format.html # index.html.erb + format.html { @harvests = @harvests.paginate(:page => params[:page]) } format.json { render json: @harvests } format.csv do - if @owner - @filename = "Growstuff-#{@owner}-Harvests-#{Time.zone.now.to_s(:number)}.csv" - @harvests = @owner.harvests.includes(:owner, :crop) - else - @filename = "Growstuff-Harvests-#{Time.zone.now.to_s(:number)}.csv" - @harvests = Harvest.includes(:owner, :crop) - end + specifics = (@owner ? "#{@owner.name}-" : @crop ? "#{@crop.name}-" : nil) + @filename = "Growstuff-#{specifics}Harvests-#{Time.zone.now.to_s(:number)}.csv" render :csv => @harvests end end diff --git a/app/controllers/plantings_controller.rb b/app/controllers/plantings_controller.rb index 0d419d2f1..62eaf5b79 100644 --- a/app/controllers/plantings_controller.rb +++ b/app/controllers/plantings_controller.rb @@ -7,24 +7,22 @@ class PlantingsController < ApplicationController # GET /plantings.json def index @owner = Member.find_by_slug(params[:owner]) + @crop = Crop.find_by_slug(params[:crop]) if @owner - @plantings = @owner.plantings.includes(:owner, :crop, :garden).paginate(:page => params[:page]) + @plantings = @owner.plantings.includes(:owner, :crop, :garden) + elsif @crop + @plantings = @crop.plantings.includes(:owner, :crop, :garden) else - @plantings = Planting.includes(:owner, :crop, :garden).paginate(:page => params[:page]) + @plantings = Planting.includes(:owner, :crop, :garden) end respond_to do |format| - format.html # index.html.erb + format.html { @plantings = @plantings.paginate(:page => params[:page]) } format.json { render json: @plantings } format.rss { render :layout => false } #index.rss.builder format.csv do - if @owner - @filename = "Growstuff-#{@owner}-Plantings-#{Time.zone.now.to_s(:number)}.csv" - @plantings = @owner.plantings.includes(:owner, :crop, :garden) - else - @filename = "Growstuff-Plantings-#{Time.zone.now.to_s(:number)}.csv" - @plantings = Planting.includes(:owner, :crop, :garden) - end + specifics = (@owner ? "#{@owner.name}-" : @crop ? "#{@crop.name}-" : nil) + @filename = "Growstuff-#{specifics}Plantings-#{Time.zone.now.to_s(:number)}.csv" render :csv => @plantings end end diff --git a/app/models/crop.rb b/app/models/crop.rb index 3efe8be57..387936d91 100644 --- a/app/models/crop.rb +++ b/app/models/crop.rb @@ -17,6 +17,9 @@ class Crop < ActiveRecord::Base belongs_to :parent, :class_name => 'Crop' has_many :varieties, :class_name => 'Crop', :foreign_key => 'parent_id' + has_and_belongs_to_many :posts + before_destroy {|crop| crop.posts.clear} + default_scope order("lower(name) asc") scope :recent, reorder("created_at desc") diff --git a/app/models/post.rb b/app/models/post.rb index 3a853c18a..7f72a4e57 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -5,6 +5,9 @@ class Post < ActiveRecord::Base belongs_to :author, :class_name => 'Member' belongs_to :forum has_many :comments, :dependent => :destroy + has_and_belongs_to_many :crops + before_destroy {|post| post.crops.clear} + after_save :update_crops_posts_association # also has_many notifications, but kinda meaningless to get at them # from this direction, so we won't set up an association for now. @@ -39,4 +42,15 @@ class Post < ActiveRecord::Base end end + private + def update_crops_posts_association + self.crops.destroy_all + # look for crops mentioned in the post. eg. [tomato](crop) + self.body.scan(Haml::Filters::GrowstuffMarkdown::CROP_REGEX) do |m| + # find crop case-insensitively + crop = Crop.where('lower(name) = ?', $1.downcase).first + # create association + self.crops << crop if crop and not self.crops.include?(crop) + end + end end diff --git a/app/views/crops/_harvests.html.haml b/app/views/crops/_harvests.html.haml index b8cb25447..d10f57d06 100644 --- a/app/views/crops/_harvests.html.haml +++ b/app/views/crops/_harvests.html.haml @@ -4,16 +4,18 @@ Nobody has harvested this crop yet. - else %ul - - crop.harvests.each do |harvest| + - crop.harvests.take(3).each do |harvest| %li = link_to "#{harvest.owner} harvested #{display_quantity(harvest)}.", harvest_path(harvest) = render :partial => 'members/location', :locals => { :member => harvest.owner } %small = distance_of_time_in_words(harvest.created_at, Time.zone.now) ago. - + %p.col-md-offset-1 + = link_to "See all #{crop.name} harvests", harvests_by_crop_path(crop) - if current_member - = link_to "Track your #{crop.name} harvests.", new_harvest_path() + %p.col-md-offset-1 + = link_to "Track your #{crop.name} harvests.", new_harvest_path(:crop_id => crop.id) - else = render :partial => 'shared/signin_signup', :locals => { :to => "track your #{crop.name} harvests" } diff --git a/app/views/crops/_plantings.html.haml b/app/views/crops/_plantings.html.haml new file mode 100644 index 000000000..72bce5608 --- /dev/null +++ b/app/views/crops/_plantings.html.haml @@ -0,0 +1,21 @@ +%h4 Plantings +- if crop.plantings.empty? + %p + Nobody has planted this crop yet. +- else + %ul + - crop.plantings.take(3).each do |planting| + %li + = link_to "#{planting.owner} planted #{planting.quantity} #{planting.planted_from}.", planting_path(planting) + = render :partial => 'members/location', :locals => { :member => planting.owner } + %small + = distance_of_time_in_words(planting.created_at, Time.zone.now) + ago. + %p.col-md-offset-1 + = link_to "See all #{crop.name} plantings", plantings_by_crop_path(crop) +- if current_member + %p.col-md-offset-1 + = link_to "Track your #{crop.name} plantings.", new_planting_path(:crop_id => crop.id) +- else + = render :partial => 'shared/signin_signup', :locals => { :to => "track your #{crop.name} plantings" } + diff --git a/app/views/crops/show.html.haml b/app/views/crops/show.html.haml index b79320698..d28084280 100644 --- a/app/views/crops/show.html.haml +++ b/app/views/crops/show.html.haml @@ -33,12 +33,17 @@ %div#cropmap - - if @crop.plantings.size > 0 + %div.pagination + = page_entries_info @posts, :model => "posts" + = will_paginate @posts - %h2 All plantings + - unless @posts.empty? + - @posts.each do |post| + = render :partial => "posts/single", :locals => { :post => post, :subject => true } - - @crop.plantings.each do |p| - = render :partial => "plantings/thumbnail", :locals => { :planting => p, :title => 'owner' } + %div.pagination + = page_entries_info @posts, :model => "posts" + = will_paginate @posts .col-md-3 - if can? :edit, @crop or can? :destroy, @crop @@ -76,5 +81,6 @@ %ul %li= link_to 'Wikipedia (English)', @crop.en_wikipedia_url + = render :partial => 'plantings', :locals => { :crop => @crop } = render :partial => 'harvests', :locals => { :crop => @crop } = render :partial => 'find_seeds', :locals => { :crop => @crop } diff --git a/app/views/harvests/index.html.haml b/app/views/harvests/index.html.haml index 90bbb4638..f80ce0dd5 100644 --- a/app/views/harvests/index.html.haml +++ b/app/views/harvests/index.html.haml @@ -1,4 +1,4 @@ -- content_for :title, @owner ? "#{@owner}'s harvests" : "Everyone's harvests" +- content_for :title, @owner ? "#{@owner}'s harvests" : @crop ? "Everyone's #{@crop.name} harvests" : "Everyone's harvests" %p #{ENV['GROWSTUFF_SITE_NAME']} helps you track what you're diff --git a/app/views/plantings/index.html.haml b/app/views/plantings/index.html.haml index 091974ef8..eb1e017dc 100644 --- a/app/views/plantings/index.html.haml +++ b/app/views/plantings/index.html.haml @@ -1,4 +1,4 @@ -- content_for :title, @owner ? "#{@owner}'s plantings" : "Everyone's plantings" +- content_for :title, @owner ? "#{@owner}'s plantings" : @crop ? "Everyone's #{@crop.name} plantings" : "Everyone's plantings" %p - if can? :create, Planting diff --git a/config/routes.rb b/config/routes.rb index 360ed2477..0d645a1be 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -12,6 +12,7 @@ Growstuff::Application.routes.draw do resources :plantings match '/plantings/owner/:owner' => 'plantings#index', :as => 'plantings_by_owner' + match '/plantings/crop/:crop' => 'plantings#index', :as => 'plantings_by_crop' resources :gardens match '/gardens/owner/:owner' => 'gardens#index', :as => 'gardens_by_owner' @@ -21,6 +22,7 @@ Growstuff::Application.routes.draw do resources :harvests match '/harvests/owner/:owner' => 'harvests#index', :as => 'harvests_by_owner' + match '/harvests/crop/:crop' => 'harvests#index', :as => 'harvests_by_crop' resources :posts match '/posts/author/:author' => 'posts#index', :as => 'posts_by_author' diff --git a/db/migrate/20140928044231_add_crops_posts_table.rb b/db/migrate/20140928044231_add_crops_posts_table.rb new file mode 100644 index 000000000..a8c06927b --- /dev/null +++ b/db/migrate/20140928044231_add_crops_posts_table.rb @@ -0,0 +1,10 @@ +class AddCropsPostsTable < ActiveRecord::Migration + def change + create_table :crops_posts, :id => false do |t| + t.integer :crop_id + t.integer :post_id + end + add_index :crops_posts, [:crop_id, :post_id] + add_index :crops_posts, :crop_id + end +end \ No newline at end of file diff --git a/db/schema.rb b/db/schema.rb index 29271f5e6..e103777e6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20140829230600) do +ActiveRecord::Schema.define(:version => 20140928053257) do create_table "account_types", :force => true do |t| t.string "name", :null => false @@ -64,6 +64,14 @@ ActiveRecord::Schema.define(:version => 20140829230600) do add_index "crops", ["name"], :name => "index_crops_on_name" add_index "crops", ["slug"], :name => "index_crops_on_slug", :unique => true + create_table "crops_posts", :id => false, :force => true do |t| + t.integer "crop_id" + t.integer "post_id" + end + + add_index "crops_posts", ["crop_id", "post_id"], :name => "index_crops_posts_on_crop_id_and_post_id" + add_index "crops_posts", ["crop_id"], :name => "index_crops_posts_on_crop_id" + create_table "forums", :force => true do |t| t.string "name", :null => false t.text "description", :null => false diff --git a/lib/haml/filters/growstuff_markdown.rb b/lib/haml/filters/growstuff_markdown.rb index 7069590e7..dba081e44 100644 --- a/lib/haml/filters/growstuff_markdown.rb +++ b/lib/haml/filters/growstuff_markdown.rb @@ -2,12 +2,13 @@ require 'bluecloth' module Haml::Filters module GrowstuffMarkdown + CROP_REGEX = /\[([^\[\]]+?)\]\(crop\)/ include Haml::Filters::Base def render(text) # turn [tomato](crop) into [tomato](http://growstuff.org/crops/tomato) - expanded = text.gsub(/\[([^\[\]]+?)\]\(crop\)/) do |m| + expanded = text.gsub(CROP_REGEX) do |m| crop_str = $1 # find crop case-insensitively crop = Crop.where('lower(name) = ?', crop_str.downcase).first diff --git a/lib/tasks/growstuff.rake b/lib/tasks/growstuff.rake index 34739217b..3c4a676bc 100644 --- a/lib/tasks/growstuff.rake +++ b/lib/tasks/growstuff.rake @@ -256,6 +256,13 @@ namespace :growstuff do end end + desc "October 2014: generate crops_posts records for existing posts" + task :generate_crops_posts_records => :environment do + Post.find_each do |p| + p.send :update_crops_posts_association + end + end + end # end oneoff section end diff --git a/spec/controllers/harvests_controller_spec.rb b/spec/controllers/harvests_controller_spec.rb index f2ecb8e32..38b0edf8c 100644 --- a/spec/controllers/harvests_controller_spec.rb +++ b/spec/controllers/harvests_controller_spec.rb @@ -12,10 +12,30 @@ describe HarvestsController do end describe "GET index" do + before do + @member1 = FactoryGirl.create(:member) + @member2 = FactoryGirl.create(:member) + @tomato = FactoryGirl.create(:tomato) + @maize = FactoryGirl.create(:maize) + @harvest1 = FactoryGirl.create(:harvest, :owner_id => @member1.id, :crop_id => @tomato.id) + @harvest2 = FactoryGirl.create(:harvest, :owner_id => @member2.id, :crop_id => @maize.id) + end + it "assigns all harvests as @harvests" do - harvest = Harvest.create! valid_attributes get :index, {} - assigns(:harvests).should eq([harvest]) + assigns(:harvests).should =~ [@harvest1, @harvest2] + end + + it "picks up owner from params and shows owner's harvests only" do + get :index, {:owner => @member1.slug} + assigns(:owner).should eq @member1 + assigns(:harvests).should eq [@harvest1] + end + + it "picks up crop from params and shows the harvests for the crop only" do + get :index, {:crop => @maize.name} + assigns(:crop).should eq @maize + assigns(:harvests).should eq [@harvest2] end end diff --git a/spec/controllers/plantings_controller_spec.rb b/spec/controllers/plantings_controller_spec.rb index 7e6a75d22..4b3a518a5 100644 --- a/spec/controllers/plantings_controller_spec.rb +++ b/spec/controllers/plantings_controller_spec.rb @@ -12,10 +12,30 @@ describe PlantingsController do end describe "GET index" do - it "picks up owner from params" do - owner = FactoryGirl.create(:member) - get :index, {:owner => owner.slug} - assigns(:owner).should eq(owner) + before do + @member1 = FactoryGirl.create(:member) + @member2 = FactoryGirl.create(:member) + @tomato = FactoryGirl.create(:tomato) + @maize = FactoryGirl.create(:maize) + @planting1 = FactoryGirl.create(:planting, :crop => @tomato, :owner => @member1) + @planting2 = FactoryGirl.create(:planting, :crop => @maize, :owner => @member2) + end + + it "assigns all plantings as @plantings" do + get :index, {} + assigns(:plantings).should =~ [@planting1, @planting2] + end + + it "picks up owner from params and shows owner's plantings only" do + get :index, {:owner => @member1.slug} + assigns(:owner).should eq @member1 + assigns(:plantings).should eq [@planting1] + end + + it "picks up crop from params and shows the plantings for the crop only" do + get :index, {:crop => @maize.name} + assigns(:crop).should eq @maize + assigns(:plantings).should eq [@planting2] end end diff --git a/spec/models/crop_spec.rb b/spec/models/crop_spec.rb index 1741faf74..b54097070 100644 --- a/spec/models/crop_spec.rb +++ b/spec/models/crop_spec.rb @@ -343,4 +343,22 @@ describe Crop do end end + context "crop-post association" do + before { + @tomato = FactoryGirl.create(:tomato) + @maize = FactoryGirl.create(:maize) + @post = FactoryGirl.create(:post, :body => "[maize](crop)[tomato](crop)[tomato](crop)") + } + + describe "destroying a crop" do + before do + @tomato.destroy + end + + it "shouod delete the association but not the posts" do + Post.find_by_id(@post.id).should_not eq nil + Post.find_by_id(@post.id).crops.should eq [@maize] + end + end + end end diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb index 441b9c6a9..3751c13c7 100644 --- a/spec/models/post_spec.rb +++ b/spec/models/post_spec.rb @@ -95,4 +95,41 @@ describe Post do end end + context "crop-post association" do + before { + @tomato = FactoryGirl.create(:tomato) + @maize = FactoryGirl.create(:maize) + @chard = FactoryGirl.create(:chard) + @post = FactoryGirl.create(:post, :body => "[maize](crop)[tomato](crop)[tomato](crop)") + } + + it "should be generated without duplicate" do + @post.crops.should =~ [@tomato, @maize] + @tomato.posts.should eq [@post] + @maize.posts.should eq [@post] + end + + it "should be updated when post was modified" do + @post.update_attributes(:body => "[chard](crop)") + + @post.crops.should eq [@chard] + @chard.posts.should eq [@post] + @tomato.posts.should eq [] + @maize.posts.should eq [] + end + + describe "destroying the post" do + before do + @crops = @post.crops + @post.destroy + end + + it "shouod delete the association but not the crops" do + Crop.find_by_id(@tomato.id).should_not eq nil + Crop.find_by_id(@maize.id).should_not eq nil + Crop.find_by_id(@tomato.id).posts.should eq [] + Crop.find_by_id(@maize.id).posts.should eq [] + end + end + end end diff --git a/spec/views/crops/show.html.haml_spec.rb b/spec/views/crops/show.html.haml_spec.rb index 6e8250b27..1ef461508 100644 --- a/spec/views/crops/show.html.haml_spec.rb +++ b/spec/views/crops/show.html.haml_spec.rb @@ -7,6 +7,16 @@ describe "crops/show" do :scientific_names => [ FactoryGirl.create(:zea_mays) ] ) assign(:crop, @crop) + @author = FactoryGirl.create(:member) + page = 1 + per_page = 2 + total_entries = 2 + @posts = WillPaginate::Collection.create(page, per_page, total_entries) do |pager| + pager.replace([ + @post1 = FactoryGirl.create(:post, :author => @author, :body => "Post it!" ), + @post2 = FactoryGirl.create(:post, :author => @author, :body => "Done!" ) + ]) + end end context 'photos' do @@ -125,11 +135,10 @@ describe "crops/show" do context "has plantings" do before(:each) do - @owner = FactoryGirl.create(:member) - @garden = FactoryGirl.create(:garden, :owner => @owner) + @owner = FactoryGirl.create(:london_member) @planting = FactoryGirl.create(:planting, - :garden => @garden, - :crop => @crop + :crop => @crop, + :owner => @owner ) @crop.reload # to pick up latest plantings_count end @@ -137,16 +146,24 @@ describe "crops/show" do it "links to people who are growing this crop" do render rendered.should contain @owner.login_name - rendered.should contain @garden.name + rendered.should contain @owner.location end + end - it "shows photos where available" do - @photo = FactoryGirl.create(:photo) - @planting.photos << @photo + context "has posts" do + it "links to posts" do render - assert_select "img", :src => @photo.thumbnail_url + @posts.each do |p| + rendered.should contain p.author.login_name + rendered.should contain p.subject + rendered.should contain p.body + end end + it "contains two gravatar icons" do + render + assert_select "img", :src => /gravatar\.com\/avatar/, :count => 2 + end end context 'varieties' do @@ -189,9 +206,36 @@ describe "crops/show" do rendered.should contain "Harvest this" end - it "links to the right crop in the planting link" do + it "links to the right crop in the new planting link" do assert_select("a[href=#{new_planting_path}?crop_id=#{@crop.id}]") end + + it "links to the right crop in the new harvest link" do + assert_select("a[href=#{new_harvest_path}?crop_id=#{@crop.id}]") + end + + it { rendered.should contain "Nobody has planted this crop yet" } + it { rendered.should contain "Nobody has harvested this crop yet" } + + context "should have a link to" do + before do + FactoryGirl.create(:planting, :crop => @crop) + FactoryGirl.create(:harvest, :crop => @crop) + @crop.reload + render + end + + it "show all plantings by the crop link" do + assert_select("a[href=#{plantings_by_crop_path @crop}]") + end + + it "show all harvests by the crop link" do + assert_select("a[href=#{harvests_by_crop_path @crop}]") + end + end + + + end context "logged in and crop wrangler" do diff --git a/spec/views/harvests/index.html.haml_spec.rb b/spec/views/harvests/index.html.haml_spec.rb index 3a147e0b6..093a383a9 100644 --- a/spec/views/harvests/index.html.haml_spec.rb +++ b/spec/views/harvests/index.html.haml_spec.rb @@ -41,4 +41,17 @@ describe "harvests/index" do assert_select "a", :href => harvests_path(:format => 'csv') assert_select "a", :href => harvests_path(:format => 'json') end + + it "displays member's name in title" do + assign(:owner, @member) + render + view.content_for(:title).should contain @member.login_name + end + + it "displays crop's name in title" do + assign(:crop, @tomato) + render + view.content_for(:title).should contain @tomato.name + end + end diff --git a/spec/views/plantings/index.html.haml_spec.rb b/spec/views/plantings/index.html.haml_spec.rb index 5ff890514..d46b4e003 100644 --- a/spec/views/plantings/index.html.haml_spec.rb +++ b/spec/views/plantings/index.html.haml_spec.rb @@ -48,4 +48,15 @@ describe "plantings/index" do assert_select "a", :href => plantings_path(:format => 'rss') end + it "displays member's name in title" do + assign(:owner, @member) + render + view.content_for(:title).should contain @member.login_name + end + + it "displays crop's name in title" do + assign(:crop, @tomato) + render + view.content_for(:title).should contain @tomato.name + end end From cf07ecfa4aa9e96c8100437653b7088b6bfb5a20 Mon Sep 17 00:00:00 2001 From: Shiho Takagi Date: Sun, 5 Oct 2014 23:16:46 +1100 Subject: [PATCH 212/288] improved test spec, crop page and added rake task --- app/views/crops/show.html.haml | 5 ++-- db/schema.rb | 1 - lib/tasks/growstuff.rake | 2 +- script/deploy-tasks.sh | 3 +++ spec/models/crop_spec.rb | 19 +++++++------- spec/models/post_spec.rb | 47 ++++++++++++++++++---------------- 6 files changed, 42 insertions(+), 35 deletions(-) diff --git a/app/views/crops/show.html.haml b/app/views/crops/show.html.haml index d28084280..670a0d438 100644 --- a/app/views/crops/show.html.haml +++ b/app/views/crops/show.html.haml @@ -33,9 +33,10 @@ %div#cropmap + %a{:name => 'posts'} %div.pagination = page_entries_info @posts, :model => "posts" - = will_paginate @posts + = will_paginate @posts, :params => {:anchor => "posts"} - unless @posts.empty? - @posts.each do |post| @@ -43,7 +44,7 @@ %div.pagination = page_entries_info @posts, :model => "posts" - = will_paginate @posts + = will_paginate @posts, :params => {:anchor => "posts"} .col-md-3 - if can? :edit, @crop or can? :destroy, @crop diff --git a/db/schema.rb b/db/schema.rb index e4425711c..9292a6fbb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,6 @@ # # It's strongly recommended to check this file into your version control system. - ActiveRecord::Schema.define(:version => 20141002022459) do create_table "account_types", :force => true do |t| diff --git a/lib/tasks/growstuff.rake b/lib/tasks/growstuff.rake index 62046d87a..f19bf1fed 100644 --- a/lib/tasks/growstuff.rake +++ b/lib/tasks/growstuff.rake @@ -267,7 +267,7 @@ namespace :growstuff do desc "October 2014: generate crops_posts records for existing posts" task :generate_crops_posts_records => :environment do Post.find_each do |p| - p.send :update_crops_posts_association + p.save end end end # end oneoff section diff --git a/script/deploy-tasks.sh b/script/deploy-tasks.sh index 3c6dfb909..451fe0032 100755 --- a/script/deploy-tasks.sh +++ b/script/deploy-tasks.sh @@ -15,3 +15,6 @@ rake growstuff:import_crops file=db/seeds/crops-11-tomatoes.csv echo "2014-10-02 - remove unused photos" rake growstuff:oneoff:remove_unused_photos + +echo "2014-10-05 - generate crops_posts records for existing posts" +rake growstuff:oneoff:generate_crops_posts_records diff --git a/spec/models/crop_spec.rb b/spec/models/crop_spec.rb index b54097070..d9e3d4b0a 100644 --- a/spec/models/crop_spec.rb +++ b/spec/models/crop_spec.rb @@ -344,20 +344,21 @@ describe Crop do end context "crop-post association" do - before { - @tomato = FactoryGirl.create(:tomato) - @maize = FactoryGirl.create(:maize) - @post = FactoryGirl.create(:post, :body => "[maize](crop)[tomato](crop)[tomato](crop)") - } + let!(:tomato) { FactoryGirl.create(:tomato) } + let!(:maize) { FactoryGirl.create(:maize) } + let!(:post) { FactoryGirl.create(:post, :body => "[maize](crop)[tomato](crop)[tomato](crop)") } describe "destroying a crop" do before do - @tomato.destroy + tomato.destroy end - it "shouod delete the association but not the posts" do - Post.find_by_id(@post.id).should_not eq nil - Post.find_by_id(@post.id).crops.should eq [@maize] + it "should delete the association between post and the crop(tomato)" do + expect(Post.find(post).crops).to eq [maize] + end + + it "should not delete the posts" do + expect(Post.find(post)).to_not eq nil end end end diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb index 3751c13c7..30c19c500 100644 --- a/spec/models/post_spec.rb +++ b/spec/models/post_spec.rb @@ -96,39 +96,42 @@ describe Post do end context "crop-post association" do - before { - @tomato = FactoryGirl.create(:tomato) - @maize = FactoryGirl.create(:maize) - @chard = FactoryGirl.create(:chard) - @post = FactoryGirl.create(:post, :body => "[maize](crop)[tomato](crop)[tomato](crop)") - } + let!(:tomato) { FactoryGirl.create(:tomato) } + let!(:maize) { FactoryGirl.create(:maize) } + let!(:chard) { FactoryGirl.create(:chard) } + let!(:post) { FactoryGirl.create(:post, :body => "[maize](crop)[tomato](crop)[tomato](crop)") } - it "should be generated without duplicate" do - @post.crops.should =~ [@tomato, @maize] - @tomato.posts.should eq [@post] - @maize.posts.should eq [@post] + it "should be generated" do + expect(tomato.posts).to eq [post] + expect(maize.posts).to eq [post] + end + + it "should not duplicate" do + expect(post.crops) =~ [tomato, maize] end it "should be updated when post was modified" do - @post.update_attributes(:body => "[chard](crop)") + post.update_attributes(:body => "[chard](crop)") - @post.crops.should eq [@chard] - @chard.posts.should eq [@post] - @tomato.posts.should eq [] - @maize.posts.should eq [] + expect(post.crops).to eq [chard] + expect(chard.posts).to eq [post] + expect(tomato.posts).to eq [] + expect(maize.posts).to eq [] end describe "destroying the post" do before do - @crops = @post.crops - @post.destroy + post.destroy end - it "shouod delete the association but not the crops" do - Crop.find_by_id(@tomato.id).should_not eq nil - Crop.find_by_id(@maize.id).should_not eq nil - Crop.find_by_id(@tomato.id).posts.should eq [] - Crop.find_by_id(@maize.id).posts.should eq [] + it "should delete the association" do + expect(Crop.find(tomato).posts).to eq [] + expect(Crop.find(maize).posts).to eq [] + end + + it "should not delete the crops" do + expect(Crop.find(tomato)).to_not eq nil + expect(Crop.find(maize)).to_not eq nil end end end From ff617397165ab35ff5eec57f81167200b8f658c0 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 8 Oct 2014 18:59:10 +0100 Subject: [PATCH 213/288] s/boring_planting/no_photo_planting for clarity --- spec/models/planting_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/models/planting_spec.rb b/spec/models/planting_spec.rb index da9404c4c..0f64a53f1 100644 --- a/spec/models/planting_spec.rb +++ b/spec/models/planting_spec.rb @@ -185,10 +185,10 @@ describe Planting do @planting.save # this one doesn't have a photo - @boring_planting = FactoryGirl.create(:planting) + @no_photo_planting = FactoryGirl.create(:planting) Planting.interesting.should include @planting - Planting.interesting.should_not include @boring_planting + Planting.interesting.should_not include @no_photo_planting end it 'ignores plantings with the same owner' do @@ -219,11 +219,11 @@ describe Planting do @planting.save # this one doesn't have a photo - @boring_planting = FactoryGirl.create(:planting) + @no_photo_planting = FactoryGirl.create(:planting) interesting = Planting.interesting(10, false) interesting.should include @planting - interesting.should include @boring_planting + interesting.should include @no_photo_planting end end From d1e0bdb53445c0a1a7a21f708ba0a5873346605d Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sun, 12 Oct 2014 09:53:09 +1100 Subject: [PATCH 214/288] toggling planting finished clears the finished at field or populates it with a cached value --- Gemfile | 1 + Gemfile.lock | 7 ++++ .../javascripts/finish_planting.js.coffee | 12 +++++++ db/schema.rb | 9 +++++ .../plantings/planting_a_crop_spec.rb | 36 ++++++++++++++----- 5 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 app/assets/javascripts/finish_planting.js.coffee diff --git a/Gemfile b/Gemfile index e47403546..93fd9858d 100644 --- a/Gemfile +++ b/Gemfile @@ -119,6 +119,7 @@ gem 'omniauth-flickr', '>= 0.0.15' gem 'rake', '>= 10.0.0' group :development, :test do + gem 'pry' gem 'haml-rails' # HTML templating language gem 'rspec-rails', '~> 2.12.1' # unit testing framework gem 'database_cleaner', '~> 1.3.0' diff --git a/Gemfile.lock b/Gemfile.lock index 7829a34de..6f4b323df 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -173,6 +173,7 @@ GEM mime-types (~> 1.16) treetop (~> 1.4.8) memcachier (0.0.2) + method_source (0.8.2) mime-types (1.25.1) mini_portile (0.6.0) multi_json (1.10.1) @@ -201,6 +202,10 @@ GEM multi_json (~> 1.0) websocket-driver (>= 0.2.0) polyglot (0.3.5) + pry (0.10.0) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) rack (1.4.5) rack-cache (1.2) rack (>= 0.4) @@ -257,6 +262,7 @@ GEM multi_json simplecov-html (~> 0.8.0) simplecov-html (0.8.0) + slop (3.6.0) sprockets (2.2.2) hike (~> 1.2) multi_json (~> 1.0) @@ -343,6 +349,7 @@ DEPENDENCIES omniauth-twitter pg poltergeist (~> 1.5.1) + pry rack (~> 1.4.5) rails (= 3.2.13) rails_12factor diff --git a/app/assets/javascripts/finish_planting.js.coffee b/app/assets/javascripts/finish_planting.js.coffee new file mode 100644 index 000000000..26f30114b --- /dev/null +++ b/app/assets/javascripts/finish_planting.js.coffee @@ -0,0 +1,12 @@ +jQuery -> + previousValue = '' + $('#planting_finished').on('click', -> + finished = $('#planting_finished_at') + if @checked + if previousValue.length > 0 + date = previousValue + finished.val(date) + else + previousValue = finished.val() + finished.val('') + ) \ No newline at end of file diff --git a/db/schema.rb b/db/schema.rb index 31f3b4487..617181402 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -64,6 +64,14 @@ ActiveRecord::Schema.define(:version => 20141002022459) do add_index "crops", ["name"], :name => "index_crops_on_name" add_index "crops", ["slug"], :name => "index_crops_on_slug", :unique => true + create_table "crops_posts", :id => false, :force => true do |t| + t.integer "crop_id" + t.integer "post_id" + end + + add_index "crops_posts", ["crop_id", "post_id"], :name => "index_crops_posts_on_crop_id_and_post_id" + add_index "crops_posts", ["crop_id"], :name => "index_crops_posts_on_crop_id" + create_table "forums", :force => true do |t| t.string "name", :null => false t.text "description", :null => false @@ -146,6 +154,7 @@ ActiveRecord::Schema.define(:version => 20141002022459) do t.text "bio" t.integer "plantings_count" t.boolean "newsletter" + t.boolean "send_planting_reminder", :default => true end add_index "members", ["confirmation_token"], :name => "index_users_on_confirmation_token", :unique => true diff --git a/spec/features/plantings/planting_a_crop_spec.rb b/spec/features/plantings/planting_a_crop_spec.rb index faa4438e8..a3edec52f 100644 --- a/spec/features/plantings/planting_a_crop_spec.rb +++ b/spec/features/plantings/planting_a_crop_spec.rb @@ -1,4 +1,4 @@ -require 'spec_helper' +require "spec_helper" feature "Planting a crop", :js => true do let(:member) { FactoryGirl.create(:member) } @@ -6,7 +6,7 @@ feature "Planting a crop", :js => true do background do login_as(member) - visit '/plantings/new' + visit "/plantings/new" end it_behaves_like "crop suggest", "planting", "crop" @@ -38,24 +38,44 @@ feature "Planting a crop", :js => true do expect(page).to have_content "maize" end - scenario "Marking a planting as finished", :js => true do + scenario "Marking a planting as finished" do fill_autocomplete "crop", :with => "m" select_from_autocomplete "maize" within "form#new_planting" do - fill_in "When?", :with => '2014-07-01' - check 'Mark as finished' - fill_in "Finished date", :with => '2014-08-30' + fill_in "When?", :with => "2014-07-01" + check "Mark as finished" + fill_in "Finished date", :with => "2014-08-30" + + # Trigger click instead of using Capybara"s uncheck + # because a date selection widget is overlapping + # the checkbox preventing interaction. + page.find("#planting_finished").trigger("click") + end + + # Javascript removes the finished at date when the + # planting is marked unfinished. + expect(page.find("#planting_finished_at").value).to eq("") + + within "form#new_planting" do + page.find("#planting_finished").trigger("click") + end + + # The finished at date was cached in Javascript in + # case the user clicks unfinished accidentally. + expect(page.find("#planting_finished_at").value).to eq("2014-08-30") + + within "form#new_planting" do click_button "Save" end expect(page).to have_content "Planting was successfully created" expect(page).to have_content "Finished: August 30, 2014" end - scenario "Marking a planting as finished without a date", :js => true do + scenario "Marking a planting as finished without a date" do fill_autocomplete "crop", :with => "m" select_from_autocomplete "maize" within "form#new_planting" do - check 'Mark as finished' + check "Mark as finished" click_button "Save" end expect(page).to have_content "Planting was successfully created" From aaf08469e65c17d4b9e61987a6447e8571bed052 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Sun, 12 Oct 2014 23:14:02 +1100 Subject: [PATCH 215/288] add date when planting is marked finished from planting show page --- app/assets/javascripts/append_date.js.coffee | 16 ++++++++++++++++ app/assets/javascripts/finish_planting.js.coffee | 2 +- app/views/plantings/show.html.haml | 12 ++++++------ 3 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 app/assets/javascripts/append_date.js.coffee diff --git a/app/assets/javascripts/append_date.js.coffee b/app/assets/javascripts/append_date.js.coffee new file mode 100644 index 000000000..abccb9969 --- /dev/null +++ b/app/assets/javascripts/append_date.js.coffee @@ -0,0 +1,16 @@ +jQuery -> + + $('.append-date').datepicker({'format': 'yyyy-mm-dd'}) + + $('.append-date').click (e) -> + e.stopPropagation() + e.preventDefault() + + $('.append-date').one 'changeDate', -> + href = $(this).attr('href') + date = $(this).datepicker('getDate') + url = "#{href}&planting[finished_at]=#{date}" + + link = $("") + $('body').append(link) + $(link).click() diff --git a/app/assets/javascripts/finish_planting.js.coffee b/app/assets/javascripts/finish_planting.js.coffee index 26f30114b..de640311c 100644 --- a/app/assets/javascripts/finish_planting.js.coffee +++ b/app/assets/javascripts/finish_planting.js.coffee @@ -3,7 +3,7 @@ jQuery -> $('#planting_finished').on('click', -> finished = $('#planting_finished_at') if @checked - if previousValue.length > 0 + if previousValue.length date = previousValue finished.val(date) else diff --git a/app/views/plantings/show.html.haml b/app/views/plantings/show.html.haml index 366d089d7..cc3d58bae 100644 --- a/app/views/plantings/show.html.haml +++ b/app/views/plantings/show.html.haml @@ -42,12 +42,12 @@ - if can? :edit, @planting or can? :destroy, @planting %p - - if can? :edit, @planting - =link_to 'Edit', edit_planting_path(@planting), :class => 'btn btn-default btn-xs' - - if ! @planting.finished - = link_to "Mark as finished", planting_path(@planting, :planting => {:finished => 1}), :method => :put, :class => 'btn btn-default btn-xs' - - if can? :destroy, @planting - =link_to 'Delete', @planting, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' + - if can? :edit, @planting + =link_to 'Edit', edit_planting_path(@planting), :class => 'btn btn-default btn-xs' + - if ! @planting.finished + = link_to "Mark as finished", planting_path(@planting, :planting => {:finished => 1}), :method => :put, :class => 'btn btn-default btn-xs append-date' + - if can? :destroy, @planting + =link_to 'Delete', @planting, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' .col-md-6 = render :partial => "crops/index_card", :locals => { :crop => @planting.crop} From c405639f224b56e32489b5e332e7f95b735c7b24 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Mon, 13 Oct 2014 06:42:57 +1100 Subject: [PATCH 216/288] write test for marking a planting finished from show page --- screenshot.png | Bin 0 -> 81774 bytes spec/features/plantings/planting_a_crop_spec.rb | 14 ++++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 screenshot.png diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..426d1fe4f1038d3bd8932829c02f6b01cdb2cbc2 GIT binary patch literal 81774 zcmc$GgtG zzxxMVo@auYJ$oh6$S^=45ax&X(yAa3GVl}$gpLY4G&d7s0uRVe zk{{I3fgdmQ&)bA2OkBA>I%8Ah)hx%+{q?^jruXq9W#E5*0SD9HQZ1;iGclQxCS zHR>AfmKtGU6e6OZC-+z9J6ul3Z5HEgu+cQ9iCe0^(&TAM^jASRl7H`VwA8MhQUCLc zB)a472Q&#kR&y2^Ps*{eG0}9we-5Ov{EdL)x0oWNT9~ALZprf)ltc%%tL+% zNn&F-h^q2uAv%a($xhX$OJ*A_OIj9Jo%IZR6mvQMnsoMjEY&1;2~Bw9E{#l~4j$Wd zfDHYy8L+cW6zAyfp>;d$LJNzG>@}&Xv_CO$d8qusQqdZ>Gm^*+9#;3c9r5JUWfi-~ zR%dn;pb(&dJkem1MUh4H9q{X~`FKz5U%ogL_sh$pi&KsJASXv7#2k{8WLdGq!_V(D zr|+t&tLrJ}uG{Eg>UO`gvacl`NHvGdVIyRyr1kI!39GQCK)a zmdYJj9xC{PukAly^<_}aDgq7}SwK%Yqy>`tjqFe7vSDxb?^^r)z^X1K%i7_U8&rq& zPCF#qUvZ=jG9T4!b~-KC{-mkoPNn(CN;v&^_M<6y!>R5?29@OJ0EE;&e@TRQvWn7l zzlLrK5YicH!5#sw@`>!PxVZ5A8diNGCdA+ber%~78?X-FcpyR?d!qO+`pGMr8p6WD zjECa#>FDTih>2xw^e8x|rg(UGD(%`FITI>_Qm^ms~xvV?|TO2jM3RL zUFFyRp0qtNAi`L6TI1gaPJb-%vON>O;vKE6POPyJuvOc3gNccyhtjV{J8e0_$(}O#%1FApG=viroA!0q< z-JrqYQIi=+W{Y8icIgAGqvHIqW`fK2QE$~7#s*V66Li}BdNrfvx~QeDPEcb$W-?p) z$?M^akt!XPvS`LC1dFo2(b2TY>zaawmd4V`iiUQJ$$GB*s2a(5H=p~1(uWN=Z0$l{ z+hEzDCn_kIv=Nfwxm@v@0K?~Di@L^PgC+(;yJC*pXIY>>hJ0W+f%wRzw3rKF|^INzWHiz8iWAUa*Rh>1gd=kniq&y$Xx-f6-c_T%Tz zkS-&M+^HKcZN{L8o(&+!eE<2gKg&PR{W??m-p1mRNObWXk;$ZS-LFq@h}=&xyij(9 z;FaC=&7F_xyUCxP6i{*@vTruOA`zKFvQIT@0+VDh(ErbGiXVJ z-@}|v*WW5I(E2`1Q%_lnaJ-A?8?OB_?Y5U4nr>EO&mn%eyw7ila*F~?Ha;%yP6Lue zzA7#*=?B|%$ff`F&P`&+L*EH!pd!b=Dp2!4PM`b-d3jo9W{d>6V7dG_ED$b^7Mh<< zS%Klxqh6B(XGPncoU#Qp5_-nTWfg_N`mxj}C!D?`Mk-e;M@6Drg3ZYncjD)hrlXey z{tqo_-1OpXQ*@tDBm!m>DR#`a_$J%)LMXc-<$;%VwIoyhJe*Ivma4LQRVhaThm+Z5 zO8xv%+H6?tEuusv3rfdS_!uQbUe2IMDsI%kEu85*^y zJf+2_UUGWc+JoX5?jef@WaaIN%N zwa`ARSFc|AliRl@H(l(S@YJ0Svt$s1!5X|E)BfmZ#YjezS#PDKWvZ{>u$2n7PtAW; zZW?sTL|_k2UTu!K_i&ApcBAdVDTQ>Q1YP@E+>S>@-S)OV^fzQn0r&4@?x@^1a_rKv zOls~8i>J(M9M{(oBrkOZ!C{hx%sM-_-#h)+coEtC7UOfy%VIUh*^ zxp%`MOSPzS=}7wByYCrdd4)C3nwM^9pjsEiB-({bN!|+|(oo3_n|WEfU)ncDhzU*R z^=%qb?;FFD#iQO@sCe|Efhs>yUXm8lGSA43H)w{#YQ1?{R-#pl@|v1ZS_=F}j=wQ7 zzYFY}cenGGe3oih&R~`lJv2hg*)Tt~M@VtgiW@cS`BEq`FGw0RJUsFyS;1i1@{bNk zRdrFc%pUV{uEEf^VyX3ZR(G`V5)W?^U%S?llcW6x5wz7Gy#)joHHev|#0^@;f4iHi zm|oBEnr+9|_r8PGpf#wvT2-^miu_&Hj1c*#TyRVVCWo_-MQ&5$-~y@fo1m)AX6W+C z(|wa~%|5h3*cNio`c3&8%=1M}Q`uWeLPAoNcD-pV8@{KvXb1{2v|9s)+h>cl`_mtb z)PdMQtBo<5e*U*N_;#GFDO{*mJY(g4#}SaDB9NHj3MTtfyO=Tl`;(*j-0$ZBI=MOQ zTFT?s=J8gLD5!$~WMcsPclBWv-Cgul%^kI2VYgl>9>?1V0g<=twrZ(0&4}X;n`E8yH|Qk~9kSnHTAJTAu8qgP zdZ{UIb>;+-TjIjrnP(2K^o1e4=%7TEz>F1^5)z+egqYn_Bvv+;5IrlS^>~#7xzf*e zUvk=)hbQHG_V^8AR z!LzDM8B!~F^ZA=o((nMaue7ag`b2A)Zp%+c(*Yo(4Gn&$U2%8NKlHx8zG(869r`dd zZ^H*u5B+cyaN~WqP^fQ5Oq>bw8#6D=&tHIWvL4BRw2{1WGUL-Cffl2>nk(zlvC2{1 zImu%qT-K<{*_%q4@t@w|_YEC)kP;{Y+c5n8PAY`8x` zK)P#r(kx1gniKQBL;h~Mw^WF&F{5!@gvAWjU5zoLfPQTWy1AL%x~uqAcC`D)l{Y^p zcXyZJ&_!6&+K3XBm$xq6z}iw@bJFUWjgoKWe(-;Q2xFQPC8!yPtw~p%V&zYe%o-SJ z%F4y4q028qid2v>t#-;oo$Q@;tOOc!RKTtd3r4UL$&-G^M?5oYXDqP&N*NN<2%l+< zKvJ#9ZzqygM1?39zK2}YHYIA6Re9aSs5iGH2wLp`WeHIoLpYXHkDLp0b;IXBus%yYy z92Z0u1=IvsrB8lwnF!+1pSmRqOCEplJcvS|ag7pvD&G)li7xMldH0`Wfc{EaSpXsg z0y*nY-iuYo*42@w_H9gAo(vkJg{}aYM50VBOGS|Um|l07)W{X<2rI^K=!fkKtRk8+ z8f@!)r(f;)eM;vll*W!$S`=cPx7%yWHWqL-ji3V>QL>!&OXxJQ-+48c`0F;cq82eF z9UWbymga(xZ|IPR+uy%cDWb>K$XbU+Mvz_#G4#WAe0;>#BNr>wQD=vb*DA!3P+z<( zTuFmP%Tsv(H6Sc140DeJRO~{D=g)JX!AuzPMygZ!iI)r$pFKswJ@hr`d-*;U!nF`` zs=>d&WSZ~+6#AN(IVU?C8mcXMNd!@U;df0#(<5-29h_@88^@(HG&$1!P42f5uW;wF zM3Ez^b>$KOU$RvFS2kYE6B}p@b<<-#-g(kv1P2a(6{sZdS5l8nL;W5_Xd_G|za9JL zkZ+>DMm0aHV6eO2qU2*z_mmY9M^4`C;A3GkMOya&y5BQmMNn2#Q*uE8gOydf^>Sh& z+UkN4Po1+zS#j|&ykTtDcVEQs5$O)eVb5VnHFRu@Ehr>JRZsnonFX-ObA;tO`b@fP zu2)p-pQJ@&#iHlu1#)t8Ia4AZ@sWmJ4@t|&xO`fG21Fv0{gbhrhDSy|HGQRj_wJo# zoeuCr!efauXW+>;HPx>5B%}t+iu-05qucg(**7FFMB~t=TGDGMK_IIHRN35+!C2%aQ@_N0W`W|t4sbh0!{!oHznGevhLagM)67F<|3o=Zc3n{cMI zWRw`Hs&=nE-f1x?W{j@3d0N`pox4+*wzxm#=lA(snDGgdKVo?r(bC*Cu(xQhX1{%g}4Gznmu%M5TQ7@=^q5ckBM+`mE4m2??PnA$jDmEGGD#& zh=JB@bxiPEv#|zfJ~nur#>o>iBwX{6OYrg^P$!(L~537S(@`1mae=% zdW~AtigR`SRlQ{9VgI|~*`E%B?40V9E~ItP>6cOPfn`*r6&M9v~vHNo&E321VltRDoiANHY9)784v`Ei#n@5_qG6k z!zTv6RC|Gnq-E&ME0gMIlk?ltBtp)6q#~v{EosD*Aa;yk#JU2g1o#V5DM|Mt*?sx< zJ%gnjjBjfEj-|{2)T<@ghq2+`_Rs~QLK}C?#+$aID1|@-7$~$dkuMn-P=QW)%&M@e z==;`yljWB$Rh)W)_C(?> zaH5P`n6C)g+wPX<*%Y>Xr=}&k|72t0pi#{ z7^Cw`TLUaI?m4hR1N*c_uWw+tbdy{{#J9rsrZSkOQvnO5YmLE?rVF(sia~o|JsvxP zhBX(Yi6@Bd@Guhfm}|jh2$Ulh`pXpVTE1U)b=Jhh${Klj=`8keDs_HwK?;Nt{O*cY zL?kVOy?HSGINf!%Ys0|-TGpUyXgE=_r<^hdfKQuYa0u-Og7 z7Bhk!uK+NNuw+K=!-r?oUZ)7g#>SBpf_Q%Xt8PdUecj=NEN@cBN)0b-=RKURrq5NxJKl=M+%G<7WfQW$3mTlF|8EgSa zzJJ3(1<1y-09ggZ2zFPZ#`HCOZ<}+WW|7 zw+s>>KQC{(d|mYVaG`}U>!IoA!-bdP&Z^b`#-`bi-1rcs+K+U(+X z5mT?tO-$-H+SKy$XmOq4k7VzM`00-GJ5QbltUAj14(uBC^kLt%he`OOY-X9+euC3` zIfblWd66+<`ihFSqRa_#3yF$_zzgJw9QpQ{>W6&ffvA_cm5uR>i$Z+f_igtV%I$6< zMMGg;)mHFN;*5nomON)u7S^{zWphqEv=Kd|pXeBRIU&`gUY83xu*!9baWgG5N2t<0c<6+a-qI7f#=@;`@R%UV@V=th<()q9f5^0`?Q_H_Zc(-%v2C zZmS=VF?v?vwv-Sy8T=)eLtsJS5Rxi=a(|aN7gr%Nmg)T(Ab7y!d`RQtA)gWzcJk*| z`z(2~RhSy@8TqTMj$|+*=Mm- zHHF>QaO{l1#QUu3?n>p`1k>;Gw%qsJM8l3zg|NK!W zV$}voynTz{?(QyfIVp84@f+aOD1}|zwrw9DZjbx#0OsokSfs5!ArjYfoY=Rww`l^7 z0rw{znT?GCuAfA1w|`KG`=tF*w>Ui;;pj*U!)HXCY_K;B4-db2yg&J%sQ4~lmD6(a zB~ZXFAQQ5M)5cuMc}VLblh#5Y3O|NHC>tGtGg;@9XvC|=fX**G8GU!xOZK@cOv}oB z_g6;)9glGPyK8VcnaU@Pu&K{0cn=- z!u;&X&S3oclcPb)(1&NNhA#9_ew%{UobU+7$erjXS6bhvITmS|pc>0A8?l?uygP&m zk>sKQsamW%F~0Z2<`=hhJSJH?=9xmq1>V)1bmAI# zLZu26n!o+6Ibj{*uEYBYM~I;Z&O&&b>4ux-utc_$f!z}qqk3&^fv2xTqfL=8&2q#v zh$ITpsbq$^0;cX6r8xGPdSa{DOzSs4Td?UT!8BH;g6yiH6{bTs zss~C}NI5)c?`qyg(`VkPvhLON^~r_eOEUtp%@&1+84dHA1<3u?6{c84aQQg+{$lo{ zq#o-l4@z0?>Ql@jj@Lj4o6qe3sRBT-XOQTs}J0X{ITKd_H(2h(6!eO<=_`8`=hQC^ISLnF@E40h zwM=Ib?9I_>t2&@P3kAdi6*V;}O-&*|8rcdaHdqHjF2AvnBtfo^*wAZZwcmzM4LHEm zly+ldqg1;pptV&5Xq|ytOh-!_Sfp0yK~V_*77!N)G@DyXn%@zDM(l&KGHkw|_YYN? zWB;f=D>v2NqtNav28RBX8?CNbchS6HZ~r#A`=B`iFAbBHnXGtl(C zpb|*}cyD(~Th?*WwgMzj7X2V!fexmYf&RpB!=Ip#jK>Gtbc4BO!wK)Bg|?-wwL0)P z`Xj@S3mL3u&)!x{d*-?CCR+$N7>Uo!q(cguHW5JaC{^28)`C6Pj!3mB_2e^n5~jA& z*2;MeuWkpTDYqcv_oN-3+qkGNNIC;JFZKF&ICfqOj{#y*1(Q#{&LMA6A&^ufoExBKe@qe*85c&LO{?bBH zU7e07qE5{sgdZprIh$Ow_REM8YqbQZ*C!*>Pd>rJ%#gajWhf^C?pW@{U^+#{2a+5g0G@2y|rk@6Gt)?}HS(t^>L%+GGWwj28WsJ;! z?0KVGN+Fi=Y;u1MLhfru@T>fwm~F}B&P$w@djL|WqP4burPm;CPx($(*0@gRi`87V z=c9xFk0NRg%d{5|n|{e#7|{XdM9 z6M+IXf8&~&*>ElU`f=Ao8-l4njn6#r2mxV zc7CFPWcJGdB))&V04i_1ZX5vRa_h=J#roFHVSEnT*&Ql@+}v^PUa8G$&@g4mLq&;M z9vXD?WLu>@3B6i&nh>QBc8N3{h^{gpBbzPPA6ao-JDMtbIj#rJfl5LBxyikFqr2Lf9xzHW<=j% z-5GXPf+U*TV;L?s|4Y}58hI`QUdh!!Q1iiO6kNNZ5bhV9>vzJkg}ZW zfcxW?L?7>a&M&|eM3avsO_?T%dh)3iD(yt^uNtj1*CFgQ6s`R7m-xw4T2j(yX_MUy z4wE)KJkrFlw7Im&YY{?LVBn5Yj8c!+GOAes0JH)5WmkbXeoAVTC65&kS)J#+si$0i z_g4G7=lZw%lkIKkVr#1vaZQjF^x$Uj0+$gaz##hp*ct1meCo)p6(br0}ek0-lfzl{NTct%u_f{nApraC%1Y z_hYDJ_P|lFyeOAXF zitpOsbgOsEk}IJ#zh7K_4)d|~4xF&gE?*Ta8(V9-zENPnXHKw4H{nZOjvA|tm!_fV zUZDh0!cI=^L^4v9-x_lwp4`a!fspQD^t2;_lq`&}&y(P*+rc&P zpUL@ahgZDx-+@&Dy9Eu3ewzG}=|7vlqLKNnCKq7S08rsH^^Vy6h3=eHT~Kf^z{0#& zaDJ4@6!&fS+?D~>JCOEzjNVjw!t7E~Q{4gg!paG?$h~TPEJmB4UHyW-$3s5 zwcWAQrlb0$fJX}d;|gyo6I0Wo(o$y^#Coy125nY#b@ex<{V!abb8={6Is?nUBLxQs zlSIkL#88a^(mgO=A{V3Fhf57HO->M0IBD;~jHv0_1Oi5Iz$T(7CMHhriRq!s`}YAY zJ~Q1hCqJHI#u5-Q?xDQG&-gGNPqI7rB!a(nONcBrM-U z^5M;RzlDag3JQ)QifU`)0|LAt_ht7On7pTj;-XzW*rE!SzaV$H&z?QAtkvmIUQpi6 z+|G>=+jf1r?|3-Sg8fKHcxP$Jp{aFsHO zV*f>swxQNs(G~-JU(;RZ59P;%%*Sm;5dezGL8b?Q%YSHK3%1ic! zE7xVC^b*K{`pBSeva^QPt*7{;b=O?e@vqiufCh>Cfwxj|BAXW0VW^fVBN1( zth{oPnRP_HI+0%!D@q&XpzEmo_UAJM2lfcRi(Obma9{?5Jy#sz>0i1GWUcB@TiSX~ zBBp=Ix0HzCTF5>`Egc=sR|Ntr5YRA?inzT2w3;Y#ev^hJAYw{C z)r0`y1mI}Rk{P&=gpv{#DJdzHW2q1s*JpI#NC1f&H7pfXR9I~FM_ukuL*mOr)fU!b zUrC`qe{MQctO4jaHhknIS`{+6Q-3qEYjZ{WU3jw)gqaoKi$0s1a~XFdGcz;atOsH* z+hU}Sg{T(Mv5vy_UOC$B@d8w@OKM0Qq6+eD5e1e6LSTr{O%L}~5ZQt9>i}DuQYR+2 zw_P7H_Zb?nc$q64#EwdA;l_)K$K{afdwt6uYj5xVK+9;Y@d5zuBpZKtQ@d<)doH9zWTZcGijeo$1RW^KfKRq2Sw8u5AtfaIh)^mqHhL1vqhoZu z)Q(=W9q+p!MfZhpO;%rPoCJjgRa$oKq-T(&i~&q#N*mUN$PdVko`{xz{KqV_-x~Tm zCvb@qs6@M|UU9MY&?`|j zV*B18>DJpA1J0Sl#kvuAO9O40CV)$qyT8-Qx?N7m-U4=tx6aNy(*=sc`4f)J_iDgt zU6yR)fBrn5H;p+1Zr?LACu|#5b{)kd$#}yHGksD31}GlLGl_uc%4O6E>W`)zsWk3! zJ18o2dpH~TD>%GYR0N~FAb`{xaHD`gDB5KX#ok3&!h2Hd?N7GbXQ2|=r>jQC;nwJi zrnx7Ur*IvleclEvL3ssd7YZI8zF5O`n>_knMjU*6MKEzx*v+gTX95B{<>#6Okr326 z2-zb9gNn=BV`(z8R?HURy-YOO-`uxD*u|msxnb#MKs;mP7Gn4kuI zlr8yYHTAuh^rB!X|J3_Ek~Lg@q)lvpXm}{UswkOWYjooWM>{eFYl^QRQABX$CjpNb`7p6IvZnBF^@B% zbWMfiFLB%ie`=`Tvew|Vt)5gV$vQbbXZVr)Y)iK0%ZN)34Qx1M0c}nBPBE8pLU*aEr zo*Z@NWFBMIkKb`WLj*$e8pN?zyrYGM=EgMuL}&S*?AcK{52?dhxTBo@IB;K>TW4sy z8@*&_G?d_U<8i^4lA`yyn0j@|PSj>4iMAhV;pHZdj0l{f8@{bc6W$5Rf9mh0rJunFNOf3D$5W z1bn=qf_w9oKs71M%S-C*rL#{3;{mmN-$91RzP%-m{yBsT8dpT3$Z#pd<_V)VA4^Pa zULH5EIlB|4c3P=saX;KJL{#~t-vd1<1^l1Q9nx49pPimKe8Qc^Ftsms7``eKWwENf zsq{7U0Z$y&l0@x+g@DkTZ{w&$?}tck<+e!-1NbdCZQ)uRpv!py67Dw(#9_bo15X3V zLNyqzhW*InAeJNf(?FB?K4By|cR(ohJvr#_VEX?=+y8L?W$l_5od_t^{$?fmjBO>FaKuce0$74 zOEdR=Yt~)l_Aq77TaCgQ2WRIwWfRg-pKP#)(;*1a@ z;=mE5s90(M%WRm6M${p536JsS{k4m>%KP~;#`20(FdMt_&p){otza0NBb#W zD!={ieqrY3U+XGRud5$Nu0)=eoeW@SHJR{8l;`E^_P+Nlx(#+$sSUNYJQ==^D4<7x z&abr9)$M$x)h>R!jSuJ)fWY{K1oK=FTzEKmyhr#KO)p~k#&yM;vCn{P8?cx1@bTSv zoqHbDjWk`&>LN7mrkIKOs@F|i#L-8hEo#gS8Ul24SMNwY_MEo*P)QvXYx8F0`pLl;~NAHAm=9dGd- zA1a)Sj$79?# z0@@b2Vz^4mVpqyQWsCr5<|K@jUK_rPMe7R3ttfsxs$=giP*_HfXi zF+NT@XKe!F;h*7#hEK1>$UIM$rHoJPe8?vvJBUKChmxgiopK|;T@#WnDDEa5^L>AZ z10vJOJv~{dLG$y6qq#K206h~MZ!qgyNwfSN*ZrKjmuNd4L z(9r)h_&wL{;KbMB%w!Go>KXx7tFtB=z4MmU5#6?gb{>fY0f!ip=3)$WCr~LTl7A(H#o_ zedW0IV*Gi|neVFuUNw+pj}D1Yx37?|I&;$3q5w8>isc(ql(R4fg?=1$<|onc@(r=5 zv365XP*8|Tola4StIQ0Az$B?xMbQvx95o~8v(-R zfaei;g;aQwc& zyuwecUbN7xk~b%b1Krcg8B!!d`I@ljH3e*~oN?pz_zRTmYBKi7G{M z5A)2#PD?vX^R=(LeKbx;Um;TT`kglfsN*ileDlV7iJVZbqSY2-CnH(@@z996Jj%K+7t_?-wkIyRF+Nuj{09&4g zVnq_-y2JbjrK5I1ak&I;p4b`0FYv>88-GkBCthv1s6G&U0J+;T7Y@d3vA3~pJYH(Z z{Q2|ete{DHpgWMPnOV}KZW`z3{my4A3=KiWCDw1qL<@P7qQ;H=fVyq!W)12DjB`!m zvT|QBrV9kMPz4+}1y&n>PG7(la&r~~zB#{0`u(wTy3s@@QQEnzd_BT?sAo%XfvO@j zL;$H%08s?eRY3gjy&~T-ezORH90VpU9Z@btZR<3Q(2?pEn^=vq*glr@kxk+`f5-g@ zC^OxnXp?nb`f7nS8#)FC$5e~dQ+?IIXeg1h0JAZ|PMN*+sNdb}7qp`$8Qd4v>6##i z^X(7qPd>sRK>aKLG$qjX&aV54ueSA{kExkWr$@BEh=_@A6-rc)UEPb-$CUse0~pI2 z3z-2UrAKSwtN>cy_RbE9^XUI!3EL;61YzXI6`-P5x9xcwVhDUk!Y^c!Kwwj}bvlQ0 zlDtS7Ylm!?%vb?Fq-EtN1lF=8o_ZyOZ;_ENTd(#TS+wWTDaWW5iU@%y z14GJBqP3ohLWPU~x%`~+@}m!})^Nq#sU}-RMa-QtOs~0NM9QY!vzFb}dpu(56T2*G z0k_KO`|`T)`jImD^nLWlO5rz+0w63(p`f8{Z;c?pU5*rsbJEt+ZRXbUK1v^jz`3Zn z^sv^%p93TRLRv#Z<4%F!(xLhO1P}mXV0us_t#~cu|7ULH@&ktPFf~^GpmGPj^`go0 z3-shcCe~d_5Eb_vCEL zYNn0%-ki|BTD1N`$+k{bwXUeGO(GR4WJ*auNN92#8*1DWlB-$5iPHGf`#&a6X;_s` z*>R(eWEwL-lH4NRimm~|=H)977oS2JznMEPCjnWIz~Si(gp7t3{Mhg%Au&-F0F^zh zFP5;eu_4jICv;-$(SVq#rlob_Gr?<>1wZ#9?|3A0P+}nwajTr$Y7)W)K*J*YMSNP? zXe4POP+C59_@vMAxU{Tn1+2{p32jVv1yWU5P>3$>I1UfXxdZ#X03QIWVSAeE8r)Bo z8jj?;?8q1+{)qha0O@!S{SgA}8v>|#lcoC3*?BNqu(rX0=(U8W`z<3v66aPN`?Q|- z)yy$2#+vVwcOmS)0afFYh`1~DzZUQx>FI#7i-{FlI!LDkDEycU3(2DCQ8kiB;zPH& zFF7Kq>t{H>pr#F1AIi}a5JkL>zDAl{g#b?kP1=z>Y{wVSrkLkI2 zTNn10L$31nJF-g;QJ`_$S+IE8uUdI*yHDo#rTX~sd;CwK&^#?o^;pMCrXJbIb$>J} zBY-jc#Y<-3djxi+dr}sI$B&rnkIz>DWV{8IE}y*%M^7R!Wnh+aNcG>XCy%#~!LhNc zl#|NJH?@oPeqJ-YynarfB{aU2SLR}zRe?mU zitXO8t7E89=+&!iuN!mxp(0JyJLHbG9)#~7aR@P=-1Guhh%o#co`!}5 z$9u5FJ2B_h2IJ_ps#?K~;sT+JW%?~4fXZDreHynryYBnSW1{IHM*Iw!f5jP}+hTnC ze8i&034%pNwkU(e!}I5&HBkq-Egy}HLU zqP_4bqdK(NK!Sg~soukK@_#omIqh`!kCyivQn@XBU0UFlOsE{fYyS z3KPLE4aZiPm7sXrgp%`%n=rJwuvag{o+Li>XXBV`1z5rxhJk6Le!7&cCBrlwh+?Ft z4~vUa1*{9rUXornr^bJsikc;mhy1%cA;6pF4%}|pV-nL7`X-LsQ-M8u-&1}LM}FZZ z5wQdyI;q!97uuoty0}0(u;Xf8XwCP6f6cA6!LbL`#B_6+7Jr-}qEF66iElq}`io^L ztOG$MHgiJXb)|2!FM@>Agn(F|2O&WYprCs#xvIfX%~c<$BG6W}+GTofBJawN8YIde zM*p+b3ovnJQW@vu$`nHr?41C-c-ve)?(VwBXrWzm!bbb!F)46%n zMS+S>8>2s$Eqwks)bY<=T1R&13_u(cMPRnH1)qLn*!)qNcl{B^!~yk|W!P0kCnu+! zfdKt2Ai&QQKK`>qws+w-R2Z~aqivHEsd?yv22k(mO2L|QCEYLoyWQ77U!}}2N+wu_ zRzC2b7aLERJcW?{IZKehMIMsGf8PWBvbM7F6vF=BZ}$*n9{Sz}JVRvqXVm2HO~jX!pnpEviy|Qe`sXv>p8Gk2{<#VcF=!w3&k0_!gMK5t{O345 z(El}>_h|p$FO+q>|Kjh!yN8xd3d)Odqm&e-6-eX~F8{UXC+Ge>ngmDXU4r5^6}!9@9C^IRe1}dS&dv zFittvI3nY0{Ww8j57BZvaMn-DZ!X?)oT$6o)%#jMVm_yZwoqkmn0NxCPVV0nHqT51 zn<)#XJ6Uq-F0@-=Ghz{fuvAg$Ic=xhx=p(gS7j@&d|eNs*W^_bVLrbRCZ7vXL}!dk z#yC!1WWswlyj;qclvO(f%=Lt-3TvEZqgrf``_1w1Cb?mObn~WZP)Ht6Lx8>5a@V)l z`^Ow}H6Nq9_L8XW-LbA8q0!!H90723^0fuI@y8!|F(=A89(V;r&t=;uHd~ZkvuCVH zIO*Y$+cm=O!X4de1JF&I84b!9&+-|5NL-CR-`N+=$P_+gzw>V9;foUV2(C$AX=YH> z>nVwoGL{K?&6)(|41MKyA9-mRuw{sI=uPJw#XJgv2s=JSzM`Saf~P544C_+Tf!$9S zDM7?ZrJ{`Uu(6L;EadOsM;H%xpV48zKTUC&i6G}BXH!TMPASVg>Yq2sS*uhxXU~k( zEZqM}Zdwyoa-0#tCoM0Z`<=t!kxWV&{kgA);EWmA3pRuuzpJtCAn^W>d?2!fok5JP zqb)l4A1^>Z^bt|fP(!t6cEUwXcR^yf6M1*}O?s?@C@^r_l52+tBQdrp8Offj7s+p8 ziz3)1i_{y%x6DQuYtB}Vf`az7Jz&dzBm4I=o`UsI0T5>0*fuqb`6zvll6yy28Z||w z>53Sa3zclP`Wwe%ON<)1Uln%S2cAS(v%i{0Wsf%yUA)1u2fuY_Ou9+L1k=wFqd+JW zOW?G?HA>UYUt)S9_Agvg)0H2u=v`XU`&`Fg^=57DMcZK6Png`~brF0{nABd{eynz; zbh`Wf%;wRO@oFZD3PKm_kTChU4-okyXrsEz!vxd}SJKo41hRFA%vJ)g8a&yv!x>FlQ11a90kHru$Q1H!T z4&wVWyGvC;!1X599pRuoZo1^|L%d7WHertTgE@(VLkZ}W(MX?3{d%RKP`aW08lnH8 zaO}iFmBEEe+KM4%UU43g)Mut#ix=RS0s(UH)~|gjF9YKCc5%ep2mKY8zg~<*?&`HG z)`Pcrg*Vt~5^Vo+uAyVgr8pw+rW`Ym{rK0@H=etg_ee%_iD*m{6*TPhDW6yhJQRJ4 z!8`kptIzTdo3}{Hp#?4N_t!Qey=Q^9Kvegxd_QPb4ppKUoR<%6nf|$}p<-+%^~{g<|93+j8HL26v}Ap}rN1;8=Tz-bJMi^WI_-Gw8RNu+J)P1Y&de zc?r`Hiv^qT%sI}sH8+ij#1m_NbgBL=8Z={Gqvlc_7v60JnuWv2&$%N= z1;OY&jF<&Xg^?46%j-&OAG7wgo*?9?~(yMmlbjMNz-b;01E{go)*bOR}E^}W|8{PPzzo?EY6yxnn1 z7UcBgc-AUbN5Xng;?qler*y=-Qhc9-5poRN$Cg7>M7qUhl@81(y)w%2Oml!O6TQNUzRze(d zmF=R)EpggBadOo#C^xNmoM2EVBP5DQ=vk_9gPtkB9;u+?QBVdhY1m$c(lT}oYn~=| zKHf+rOHVo7djtHZjKT>LOCRq9uxFu@LCbKBycW#Rv#>eF=s3bie5=R+pAugA2x{S{ z=(efGNu}tEuDkzLVzU=Np7`gI4m8_VpB(eQ*n7*UD%-AYbO9ntiik*~Sb&rWNP|ds zcSwgcNQWSyNGKpBAtfD3r?hl;OLuqexo+?0{l0hacmLZzz8`yyp<@kYv99a9&U3~w zk9o{^wYCi$f7!=`n$KKsp@|&YEH5TPG&*VJx|O)c&@O55TQ<)84y zrqkv;FqFLq=A-)5p2(G*eA$+ALW}*Rg2Bz*^NMYJMfoOL8AX?H)%qsc(WwV-L^*rt zDp&VxdF#XO%~Af)0_d+2G0O4)InPJT@+*N)P~O`j|>U%345Tv`l7`NNXPyTe+zgqvmuN@E ze(ppDpU6>!ZjH-=JU~m8X8K;_MB%wn^h7y(QALVTs-b1HX>4?idUTrM7t@V*N$Fnq zsJy+_PI+tiuIW8DHtNJ?X-6a+uFkJM==wU(F=p3!9B+)Wf|-|#|Mt#>uIKQuhM?uc z=H<=gPjO#PhaL{n>JHW3?u#eK(!d~fJ4mSde5kOAKOv{39Z9P z!W}$0YB+mCj$k@3E3n>O4#@5&Tk|+f+`7Tm>R%N`|K^;dW6n2urZ1I1Jg{}EF?MRY zH*cuhSnyyc70vJ(VC_jkX?5_39MQb^;U{QTeg--UFFmj{k*^4dtSkn!LzxM;7n=ol=!KmQT_M8N5XvEs`k4n3n~-Umf0N5 zMW??Q%yzR^PRuu+7Sfj#PBdv*c}`{9p(4!8%~m^2iMP6^9wjD6ve>fhY|m>-13dWwLR~~y|T(I5rn5YbO<|1_+q^R4sWAAZP#OpC!`uTdTe+0*$k0bY>Qr7XZ zyArJ-@6;$R#D$@Rl^l)c(3rpCM~Melf83P(kcP}ovBZ)zWO4r4POJ$5aE%E z*S^s2yC{@Qy};O0iRN>(BFc%6ML!O+)((gekdK#3chE;9;#^;v@#*AoBe|$1_au7H zOoQ<9UV9N9q4;aG6SMhEJSsv_Wk9H8bFx*wVtjfM1%Z$tl{oTunH?P={Vm-#z=_bc zG{x=WcO0*Z@MWAlyJ}g@u(iJ8n}1hJ2#eVw?QmUPO zgx?C!Y!`7*!m(x9eWh=Vp1@c0e3Wze7u|2P7L`q^LraQEAFjKJ6?nOERc{L$B*&#% zy?^=>JbBV#!%l;xmcfypnJz3V!xxpY{7rK$$|TlGN|F39EydEY?|dLiMR&sIg?o8y z308aiYGxJZ^RaJ*>DWs11BjFa$4rx09kEk^t5sww%<>^V#ckc?W_TyI=#klgWWQ~8Z#ZLJWHX2^F->-!>8s0T(S;5=-LMlc>nQhUzxw}iz zuk^5`WA56@d`On@%}}>}Tv!cRjV%7i>NwJvTm5#{#exk>OfSi+ysg;JuT1Ch-g%Pa zt2CXeZMAX`TYeU;VR|?|u4os3o?(I9EZbXlLc^hJ-nWU(QeRdJw7d^5TqLvRu z7MXNIw&=^uqt#|2-H}tnd5mDenwOY5>#E7R;MPXrxp4bk75(<(oa@%=0*-_^ zmL@N;e}8GApJ_in*Tc~T4FaX+Dw>QjtvnA;)`#quyQxQA7NN1{`*YoeZ@%{LN)uY_ z(mC)g&6a-nRPJFB`K@jLu>8#Ijn|7ezQRZLgL3mdtAcdftr06e|pr)3s$K%0AY$ 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

    9=510=xV4bvgTV1@v*3QiL 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 literal 0 HcmV?d00001 diff --git a/spec/features/plantings/planting_a_crop_spec.rb b/spec/features/plantings/planting_a_crop_spec.rb index a3edec52f..d8fae5c64 100644 --- a/spec/features/plantings/planting_a_crop_spec.rb +++ b/spec/features/plantings/planting_a_crop_spec.rb @@ -3,6 +3,8 @@ require "spec_helper" feature "Planting a crop", :js => true do let(:member) { FactoryGirl.create(:member) } let!(:maize) { FactoryGirl.create(:maize) } + let(:garden) { FactoryGirl.create(:garden, owner: member) } + let(:planting) { FactoryGirl.create(:planting, garden: garden, planted_at: Date.parse("2013-3-10")) } background do login_as(member) @@ -82,5 +84,17 @@ feature "Planting a crop", :js => true do expect(page).to have_content "Finished: Yes (no date specified)" end + scenario "Marking a planting as finished from the show page" do + this_month = Date.today.strftime("%B") + this_year = Date.today.strftime("%Y") + visit planting_path(planting) + click_link "Mark as finished" + within "div.datepicker" do + expect(page).to have_content "#{this_month}" + page.find(".datepicker-days td.day", text: "21").click + end + expect(page).to have_content "Finished: #{this_month} 21, #{this_year}" + end + end From 3a46a5bd18eaa79ef5139ccb7965e737a82621ca Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Mon, 13 Oct 2014 06:48:46 +1100 Subject: [PATCH 217/288] add comments to javascripts --- app/assets/javascripts/append_date.js.coffee | 16 +++++++++++----- .../javascripts/finish_planting.js.coffee | 5 +++++ screenshot.png | Bin 81774 -> 0 bytes 3 files changed, 16 insertions(+), 5 deletions(-) delete mode 100644 screenshot.png diff --git a/app/assets/javascripts/append_date.js.coffee b/app/assets/javascripts/append_date.js.coffee index abccb9969..cf54f4654 100644 --- a/app/assets/javascripts/append_date.js.coffee +++ b/app/assets/javascripts/append_date.js.coffee @@ -1,12 +1,18 @@ -jQuery -> - - $('.append-date').datepicker({'format': 'yyyy-mm-dd'}) +# Displays datepicker to finished at date +# when marking a planting finished using a +# button. The button must have class 'append-date'. - $('.append-date').click (e) -> +jQuery -> + + el = $('.append-date') + + el.datepicker({'format': 'yyyy-mm-dd'}) + + el.click (e) -> e.stopPropagation() e.preventDefault() - $('.append-date').one 'changeDate', -> + el.one 'changeDate', -> href = $(this).attr('href') date = $(this).datepicker('getDate') url = "#{href}&planting[finished_at]=#{date}" diff --git a/app/assets/javascripts/finish_planting.js.coffee b/app/assets/javascripts/finish_planting.js.coffee index de640311c..29b435ecf 100644 --- a/app/assets/javascripts/finish_planting.js.coffee +++ b/app/assets/javascripts/finish_planting.js.coffee @@ -1,3 +1,8 @@ +# Clears the finished at date field when +# a planting is marked unfinished, and +# repopulates the field with a cached value +# marking unfinshed is undone. + jQuery -> previousValue = '' $('#planting_finished').on('click', -> diff --git a/screenshot.png b/screenshot.png deleted file mode 100644 index 426d1fe4f1038d3bd8932829c02f6b01cdb2cbc2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81774 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%0AY$ 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

    9=510=xV4bvgTV1@v*3QiL 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 From 6bcb9dae4abf41aaf61fc239abe0ef942f89f528 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Wed, 15 Oct 2014 06:41:28 +1100 Subject: [PATCH 218/288] trigger datepicker when planting finished checkbox is ticked --- Gemfile | 5 ++++- app/assets/javascripts/finish_planting.js.coffee | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 93fd9858d..d789f6f0e 100644 --- a/Gemfile +++ b/Gemfile @@ -83,6 +83,10 @@ group :development do # Installation of the debugger gem fails on Travis CI, # so we don't use it in the test environment gem 'debugger' + # A debugger and irb alternative. Pry doesn't play nice + # with unicorn, so start a Webrick server when debugging + # with Pry + gem 'pry' gem 'better_errors' gem 'binding_of_caller' gem 'letter_opener' @@ -119,7 +123,6 @@ gem 'omniauth-flickr', '>= 0.0.15' gem 'rake', '>= 10.0.0' group :development, :test do - gem 'pry' gem 'haml-rails' # HTML templating language gem 'rspec-rails', '~> 2.12.1' # unit testing framework gem 'database_cleaner', '~> 1.3.0' diff --git a/app/assets/javascripts/finish_planting.js.coffee b/app/assets/javascripts/finish_planting.js.coffee index 29b435ecf..b8139b7fe 100644 --- a/app/assets/javascripts/finish_planting.js.coffee +++ b/app/assets/javascripts/finish_planting.js.coffee @@ -1,7 +1,7 @@ # Clears the finished at date field when # a planting is marked unfinished, and # repopulates the field with a cached value -# marking unfinshed is undone. +# marking unfinished is undone. jQuery -> previousValue = '' @@ -10,7 +10,9 @@ jQuery -> if @checked if previousValue.length date = previousValue - finished.val(date) + finished.val(date) + else + finished.trigger('focus') else previousValue = finished.val() finished.val('') From 25cb3fd469426b7f9b9023922cf4e4c2163f8528 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 15 Oct 2014 13:58:21 +0100 Subject: [PATCH 219/288] Fixed scientific name upload bug and wrote tests Wrote unit tests and refactored Crop.create_from_csv The actual bug (now fixed) is that if you didn't specify a SN in the CSV, it would try and pick it up from the parent crop, but then the testing for "is this a dup?" was based on what was in the CSV and didn't take that parent SN into account. This is now fixed. --- app/models/crop.rb | 71 +++++++++++++------------ db/seeds.rb | 2 +- spec/factories/member.rb | 5 ++ spec/models/crop_spec.rb | 110 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 153 insertions(+), 35 deletions(-) diff --git a/app/models/crop.rb b/app/models/crop.rb index 3efe8be57..e4225f7b4 100644 --- a/app/models/crop.rb +++ b/app/models/crop.rb @@ -127,52 +127,55 @@ class Crop < ActiveRecord::Base # - en_wikipedia_url (required) # - parent (name, optional) - def Crop.create_from_csv(row, definitely_new=false) + def Crop.create_from_csv(row) name,scientific_name,en_wikipedia_url,parent = row - @cropbot = Member.find_by_login_name('cropbot') - raise "cropbot account not found: run rake db:seed" unless @cropbot + cropbot = Member.find_by_login_name('cropbot') + raise "cropbot account not found: run rake db:seed" unless cropbot + + crop = Crop.find_or_create_by_name(name) + crop.update_attributes( + :en_wikipedia_url => en_wikipedia_url, + :creator_id => cropbot.id + ) - if definitely_new then - @crop = Crop.create( - :name => name, - :en_wikipedia_url => en_wikipedia_url, - :creator_id => @cropbot.id - ) - else - @crop = Crop.find_or_create_by_name(name) - @crop.update_attributes( - :en_wikipedia_url => en_wikipedia_url, - :creator_id => @cropbot.id - ) - end if parent - @parent = Crop.find_by_name(parent) - if @parent - @crop.update_attributes(:parent_id => @parent.id) + parent = Crop.find_by_name(parent) + if parent + crop.update_attributes(:parent_id => parent.id) else logger.warn("Warning: parent crop #{parent} not found") end end - unless @crop.scientific_names.exists?(:scientific_name => scientific_name) - @sn = '' - if scientific_name - @sn = scientific_name - elsif @crop.parent - @sn = @crop.parent.scientific_names.first.scientific_name - end + crop.add_scientific_name_from_csv(scientific_name) - if @sn - @crop.scientific_names.create( - :scientific_name => @sn, - :creator_id => @cropbot.id - ) - else - logger.warn("Warning: no scientific name (not even on parent crop) for #{@crop}") - end + end + def add_scientific_name_from_csv(scientific_name) + name_to_add = nil + if ! scientific_name.blank? # i.e. we actually passed one in, which isn't a given + name_to_add = scientific_name + elsif parent && parent.default_scientific_name + name_to_add = parent.default_scientific_name + else + logger.warn("Warning: no scientific name (not even on parent crop) for #{self}") end + + if name_to_add + if scientific_names.exists?(:scientific_name => name_to_add) + logger.warn("Warning: skipping duplicate scientific name #{name_to_add} for #{self}") + else + cropbot = Member.find_by_login_name('cropbot') + raise "cropbot account not found: run rake db:seed" unless cropbot + + scientific_names.create( + :scientific_name => name_to_add, + :creator_id => cropbot.id + ) + end + end + end # Crop.search(string) diff --git a/db/seeds.rb b/db/seeds.rb index 5dd1245b6..2aa4fd12b 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -29,7 +29,7 @@ def load_crops Dir.glob("#{source_path}/crops*.csv").each do |crop_file| puts "Loading crops from #{crop_file}..." CSV.foreach(crop_file) do |row| - Crop.create_from_csv(row, definitely_new=true) + Crop.create_from_csv(row) end end puts "Finished loading crops" diff --git a/spec/factories/member.rb b/spec/factories/member.rb index bb4523a33..b8b23a58a 100644 --- a/spec/factories/member.rb +++ b/spec/factories/member.rb @@ -11,6 +11,11 @@ FactoryGirl.define do show_email false bio 'I love seeds' + # cropbot is needed for certain tests, eg. Crop.create_from_csv + factory :cropbot do + login_name 'cropbot' + end + factory :no_tos_member do tos_agreement false end diff --git a/spec/models/crop_spec.rb b/spec/models/crop_spec.rb index 1741faf74..30cb34802 100644 --- a/spec/models/crop_spec.rb +++ b/spec/models/crop_spec.rb @@ -343,4 +343,114 @@ describe Crop do end end + context "csv loading" do + + before(:each) do + # don't use 'let' for this -- we need to actually create it, + # regardless of whether it's used. + @cropbot = FactoryGirl.create(:cropbot) + end + + it "adds a scientific name to a crop that has none" do + tomato = FactoryGirl.create(:tomato) + expect(tomato.scientific_names.size).to eq 0 + tomato.add_scientific_name_from_csv("Foo bar") + expect(tomato.scientific_names.size).to eq 1 + expect(tomato.default_scientific_name).to eq "Foo bar" + end + + it "picks up scientific name from parent crop if available" do + parent = FactoryGirl.create(:crop, :name => 'parent crop') + parent.add_scientific_name_from_csv("Parentis cropis") + parent.save + parent.reload + + tomato = FactoryGirl.create(:tomato, :parent => parent) + expect(tomato.parent).to eq parent + expect(tomato.parent.default_scientific_name).to eq "Parentis cropis" + + tomato.add_scientific_name_from_csv('') + expect(tomato.default_scientific_name).to eq "Parentis cropis" + end + + it "doesn't add a duplicate scientific name" do + tomato = FactoryGirl.create(:tomato) + expect(tomato.scientific_names.size).to eq 0 + tomato.add_scientific_name_from_csv("Foo bar") + expect(tomato.scientific_names.size).to eq 1 + tomato.add_scientific_name_from_csv("Foo bar") + expect(tomato.scientific_names.size).to eq 1 # shouldn't increase + tomato.add_scientific_name_from_csv("Baz quux") + expect(tomato.scientific_names.size).to eq 2 + end + + it "doesn't add a duplicate scientific name from parent" do + parent = FactoryGirl.create(:crop, :name => 'parent') + parent.add_scientific_name_from_csv("Parentis cropis") + parent.save + parent.reload + + tomato = FactoryGirl.create(:tomato, :parent => parent) + expect(tomato.scientific_names.size).to eq 0 + tomato.add_scientific_name_from_csv('') + expect(tomato.scientific_names.size).to eq 1 # picks up parent SN + tomato.add_scientific_name_from_csv('') + expect(tomato.scientific_names.size).to eq 1 # shouldn't increase now + end + + + it "loads the simplest possible crop" do + tomato_row = "tomato,,http://en.wikipedia.org/wiki/Tomato" + + CSV.parse(tomato_row) do |row| + Crop.create_from_csv(row) + end + + loaded = Crop.last + expect(loaded.name).to eq "tomato" + expect(loaded.en_wikipedia_url).to eq 'http://en.wikipedia.org/wiki/Tomato' + expect(loaded.creator).to eq @cropbot + end + + it "loads a crop with a scientific name" do + tomato_row = "tomato,Solanum lycopersicum,http://en.wikipedia.org/wiki/Tomato" + + CSV.parse(tomato_row) do |row| + Crop.create_from_csv(row) + end + + loaded = Crop.last + expect(loaded.name).to eq "tomato" + expect(loaded.scientific_names.size).to eq 1 + expect(loaded.scientific_names.last.scientific_name).to eq "Solanum lycopersicum" + end + + it "loads a crop with a parent" do + parent = FactoryGirl.create(:crop, :name => 'parent') + tomato_row = "tomato,,http://en.wikipedia.org/wiki/Tomato,parent" + + CSV.parse(tomato_row) do |row| + Crop.create_from_csv(row) + end + + loaded = Crop.last + expect(loaded.parent).to eq parent + end + + it "doesn't add unnecessary duplicate crops" do + tomato_row = "tomato,Solanum lycopersicum,http://en.wikipedia.org/wiki/Tomato" + + CSV.parse(tomato_row) do |row| + Crop.create_from_csv(row) + end + + loaded = Crop.last + expect(loaded.name).to eq "tomato" + expect(loaded.en_wikipedia_url).to eq 'http://en.wikipedia.org/wiki/Tomato' + expect(loaded.creator).to eq @cropbot + + end + + end + end From 1d81064ced34b2f11eda9dfd51bbd4e9474ac111 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 15 Oct 2014 14:03:00 +0100 Subject: [PATCH 220/288] removed unused Crop.random method --- app/models/crop.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/crop.rb b/app/models/crop.rb index e4225f7b4..0188c7955 100644 --- a/app/models/crop.rb +++ b/app/models/crop.rb @@ -30,10 +30,10 @@ class Crop < ActiveRecord::Base :message => 'is not a valid English Wikipedia URL' } - def Crop.random - @crop = Crop.offset(rand(Crop.count)).first - return @crop - end +# def Crop.random +# @crop = Crop.offset(rand(Crop.count)).first +# return @crop +#aend def to_s return name From 8e6a57c4429eac88ab934f422ab11bf16b0a7663 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 15 Oct 2014 15:52:13 +0100 Subject: [PATCH 221/288] Attempt at writing tests for planting reminder email (Test are broken) OK, so I decided to use the capybara_email gem to write these tests. It's basically working but there's a problem with the has_link matcher. Basically the emails sent by ActionMailer have URLs like http://localhost:8080... whereas the tests asking for planting_url() or similar are looking for http://example.com... I don't understand AT ALL why there is this discrepancy, but it looks like ActionMailer is using the settings from the development environment, instead of the testing environment. WHYYYYYY??? I'm pushing this up so that maybe someone else can figure it out, because I'm stumped. --- Gemfile | 1 + Gemfile.lock | 4 ++ spec/features/planting_reminder_spec.rb | 57 +++++++++++++++++++++++++ spec/spec_helper.rb | 2 + 4 files changed, 64 insertions(+) create mode 100644 spec/features/planting_reminder_spec.rb diff --git a/Gemfile b/Gemfile index e47403546..8428c63f3 100644 --- a/Gemfile +++ b/Gemfile @@ -126,5 +126,6 @@ group :development, :test do gem 'factory_girl_rails', '~> 4.0' # for creating test data gem 'coveralls', require: false # coverage analysis gem 'capybara' # integration tests + gem 'capybara-email' # integration tests for email gem 'poltergeist', '~> 1.5.1' # for headless JS testing end diff --git a/Gemfile.lock b/Gemfile.lock index 7829a34de..9127443fb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -67,6 +67,9 @@ GEM rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (~> 2.0) + capybara-email (2.4.0) + capybara (~> 2.4) + mail chunky_png (1.3.1) cliver (0.3.2) coderay (1.1.0) @@ -308,6 +311,7 @@ DEPENDENCIES bundler (>= 1.1.5) cancan capybara + capybara-email coffee-rails (~> 3.2.1) compass-rails (~> 1.0.3) coveralls diff --git a/spec/features/planting_reminder_spec.rb b/spec/features/planting_reminder_spec.rb new file mode 100644 index 000000000..849b971e5 --- /dev/null +++ b/spec/features/planting_reminder_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' +require 'capybara/email/rspec' + +feature "Planting reminder email" do + let(:member) { FactoryGirl.create(:member) } + let(:mail) { Notifier.planting_reminder(member) } + + scenario "has a greeting" do + expect(mail).to have_content "Hello" + end + + context "when member has no plantings" do + let(:member) { FactoryGirl.create(:member) } + let(:mail) { Notifier.planting_reminder(member) } + + scenario "tells you to tracking plantings" do + expect(mail).to have_content "planting your first crop" + end + + scenario "doesn't list plantings" do + expect(mail).not_to have_content "most recent plantings you've told us about" + end + + end + + context "when member has some plantings" do + let(:member) { FactoryGirl.create(:member) } + let(:mail) { Notifier.planting_reminder(member) } + + before :each do + @p1 = FactoryGirl.create(:planting, + :garden => member.gardens.first, + :owner => member + ) + @p2 = FactoryGirl.create(:planting, + :garden => member.gardens.first, + :owner => member + ) + end + + scenario "lists plantings" do + puts Rails.env + puts planting_url(@p1) + expect(mail).to have_content "most recent plantings you've told us about" + expect(mail).to have_link @p1.to_s, :href => planting_url(@p1) + expect(mail).to have_link @p2.to_s, :href => planting_url(@p2) + expect(mail).to have_content "keep your garden records up to date" + end + end + + context "when member has no harvests" do + end + + context "when member has some harvests" do + end + +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9af45a394..f77c8b267 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -25,6 +25,8 @@ end require 'capybara' require 'capybara/poltergeist' Capybara.javascript_driver = :poltergeist +Capybara.app_host = 'http://localhost' +Capybara.server_port = 8080 include Warden::Test::Helpers From a3e02a3e61bc409b7773cdabe37bead680f66f6f Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 15 Oct 2014 16:47:43 +0100 Subject: [PATCH 222/288] Actually delete Crop.random, don't just comment it out --- app/models/crop.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/models/crop.rb b/app/models/crop.rb index 3ae678a9e..f1d5dcc1d 100644 --- a/app/models/crop.rb +++ b/app/models/crop.rb @@ -33,11 +33,6 @@ class Crop < ActiveRecord::Base :message => 'is not a valid English Wikipedia URL' } -# def Crop.random -# @crop = Crop.offset(rand(Crop.count)).first -# return @crop -#aend - def to_s return name end From 6a7c935d43e8de4e818ff9435f4adb420cd1aa79 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Thu, 16 Oct 2014 06:37:02 +1100 Subject: [PATCH 223/288] try setting locale from query parameter first --- app/controllers/application_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 1b27f01fc..e417984fa 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -35,10 +35,10 @@ class ApplicationController < ActionController::Base end def set_locale - I18n.locale = extract_locale_tld || I18n.default_locale + I18n.locale = params[:locale] || extract_locale_from_subdomain || I18n.default_locale end - def extract_locale_tld + def extract_locale_from_subdomain parsed_locale = request.subdomains.first I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil end From 0f75a969447e087b0c8607cd18854551bc0264f0 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Thu, 16 Oct 2014 06:37:50 +1100 Subject: [PATCH 224/288] write spec to test that setting a different locale fundamentally works --- spec/features/locale_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 spec/features/locale_spec.rb diff --git a/spec/features/locale_spec.rb b/spec/features/locale_spec.rb new file mode 100644 index 000000000..bffedfb75 --- /dev/null +++ b/spec/features/locale_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' + +feature "Changing locales" do + + scenario "Locale can be set with a query param" do + visit "/" + expect(page).to have_content("Growstuff (dev) is a community of food gardeners.") + visit "/?locale=ja" + expect(page).to have_content("Growstuff (dev)はガーデナーのコミュニティです。") + end + +end \ No newline at end of file From d627d8394e2c3584a366016d30e0084beec4e117 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Thu, 16 Oct 2014 06:56:39 +1100 Subject: [PATCH 225/288] forgot which env I was in --- spec/features/locale_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/locale_spec.rb b/spec/features/locale_spec.rb index bffedfb75..8e7aad90c 100644 --- a/spec/features/locale_spec.rb +++ b/spec/features/locale_spec.rb @@ -4,9 +4,9 @@ feature "Changing locales" do scenario "Locale can be set with a query param" do visit "/" - expect(page).to have_content("Growstuff (dev) is a community of food gardeners.") + expect(page).to have_content("Growstuff (test) is a community of food gardeners.") visit "/?locale=ja" - expect(page).to have_content("Growstuff (dev)はガーデナーのコミュニティです。") + expect(page).to have_content("Growstuff (test)はガーデナーのコミュニティです。") end end \ No newline at end of file From 8994bcf28cf98728f190de6eb9f2bc46a39bf75b Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Thu, 16 Oct 2014 07:14:03 +1100 Subject: [PATCH 226/288] ok, we'll just remove all references to environment --- spec/features/locale_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/locale_spec.rb b/spec/features/locale_spec.rb index 8e7aad90c..37f1b9905 100644 --- a/spec/features/locale_spec.rb +++ b/spec/features/locale_spec.rb @@ -4,9 +4,9 @@ feature "Changing locales" do scenario "Locale can be set with a query param" do visit "/" - expect(page).to have_content("Growstuff (test) is a community of food gardeners.") + expect(page).to have_content("a community of food gardeners.") visit "/?locale=ja" - expect(page).to have_content("Growstuff (test)はガーデナーのコミュニティです。") + expect(page).to have_content("はガーデナーのコミュニティです。") end end \ No newline at end of file From 2515d34277a3420d71d25ab0553cc53b3e3c752b Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Thu, 16 Oct 2014 08:09:20 +1100 Subject: [PATCH 227/288] add teardown to reset locale to default en --- spec/features/locale_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/features/locale_spec.rb b/spec/features/locale_spec.rb index 37f1b9905..9a787f6cc 100644 --- a/spec/features/locale_spec.rb +++ b/spec/features/locale_spec.rb @@ -2,6 +2,10 @@ require 'spec_helper' feature "Changing locales" do + after do + I18n.locale = :en + end + scenario "Locale can be set with a query param" do visit "/" expect(page).to have_content("a community of food gardeners.") From e85cb4598db71803b76ca093f216c28181f0f603 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sat, 18 Oct 2014 12:38:44 +0100 Subject: [PATCH 228/288] Add alternate name model. --- app/models/alternate_name.rb | 5 +++++ .../20141018111015_create_alternate_names.rb | 11 +++++++++++ db/schema.rb | 11 ++++++++++- spec/factories/alternate_names.rb | 14 ++++++++++++++ spec/models/alternate_name_spec.rb | 11 +++++++++++ 5 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 app/models/alternate_name.rb create mode 100644 db/migrate/20141018111015_create_alternate_names.rb create mode 100644 spec/factories/alternate_names.rb create mode 100644 spec/models/alternate_name_spec.rb diff --git a/app/models/alternate_name.rb b/app/models/alternate_name.rb new file mode 100644 index 000000000..fb92a2bc9 --- /dev/null +++ b/app/models/alternate_name.rb @@ -0,0 +1,5 @@ +class AlternateName < ActiveRecord::Base + attr_accessible :crop_id, :name, :creator_id + belongs_to :crop + belongs_to :creator, :class_name => 'Member' +end diff --git a/db/migrate/20141018111015_create_alternate_names.rb b/db/migrate/20141018111015_create_alternate_names.rb new file mode 100644 index 000000000..8a63471c8 --- /dev/null +++ b/db/migrate/20141018111015_create_alternate_names.rb @@ -0,0 +1,11 @@ +class CreateAlternateNames < ActiveRecord::Migration + def change + create_table :alternate_names do |t| + t.string :name, :null => false + t.integer :crop_id, :null => false + t.integer :creator_id, :null => false + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 9292a6fbb..39a68cab2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20141002022459) do +ActiveRecord::Schema.define(:version => 20141018111015) do create_table "account_types", :force => true do |t| t.string "name", :null => false @@ -29,6 +29,14 @@ ActiveRecord::Schema.define(:version => 20141002022459) do t.datetime "updated_at", :null => false end + create_table "alternate_names", :force => true do |t| + t.string "name", :null => false + t.integer "crop_id", :null => false + t.integer "creator_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + create_table "authentications", :force => true do |t| t.integer "member_id", :null => false t.string "provider", :null => false @@ -154,6 +162,7 @@ ActiveRecord::Schema.define(:version => 20141002022459) do t.text "bio" t.integer "plantings_count" t.boolean "newsletter" + t.boolean "send_planting_reminder", :default => true end add_index "members", ["confirmation_token"], :name => "index_users_on_confirmation_token", :unique => true diff --git a/spec/factories/alternate_names.rb b/spec/factories/alternate_names.rb new file mode 100644 index 000000000..72c73d99c --- /dev/null +++ b/spec/factories/alternate_names.rb @@ -0,0 +1,14 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :alternate_name do + association :crop, factory: :crop + name "alternate name" + creator + + factory :alternate_tomato do + association :crop, factory: :tomato + name "alternative tomato" + end + end +end diff --git a/spec/models/alternate_name_spec.rb b/spec/models/alternate_name_spec.rb new file mode 100644 index 000000000..f91d6dc1f --- /dev/null +++ b/spec/models/alternate_name_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe AlternateName do + before (:each) do + @an = FactoryGirl.create(:alternate_tomato) + end + + it 'should save a basic alternate name' do + @an.save.should be_true + end +end From 83fa291060dc005b9965c1717c2c6c4bb1a628c6 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sat, 18 Oct 2014 13:49:57 +0100 Subject: [PATCH 229/288] Fetch alternate names for a crop. --- app/models/crop.rb | 1 + spec/models/alternate_name_spec.rb | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/app/models/crop.rb b/app/models/crop.rb index 387936d91..fa99ed90c 100644 --- a/app/models/crop.rb +++ b/app/models/crop.rb @@ -8,6 +8,7 @@ class Crop < ActiveRecord::Base :allow_destroy => true, :reject_if => :all_blank + has_many :alternate_names has_many :plantings has_many :photos, :through => :plantings has_many :seeds diff --git a/spec/models/alternate_name_spec.rb b/spec/models/alternate_name_spec.rb index f91d6dc1f..e7772dca0 100644 --- a/spec/models/alternate_name_spec.rb +++ b/spec/models/alternate_name_spec.rb @@ -8,4 +8,17 @@ describe AlternateName do it 'should save a basic alternate name' do @an.save.should be_true end + + it 'should be possible to add multiple alternate names to a crop' do + crop = @an.crop + an2 = AlternateName.create( + :name => "really alternative tomato", + :crop_id => crop.id, + :creator_id => @an.creator.id + ) + crop.alternate_names << an2 + crop.alternate_names.should include @an + crop.alternate_names.should include an2 + end + end From 9ededef54d6b770b7c308035c6946d53ee4c9590 Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 18 Oct 2014 14:00:22 +0100 Subject: [PATCH 230/288] Added tests for content of planting reminder email --- Gemfile.lock | 41 +++++++++-------- .../harvests/harvesting_a_crop_spec.rb | 3 +- spec/features/planting_reminder_spec.rb | 44 ++++++++++++++----- .../plantings/planting_a_crop_spec.rb | 2 +- 4 files changed, 55 insertions(+), 35 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 53b817bdb..32d80d3c3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -61,7 +61,7 @@ GEM railties (>= 3.0) builder (3.0.4) cancan (1.6.10) - capybara (2.4.1) + capybara (2.4.4) mime-types (>= 1.16) nokogiri (>= 1.3.3) rack (>= 1.0.0) @@ -70,7 +70,7 @@ GEM capybara-email (2.4.0) capybara (~> 2.4) mail - chunky_png (1.3.1) + chunky_png (1.3.2) cliver (0.3.2) coderay (1.1.0) coffee-rails (3.2.2) @@ -118,15 +118,14 @@ GEM thread thread_safe erubis (2.7.0) - execjs (2.2.1) - factory_girl (4.4.0) + execjs (2.2.2) + factory_girl (4.5.0) activesupport (>= 3.0.0) - factory_girl_rails (4.4.1) - factory_girl (~> 4.4.0) + factory_girl_rails (4.5.0) + factory_girl (~> 4.5.0) railties (>= 3.0.0) - figaro (0.7.0) - bundler (~> 1.0) - rails (>= 3, < 5) + figaro (1.0.0) + thor (~> 0.14) flickraw (0.9.8) friendly_id (4.0.10.1) activerecord (>= 3.0, < 4.0) @@ -151,7 +150,7 @@ GEM multi_json (~> 1.0) multi_xml (>= 0.5.2) i18n (0.6.1) - i18n-tasks (0.7.6) + i18n-tasks (0.7.7) activesupport easy_translate (>= 0.5.0) erubis @@ -161,7 +160,7 @@ GEM term-ansicolor terminal-table journey (1.0.4) - jquery-rails (3.1.1) + jquery-rails (3.1.2) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) jquery-ui-rails (4.1.2) @@ -175,7 +174,7 @@ GEM addressable (~> 2.3) leaflet-markercluster-rails (0.7.0) railties (>= 3.1) - leaflet-rails (0.7.3) + leaflet-rails (0.7.4) less (2.5.1) commonjs (~> 0.2.7) less-rails (2.5.0) @@ -195,8 +194,8 @@ GEM mini_portile (0.6.0) multi_json (1.10.1) multi_xml (0.5.5) - netrc (0.7.7) - newrelic_rpm (3.9.3.241) + netrc (0.8.0) + newrelic_rpm (3.9.5.251) nokogiri (1.6.3.1) mini_portile (= 0.6.0) oauth (0.4.7) @@ -208,7 +207,7 @@ GEM omniauth-oauth (1.0.1) oauth omniauth (~> 1.0) - omniauth-twitter (1.0.1) + omniauth-twitter (1.1.0) multi_json (~> 1.3) omniauth-oauth (~> 1.0) orm_adapter (0.5.0) @@ -219,7 +218,7 @@ GEM multi_json (~> 1.0) websocket-driver (>= 0.2.0) polyglot (0.3.5) - pry (0.10.0) + pry (0.10.1) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) @@ -238,7 +237,7 @@ GEM activesupport (= 3.2.13) bundler (~> 1.0) railties (= 3.2.13) - rails_12factor (0.0.2) + rails_12factor (0.0.3) rails_serve_static_assets rails_stdout_logging rails_serve_static_assets (0.0.2) @@ -274,9 +273,9 @@ GEM railties (~> 3.2.0) sass (>= 3.1.10) tilt (~> 1.3) - simplecov (0.9.0) + simplecov (0.9.1) docile (~> 1.1.0) - multi_json + multi_json (~> 1.0) simplecov-html (~> 0.8.0) simplecov-html (0.8.0) slop (3.6.0) @@ -297,7 +296,7 @@ GEM thread (0.1.4) thread_safe (0.3.4) tilt (1.4.1) - tins (1.3.2) + tins (1.3.3) treetop (1.4.15) polyglot polyglot (>= 0.3.1) @@ -315,7 +314,7 @@ GEM nokogiri (>= 1.2.0) rack (>= 1.0) rack-test (>= 0.5.3) - websocket-driver (0.3.4) + websocket-driver (0.3.5) will_paginate (3.0.7) xpath (2.0.0) nokogiri (~> 1.3) diff --git a/spec/features/harvests/harvesting_a_crop_spec.rb b/spec/features/harvests/harvesting_a_crop_spec.rb index 7a366309a..0feb29618 100644 --- a/spec/features/harvests/harvesting_a_crop_spec.rb +++ b/spec/features/harvests/harvesting_a_crop_spec.rb @@ -55,4 +55,5 @@ feature "Harvesting a crop", :js => true do end -end \ No newline at end of file +end + diff --git a/spec/features/planting_reminder_spec.rb b/spec/features/planting_reminder_spec.rb index 849b971e5..5adb44095 100644 --- a/spec/features/planting_reminder_spec.rb +++ b/spec/features/planting_reminder_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require 'capybara/email/rspec' -feature "Planting reminder email" do +feature "Planting reminder email", :js => true do let(:member) { FactoryGirl.create(:member) } let(:mail) { Notifier.planting_reminder(member) } @@ -10,10 +10,7 @@ feature "Planting reminder email" do end context "when member has no plantings" do - let(:member) { FactoryGirl.create(:member) } - let(:mail) { Notifier.planting_reminder(member) } - - scenario "tells you to tracking plantings" do + scenario "tells you to track your plantings" do expect(mail).to have_content "planting your first crop" end @@ -24,9 +21,6 @@ feature "Planting reminder email" do end context "when member has some plantings" do - let(:member) { FactoryGirl.create(:member) } - let(:mail) { Notifier.planting_reminder(member) } - before :each do @p1 = FactoryGirl.create(:planting, :garden => member.gardens.first, @@ -39,19 +33,45 @@ feature "Planting reminder email" do end scenario "lists plantings" do - puts Rails.env - puts planting_url(@p1) expect(mail).to have_content "most recent plantings you've told us about" - expect(mail).to have_link @p1.to_s, :href => planting_url(@p1) - expect(mail).to have_link @p2.to_s, :href => planting_url(@p2) + 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_content "keep your garden records up to date" end end context "when member has no harvests" do + scenario "tells you to tracking plantings" do + expect(mail).to have_content "Get started now by tracking your first harvest" + end + + scenario "doesn't list plantings" do + expect(mail).not_to have_content "the last few things you harvested were" + end + end context "when member has some harvests" do + before :each do + @h1 = FactoryGirl.create(:harvest, + :owner => member + ) + @h2 = FactoryGirl.create(:harvest, + :owner => member + ) + end + + 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_content "Harvested anything else lately?" + end + end end diff --git a/spec/features/plantings/planting_a_crop_spec.rb b/spec/features/plantings/planting_a_crop_spec.rb index d8fae5c64..be5fc1142 100644 --- a/spec/features/plantings/planting_a_crop_spec.rb +++ b/spec/features/plantings/planting_a_crop_spec.rb @@ -65,7 +65,7 @@ feature "Planting a crop", :js => true do # The finished at date was cached in Javascript in # case the user clicks unfinished accidentally. expect(page.find("#planting_finished_at").value).to eq("2014-08-30") - + within "form#new_planting" do click_button "Save" end From 627587ee1b5468bf719e978a850b85be4627bd5a Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sat, 18 Oct 2014 14:19:09 +0100 Subject: [PATCH 231/288] Show alternate names on crop page --- app/views/crops/show.html.haml | 6 ++++++ spec/factories/alternate_names.rb | 8 ++++---- spec/factories/crop.rb | 3 +++ spec/features/crop_spec.rb | 10 ++++++++++ 4 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 spec/features/crop_spec.rb diff --git a/app/views/crops/show.html.haml b/app/views/crops/show.html.haml index 670a0d438..b031ec5b2 100644 --- a/app/views/crops/show.html.haml +++ b/app/views/crops/show.html.haml @@ -72,6 +72,12 @@ - if can? :edit, @crop = link_to 'Add', new_scientific_name_path( :crop_id => @crop.id ), { :class => 'btn btn-default btn-xs' } + %h4 Alternate names + %ul + - @crop.alternate_names.each do |an| + %li + = an.name + = render :partial => 'varieties', :locals => { :crop => @crop } = render :partial => 'grown_for', :locals => { :crop => @crop } diff --git a/spec/factories/alternate_names.rb b/spec/factories/alternate_names.rb index 72c73d99c..1c14d60ac 100644 --- a/spec/factories/alternate_names.rb +++ b/spec/factories/alternate_names.rb @@ -2,13 +2,13 @@ FactoryGirl.define do factory :alternate_name do - association :crop, factory: :crop name "alternate name" + crop creator - factory :alternate_tomato do - association :crop, factory: :tomato - name "alternative tomato" + factory :alternate_eggplant do + association :crop, factory: :eggplant + name "aubergine" end end end diff --git a/spec/factories/crop.rb b/spec/factories/crop.rb index 975e8d323..c6839e983 100644 --- a/spec/factories/crop.rb +++ b/spec/factories/crop.rb @@ -40,6 +40,9 @@ FactoryGirl.define do name "popcorn" end + factory :eggplant do + name "eggplant" + end # This should have a name that is alphabetically earlier than :uppercase # crop to ensure that the ordering tests work. diff --git a/spec/features/crop_spec.rb b/spec/features/crop_spec.rb new file mode 100644 index 000000000..9a2bb273b --- /dev/null +++ b/spec/features/crop_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' + +feature "Alternate names" do + let(:alternate_eggplant) { FactoryGirl.create(:alternate_eggplant) } + + scenario "Display alternate names on crop page" do + visit crop_path(alternate_eggplant.crop) + expect(page).to have_content alternate_eggplant.name + end +end From ae27a0a5b56a8c481e852346500a239959f79a7a Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 18 Oct 2014 14:38:00 +0100 Subject: [PATCH 232/288] Added some additional hierarchy around cherry tomatoes --- db/seeds/crops-11-tomatoes.csv | 78 +++++++++++++++++----------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/db/seeds/crops-11-tomatoes.csv b/db/seeds/crops-11-tomatoes.csv index eb28aaa79..acb39a039 100644 --- a/db/seeds/crops-11-tomatoes.csv +++ b/db/seeds/crops-11-tomatoes.csv @@ -1,41 +1,41 @@ -adoration tomato,,https://en.wikipedia.org/wiki/Adoration_%28Tomato%29,tomato, -alicante tomato,,https://en.wikipedia.org/wiki/Alicante_%28tomato%29,tomato, -Amish paste tomato,,http://en.wikipedia.org/wiki/Amish_Paste,tomato, -azoychka tomato,,https://en.wikipedia.org/wiki/Azoychka%28Tomato%29,tomato, -beefsteak tomato,,https://en.wikipedia.org/wiki/Beefsteak_(tomato),tomato, -better boy tomato,,https://en.wikipedia.org/wiki/Better_Boy,tomato, -big rainbow tomato,,https://en.wikipedia.org/wiki/Big_Rainbow_(Tomato),tomato, -Blaby special tomato,,https://en.wikipedia.org/wiki/Blaby_Special_(Tomato),tomato, -black krim tomato,,https://en.wikipedia.org/wiki/Black_Krim_%28tomato%29,tomato, -brandywine tomato,,https://en.wikipedia.org/wiki/Brandywine_(tomato),tomato, -campari tomato,,https://en.wikipedia.org/wiki/Campari_tomato,tomato, -Cherokee purple tomato,,https://en.wikipedia.org/wiki/Cherokee_purple,tomato, +adoration tomato,,https://en.wikipedia.org/wiki/Adoration_%28Tomato%29,tomato +alicante tomato,,https://en.wikipedia.org/wiki/Alicante_%28tomato%29,tomato +Amish paste tomato,,http://en.wikipedia.org/wiki/Amish_Paste,tomato +Aunt Ruby's German green tomato,,http://en.wikipedia.org/wiki/Aunt_Ruby%27s_German_Green,tomato +azoychka tomato,,https://en.wikipedia.org/wiki/Azoychka%28Tomato%29,tomato +beefsteak tomato,,https://en.wikipedia.org/wiki/Beefsteak_(tomato),tomato +better boy tomato,,https://en.wikipedia.org/wiki/Better_Boy,tomato +big rainbow tomato,,https://en.wikipedia.org/wiki/Big_Rainbow_(Tomato),tomato +Blaby special tomato,,https://en.wikipedia.org/wiki/Blaby_Special_(Tomato),tomato +black krim tomato,,https://en.wikipedia.org/wiki/Black_Krim_%28tomato%29,tomato +brandywine tomato,,https://en.wikipedia.org/wiki/Brandywine_(tomato),tomato +campari tomato,,https://en.wikipedia.org/wiki/Campari_tomato,tomato +celebrity tomato,,http://en.wikipedia.org/wiki/Celebrity_(tomato),tomato +Cherokee purple tomato,,https://en.wikipedia.org/wiki/Cherokee_purple,tomato +cherry tomato,Solanum lycopersicum var. cerasiforme,http://en.wikipedia.org/wiki/Cherry_tomato,tomato currant tomato,solanum pimpinellifolium,https://en.wikipedia.org/wiki/Solanum_pimpinellifolium, -early girl tomato,,https://en.wikipedia.org/wiki/Early_Girl,tomato, -Fourth of July tomato,,https://en.wikipedia.org/wiki/Fourth_of_July_(tomato_variety),tomato, -garden peach tomato,,https://en.wikipedia.org/wiki/Garden_peach_tomato,tomato, -green zebra tomato,,https://en.wikipedia.org/wiki/Green_Zebra,tomato, -hillbilly tomato,,http://en.wikipedia.org/wiki/Hillbilly_(tomato),tomato, -jubilee tomato,,http://en.wikipedia.org/wiki/Jubilee_(tomato),tomato, -lillian's yellow tomato,,http://en.wikipedia.org/wiki/Lillian%27s_Yellow_(tomato),tomato, -Matt's wild cherry tomato,,http://en.wikipedia.org/wiki/Matt%27s_Wild_Cherry,tomato, -mortgage lifter tomato,,http://en.wikipedia.org/wiki/Mortgage_Lifter,tomato, -Mr. Stripey tomato,,http://en.wikipedia.org/wiki/Mr._Stripey,tomato, -Roma tomato,,http://en.wikipedia.org/wiki/Roma_tomato,tomato, -San Marzano tomato,,http://en.wikipedia.org/wiki/San_Marzano_tomato,tomato, -Santorini tomato,,http://en.wikipedia.org/wiki/Santorini_(tomato),tomato, -stupice tomato,,http://en.wikipedia.org/wiki/Super_Sweet_100,tomato, -tigerella tomato,,http://en.wikipedia.org/wiki/Tigerella,tomato, -tomaccio tomato,,http://en.wikipedia.org/wiki/Tomaccio_(tomato),tomato, +early girl tomato,,https://en.wikipedia.org/wiki/Early_Girl,tomato +Fourth of July tomato,,https://en.wikipedia.org/wiki/Fourth_of_July_(tomato_variety),tomato +garden peach tomato,,https://en.wikipedia.org/wiki/Garden_peach_tomato,tomato +grape tomato,,http://en.wikipedia.org/wiki/Grape_tomato,tomato +green zebra tomato,,https://en.wikipedia.org/wiki/Green_Zebra,tomato +Hanover tomato,,http://en.wikipedia.org/wiki/Hanover_tomato,tomato +hillbilly tomato,,http://en.wikipedia.org/wiki/Hillbilly_(tomato),tomato +jubilee tomato,,http://en.wikipedia.org/wiki/Jubilee_(tomato),tomato +lillian's yellow tomato,,http://en.wikipedia.org/wiki/Lillian%27s_Yellow_(tomato),tomato +marglobe tomato,,http://en.wikipedia.org/wiki/Marglobe,tomato +Matt's wild cherry tomato,,http://en.wikipedia.org/wiki/Matt%27s_Wild_Cherry,cherry tomato +mortgage lifter tomato,,http://en.wikipedia.org/wiki/Mortgage_Lifter,tomato +Mr. Stripey tomato,,http://en.wikipedia.org/wiki/Mr._Stripey,tomato +pear tomato,,http://en.wikipedia.org/wiki/Pear_tomato,tomato +Roma tomato,,http://en.wikipedia.org/wiki/Roma_tomato,tomato +San Marzano tomato,,http://en.wikipedia.org/wiki/San_Marzano_tomato,tomato +Santorini tomato,,http://en.wikipedia.org/wiki/Santorini_(tomato),cherry tomato +stupice tomato,,http://en.wikipedia.org/wiki/Super_Sweet_100,tomato +super sweet 100 tomato,,http://en.wikipedia.org/wiki/Super_Sweet_100,cherry tomato +three sisters tomato,,http://en.wikipedia.org/wiki/Three_Sisters_(tomato),tomato +tigerella tomato,,http://en.wikipedia.org/wiki/Tigerella,tomato +tomaccio tomato,,http://en.wikipedia.org/wiki/Tomaccio_(tomato),cherry tomato +tomberry,,http://en.wikipedia.org/wiki/Tomberry,tomato traveller tomato,,http://en.wikipedia.org/wiki/Traveller_(tomato),tomato -three sisters tomato,,http://en.wikipedia.org/wiki/Three_Sisters_(tomato),tomato, -Hanover tomato,,http://en.wikipedia.org/wiki/Hanover_tomato,tomato, -celebrity tomato,,http://en.wikipedia.org/wiki/Celebrity_(tomato),tomato, -tomberry,,http://en.wikipedia.org/wiki/Tomberry,tomato, -super sweet 100 tomato,,http://en.wikipedia.org/wiki/Super_Sweet_100,tomato, -marglobe tomato,,http://en.wikipedia.org/wiki/Marglobe,tomato, -grape tomato,,http://en.wikipedia.org/wiki/Grape_tomato,tomato, -cherry tomato,,http://en.wikipedia.org/wiki/Cherry_tomato,tomato, -Aunt Ruby's German green tomato,,http://en.wikipedia.org/wiki/Aunt_Ruby%27s_German_Green,tomato, -white queen tomato,,http://en.wikipedia.org/wiki/White_Queen_tomato,tomato, -pear tomato,,http://en.wikipedia.org/wiki/Pear_tomato,tomato, +white queen tomato,,http://en.wikipedia.org/wiki/White_Queen_tomato,tomato From 7e4b51d1cb0656c1b3f4293c6732ce9c1a4d0c11 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sat, 18 Oct 2014 15:18:23 +0100 Subject: [PATCH 233/288] Add "Edit" link for alternate names on crops page. --- app/controllers/alternate_names_controller.rb | 8 +++++++ app/models/ability.rb | 1 + app/views/crops/show.html.haml | 2 ++ config/routes.rb | 1 + spec/features/crop_spec.rb | 23 +++++++++++++++++++ 5 files changed, 35 insertions(+) create mode 100644 app/controllers/alternate_names_controller.rb diff --git a/app/controllers/alternate_names_controller.rb b/app/controllers/alternate_names_controller.rb new file mode 100644 index 000000000..23028693c --- /dev/null +++ b/app/controllers/alternate_names_controller.rb @@ -0,0 +1,8 @@ +class AlternateNamesController < ApplicationController + load_and_authorize_resource + + # GET /alternate_names/1/edit + def edit + @alternate_name = AlternateName.find(params[:id]) + end +end diff --git a/app/models/ability.rb b/app/models/ability.rb index e33f4a022..3e76c6738 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -40,6 +40,7 @@ class Ability can :wrangle, Crop can :manage, Crop can :manage, ScientificName + can :manage, AlternateName end # can create & destroy their own authentications against other sites. diff --git a/app/views/crops/show.html.haml b/app/views/crops/show.html.haml index b031ec5b2..c7d02d8c3 100644 --- a/app/views/crops/show.html.haml +++ b/app/views/crops/show.html.haml @@ -77,6 +77,8 @@ - @crop.alternate_names.each do |an| %li = an.name + - if can? :edit, an + = link_to 'Edit', edit_alternate_name_path(an), { :class => 'btn btn-default btn-xs' } = render :partial => 'varieties', :locals => { :crop => @crop } diff --git a/config/routes.rb b/config/routes.rb index 0d645a1be..ac6885e54 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -28,6 +28,7 @@ Growstuff::Application.routes.draw do match '/posts/author/:author' => 'posts#index', :as => 'posts_by_author' resources :scientific_names + resources :alternate_names match 'crops/wrangle' => 'crops#wrangle', :as => 'wrangle_crops' match 'crops/hierarchy' => 'crops#hierarchy', :as => 'crops_hierarchy' diff --git a/spec/features/crop_spec.rb b/spec/features/crop_spec.rb index 9a2bb273b..3b61c3ec5 100644 --- a/spec/features/crop_spec.rb +++ b/spec/features/crop_spec.rb @@ -7,4 +7,27 @@ feature "Alternate names" do visit crop_path(alternate_eggplant.crop) expect(page).to have_content alternate_eggplant.name end + + context "User is a crop wrangler" do + let!(:crop_wranglers) { FactoryGirl.create_list(:crop_wrangling_member, 3) } + let(:member){crop_wranglers.first} + + before :each do + visit root_path + click_link 'Sign in' + fill_in 'Login', with: member.login_name + fill_in 'Password', with: member.password + click_button 'Sign in' + page.should have_content member.login_name + end + + scenario "Crop wranglers can edit alternate names" do + visit crop_path(alternate_eggplant.crop) + expect(page).to have_content "CROP WRANGLER" + expect(page).to have_content alternate_eggplant.name + expect(page).to have_link "Edit", :href => edit_alternate_name_path(alternate_eggplant) + end + + end + end From cdf6d4b09ae6e289441076146c6516ced79b3b51 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sat, 18 Oct 2014 15:29:21 +0100 Subject: [PATCH 234/288] Delete alternate name button on crops page. --- app/views/crops/show.html.haml | 2 ++ spec/features/crop_spec.rb | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/app/views/crops/show.html.haml b/app/views/crops/show.html.haml index c7d02d8c3..5d74f6d8a 100644 --- a/app/views/crops/show.html.haml +++ b/app/views/crops/show.html.haml @@ -79,6 +79,8 @@ = an.name - if can? :edit, an = link_to 'Edit', edit_alternate_name_path(an), { :class => 'btn btn-default btn-xs' } + - if can? :destroy, an + = link_to 'Delete', an, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' = render :partial => 'varieties', :locals => { :crop => @crop } diff --git a/spec/features/crop_spec.rb b/spec/features/crop_spec.rb index 3b61c3ec5..9f825015f 100644 --- a/spec/features/crop_spec.rb +++ b/spec/features/crop_spec.rb @@ -28,6 +28,12 @@ feature "Alternate names" do expect(page).to have_link "Edit", :href => edit_alternate_name_path(alternate_eggplant) end + scenario "Crop wranglers can delete alternate names" do + visit crop_path(alternate_eggplant.crop) + expect(page).to have_link "Delete", + href: alternate_name_path(alternate_eggplant) + end + end end From 77d1d067fa0fa6d74accda5f54795633574ff579 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sat, 18 Oct 2014 15:35:57 +0100 Subject: [PATCH 235/288] Crop wranglers can add alternate names --- app/views/crops/show.html.haml | 3 +++ spec/features/crop_spec.rb | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/app/views/crops/show.html.haml b/app/views/crops/show.html.haml index 5d74f6d8a..ad5abab1b 100644 --- a/app/views/crops/show.html.haml +++ b/app/views/crops/show.html.haml @@ -81,6 +81,9 @@ = link_to 'Edit', edit_alternate_name_path(an), { :class => 'btn btn-default btn-xs' } - if can? :destroy, an = link_to 'Delete', an, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' + %p + - if can? :edit, @crop + = link_to 'Add', new_alternate_name_path( :crop_id => @crop.id ), { :class => 'btn btn-default btn-xs' } = render :partial => 'varieties', :locals => { :crop => @crop } diff --git a/spec/features/crop_spec.rb b/spec/features/crop_spec.rb index 9f825015f..52620da81 100644 --- a/spec/features/crop_spec.rb +++ b/spec/features/crop_spec.rb @@ -34,6 +34,12 @@ feature "Alternate names" do href: alternate_name_path(alternate_eggplant) end + scenario "Crop wranglers can add alternate names" do + crop = alternate_eggplant.crop + visit crop_path(crop) + expect(page).to have_link "Add", + href: new_alternate_name_path(crop_id: crop.id) + end end end From 29a5fe07c2e2d4d0efb284e000703e44f741fde0 Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 18 Oct 2014 16:01:00 +0100 Subject: [PATCH 236/288] use rails paths when visiting pages --- spec/features/harvests/harvesting_a_crop_spec.rb | 4 ++-- spec/features/locale_spec.rb | 8 ++++---- spec/features/plantings/planting_a_crop_spec.rb | 4 ++-- spec/features/seeds/adding_seeds_spec.rb | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/spec/features/harvests/harvesting_a_crop_spec.rb b/spec/features/harvests/harvesting_a_crop_spec.rb index 0feb29618..caffb237d 100644 --- a/spec/features/harvests/harvesting_a_crop_spec.rb +++ b/spec/features/harvests/harvesting_a_crop_spec.rb @@ -6,7 +6,7 @@ feature "Harvesting a crop", :js => true do background do login_as(member) - visit '/harvests/new' + visit new_harvest_path end it_behaves_like "crop suggest", "harvest", "crop" @@ -26,7 +26,7 @@ feature "Harvesting a crop", :js => true do end scenario "Harvesting from crop page" do - visit "/crops/maize" + visit crop_path(maize) click_link "Harvest this" within "form#new_harvest" do expect(page).to have_selector "input[value='maize']" diff --git a/spec/features/locale_spec.rb b/spec/features/locale_spec.rb index 9a787f6cc..b5893858d 100644 --- a/spec/features/locale_spec.rb +++ b/spec/features/locale_spec.rb @@ -7,10 +7,10 @@ feature "Changing locales" do end scenario "Locale can be set with a query param" do - visit "/" + visit root_path expect(page).to have_content("a community of food gardeners.") - visit "/?locale=ja" - expect(page).to have_content("はガーデナーのコミュニティです。") + visit root_path(:locale => 'ja') + expect(page).to have_content("はガーデナーのコミュニティです。") end -end \ No newline at end of file +end diff --git a/spec/features/plantings/planting_a_crop_spec.rb b/spec/features/plantings/planting_a_crop_spec.rb index be5fc1142..ab120fa48 100644 --- a/spec/features/plantings/planting_a_crop_spec.rb +++ b/spec/features/plantings/planting_a_crop_spec.rb @@ -8,7 +8,7 @@ feature "Planting a crop", :js => true do background do login_as(member) - visit "/plantings/new" + visit new_planting_path end it_behaves_like "crop suggest", "planting", "crop" @@ -29,7 +29,7 @@ feature "Planting a crop", :js => true do end scenario "Planting from crop page" do - visit "/crops/maize" + visit crop_path(maize) click_link "Plant this" within "form#new_planting" do expect(page).to have_selector "input[value='maize']" diff --git a/spec/features/seeds/adding_seeds_spec.rb b/spec/features/seeds/adding_seeds_spec.rb index 7df57b95a..ca79c5b8c 100644 --- a/spec/features/seeds/adding_seeds_spec.rb +++ b/spec/features/seeds/adding_seeds_spec.rb @@ -6,7 +6,7 @@ feature "Harvesting a crop", :js => true do background do login_as(member) - visit '/seeds/new' + visit new_seed_path end it_behaves_like "crop suggest", "seed", "crop" @@ -26,7 +26,7 @@ feature "Harvesting a crop", :js => true do end scenario "Adding a seed from crop page" do - visit "/crops/maize" + visit crop_path(maize) click_link "Add seeds to stash" within "form#new_seed" do expect(page).to have_selector "input[value='maize']" From 10f6214c6ced4a8a1a1bfdc421486f1bb2d689e9 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sat, 18 Oct 2014 16:34:38 +0100 Subject: [PATCH 237/288] Views for adding/editing alternate names. --- app/controllers/alternate_names_controller.rb | 12 +++++++++ app/views/alternate_names/_form.html.haml | 25 +++++++++++++++++++ app/views/alternate_names/edit.html.haml | 9 +++++++ app/views/alternate_names/new.html.haml | 3 +++ spec/features/crop_spec.rb | 13 ++++++++++ 5 files changed, 62 insertions(+) create mode 100644 app/views/alternate_names/_form.html.haml create mode 100644 app/views/alternate_names/edit.html.haml create mode 100644 app/views/alternate_names/new.html.haml diff --git a/app/controllers/alternate_names_controller.rb b/app/controllers/alternate_names_controller.rb index 23028693c..b28e51f34 100644 --- a/app/controllers/alternate_names_controller.rb +++ b/app/controllers/alternate_names_controller.rb @@ -5,4 +5,16 @@ class AlternateNamesController < ApplicationController def edit @alternate_name = AlternateName.find(params[:id]) end + + # GET /alternate_names/new + # GET /alternate_names/new.json + def new + @alternate_name = AlternateName.new + @crop = Crop.find_by_id(params[:crop_id]) || Crop.new + + respond_to do |format| + format.html # new.html.haml + format.json { render json: @alternate_name } + end + end end diff --git a/app/views/alternate_names/_form.html.haml b/app/views/alternate_names/_form.html.haml new file mode 100644 index 000000000..7d6f9abd5 --- /dev/null +++ b/app/views/alternate_names/_form.html.haml @@ -0,0 +1,25 @@ += form_for @alternate_name, :html => {:class => 'form-horizontal', :role => "form"} do |f| + - if @alternate_name.errors.any? + #error_explanation + %h2= "#{pluralize(@alternate_name.errors.count, "error")} prohibited this alternate_name from being saved:" + %ul + - @alternate_name.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. + + .form-group + = f.label :crop_id, :class => 'control-label col-md-2' + .col-md-8 + = collection_select(:alternate_name, :crop_id, Crop.all, :id, :name, { :selected => @alternate_name.crop_id || @crop.id }, :class => 'form-control') + .form-group + = f.label :name, :class => 'control-label col-md-2' + .col-md-8 + = f.text_field :name, :class => 'form-control' + .form-group + .form-actions.col-md-offset-2.col-md-8 + = f.submit 'Save', :class => 'btn btn-primary' diff --git a/app/views/alternate_names/edit.html.haml b/app/views/alternate_names/edit.html.haml new file mode 100644 index 000000000..0818bb0ff --- /dev/null +++ b/app/views/alternate_names/edit.html.haml @@ -0,0 +1,9 @@ +- content_for :title, "Edit alternate name" + +%p + Added by + = @alternate_name.creator + = distance_of_time_in_words(@alternate_name.created_at, Time.zone.now) + ago. + += render 'form' diff --git a/app/views/alternate_names/new.html.haml b/app/views/alternate_names/new.html.haml new file mode 100644 index 000000000..7c51473b7 --- /dev/null +++ b/app/views/alternate_names/new.html.haml @@ -0,0 +1,3 @@ +- content_for :title, "New alternate name" + += render 'form' diff --git a/spec/features/crop_spec.rb b/spec/features/crop_spec.rb index 52620da81..ecaa6b224 100644 --- a/spec/features/crop_spec.rb +++ b/spec/features/crop_spec.rb @@ -40,6 +40,19 @@ feature "Alternate names" do expect(page).to have_link "Add", href: new_alternate_name_path(crop_id: crop.id) end + + scenario "The add-alternate-name page works" do + crop = alternate_eggplant.crop + visit new_alternate_name_path(crop_id: crop.id) + expect(page).to have_css "option[value='#{crop.id}'][selected=selected]" + end + + scenario "The edit-alternate-name page works" do + crop = alternate_eggplant.crop + visit edit_alternate_name_path(alternate_eggplant) + expect(page).to have_css "option[value='#{crop.id}'][selected=selected]" + end + end end From 37eb2a0e34e105de0a49826ad07e0c4a19723d17 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sat, 18 Oct 2014 16:49:27 +0100 Subject: [PATCH 238/288] Show alternate names on their own page. --- app/controllers/alternate_names_controller.rb | 11 +++++++++++ app/views/alternate_names/show.html.haml | 13 +++++++++++++ spec/features/crop_spec.rb | 5 +++++ 3 files changed, 29 insertions(+) create mode 100644 app/views/alternate_names/show.html.haml diff --git a/app/controllers/alternate_names_controller.rb b/app/controllers/alternate_names_controller.rb index b28e51f34..af53f9350 100644 --- a/app/controllers/alternate_names_controller.rb +++ b/app/controllers/alternate_names_controller.rb @@ -6,6 +6,17 @@ class AlternateNamesController < ApplicationController @alternate_name = AlternateName.find(params[:id]) end + # GET /alternate_names/1 + # GET /alternate_names/1.json + def show + @alternate_name = AlternateName.find(params[:id]) + + respond_to do |format| + format.html # show.html.haml + format.json { render json: @alternate_name } + end + end + # GET /alternate_names/new # GET /alternate_names/new.json def new diff --git a/app/views/alternate_names/show.html.haml b/app/views/alternate_names/show.html.haml new file mode 100644 index 000000000..2f7db7f14 --- /dev/null +++ b/app/views/alternate_names/show.html.haml @@ -0,0 +1,13 @@ +%p#notice= notice + +%p + %b Alternate name: + = @alternate_name.name +%p + %b Crop: + = link_to @alternate_name.crop + +- if can? :edit, @alternate_name + = link_to 'Edit', edit_alternate_name_path(@alternate_name), :class => 'btn btn-default btn-xs' +\| += link_to 'Back', alternate_names_path diff --git a/spec/features/crop_spec.rb b/spec/features/crop_spec.rb index ecaa6b224..83b9ff5c8 100644 --- a/spec/features/crop_spec.rb +++ b/spec/features/crop_spec.rb @@ -41,6 +41,11 @@ feature "Alternate names" do href: new_alternate_name_path(crop_id: crop.id) end + scenario "The show-alternate-name page works" do + visit alternate_name_path(alternate_eggplant) + expect(page).to have_content alternate_eggplant.crop.name + end + scenario "The add-alternate-name page works" do crop = alternate_eggplant.crop visit new_alternate_name_path(crop_id: crop.id) From eacfadae20850d18b49a39134c3d02f8a39a68be Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sat, 18 Oct 2014 16:51:43 +0100 Subject: [PATCH 239/288] Make editing alternate names actually do something. --- app/controllers/alternate_names_controller.rb | 16 ++++++++++++++++ spec/features/crop_spec.rb | 3 +++ 2 files changed, 19 insertions(+) diff --git a/app/controllers/alternate_names_controller.rb b/app/controllers/alternate_names_controller.rb index af53f9350..857233fa0 100644 --- a/app/controllers/alternate_names_controller.rb +++ b/app/controllers/alternate_names_controller.rb @@ -28,4 +28,20 @@ class AlternateNamesController < ApplicationController format.json { render json: @alternate_name } end end + + # PUT /alternate_names/1 + # PUT /alternate_names/1.json + def update + @alternate_name = AlternateName.find(params[:id]) + + respond_to do |format| + if @alternate_name.update_attributes(params[:alternate_name]) + format.html { redirect_to @alternate_name.crop, notice: 'Alternate name was successfully updated.' } + format.json { head :no_content } + else + format.html { render action: "edit" } + format.json { render json: @alternate_name.errors, status: :unprocessable_entity } + end + end + end end diff --git a/spec/features/crop_spec.rb b/spec/features/crop_spec.rb index 83b9ff5c8..e0f6a6773 100644 --- a/spec/features/crop_spec.rb +++ b/spec/features/crop_spec.rb @@ -56,6 +56,9 @@ feature "Alternate names" do crop = alternate_eggplant.crop visit edit_alternate_name_path(alternate_eggplant) expect(page).to have_css "option[value='#{crop.id}'][selected=selected]" + fill_in 'Name', with: "alternative aubergine" + click_on "Save" + expect(page).to have_content "alternative aubergine" end end From b0096035813b70817921342e92bfe2ef4c415b53 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sat, 18 Oct 2014 16:54:02 +0100 Subject: [PATCH 240/288] Fix link to crop on alternate name page --- app/views/alternate_names/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/alternate_names/show.html.haml b/app/views/alternate_names/show.html.haml index 2f7db7f14..67dfc9a37 100644 --- a/app/views/alternate_names/show.html.haml +++ b/app/views/alternate_names/show.html.haml @@ -5,7 +5,7 @@ = @alternate_name.name %p %b Crop: - = link_to @alternate_name.crop + = link_to @alternate_name.crop, @alternate_name.crop - if can? :edit, @alternate_name = link_to 'Edit', edit_alternate_name_path(@alternate_name), :class => 'btn btn-default btn-xs' From edf2f36bcd48ea10eeb76ccd2ec149ed9309588e Mon Sep 17 00:00:00 2001 From: Cesy Avon Date: Sat, 18 Oct 2014 16:58:23 +0100 Subject: [PATCH 241/288] Upgrading to ruby 2.1.2 --- .ruby-version | 2 +- Gemfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.ruby-version b/.ruby-version index 3e3c2f1e5..eca07e4c1 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.1.1 +2.1.2 diff --git a/Gemfile b/Gemfile index d463e130e..2360041eb 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ source 'https://rubygems.org' -ruby "2.1.1" +ruby "2.1.2" gem 'bundler', '>=1.1.5' From c7c85aaa664498002928d75e3a0f27995ad1ea3c Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sat, 18 Oct 2014 17:03:54 +0100 Subject: [PATCH 242/288] Creation and deletion of alternate names --- app/controllers/alternate_names_controller.rb | 30 +++++++++++++++++++ spec/features/crop_spec.rb | 3 ++ 2 files changed, 33 insertions(+) diff --git a/app/controllers/alternate_names_controller.rb b/app/controllers/alternate_names_controller.rb index 857233fa0..c7a0ac910 100644 --- a/app/controllers/alternate_names_controller.rb +++ b/app/controllers/alternate_names_controller.rb @@ -29,6 +29,23 @@ class AlternateNamesController < ApplicationController end end + # POST /alternate_names + # POST /alternate_names.json + def create + params[:alternate_name][:creator_id] = current_member.id + @alternate_name = AlternateName.new(params[:alternate_name]) + + respond_to do |format| + if @alternate_name.save + format.html { redirect_to @alternate_name.crop, notice: 'Alternate name was successfully created.' } + format.json { render json: @alternate_name, status: :created, location: @alternate_name } + else + format.html { render action: "new" } + format.json { render json: @alternate_name.errors, status: :unprocessable_entity } + end + end + end + # PUT /alternate_names/1 # PUT /alternate_names/1.json def update @@ -44,4 +61,17 @@ class AlternateNamesController < ApplicationController end end end + + # DELETE /alternate_names/1 + # DELETE /alternate_names/1.json + def destroy + @alternate_name = AlternateName.find(params[:id]) + @crop = @alternate_name.crop + @alternate_name.destroy + + respond_to do |format| + format.html { redirect_to @crop } + format.json { head :no_content } + end + end end diff --git a/spec/features/crop_spec.rb b/spec/features/crop_spec.rb index e0f6a6773..28fd35f2c 100644 --- a/spec/features/crop_spec.rb +++ b/spec/features/crop_spec.rb @@ -50,6 +50,9 @@ feature "Alternate names" do crop = alternate_eggplant.crop visit new_alternate_name_path(crop_id: crop.id) expect(page).to have_css "option[value='#{crop.id}'][selected=selected]" + fill_in 'Name', with: "not an aubergine" + click_on "Save" + expect(page).to have_content "not an aubergine" end scenario "The edit-alternate-name page works" do From dc2cf5275c7b9ed51fce35e29134416f42e1ed67 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sat, 18 Oct 2014 17:30:22 +0100 Subject: [PATCH 243/288] Fix model tests for alternate names. --- spec/models/alternate_name_spec.rb | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/spec/models/alternate_name_spec.rb b/spec/models/alternate_name_spec.rb index e7772dca0..f7acd0efc 100644 --- a/spec/models/alternate_name_spec.rb +++ b/spec/models/alternate_name_spec.rb @@ -1,24 +1,22 @@ require 'spec_helper' describe AlternateName do - before (:each) do - @an = FactoryGirl.create(:alternate_tomato) - end + let(:an) { FactoryGirl.create(:alternate_eggplant) } it 'should save a basic alternate name' do - @an.save.should be_true + expect(an.save).to be_true end it 'should be possible to add multiple alternate names to a crop' do - crop = @an.crop + crop = an.crop an2 = AlternateName.create( :name => "really alternative tomato", :crop_id => crop.id, - :creator_id => @an.creator.id + :creator_id => an.creator.id ) crop.alternate_names << an2 - crop.alternate_names.should include @an - crop.alternate_names.should include an2 + expect(crop.alternate_names).to include an + expect(crop.alternate_names).to include an2 end end From 8a97aa1a9bc1170856205469971eb1d78e637f21 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sat, 18 Oct 2014 17:34:33 +0100 Subject: [PATCH 244/288] Delete redundant old-style test. --- spec/features/crop_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/features/crop_spec.rb b/spec/features/crop_spec.rb index 28fd35f2c..db89af134 100644 --- a/spec/features/crop_spec.rb +++ b/spec/features/crop_spec.rb @@ -18,7 +18,6 @@ feature "Alternate names" do fill_in 'Login', with: member.login_name fill_in 'Password', with: member.password click_button 'Sign in' - page.should have_content member.login_name end scenario "Crop wranglers can edit alternate names" do From 96d2fa1cb6739842bb13eee63573c0d9ad48db19 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sat, 18 Oct 2014 17:41:52 +0100 Subject: [PATCH 245/288] Put alternate names controller in standard order. --- app/controllers/alternate_names_controller.rb | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/app/controllers/alternate_names_controller.rb b/app/controllers/alternate_names_controller.rb index c7a0ac910..34aa738d3 100644 --- a/app/controllers/alternate_names_controller.rb +++ b/app/controllers/alternate_names_controller.rb @@ -1,9 +1,15 @@ class AlternateNamesController < ApplicationController load_and_authorize_resource - # GET /alternate_names/1/edit - def edit - @alternate_name = AlternateName.find(params[:id]) + # GET /scientific_names + # GET /scientific_names.json + def index + @scientific_names = ScientificName.all + + respond_to do |format| + format.html # index.html.haml + format.json { render json: @scientific_names } + end end # GET /alternate_names/1 @@ -29,6 +35,11 @@ class AlternateNamesController < ApplicationController end end + # GET /alternate_names/1/edit + def edit + @alternate_name = AlternateName.find(params[:id]) + end + # POST /alternate_names # POST /alternate_names.json def create From 0ae68737c3769babfa12c492aa795e0eac374097 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sat, 18 Oct 2014 17:43:59 +0100 Subject: [PATCH 246/288] Use login_as helper method in altname feature tests. --- spec/features/crop_spec.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/spec/features/crop_spec.rb b/spec/features/crop_spec.rb index db89af134..89e81eeb2 100644 --- a/spec/features/crop_spec.rb +++ b/spec/features/crop_spec.rb @@ -13,11 +13,7 @@ feature "Alternate names" do let(:member){crop_wranglers.first} before :each do - visit root_path - click_link 'Sign in' - fill_in 'Login', with: member.login_name - fill_in 'Password', with: member.password - click_button 'Sign in' + login_as(member) end scenario "Crop wranglers can edit alternate names" do From c447c1cb3a4abb459e8be0f3f0e1bde001251f5c Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sat, 18 Oct 2014 17:49:13 +0100 Subject: [PATCH 247/288] Make edit-altname test more featurey. --- app/views/crops/show.html.haml | 50 ++++++++++++++++++---------------- spec/features/crop_spec.rb | 19 +++++-------- 2 files changed, 33 insertions(+), 36 deletions(-) diff --git a/app/views/crops/show.html.haml b/app/views/crops/show.html.haml index ad5abab1b..2c9413795 100644 --- a/app/views/crops/show.html.haml +++ b/app/views/crops/show.html.haml @@ -59,31 +59,33 @@ - if can? :destroy, @crop = link_to 'Delete crop', @crop, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' - %h4 Scientific names - %ul - - @crop.scientific_names.each do |sn| - %li - = sn.scientific_name - - if can? :edit, sn - = link_to 'Edit', edit_scientific_name_path(sn), { :class => 'btn btn-default btn-xs' } - - if can? :destroy, sn - = link_to 'Delete', sn, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' - %p - - if can? :edit, @crop - = link_to 'Add', new_scientific_name_path( :crop_id => @crop.id ), { :class => 'btn btn-default btn-xs' } + .scientific_names + %h4 Scientific names + %ul + - @crop.scientific_names.each do |sn| + %li + = sn.scientific_name + - if can? :edit, sn + = link_to 'Edit', edit_scientific_name_path(sn), { :class => 'btn btn-default btn-xs' } + - if can? :destroy, sn + = link_to 'Delete', sn, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' + %p + - if can? :edit, @crop + = link_to 'Add', new_scientific_name_path( :crop_id => @crop.id ), { :class => 'btn btn-default btn-xs' } - %h4 Alternate names - %ul - - @crop.alternate_names.each do |an| - %li - = an.name - - if can? :edit, an - = link_to 'Edit', edit_alternate_name_path(an), { :class => 'btn btn-default btn-xs' } - - if can? :destroy, an - = link_to 'Delete', an, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' - %p - - if can? :edit, @crop - = link_to 'Add', new_alternate_name_path( :crop_id => @crop.id ), { :class => 'btn btn-default btn-xs' } + .alternate_names + %h4 Alternate names + %ul + - @crop.alternate_names.each do |an| + %li + = an.name + - if can? :edit, an + = link_to 'Edit', edit_alternate_name_path(an), { :class => 'btn btn-default btn-xs' } + - if can? :destroy, an + = link_to 'Delete', an, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' + %p + - if can? :edit, @crop + = link_to 'Add', new_alternate_name_path( :crop_id => @crop.id ), { :class => 'btn btn-default btn-xs' } = render :partial => 'varieties', :locals => { :crop => @crop } diff --git a/spec/features/crop_spec.rb b/spec/features/crop_spec.rb index 89e81eeb2..be8c21b9c 100644 --- a/spec/features/crop_spec.rb +++ b/spec/features/crop_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' feature "Alternate names" do let(:alternate_eggplant) { FactoryGirl.create(:alternate_eggplant) } + let(:crop) { alternate_eggplant.crop } scenario "Display alternate names on crop page" do visit crop_path(alternate_eggplant.crop) @@ -17,10 +18,15 @@ feature "Alternate names" do end scenario "Crop wranglers can edit alternate names" do - visit crop_path(alternate_eggplant.crop) + visit crop_path(crop) expect(page).to have_content "CROP WRANGLER" expect(page).to have_content alternate_eggplant.name expect(page).to have_link "Edit", :href => edit_alternate_name_path(alternate_eggplant) + within('.alternate_names') { click_on "Edit" } + expect(page).to have_css "option[value='#{crop.id}'][selected=selected]" + fill_in 'Name', with: "alternative aubergine" + click_on "Save" + expect(page).to have_content "alternative aubergine" end scenario "Crop wranglers can delete alternate names" do @@ -30,7 +36,6 @@ feature "Alternate names" do end scenario "Crop wranglers can add alternate names" do - crop = alternate_eggplant.crop visit crop_path(crop) expect(page).to have_link "Add", href: new_alternate_name_path(crop_id: crop.id) @@ -42,7 +47,6 @@ feature "Alternate names" do end scenario "The add-alternate-name page works" do - crop = alternate_eggplant.crop visit new_alternate_name_path(crop_id: crop.id) expect(page).to have_css "option[value='#{crop.id}'][selected=selected]" fill_in 'Name', with: "not an aubergine" @@ -50,15 +54,6 @@ feature "Alternate names" do expect(page).to have_content "not an aubergine" end - scenario "The edit-alternate-name page works" do - crop = alternate_eggplant.crop - visit edit_alternate_name_path(alternate_eggplant) - expect(page).to have_css "option[value='#{crop.id}'][selected=selected]" - fill_in 'Name', with: "alternative aubergine" - click_on "Save" - expect(page).to have_content "alternative aubergine" - end - end end From 110ae99d8384e59b0848685cde118c6442fbf98b Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sat, 18 Oct 2014 17:55:56 +0100 Subject: [PATCH 248/288] Fix and test altname index page. --- app/controllers/alternate_names_controller.rb | 8 ++++---- spec/features/crop_spec.rb | 7 ++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/app/controllers/alternate_names_controller.rb b/app/controllers/alternate_names_controller.rb index 34aa738d3..9d1496e9a 100644 --- a/app/controllers/alternate_names_controller.rb +++ b/app/controllers/alternate_names_controller.rb @@ -1,14 +1,14 @@ class AlternateNamesController < ApplicationController load_and_authorize_resource - # GET /scientific_names - # GET /scientific_names.json + # GET /alternate_names + # GET /alternate_names.json def index - @scientific_names = ScientificName.all + @alternate_names = AlternateName.all respond_to do |format| format.html # index.html.haml - format.json { render json: @scientific_names } + format.json { render json: @alternate_names } end end diff --git a/spec/features/crop_spec.rb b/spec/features/crop_spec.rb index be8c21b9c..d8e2f31a1 100644 --- a/spec/features/crop_spec.rb +++ b/spec/features/crop_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' feature "Alternate names" do - let(:alternate_eggplant) { FactoryGirl.create(:alternate_eggplant) } + let!(:alternate_eggplant) { FactoryGirl.create(:alternate_eggplant) } let(:crop) { alternate_eggplant.crop } scenario "Display alternate names on crop page" do @@ -9,6 +9,11 @@ feature "Alternate names" do expect(page).to have_content alternate_eggplant.name end + scenario "Index page for alternate names" do + visit alternate_names_path + expect(page).to have_content alternate_eggplant.name + end + context "User is a crop wrangler" do let!(:crop_wranglers) { FactoryGirl.create_list(:crop_wrangling_member, 3) } let(:member){crop_wranglers.first} From 52fd2b86d64b8e8e360d6ec38d3dc1c889f136aa Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sat, 18 Oct 2014 18:01:54 +0100 Subject: [PATCH 249/288] Test altname deletion end-to-end. --- spec/features/crop_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/features/crop_spec.rb b/spec/features/crop_spec.rb index d8e2f31a1..6cd26b891 100644 --- a/spec/features/crop_spec.rb +++ b/spec/features/crop_spec.rb @@ -38,6 +38,8 @@ feature "Alternate names" do visit crop_path(alternate_eggplant.crop) expect(page).to have_link "Delete", href: alternate_name_path(alternate_eggplant) + within('.alternate_names') { click_on "Delete" } + expect(page).to_not have_content alternate_eggplant.name end scenario "Crop wranglers can add alternate names" do From 792062e0d25b7de5a00e2ccb7d0aad156a81f093 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sat, 18 Oct 2014 18:02:59 +0100 Subject: [PATCH 250/288] Make add-altname test more featurey. --- spec/features/crop_spec.rb | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/spec/features/crop_spec.rb b/spec/features/crop_spec.rb index 6cd26b891..3c648f763 100644 --- a/spec/features/crop_spec.rb +++ b/spec/features/crop_spec.rb @@ -46,6 +46,11 @@ feature "Alternate names" do visit crop_path(crop) expect(page).to have_link "Add", href: new_alternate_name_path(crop_id: crop.id) + within('.alternate_names') { click_on "Add" } + expect(page).to have_css "option[value='#{crop.id}'][selected=selected]" + fill_in 'Name', with: "not an aubergine" + click_on "Save" + expect(page).to have_content "not an aubergine" end scenario "The show-alternate-name page works" do @@ -53,14 +58,6 @@ feature "Alternate names" do expect(page).to have_content alternate_eggplant.crop.name end - scenario "The add-alternate-name page works" do - visit new_alternate_name_path(crop_id: crop.id) - expect(page).to have_css "option[value='#{crop.id}'][selected=selected]" - fill_in 'Name', with: "not an aubergine" - click_on "Save" - expect(page).to have_content "not an aubergine" - end - end end From 006bc54f3f935cfb59404ad8d081aa18310a5dba Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sat, 18 Oct 2014 18:13:04 +0100 Subject: [PATCH 251/288] Oops, add the altname index HAML file. --- app/views/alternate_names/index.html.haml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 app/views/alternate_names/index.html.haml diff --git a/app/views/alternate_names/index.html.haml b/app/views/alternate_names/index.html.haml new file mode 100644 index 000000000..60a83bde2 --- /dev/null +++ b/app/views/alternate_names/index.html.haml @@ -0,0 +1,23 @@ +%h1 Listing alternate_names + +- if can? :create, AlternateName + %p= link_to 'New Alternate name', new_alternate_name_path, :class => 'btn btn-primary' + +%table + %tr + %th Alternate name + %th Crop + %th + %th + + - @alternate_names.each do |alternate_name| + %tr + %td= link_to alternate_name.name, alternate_name + %td= alternate_name.crop + %td= link_to 'Show', alternate_name + %td + - if can? :edit, alternate_name + = link_to 'Edit', edit_alternate_name_path(alternate_name), :class => 'btn btn-default btn-xs' + %td + - if can? :destroy, alternate_name + = link_to 'Delete', alternate_name, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' From 1d3c24ae277c14e00aa9aa0a6bc9a20016a96f81 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sun, 19 Oct 2014 11:28:51 +0100 Subject: [PATCH 252/288] Use content_for :title on altname index page. --- app/views/alternate_names/index.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/alternate_names/index.html.haml b/app/views/alternate_names/index.html.haml index 60a83bde2..5cc1f69d0 100644 --- a/app/views/alternate_names/index.html.haml +++ b/app/views/alternate_names/index.html.haml @@ -1,7 +1,7 @@ -%h1 Listing alternate_names +- content_for :title, "Listing alternate names" - if can? :create, AlternateName - %p= link_to 'New Alternate name', new_alternate_name_path, :class => 'btn btn-primary' + %p= link_to 'New alternate name', new_alternate_name_path, :class => 'btn btn-primary' %table %tr From 3a797d4434ada4a34e7f0c98ce9d8826e4867d50 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sun, 19 Oct 2014 11:42:49 +0100 Subject: [PATCH 253/288] Use content_for :title in scientific names index. --- app/views/scientific_names/index.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/scientific_names/index.html.haml b/app/views/scientific_names/index.html.haml index 28cd84710..e0752b156 100644 --- a/app/views/scientific_names/index.html.haml +++ b/app/views/scientific_names/index.html.haml @@ -1,4 +1,4 @@ -%h1 Listing scientific_names +- content_for :title, "Listing scientific names" - if can? :create, ScientificName %p= link_to 'New Scientific name', new_scientific_name_path, :class => 'btn btn-primary' From d0f7169c59d9ced87722c88b1ba90abebaed914c Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sun, 19 Oct 2014 11:50:21 +0100 Subject: [PATCH 254/288] Put alternate name features in their own file. --- spec/features/{crop_spec.rb => alternate_name_spec.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename spec/features/{crop_spec.rb => alternate_name_spec.rb} (100%) diff --git a/spec/features/crop_spec.rb b/spec/features/alternate_name_spec.rb similarity index 100% rename from spec/features/crop_spec.rb rename to spec/features/alternate_name_spec.rb From 29d53a8f8b5fcd52c624264afb044103969ec9a6 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sun, 19 Oct 2014 12:02:31 +0100 Subject: [PATCH 255/288] Test for notices in altname CRUD features. --- app/controllers/alternate_names_controller.rb | 4 +++- spec/features/alternate_name_spec.rb | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/controllers/alternate_names_controller.rb b/app/controllers/alternate_names_controller.rb index 9d1496e9a..ea8a8f76c 100644 --- a/app/controllers/alternate_names_controller.rb +++ b/app/controllers/alternate_names_controller.rb @@ -81,7 +81,9 @@ class AlternateNamesController < ApplicationController @alternate_name.destroy respond_to do |format| - format.html { redirect_to @crop } + format.html { + redirect_to @crop, notice: 'Alternate name was successfully deleted.' + } format.json { head :no_content } end end diff --git a/spec/features/alternate_name_spec.rb b/spec/features/alternate_name_spec.rb index 3c648f763..a58e1c365 100644 --- a/spec/features/alternate_name_spec.rb +++ b/spec/features/alternate_name_spec.rb @@ -32,6 +32,7 @@ feature "Alternate names" do fill_in 'Name', with: "alternative aubergine" click_on "Save" expect(page).to have_content "alternative aubergine" + expect(page).to have_content 'Alternate name was successfully updated' end scenario "Crop wranglers can delete alternate names" do @@ -40,6 +41,7 @@ feature "Alternate names" do href: alternate_name_path(alternate_eggplant) within('.alternate_names') { click_on "Delete" } expect(page).to_not have_content alternate_eggplant.name + expect(page).to have_content 'Alternate name was successfully deleted' end scenario "Crop wranglers can add alternate names" do @@ -51,6 +53,7 @@ feature "Alternate names" do fill_in 'Name', with: "not an aubergine" click_on "Save" expect(page).to have_content "not an aubergine" + expect(page).to have_content 'Alternate name was successfully created' end scenario "The show-alternate-name page works" do From 82a61387a3b49bc6630bd7d5b4c6571a3c9773d8 Mon Sep 17 00:00:00 2001 From: Skud Date: Sun, 19 Oct 2014 12:23:19 +0100 Subject: [PATCH 256/288] Improved layout of member profile page In doing this, I added some content areas for all pages on the site: 1) subtitle 2) buttonbar These are intended to help standardise the layout of all pages. On the member page, the subtitle is the location, and the buttonbar has links like "edit profile", "upgrade account", etc (or if looking at someone else's page, then "send message" etc). I also implemented subtitle/buttonbar on the crop detail page (the subtitle is the default scientific name). The rest is just refactoring and tests. I've removed some view tests and put them in feature tests instead. --- app/assets/stylesheets/overrides.css.less | 15 ++ app/views/crops/show.html.haml | 22 +- app/views/gardens/new.html.haml | 2 +- app/views/layouts/application.html.haml | 13 +- app/views/members/_avatar.html.haml | 2 +- app/views/members/_bio.html.haml | 9 + app/views/members/_contact.html.haml | 17 ++ app/views/members/_gardens.html.haml | 27 +++ app/views/members/_stats.html.haml | 23 ++ app/views/members/show.html.haml | 115 +++------- app/views/scientific_names/index.html.haml | 2 +- spec/features/crop_wranglers_spec.rb | 11 +- spec/features/crops/crop_detail_page_spec.rb | 32 +++ spec/features/member_profile_spec.rb | 132 ++++++++++++ spec/views/crops/show.html.haml_spec.rb | 21 -- spec/views/members/show.html.haml_spec.rb | 214 ------------------- 16 files changed, 308 insertions(+), 349 deletions(-) create mode 100644 app/views/members/_bio.html.haml create mode 100644 app/views/members/_contact.html.haml create mode 100644 app/views/members/_gardens.html.haml create mode 100644 app/views/members/_stats.html.haml create mode 100644 spec/features/crops/crop_detail_page_spec.rb create mode 100644 spec/features/member_profile_spec.rb delete mode 100644 spec/views/members/show.html.haml_spec.rb diff --git a/app/assets/stylesheets/overrides.css.less b/app/assets/stylesheets/overrides.css.less index 7b9952c5d..5826d5c4c 100644 --- a/app/assets/stylesheets/overrides.css.less +++ b/app/assets/stylesheets/overrides.css.less @@ -36,6 +36,21 @@ h2 { font-size: 150%; } +#headingarea { + border-bottom: 1px solid lighten(@brown, 50%) +} + +/* +#subtitle { + color: lighten(@brown, 30%); + margin-top: 0px; + padding-top: 0px; + padding-left: 1em; + font-style: italic; + font-weight: normal; +} +*/ + h3 { font-size: 120%; } diff --git a/app/views/crops/show.html.haml b/app/views/crops/show.html.haml index 670a0d438..c67cb12ce 100644 --- a/app/views/crops/show.html.haml +++ b/app/views/crops/show.html.haml @@ -1,18 +1,18 @@ - 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 can? :create, Seed + = link_to 'Add seeds to stash', new_seed_path(:params => { :crop_id => @crop.id }), :class => 'btn btn-default' + .row .col-md-9 - %p - - if can? :create, Planting - = link_to "Plant this", new_planting_path(:crop_id => @crop.id), :class => 'btn btn-primary' - - 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' = render :partial => 'photos', :locals => { :crop => @crop } diff --git a/app/views/gardens/new.html.haml b/app/views/gardens/new.html.haml index 3e7ee5f67..4fdc839b1 100644 --- a/app/views/gardens/new.html.haml +++ b/app/views/gardens/new.html.haml @@ -1,3 +1,3 @@ -%h1 New garden +- content_for :title, "New garden" = render 'form' diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 7ff38d7c2..559daa608 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -8,8 +8,17 @@ .container .row .col-md-12 - - if content_for?(:title) - %h1= yield(:title) + #headingarea + - if content_for?(:title) + %h1#title + = yield(:title) + - if content_for?(:subtitle) + %small= yield(:subtitle) + + - if content_for?(:buttonbar) + %p + .btn-group + = yield(:buttonbar) - if notice .alert.alert-success = notice diff --git a/app/views/members/_avatar.html.haml b/app/views/members/_avatar.html.haml index c56c8768a..25c0e3140 100644 --- a/app/views/members/_avatar.html.haml +++ b/app/views/members/_avatar.html.haml @@ -5,5 +5,5 @@ :size => defined?(size) ? size : 150, | :default => :identicon }), | :alt => '', | - :class => 'img-rounded img-responsive' ), | + :class => 'img-rounded img-responsive avatar' ), | member_path(member) diff --git a/app/views/members/_bio.html.haml b/app/views/members/_bio.html.haml new file mode 100644 index 000000000..3858d224c --- /dev/null +++ b/app/views/members/_bio.html.haml @@ -0,0 +1,9 @@ +%h2 All about #{member.login_name} +- if member.bio.blank? + - if can? :edit, member + = link_to "Add a bio to complete your profile." + - else + #{member.login_name} hasn't written a bio yet. +- else + :growstuff_markdown + #{ strip_tags member.bio } diff --git a/app/views/members/_contact.html.haml b/app/views/members/_contact.html.haml new file mode 100644 index 000000000..e5d24327b --- /dev/null +++ b/app/views/members/_contact.html.haml @@ -0,0 +1,17 @@ +- if twitter_auth || flickr_auth || member.show_email + %h4 Contact + + - if twitter_auth + %p + = image_tag "twitter_32.png", :size => "32x32", :alt => 'Twitter logo' + =link_to twitter_auth.name, "http://twitter.com/#{twitter_auth.name}" + + - if flickr_auth + %p + = image_tag "flickr_32.png", :size => "32x32", :alt => 'Flickr logo' + =link_to flickr_auth.name, "http://flickr.com/photos/#{flickr_auth.uid}" + + - if member.show_email + %p + Email: + = mail_to member.email diff --git a/app/views/members/_gardens.html.haml b/app/views/members/_gardens.html.haml new file mode 100644 index 000000000..89ed8f83e --- /dev/null +++ b/app/views/members/_gardens.html.haml @@ -0,0 +1,27 @@ +%h2 #{member.login_name}'s gardens +.tabbable + %ul.nav.nav-tabs + - first_garden = true + - member.gardens.each do |g| + %li{:class => first_garden ? 'active' : '' } + - first_garden = false + = link_to g.name, "#garden#{g.id}", 'data-toggle' => 'tab' + - if current_member == member + %li= link_to 'New Garden', new_garden_path + .tab-content + - first_garden = true + - member.gardens.each do |g| + + %div{:class => ['tab-pane', first_garden ? 'active' : ''], :id => "garden#{g.id}"} + - first_garden = false + + %div + :growstuff_markdown + #{ strip_tags g.description } + + %h3 What's planted here? + - g.featured_plantings.each do |p| + = render :partial => "plantings/thumbnail", :locals => { :planting => p, :hide_description => true } + + %p + = link_to "More about this garden...", url_for(g) diff --git a/app/views/members/_stats.html.haml b/app/views/members/_stats.html.haml new file mode 100644 index 000000000..38d6b96c3 --- /dev/null +++ b/app/views/members/_stats.html.haml @@ -0,0 +1,23 @@ +%h3 Activity + +%ul + %li + - if member.plantings.count > 0 + = link_to pluralize(member.plantings.count, "planting"), plantings_by_owner_path(:owner => member) + - else + 0 plantings + %li + - if member.harvests.count > 0 + = link_to pluralize(member.harvests.count, "harvest"), harvests_by_owner_path(:owner => member) + - else + 0 harvests + %li + - if member.seeds.count > 0 + = link_to pluralize(member.seeds.count, "seeds"), seeds_by_owner_path(:owner => member) + - else + 0 seeds + %li + - if member.posts.count > 0 + = link_to pluralize(member.posts.count, "post"), posts_by_author_path(:author => member) + - else + 0 posts diff --git a/app/views/members/show.html.haml b/app/views/members/show.html.haml index acbcb8c34..317caae1b 100644 --- a/app/views/members/show.html.haml +++ b/app/views/members/show.html.haml @@ -1,15 +1,28 @@ -- content_for :title, "#{@member.login_name}" -- content_for :member_rss_login_name, "#{@member.login_name}" -- content_for :member_rss_slug, "#{@member.slug}" +- content_for :title, @member.login_name +- content_for :subtitle, @member.location +- content_for :buttonbar do + - if can? :update, @member + = link_to 'Edit profile', edit_member_registration_path, :class => 'btn btn-default' + - if @member == current_member && !@member.is_paid? + = link_to "Upgrade account", shop_path, :class => 'btn btn-default' + -if can? :create, Notification and current_member != @member + =link_to 'Send message', new_notification_path(:recipient_id => @member.id), :class => 'btn btn-default' + +- content_for :member_rss_login_name, @member.login_name +- content_for :member_rss_slug, @member.slug .row - .col-md-3 - = render :partial => "members/avatar", :locals => { :member => @member } - -if can? :create, Notification and current_member != @member - %p - %br/ - =link_to 'Send Message', new_notification_path(:recipient_id => @member.id), :class => 'btn btn-primary' + .col-md-9 + + = render :partial => "bio", :locals => { :member => @member } + + = render :partial => "gardens", :locals => { :member => @member } + + .col-md-3 + = render :partial => "avatar", :locals => { :member => @member } + + %h3 Account details %p %strong Member since: @@ -20,86 +33,6 @@ = @member.account_type account - - if @member == current_member && !@member.is_paid? - = link_to "Upgrade", shop_path, :class => 'btn btn-primary btn-xs' + = render :partial => "contact", :locals => { :member => @member, :twitter_auth => @twitter_auth, :flickr_auth => @flickr_auth } - - if @twitter_auth || @flickr_auth || @member.show_email - %h4 Contact - - - if @twitter_auth - %p - = image_tag "twitter_32.png", :size => "32x32", :alt => 'Twitter logo' - =link_to @twitter_auth.name, "http://twitter.com/#{@twitter_auth.name}" - - - if @flickr_auth - %p - = image_tag "flickr_32.png", :size => "32x32", :alt => 'Flickr logo' - =link_to @flickr_auth.name, "http://flickr.com/photos/#{@flickr_auth.uid}" - - - if @member.show_email - %p - Email: - = mail_to @member.email - - - if @member.location.to_s != '' - %h4 Location - %p - = link_to image_tag("http://maps.google.com/maps/api/staticmap?size=200x200&maptype=roadmap&sensor=false&markers=color:green|label:A|#{@member.latitude},#{@member.longitude}&zoom=12", :alt => "Map showing #{@member.location}", :width => 200, :height => 200 ), place_path(@member.location) - %br/ - = link_to @member.location, place_path(@member.location) - - .col-md-9 - %p - - if can? :update, @member - %p - = link_to 'Edit your profile', "edit", :class => 'btn btn-primary' - - if @member.bio - %h2 Bio - :growstuff_markdown - #{ strip_tags @member.bio } - %h2 Gardens - .tabbable - %ul.nav.nav-tabs - - first_garden = true - - @member.gardens.each do |g| - %li{:class => first_garden ? 'active' : '' } - - first_garden = false - = link_to g.name, "#garden#{g.id}", 'data-toggle' => 'tab' - - if current_member == @member - %li= link_to 'New Garden', '#garden_new', 'data-toggle' => 'tab' - .tab-content - - first_garden = true - - @member.gardens.each do |g| - - %div{:class => ['tab-pane', first_garden ? 'active' : ''], :id => "garden#{g.id}"} - - first_garden = false - - %div - :growstuff_markdown - #{ strip_tags g.description } - - %h3 What's planted here? - - g.featured_plantings.each do |p| - = render :partial => "plantings/thumbnail", :locals => { :planting => p, :hide_description => true } - - %p - = link_to "More about this garden...", url_for(g) - - - if current_member == @member - %div{:class => 'tab-pane', :id => "garden_new"} - %h3 Create a new garden - = render 'gardens/form' - - %h3 Seeds - %p - - if @member.seeds.count > 0 - = link_to pluralize(@member.seeds.count, "variety"), seeds_path(:owner_id => @member.id) - - else - No seeds yet. - - %h3 Posts - - if @member.posts.count > 0 - - @member.posts.each do |post| - = render :partial => "posts/single", :locals => { :post => post, :subject => true } - - else - %p Nothing posted yet. + = render :partial => "stats", :locals => { :member => @member } diff --git a/app/views/scientific_names/index.html.haml b/app/views/scientific_names/index.html.haml index 28cd84710..8a5ae901d 100644 --- a/app/views/scientific_names/index.html.haml +++ b/app/views/scientific_names/index.html.haml @@ -1,4 +1,4 @@ -%h1 Listing scientific_names +-content_for :title, "Listing scientific_names" - if can? :create, ScientificName %p= link_to 'New Scientific name', new_scientific_name_path, :class => 'btn btn-primary' diff --git a/spec/features/crop_wranglers_spec.rb b/spec/features/crop_wranglers_spec.rb index 80475bd18..32c96e639 100644 --- a/spec/features/crop_wranglers_spec.rb +++ b/spec/features/crop_wranglers_spec.rb @@ -4,16 +4,13 @@ feature "crop wranglers" do context "signed in member" do let!(:crop_wranglers) { FactoryGirl.create_list(:crop_wrangling_member, 3) } let(:member){crop_wranglers.first} - before :each do - visit root_path - click_link 'Sign in' - fill_in 'Login', with: member.login_name - fill_in 'Password', with: member.password - click_button 'Sign in' - page.should have_content member.login_name + + background do + login_as(member) end scenario "crop wranglers are listed on the crop wrangler page" do + visit root_path click_link 'Crop Wrangling' within '.crop_wranglers' do diff --git a/spec/features/crops/crop_detail_page_spec.rb b/spec/features/crops/crop_detail_page_spec.rb new file mode 100644 index 000000000..a941cb075 --- /dev/null +++ b/spec/features/crops/crop_detail_page_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +feature "crop detail page" do + + let(:crop) { FactoryGirl.create(:crop) } + + context "signed in member" do + let(:member) { FactoryGirl.create(:member) } + + background do + login_as(member) + end + + context "action buttons" do + + background do + visit crop_path(crop) + end + + scenario "has a link to plant the crop" do + expect(page).to have_link "Plant this", :href => new_planting_path(:crop_id => crop.id) + end + scenario "has a link to harvest the crop" do + expect(page).to have_link "Harvest this", :href => new_harvest_path(:crop_id => crop.id) + end + scenario "has a link to add seeds" do + expect(page).to have_link "Add seeds to stash", :href => new_seed_path(:crop_id => crop.id) + end + + end + end +end diff --git a/spec/features/member_profile_spec.rb b/spec/features/member_profile_spec.rb new file mode 100644 index 000000000..88f29548a --- /dev/null +++ b/spec/features/member_profile_spec.rb @@ -0,0 +1,132 @@ +require 'spec_helper' + +feature "member profile" do + + context "signed out member" do + let(:member) { FactoryGirl.create(:member) } + + scenario "basic details on member profile page" do + visit member_path(member) + expect(page).to have_css("h1", :text => member.login_name) + expect(page).to have_content member.bio + expect(page).to have_content "Member since: #{member.created_at.to_s(:date)}" + expect(page).to have_content "Account type: Free account" + expect(page).to have_content "#{member.login_name}'s gardens" + expect(page).to have_link "More about this garden...", :href => garden_path(member.gardens.first) + end + + scenario "no bio" do + member.bio = nil + member.save + visit member_path(member) + expect(page).to have_content "hasn't written a bio yet" + end + + scenario "gravatar" do + visit member_path(member) + expect(page).to have_css "img.avatar" + end + + context "location" do + scenario "member has set location" do + london_member = FactoryGirl.create(:london_member) + visit member_path(london_member) + expect(page).to have_css("h1>small", :text => london_member.location) + end + + scenario "member has not set location" do + visit member_path(member) + expect(page).not_to have_css("h1>small") + end + end + + context "email privacy" do + scenario "public email address" do + public_member = FactoryGirl.create(:public_member) + visit member_path(public_member) + expect(page).to have_content public_member.email + end + scenario "private email address" do + visit member_path(member) + expect(page).not_to have_content member.email + end + end + + context "activity stats" do + + scenario "with no activity" do + visit member_path(member) + expect(page).to have_content "Activity" + expect(page).to have_content "0 plantings" + expect(page).to have_content "0 harvests" + expect(page).to have_content "0 seeds" + expect(page).to have_content "0 posts" + end + + scenario "with some activity" do + FactoryGirl.create_list(:planting, 2, :owner => member) + FactoryGirl.create_list(:harvest, 3, :owner => member) + FactoryGirl.create_list(:seed, 4, :owner => member) + FactoryGirl.create_list(:post, 5, :author => member) + visit member_path(member) + expect(page).to have_link "2 plantings", :href => plantings_by_owner_path(:owner => member) + expect(page).to have_link "3 harvests", :href => harvests_by_owner_path(:owner => member) + expect(page).to have_link "4 seeds", :href => seeds_by_owner_path(:owner => member) + expect(page).to have_link "5 posts", :href => posts_by_author_path(:author => member) + end + + end + + scenario "twitter link" do + twitter_auth = FactoryGirl.create(:authentication, :member => member) + visit member_path(member) + expect(page).to have_link twitter_auth.name, :href => "http://twitter.com/#{twitter_auth.name}" + end + + scenario "flickr link" do + flickr_auth = FactoryGirl.create(:flickr_authentication, :member => member) + visit member_path(member) + expect(page).to have_link flickr_auth.name, :href => "http://flickr.com/photos/#{flickr_auth.uid}" + end + + end + + context "signed in member" do + let(:member) { FactoryGirl.create(:member) } + let(:other_member) { FactoryGirl.create(:member) } + + background do + login_as(member) + end + + context "your own profile page" do + background do + visit member_path(member) + end + + scenario "has a link to create new garden" do + expect(page).to have_link "New Garden", :href => new_garden_path + end + + scenario "has a button to edit profile" do + expect(page).to have_link "Edit profile", :href => edit_member_registration_path + end + + scenario "has a button to upgrade account" do + expect(page).to have_link "Upgrade account", :href => shop_path + end + + end + + context "someone else's profile page" do + background do + visit member_path(other_member) + end + + scenario "has a private message button" do + expect(page).to have_link "Send message", :href => new_notification_path(:recipient_id => other_member.id) + end + end + + end +end diff --git a/spec/views/crops/show.html.haml_spec.rb b/spec/views/crops/show.html.haml_spec.rb index 1ef461508..ca6166721 100644 --- a/spec/views/crops/show.html.haml_spec.rb +++ b/spec/views/crops/show.html.haml_spec.rb @@ -185,11 +185,6 @@ describe "crops/show" do end - it 'tells you to sign in/sign up' do - render - rendered.should contain 'Sign in or sign up to plant' - end - context 'logged in' do before(:each) do @member = FactoryGirl.create(:member) @@ -198,22 +193,6 @@ describe "crops/show" do render end - it "shows a plant this button" 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 new planting link" do - assert_select("a[href=#{new_planting_path}?crop_id=#{@crop.id}]") - end - - it "links to the right crop in the new harvest link" do - assert_select("a[href=#{new_harvest_path}?crop_id=#{@crop.id}]") - end - it { rendered.should contain "Nobody has planted this crop yet" } it { rendered.should contain "Nobody has harvested this crop yet" } diff --git a/spec/views/members/show.html.haml_spec.rb b/spec/views/members/show.html.haml_spec.rb deleted file mode 100644 index f6c0279d6..000000000 --- a/spec/views/members/show.html.haml_spec.rb +++ /dev/null @@ -1,214 +0,0 @@ -require 'spec_helper' - -describe "members/show" do - before(:each) do - controller.stub(:current_user) { nil } - @member = FactoryGirl.create(:member) - @garden = FactoryGirl.create(:garden, :owner => @member) - end - - context "the basics" do - before(:each) do - render - end - - it "shows the bio" do - assert_select "h2", "Bio" - rendered.should contain @member.bio - end - - it "shows account creation date" do - @time = @member.created_at - rendered.should contain "Member since" - rendered.should contain @time.strftime("%B %d, %Y") - end - - it "shows account type" do - rendered.should contain "Free account" - end - - it "contains a gravatar icon" do - assert_select "img", :src => /gravatar\.com\/avatar/ - end - end - - context 'no bio' do - before(:each) do - @member = FactoryGirl.create(:no_bio_member) - render - end - - it "doesn't show the bio" do - rendered.should_not contain "Bio" - end - end - - context 'twitter' do - context "no twitter" do - it "doesn't show twitter link" do - render - assert_select "a[href^=http://twitter.com/]", :count => 0 - end - end - context 'has twitter' do - it "shows twitter link" do - @twitter_auth = FactoryGirl.create(:authentication, :member => @member) - render - assert_select "a", :href => "http://twitter.com/#{@twitter_auth.name}" - end - end - end - - context 'flickr' do - context "no flickr" do - it "doesn't show flickr link" do - render - assert_select "a[href^=http://flickr.com/]", :count => 0 - end - end - context 'has flickr' do - it "shows flickr link" do - @flickr_auth = FactoryGirl.create(:flickr_authentication, :member => @member) - render - assert_select "a", :href => "http://flickr.com/photos/#{@flickr_auth.uid}" - end - end - end - - context "gardens and plantings" do - before(:each) do - @planting = FactoryGirl.create(:planting, :garden => @garden) - render - end - - it "shows the auto-created garden" do - assert_select "li.active>a", :text => "Garden" - end - - it 'shows the garden description' do - rendered.should contain "totally cool garden" - end - - it 'renders markdown in the garden description' do - assert_select "strong", "totally" - end - - it "shows the plantings in the garden" do - rendered.should contain @planting.crop.name - end - - it "doesn't show the note about random plantings" do - rendered.should_not contain "Note: these are a random selection" - end - - it "doesn't show the email address" do - rendered.should_not contain @member.email - end - - it "does not contain a 'New Garden' link" do - assert_select "a[href=#garden_new]", false - end - - it "does not contain a 'New Garden' tab" do - assert_select "#garden_new", false - end - end - - context "signed in member" do - before(:each) do - sign_in @member - controller.stub(:current_user) { @member } - render - end - - it "contains a 'New Garden' link" do - assert_select "a[href=#garden_new]", :text => "New Garden" - end - - it "contains an edit settings button" do - rendered.should contain "Edit your profile" - end - - it "asks you to upgrade your account" do - rendered.should contain "Upgrade" - end - - it "contains no send message button" do - rendered.should_not contain "Send Message" - end - end - - context "signed in as different member" do - before(:each) do - @member2 = FactoryGirl.create(:member) - sign_in @member2 - controller.stub(:current_user) { @member2 } - render - end - - it "does not contain a 'New Garden' link" do - assert_select "a[href=#garden_new]", false - end - - it "does not contain a 'New Garden' tab" do - assert_select "#garden_new", false - end - - it "contains no edit settings button" do - rendered.should_not contain "Edit Settings" - end - - it "contains a send message button" do - rendered.should contain "Send Message" - end - end - - context "public member" do - before(:each) do - @member = FactoryGirl.create(:public_member) - render - end - - it "shows the email address" do - rendered.should contain @member.email - end - - it "doesn't show a send message button" do - rendered.should_not contain "Send Message" - end - end - - context "geolocations" do - before(:each) do - @member = FactoryGirl.create(:london_member) - render - end - it "shows the location" do - rendered.should contain @member.location - end - it "shows a map" do - assert_select "img", :src => /maps\.google\.com/ - end - - it 'includes a link to places page' do - assert_select 'a', :href => place_path(@member.location) - end - end - - context "no location stated" do - before(:each) do - @member = FactoryGirl.create(:member) - render - end - it "doesn't have a location" do - @member.location.to_s.should eq '' - end - it "doesn't show the location" do - rendered.should_not contain "Location:" - end - it "doesn't show a map" do - assert_select "img[src*=maps]", false - end - end - -end From e613907506c39491bb14cbb8246c5841621f3a7e Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sun, 19 Oct 2014 13:21:30 +0100 Subject: [PATCH 257/288] Check status codes on page visits. --- spec/features/alternate_name_spec.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/features/alternate_name_spec.rb b/spec/features/alternate_name_spec.rb index a58e1c365..086584c40 100644 --- a/spec/features/alternate_name_spec.rb +++ b/spec/features/alternate_name_spec.rb @@ -6,6 +6,7 @@ feature "Alternate names" do scenario "Display alternate names on crop page" do visit crop_path(alternate_eggplant.crop) + expect(page.status_code).to equal 200 expect(page).to have_content alternate_eggplant.name end @@ -24,13 +25,16 @@ feature "Alternate names" do scenario "Crop wranglers can edit alternate names" do visit crop_path(crop) + expect(page.status_code).to equal 200 expect(page).to have_content "CROP WRANGLER" expect(page).to have_content alternate_eggplant.name expect(page).to have_link "Edit", :href => edit_alternate_name_path(alternate_eggplant) within('.alternate_names') { click_on "Edit" } + expect(page.status_code).to equal 200 expect(page).to have_css "option[value='#{crop.id}'][selected=selected]" fill_in 'Name', with: "alternative aubergine" click_on "Save" + expect(page.status_code).to equal 200 expect(page).to have_content "alternative aubergine" expect(page).to have_content 'Alternate name was successfully updated' end @@ -40,6 +44,7 @@ feature "Alternate names" do expect(page).to have_link "Delete", href: alternate_name_path(alternate_eggplant) within('.alternate_names') { click_on "Delete" } + expect(page.status_code).to equal 200 expect(page).to_not have_content alternate_eggplant.name expect(page).to have_content 'Alternate name was successfully deleted' end @@ -49,15 +54,18 @@ feature "Alternate names" do expect(page).to have_link "Add", href: new_alternate_name_path(crop_id: crop.id) within('.alternate_names') { click_on "Add" } + expect(page.status_code).to equal 200 expect(page).to have_css "option[value='#{crop.id}'][selected=selected]" fill_in 'Name', with: "not an aubergine" click_on "Save" + expect(page.status_code).to equal 200 expect(page).to have_content "not an aubergine" expect(page).to have_content 'Alternate name was successfully created' end scenario "The show-alternate-name page works" do visit alternate_name_path(alternate_eggplant) + expect(page.status_code).to equal 200 expect(page).to have_content alternate_eggplant.crop.name end From 41566d39f31fc3a6592ede74ae7fb6ecd856e4fa Mon Sep 17 00:00:00 2001 From: Skud Date: Sun, 19 Oct 2014 13:50:43 +0100 Subject: [PATCH 258/288] Added map to member profile page --- app/assets/javascripts/members.js.erb | 30 +++++++++++++++++++++++ app/assets/stylesheets/overrides.css.less | 4 +++ app/controllers/members_controller.rb | 2 ++ app/views/members/_account.html.haml | 12 +++++++++ app/views/members/_map.html.haml | 5 ++++ app/views/members/show.html.haml | 16 ++---------- spec/features/member_profile_spec.rb | 17 +++++++++++++ 7 files changed, 72 insertions(+), 14 deletions(-) create mode 100644 app/assets/javascripts/members.js.erb create mode 100644 app/views/members/_account.html.haml create mode 100644 app/views/members/_map.html.haml diff --git a/app/assets/javascripts/members.js.erb b/app/assets/javascripts/members.js.erb new file mode 100644 index 000000000..a3f29904d --- /dev/null +++ b/app/assets/javascripts/members.js.erb @@ -0,0 +1,30 @@ +if (document.getElementById("membermap") !== null) { + mapbox_map_id = "<%= Rails.env == 'test' ? 0 : Growstuff::Application.config.mapbox_map_id %>"; + mapbox_base_url = "https://c.tiles.mapbox.com/v3/" + mapbox_map_id + "/{z}/{x}/{y}.png"; + + L.Icon.Default.imagePath = '/assets' + + + $.getJSON(location.pathname + '.json', function(member) { + console.log(JSON.stringify(member.latitude)); + if (member.latitude && member.longitude) { + membermap = L.map('membermap').setView([member.latitude, member.longitude], 4); + + L.tileLayer(mapbox_base_url, { + attribution: 'Map data © OpenStreetMap contributors under ODbL | Map imagery © Mapbox', + maxZoom: 18 + }).addTo(membermap); + console.log("found lat and long") + marker = new L.Marker(new L.LatLng(member.latitude, member.longitude)); + + member_url = "/members/" + member.slug; + member_link = "" + member.login_name + ""; + + where = "

    " + member.location + "

    "; + + marker.bindPopup(member_link + where).openPopup(); + marker.addTo(membermap); + } + }); + +} diff --git a/app/assets/stylesheets/overrides.css.less b/app/assets/stylesheets/overrides.css.less index 5826d5c4c..a4ac0587f 100644 --- a/app/assets/stylesheets/overrides.css.less +++ b/app/assets/stylesheets/overrides.css.less @@ -113,6 +113,10 @@ p.stats { height: 500px; } +#membermap { + height: 250px; +} + .member-location { font-size: small; font-style: italic; diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index ab3de0001..247ac1c8b 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -10,6 +10,7 @@ class MembersController < ApplicationController respond_to do |format| format.html # index.html.haml + format.json { render :json => @members.to_json(:only => [:id, :login_name, :slug, :bio, :created_at, :location, :latitude, :longitude]) } end end @@ -25,6 +26,7 @@ class MembersController < ApplicationController respond_to do |format| format.html # show.html.haml + format.json { render :json => @member.to_json(:only => [:id, :login_name, :bio, :created_at, :slug, :location, :latitude, :longitude]) } format.rss { render( :layout => false, :locals => { :member => @member } diff --git a/app/views/members/_account.html.haml b/app/views/members/_account.html.haml new file mode 100644 index 000000000..37ff0c699 --- /dev/null +++ b/app/views/members/_account.html.haml @@ -0,0 +1,12 @@ +%h3 Account details + +%p + %strong Member since: + = member.created_at.to_s(:date) + +%p + %strong Account type: + = member.account_type + account + + diff --git a/app/views/members/_map.html.haml b/app/views/members/_map.html.haml new file mode 100644 index 000000000..d98792501 --- /dev/null +++ b/app/views/members/_map.html.haml @@ -0,0 +1,5 @@ +- if member.latitude and member.longitude + %div#membermap + %p + See other members near + = link_to member.location, place_path(member.location) diff --git a/app/views/members/show.html.haml b/app/views/members/show.html.haml index 317caae1b..edf4d0b68 100644 --- a/app/views/members/show.html.haml +++ b/app/views/members/show.html.haml @@ -15,24 +15,12 @@ .col-md-9 + = render :partial => "map", :locals => { :member => @member } = render :partial => "bio", :locals => { :member => @member } - = render :partial => "gardens", :locals => { :member => @member } .col-md-3 = render :partial => "avatar", :locals => { :member => @member } - - %h3 Account details - - %p - %strong Member since: - = @member.created_at.to_s(:date) - - %p - %strong Account type: - = @member.account_type - account - + = render :partial => "account", :locals => { :member => @member } = render :partial => "contact", :locals => { :member => @member, :twitter_auth => @twitter_auth, :flickr_auth => @flickr_auth } - = render :partial => "stats", :locals => { :member => @member } diff --git a/spec/features/member_profile_spec.rb b/spec/features/member_profile_spec.rb index 88f29548a..809b052a8 100644 --- a/spec/features/member_profile_spec.rb +++ b/spec/features/member_profile_spec.rb @@ -32,11 +32,28 @@ feature "member profile" do london_member = FactoryGirl.create(:london_member) visit member_path(london_member) expect(page).to have_css("h1>small", :text => london_member.location) + expect(page).to have_css("#membermap") + expect(page).to have_content "See other members near #{london_member.location}" end scenario "member has not set location" do visit member_path(member) expect(page).not_to have_css("h1>small") + expect(page).not_to have_css("#membermap") + expect(page).not_to have_content "See other members near" + end + + end + + context "email privacy" do + scenario "public email address" do + public_member = FactoryGirl.create(:public_member) + visit member_path(public_member) + expect(page).to have_content public_member.email + end + scenario "private email address" do + visit member_path(member) + expect(page).not_to have_content member.email end end From 16677a3b8637e09c348409f97ec2f5188fd26abc Mon Sep 17 00:00:00 2001 From: Skud Date: Sun, 19 Oct 2014 14:04:32 +0100 Subject: [PATCH 259/288] Removed underline on header area --- app/assets/stylesheets/overrides.css.less | 4 ---- app/views/layouts/application.html.haml | 11 +++++------ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/app/assets/stylesheets/overrides.css.less b/app/assets/stylesheets/overrides.css.less index a4ac0587f..8b9938c33 100644 --- a/app/assets/stylesheets/overrides.css.less +++ b/app/assets/stylesheets/overrides.css.less @@ -36,10 +36,6 @@ h2 { font-size: 150%; } -#headingarea { - border-bottom: 1px solid lighten(@brown, 50%) -} - /* #subtitle { color: lighten(@brown, 30%); diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 559daa608..a3e1fd1d5 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -8,12 +8,11 @@ .container .row .col-md-12 - #headingarea - - if content_for?(:title) - %h1#title - = yield(:title) - - if content_for?(:subtitle) - %small= yield(:subtitle) + - if content_for?(:title) + %h1#title + = yield(:title) + - if content_for?(:subtitle) + %small= yield(:subtitle) - if content_for?(:buttonbar) %p From f468a8b77b9b55a6e5255bab0a2f25376b45c0c8 Mon Sep 17 00:00:00 2001 From: Skud Date: Sun, 19 Oct 2014 14:04:55 +0100 Subject: [PATCH 260/288] Changed all h1 to content_for :title --- app/views/account_types/edit.html.haml | 2 +- app/views/account_types/index.html.haml | 2 +- app/views/account_types/new.html.haml | 2 +- app/views/accounts/edit.html.haml | 2 +- app/views/accounts/index.html.haml | 2 +- app/views/accounts/new.html.haml | 2 +- app/views/authentications/index.html.haml | 2 +- app/views/plant_parts/edit.html.haml | 2 +- app/views/plant_parts/new.html.haml | 2 +- app/views/products/edit.html.haml | 2 +- app/views/products/index.html.haml | 2 +- app/views/products/new.html.haml | 2 +- app/views/roles/edit.html.haml | 2 +- app/views/roles/index.html.haml | 2 +- app/views/roles/new.html.haml | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/views/account_types/edit.html.haml b/app/views/account_types/edit.html.haml index e8d328a21..79f11726d 100644 --- a/app/views/account_types/edit.html.haml +++ b/app/views/account_types/edit.html.haml @@ -1,4 +1,4 @@ -%h1 Editing account_type +-content_for :title, "Editing account type" = render 'form' diff --git a/app/views/account_types/index.html.haml b/app/views/account_types/index.html.haml index a27434af4..5fc8a3f05 100644 --- a/app/views/account_types/index.html.haml +++ b/app/views/account_types/index.html.haml @@ -1,4 +1,4 @@ -%h1 Listing account_types +- content_for :title, "Listing account types" %table %tr diff --git a/app/views/account_types/new.html.haml b/app/views/account_types/new.html.haml index bf85a1baf..92d309715 100644 --- a/app/views/account_types/new.html.haml +++ b/app/views/account_types/new.html.haml @@ -1,4 +1,4 @@ -%h1 New account_type +- content_for :title, "New account type" = render 'form' diff --git a/app/views/accounts/edit.html.haml b/app/views/accounts/edit.html.haml index 5b4039cfa..ef8d08801 100644 --- a/app/views/accounts/edit.html.haml +++ b/app/views/accounts/edit.html.haml @@ -1,4 +1,4 @@ -%h1 Editing account +- content_for :title, "Editing account" = render 'form' diff --git a/app/views/accounts/index.html.haml b/app/views/accounts/index.html.haml index 66345747f..1dfef6438 100644 --- a/app/views/accounts/index.html.haml +++ b/app/views/accounts/index.html.haml @@ -1,4 +1,4 @@ -%h1 Listing accounts +- content_for :title, "Listing accounts" %table %tr diff --git a/app/views/accounts/new.html.haml b/app/views/accounts/new.html.haml index 489317b27..9430cc2ed 100644 --- a/app/views/accounts/new.html.haml +++ b/app/views/accounts/new.html.haml @@ -1,4 +1,4 @@ -%h1 New account +- content_for :title, "New account" = render 'form' diff --git a/app/views/authentications/index.html.haml b/app/views/authentications/index.html.haml index 61cd83250..3bb1607f1 100644 --- a/app/views/authentications/index.html.haml +++ b/app/views/authentications/index.html.haml @@ -1,4 +1,4 @@ -%h1 Linked accounts on other sites +- content_for :title, "Linked accounts on other sites" - if @authentications - unless @authentications.empty? diff --git a/app/views/plant_parts/edit.html.haml b/app/views/plant_parts/edit.html.haml index 6add25fb3..f17252ef8 100644 --- a/app/views/plant_parts/edit.html.haml +++ b/app/views/plant_parts/edit.html.haml @@ -1,4 +1,4 @@ -%h1 Editing plant_part +- content_for :title, "Editing plant part" = render 'form' diff --git a/app/views/plant_parts/new.html.haml b/app/views/plant_parts/new.html.haml index 604c38a04..e0d446dcb 100644 --- a/app/views/plant_parts/new.html.haml +++ b/app/views/plant_parts/new.html.haml @@ -1,4 +1,4 @@ -%h1 New plant_part +- content_for :title, "New plant part" = render 'form' diff --git a/app/views/products/edit.html.haml b/app/views/products/edit.html.haml index 9c16087ea..84ebcb870 100644 --- a/app/views/products/edit.html.haml +++ b/app/views/products/edit.html.haml @@ -1,4 +1,4 @@ -%h1 Editing product +- content_for :title, "Editing product" = render 'form' diff --git a/app/views/products/index.html.haml b/app/views/products/index.html.haml index 61940aaa8..17053aeca 100644 --- a/app/views/products/index.html.haml +++ b/app/views/products/index.html.haml @@ -1,4 +1,4 @@ -%h1 Listing products +- content_for :title, "Listing products" %table %tr diff --git a/app/views/products/new.html.haml b/app/views/products/new.html.haml index 71ed863eb..57b944d9a 100644 --- a/app/views/products/new.html.haml +++ b/app/views/products/new.html.haml @@ -1,4 +1,4 @@ -%h1 New product +- content_for :title, "New product" = render 'form' diff --git a/app/views/roles/edit.html.haml b/app/views/roles/edit.html.haml index 427975d14..83520111b 100644 --- a/app/views/roles/edit.html.haml +++ b/app/views/roles/edit.html.haml @@ -1,4 +1,4 @@ -%h1 Editing role +- content_for :title, "Editing role" = render 'form' diff --git a/app/views/roles/index.html.haml b/app/views/roles/index.html.haml index ae51b2d36..8548645c4 100644 --- a/app/views/roles/index.html.haml +++ b/app/views/roles/index.html.haml @@ -1,4 +1,4 @@ -%h1 Listing roles +- content_for :title, "Listing roles" - if can? :create, Role %p= link_to 'New Role', new_role_path, :class => 'btn btn-primary' diff --git a/app/views/roles/new.html.haml b/app/views/roles/new.html.haml index 6e07588dd..30aae3fe1 100644 --- a/app/views/roles/new.html.haml +++ b/app/views/roles/new.html.haml @@ -1,4 +1,4 @@ -%h1 New role +- content_for :title, "New role" = render 'form' From 496c070efd3d88415a3af9708880366193d5fe02 Mon Sep 17 00:00:00 2001 From: Skud Date: Sun, 19 Oct 2014 14:14:24 +0100 Subject: [PATCH 261/288] Tweaked layout of crop page sidebar --- app/views/crops/_find_seeds.html.haml | 4 +++- app/views/crops/_harvests.html.haml | 8 ++++---- app/views/crops/_plantings.html.haml | 8 ++++---- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/views/crops/_find_seeds.html.haml b/app/views/crops/_find_seeds.html.haml index db7c5b4b7..9b7797144 100644 --- a/app/views/crops/_find_seeds.html.haml +++ b/app/views/crops/_find_seeds.html.haml @@ -8,7 +8,9 @@ %li = link_to "#{seed.owner} will trade #{seed.tradable_to}.", seed_path(seed) = render :partial => 'members/location', :locals => { :member => seed.owner } + %p + = link_to "View all #{crop.name} seeds", seeds_by_crop_path(crop) - if current_member - = link_to "List your seeds to trade.", new_seed_path() + = link_to "List achiote seeds to trade", new_seed_path(:crop_id => crop.id) - else = render :partial => 'shared/signin_signup', :locals => { :to => 'list your seeds to trade' } diff --git a/app/views/crops/_harvests.html.haml b/app/views/crops/_harvests.html.haml index d10f57d06..bac61e580 100644 --- a/app/views/crops/_harvests.html.haml +++ b/app/views/crops/_harvests.html.haml @@ -11,11 +11,11 @@ %small = distance_of_time_in_words(harvest.created_at, Time.zone.now) ago. - %p.col-md-offset-1 - = link_to "See all #{crop.name} harvests", harvests_by_crop_path(crop) + %p + = link_to "View all #{crop.name} harvests", harvests_by_crop_path(crop) - if current_member - %p.col-md-offset-1 - = link_to "Track your #{crop.name} harvests.", new_harvest_path(:crop_id => crop.id) + %p + = link_to "Harvest #{crop.name}", new_harvest_path(:crop_id => crop.id) - else = render :partial => 'shared/signin_signup', :locals => { :to => "track your #{crop.name} harvests" } diff --git a/app/views/crops/_plantings.html.haml b/app/views/crops/_plantings.html.haml index 72bce5608..85a2ff88a 100644 --- a/app/views/crops/_plantings.html.haml +++ b/app/views/crops/_plantings.html.haml @@ -11,11 +11,11 @@ %small = distance_of_time_in_words(planting.created_at, Time.zone.now) ago. - %p.col-md-offset-1 - = link_to "See all #{crop.name} plantings", plantings_by_crop_path(crop) + %p + = link_to "View all #{crop.name} plantings", plantings_by_crop_path(crop) - if current_member - %p.col-md-offset-1 - = link_to "Track your #{crop.name} plantings.", new_planting_path(:crop_id => crop.id) + %p + = link_to "Plant #{crop.name}", new_planting_path(:crop_id => crop.id) - else = render :partial => 'shared/signin_signup', :locals => { :to => "track your #{crop.name} plantings" } From dc1afb8913903760aada7f90a7f153222bac4c3e Mon Sep 17 00:00:00 2001 From: Skud Date: Sun, 19 Oct 2014 14:25:47 +0100 Subject: [PATCH 262/288] Added seeds_by_crop_path routes and stuff --- app/controllers/plantings_controller.rb | 6 +++--- app/controllers/seeds_controller.rb | 3 +++ app/views/harvests/index.html.haml | 4 ++-- app/views/seeds/index.html.haml | 6 +++--- config/routes.rb | 1 + spec/controllers/member_controller_spec.rb | 8 ++++---- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/app/controllers/plantings_controller.rb b/app/controllers/plantings_controller.rb index 62eaf5b79..b08fe98fd 100644 --- a/app/controllers/plantings_controller.rb +++ b/app/controllers/plantings_controller.rb @@ -9,11 +9,11 @@ class PlantingsController < ApplicationController @owner = Member.find_by_slug(params[:owner]) @crop = Crop.find_by_slug(params[:crop]) if @owner - @plantings = @owner.plantings.includes(:owner, :crop, :garden) + @plantings = @owner.plantings.includes(:owner, :crop, :garden).paginate(:page => params[:page]) elsif @crop - @plantings = @crop.plantings.includes(:owner, :crop, :garden) + @plantings = @crop.plantings.includes(:owner, :crop, :garden).paginate(:page => params[:page]) else - @plantings = Planting.includes(:owner, :crop, :garden) + @plantings = Planting.includes(:owner, :crop, :garden).paginate(:page => params[:page]) end respond_to do |format| diff --git a/app/controllers/seeds_controller.rb b/app/controllers/seeds_controller.rb index 30918b12e..03960624b 100644 --- a/app/controllers/seeds_controller.rb +++ b/app/controllers/seeds_controller.rb @@ -7,8 +7,11 @@ class SeedsController < ApplicationController # GET /seeds.json def index @owner = Member.find_by_slug(params[:owner]) + @crop = Crop.find_by_slug(params[:crop]) if @owner @seeds = @owner.seeds.includes(:owner, :crop).paginate(:page => params[:page]) + elsif @crop + @seeds = @crop.seeds.includes(:owner, :crop).paginate(:page => params[:page]) else @seeds = Seed.includes(:owner, :crop).paginate(:page => params[:page]) end diff --git a/app/views/harvests/index.html.haml b/app/views/harvests/index.html.haml index f80ce0dd5..f50129c9a 100644 --- a/app/views/harvests/index.html.haml +++ b/app/views/harvests/index.html.haml @@ -10,11 +10,11 @@ %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' + = link_to "View everyone's harvests", harvests_path, :class => 'btn btn-default' - 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' + = link_to 'View your harvests', harvests_by_owner_path(:owner => current_member.slug), :class => 'btn btn-default' - else = render :partial => 'shared/signin_signup', :locals => { :to => 'track your harvests' } diff --git a/app/views/seeds/index.html.haml b/app/views/seeds/index.html.haml index faee35465..9993e83ec 100644 --- a/app/views/seeds/index.html.haml +++ b/app/views/seeds/index.html.haml @@ -1,4 +1,4 @@ -- content_for :title, @owner ? "#{@owner}'s seeds" : "Everyone's seeds" +- content_for :title, @owner ? "#{@owner}'s seeds" : @crop ? "Everyone's #{@crop.name} seeds" : "Everyone's seeds" %p #{ENV['GROWSTUFF_SITE_NAME']} helps you track your seed @@ -10,11 +10,11 @@ %p - if @owner == current_member = link_to 'Add seeds', new_seed_path, :class => 'btn btn-primary' - = link_to "View everyone's seeds", seeds_path, :class => 'btn' + = link_to "View everyone's seeds", seeds_path, :class => 'btn btn-default' - else # everyone's seeds = link_to 'Add seeds', new_seed_path, :class => 'btn btn-primary' - if current_member - = link_to 'View your seeds', seeds_by_owner_path(:owner => current_member.slug), :class => 'btn' + = link_to 'View your seeds', seeds_by_owner_path(:owner => current_member.slug), :class => 'btn btn-default' - else = render :partial => 'shared/signin_signup', :locals => { :to => 'add seeds to your stash' } diff --git a/config/routes.rb b/config/routes.rb index 0d645a1be..bdef427d4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -19,6 +19,7 @@ Growstuff::Application.routes.draw do resources :seeds match '/seeds/owner/:owner' => 'seeds#index', :as => 'seeds_by_owner' + match '/seeds/crop/:crop' => 'seeds#index', :as => 'seeds_by_crop' resources :harvests match '/harvests/owner/:owner' => 'harvests#index', :as => 'harvests_by_owner' diff --git a/spec/controllers/member_controller_spec.rb b/spec/controllers/member_controller_spec.rb index 906a12b82..ff5a0ae42 100644 --- a/spec/controllers/member_controller_spec.rb +++ b/spec/controllers/member_controller_spec.rb @@ -17,17 +17,17 @@ describe MembersController do end describe "GET JSON index" do - it "does NOT provide JSON for members" do + it "provides JSON for members" do get :index, :format => 'json' - response.should_not be_success + response.should be_success end end describe "GET show" do - it "does NOT provide JSON for member profile" do + it "provides JSON for member profile" do get :show, { :id => @member.id , :format => 'json' } - response.should_not be_success + response.should be_success end it "assigns @posts with the member's posts" do From 1daee9612c9d1f00d0eccf918c125164c7e2c30f Mon Sep 17 00:00:00 2001 From: emmawinston Date: Sun, 19 Oct 2014 14:52:10 +0100 Subject: [PATCH 263/288] Changed set to album on photo page --- app/views/photos/new.html.haml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/views/photos/new.html.haml b/app/views/photos/new.html.haml index bc3eb35df..7b89997be 100644 --- a/app/views/photos/new.html.haml +++ b/app/views/photos/new.html.haml @@ -13,7 +13,7 @@ - if @sets and @sets.length > 0 %p = form_tag(new_photo_path, :method => :get, :class => 'form-inline') do - = label_tag :set, "Choose a photo set:", :class => 'control-label' + = label_tag :set, "Choose a photo album:", :class => 'control-label' = select_tag :set, options_for_select(@sets, @current_set), :class => 'input-large' = hidden_field_tag :type, @type = hidden_field_tag :id, @id @@ -36,4 +36,3 @@ You must =link_to "connect your account to Flickr", '/auth/flickr' to add photos. - From e5cd14cd4539a884e2d862f66baff7e2eb51b315 Mon Sep 17 00:00:00 2001 From: emmawinston Date: Sun, 19 Oct 2014 14:56:44 +0100 Subject: [PATCH 264/288] Added myself to the contributors file --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index cdec8a25a..650f8c321 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -47,3 +47,4 @@ submit the change with your pull request. - Cheri Allen / [cherimarie](https://github.com/cherimarie) - Maki Sugita / [macckii](https:://github.com/macckii) - Shiho Takagi / [oshiho3](https://github.com/oshiho3) +- Emma Winston / [emmawinston](https://github.com/emmawinston) From ef8d55230157516674d1f34592b1be7f66616c19 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sun, 19 Oct 2014 15:06:34 +0100 Subject: [PATCH 265/288] Update Ruby to 2.1.2 in .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f65de9cb5..7ed73fce0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ language: ruby env: GROWSTUFF_SITE_NAME="Growstuff (travis)" RAILS_SECRET_TOKEN='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' bundler_args: --without development production staging rvm: - - 2.1.1 + - 2.1.2 before_script: - psql -c 'create database growstuff_test;' -U postgres script: From 2aabcce70d9cfcb734410ebd8783308ead4959f6 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sun, 19 Oct 2014 12:13:17 +0100 Subject: [PATCH 266/288] Feature tests for scientific names. --- spec/features/scientific_name_spec.rb | 66 +++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 spec/features/scientific_name_spec.rb diff --git a/spec/features/scientific_name_spec.rb b/spec/features/scientific_name_spec.rb new file mode 100644 index 000000000..5a3a82989 --- /dev/null +++ b/spec/features/scientific_name_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +feature "Scientific names" do + let!(:zea_mays) { FactoryGirl.create(:zea_mays) } + let(:crop) { zea_mays.crop } + + scenario "Display scientific names on crop page" do + visit crop_path(zea_mays.crop) + expect(page).to have_content zea_mays.scientific_name + end + + scenario "Index page for scientific names" do + visit scientific_names_path + expect(page).to have_content zea_mays.scientific_name + end + + context "User is a crop wrangler" do + let!(:crop_wranglers) { FactoryGirl.create_list(:crop_wrangling_member, 3) } + let(:member){crop_wranglers.first} + + before :each do + login_as(member) + end + + scenario "Crop wranglers can edit scientific names" do + visit crop_path(crop) + expect(page).to have_content "CROP WRANGLER" + expect(page).to have_content zea_mays.scientific_name + expect(page).to have_link "Edit", :href => edit_scientific_name_path(zea_mays) + within('.scientific_names') { click_on "Edit" } + expect(page).to have_css "option[value='#{crop.id}'][selected=selected]" + fill_in 'Scientific name', with: "Zea mirabila" + click_on "Save" + expect(page).to have_content "Zea mirabila" + expect(page).to have_content 'Scientific name was successfully updated' + end + + scenario "Crop wranglers can delete scientific names" do + visit crop_path(zea_mays.crop) + expect(page).to have_link "Delete", + href: scientific_name_path(zea_mays) + within('.scientific_names') { click_on "Delete" } + expect(page).to_not have_content zea_mays.scientific_name + expect(page).to have_content 'Scientific name was successfully deleted' + end + + scenario "Crop wranglers can add scientific names" do + visit crop_path(crop) + expect(page).to have_link "Add", + href: new_scientific_name_path(crop_id: crop.id) + within('.scientific_names') { click_on "Add" } + expect(page).to have_css "option[value='#{crop.id}'][selected=selected]" + fill_in 'Scientific name', with: "Zea mirabila" + click_on "Save" + expect(page).to have_content "Zea mirabila" + expect(page).to have_content 'Scientific name was successfully created' + end + + scenario "The show-scientific-name page works" do + visit scientific_name_path(zea_mays) + expect(page).to have_content zea_mays.crop.name + end + + end + +end From 6de2112c4d042e95d0555d9b0ac8018ac6606cb2 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sun, 19 Oct 2014 12:16:05 +0100 Subject: [PATCH 267/288] Display note on successful sciname deletion. --- app/controllers/scientific_names_controller.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/scientific_names_controller.rb b/app/controllers/scientific_names_controller.rb index 202c3942c..3911a6618 100644 --- a/app/controllers/scientific_names_controller.rb +++ b/app/controllers/scientific_names_controller.rb @@ -83,7 +83,9 @@ class ScientificNamesController < ApplicationController @scientific_name.destroy respond_to do |format| - format.html { redirect_to @crop } + format.html { + redirect_to @crop, notice: 'Scientific name was successfully deleted.' + } format.json { head :no_content } end end From 56b7d89d9ea709033ae5d74eba182a0c694240bb Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sun, 19 Oct 2014 12:19:55 +0100 Subject: [PATCH 268/288] Link scinames to crops, don't show numeric crop IDs --- app/views/scientific_names/show.html.haml | 2 +- spec/features/scientific_name_spec.rb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/scientific_names/show.html.haml b/app/views/scientific_names/show.html.haml index 23e3a3c47..a3efe2d6f 100644 --- a/app/views/scientific_names/show.html.haml +++ b/app/views/scientific_names/show.html.haml @@ -5,7 +5,7 @@ = @scientific_name.scientific_name %p %b Crop: - = @scientific_name.crop_id + = link_to @scientific_name.crop, @scientific_name.crop = link_to 'Edit', edit_scientific_name_path(@scientific_name), :class => 'btn btn-default btn-xs' \| diff --git a/spec/features/scientific_name_spec.rb b/spec/features/scientific_name_spec.rb index 5a3a82989..01093a9c2 100644 --- a/spec/features/scientific_name_spec.rb +++ b/spec/features/scientific_name_spec.rb @@ -58,7 +58,8 @@ feature "Scientific names" do scenario "The show-scientific-name page works" do visit scientific_name_path(zea_mays) - expect(page).to have_content zea_mays.crop.name + expect(page).to have_link zea_mays.crop.name, + href: crop_path(zea_mays.crop) end end From b17aaf262dc04b916e46269d32362ba4ac74683a Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sun, 19 Oct 2014 12:25:41 +0100 Subject: [PATCH 269/288] Test HTTP statuses in sciname feature tests This makes it easier to tell the difference between "test failed because the expected content isn't there" and "test failed because the whole page is broken". It also guards against "test passed incorrectly because the expected content was part of the error message". --- spec/features/scientific_name_spec.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/features/scientific_name_spec.rb b/spec/features/scientific_name_spec.rb index 01093a9c2..1fda79d42 100644 --- a/spec/features/scientific_name_spec.rb +++ b/spec/features/scientific_name_spec.rb @@ -6,11 +6,13 @@ feature "Scientific names" do scenario "Display scientific names on crop page" do visit crop_path(zea_mays.crop) + expect(page.status_code).to equal 200 expect(page).to have_content zea_mays.scientific_name end scenario "Index page for scientific names" do visit scientific_names_path + expect(page.status_code).to equal 200 expect(page).to have_content zea_mays.scientific_name end @@ -24,10 +26,12 @@ feature "Scientific names" do scenario "Crop wranglers can edit scientific names" do visit crop_path(crop) + expect(page.status_code).to equal 200 expect(page).to have_content "CROP WRANGLER" expect(page).to have_content zea_mays.scientific_name expect(page).to have_link "Edit", :href => edit_scientific_name_path(zea_mays) within('.scientific_names') { click_on "Edit" } + expect(page.status_code).to equal 200 expect(page).to have_css "option[value='#{crop.id}'][selected=selected]" fill_in 'Scientific name', with: "Zea mirabila" click_on "Save" @@ -40,6 +44,7 @@ feature "Scientific names" do expect(page).to have_link "Delete", href: scientific_name_path(zea_mays) within('.scientific_names') { click_on "Delete" } + expect(page.status_code).to equal 200 expect(page).to_not have_content zea_mays.scientific_name expect(page).to have_content 'Scientific name was successfully deleted' end @@ -49,15 +54,18 @@ feature "Scientific names" do expect(page).to have_link "Add", href: new_scientific_name_path(crop_id: crop.id) within('.scientific_names') { click_on "Add" } + expect(page.status_code).to equal 200 expect(page).to have_css "option[value='#{crop.id}'][selected=selected]" fill_in 'Scientific name', with: "Zea mirabila" click_on "Save" + expect(page.status_code).to equal 200 expect(page).to have_content "Zea mirabila" expect(page).to have_content 'Scientific name was successfully created' end scenario "The show-scientific-name page works" do visit scientific_name_path(zea_mays) + expect(page.status_code).to equal 200 expect(page).to have_link zea_mays.crop.name, href: crop_path(zea_mays.crop) end From 58f1bca68362148bf15abec10c6a1b6062b5c198 Mon Sep 17 00:00:00 2001 From: Skud Date: Sun, 19 Oct 2014 16:51:08 +0100 Subject: [PATCH 270/288] removed spurious console.log calls --- app/assets/javascripts/members.js.erb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/assets/javascripts/members.js.erb b/app/assets/javascripts/members.js.erb index a3f29904d..f55f35732 100644 --- a/app/assets/javascripts/members.js.erb +++ b/app/assets/javascripts/members.js.erb @@ -6,7 +6,6 @@ if (document.getElementById("membermap") !== null) { $.getJSON(location.pathname + '.json', function(member) { - console.log(JSON.stringify(member.latitude)); if (member.latitude && member.longitude) { membermap = L.map('membermap').setView([member.latitude, member.longitude], 4); @@ -14,7 +13,6 @@ if (document.getElementById("membermap") !== null) { attribution: 'Map data © OpenStreetMap contributors under ODbL | Map imagery © Mapbox', maxZoom: 18 }).addTo(membermap); - console.log("found lat and long") marker = new L.Marker(new L.LatLng(member.latitude, member.longitude)); member_url = "/members/" + member.slug; From a25eb2d631ef1a8319237580f0c8bf4879e180de Mon Sep 17 00:00:00 2001 From: Skud Date: Sun, 19 Oct 2014 16:52:45 +0100 Subject: [PATCH 271/288] Deleted old view tests for crops --- spec/views/crops/show.html.haml_spec.rb | 242 ------------------------ 1 file changed, 242 deletions(-) delete mode 100644 spec/views/crops/show.html.haml_spec.rb diff --git a/spec/views/crops/show.html.haml_spec.rb b/spec/views/crops/show.html.haml_spec.rb deleted file mode 100644 index ca6166721..000000000 --- a/spec/views/crops/show.html.haml_spec.rb +++ /dev/null @@ -1,242 +0,0 @@ -require 'spec_helper' - -describe "crops/show" do - before(:each) do - controller.stub(:current_user) { nil } - @crop = FactoryGirl.create(:maize, - :scientific_names => [ FactoryGirl.create(:zea_mays) ] - ) - assign(:crop, @crop) - @author = FactoryGirl.create(:member) - page = 1 - per_page = 2 - total_entries = 2 - @posts = WillPaginate::Collection.create(page, per_page, total_entries) do |pager| - pager.replace([ - @post1 = FactoryGirl.create(:post, :author => @author, :body => "Post it!" ), - @post2 = FactoryGirl.create(:post, :author => @author, :body => "Done!" ) - ]) - end - end - - context 'photos' do - before(:each) do - @planting = FactoryGirl.create(:planting, :crop => @crop) - @photo1 = FactoryGirl.create(:photo) - @photo2 = FactoryGirl.create(:photo) - @photo3 = FactoryGirl.create(:photo) - @planting.photos << [@photo1, @photo2, @photo3] - render - end - - it 'shows 3 photos across the top of the page' do - assert_select "div.thumbnail>a>img", :count => 3 - end - - it 'links to the photo detail page' do - assert_select "a[href=#{photo_path(@photo1)}]" - end - - it 'links to the photo owner' do - assert_select "a[href=#{member_path(@photo1.owner)}]" - end - end - - context "map" do - it "has a map" do - render - assert_select("div#cropmap") - end - - it "explains what's shown on the map" do - render - rendered.should contain "Only plantings by members who have set their locations are shown on this map" - end - - it "shows a 'set your location' link to people who need to" do - @nowhere = FactoryGirl.create(:member) - sign_in @nowhere - controller.stub(:current_user) { @nowhere } - render - rendered.should contain "Set your location" - end - - it "doesn't show 'set your location' to people who have one" do - @somewhere = FactoryGirl.create(:london_member) - sign_in @somewhere - controller.stub(:current_user) { @somewhere } - render - rendered.should_not contain "Set your location" - end - - end - - it "shows the wikipedia URL" do - render - assert_select("a[href=#{@crop.en_wikipedia_url}]", 'Wikipedia (English)') - end - - it "shows the scientific name" do - render - rendered.should contain "Scientific names" - rendered.should contain "Zea mays" - end - - context "seeds available for trade" do - before(:each) do - @owner1 = FactoryGirl.create(:london_member) - @owner2 = FactoryGirl.create(:member) # no location - @seed1 = FactoryGirl.create(:tradable_seed, :owner => @owner1, :crop => @crop) - @seed2 = FactoryGirl.create(:tradable_seed, :owner => @owner2, :crop => @crop) - render - end - - it "shows a heading" do - rendered.should contain "Find seeds" - end - - it "shows a list of people with seeds to trade" do - @crop.seeds.each do |seed| - assert_select "a[href=#{seed_path(seed)}]" - end - end - end - - context "harvests" do - before(:each) do - @owner1 = FactoryGirl.create(:london_member) - @h1 = FactoryGirl.create(:harvest, :owner => @owner1, :crop => @crop) - @h2 = FactoryGirl.create(:harvest, :owner => @owner1, :crop => @crop) - render - end - - it "shows a heading" do - rendered.should contain "Harvests" - end - - it "shows a list of people who have harvested this crop" do - @crop.harvests.each do |harvest| - assert_select "a[href=#{harvest_path(harvest)}]" - end - end - end - - context "no seeds available for trade" do - it "shows a heading" do - render - rendered.should contain "Find seeds" - end - - it "suggests you trade seeds" do - render - rendered.should contain "There are no seeds available to trade." - end - end - - context "has plantings" do - before(:each) do - @owner = FactoryGirl.create(:london_member) - @planting = FactoryGirl.create(:planting, - :crop => @crop, - :owner => @owner - ) - @crop.reload # to pick up latest plantings_count - end - - it "links to people who are growing this crop" do - render - rendered.should contain @owner.login_name - rendered.should contain @owner.location - end - end - - context "has posts" do - it "links to posts" do - render - @posts.each do |p| - rendered.should contain p.author.login_name - rendered.should contain p.subject - rendered.should contain p.body - end - end - - it "contains two gravatar icons" do - render - assert_select "img", :src => /gravatar\.com\/avatar/, :count => 2 - end - end - - context 'varieties' do - before(:each) do - @popcorn = FactoryGirl.create(:popcorn, :parent_id => @crop.id) - @ubercrop = FactoryGirl.create(:crop, :name => 'ubercrop') - @crop.parent_id = @ubercrop.id - @crop.save - render - end - - it 'shows popcorn as a child variety' do - rendered.should contain @popcorn.name - end - - it 'shows parent crop' do - rendered.should contain @ubercrop.name - end - - end - - context 'logged in' do - before(:each) do - @member = FactoryGirl.create(:member) - sign_in @member - controller.stub(:current_user) { @member } - render - end - - it { rendered.should contain "Nobody has planted this crop yet" } - it { rendered.should contain "Nobody has harvested this crop yet" } - - context "should have a link to" do - before do - FactoryGirl.create(:planting, :crop => @crop) - FactoryGirl.create(:harvest, :crop => @crop) - @crop.reload - render - end - - it "show all plantings by the crop link" do - assert_select("a[href=#{plantings_by_crop_path @crop}]") - end - - it "show all harvests by the crop link" do - assert_select("a[href=#{harvests_by_crop_path @crop}]") - end - end - - - - end - - context "logged in and crop wrangler" do - - before(:each) do - @member = FactoryGirl.create(:crop_wrangling_member) - sign_in @member - controller.stub(:current_user) { @member } - render - end - - it "links to the edit crop form" do - assert_select "a[href=#{edit_crop_path(@crop)}]", :text => "Edit crop" - end - - it "links to the add scientific name form" do - assert_select "a[href^=#{new_scientific_name_path}]", :text => "Add" - end - - it "links to the edit scientific name form" do - assert_select "a[href=#{edit_scientific_name_path(@crop.scientific_names.first)}]", :text => "Edit" - end - end - -end From ab449a65f8ca261abc6b16d10986d4939eddec93 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Mon, 20 Oct 2014 21:27:39 +1100 Subject: [PATCH 272/288] remove 'to be translated' message from ja locale file --- config/locales/ja.yml | 75 +------------------------------------------ 1 file changed, 1 insertion(+), 74 deletions(-) diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 1f2fe3247..925a54399 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -2,77 +2,4 @@ ja: home: blurb: intro: "%{site_name}はガーデナーのコミュニティです。" - perks: "翻訳中" - sign_up: "翻訳中" - already_html: "翻訳中" - sign_in_linktext: "翻訳中" - - crops: - our_crops: "翻訳中" - recently_planted: "翻訳中" - recently_added: "翻訳中" - view_all: "翻訳中" - - discuss: - discussion: "翻訳中" - forums: "翻訳中" - view_all: "翻訳中" - - keep_in_touch: - keep_in_touch: "翻訳中" - twitter_html: "翻訳中" - twitter_linktext: "翻訳中" - blog_html: "翻訳中" - blog_linktext: "翻訳中" - newsletter_html: "翻訳中" - newsletter_linktext: "翻訳中" - - members: - title: "翻訳中" - view_all: "翻訳中" - - open: - open_source_title: "翻訳中" - open_source_body_html: "翻訳中" - why_linktext: "翻訳中" - github_linktext: "翻訳中" - open_data_title: "翻訳中" - open_data_body_html: "翻訳中" - creative_commons_linktext: "翻訳中" - wiki_linktext: "翻訳中" - api_docs_linktext: "翻訳中" - get_involved_title: "翻訳中" - get_involved_body_html: "翻訳中" - talk_linktext: "翻訳中" - wiki_linktext: "翻訳中" - support_title: "翻訳中" - support_body_html: "翻訳中" - ad_free_linktext: "翻訳中" - - - seeds: - title: "翻訳中" - owner: "翻訳中" - crop: "翻訳中" - description: "翻訳中" - trade_to: "翻訳中" - from: "翻訳中" - unspecified: "翻訳中" - details: "翻訳中" - view_all: "翻訳中" - - stats: - message_html: "翻訳中" - member_linktext: "翻訳中" - number_crops_linktext: "翻訳中" - number_plantings_linktext: "翻訳中" - number_gardens_linktext: "翻訳中" - - index: - welcome: "翻訳中" - plant: "翻訳中" - harvest: "翻訳中" - add_seeds: "翻訳中" - post: "翻訳中" - edit_profile: "翻訳中" - + \ No newline at end of file From a6508b5c9fe804e80127ab024ea9f11c109fcab8 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Mon, 20 Oct 2014 22:47:13 +1100 Subject: [PATCH 273/288] replace debugger with byebug --- Gemfile | 4 +--- Gemfile.lock | 11 +++++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Gemfile b/Gemfile index 2360041eb..310292a25 100644 --- a/Gemfile +++ b/Gemfile @@ -80,9 +80,6 @@ gem 'flickraw' # To use debugger group :development do - # Installation of the debugger gem fails on Travis CI, - # so we don't use it in the test environment - gem 'debugger' # A debugger and irb alternative. Pry doesn't play nice # with unicorn, so start a Webrick server when debugging # with Pry @@ -123,6 +120,7 @@ gem 'omniauth-flickr', '>= 0.0.15' gem 'rake', '>= 10.0.0' group :development, :test do + gem 'byebug' # debugging gem 'haml-rails' # HTML templating language gem 'rspec-rails', '~> 2.12.1' # unit testing framework gem 'database_cleaner', '~> 1.3.0' diff --git a/Gemfile.lock b/Gemfile.lock index fae16d6ad..cb7cb2688 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -60,6 +60,10 @@ GEM bootstrap-datepicker-rails (1.3.0.2) railties (>= 3.0) builder (3.0.4) + byebug (3.5.1) + columnize (~> 0.8) + debugger-linecache (~> 1.2) + slop (~> 3.6) cancan (1.6.10) capybara (2.4.1) mime-types (>= 1.16) @@ -96,12 +100,7 @@ GEM dalli (2.7.2) database_cleaner (1.3.0) debug_inspector (0.0.2) - debugger (1.6.8) - columnize (>= 0.3.1) - debugger-linecache (~> 1.2.0) - debugger-ruby_core_source (~> 1.3.5) debugger-linecache (1.2.0) - debugger-ruby_core_source (1.3.5) devise (3.2.4) bcrypt (~> 3.0) orm_adapter (~> 0.1) @@ -328,6 +327,7 @@ DEPENDENCIES bluecloth bootstrap-datepicker-rails bundler (>= 1.1.5) + byebug cancan capybara coffee-rails (~> 3.2.1) @@ -336,7 +336,6 @@ DEPENDENCIES csv_shaper dalli database_cleaner (~> 1.3.0) - debugger devise (~> 3.2.0) factory_girl_rails (~> 4.0) figaro From 48875dacb3bcb8440b93b9f9c42f1c80c5921f02 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Tue, 21 Oct 2014 06:53:21 +1100 Subject: [PATCH 274/288] remove pry because it's redundant with byebug --- Gemfile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Gemfile b/Gemfile index 310292a25..1739744bf 100644 --- a/Gemfile +++ b/Gemfile @@ -80,10 +80,6 @@ gem 'flickraw' # To use debugger group :development do - # A debugger and irb alternative. Pry doesn't play nice - # with unicorn, so start a Webrick server when debugging - # with Pry - gem 'pry' gem 'better_errors' gem 'binding_of_caller' gem 'letter_opener' From c244da8b73c98945d49340ede84946bfbcbf291b Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Tue, 21 Oct 2014 07:24:08 +1100 Subject: [PATCH 275/288] make English fallback language --- Gemfile | 3 --- Gemfile.lock | 8 -------- config/application.rb | 8 ++++++-- config/initializers/locale.rb | 4 ---- 4 files changed, 6 insertions(+), 17 deletions(-) delete mode 100644 config/initializers/locale.rb diff --git a/Gemfile b/Gemfile index 2360041eb..124c1f4f9 100644 --- a/Gemfile +++ b/Gemfile @@ -80,9 +80,6 @@ gem 'flickraw' # To use debugger group :development do - # Installation of the debugger gem fails on Travis CI, - # so we don't use it in the test environment - gem 'debugger' # A debugger and irb alternative. Pry doesn't play nice # with unicorn, so start a Webrick server when debugging # with Pry diff --git a/Gemfile.lock b/Gemfile.lock index fae16d6ad..6f6b7d1a5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -77,7 +77,6 @@ GEM coffee-script-source execjs coffee-script-source (1.8.0) - columnize (0.8.9) commonjs (0.2.7) compass (0.12.7) chunky_png (~> 1.2) @@ -96,12 +95,6 @@ GEM dalli (2.7.2) database_cleaner (1.3.0) debug_inspector (0.0.2) - debugger (1.6.8) - columnize (>= 0.3.1) - debugger-linecache (~> 1.2.0) - debugger-ruby_core_source (~> 1.3.5) - debugger-linecache (1.2.0) - debugger-ruby_core_source (1.3.5) devise (3.2.4) bcrypt (~> 3.0) orm_adapter (~> 0.1) @@ -336,7 +329,6 @@ DEPENDENCIES csv_shaper dalli database_cleaner (~> 1.3.0) - debugger devise (~> 3.2.0) factory_girl_rails (~> 4.0) figaro diff --git a/config/application.rb b/config/application.rb index 6bbe1fe7d..5b01d3aa8 100644 --- a/config/application.rb +++ b/config/application.rb @@ -31,8 +31,12 @@ module Growstuff config.active_record.default_timezone = :local # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. - # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] - # config.i18n.default_locale = :de + I18n.load_path += Dir[Rails.root.join('config', 'locales', '*.{rb,yml}')] + I18n.default_locale = :en + # rails will fallback to config.i18n.default_locale translation + config.i18n.fallbacks = true + # rails will fallback to en, no matter what is set as config.i18n.default_locale + config.i18n.fallbacks = [:en] # Configure the default encoding used in templates for Ruby 1.9. config.encoding = "utf-8" diff --git a/config/initializers/locale.rb b/config/initializers/locale.rb deleted file mode 100644 index fb251b800..000000000 --- a/config/initializers/locale.rb +++ /dev/null @@ -1,4 +0,0 @@ -I18n.load_path += Dir[Rails.root.join('config', 'locales', '*.{rb,yml}')] -I18n.default_locale = :en - - \ No newline at end of file From e9d1d11535b08d4baca9c4266d4ee3345fb668d3 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Tue, 21 Oct 2014 19:43:51 +1100 Subject: [PATCH 276/288] add append date functionality for planting finished on garden show page and planting list page --- Gemfile | 4 ---- Gemfile.lock | 8 -------- app/views/plantings/_thumbnail.html.haml | 2 +- app/views/plantings/index.html.haml | 2 +- 4 files changed, 2 insertions(+), 14 deletions(-) diff --git a/Gemfile b/Gemfile index 2360041eb..65000a6d0 100644 --- a/Gemfile +++ b/Gemfile @@ -78,11 +78,7 @@ gem 'flickraw' # Use unicorn as the app server # gem 'unicorn' -# To use debugger group :development do - # Installation of the debugger gem fails on Travis CI, - # so we don't use it in the test environment - gem 'debugger' # A debugger and irb alternative. Pry doesn't play nice # with unicorn, so start a Webrick server when debugging # with Pry diff --git a/Gemfile.lock b/Gemfile.lock index fae16d6ad..6f6b7d1a5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -77,7 +77,6 @@ GEM coffee-script-source execjs coffee-script-source (1.8.0) - columnize (0.8.9) commonjs (0.2.7) compass (0.12.7) chunky_png (~> 1.2) @@ -96,12 +95,6 @@ GEM dalli (2.7.2) database_cleaner (1.3.0) debug_inspector (0.0.2) - debugger (1.6.8) - columnize (>= 0.3.1) - debugger-linecache (~> 1.2.0) - debugger-ruby_core_source (~> 1.3.5) - debugger-linecache (1.2.0) - debugger-ruby_core_source (1.3.5) devise (3.2.4) bcrypt (~> 3.0) orm_adapter (~> 0.1) @@ -336,7 +329,6 @@ DEPENDENCIES csv_shaper dalli database_cleaner (~> 1.3.0) - debugger devise (~> 3.2.0) factory_girl_rails (~> 4.0) figaro diff --git a/app/views/plantings/_thumbnail.html.haml b/app/views/plantings/_thumbnail.html.haml index c8588abf5..4d2b6cdee 100644 --- a/app/views/plantings/_thumbnail.html.haml +++ b/app/views/plantings/_thumbnail.html.haml @@ -42,6 +42,6 @@ - if can? :edit, planting =link_to 'Edit', edit_planting_path(planting), :class => 'btn btn-default btn-xs' - if ! planting.finished - = link_to "Mark as finished", planting_path(planting, :planting => {:finished => 1}), :method => :put, :class => 'btn btn-default btn-xs' + = link_to "Mark as finished", planting_path(planting, :planting => {:finished => 1}), :method => :put, :class => 'btn btn-default btn-xs append-date' - if can? :destroy, planting =link_to 'Delete', planting, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' diff --git a/app/views/plantings/index.html.haml b/app/views/plantings/index.html.haml index eb1e017dc..e3edabae5 100644 --- a/app/views/plantings/index.html.haml +++ b/app/views/plantings/index.html.haml @@ -53,7 +53,7 @@ - if can? :edit, planting =link_to 'Edit', edit_planting_path(planting), :class => 'btn btn-default btn-xs' - if ! planting.finished - = link_to "Mark as finished", planting_path(planting, :planting => {:finished => 1}), :method => :put, :class => 'btn btn-default btn-xs' + = link_to "Mark as finished", planting_path(planting, :planting => {:finished => 1}), :method => :put, :class => 'btn btn-default btn-xs append-date' - if can? :destroy, planting =link_to 'Delete', planting, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs' From 9b10a7c3ceb0d9cb1f8b368342ceb14ac6e36cbb Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Thu, 23 Oct 2014 22:18:52 +1100 Subject: [PATCH 277/288] write shared example group for append date --- Gemfile | 1 + Gemfile.lock | 7 ++++++ spec/features/gardens_spec.rb | 9 +++++++ .../plantings/planting_a_crop_spec.rb | 24 +++++++++---------- .../shared_examples/append_date_spec.rb | 15 ++++++++++++ .../shared_examples/crop_suggest_spec.rb | 2 -- 6 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 spec/features/shared_examples/append_date_spec.rb diff --git a/Gemfile b/Gemfile index 65000a6d0..939a146b2 100644 --- a/Gemfile +++ b/Gemfile @@ -119,6 +119,7 @@ gem 'omniauth-flickr', '>= 0.0.15' gem 'rake', '>= 10.0.0' group :development, :test do + gem 'byebug' gem 'haml-rails' # HTML templating language gem 'rspec-rails', '~> 2.12.1' # unit testing framework gem 'database_cleaner', '~> 1.3.0' diff --git a/Gemfile.lock b/Gemfile.lock index 6f6b7d1a5..cb7cb2688 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -60,6 +60,10 @@ GEM bootstrap-datepicker-rails (1.3.0.2) railties (>= 3.0) builder (3.0.4) + byebug (3.5.1) + columnize (~> 0.8) + debugger-linecache (~> 1.2) + slop (~> 3.6) cancan (1.6.10) capybara (2.4.1) mime-types (>= 1.16) @@ -77,6 +81,7 @@ GEM coffee-script-source execjs coffee-script-source (1.8.0) + columnize (0.8.9) commonjs (0.2.7) compass (0.12.7) chunky_png (~> 1.2) @@ -95,6 +100,7 @@ GEM dalli (2.7.2) database_cleaner (1.3.0) debug_inspector (0.0.2) + debugger-linecache (1.2.0) devise (3.2.4) bcrypt (~> 3.0) orm_adapter (~> 0.1) @@ -321,6 +327,7 @@ DEPENDENCIES bluecloth bootstrap-datepicker-rails bundler (>= 1.1.5) + byebug cancan capybara coffee-rails (~> 3.2.1) diff --git a/spec/features/gardens_spec.rb b/spec/features/gardens_spec.rb index 1b803a41e..2b4c22d8b 100644 --- a/spec/features/gardens_spec.rb +++ b/spec/features/gardens_spec.rb @@ -2,6 +2,8 @@ require 'spec_helper' feature "Planting a crop", :js => true do let!(:garden) { FactoryGirl.create(:garden) } + let!(:planting) { FactoryGirl.create(:planting, garden: garden, planted_at: Date.parse("2013-3-10")) } + background do login_as(garden.owner) @@ -54,4 +56,11 @@ feature "Planting a crop", :js => true do expect(page).to have_content "#{garden.owner}'s gardens" end + describe "Making a planting inactive from garden show" do + let(:path) { garden_path(garden) } + let(:link_text) { "Mark as finished" } + + it_behaves_like "append date" + end + end diff --git a/spec/features/plantings/planting_a_crop_spec.rb b/spec/features/plantings/planting_a_crop_spec.rb index d8fae5c64..3f903887a 100644 --- a/spec/features/plantings/planting_a_crop_spec.rb +++ b/spec/features/plantings/planting_a_crop_spec.rb @@ -4,14 +4,14 @@ feature "Planting a crop", :js => true do let(:member) { FactoryGirl.create(:member) } let!(:maize) { FactoryGirl.create(:maize) } let(:garden) { FactoryGirl.create(:garden, owner: member) } - let(:planting) { FactoryGirl.create(:planting, garden: garden, planted_at: Date.parse("2013-3-10")) } + let!(:planting) { FactoryGirl.create(:planting, garden: garden, planted_at: Date.parse("2013-3-10")) } background do login_as(member) visit "/plantings/new" end - it_behaves_like "crop suggest", "planting", "crop" + it_behaves_like "crop suggest", "planting" scenario "Creating a new planting" do fill_autocomplete "crop", :with => "m" @@ -84,16 +84,16 @@ feature "Planting a crop", :js => true do expect(page).to have_content "Finished: Yes (no date specified)" end - scenario "Marking a planting as finished from the show page" do - this_month = Date.today.strftime("%B") - this_year = Date.today.strftime("%Y") - visit planting_path(planting) - click_link "Mark as finished" - within "div.datepicker" do - expect(page).to have_content "#{this_month}" - page.find(".datepicker-days td.day", text: "21").click - end - expect(page).to have_content "Finished: #{this_month} 21, #{this_year}" + describe "Marking a planting as finished from the show page" do + let(:path) { planting_path(planting) } + let(:link_text) { "Mark as finished" } + it_behaves_like "append date" + end + + describe "Marking a planting as finished from the list page" do + let(:path) { plantings_path } + let(:link_text) { "Mark as finished" } + it_behaves_like "append date" end end diff --git a/spec/features/shared_examples/append_date_spec.rb b/spec/features/shared_examples/append_date_spec.rb new file mode 100644 index 000000000..bf4a297b1 --- /dev/null +++ b/spec/features/shared_examples/append_date_spec.rb @@ -0,0 +1,15 @@ +shared_examples "append date" do + + scenario "Displaying a datepicker" do + this_month = Date.today.strftime("%B") + this_year = Date.today.strftime("%Y") + visit path + click_link link_text + within "div.datepicker" do + expect(page).to have_content "#{this_month}" + page.find(".datepicker-days td.day", text: "21").click + end + expect(page).to have_content "Finished: #{this_month} 21, #{this_year}" + end + +end \ No newline at end of file diff --git a/spec/features/shared_examples/crop_suggest_spec.rb b/spec/features/shared_examples/crop_suggest_spec.rb index 499bab891..da10072a3 100644 --- a/spec/features/shared_examples/crop_suggest_spec.rb +++ b/spec/features/shared_examples/crop_suggest_spec.rb @@ -1,5 +1,3 @@ -require 'spec_helper' - shared_examples "crop suggest" do |resource| let!(:popcorn) { FactoryGirl.create(:popcorn) } let!(:pear) { FactoryGirl.create(:pear) } From 0ccd6e843a1b1e79de69e89ee00aab965b51423a Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Thu, 23 Oct 2014 22:43:38 +1100 Subject: [PATCH 278/288] implement confirm without date functionality on appty on append date js --- app/assets/javascripts/append_date.js.coffee | 18 +++++++++++++++++- .../shared_examples/append_date_spec.rb | 9 ++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/append_date.js.coffee b/app/assets/javascripts/append_date.js.coffee index cf54f4654..8fc31de9d 100644 --- a/app/assets/javascripts/append_date.js.coffee +++ b/app/assets/javascripts/append_date.js.coffee @@ -8,12 +8,28 @@ jQuery -> el.datepicker({'format': 'yyyy-mm-dd'}) + href = el.attr('href') + + originalText = el.text() + el.click (e) -> e.stopPropagation() e.preventDefault() + $(this).text('Confirm without date') + + $(this).bind('click.confirm', (e) -> + link = $("") + $('body').append(link) + $(link).click() + ) + + $(this).blur (e) -> + $(this).text(originalText) + $(this).unbind('click.confirm') + + el.one 'changeDate', -> - href = $(this).attr('href') date = $(this).datepicker('getDate') url = "#{href}&planting[finished_at]=#{date}" diff --git a/spec/features/shared_examples/append_date_spec.rb b/spec/features/shared_examples/append_date_spec.rb index bf4a297b1..089680ff9 100644 --- a/spec/features/shared_examples/append_date_spec.rb +++ b/spec/features/shared_examples/append_date_spec.rb @@ -1,6 +1,6 @@ shared_examples "append date" do - scenario "Displaying a datepicker" do + scenario "Selecting a date with datepicker" do this_month = Date.today.strftime("%B") this_year = Date.today.strftime("%Y") visit path @@ -12,4 +12,11 @@ shared_examples "append date" do expect(page).to have_content "Finished: #{this_month} 21, #{this_year}" end + scenario "Confirming without selecting date" do + visit path + click_link link_text + click_link "Confirm without date" + expect(page).to have_content("Finished: Yes (no date specified) ") + end + end \ No newline at end of file From 6921119301a71389c6d5cbfcd5e847e440a0f449 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sun, 2 Nov 2014 22:21:32 +0000 Subject: [PATCH 279/288] Make Capybara actually hit the server. Background: http://talk.growstuff.org/t/mysteriously-broken-feature-tests/120 @oshiho3's suggested fix didn't work, so I tried all the suggestions at http://stackoverflow.com/questions/6536503/capybara-with-subdomains-default-host until I found one that fixed the problem. --- config/environments/test.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/environments/test.rb b/config/environments/test.rb index 6f49b5ef0..6fb8cad3e 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -86,3 +86,7 @@ Geocoder::Lookup::Test.add_stub( # Unknown location Geocoder::Lookup::Test.add_stub( "Tatooine", []) + +Capybara.configure do |config| + config.always_include_port = true +end From b9891688878e12746b8d62fa17219854f373a213 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Sun, 2 Nov 2014 22:47:14 +0000 Subject: [PATCH 280/288] Fix "shared example group previously defined" warning We were getting the warning ``` WARNING: Shared example group 'crop suggest' has been previously defined at: /Users/miles/src/growstuff/spec/features/shared_examples/crop_suggest_spec.rb:3 ...and you are now defining it at: /Users/miles/src/growstuff/spec/features/shared_examples/crop_suggest_spec.rb:3 The new definition will overwrite the original one. ``` Following the suggestion at https://github.com/rspec/rspec-core/issues/828#issuecomment-38789977, I've renamed crop_suggest_spec.rb to crop_suggest.rb, which made the error go away without reducing the number of tests run. RSpec must have thought it was a spec file and loaded it directly, then loaded it again when it was first required by an actual spec file. --- .../shared_examples/{crop_suggest_spec.rb => crop_suggest.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename spec/features/shared_examples/{crop_suggest_spec.rb => crop_suggest.rb} (100%) diff --git a/spec/features/shared_examples/crop_suggest_spec.rb b/spec/features/shared_examples/crop_suggest.rb similarity index 100% rename from spec/features/shared_examples/crop_suggest_spec.rb rename to spec/features/shared_examples/crop_suggest.rb From 2426c51951f0148a287fa41019623ef78b85cfb6 Mon Sep 17 00:00:00 2001 From: Skud Date: Tue, 11 Nov 2014 19:29:32 +1100 Subject: [PATCH 281/288] Fixed bug in deleting unused photos ... and wow, my tests were really not testing what I thought they were testing :-/ --- app/models/photo.rb | 2 +- spec/models/photo_spec.rb | 21 ++++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/app/models/photo.rb b/app/models/photo.rb index 32679eeed..862b9d28b 100644 --- a/app/models/photo.rb +++ b/app/models/photo.rb @@ -14,7 +14,7 @@ class Photo < ActiveRecord::Base # remove photos that aren't used by anything def destroy_if_unused - unless plantings.size > 0 and harvests.size > 0 + unless plantings.size > 0 or harvests.size > 0 self.destroy end end diff --git a/spec/models/photo_spec.rb b/spec/models/photo_spec.rb index 2a0b1d5ff..f17127301 100644 --- a/spec/models/photo_spec.rb +++ b/spec/models/photo_spec.rb @@ -39,16 +39,20 @@ describe Photo do expect(lambda { photo.reload }).to raise_error ActiveRecord::RecordNotFound end - it 'they are no longer used by plantings' do + it 'they are used by plantings but not harvests' do + harvest.photos << photo planting.photos << photo - planting.destroy # photo is now no longer used by anything - expect(lambda { photo.reload }).to raise_error ActiveRecord::RecordNotFound + harvest.destroy # photo is now used by harvest but not planting + photo.destroy_if_unused + expect(lambda { photo.reload }).not_to raise_error ActiveRecord::RecordNotFound end - it 'they are no longer used by harvests' do + it 'they are used by harvests but not plantings' do harvest.photos << photo - harvest.destroy # photo is now no longer used by anything - expect(lambda { photo.reload }).to raise_error ActiveRecord::RecordNotFound + planting.photos << photo + planting.destroy # photo is now used by harvest but not planting + photo.destroy_if_unused + expect(lambda { photo.reload }).not_to raise_error ActiveRecord::RecordNotFound end it 'they are no longer used by anything' do @@ -59,11 +63,14 @@ describe Photo do planting.destroy # photo is still used by harvest photo.reload - expect(photo).to be_an_instance_of Photo expect(photo.plantings.size).to eq 0 expect(photo.harvests.size).to eq 1 harvest.destroy # photo is now no longer used by anything + photo.reload + expect(photo.plantings.size).to eq 0 + expect(photo.harvests.size).to eq 0 + photo.destroy_if_unused expect(lambda { photo.reload }).to raise_error ActiveRecord::RecordNotFound end From eb98910bea16d747b3fb5414dedf671b7d4c93eb Mon Sep 17 00:00:00 2001 From: Skud Date: Tue, 11 Nov 2014 19:45:07 +1100 Subject: [PATCH 282/288] Only send planting reminders to confirmed accounts --- lib/tasks/growstuff.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/growstuff.rake b/lib/tasks/growstuff.rake index c1043963d..4dd11bbcf 100644 --- a/lib/tasks/growstuff.rake +++ b/lib/tasks/growstuff.rake @@ -48,7 +48,7 @@ namespace :growstuff do every_n_weeks = 2 # send fortnightly if Date.today.cwday == send_on_day and Date.today.cw_week % every_n_weeks == 0 - Member.find_each do |m| + Member.confirmed.find_each do |m| Notifier.planting_reminder(m).deliver! end end From 50b8acc67cd51ca9ba131d56a473ef1ced163401 Mon Sep 17 00:00:00 2001 From: Taylor Griffin Date: Tue, 11 Nov 2014 19:52:31 +1100 Subject: [PATCH 283/288] put finished at bottom of list to make it more obvious when marking a planting finished --- app/views/plantings/_thumbnail.html.haml | 16 ++++++++-------- app/views/plantings/show.html.haml | 17 +++++++++-------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/app/views/plantings/_thumbnail.html.haml b/app/views/plantings/_thumbnail.html.haml index 4d2b6cdee..fd34b0703 100644 --- a/app/views/plantings/_thumbnail.html.haml +++ b/app/views/plantings/_thumbnail.html.haml @@ -17,14 +17,6 @@ in = link_to planting.location, planting.garden - - if planting.finished - %p - Finished: - - if planting.finished_at - = planting.finished_at - - else - Yes (no date specified) - %p - if planting.quantity Quantity: @@ -37,6 +29,14 @@ :growstuff_markdown #{ planting.description } + - if planting.finished + %p + Finished: + - if planting.finished_at + = planting.finished_at + - else + Yes (no date specified) + - if can? :edit, planting or can? :destroy, planting %p - if can? :edit, planting diff --git a/app/views/plantings/show.html.haml b/app/views/plantings/show.html.haml index cc3d58bae..72819f7ac 100644 --- a/app/views/plantings/show.html.haml +++ b/app/views/plantings/show.html.haml @@ -11,14 +11,6 @@ %b Planted: = @planting.planted_at ? @planting.planted_at : "not specified" - - if @planting.finished - %p - %b Finished: - - if @planting.finished_at - = @planting.finished_at - - else - Yes (no date specified) - %p %b Where: =link_to "#{@planting.owner}'s", @planting.owner @@ -39,6 +31,15 @@ %b Sun or shade? = @planting.sunniness + - if @planting.finished + %p + %b Finished: + - if @planting.finished_at + = @planting.finished_at + - else + Yes (no date specified) + + - if can? :edit, @planting or can? :destroy, @planting %p From 267c67125922dd7fbc5f3a5681c914d2871a4391 Mon Sep 17 00:00:00 2001 From: Skud Date: Tue, 11 Nov 2014 20:55:04 +1100 Subject: [PATCH 284/288] cweek not cw_week --- lib/tasks/growstuff.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/growstuff.rake b/lib/tasks/growstuff.rake index 4dd11bbcf..0cf0a50f7 100644 --- a/lib/tasks/growstuff.rake +++ b/lib/tasks/growstuff.rake @@ -47,7 +47,7 @@ namespace :growstuff do send_on_day = 3 # wednesday every_n_weeks = 2 # send fortnightly - if Date.today.cwday == send_on_day and Date.today.cw_week % every_n_weeks == 0 + if Date.today.cwday == send_on_day and Date.today.cweek % every_n_weeks == 0 Member.confirmed.find_each do |m| Notifier.planting_reminder(m).deliver! end From caad748d081107203822ab2e041ea19cd57cf752 Mon Sep 17 00:00:00 2001 From: Yoong Kang Lim Date: Tue, 11 Nov 2014 20:54:16 +1100 Subject: [PATCH 285/288] Plantings index should display finished at date instead of planted at under the finished column. Added feature test for plantings index page. --- app/views/plantings/index.html.haml | 2 +- spec/features/plantings/planting_a_crop_spec.rb | 3 +++ spec/views/plantings/index.html.haml_spec.rb | 15 +++++++++++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/views/plantings/index.html.haml b/app/views/plantings/index.html.haml index eb1e017dc..fe69ed344 100644 --- a/app/views/plantings/index.html.haml +++ b/app/views/plantings/index.html.haml @@ -43,7 +43,7 @@ %td= planting.planted_at %td - if planting.finished and planting.finished_at - = planting.planted_at + = planting.finished_at - elsif planting.finished Yes (no date specified) %td= planting.sunniness diff --git a/spec/features/plantings/planting_a_crop_spec.rb b/spec/features/plantings/planting_a_crop_spec.rb index ab120fa48..99114ab43 100644 --- a/spec/features/plantings/planting_a_crop_spec.rb +++ b/spec/features/plantings/planting_a_crop_spec.rb @@ -71,6 +71,9 @@ feature "Planting a crop", :js => true do end expect(page).to have_content "Planting was successfully created" expect(page).to have_content "Finished: August 30, 2014" + + visit plantings_path + expect(page).to have_content "August 30, 2014" end scenario "Marking a planting as finished without a date" do diff --git a/spec/views/plantings/index.html.haml_spec.rb b/spec/views/plantings/index.html.haml_spec.rb index d46b4e003..f80331117 100644 --- a/spec/views/plantings/index.html.haml_spec.rb +++ b/spec/views/plantings/index.html.haml_spec.rb @@ -8,8 +8,8 @@ describe "plantings/index" do @tomato = FactoryGirl.create(:tomato) @maize = FactoryGirl.create(:maize) page = 1 - per_page = 2 - total_entries = 2 + per_page = 3 + total_entries = 3 plantings = WillPaginate::Collection.create(page, per_page, total_entries) do |pager| pager.replace([ FactoryGirl.create(:planting, @@ -22,6 +22,13 @@ describe "plantings/index" do :crop => @maize, :description => '', :planted_at => Time.local(2013, 1, 13) + ), + FactoryGirl.create(:planting, + :garden => @garden, + :crop => @tomato, + :planted_at => Time.local(2013, 1, 13), + :finished_at => Time.local(2013, 1, 20), + :finished => true ) ]) end @@ -40,6 +47,10 @@ describe "plantings/index" do rendered.should contain 'January 13, 2013' end + it "displays finished time" do + rendered.should contain 'January 20, 2013' + end + it "provides data links" do render rendered.should contain "The data on this page is available in the following formats:" From 7023b4969bc3fa8a3e6a7ad9f31fbc74a99f8cf7 Mon Sep 17 00:00:00 2001 From: Kevin Rio Date: Tue, 11 Nov 2014 22:42:01 -0500 Subject: [PATCH 286/288] Update position of activity on member details page Switched activity and contact. --- app/views/members/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/members/show.html.haml b/app/views/members/show.html.haml index edf4d0b68..afb10d932 100644 --- a/app/views/members/show.html.haml +++ b/app/views/members/show.html.haml @@ -22,5 +22,5 @@ .col-md-3 = render :partial => "avatar", :locals => { :member => @member } = render :partial => "account", :locals => { :member => @member } - = render :partial => "contact", :locals => { :member => @member, :twitter_auth => @twitter_auth, :flickr_auth => @flickr_auth } = render :partial => "stats", :locals => { :member => @member } + = render :partial => "contact", :locals => { :member => @member, :twitter_auth => @twitter_auth, :flickr_auth => @flickr_auth } From b6d2a078b17a61d4e476141a4996e6faee5bda4c Mon Sep 17 00:00:00 2001 From: Kevin Rio Date: Tue, 11 Nov 2014 23:28:22 -0500 Subject: [PATCH 287/288] Adding to contributors list --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 650f8c321..d4e5085ee 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -48,3 +48,4 @@ submit the change with your pull request. - Maki Sugita / [macckii](https:://github.com/macckii) - Shiho Takagi / [oshiho3](https://github.com/oshiho3) - Emma Winston / [emmawinston](https://github.com/emmawinston) +- Kevin Rio / [krio](https://github.com/krio) From bfce66a8f7eea8050a7be029053f1d54959157a2 Mon Sep 17 00:00:00 2001 From: Skud Date: Thu, 13 Nov 2014 11:28:14 +1100 Subject: [PATCH 288/288] Increase devise timeout to 30 days --- config/initializers/devise.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index eb51fab96..801a6677f 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -126,8 +126,8 @@ Devise.setup do |config| # ==> Configuration for :timeoutable # The time you want to timeout the user session without activity. After this # time the user will be asked for credentials again. Default is 30 minutes. - # config.timeout_in = 30.minutes - + config.timeout_in = 30.days + # If true, expires auth token on session timeout. # config.expire_auth_token_on_timeout = false