Compare commits

..

7 Commits

Author SHA1 Message Date
Skud
d066801943 Merge pull request #616 from pmackay/api_v1
Api v1 - initial start
2015-01-13 10:50:01 +11:00
Paul Mackay
016feaf8bf #582: Change license checks to expect() syntax. 2015-01-12 10:25:00 +00:00
Paul Mackay
1b5d2bb898 #582: Fixes for review comments. 2015-01-11 19:25:40 +00:00
Paul Mackay
a3e3abecf3 Merge api-v1-jbuilder into new API branch. 2015-01-07 08:41:26 +00:00
Paul Mackay
223a06ef8b Some crop API tweaks, basic LD stuff. 2014-11-07 07:03:27 +00:00
Paul Mackay
f3eb5774ef Add spec test for 2 crop API calls, including license. 2014-11-03 16:45:37 +00:00
Paul Mackay
800a1f10e3 First commit for v1 API implementation based on jbuilder. 2014-11-03 15:03:59 +00:00
554 changed files with 5464 additions and 11435 deletions

2
.gitignore vendored
View File

@@ -13,5 +13,3 @@ Pathogen:
custom_plan.rb custom_plan.rb
zeus.json zeus.json
.bundle .bundle
config/application.yml
.idea/**

1
.rspec
View File

@@ -1,2 +1 @@
--color --color
--require spec_helper

View File

@@ -1 +1 @@
2.2.4 2.1.5

View File

@@ -1,37 +1,12 @@
sudo: false ---
language: ruby language: ruby
cache: bundler
env:
matrix:
- GROWSTUFF_SITE_NAME="Growstuff (travis)" RAILS_SECRET_TOKEN='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' GROWSTUFF_ELASTICSEARCH='true'
- GROWSTUFF_SITE_NAME="Growstuff (travis)" RAILS_SECRET_TOKEN='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' GROWSTUFF_ELASTICSEARCH='false'
global:
secure: "Z5TpM2jEX4UCvNePnk/LwltQX48U2u9BRc+Iypr1x9QW2o228QJhPIOH39a8RMUrepGnkQIq9q3ZRUn98RfrJz1yThtlNFL3NmzdQ57gKgjGwfpa0e4Dwj/ZJqV2D84tDGjvdVYLP7zzaYZxQcwk/cgNpzKf/jq97HLNP7CYuf4="
rvm:
- 2.2.4
before_script:
- psql -c 'create database growstuff_test;' -U postgres
script:
- bundle exec rake db:migrate --trace
- bundle exec rspec spec/
services:
- elasticsearch
before_deploy:
- bundle exec script/heroku_maintenance.rb on
deploy:
provider: heroku
api_key:
secure: WrQxf0fEKkCdXrjcejurobOnNNz3he4dDwjBbToXbQTQNDObPp7NetJrLsfM8FiUFEeOuvhIHHiDQtMvY720zGGAGxDptvgFS+0QHCUqoTRZA/yFfUmHlG2jROXTzk5uVK0AE4k6Ion5kX8+mM0EnMT/7u+MTFiukrJctSiEXfg=
on:
repo: Growstuff/growstuff
app:
dev: growstuff-staging
travis_deploy: tranquil-basin-3130
travis_containers: tranquil-basin-3130
run:
- "rake db:migrate"
- "script/deploy-tasks.sh"
- restart
after_deploy:
- bundle exec script/heroku_maintenance.rb off
env: GROWSTUFF_SITE_NAME="Growstuff (travis)" RAILS_SECRET_TOKEN='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
bundler_args: --without development production staging
rvm:
- 2.1.5
before_script:
- psql -c 'create database growstuff_test;' -U postgres
script:
- bundle exec rake db:migrate --trace
- bundle exec rspec spec/

View File

@@ -11,12 +11,10 @@ submit the change with your pull request.
- Alex Bayley / [Skud](https://github.com/Skud) - Alex Bayley / [Skud](https://github.com/Skud)
- Cesy / [cesy](https://github.com/cesy) - Cesy / [cesy](https://github.com/cesy)
- Miles Gould / [pozorvlak](https://github.com/pozorvlak) - Miles Gould / [pozorvlak](https://github.com/pozorvlak)
- Taylor Griffin / [tygriffin](https://github.com/tygriffin) - Joseph Caudle / [jcaudle](https://github.com/jcaudle)
- Mackenzie Morgan / [maco](https://github.com/maco)
## Contributors ## Contributors
- Joseph Caudle / [jcaudle](https://github.com/jcaudle)
- Ricky Amianym / [amianym](https://github.com/amianym) - Ricky Amianym / [amianym](https://github.com/amianym)
- Juliet Kemp / [julietk](https://github.com/julietk) - Juliet Kemp / [julietk](https://github.com/julietk)
- Federico Mena Quintero / [federicomenaquintero](https://github.com/federicomenaquintero) - Federico Mena Quintero / [federicomenaquintero](https://github.com/federicomenaquintero)
@@ -24,6 +22,7 @@ submit the change with your pull request.
- Maia Sauren / [sauramaia](https://github.com/sauramaia) - Maia Sauren / [sauramaia](https://github.com/sauramaia)
- Norman Ancajas / [nbancajas](https://github.com/nbancajas) - Norman Ancajas / [nbancajas](https://github.com/nbancajas)
- Jonathan "Duke" Leto / [leto](https://github.com/leto) - Jonathan "Duke" Leto / [leto](https://github.com/leto)
- Mackenzie Morgan / [maco](https://github.com/maco)
- Amy Hendrix / [sabreuse](https://github.com/sabreuse) - Amy Hendrix / [sabreuse](https://github.com/sabreuse)
- CephLPod / [cephLpod](https://github.com/cephLpod/) - CephLPod / [cephLpod](https://github.com/cephLpod/)
- Gemma Mason / [gemmaellen](https://github.com/gemmaellen) - Gemma Mason / [gemmaellen](https://github.com/gemmaellen)
@@ -41,6 +40,7 @@ submit the change with your pull request.
- Marty Hines / [martyhines](https://github.com/martyhines) - Marty Hines / [martyhines](https://github.com/martyhines)
- Amelia Greenhall / [ameliagreenhall](https://github.com/ameliagreenhall) - Amelia Greenhall / [ameliagreenhall](https://github.com/ameliagreenhall)
- Barb Natali / [barbnatali](https://github.com/barbnatali) - Barb Natali / [barbnatali](https://github.com/barbnatali)
- Taylor Griffin / [tygriffin](https://github.com/tygriffin)
- Marlena Compton / [Marlena](https://github.com/marlena) - Marlena Compton / [Marlena](https://github.com/marlena)
- Elizabeth A. Kari / [catfriend](https://github.com/catfriend) - Elizabeth A. Kari / [catfriend](https://github.com/catfriend)
- Cheri Allen / [cherimarie](https://github.com/cherimarie) - Cheri Allen / [cherimarie](https://github.com/cherimarie)
@@ -51,18 +51,4 @@ submit the change with your pull request.
- Yoong Kang Lim / [yoongkang](https://github.com/yoongkang) - Yoong Kang Lim / [yoongkang](https://github.com/yoongkang)
- Kevin Yang / [kevieyang](https://github.com/kevieyang) - Kevin Yang / [kevieyang](https://github.com/kevieyang)
- Justin Hamman / [juzham](https://github.com/juzham) - Justin Hamman / [juzham](https://github.com/juzham)
- Rocky Jaiswal / [rocky-jaiswal](https://github.com/rocky-jaiswal)
- Robert Landreaux / [robertlandreaux](https://github.com/robertlandreaux)
- Savant Krishna / [sksavant](https://github.com/sksavant)
- Jake Yesbeck / [yez](https://github.com/yez)
- Mauricio Gonzalez / [mauricio-gonzalez](https://github.com/mauricio-gonzalez)
- Andrey Bazhutkin / [andrba](https://github.com/andrba)
- Gabriel Sandoval / [gabrielsandoval](https://github.com/gabrielsandoval)
- Cjay Billones / [CjayBillones](https://github.com/CjayBillones)
- Katy Ereira / [maccath](https://github.com/maccath)
- Gabrielle DeWitt / [gabrielle27](https://github.com/gabrielle27)
- Manmeet Singh / [manmeetsingh](https://github.com/manmeetsingh)
- Jym Paul Carandang / [jacarandang](https://github.com/jacarandang)
- Anthony Atkinson / [sha1sum](https://github.com/sha1sum)
- Terence Conquest / [twconquest](https://github.com/twconquest)
- Daniel O'Connor / [CloCkWeRX](https://github.com/CloCkWeRX)

160
Gemfile
View File

@@ -1,48 +1,92 @@
source 'https://rubygems.org' source 'https://rubygems.org'
ruby '2.2.4' ruby "2.1.5"
gem 'rails', '~> 4.1.11'
gem 'bundler', '>=1.1.5' gem 'bundler', '>=1.1.5'
gem 'sass-rails', '~> 5.0.4' gem 'rails', '3.2.13'
gem 'coffee-rails', '~> 4.1.0' gem 'rack', '~>1.4.5'
gem 'json', '~>1.7.7'
gem 'haml' gem 'haml'
# CSS framework
gem 'bootstrap-sass', '~> 3.3.6'
gem 'font-awesome-sass'
gem 'uglifier', '~> 2.7.2' # JavaScript compressor
gem 'jquery-rails'
gem 'jquery-ui-rails', '~> 5.0.2'
gem 'js-routes' # provides access to Rails routes in Javascript
gem 'flickraw'
gem 'leaflet-rails' gem 'leaflet-rails'
gem 'leaflet-markercluster-rails' gem 'leaflet-markercluster-rails'
gem 'unicorn' # http server gem 'unicorn' # http server
gem 'pg' gem 'pg'
gem 'figaro' # for handling config via ENV variables
gem 'cancancan', '~> 1.9' # for checking member privileges
gem 'gibbon', '~>1.2.0' # for Mailchimp newsletter subscriptions
gem 'csv_shaper' # CSV export
gem 'ruby-units' # for unit conversion
gem 'comfortable_mexican_sofa', '~> 1.12.0' # content management system gem 'figaro' # for handling config via ENV variables
gem 'kaminari' # pagination gem 'cancan' # for checking member privileges
gem 'bootstrap-kaminari-views' # bootstrap views for kaminari
gem 'gibbon' # for Mailchimp newsletter subscriptions
gem 'csv_shaper' # CSV export
# vendored activemerchant for testing- needed for bogus paypal # vendored activemerchant for testing- needed for bogus paypal
# gateway monkeypatch # gateway monkeypatch
gem 'activemerchant', '1.33.0', gem 'activemerchant', '1.33.0',
:path => 'vendor/gems/activemerchant-1.33.0', :path => 'vendor/gems/activemerchant-1.33.0',
:require => 'active_merchant' :require => 'active_merchant'
gem 'active_utils', '1.0.5', gem 'active_utils', '1.0.5',
:path => 'vendor/gems/active_utils-1.0.5' :path => 'vendor/gems/active_utils-1.0.5'
group :production, :staging do
gem 'newrelic_rpm'
gem 'dalli'
gem 'memcachier'
gem 'rails_12factor' # supresses heroku plugin injection
end
# Gems used only for assets and not required
# in production environments by default.
group :assets do
# CSS preprocessor, used for app/assets/stylesheets/application.css
gem 'sass-rails', '~> 3.2.3'
# CoffeeScript is a Python-like language that compiles to JavaScript
gem 'coffee-rails', '~> 3.2.1'
# less-rails depends on a JavaScript engine; we use therubyracer.
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
# long term, we'll probably want node.js for performance, but this will do
# for now as it's easier for new people to install
gem "therubyracer", "~> 0.12", :platforms => :ruby
# libv8 is needed by therubyracer and is a bit finicky
gem 'libv8', '3.16.14.7'
# Another CSS preprocessor, used for Bootstrap overrides
gem "less", '~>2.5.0'
gem "less-rails", '~> 2.5.0'
# CSS framework
gem "less-rails-bootstrap", '~> 3.2.0'
gem 'uglifier', '>= 1.0.3' # JavaScript compressor
gem 'compass-rails', '~> 1.0.3' # Yet Another CSS framework
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
# gem 'bcrypt-ruby', '~> 3.0.0'
# To use Jbuilder templates for JSON
gem 'jbuilder'
# Use unicorn as the app server
# gem 'unicorn'
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'
end
# Markdown formatting for updates etc # Markdown formatting for updates etc
gem 'bluecloth' gem 'bluecloth'
@@ -51,10 +95,10 @@ gem 'bluecloth'
gem 'will_paginate', '~> 3.0' gem 'will_paginate', '~> 3.0'
# user signup/login/etc # user signup/login/etc
gem 'devise', '~> 3.5.0' gem 'devise', '~> 3.2.0'
# nicely formatted URLs # nicely formatted URLs
gem 'friendly_id', '~> 5.0.4' gem 'friendly_id', '~> 4.0.10'
# gravatars # gravatars
gem 'gravatar-ultimate' gem 'gravatar-ultimate'
@@ -72,51 +116,19 @@ gem 'omniauth'
gem 'omniauth-twitter' gem 'omniauth-twitter'
gem 'omniauth-flickr', '>= 0.0.15' gem 'omniauth-flickr', '>= 0.0.15'
# client for Elasticsearch. Elasticsearch is a flexible
# and powerful, distributed, real-time search and analytics engine.
# An example of the use in the project is fuzzy crop search.
gem "elasticsearch-model"
gem "elasticsearch-rails"
gem 'rake', '>= 10.0.0' gem 'rake', '>= 10.0.0'
group :production, :staging do
gem 'newrelic_rpm'
gem 'dalli'
gem 'memcachier'
gem 'rails_12factor' # supresses heroku plugin injection
gem 'bonsai-elasticsearch-rails' # Integration with Bonsa-Elasticsearch on heroku
end
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'
gem 'quiet_assets'
gem 'guard'
gem 'guard-rspec'
end
group :development, :test do group :development, :test do
gem 'haml-rails' # HTML templating language gem 'byebug' # debugging
gem 'rspec-rails', '~> 3.4.0' # unit testing framework gem 'haml-rails' # HTML templating language
gem 'rspec-activemodel-mocks' gem 'rspec-rails', '~> 2.12.1' # unit testing framework
gem 'byebug' # debugging gem 'database_cleaner', '~> 1.3.0'
gem 'database_cleaner', '~> 1.5.0' gem 'webrat' # provides HTML matchers for view tests
gem 'webrat' # provides HTML matchers for view tests gem 'factory_girl_rails', '~> 4.0' # for creating test data
gem 'factory_girl_rails', '~> 4.5.0' # for creating test data gem 'coveralls', require: false # coverage analysis
gem 'coveralls', require: false # coverage analysis gem 'capybara' # integration tests
gem 'capybara' # integration tests gem 'capybara-email' # integration tests for email
gem 'capybara-email' # integration tests for email gem 'poltergeist', '~> 1.5.1' # for headless JS testing
gem 'poltergeist', '~> 1.6' # for headless JS testing gem 'i18n-tasks' # adds tests for finding missing and unused translations
gem 'i18n-tasks' # adds tests for finding missing and unused translations gem 'json_spec' # extra ways to test JSON data
gem 'selenium-webdriver'
end
group :travis do
gem 'heroku-api'
end end

View File

@@ -20,409 +20,311 @@ PATH
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actionmailer (4.1.15) actionmailer (3.2.13)
actionpack (= 4.1.15) actionpack (= 3.2.13)
actionview (= 4.1.15) mail (~> 2.5.3)
mail (~> 2.5, >= 2.5.4) actionpack (3.2.13)
actionpack (4.1.15) activemodel (= 3.2.13)
actionview (= 4.1.15) activesupport (= 3.2.13)
activesupport (= 4.1.15) builder (~> 3.0.0)
rack (~> 1.5.2)
rack-test (~> 0.6.2)
actionview (4.1.15)
activesupport (= 4.1.15)
builder (~> 3.1)
erubis (~> 2.7.0) erubis (~> 2.7.0)
active_link_to (1.0.3) journey (~> 1.0.4)
actionpack rack (~> 1.4.5)
activemodel (4.1.15) rack-cache (~> 1.2)
activesupport (= 4.1.15) rack-test (~> 0.6.1)
builder (~> 3.1) sprockets (~> 2.2.1)
activerecord (4.1.15) activemodel (3.2.13)
activemodel (= 4.1.15) activesupport (= 3.2.13)
activesupport (= 4.1.15) builder (~> 3.0.0)
arel (~> 5.0.0) activerecord (3.2.13)
activesupport (4.1.15) activemodel (= 3.2.13)
i18n (~> 0.6, >= 0.6.9) activesupport (= 3.2.13)
json (~> 1.7, >= 1.7.7) arel (~> 3.0.2)
minitest (~> 5.1) tzinfo (~> 0.3.29)
thread_safe (~> 0.1) activeresource (3.2.13)
tzinfo (~> 1.1) activemodel (= 3.2.13)
addressable (2.4.0) activesupport (= 3.2.13)
arel (5.0.1.20140414130214) activesupport (3.2.13)
ast (2.2.0) i18n (= 0.6.1)
autoprefixer-rails (6.3.6.1) multi_json (~> 1.0)
execjs addressable (2.3.6)
bcrypt (3.1.11) arel (3.0.3)
better_errors (2.1.1) bcrypt (3.1.9)
better_errors (2.0.0)
coderay (>= 1.0.0) coderay (>= 1.0.0)
erubis (>= 2.6.6) erubis (>= 2.6.6)
rack (>= 0.9.0) rack (>= 0.9.0)
binding_of_caller (0.7.2) binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
bluecloth (2.2.0) bluecloth (2.2.0)
bonsai-elasticsearch-rails (0.0.4) bootstrap-datepicker-rails (1.3.0.2)
bootstrap-datepicker-rails (1.6.1.1)
railties (>= 3.0) railties (>= 3.0)
bootstrap-kaminari-views (0.0.5) builder (3.0.4)
kaminari (>= 0.13) byebug (3.5.1)
rails (>= 3.1) columnize (~> 0.8)
bootstrap-sass (3.3.6) debugger-linecache (~> 1.2)
autoprefixer-rails (>= 5.2.1) slop (~> 3.6)
sass (>= 3.3.4) cancan (1.6.10)
bootstrap_form (2.3.0) capybara (2.4.4)
builder (3.2.2)
byebug (9.0.4)
cancancan (1.14.0)
capybara (2.7.1)
addressable
mime-types (>= 1.16) mime-types (>= 1.16)
nokogiri (>= 1.3.3) nokogiri (>= 1.3.3)
rack (>= 1.0.0) rack (>= 1.0.0)
rack-test (>= 0.5.4) rack-test (>= 0.5.4)
xpath (~> 2.0) xpath (~> 2.0)
capybara-email (2.5.0) capybara-email (2.4.0)
capybara (~> 2.4) capybara (~> 2.4)
mail mail
childprocess (0.5.9) chunky_png (1.3.3)
ffi (~> 1.0, >= 1.0.11)
climate_control (0.0.3)
activesupport (>= 3.0)
cliver (0.3.2) cliver (0.3.2)
cocaine (0.5.8) coderay (1.1.0)
climate_control (>= 0.0.3, < 1.0) coffee-rails (3.2.2)
codemirror-rails (5.11)
railties (>= 3.0, < 5)
coderay (1.1.1)
coffee-rails (4.1.1)
coffee-script (>= 2.2.0) coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.1.x) railties (~> 3.2.0)
coffee-script (2.4.1) coffee-script (2.3.0)
coffee-script-source coffee-script-source
execjs execjs
coffee-script-source (1.10.0) coffee-script-source (1.8.0)
comfortable_mexican_sofa (1.12.9) columnize (0.8.9)
active_link_to (>= 1.0.0) commonjs (0.2.7)
bootstrap-sass (>= 3.2.0) compass (0.12.7)
bootstrap_form (>= 2.2.0) chunky_png (~> 1.2)
codemirror-rails (>= 3.0.0) fssm (>= 0.2.7)
coffee-rails (>= 3.1.0) sass (~> 3.2.19)
haml-rails (>= 0.3.0) compass-rails (1.0.3)
jquery-rails (>= 3.0.0) compass (>= 0.12.2, < 0.14)
jquery-ui-rails (>= 5.0.0) coveralls (0.7.1)
kramdown (>= 1.0.0) multi_json (~> 1.3)
paperclip (>= 4.0.0) rest-client
plupload-rails (>= 1.2.1) simplecov (>= 0.7)
rails (>= 4.0.0, < 5) term-ansicolor
rails-i18n (>= 4.0.0) thor
sass-rails (>= 4.0.3) csv_shaper (1.1.1)
concurrent-ruby (1.0.2)
coveralls (0.8.13)
json (~> 1.8)
simplecov (~> 0.11.0)
term-ansicolor (~> 1.3)
thor (~> 0.19.1)
tins (~> 1.6.0)
csv_shaper (1.2.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
dalli (2.7.6) dalli (2.7.2)
database_cleaner (1.5.3) database_cleaner (1.3.0)
debug_inspector (0.0.2) debug_inspector (0.0.2)
devise (3.5.10) debugger-linecache (1.2.0)
devise (3.2.4)
bcrypt (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
railties (>= 3.2.6, < 5) railties (>= 3.2.6, < 5)
responders
thread_safe (~> 0.1) thread_safe (~> 0.1)
warden (~> 1.2.3) warden (~> 1.2.3)
diff-lcs (1.2.5) diff-lcs (1.1.3)
docile (1.1.5) docile (1.1.5)
easy_translate (0.5.0) easy_translate (0.5.0)
json json
thread thread
thread_safe thread_safe
elasticsearch (1.0.17)
elasticsearch-api (= 1.0.17)
elasticsearch-transport (= 1.0.17)
elasticsearch-api (1.0.17)
multi_json
elasticsearch-model (0.1.9)
activesupport (> 3)
elasticsearch (> 0.4)
hashie
elasticsearch-rails (0.1.9)
elasticsearch-transport (1.0.17)
faraday
multi_json
erubis (2.7.0) erubis (2.7.0)
excon (0.49.0) execjs (2.2.2)
execjs (2.7.0)
factory_girl (4.5.0) factory_girl (4.5.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
factory_girl_rails (4.5.0) factory_girl_rails (4.5.0)
factory_girl (~> 4.5.0) factory_girl (~> 4.5.0)
railties (>= 3.0.0) railties (>= 3.0.0)
faraday (0.9.2) figaro (1.0.0)
multipart-post (>= 1.2, < 3)
ffi (1.9.10)
figaro (1.1.1)
thor (~> 0.14) thor (~> 0.14)
flickraw (0.9.8) flickraw (0.9.8)
font-awesome-sass (4.6.2) friendly_id (4.0.10.1)
sass (>= 3.2) activerecord (>= 3.0, < 4.0)
formatador (0.2.5) fssm (0.2.10)
friendly_id (5.0.5) gibbon (1.1.4)
activerecord (>= 4.0.0)
gibbon (1.2.0)
httparty httparty
multi_json (>= 1.9.0) multi_json (>= 1.3.4)
gravatar-ultimate (2.0.0) gravatar-ultimate (2.0.0)
activesupport (>= 2.3.14) activesupport (>= 2.3.14)
rack rack
guard (2.14.0) haml (4.0.5)
formatador (>= 0.2.4)
listen (>= 2.7, < 4.0)
lumberjack (~> 1.0)
nenv (~> 0.1)
notiffany (~> 0.0)
pry (>= 0.9.12)
shellany (~> 0.0)
thor (>= 0.18.1)
guard-compat (1.2.1)
guard-rspec (4.6.5)
guard (~> 2.1)
guard-compat (~> 1.1)
rspec (>= 2.99.0, < 4.0)
haml (4.0.7)
tilt tilt
haml-rails (0.9.0) haml-rails (0.4)
actionpack (>= 4.0.1) actionpack (>= 3.1, < 4.1)
activesupport (>= 4.0.1) activesupport (>= 3.1, < 4.1)
haml (>= 4.0.6, < 5.0) haml (>= 3.1, < 4.1)
html2haml (>= 1.0.1) railties (>= 3.1, < 4.1)
railties (>= 4.0.1) hashie (3.3.2)
hashie (3.4.4) highline (1.6.21)
heroku-api (0.4.2) hike (1.2.3)
excon (~> 0.45) httparty (0.11.0)
multi_json (~> 1.8) multi_json (~> 1.0)
highline (1.7.8)
html2haml (2.0.0)
erubis (~> 2.7.0)
haml (~> 4.0.0)
nokogiri (~> 1.6.0)
ruby_parser (~> 3.5)
httparty (0.13.3)
json (~> 1.8)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
i18n (0.7.0) i18n (0.6.1)
i18n-tasks (0.9.5) i18n-tasks (0.7.8)
activesupport (>= 4.0.2) activesupport
ast (>= 2.1.0)
easy_translate (>= 0.5.0) easy_translate (>= 0.5.0)
erubis erubis
highline (>= 1.7.3) highline
i18n i18n
parser (>= 2.2.3.0) slop (>= 3.5.0)
term-ansicolor (>= 1.3.2) term-ansicolor
terminal-table (>= 1.5.1) terminal-table
jquery-rails (3.1.4) jbuilder (2.2.6)
activesupport (>= 3.0.0, < 5)
multi_json (~> 1.2)
journey (1.0.4)
jquery-rails (3.1.2)
railties (>= 3.0, < 5.0) railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0) thor (>= 0.14, < 2.0)
jquery-ui-rails (5.0.5) jquery-ui-rails (4.1.2)
railties (>= 3.2.16) railties (>= 3.1.0)
js-routes (1.2.5) js-routes (0.9.9)
railties (>= 3.2) railties (>= 3.2)
sprockets-rails sprockets-rails
json (1.8.3) json (1.7.7)
kaminari (0.16.3) json_spec (1.1.4)
actionpack (>= 3.0.0) multi_json (~> 1.0)
activesupport (>= 3.0.0) rspec (>= 2.0, < 4.0)
kgio (2.10.0) kgio (2.9.2)
kramdown (1.11.1)
launchy (2.4.3) launchy (2.4.3)
addressable (~> 2.3) addressable (~> 2.3)
leaflet-markercluster-rails (0.7.0) leaflet-markercluster-rails (0.7.0)
railties (>= 3.1) railties (>= 3.1)
leaflet-rails (0.7.7) leaflet-rails (0.7.4)
letter_opener (1.4.1) less (2.5.1)
commonjs (~> 0.2.7)
less-rails (2.5.0)
actionpack (>= 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) launchy (~> 2.2)
listen (3.1.5) libv8 (3.16.14.7)
rb-fsevent (~> 0.9, >= 0.9.4) mail (2.5.4)
rb-inotify (~> 0.9, >= 0.9.7) mime-types (~> 1.16)
ruby_dep (~> 1.2) treetop (~> 1.4.8)
lumberjack (1.0.10)
mail (2.6.4)
mime-types (>= 1.16, < 4)
memcachier (0.0.2) memcachier (0.0.2)
method_source (0.8.2) method_source (0.8.2)
mime-types (3.0) mime-types (1.25.1)
mime-types-data (~> 3.2015) mini_portile (0.6.1)
mime-types-data (3.2016.0221) multi_json (1.10.1)
mimemagic (0.3.0)
mini_portile2 (2.0.0)
minitest (5.9.0)
multi_json (1.11.3)
multi_xml (0.5.5) multi_xml (0.5.5)
multipart-post (2.0.0) netrc (0.8.0)
nenv (0.3.0) newrelic_rpm (3.9.7.266)
newrelic_rpm (3.15.2.317) nokogiri (1.6.5)
nokogiri (1.6.7.2) mini_portile (~> 0.6.0)
mini_portile2 (~> 2.0.0.rc2) oauth (0.4.7)
notiffany (0.1.0) omniauth (1.2.2)
nenv (~> 0.1)
shellany (~> 0.0)
oauth (0.5.1)
omniauth (1.3.1)
hashie (>= 1.2, < 4) hashie (>= 1.2, < 4)
rack (>= 1.0, < 3) rack (~> 1.0)
omniauth-flickr (0.0.19) omniauth-flickr (0.0.15)
multi_json (~> 1.11.0)
omniauth-oauth (~> 1.0) omniauth-oauth (~> 1.0)
omniauth-oauth (1.1.0) omniauth-oauth (1.0.1)
oauth oauth
omniauth (~> 1.0) omniauth (~> 1.0)
omniauth-twitter (1.2.1) omniauth-twitter (1.1.0)
json (~> 1.3) multi_json (~> 1.3)
omniauth-oauth (~> 1.1) omniauth-oauth (~> 1.0)
orm_adapter (0.5.0) orm_adapter (0.5.0)
paperclip (4.3.6) pg (0.17.1)
activemodel (>= 3.2.0) poltergeist (1.5.1)
activesupport (>= 3.2.0)
cocaine (~> 0.5.5)
mime-types
mimemagic (= 0.3.0)
parser (2.3.1.0)
ast (~> 2.2)
pg (0.18.4)
plupload-rails (1.2.1)
rails (>= 3.1)
poltergeist (1.9.0)
capybara (~> 2.1) capybara (~> 2.1)
cliver (~> 0.3.1) cliver (~> 0.3.1)
multi_json (~> 1.0) multi_json (~> 1.0)
websocket-driver (>= 0.2.0) websocket-driver (>= 0.2.0)
pry (0.10.3) polyglot (0.3.5)
pry (0.10.1)
coderay (~> 1.1.0) coderay (~> 1.1.0)
method_source (~> 0.8.1) method_source (~> 0.8.1)
slop (~> 3.4) slop (~> 3.4)
quiet_assets (1.1.0) rack (1.4.5)
railties (>= 3.1, < 5.0) rack-cache (1.2)
rack (1.5.5) rack (>= 0.4)
rack-test (0.6.3) rack-ssl (1.3.4)
rack
rack-test (0.6.2)
rack (>= 1.0) rack (>= 1.0)
rails (4.1.15) rails (3.2.13)
actionmailer (= 4.1.15) actionmailer (= 3.2.13)
actionpack (= 4.1.15) actionpack (= 3.2.13)
actionview (= 4.1.15) activerecord (= 3.2.13)
activemodel (= 4.1.15) activeresource (= 3.2.13)
activerecord (= 4.1.15) activesupport (= 3.2.13)
activesupport (= 4.1.15) bundler (~> 1.0)
bundler (>= 1.3.0, < 2.0) railties (= 3.2.13)
railties (= 4.1.15)
sprockets-rails (~> 2.0)
rails-i18n (4.0.8)
i18n (~> 0.7)
railties (~> 4.0)
rails_12factor (0.0.3) rails_12factor (0.0.3)
rails_serve_static_assets rails_serve_static_assets
rails_stdout_logging rails_stdout_logging
rails_serve_static_assets (0.0.5) rails_serve_static_assets (0.0.2)
rails_stdout_logging (0.0.5) rails_stdout_logging (0.0.3)
railties (4.1.15) railties (3.2.13)
actionpack (= 4.1.15) actionpack (= 3.2.13)
activesupport (= 4.1.15) activesupport (= 3.2.13)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) rdoc (~> 3.4)
raindrops (0.16.0) thor (>= 0.14.6, < 2.0)
rake (11.1.2) raindrops (0.13.0)
rb-fsevent (0.9.7) rake (10.4.0)
rb-inotify (0.9.7) rdoc (3.12.2)
ffi (>= 0.5.0) json (~> 1.4)
responders (1.1.2) ref (1.0.5)
railties (>= 3.2, < 4.2) rest-client (1.7.2)
rspec (3.4.0) mime-types (>= 1.16, < 3.0)
rspec-core (~> 3.4.0) netrc (~> 0.7)
rspec-expectations (~> 3.4.0) rspec (2.12.0)
rspec-mocks (~> 3.4.0) rspec-core (~> 2.12.0)
rspec-activemodel-mocks (1.0.3) rspec-expectations (~> 2.12.0)
activemodel (>= 3.0) rspec-mocks (~> 2.12.0)
activesupport (>= 3.0) rspec-core (2.12.2)
rspec-mocks (>= 2.99, < 4.0) rspec-expectations (2.12.1)
rspec-core (3.4.4) diff-lcs (~> 1.1.3)
rspec-support (~> 3.4.0) rspec-mocks (2.12.2)
rspec-expectations (3.4.0) rspec-rails (2.12.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.4.0)
rspec-mocks (3.4.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.4.0)
rspec-rails (3.4.2)
actionpack (>= 3.0, < 4.3)
activesupport (>= 3.0, < 4.3)
railties (>= 3.0, < 4.3)
rspec-core (~> 3.4.0)
rspec-expectations (~> 3.4.0)
rspec-mocks (~> 3.4.0)
rspec-support (~> 3.4.0)
rspec-support (3.4.1)
ruby-units (2.0.0)
ruby_dep (1.3.1)
ruby_parser (3.8.2)
sexp_processor (~> 4.1)
rubyzip (1.2.0)
sass (3.4.22)
sass-rails (5.0.4)
railties (>= 4.0.0, < 5.0)
sass (~> 3.1)
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
selenium-webdriver (2.53.0)
childprocess (~> 0.5)
rubyzip (~> 1.0)
websocket (~> 1.0)
sexp_processor (4.7.0)
shellany (0.0.1)
simplecov (0.11.2)
docile (~> 1.1.0)
json (~> 1.8)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.0)
slop (3.6.0)
sprockets (3.6.0)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (2.3.3)
actionpack (>= 3.0) actionpack (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
sprockets (>= 2.8, < 4.0) railties (>= 3.0)
term-ansicolor (1.3.2) rspec-core (~> 2.12.0)
rspec-expectations (~> 2.12.0)
rspec-mocks (~> 2.12.0)
sass (3.2.19)
sass-rails (3.2.6)
railties (~> 3.2.0)
sass (>= 3.1.10)
tilt (~> 1.3)
simplecov (0.9.1)
docile (~> 1.1.0)
multi_json (~> 1.0)
simplecov-html (~> 0.8.0)
simplecov-html (0.8.0)
slop (3.6.0)
sprockets (2.2.3)
hike (~> 1.2)
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) tins (~> 1.0)
terminal-table (1.5.2) terminal-table (1.4.5)
therubyracer (0.12.1)
libv8 (~> 3.16.14.0)
ref
thor (0.19.1) thor (0.19.1)
thread (0.2.2) thread (0.1.4)
thread_safe (0.3.5) thread_safe (0.3.4)
tilt (2.0.4) tilt (1.4.1)
tins (1.6.0) tins (1.3.3)
tzinfo (1.2.2) treetop (1.4.15)
thread_safe (~> 0.1) polyglot
uglifier (2.7.2) polyglot (>= 0.3.1)
tzinfo (0.3.42)
uglifier (2.2.1)
execjs (>= 0.3.0) execjs (>= 0.3.0)
json (>= 1.8.0) multi_json (~> 1.0, >= 1.0.2)
unicorn (5.1.0) unicorn (4.8.3)
kgio (~> 2.6) kgio (~> 2.6)
rack
raindrops (~> 0.7) raindrops (~> 0.7)
warden (1.2.6) warden (1.2.3)
rack (>= 1.0) rack (>= 1.0)
webrat (0.7.3) webrat (0.7.3)
nokogiri (>= 1.2.0) nokogiri (>= 1.2.0)
rack (>= 1.0) rack (>= 1.0)
rack-test (>= 0.5.3) rack-test (>= 0.5.3)
websocket (1.2.3) websocket-driver (0.4.0)
websocket-driver (0.6.4) will_paginate (3.0.7)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2)
will_paginate (3.1.0)
xpath (2.0.0) xpath (2.0.0)
nokogiri (~> 1.3) nokogiri (~> 1.3)
@@ -435,69 +337,58 @@ DEPENDENCIES
better_errors better_errors
binding_of_caller binding_of_caller
bluecloth bluecloth
bonsai-elasticsearch-rails
bootstrap-datepicker-rails bootstrap-datepicker-rails
bootstrap-kaminari-views
bootstrap-sass (~> 3.3.6)
bundler (>= 1.1.5) bundler (>= 1.1.5)
byebug byebug
cancancan (~> 1.9) cancan
capybara capybara
capybara-email capybara-email
coffee-rails (~> 4.1.0) coffee-rails (~> 3.2.1)
comfortable_mexican_sofa (~> 1.12.0) compass-rails (~> 1.0.3)
coveralls coveralls
csv_shaper csv_shaper
dalli dalli
database_cleaner (~> 1.5.0) database_cleaner (~> 1.3.0)
devise (~> 3.5.0) devise (~> 3.2.0)
elasticsearch-model factory_girl_rails (~> 4.0)
elasticsearch-rails
factory_girl_rails (~> 4.5.0)
figaro figaro
flickraw flickraw
font-awesome-sass friendly_id (~> 4.0.10)
friendly_id (~> 5.0.4)
geocoder! geocoder!
gibbon (~> 1.2.0) gibbon
gravatar-ultimate gravatar-ultimate
guard
guard-rspec
haml haml
haml-rails haml-rails
heroku-api
i18n-tasks i18n-tasks
jbuilder
jquery-rails jquery-rails
jquery-ui-rails (~> 5.0.2) jquery-ui-rails
js-routes js-routes
kaminari json (~> 1.7.7)
json_spec
leaflet-markercluster-rails leaflet-markercluster-rails
leaflet-rails leaflet-rails
less (~> 2.5.0)
less-rails (~> 2.5.0)
less-rails-bootstrap (~> 3.2.0)
letter_opener letter_opener
libv8 (= 3.16.14.7)
memcachier memcachier
newrelic_rpm newrelic_rpm
omniauth omniauth
omniauth-flickr (>= 0.0.15) omniauth-flickr (>= 0.0.15)
omniauth-twitter omniauth-twitter
pg pg
poltergeist (~> 1.6) poltergeist (~> 1.5.1)
pry pry
quiet_assets rack (~> 1.4.5)
rails (~> 4.1.11) rails (= 3.2.13)
rails_12factor rails_12factor
rake (>= 10.0.0) rake (>= 10.0.0)
rspec-activemodel-mocks rspec-rails (~> 2.12.1)
rspec-rails (~> 3.4.0) sass-rails (~> 3.2.3)
ruby-units therubyracer (~> 0.12)
sass-rails (~> 5.0.4) uglifier (>= 1.0.3)
selenium-webdriver
uglifier (~> 2.7.2)
unicorn unicorn
webrat webrat
will_paginate (~> 3.0) will_paginate (~> 3.0)
RUBY VERSION
ruby 2.1.8p440
BUNDLED WITH
1.12.4

View File

@@ -1,13 +0,0 @@
guard :rspec,
cmd: 'bundle exec rspec --format documentation',
failed_mode: :keep do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/libs/#{m[1]}_spec.rb" }
watch('spec/spec_helper.rb') { "spec" }
# Rails example
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch(%r{^app/(.*)(\.erb|\.haml|\.slim)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
watch('config/routes.rb') { "spec/routing" }
end

View File

@@ -32,14 +32,6 @@ frontend features. We welcome contributions -- see
* Drop in to one of our [discussion forums](http://wiki.growstuff.org/index.php/Discussion_forums) to chat to other developers, get help, etc. * Drop in to one of our [discussion forums](http://wiki.growstuff.org/index.php/Discussion_forums) to chat to other developers, get help, etc.
* You may also be interested in our [API](http://wiki.growstuff.org/index.php/API). * You may also be interested in our [API](http://wiki.growstuff.org/index.php/API).
The wiki is down right now, so here's what you need to do on Mac OS X to get set up.
```
gem install bundle
gem install pg -v '0.18.4' -- --with-pg-config=/Applications/Postgres.app/Contents/Versions/latest/bin/pg_config
bundle install
```
## For designers, writers, researchers, data wranglers, and other contributors ## For designers, writers, researchers, data wranglers, and other contributors
There are heaps of ways to get involved and contribute no matter what There are heaps of ways to get involved and contribute no matter what

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 613 B

After

Width:  |  Height:  |  Size: 572 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -15,7 +15,8 @@
//= require js-routes //= require js-routes
//= require jquery //= require jquery
//= require jquery_ujs //= require jquery_ujs
//= require jquery-ui/autocomplete //= require jquery.ui.autocomplete
//= require bootstrap-sprockets //= require twitter/bootstrap
//= require bootstrap-datepicker
//= require_tree . //= require_tree .
//= require bootstrap-datepicker

View File

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

View File

@@ -47,7 +47,3 @@ function showCropMap(cropmap) {
cropmap.addLayer(markers); cropmap.addLayer(markers);
} }
$('.btn.toggle.crop-hierarchy').click(function () {
$('.toggle.crop-hierarchy').toggleClass('hide');
});

View File

@@ -9,11 +9,11 @@ jQuery ->
finished = $('#planting_finished_at') finished = $('#planting_finished_at')
if @checked if @checked
if previousValue.length if previousValue.length
date = previousValue date = previousValue
finished.val(date) finished.val(date)
else else
finished.trigger('focus') finished.trigger('focus')
else else
previousValue = finished.val() previousValue = finished.val()
finished.val('') finished.val('')
) )

View File

@@ -4,42 +4,3 @@
jQuery -> jQuery ->
$('.add-datepicker').datepicker('format' : 'yyyy-mm-dd') $('.add-datepicker').datepicker('format' : 'yyyy-mm-dd')
$('#add-sci_name-row').css("display", "inline-block")
$('#remove-sci_name-row').css("display", "inline-block")
$("#add-alt_name-row").css("display", "inline-block")
$("#remove-alt_name-row").css("display", "inline-block")
$ ->
sci_template = "<div id='sci_template[INDEX]' class='template col-md-12'><div class='col-md-2'><label>Scientific name INDEX:</label></div><div class='col-md-8'><input name='sci_name[INDEX]' class='form-control', id='sci_name[INDEX]')'></input><span class='help-block'>Scientific name of crop.</span></div><div class='col-md-2'></div></div>"
sci_index = $('#scientific_names .template').length + 1
$('#add-sci_name-row').click ->
compiled_input = $(sci_template.split("INDEX").join(sci_index))
$('#scientific_names').append(compiled_input)
sci_index = sci_index + 1
$('#remove-sci_name-row').click ->
if (sci_index > 2)
sci_index = sci_index - 1
tmp = 'sci_template[' + sci_index + ']'
element = document.getElementById(tmp)
element.remove()
alt_template = "<div id='alt_template[INDEX]' class='template col-md-12'><div class='col-md-2'><label>Alternate name INDEX:</label></div><div class='col-md-8'><input name='alt_name[INDEX]' class='form-control', id='alt_name[INDEX]')'></input><span class='help-block'>Alternate name of crop.</span></div><div class='col-md-2'></div></div>"
alt_index = $('#alternate_names .template').length + 1
$('#add-alt_name-row').click ->
compiled_input = $(alt_template.split("INDEX").join(alt_index))
$('#alternate_names').append(compiled_input)
alt_index = alt_index + 1
$('#remove-alt_name-row').click ->
if (alt_index > 2)
alt_index = alt_index - 1
tmp = 'alt_template[' + alt_index + ']'
element = document.getElementById(tmp)
console.log("%s",tmp)
element.remove()

View File

@@ -0,0 +1,14 @@
/*
* This is a manifest file that'll automatically include all the stylesheets available in this directory
* 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
*= require leaflet.markercluster.default
*= require custom_bootstrap/custom_bootstrap
*= require overrides.css
*= require_tree .
*/

View File

@@ -1,7 +0,0 @@
@import 'jquery-ui/autocomplete'
@import 'bootstrap-datepicker'
@import 'leaflet'
@import 'leaflet.markercluster'
@import 'leaflet.markercluster.default'
@import 'custom_bootstrap/custom_bootstrap'
@import 'overrides'

View File

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

View File

@@ -0,0 +1,56 @@
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!! AUTOMATICALLY GENERATED FILE. DO NOT MODIFY !!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// 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 and dependencies
@import "twitter/bootstrap/normalize.less";
@import "twitter/bootstrap/print.less";
@import "twitter/bootstrap/glyphicons.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/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/responsive-embed.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";

View File

@@ -1,59 +0,0 @@
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!! AUTOMATICALLY GENERATED FILE. DO NOT MODIFY !!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// Core variables and mixins
@import "bootstrap-sprockets"
@import "bootstrap/variables"
// Modify this for custom colors, font-sizes, etc
@import "custom_bootstrap/variables"
@import "bootstrap/mixins"
// Modify this for custom mixins
@import "custom_bootstrap/mixins"
// Reset and dependencies
@import "bootstrap/normalize"
@import "bootstrap/print"
@import "bootstrap/glyphicons"
// Core CSS
@import "bootstrap/scaffolding"
@import "bootstrap/type"
@import "bootstrap/code"
@import "bootstrap/grid"
@import "bootstrap/tables"
@import "bootstrap/forms"
@import "bootstrap/buttons"
// Components
@import "bootstrap/component-animations"
@import "bootstrap/dropdowns"
@import "bootstrap/button-groups"
@import "bootstrap/input-groups"
@import "bootstrap/navs"
@import "bootstrap/navbar"
@import "bootstrap/breadcrumbs"
@import "bootstrap/pagination"
@import "bootstrap/pager"
@import "bootstrap/labels"
@import "bootstrap/badges"
@import "bootstrap/jumbotron"
@import "bootstrap/thumbnails"
@import "bootstrap/alerts"
@import "bootstrap/progress-bars"
@import "bootstrap/media"
@import "bootstrap/list-group"
@import "bootstrap/panels"
@import "bootstrap/responsive-embed"
@import "bootstrap/wells"
@import "bootstrap/close"
// Components w/ JavaScript
@import "bootstrap/modals"
@import "bootstrap/tooltip"
@import "bootstrap/popovers"
@import "bootstrap/carousel"
// Utility classes
@import "bootstrap/utilities"
@import "bootstrap/responsive-utilities"

View File

@@ -0,0 +1,54 @@
// 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;
@brown: #413f3b;
@green: #5f8e43;
@blue: #2f4365;
@red: #8e4d43;
@orange: #b2685c;
@yellow: #b2935c;
@body-bg: @beige;
@text-color: @brown;
@link-color: @green;
@brand-primary: @green;
@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: 14px;
@font-family-base: @font-family-sans-serif;
@line-height-base: 1.5;
@alt-font-family: @font-family-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
// Hero unit
@jumbotron-bg: darken(@body-bg, 10%);
// Nav bar
@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-default-brand-color: lighten(@green, 20%);
// Top nav collapse threshold
@grid-float-breakpoint: @screen-md-min;
@dropdown-bg: lighten(@beige, 10%);
@dropdown-link-color: @brown;
@dropdown-link-hover-color: @brown;
@dropdown-link-hover-bg: lighten(@green, 50%);

View File

@@ -1,54 +0,0 @@
// Use this file to override Twitter Bootstrap variables or define own variables.
// Import original variables so they can be used in overrides
//@import 'bootstrap/variables.scss'
// 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
$brand-primary: $green
$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: 14px
$font-family-base: $font-family-sans-serif
$line-height-base: 1.5
$alt-font-family: $font-family-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
// Hero unit
$jumbotron-bg: darken($body-bg, 10%)
// Nav bar
$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-default-brand-color: lighten($green, 20%)
// Top nav collapse threshold
$grid-float-breakpoint: $screen-md-min
$dropdown-bg: lighten($beige, 10%)
$dropdown-link-color: $brown
$dropdown-link-hover-color: $brown
$dropdown-link-hover-bg: lighten($green, 50%)

View File

@@ -0,0 +1,3 @@
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
border: none;
}

View File

@@ -1,10 +0,0 @@
.leaflet-popup-content-wrapper,
.leaflet-popup-tip
border: none
.thumbnail
background: #fff !important
border: solid 1px whitesmoke
.thumbnail .crop-thumbnail .cropinfo
padding-top: 14px

View File

@@ -0,0 +1,263 @@
@import "twitter/bootstrap/bootstrap";
@import "custom_bootstrap/variables";
// this padding needs to be done before the responsive stuff is imported
body {
// 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";
// Set the correct sprite paths
@iconSpritePath: asset-path("twitter/bootstrap/glyphicons-halflings");
@iconWhiteSpritePath: asset-path("twitter/bootstrap/glyphicons-halflings-white");
// Set the Font Awesome (Font Awesome is default. You can disable by commenting below lines)
@fontAwesomeEotPath: asset-url("fontawesome-webfont.eot");
@fontAwesomeEotPath_iefix: asset-url("fontawesome-webfont.eot#iefix");
@fontAwesomeWoffPath: asset-url("fontawesome-webfont.woff");
@fontAwesomeTtfPath: asset-url("fontawesome-webfont.ttf");
@fontAwesomeSvgPath: asset-url("fontawesome-webfont.svg#fontawesomeregular");
// Font Awesome
//@import "fontawesome/font-awesome";
// Glyphicons
//@import "twitter/bootstrap/sprites.less";
.list-inline > li.first {
padding-left: 0px;
}
h2 {
font-size: 150%;
}
/*
#subtitle {
color: lighten(@brown, 30%);
margin-top: 0px;
padding-top: 0px;
padding-left: 1em;
font-style: italic;
font-weight: normal;
}
*/
h3 {
font-size: 120%;
}
.main {
padding-right: 1em;
}
.sidebar {
margin-left: -1px;
border-left: 1px solid darken(@beige, 10%);
padding-left: 1em;
}
// this is used for eg. crops and members index pages
.six-across:nth-child(6n+1) {
margin-left: 0px
}
.three-across:nth-child(3n+1) {
margin-left: 0px;
clear: both;
}
// let's condense the hero unit a little
.jumbotron {
padding-top: 30px;
padding-bottom: 30px;
}
// info under the main heading on homepage
.jumbotron .info {
padding-top: 15px;
}
// signup widget on homepage
.jumbotron .signup {
background-color: lighten(@green, 40%);
border: 1px solid lighten(@green, 20%);
border-radius: 6px;
padding: 15px;
text-align: center;
line-height: 200%;
}
// stats shown on homepage. eg. "999 members..."
p.stats {
font-weight: bold;
}
.homepage-members {
height: 100px;
}
.homepage-members:nth-child(odd) {
margin-left: 0px;
}
#placesmap, #cropmap {
height: 500px;
}
#membermap {
height: 250px;
}
.member-location {
font-size: small;
font-style: italic;
}
.member-location a {
color: @brown;
}
.crop-thumbnail {
position:relative;
padding:0;
img {
width:100%;
}
.text {
display:none;
color:#000;
position:absolute;
bottom:0;
background:rgba(0, 0, 0, 0.8);
width:100%;
margin:0;
}
p {
padding:5px;
margin:0;
color:#fff;
}
&:hover {
.text {
display:block;
}
}
}
.member-thumbnail {
img {
height: 85px;
width: 85px;
max-width: 85px
}
}
.thumbnail {
margin-bottom: 1.5em;
.scientific-name small, .crop-name a {
display: inline-block;
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1em;
padding-bottom: 2px;
}
.crop-name a {
padding-top: 2px;
}
.scientific-name small {
margin-bottom: -2px;
}
}
li.crop-hierarchy {
list-style-type: disc;
}
.navbar-brand {
margin: 0px;
padding: 0px;
}
.navbar-bottom {
margin: 40px 0px 0px 0px !important;
}
// navbar centering
footer .navbar .nav {
float: none;
display: inline-block;
*display: inline;
/* ie7 fix */
*zoom: 1;
/* hasLayout ie7 trigger */
vertical-align: top;
> li {
float: none;
display: inline-block;
*display: inline;
/* ie7 fix */
*zoom: 1;
/* hasLayout ie7 trigger */
vertical-align: top;
}
}
.navbar-bottom.navbar {
text-align: center;
border-radius: 0;
}
.crop-image, .member-image {
width: 100%;
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) {
.sidebar {
margin-left: 0;
border-left: none;
padding-left: 0;
}
#map {
height: 300px;
}
.navbar .nav > li {
display: block;
}
}

View File

@@ -1,290 +0,0 @@
@import "bootstrap-sprockets"
@import "bootstrap"
@import "custom_bootstrap/variables"
// this padding needs to be done before the responsive stuff is imported
body
// modifying this for our promotional banner. can be replaced after if
// needed.
// padding-top: $navbar-height + 15px
padding-top: $navbar-height
//@import "bootstrap/responsive"
// Font Awesome
@import "font-awesome-sprockets"
@import "font-awesome"
// Glyphicons
//@import "bootstrap/glyphicons"
.list-inline > li.first
padding-left: 0px
h2
font-size: 150%
//#subtitle
// color: lighten($brown, 30%)
// font-style: italic
// font-weight: normal
// margin-top: 0px
// padding-left: 1em
// padding-top: 0px
h3
font-size: 120%
.main
padding-right: 1em
.sidebar
border-left: 1px solid darken($beige, 10%)
margin-left: -1px
padding-left: 1em
// this is used for eg. crops and members index pages
.six-across:nth-child(6n+1)
margin-left: 0px
.three-across:nth-child(3n+1)
margin-left: 0px
clear: both
// let's condense the hero unit a little
.jumbotron
padding-top: 30px
padding-bottom: 30px
// info under the main heading on homepage
.jumbotron .info
padding-top: 15px
// signup widget on homepage
.jumbotron .signup
background-color: lighten($green, 40%)
border: 1px solid lighten($green, 20%)
border-radius: 6px
line-height: 200%
padding: 15px
text-align: center
// stats shown on homepage. eg. "999 members..."
p.stats
font-weight: bold
.member-cards
display: flex
flex: none
flex-wrap: wrap
justify-content: space-between
.member-thumbnail
padding: .25em
div
width: 5em
display: inline-block
vertical-align: top
.member-thumbnail div~div
padding-left: 1em
width: 15em
#placesmap, #cropmap
height: 500px
#membermap
height: 250px
.member-location
font-size: small
font-style: italic
.member-location a
color: $brown
.photo-thumbnail
padding: 0
position: relative
img
width: 100%
.text
display: none
color: #000
position: absolute
bottom: 0
background: rgba(0, 0, 0, 0.8)
width: 100%
margin: 0
p
padding: 5px
margin: 0
color: #fff
&:hover
.text
display: block
.thumbnail
border: none
text-align: center
margin-bottom: 1.5em
.member-thumbnail
text-align: left
img
height: 85px
width: 85px
max-width: 85px
.crop-thumbnail
height: 220px
.cropinfo
display: inline-block
max-width: 100%
white-space: nowrap
line-height: 1em
padding-bottom: 2px
.cropname
overflow: hidden
text-overflow: ellipsis
.scientificname
font-size: small
font-style: italic
overflow: hidden
text-overflow: ellipsis
.plantingcount
font-size: small
.crop-name a
padding-top: 2px
.scientific-name small
margin-bottom: -2px
li.crop-hierarchy
list-style-type: disc
.navbar-brand
margin: 0px
padding: 0px
.navbar-bottom
margin: 40px 0px 0px 0px !important
// footer
footer
#footer1, #footer2, #footer3
text-align: left
padding-top: 1em
padding-bottom: 2em
ul
list-style-type: none
list-style-position: outside
padding-left: 0px
margin-left: 0px
a
color: $navbar-default-link-color
text-decoration: none
a:hover
color: $navbar-default-link-hover-color
a:active
color: $navbar-default-link-active-color
.navbar-bottom.navbar
border-radius: 0
// ensure footer is pushed to bottom of browser window
#maincontainer
min-height: 80%
html, body
height: 100%
.crop-image, .member-image
width: 100%
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
.alert
a
font-weight: 800
// Overrides applying only to mobile view. This must be at the end of the overrides file.
@media only screen and (max-width: 767px)
.sidebar
margin-left: 0
border-left: none
padding-left: 0
#map
height: 300px
.navbar .nav > li
display: block
/* override "info" alert boxes to be green, not blue, on Growstuff */
$state-info-text: darken($green, 10%)
$state-info-bg: lighten($green, 50%)
/* and set "success" to be the same, as it was just very slightly
* different because the default bootstrap green is slightly different
* from ours */
$state-success-text: darken($green, 10%)
$state-success-bg: lighten($green, 50%)
.hide
display: none
#add-sci_name-row, #remove-sci_name-row, #add-alt_name-row, #remove-alt_name-row
display: none
.panel-footer
height: 6em
#gardens_panel_body
height: 20em
.form-group.required .control-label:before
content: "* "
color: red
.margin-bottom
margin-bottom: 1em
.red
color: red

View File

@@ -36,7 +36,7 @@ class AccountTypesController < ApplicationController
# POST /account_types # POST /account_types
def create def create
@account_type = AccountType.new(account_type_params) @account_type = AccountType.new(params[:account_type])
respond_to do |format| respond_to do |format|
if @account_type.save if @account_type.save
@@ -52,7 +52,7 @@ class AccountTypesController < ApplicationController
@account_type = AccountType.find(params[:id]) @account_type = AccountType.find(params[:id])
respond_to do |format| respond_to do |format|
if @account_type.update(account_type_params) if @account_type.update_attributes(params[:account_type])
format.html { redirect_to @account_type, notice: 'Account type was successfully updated.' } format.html { redirect_to @account_type, notice: 'Account type was successfully updated.' }
else else
format.html { render action: "edit" } format.html { render action: "edit" }
@@ -69,10 +69,4 @@ class AccountTypesController < ApplicationController
format.html { redirect_to account_types_url, notice: 'Account type was successfully deleted.' } format.html { redirect_to account_types_url, notice: 'Account type was successfully deleted.' }
end end
end end
private
def account_type_params
params.require(:account_type).permit(:is_paid, :is_permanent_paid, :name)
end
end end

View File

@@ -30,7 +30,7 @@ class AccountsController < ApplicationController
@account = Account.find(params[:id]) @account = Account.find(params[:id])
respond_to do |format| respond_to do |format|
if @account.update(params[:account]) if @account.update_attributes(params[:account])
format.html { redirect_to @account, notice: 'Account detail was successfully updated.' } format.html { redirect_to @account, notice: 'Account detail was successfully updated.' }
else else
format.html { render action: "edit" } format.html { render action: "edit" }
@@ -38,10 +38,4 @@ class AccountsController < ApplicationController
end end
end end
private
def account_params
params.require(:account).permit(:account_type_id, :member_id, :paid_until)
end
end end

View File

@@ -8,7 +8,7 @@ class Admin::OrdersController < ApplicationController
def search def search
authorize! :manage, :all authorize! :manage, :all
@orders = Order.search({by: params[:search_by], for: params[:search_text]}) @orders = Order.search({:by => params[:search_by], :for => params[:search_text]})
if @orders.empty? if @orders.empty?
flash[:alert] = "Couldn't find order with #{params[:search_by]} = #{params[:search_text]}" flash[:alert] = "Couldn't find order with #{params[:search_by]} = #{params[:search_text]}"

View File

@@ -1,5 +1,5 @@
class AlternateNamesController < ApplicationController class AlternateNamesController < ApplicationController
before_filter :authenticate_member!, except: [:index, :show] before_filter :authenticate_member!, :except => [:index, :show]
load_and_authorize_resource load_and_authorize_resource
# GET /alternate_names # GET /alternate_names
@@ -45,7 +45,7 @@ class AlternateNamesController < ApplicationController
# POST /alternate_names.json # POST /alternate_names.json
def create def create
params[:alternate_name][:creator_id] = current_member.id params[:alternate_name][:creator_id] = current_member.id
@alternate_name = AlternateName.new(alternate_name_params) @alternate_name = AlternateName.new(params[:alternate_name])
respond_to do |format| respond_to do |format|
if @alternate_name.save if @alternate_name.save
@@ -64,7 +64,7 @@ class AlternateNamesController < ApplicationController
@alternate_name = AlternateName.find(params[:id]) @alternate_name = AlternateName.find(params[:id])
respond_to do |format| respond_to do |format|
if @alternate_name.update(alternate_name_params) if @alternate_name.update_attributes(params[:alternate_name])
format.html { redirect_to @alternate_name.crop, notice: 'Alternate name was successfully updated.' } format.html { redirect_to @alternate_name.crop, notice: 'Alternate name was successfully updated.' }
format.json { head :no_content } format.json { head :no_content }
else else
@@ -88,10 +88,4 @@ class AlternateNamesController < ApplicationController
format.json { head :no_content } format.json { head :no_content }
end end
end end
private
def alternate_name_params
params.require(:alternate_name).permit(:crop_id, :name, :creator_id)
end
end end

View File

@@ -0,0 +1,13 @@
class Api::V1::CropsController < ApplicationController
# GET /api/v1/crops
def index
@crops = Crop.all
end
# GET /crops/1
def show
@crop = Crop.find(params[:id])
end
end

View File

@@ -22,10 +22,6 @@ class ApplicationController < ActionController::Base
stored_location_for(:member) || root_path stored_location_for(:member) || root_path
end end
def after_sign_out_path_for(resource_or_scope)
request.referrer
end
# tweak CanCan defaults because we don't have a "current_user" method # tweak CanCan defaults because we don't have a "current_user" method
# this means that we use current_user in specs but current_member everywhere # this means that we use current_user in specs but current_member everywhere
# else in the code. # else in the code.
@@ -35,9 +31,9 @@ class ApplicationController < ActionController::Base
# CanCan error handling # CanCan error handling
rescue_from CanCan::AccessDenied do |exception| rescue_from CanCan::AccessDenied do |exception|
redirect_to request.referer || root_url, alert: exception.message redirect_to request.referer || root_url, :alert => exception.message
end end
def set_locale def set_locale
I18n.locale = params[:locale] || extract_locale_from_subdomain || I18n.default_locale I18n.locale = params[:locale] || extract_locale_from_subdomain || I18n.default_locale
end end
@@ -47,36 +43,4 @@ class ApplicationController < ActionController::Base
I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end end
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) do |member|
member.permit(:login_name, :email, :password, :password_confirmation,
:remember_me, :login,
# terms of service
:tos_agreement,
# profile stuff
:bio, :location, :latitude, :longitude,
# email settings
:show_email, :newsletter, :send_notification_email, :send_planting_reminder
)
end
devise_parameter_sanitizer.for(:account_update) do |member|
member.permit(:login_name, :email, :password, :password_confirmation,
:remember_me, :login,
# terms of service
:tos_agreement,
# profile stuff
:bio, :location, :latitude, :longitude,
# email settings
:show_email, :newsletter, :send_notification_email, :send_planting_reminder,
#update password
:current_password
)
end
end
end end

View File

@@ -2,6 +2,15 @@ class AuthenticationsController < ApplicationController
before_filter :authenticate_member! before_filter :authenticate_member!
load_and_authorize_resource load_and_authorize_resource
# GET /authentications
def index
@authentications = current_member.authentications if member_signed_in?
respond_to do |format|
format.html # index.html.erb
end
end
# POST /authentications # POST /authentications
def create def create
auth = request.env['omniauth.auth'] auth = request.env['omniauth.auth']
@@ -18,17 +27,12 @@ class AuthenticationsController < ApplicationController
name = auth['info']['name'] name = auth['info']['name']
end end
@authentication = current_member.authentications @authentication = current_member.authentications.find_or_create_by_provider_and_uid(
.create_with( :provider => auth['provider'],
name: name, :uid => auth['uid'],
token: auth['credentials']['token'], :name => name,
secret: auth['credentials']['secret'] :token => auth['credentials']['token'],
) :secret => auth['credentials']['secret'])
.find_or_create_by(
provider: auth['provider'],
uid: auth['uid'],
name: name)
flash[:notice] = "Authentication successful." flash[:notice] = "Authentication successful."
else else
flash[:notice] = "Authentication failed." flash[:notice] = "Authentication failed."

View File

@@ -1,16 +1,18 @@
class CommentsController < ApplicationController class CommentsController < ApplicationController
before_filter :authenticate_member!, except: [:index, :show] before_filter :authenticate_member!, :except => [:index, :show]
load_and_authorize_resource load_and_authorize_resource
cache_sweeper :comment_sweeper
# GET /comments # GET /comments
# GET /comments.json # GET /comments.json
def index def index
@comments = Comment.paginate(page: params[:page]) @comments = Comment.paginate(:page => params[:page])
respond_to do |format| respond_to do |format|
format.html # index.html.erb format.html # index.html.erb
format.json { render json: @comments } format.json { render json: @comments }
format.rss { render layout: false } format.rss { render :layout => false }
end end
end end
@@ -39,7 +41,7 @@ class CommentsController < ApplicationController
end end
else else
redirect_to request.referer || root_url, redirect_to request.referer || root_url,
alert: "Can't post a comment on a non-existent post" :alert => "Can't post a comment on a non-existent post"
end end
end end
@@ -53,11 +55,11 @@ class CommentsController < ApplicationController
# POST /comments.json # POST /comments.json
def create def create
params[:comment][:author_id] = current_member.id params[:comment][:author_id] = current_member.id
@comment = Comment.new(comment_params) @comment = Comment.new(params[:comment])
respond_to do |format| respond_to do |format|
if @comment.save if @comment.save
format.html { redirect_to @comment.post, notice: "Comment was successfully created." } format.html { redirect_to @comment.post, notice: 'Comment was successfully created.' }
format.json { render json: @comment, status: :created, location: @comment } format.json { render json: @comment, status: :created, location: @comment }
else else
format.html { render action: "new" } format.html { render action: "new" }
@@ -77,7 +79,7 @@ class CommentsController < ApplicationController
params[:comment].delete("author_id") params[:comment].delete("author_id")
respond_to do |format| respond_to do |format|
if @comment.update(comment_params) if @comment.update_attributes(params[:comment])
format.html { redirect_to @comment.post, notice: 'Comment was successfully updated.' } format.html { redirect_to @comment.post, notice: 'Comment was successfully updated.' }
format.json { head :no_content } format.json { head :no_content }
else else
@@ -99,10 +101,4 @@ class CommentsController < ApplicationController
format.json { head :no_content } format.json { head :no_content }
end end
end end
private
def comment_params
params.require(:comment).permit(:author_id, :body, :post_id)
end
end end

View File

@@ -1,9 +1,9 @@
require 'will_paginate/array'
class CropsController < ApplicationController class CropsController < ApplicationController
before_filter :authenticate_member!, except: [:index, :hierarchy, :search, :show] before_filter :authenticate_member!, :except => [:index, :hierarchy, :search, :show]
load_and_authorize_resource load_and_authorize_resource
skip_authorize_resource only: [:hierarchy, :search] skip_authorize_resource :only => [:hierarchy, :search]
cache_sweeper :crop_sweeper
# GET /crops # GET /crops
# GET /crops.json # GET /crops.json
@@ -11,43 +11,30 @@ class CropsController < ApplicationController
@sort = params[:sort] @sort = params[:sort]
if @sort == 'alpha' if @sort == 'alpha'
# alphabetical order # alphabetical order
@crops = Crop.includes(:scientific_names, {plantings: :photos}) @crops = Crop.includes(:scientific_names, {:plantings => :photos}).paginate(:page => params[:page])
@paginated_crops = @crops.approved.paginate(page: params[:page])
else else
# default to sorting by popularity # default to sorting by popularity
@crops = Crop.popular.includes(:scientific_names, {plantings: :photos}) @crops = Crop.popular.includes(:scientific_names, {:plantings => :photos}).paginate(:page => params[:page])
@paginated_crops = @crops.approved.paginate(page: params[:page])
end end
respond_to do |format| respond_to do |format|
format.html format.html
format.json { render json: @crops } format.json { render :json => @crops }
format.rss do format.rss do
@crops = Crop.recent.includes(:scientific_names, :creator) @crops = Crop.recent.includes(:scientific_names, :creator)
render rss: @crops render :rss => @crops
end end
format.csv do format.csv do
@filename = "Growstuff-Crops-#{Time.zone.now.to_s(:number)}.csv" @filename = "Growstuff-Crops-#{Time.zone.now.to_s(:number)}.csv"
@crops = Crop.includes(:scientific_names, :plantings, :seeds, :creator) @crops = Crop.includes(:scientific_names, :plantings, :seeds, :creator)
render csv: @crops render :csv => @crops
end end
end end
end end
# GET /crops/wrangle # GET /crops/wrangle
def wrangle def wrangle
@approval_status = params[:approval_status] @crops = Crop.recent.paginate(:page => params[:page])
case @approval_status
when "pending"
@crops = Crop.pending_approval
when "rejected"
@crops = Crop.rejected
else
@crops = Crop.recent
end
@crops = @crops.paginate(page: params[:page])
@crop_wranglers = Role.crop_wranglers @crop_wranglers = Role.crop_wranglers
respond_to do |format| respond_to do |format|
format.html format.html
@@ -64,27 +51,32 @@ class CropsController < ApplicationController
# GET /crops/search # GET /crops/search
def search def search
@term = params[:term] @search = params[:search]
@matches = Crop.search(@term) @exact_match = Crop.find_by_name(params[:search])
@paginated_matches = @matches.paginate(page: params[:page])
@partial_matches = Crop.search(params[:search])
# exclude exact match from partial match list
@partial_matches.reject!{ |r| @exact_match && r.eql?(@exact_match) }
@fuzzy = Crop.search(params[:term])
respond_to do |format| respond_to do |format|
format.html format.html
format.json { render json: @matches } format.json { render :json => @fuzzy }
end end
end end
# GET /crops/1 # GET /crops/1
# GET /crops/1.json # GET /crops/1.json
def show def show
@crop = Crop.includes(:scientific_names, {plantings: :photos}).find(params[:id]) @crop = Crop.includes(:scientific_names, {:plantings => :photos}).find(params[:id])
@posts = @crop.posts.paginate(page: params[:page]) @posts = @crop.posts.paginate(:page => params[:page])
respond_to do |format| respond_to do |format|
format.html # show.html.haml format.html # show.html.haml
format.json do format.json do
render json: @crop.to_json(include: { render :json => @crop.to_json(:include => {
plantings: { include: { owner: { only: [:id, :login_name, :location, :latitude, :longitude] }}} :plantings => { :include => { :owner => { :only => [:id, :login_name, :location, :latitude, :longitude] }}}
}) })
end end
end end
@@ -94,9 +86,9 @@ class CropsController < ApplicationController
# GET /crops/new.json # GET /crops/new.json
def new def new
@crop = Crop.new @crop = Crop.new
@crop.alternate_names.build 3.times do
@crop.scientific_names.build @crop.scientific_names.build
end
respond_to do |format| respond_to do |format|
format.html # new.html.haml format.html # new.html.haml
format.json { render json: @crop } format.json { render json: @crop }
@@ -106,41 +98,17 @@ class CropsController < ApplicationController
# GET /crops/1/edit # GET /crops/1/edit
def edit def edit
@crop = Crop.find(params[:id]) @crop = Crop.find(params[:id])
@crop.alternate_names.build if @crop.alternate_names.blank?
@crop.scientific_names.build if @crop.scientific_names.blank?
end end
# POST /crops # POST /crops
# POST /crops.json # POST /crops.json
def create def create
params[:crop][:creator_id] = current_member.id
@crop = Crop.new(crop_params) @crop = Crop.new(params[:crop])
if current_member.has_role? :crop_wrangler
@crop.creator = current_member
success_msg = "Crop was successfully created."
else
@crop.requester = current_member
@crop.approval_status = "pending"
success_msg = "Crop was successfully requested."
end
respond_to do |format| respond_to do |format|
if @crop.save if @crop.save
params[:alt_name].each do |index, value| format.html { redirect_to @crop, notice: 'Crop was successfully created.' }
@crop.alternate_names.create(name: value, creator_id: current_member.id)
end
params[:sci_name].each do |index, value|
@crop.scientific_names.create(scientific_name: value, creator_id: current_member.id)
end
unless current_member.has_role? :crop_wrangler
Role.crop_wranglers.each do |w|
Notifier.new_crop_request(w, @crop).deliver!
end
end
format.html { redirect_to @crop, notice: success_msg }
format.json { render json: @crop, status: :created, location: @crop } format.json { render json: @crop, status: :created, location: @crop }
else else
format.html { render action: "new" } format.html { render action: "new" }
@@ -154,35 +122,8 @@ class CropsController < ApplicationController
def update def update
@crop = Crop.find(params[:id]) @crop = Crop.find(params[:id])
previous_status = @crop.approval_status
@crop.creator = current_member if previous_status == "pending"
respond_to do |format| respond_to do |format|
if @crop.update(crop_params) if @crop.update_attributes(params[:crop])
if !params[:alt_name].nil?
@crop.alternate_names.each do |alt_name|
alt_name.destroy
end
params[:alt_name].each do |index, value|
alt_name = @crop.alternate_names.create(name: value, creator_id: current_member.id)
end
@crop.scientific_names.each do |sci_name|
sci_name.destroy
end
params[:sci_name].each do |index, value|
sci_name = @crop.scientific_names.create(scientific_name: value, creator_id: current_member.id)
end
end
if previous_status == "pending"
requester = @crop.requester
new_status = @crop.approval_status
Notifier.crop_request_approved(requester, @crop).deliver! if new_status == "approved"
Notifier.crop_request_rejected(requester, @crop).deliver! if new_status == "rejected"
end
format.html { redirect_to @crop, notice: 'Crop was successfully updated.' } format.html { redirect_to @crop, notice: 'Crop was successfully updated.' }
format.json { head :no_content } format.json { head :no_content }
else else
@@ -203,10 +144,4 @@ class CropsController < ApplicationController
format.json { head :no_content } format.json { head :no_content }
end end
end end
private
def crop_params
params.require(:crop).permit(:en_wikipedia_url, :name, :parent_id, :creator_id, :approval_status, :request_notes, :reason_for_rejection, :rejection_notes, scientific_names_attributes: [:scientific_name, :_destroy, :id])
end
end end

View File

@@ -1,12 +1,8 @@
class FollowsController < ApplicationController class FollowsController < ApplicationController
before_filter :authenticate_member!
load_and_authorize_resource
skip_load_resource only: :create
# POST /follows # POST /follows
def create def create
@follow = current_member.follows.build(:followed_id => params[:followed_id])
@follow = current_member.follows.build(followed_id: follow_params[:followed_id])
if @follow.save if @follow.save
flash[:notice] = "Followed #{ @follow.followed.login_name }" flash[:notice] = "Followed #{ @follow.followed.login_name }"
@@ -19,17 +15,11 @@ class FollowsController < ApplicationController
# DELETE /follows/1 # DELETE /follows/1
def destroy def destroy
@follow = current_member.follows.find(follow_params[:id]) @follow = current_member.follows.find(params[:id])
unfollowed_name = @follow.followed.login_name unfollowed_name = @follow.followed.login_name
@follow.destroy @follow.destroy
flash[:notice] = "Unfollowed #{ unfollowed_name }" flash[:notice] = "Unfollowed #{ unfollowed_name }"
redirect_to root_path redirect_to root_path
end end
private
def follow_params
params.permit(:id, :followed_id, :follower_id, :authenticity_token, :_method)
end
end end

View File

@@ -1,6 +1,8 @@
class ForumsController < ApplicationController class ForumsController < ApplicationController
load_and_authorize_resource load_and_authorize_resource
cache_sweeper :forum_sweeper
# GET /forums # GET /forums
# GET /forums.json # GET /forums.json
def index def index
@@ -42,7 +44,7 @@ class ForumsController < ApplicationController
# POST /forums # POST /forums
# POST /forums.json # POST /forums.json
def create def create
@forum = Forum.new(forum_params) @forum = Forum.new(params[:forum])
respond_to do |format| respond_to do |format|
if @forum.save if @forum.save
@@ -61,7 +63,7 @@ class ForumsController < ApplicationController
@forum = Forum.find(params[:id]) @forum = Forum.find(params[:id])
respond_to do |format| respond_to do |format|
if @forum.update(forum_params) if @forum.update_attributes(params[:forum])
format.html { redirect_to @forum, notice: 'Forum was successfully updated.' } format.html { redirect_to @forum, notice: 'Forum was successfully updated.' }
format.json { head :no_content } format.json { head :no_content }
else else
@@ -82,10 +84,4 @@ class ForumsController < ApplicationController
format.json { head :no_content } format.json { head :no_content }
end end
end end
private
def forum_params
params.require(:forum).permit(:description, :name, :owner_id, :slug)
end
end end

View File

@@ -1,15 +1,16 @@
class GardensController < ApplicationController class GardensController < ApplicationController
before_filter :authenticate_member!, except: [:index, :show] before_filter :authenticate_member!, :except => [:index, :show]
load_and_authorize_resource load_and_authorize_resource
cache_sweeper :garden_sweeper
# GET /gardens # GET /gardens
# GET /gardens.json # GET /gardens.json
def index def index
@gardens = Garden.paginate(page: params[:page]) @gardens = Garden.paginate(:page => params[:page])
@owner = Member.find_by_slug(params[:owner]) @owner = Member.find_by_slug(params[:owner])
if @owner if @owner
@gardens = @owner.gardens.paginate(page: params[:page]) @gardens = @owner.gardens.paginate(:page => params[:page])
end end
respond_to do |format| respond_to do |format|
@@ -49,13 +50,12 @@ class GardensController < ApplicationController
# POST /gardens.json # POST /gardens.json
def create def create
params[:garden][:owner_id] = current_member.id params[:garden][:owner_id] = current_member.id
@garden = Garden.new(garden_params) @garden = Garden.new(params[:garden])
respond_to do |format| respond_to do |format|
if @garden.save if @garden.save
format.html { redirect_to @garden, notice: 'Garden was successfully created.' } format.html { redirect_to @garden, notice: 'Garden was successfully created.' }
format.json { render json: @garden, status: :created, location: @garden } format.json { render json: @garden, status: :created, location: @garden }
expire_fragment("homepage_stats")
else else
format.html { render action: "new" } format.html { render action: "new" }
format.json { render json: @garden.errors, status: :unprocessable_entity } format.json { render json: @garden.errors, status: :unprocessable_entity }
@@ -69,7 +69,7 @@ class GardensController < ApplicationController
@garden = Garden.find(params[:id]) @garden = Garden.find(params[:id])
respond_to do |format| respond_to do |format|
if @garden.update(garden_params) if @garden.update_attributes(params[:garden])
format.html { redirect_to @garden, notice: 'Garden was successfully updated.' } format.html { redirect_to @garden, notice: 'Garden was successfully updated.' }
format.json { head :no_content } format.json { head :no_content }
else else
@@ -84,18 +84,10 @@ class GardensController < ApplicationController
def destroy def destroy
@garden = Garden.find(params[:id]) @garden = Garden.find(params[:id])
@garden.destroy @garden.destroy
expire_fragment("homepage_stats")
respond_to do |format| respond_to do |format|
format.html { redirect_to gardens_by_owner_path(owner: @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 } format.json { head :no_content }
end end
end end
private
def garden_params
params.require(:garden).permit(:name, :slug, :owner_id, :description, :active,
:location, :latitude, :longitude, :area, :area_unit)
end
end end

View File

@@ -1,5 +1,5 @@
class HarvestsController < ApplicationController class HarvestsController < ApplicationController
before_filter :authenticate_member!, except: [:index, :show] before_filter :authenticate_member!, :except => [:index, :show]
load_and_authorize_resource load_and_authorize_resource
@@ -17,12 +17,12 @@ class HarvestsController < ApplicationController
end end
respond_to do |format| respond_to do |format|
format.html { @harvests = @harvests.paginate(page: params[:page]) } format.html { @harvests = @harvests.paginate(:page => params[:page]) }
format.json { render json: @harvests } format.json { render json: @harvests }
format.csv do format.csv do
specifics = (@owner ? "#{@owner.login_name}-" : @crop ? "#{@crop.name}-" : nil) specifics = (@owner ? "#{@owner.name}-" : @crop ? "#{@crop.name}-" : nil)
@filename = "Growstuff-#{specifics}Harvests-#{Time.zone.now.to_s(:number)}.csv" @filename = "Growstuff-#{specifics}Harvests-#{Time.zone.now.to_s(:number)}.csv"
render csv: @harvests render :csv => @harvests
end end
end end
end end
@@ -62,7 +62,7 @@ class HarvestsController < ApplicationController
def create def create
params[:harvest][:owner_id] = current_member.id params[:harvest][:owner_id] = current_member.id
params[:harvested_at] = parse_date(params[:harvested_at]) params[:harvested_at] = parse_date(params[:harvested_at])
@harvest = Harvest.new(harvest_params) @harvest = Harvest.new(params[:harvest])
respond_to do |format| respond_to do |format|
if @harvest.save if @harvest.save
@@ -81,7 +81,7 @@ class HarvestsController < ApplicationController
@harvest = Harvest.find(params[:id]) @harvest = Harvest.find(params[:id])
respond_to do |format| respond_to do |format|
if @harvest.update(harvest_params) if @harvest.update_attributes(params[:harvest])
format.html { redirect_to @harvest, notice: 'Harvest was successfully updated.' } format.html { redirect_to @harvest, notice: 'Harvest was successfully updated.' }
format.json { head :no_content } format.json { head :no_content }
else else
@@ -102,11 +102,4 @@ class HarvestsController < ApplicationController
format.json { head :no_content } format.json { head :no_content }
end end
end end
private
def harvest_params
params.require(:harvest).permit(:crop_id, :harvested_at, :description, :owner_id,
:quantity, :unit, :weight_quantity, :weight_unit, :plant_part_id, :slug, :si_weight)
end
end end

View File

@@ -1,21 +1,16 @@
class MembersController < ApplicationController class MembersController < ApplicationController
load_and_authorize_resource load_and_authorize_resource
skip_authorize_resource only: [:nearby, :unsubscribe] cache_sweeper :member_sweeper
after_action :expire_cache_fragments, only: :create skip_authorize_resource :only => :nearby
def index def index
@sort = params[:sort] @members = Member.confirmed.paginate(:page => params[:page])
if @sort == 'recently_joined'
@members = Member.confirmed.recently_joined.paginate(page: params[:page])
else
@members = Member.confirmed.paginate(page: params[:page])
end
respond_to do |format| respond_to do |format|
format.html # index.html.haml format.html # index.html.haml
format.json { render json: @members.to_json(only: [:id, :login_name, :slug, :bio, :created_at, :location, :latitude, :longitude]) } format.json { render :json => @members.to_json(:only => [:id, :login_name, :slug, :bio, :created_at, :location, :latitude, :longitude]) }
end end
end end
@@ -31,49 +26,22 @@ class MembersController < ApplicationController
respond_to do |format| respond_to do |format|
format.html # show.html.haml format.html # show.html.haml
format.json { render json: @member.to_json(only: [:id, :login_name, :bio, :created_at, :slug, :location, :latitude, :longitude]) } format.json { render :json => @member.to_json(:only => [:id, :login_name, :bio, :created_at, :slug, :location, :latitude, :longitude]) }
format.rss { render( format.rss { render(
layout: false, :layout => false,
locals: { member: @member } :locals => { :member => @member }
)} )}
end end
end end
def view_follows def view_follows
@member = Member.confirmed.find(params[:login_name]) @member = Member.confirmed.find(params[:login_name])
@follows = @member.followed.paginate(page: params[:page]) @follows = @member.followed.paginate(:page => params[:page])
end end
def view_followers def view_followers
@member = Member.confirmed.find(params[:login_name]) @member = Member.confirmed.find(params[:login_name])
@followers = @member.followers.paginate(page: params[:page]) @followers = @member.followers.paginate(:page => params[:page])
end
EMAIL_TYPE_STRING = {
send_notification_email: "direct message notifications",
send_planting_reminder: "planting reminders"
}
def unsubscribe
begin
verifier = ActiveSupport::MessageVerifier.new(ENV['RAILS_SECRET_TOKEN'])
decrypted_message = verifier.verify(params[:message])
@member = Member.find(decrypted_message[:member_id])
@type = decrypted_message[:type]
@member.update(@type => false)
flash.now[:notice] = "You have been unsubscribed from #{EMAIL_TYPE_STRING[@type]} emails."
rescue ActiveSupport::MessageVerifier::InvalidSignature
flash.now[:alert] = "We're sorry, there was an error updating your settings."
end
end
private
def expire_cache_fragments
expire_fragment("homepage_stats")
end end
end end

View File

@@ -5,7 +5,7 @@ class NotificationsController < ApplicationController
# GET /notifications # GET /notifications
def index def index
@notifications = Notification.where(recipient_id: current_member).page(params[:page]) @notifications = Notification.find_all_by_recipient_id(current_member)
respond_to do |format| respond_to do |format|
format.html # index.html.erb format.html # index.html.erb
@@ -36,23 +36,6 @@ class NotificationsController < ApplicationController
end end
end end
# GET /notifications/1/reply
def reply
@notification = Notification.new
@sender_notification = Notification.find(params[:id])
@sender_notification.read = true
@sender_notification.save
@recipient = @sender_notification.sender
@subject = @sender_notification.subject =~ /^Re: / ?
@sender_notification.subject :
"Re: " + @sender_notification.subject
respond_to do |format|
format.html # reply.html.haml
end
end
# DELETE /notifications/1 # DELETE /notifications/1
def destroy def destroy
@notification = Notification.find(params[:id]) @notification = Notification.find(params[:id])
@@ -66,7 +49,7 @@ class NotificationsController < ApplicationController
# POST /notifications # POST /notifications
def create def create
params[:notification][:sender_id] = current_member.id params[:notification][:sender_id] = current_member.id
@notification = Notification.new(notification_params) @notification = Notification.new(params[:notification])
@recipient = Member.find_by_id(params[:notification][:recipient_id]) @recipient = Member.find_by_id(params[:notification][:recipient_id])
respond_to do |format| respond_to do |format|
@@ -77,10 +60,4 @@ class NotificationsController < ApplicationController
end end
end end
end end
private
def notification_params
params.require(:notification).permit(:sender_id, :recipient_id, :subject, :body, :post_id, :read)
end
end end

View File

@@ -7,8 +7,8 @@ class OrderItemsController < ApplicationController
if params[:order_item][:price] if params[:order_item][:price]
params[:order_item][:price] = params[:order_item][:price].to_f * 100 # convert to cents params[:order_item][:price] = params[:order_item][:price].to_f * 100 # convert to cents
end end
@order_item = OrderItem.new(order_item_params) @order_item = OrderItem.new(params[:order_item])
@order_item.order = current_member.current_order || Order.create(member_id: current_member.id) @order_item.order = current_member.current_order || Order.create(:member_id => current_member.id)
respond_to do |format| respond_to do |format|
if @order_item.save if @order_item.save
@@ -20,10 +20,4 @@ class OrderItemsController < ApplicationController
end end
end end
end end
private
def order_item_params
params.require(:order_item).permit(:order_id, :price, :product_id, :quantity)
end
end end

View File

@@ -4,7 +4,7 @@ class OrdersController < ApplicationController
# GET /orders # GET /orders
def index def index
@orders = Order.where(member_id: current_member.id) @orders = Order.find_all_by_member_id(current_member.id)
respond_to do |format| respond_to do |format|
format.html # index.html.erb format.html # index.html.erb
@@ -34,15 +34,15 @@ class OrdersController < ApplicationController
@order = Order.find(params[:id]) @order = Order.find(params[:id])
respond_to do |format| respond_to do |format|
if @order.update_attributes(referral_code: params[:referral_code]) if @order.update_attributes(:referral_code => params[:referral_code])
response = EXPRESS_GATEWAY.setup_purchase( response = EXPRESS_GATEWAY.setup_purchase(
@order.total, @order.total,
items: @order.activemerchant_items, :items => @order.activemerchant_items,
currency: Growstuff::Application.config.currency, :currency => Growstuff::Application.config.currency,
no_shipping: true, :no_shipping => true,
ip: request.remote_ip, :ip => request.remote_ip,
return_url: complete_order_url, :return_url => complete_order_url,
cancel_return_url: shop_url :cancel_return_url => shop_url
) )
format.html { redirect_to EXPRESS_GATEWAY.redirect_url_for(response.token) } format.html { redirect_to EXPRESS_GATEWAY.redirect_url_for(response.token) }
else else
@@ -58,10 +58,10 @@ class OrdersController < ApplicationController
if (params[:token] && params['PayerID']) if (params[:token] && params['PayerID'])
purchase = EXPRESS_GATEWAY.purchase( purchase = EXPRESS_GATEWAY.purchase(
@order.total, @order.total,
currency: Growstuff::Application.config.currency, :currency => Growstuff::Application.config.currency,
ip: request.remote_ip, :ip => request.remote_ip,
payer_id: params['PayerID'], :payer_id => params['PayerID'],
token: params[:token] :token => params[:token]
) )
if purchase.success? if purchase.success?
@order.completed_at = Time.zone.now @order.completed_at = Time.zone.now

View File

@@ -1,11 +1,13 @@
class PhotosController < ApplicationController class PhotosController < ApplicationController
before_filter :authenticate_member!, except: [:index, :show] before_filter :authenticate_member!, :except => [:index, :show]
load_and_authorize_resource load_and_authorize_resource
cache_sweeper :photo_sweeper
# GET /photos # GET /photos
# GET /photos.json # GET /photos.json
def index def index
@photos = Photo.paginate(page: params[:page]) @photos = Photo.paginate(:page => params[:page])
respond_to do |format| respond_to do |format|
format.html # index.html.erb format.html # index.html.erb
@@ -59,13 +61,13 @@ class PhotosController < ApplicationController
# POST /photos.json # POST /photos.json
def create def create
@photo = Photo.find_by_flickr_photo_id(params[:photo][:flickr_photo_id]) || @photo = Photo.find_by_flickr_photo_id(params[:photo][:flickr_photo_id]) ||
Photo.new(photo_params) Photo.new(params[:photo])
@photo.owner_id = current_member.id @photo.owner_id = current_member.id
@photo.set_flickr_metadata @photo.set_flickr_metadata
# several models can have photos. we need to know what model and the id # several models can have photos. we need to know what model and the id
# for the entry to attach the photo to # for the entry to attach the photo to
valid_models = ["planting", "harvest", "garden"] valid_models = ["planting", "harvest"]
if params[:type] if params[:type]
if valid_models.include?(params[:type]) if valid_models.include?(params[:type])
if params[:id] if params[:id]
@@ -109,7 +111,7 @@ class PhotosController < ApplicationController
@photo = Photo.find(params[:id]) @photo = Photo.find(params[:id])
respond_to do |format| respond_to do |format|
if @photo.update(photo_params) if @photo.update_attributes(params[:photo])
format.html { redirect_to @photo, notice: 'Photo was successfully updated.' } format.html { redirect_to @photo, notice: 'Photo was successfully updated.' }
format.json { head :no_content } format.json { head :no_content }
else else
@@ -124,18 +126,10 @@ class PhotosController < ApplicationController
def destroy def destroy
@photo = Photo.find(params[:id]) @photo = Photo.find(params[:id])
@photo.destroy @photo.destroy
flash[:alert] = "Photo successfully deleted."
respond_to do |format| respond_to do |format|
format.html { redirect_to photos_url } format.html { redirect_to photos_url }
format.json { head :no_content } format.json { head :no_content }
end end
end end
private
def photo_params
params.require(:photo).permit(:flickr_photo_id, :owner_id, :title, :license_name,
:license_url, :thumbnail_url, :fullsize_url, :link_url)
end
end end

View File

@@ -5,7 +5,7 @@ class PlacesController < ApplicationController
respond_to do |format| respond_to do |format|
format.html format.html
# json response is whatever we want to map here # json response is whatever we want to map here
format.json { render json: Member.located.to_json(only: [:id, :login_name, :slug, :location, :latitude, :longitude]) } format.json { render :json => Member.located.to_json(:only => [:id, :login_name, :slug, :location, :latitude, :longitude]) }
end end
end end
@@ -16,22 +16,14 @@ class PlacesController < ApplicationController
@nearby_members = Member.nearest_to(params[:place]) @nearby_members = Member.nearest_to(params[:place])
respond_to do |format| respond_to do |format|
format.html # show.html.haml format.html # show.html.haml
format.json { render json: @nearby_members.to_json(only: [:id, :login_name, :slug, :location, :latitude, :longitude]) } format.json { render :json => @nearby_members.to_json(:only => [:id, :login_name, :slug, :location, :latitude, :longitude]) }
end end
end end
def search def search
if params[:new_place].empty? respond_to do |format|
respond_to do |format| format.html do
format.html do redirect_to place_path(params[:new_place])
redirect_to places_path, alert: 'Please enter a valid location'
end
end
else
respond_to do |format|
format.html do
redirect_to place_path(params[:new_place])
end
end end
end end
end end

View File

@@ -42,7 +42,7 @@ class PlantPartsController < ApplicationController
# POST /plant_parts # POST /plant_parts
# POST /plant_parts.json # POST /plant_parts.json
def create def create
@plant_part = PlantPart.new(plant_part_params) @plant_part = PlantPart.new(params[:plant_part])
respond_to do |format| respond_to do |format|
if @plant_part.save if @plant_part.save
@@ -61,7 +61,7 @@ class PlantPartsController < ApplicationController
@plant_part = PlantPart.find(params[:id]) @plant_part = PlantPart.find(params[:id])
respond_to do |format| respond_to do |format|
if @plant_part.update(plant_part_params) if @plant_part.update_attributes(params[:plant_part])
format.html { redirect_to @plant_part, notice: 'Plant part was successfully updated.' } format.html { redirect_to @plant_part, notice: 'Plant part was successfully updated.' }
format.json { head :no_content } format.json { head :no_content }
else else
@@ -82,10 +82,4 @@ class PlantPartsController < ApplicationController
format.json { head :no_content } format.json { head :no_content }
end end
end end
private
def plant_part_params
params.require(:plant_part).permit(:name, :slug)
end
end end

View File

@@ -1,6 +1,9 @@
class PlantingsController < ApplicationController class PlantingsController < ApplicationController
before_filter :authenticate_member!, except: [:index, :show] before_filter :authenticate_member!, :except => [:index, :show]
load_and_authorize_resource load_and_authorize_resource
cache_sweeper :planting_sweeper
# GET /plantings # GET /plantings
# GET /plantings.json # GET /plantings.json
@@ -8,21 +11,21 @@ class PlantingsController < ApplicationController
@owner = Member.find_by_slug(params[:owner]) @owner = Member.find_by_slug(params[:owner])
@crop = Crop.find_by_slug(params[:crop]) @crop = Crop.find_by_slug(params[:crop])
if @owner if @owner
@plantings = @owner.plantings.includes(:owner, :crop, :garden).paginate(page: params[:page]) @plantings = @owner.plantings.includes(:owner, :crop, :garden).paginate(:page => params[:page])
elsif @crop elsif @crop
@plantings = @crop.plantings.includes(:owner, :crop, :garden).paginate(page: params[:page]) @plantings = @crop.plantings.includes(:owner, :crop, :garden).paginate(:page => params[:page])
else else
@plantings = Planting.includes(:owner, :crop, :garden).paginate(page: params[:page]) @plantings = Planting.includes(:owner, :crop, :garden).paginate(:page => params[:page])
end end
respond_to do |format| respond_to do |format|
format.html { @plantings = @plantings.paginate(page: params[:page]) } format.html { @plantings = @plantings.paginate(:page => params[:page]) }
format.json { render json: @plantings } format.json { render json: @plantings }
format.rss { render layout: false } #index.rss.builder format.rss { render :layout => false } #index.rss.builder
format.csv do format.csv do
specifics = (@owner ? "#{@owner.login_name}-" : @crop ? "#{@crop.name}-" : nil) specifics = (@owner ? "#{@owner.name}-" : @crop ? "#{@crop.name}-" : nil)
@filename = "Growstuff-#{specifics}Plantings-#{Time.zone.now.to_s(:number)}.csv" @filename = "Growstuff-#{specifics}Plantings-#{Time.zone.now.to_s(:number)}.csv"
render csv: @plantings render :csv => @plantings
end end
end end
end end
@@ -30,7 +33,7 @@ class PlantingsController < ApplicationController
# GET /plantings/1 # GET /plantings/1
# GET /plantings/1.json # GET /plantings/1.json
def show def show
@planting = Planting.includes(:owner, :crop, :garden, :photos).friendly.find(params[:id]) @planting = Planting.includes(:owner, :crop, :garden, :photos).find(params[:id])
respond_to do |format| respond_to do |format|
format.html # show.html.erb format.html # show.html.erb
@@ -65,16 +68,14 @@ class PlantingsController < ApplicationController
# POST /plantings # POST /plantings
# POST /plantings.json # POST /plantings.json
def create def create
params[:planting][:owner_id] = current_member.id
params[:planted_at] = parse_date(params[:planted_at]) params[:planted_at] = parse_date(params[:planted_at])
@planting = Planting.new(planting_params) @planting = Planting.new(params[:planting])
@planting.owner = current_member
respond_to do |format| respond_to do |format|
if @planting.save if @planting.save
@planting.update_attribute(:days_before_maturity, update_days_before_maturity(@planting, planting_params[:crop_id]))
format.html { redirect_to @planting, notice: 'Planting was successfully created.' } format.html { redirect_to @planting, notice: 'Planting was successfully created.' }
format.json { render json: @planting, status: :created, location: @planting } format.json { render json: @planting, status: :created, location: @planting }
expire_fragment("homepage_stats")
else else
format.html { render action: "new" } format.html { render action: "new" }
format.json { render json: @planting.errors, status: :unprocessable_entity } format.json { render json: @planting.errors, status: :unprocessable_entity }
@@ -89,8 +90,7 @@ class PlantingsController < ApplicationController
params[:planted_at] = parse_date(params[:planted_at]) params[:planted_at] = parse_date(params[:planted_at])
respond_to do |format| respond_to do |format|
if @planting.update(planting_params) if @planting.update_attributes(params[:planting])
@planting.update_attribute(:days_before_maturity, update_days_before_maturity(@planting, planting_params[:crop_id]))
format.html { redirect_to @planting, notice: 'Planting was successfully updated.' } format.html { redirect_to @planting, notice: 'Planting was successfully updated.' }
format.json { head :no_content } format.json { head :no_content }
else else
@@ -106,27 +106,10 @@ class PlantingsController < ApplicationController
@planting = Planting.find(params[:id]) @planting = Planting.find(params[:id])
@garden = @planting.garden @garden = @planting.garden
@planting.destroy @planting.destroy
expire_fragment("homepage_stats")
respond_to do |format| respond_to do |format|
format.html { redirect_to @garden } format.html { redirect_to @garden }
format.json { head :no_content } format.json { head :no_content }
end end
end end
private
def planting_params
params.require(:planting).permit(:crop_id, :description, :garden_id, :planted_at,
:quantity, :sunniness, :planted_from, :owner_id, :finished,
:finished_at)
end
def update_days_before_maturity(planting, crop_id)
if planting.finished_at.nil?
planting.calculate_days_before_maturity(planting, crop_id)
else
(planting.finished_at - planting.planted_at).to_i
end
end
end end

View File

@@ -1,36 +1,38 @@
class PostsController < ApplicationController class PostsController < ApplicationController
before_filter :authenticate_member!, except: [:index, :show] before_filter :authenticate_member!, :except => [:index, :show]
load_and_authorize_resource load_and_authorize_resource
cache_sweeper :post_sweeper
# GET /posts # GET /posts
# GET /posts.json # GET /posts.json
def index def index
@author = Member.find_by_slug(params[:author]) @author = Member.find_by_slug(params[:author])
if @author if @author
@posts = @author.posts.includes(:author, { comments: :author }).paginate(page: params[:page]) @posts = @author.posts.includes(:author, { :comments => :author }).paginate(:page => params[:page])
else else
@posts = Post.includes(:author, { comments: :author }).paginate(page: params[:page]) @posts = Post.includes(:author, { :comments => :author }).paginate(:page => params[:page])
end end
respond_to do |format| respond_to do |format|
format.html # index.html.haml format.html # index.html.haml
format.json { render json: @posts } format.json { render json: @posts }
format.rss { render layout: false } #index.rss.builder format.rss { render :layout => false } #index.rss.builder
end end
end end
# GET /posts/1 # GET /posts/1
# GET /posts/1.json # GET /posts/1.json
def show def show
@post = Post.includes(:author, { comments: :author }).find(params[:id]) @post = Post.includes(:author, { :comments => :author }).find(params[:id])
respond_to do |format| respond_to do |format|
format.html # show.html.haml format.html # show.html.haml
format.json { render json: @post } format.json { render json: @post }
format.rss { render( format.rss { render(
layout: false, :layout => false,
locals: { post: @post } :locals => { :post => @post }
)} )}
end end
end end
@@ -56,7 +58,7 @@ class PostsController < ApplicationController
# POST /posts.json # POST /posts.json
def create def create
params[:post][:author_id] = current_member.id params[:post][:author_id] = current_member.id
@post = Post.new(post_params) @post = Post.new(params[:post])
respond_to do |format| respond_to do |format|
if @post.save if @post.save
@@ -75,7 +77,7 @@ class PostsController < ApplicationController
@post = Post.find(params[:id]) @post = Post.find(params[:id])
respond_to do |format| respond_to do |format|
if @post.update(post_params) if @post.update_attributes(params[:post])
format.html { redirect_to @post, notice: 'Post was successfully updated.' } format.html { redirect_to @post, notice: 'Post was successfully updated.' }
format.json { head :no_content } format.json { head :no_content }
else else
@@ -96,10 +98,4 @@ class PostsController < ApplicationController
format.json { head :no_content } format.json { head :no_content }
end end
end end
private
def post_params
params.require(:post).permit(:body, :subject, :author_id, :forum_id)
end
end end

View File

@@ -36,7 +36,7 @@ class ProductsController < ApplicationController
# POST /products # POST /products
def create def create
@product = Product.new(product_params) @product = Product.new(params[:product])
respond_to do |format| respond_to do |format|
if @product.save if @product.save
@@ -52,7 +52,7 @@ class ProductsController < ApplicationController
@product = Product.find(params[:id]) @product = Product.find(params[:id])
respond_to do |format| respond_to do |format|
if @product.update(product_params) if @product.update_attributes(params[:product])
format.html { redirect_to @product, notice: 'Product was successfully updated.' } format.html { redirect_to @product, notice: 'Product was successfully updated.' }
else else
format.html { render action: "edit" } format.html { render action: "edit" }
@@ -69,11 +69,4 @@ class ProductsController < ApplicationController
format.html { redirect_to products_url } format.html { redirect_to products_url }
end end
end end
private
def product_params
params.require(:product).permit(:description, :min_price, :recommended_price, :name,
:account_type_id, :paid_months)
end
end end

View File

@@ -1,5 +1,7 @@
class RegistrationsController < Devise::RegistrationsController class RegistrationsController < Devise::RegistrationsController
cache_sweeper :member_sweeper
def edit def edit
@twitter_auth = current_member.auth('twitter') @twitter_auth = current_member.auth('twitter')
@flickr_auth = current_member.auth('flickr') @flickr_auth = current_member.auth('flickr')
@@ -28,7 +30,7 @@ class RegistrationsController < Devise::RegistrationsController
if successfully_updated if successfully_updated
set_flash_message :notice, :updated set_flash_message :notice, :updated
# Sign in the member bypassing validation in case their password changed # Sign in the member bypassing validation in case their password changed
sign_in @member, bypass: true sign_in @member, :bypass => true
redirect_to edit_member_registration_path redirect_to edit_member_registration_path
else else
render "edit" render "edit"

View File

@@ -1,20 +0,0 @@
class RobotsController < ApplicationController
DEFAULT_FILENAME = 'config/robots.txt'.freeze
def robots
filename = if subdomain && subdomain != 'www'
"config/robots.#{ subdomain }.txt"
end
file_to_render = File.exists?(filename.to_s) ? filename : DEFAULT_FILENAME
render file: file_to_render, layout: false, content_type: 'text/plain'
end
private
def subdomain
request.subdomain.present? ? request.subdomain : nil
end
end

View File

@@ -36,7 +36,7 @@ class RolesController < ApplicationController
# POST /roles # POST /roles
def create def create
@role = Role.new(role_params) @role = Role.new(params[:role])
respond_to do |format| respond_to do |format|
if @role.save if @role.save
@@ -52,7 +52,7 @@ class RolesController < ApplicationController
@role = Role.find(params[:id]) @role = Role.find(params[:id])
respond_to do |format| respond_to do |format|
if @role.update(role_params) if @role.update_attributes(params[:role])
format.html { redirect_to @role, notice: 'Role was successfully updated.' } format.html { redirect_to @role, notice: 'Role was successfully updated.' }
else else
format.html { render action: "edit" } format.html { render action: "edit" }
@@ -69,10 +69,4 @@ class RolesController < ApplicationController
format.html { redirect_to roles_url } format.html { redirect_to roles_url }
end end
end end
private
def role_params
params.require(:role).permit(:description, :name, :members, :slug)
end
end end

View File

@@ -1,7 +1,9 @@
class ScientificNamesController < ApplicationController class ScientificNamesController < ApplicationController
before_filter :authenticate_member!, except: [:index, :show] before_filter :authenticate_member!, :except => [:index, :show]
load_and_authorize_resource load_and_authorize_resource
cache_sweeper :scientific_name_sweeper
# GET /scientific_names # GET /scientific_names
# GET /scientific_names.json # GET /scientific_names.json
def index def index
@@ -45,7 +47,7 @@ class ScientificNamesController < ApplicationController
# POST /scientific_names.json # POST /scientific_names.json
def create def create
params[:scientific_name][:creator_id] = current_member.id params[:scientific_name][:creator_id] = current_member.id
@scientific_name = ScientificName.new(scientific_name_params) @scientific_name = ScientificName.new(params[:scientific_name])
respond_to do |format| respond_to do |format|
if @scientific_name.save if @scientific_name.save
@@ -64,7 +66,7 @@ class ScientificNamesController < ApplicationController
@scientific_name = ScientificName.find(params[:id]) @scientific_name = ScientificName.find(params[:id])
respond_to do |format| respond_to do |format|
if @scientific_name.update(scientific_name_params) if @scientific_name.update_attributes(params[:scientific_name])
format.html { redirect_to @scientific_name.crop, notice: 'Scientific name was successfully updated.' } format.html { redirect_to @scientific_name.crop, notice: 'Scientific name was successfully updated.' }
format.json { head :no_content } format.json { head :no_content }
else else
@@ -88,10 +90,4 @@ class ScientificNamesController < ApplicationController
format.json { head :no_content } format.json { head :no_content }
end end
end end
private
def scientific_name_params
params.require(:scientific_name).permit(:crop_id, :scientific_name, :creator_id)
end
end end

View File

@@ -1,24 +1,26 @@
class SeedsController < ApplicationController class SeedsController < ApplicationController
before_filter :authenticate_member!, except: [:index, :show] before_filter :authenticate_member!, :except => [:index, :show]
load_and_authorize_resource load_and_authorize_resource
cache_sweeper :seed_sweeper
# GET /seeds # GET /seeds
# GET /seeds.json # GET /seeds.json
def index def index
@owner = Member.find_by_slug(params[:owner]) @owner = Member.find_by_slug(params[:owner])
@crop = Crop.find_by_slug(params[:crop]) @crop = Crop.find_by_slug(params[:crop])
if @owner if @owner
@seeds = @owner.seeds.includes(:owner, :crop).paginate(page: params[:page]) @seeds = @owner.seeds.includes(:owner, :crop).paginate(:page => params[:page])
elsif @crop elsif @crop
@seeds = @crop.seeds.includes(:owner, :crop).paginate(page: params[:page]) @seeds = @crop.seeds.includes(:owner, :crop).paginate(:page => params[:page])
else else
@seeds = Seed.includes(:owner, :crop).paginate(page: params[:page]) @seeds = Seed.includes(:owner, :crop).paginate(:page => params[:page])
end end
respond_to do |format| respond_to do |format|
format.html # index.html.erb format.html # index.html.erb
format.json { render json: @seeds } format.json { render json: @seeds }
format.rss { render layout: false } #index.rss.builder format.rss { render :layout => false } #index.rss.builder
format.csv do format.csv do
if @owner if @owner
@filename = "Growstuff-#{@owner}-Seeds-#{Time.zone.now.to_s(:number)}.csv" @filename = "Growstuff-#{@owner}-Seeds-#{Time.zone.now.to_s(:number)}.csv"
@@ -27,7 +29,7 @@ class SeedsController < ApplicationController
@filename = "Growstuff-Seeds-#{Time.zone.now.to_s(:number)}.csv" @filename = "Growstuff-Seeds-#{Time.zone.now.to_s(:number)}.csv"
@seeds = Seed.includes(:owner, :crop) @seeds = Seed.includes(:owner, :crop)
end end
render csv: @seeds render :csv => @seeds
end end
end end
end end
@@ -66,7 +68,7 @@ class SeedsController < ApplicationController
# POST /seeds.json # POST /seeds.json
def create def create
params[:seed][:owner_id] = current_member.id params[:seed][:owner_id] = current_member.id
@seed = Seed.new(seed_params) @seed = Seed.new(params[:seed])
respond_to do |format| respond_to do |format|
if @seed.save if @seed.save
@@ -85,7 +87,7 @@ class SeedsController < ApplicationController
@seed = Seed.find(params[:id]) @seed = Seed.find(params[:id])
respond_to do |format| respond_to do |format|
if @seed.update(seed_params) if @seed.update_attributes(params[:seed])
format.html { redirect_to @seed, notice: 'Seed was successfully updated.' } format.html { redirect_to @seed, notice: 'Seed was successfully updated.' }
format.json { head :no_content } format.json { head :no_content }
else else
@@ -106,13 +108,4 @@ class SeedsController < ApplicationController
format.json { head :no_content } format.json { head :no_content }
end end
end end
private
def seed_params
params.require(:seed).permit(
:owner_id, :crop_id, :description, :quantity, :plant_before,
:days_until_maturity_min, :days_until_maturity_max, :organic, :gmo,
:heirloom, :tradable_to, :slug)
end
end end

View File

@@ -21,34 +21,8 @@ module ApplicationHelper
link = "http://www.wolframalpha.com/input/?i=#{pid}+#{currency}" link = "http://www.wolframalpha.com/input/?i=#{pid}+#{currency}"
return link_to "(convert)", return link_to "(convert)",
link, link,
target: "_blank" :target => "_blank"
end end
# Produces a cache key for uniquely identifying cached fragments.
def cache_key_for(klass, identifier="all")
count = klass.count
max_updated_at = klass.maximum(:updated_at).try(:utc).try(:to_s, :number)
"#{klass.name.downcase.pluralize}/#{identifier}-#{count}-#{max_updated_at}"
end
def required_field_help_text
asterisk = content_tag :span, '*', class: ['red']
text = content_tag :em, 'denotes a required field'
content_tag :div, asterisk + ' '.html_safe + text, class: ['margin-bottom']
end
#
# Returns an image uri for a given member.
#
# Falls back to Gravatar
#
def avatar_uri(member, size = 150)
return member.preferred_avatar_uri if member.preferred_avatar_uri.present?
Gravatar.new(member.email).image_url({
size: size,
default: :identicon
})
end
end end

View File

@@ -19,4 +19,4 @@ module AutoSuggestHelper
}.html_safe }.html_safe
end end
end end

View File

@@ -1,17 +0,0 @@
module CropsHelper
def display_seed_availability(member, crop)
total_quantity = 0
member.seeds.each do |seed|
if seed.crop.name == crop.name
total_quantity = total_quantity + seed.quantity
end
end
if (total_quantity != 0)
"You have #{pluralize(total_quantity, "seed")} of this crop."
else
"You don't have any seeds of this crop."
end
end
end

View File

@@ -1,25 +0,0 @@
module GardensHelper
def display_garden_description(garden)
if garden.description.nil?
"no description provided."
else
truncate(garden.description, length: 130, separator: ' ', omission: '... ') { link_to "Read more", garden_path(garden) }
end
end
def display_garden_plantings(plantings)
if plantings.blank?
"None"
else
output = ""
plantings.first(2).each do |planting|
output += "<li>"
output += planting.quantity.nil? ? "0 " : "#{planting.quantity} "
output += link_to planting.crop.name, planting.crop
output += ", planted on #{planting.planted_at}</li>"
end
output.html_safe
end
end
end

View File

@@ -18,11 +18,11 @@ module HarvestsHelper
def display_human_quantity(harvest) def display_human_quantity(harvest)
if ! harvest.quantity.blank? && harvest.quantity > 0 if ! harvest.quantity.blank? && harvest.quantity > 0
if harvest.unit == 'individual' # just the number if harvest.unit == 'individual' # just the number
number_to_human(harvest.quantity, strip_insignificant_zeros: true) number_to_human(harvest.quantity, :strip_insignificant_zeros => true)
elsif ! harvest.unit.blank? # pluralize anything else elsif ! harvest.unit.blank? # pluralize anything else
return pluralize(number_to_human(harvest.quantity, strip_insignificant_zeros: true), harvest.unit) return pluralize(number_to_human(harvest.quantity, :strip_insignificant_zeros => true), harvest.unit)
else else
return "#{number_to_human(harvest.quantity, strip_insignificant_zeros: true)} #{harvest.unit}" return "#{number_to_human(harvest.quantity, :strip_insignificant_zeros => true)} #{harvest.unit}"
end end
else else
return nil return nil
@@ -31,18 +31,10 @@ module HarvestsHelper
def display_weight(harvest) def display_weight(harvest)
if ! harvest.weight_quantity.blank? && harvest.weight_quantity > 0 if ! harvest.weight_quantity.blank? && harvest.weight_quantity > 0
return "#{number_to_human(harvest.weight_quantity, strip_insignificant_zeros: true)} #{harvest.weight_unit}" return "#{number_to_human(harvest.weight_quantity, :strip_insignificant_zeros => true)} #{harvest.weight_unit}"
else else
return nil return nil
end end
end end
def display_harvest_description(harvest)
if harvest.description.empty?
"No description provided."
else
truncate(harvest.description, length: 130, separator: ' ', omission: '... ') { link_to "Read more", harvest_path(harvest) }
end
end
end end

View File

@@ -2,10 +2,15 @@ module NotificationsHelper
def reply_link(notification) def reply_link(notification)
if notification.post if notification.post
# comment on the post in question # comment on the post in question
new_comment_url(post_id: notification.post.id) new_comment_url(:post_id => notification.post.id)
else else
# by default, reply link sends a PM in return # by default, reply link sends a PM in return
reply_notification_url(notification) new_notification_url(
:recipient_id => notification.sender.id,
:subject => notification.subject =~ /^Re: / ?
notification.subject :
"Re: " + notification.subject
)
end end
end end
end end

View File

@@ -1,45 +0,0 @@
module PlantingsHelper
def display_days_before_maturity(planting)
if planting.finished?
0
elsif !planting.finished_at.nil?
((p = planting.finished_at - DateTime.now).to_i) <= 0 ? 0 : p.to_i
elsif planting.days_before_maturity.nil?
"unknown"
else
((p = (planting.planted_at + planting.days_before_maturity) - DateTime.now).to_i <= 0) ? 0 : p.to_i
end
end
def display_finished(planting)
if !planting.finished_at.nil?
planting.finished_at
elsif planting.finished
"Yes (no date specified)"
else
"(no date specified)"
end
end
def display_planted_from(planting)
!planting.planted_from.blank? ? planting.planted_from : "not specified"
end
def display_planting_quantity(planting)
!planting.quantity.blank? ? planting.quantity : "not specified"
end
def display_planting(planting)
if planting.quantity.to_i > 0 && planting.planted_from.present?
return "#{planting.owner} planted #{pluralize(planting.quantity, planting.planted_from)}."
elsif planting.quantity.to_i > 0
return "#{planting.owner} planted #{pluralize(planting.quantity, 'unit')}."
elsif planting.planted_from.present?
return "#{planting.owner} planted #{planting.planted_from.pluralize}."
else
return "#{planting.owner}."
end
end
end

View File

@@ -1,11 +0,0 @@
module SeedsHelper
def display_seed_description(seed)
if seed.description.nil?
"no description provided."
else
truncate(seed.description, length: 130, separator: ' ', omission: '... ') { link_to "Read more", seed_path(seed) }
end
end
end

View File

@@ -2,53 +2,24 @@ class Notifier < ActionMailer::Base
include NotificationsHelper include NotificationsHelper
default from: "Growstuff <noreply@growstuff.org>" default from: "Growstuff <noreply@growstuff.org>"
def verifier()
if ENV['RAILS_SECRET_TOKEN']
return ActiveSupport::MessageVerifier.new(ENV['RAILS_SECRET_TOKEN'])
else
raise "RAILS_SECRET_TOKEN environment variable not set - have you created config/application.yml?"
end
end
def notify(notification) def notify(notification)
@notification = notification @notification = notification
@reply_link = reply_link(@notification) @reply_link = reply_link(@notification)
# Encrypting mail(:to => @notification.recipient.email,
@signed_message = verifier.generate ({ member_id: @notification.recipient.id, type: :send_notification_email }) :subject => @notification.subject)
mail(to: @notification.recipient.email,
subject: @notification.subject)
end end
def planting_reminder(member) def planting_reminder(member)
@member = member @member = member
@plantings = @member.plantings.first(5) @plantings = @member.plantings.reorder.last(5)
@harvests = @member.harvests.first(5) @harvests = @member.harvests.reorder.last(5)
# Encrypting
@signed_message = verifier.generate ({ member_id: @member.id, type: :send_planting_reminder })
if @member.send_planting_reminder if @member.send_planting_reminder
mail(to: @member.email, mail(:to => @member.email,
subject: "What have you planted lately?") :subject => "What have you planted lately?")
end end
end end
def new_crop_request(member, request)
@member, @request = member, request
mail(to: @member.email, subject: "#{@request.requester.login_name} has requested #{@request.name} as a new crop")
end
def crop_request_approved(member, crop)
@member, @crop = member, crop
mail(to: @member.email, subject: "#{crop.name.capitalize} has been approved")
end
def crop_request_rejected(member, crop)
@member, @crop = member, crop
mail(to: @member.email, subject: "#{crop.name.capitalize} has been rejected")
end
end end

View File

@@ -21,32 +21,14 @@ class Ability
cannot :read, Account cannot :read, Account
cannot :read, AccountType cannot :read, AccountType
# nobody should be able to view unapproved crops unless they
# are wranglers or admins
cannot :read, Crop
can :read, Crop, approval_status: "approved"
# scientific names should only be viewable if associated crop is approved
cannot :read, ScientificName
can :read, ScientificName do |sn|
sn.crop.approved?
end
# ... same for alternate names
cannot :read, AlternateName
can :read, AlternateName do |an|
an.crop.approved?
end
if member if member
# members can see even rejected or pending crops if they requested it
can :read, Crop, requester_id: member.id
# managing your own user settings # managing your own user settings
can :update, Member, id: member.id can :update, Member, :id => member.id
# can read/delete notifications that were sent to them # can read/delete notifications that were sent to them
can :read, Notification, recipient_id: member.id can :read, Notification, :recipient_id => member.id
can :destroy, Notification, recipient_id: member.id can :destroy, Notification, :recipient_id => member.id
can :reply, Notification, recipient_id: member.id
# can send a private message to anyone but themselves # can send a private message to anyone but themselves
# note: sadly, we can't test for this from the view, but it works # note: sadly, we can't test for this from the view, but it works
# for the model/controller # for the model/controller
@@ -63,63 +45,58 @@ class Ability
can :manage, AlternateName can :manage, AlternateName
end end
# any member can create a crop provisionally
can :create, Crop
# can create & destroy their own authentications against other sites. # can create & destroy their own authentications against other sites.
can :create, Authentication can :create, Authentication
can :destroy, Authentication, member_id: member.id can :destroy, Authentication, :member_id => member.id
# anyone can create a post, or comment on a post, # anyone can create a post, or comment on a post,
# but only the author can edit/destroy it. # but only the author can edit/destroy it.
can :create, Post can :create, Post
can :update, Post, author_id: member.id can :update, Post, :author_id => member.id
can :destroy, Post, author_id: member.id can :destroy, Post, :author_id => member.id
can :create, Comment can :create, Comment
can :update, Comment, author_id: member.id can :update, Comment, :author_id => member.id
can :destroy, Comment, author_id: member.id can :destroy, Comment, :author_id => member.id
# same deal for gardens and plantings # same deal for gardens and plantings
can :create, Garden can :create, Garden
can :update, Garden, owner_id: member.id can :update, Garden, :owner_id => member.id
can :destroy, Garden, owner_id: member.id can :destroy, Garden, :owner_id => member.id
can :create, Planting can :create, Planting
can :update, Planting, garden: { owner_id: member.id } can :update, Planting, :garden => { :owner_id => member.id }
can :destroy, Planting, garden: { owner_id: member.id } can :destroy, Planting, :garden => { :owner_id => member.id }
can :create, Harvest can :create, Harvest
can :update, Harvest, owner_id: member.id can :update, Harvest, :owner_id => member.id
can :destroy, Harvest, owner_id: member.id can :destroy, Harvest, :owner_id => member.id
can :create, Photo can :create, Photo
can :update, Photo, owner_id: member.id can :update, Photo, :owner_id => member.id
can :destroy, Photo, owner_id: member.id can :destroy, Photo, :owner_id => member.id
can :create, Seed can :create, Seed
can :update, Seed, owner_id: member.id can :update, Seed, :owner_id => member.id
can :destroy, Seed, owner_id: member.id can :destroy, Seed, :owner_id => member.id
# orders/shop/etc # orders/shop/etc
can :create, Order can :create, Order
can :read, Order, member_id: member.id can :read, Order, :member_id => member.id
can :complete, Order, member_id: member.id, completed_at: nil can :complete, Order, :member_id => member.id, :completed_at => nil
can :checkout, Order, member_id: member.id, completed_at: nil can :checkout, Order, :member_id => member.id, :completed_at => nil
can :cancel, Order, member_id: member.id, completed_at: nil can :cancel, Order, :member_id => member.id, :completed_at => nil
can :destroy, Order, member_id: member.id, completed_at: nil can :destroy, Order, :member_id => member.id, :completed_at => nil
can :create, OrderItem can :create, OrderItem
# for now, let's not let people mess with individual order items # for now, let's not let people mess with individual order items
cannot :read, OrderItem, order: { member_id: member.id } cannot :read, OrderItem, :order => { :member_id => member.id }
cannot :update, OrderItem, order: { member_id: member.id, completed_at: nil } cannot :update, OrderItem, :order => { :member_id => member.id, :completed_at => nil }
cannot :destroy, OrderItem, order: { member_id: member.id, completed_at: nil } cannot :destroy, OrderItem, :order => { :member_id => member.id, :completed_at => nil }
# following/unfollowing permissions # following/unfollowing permissions
can :create, Follow can :create, Follow do |f|
cannot :create, Follow, followed_id: member.id # can't follow yourself !member.already_following?(f.followed) && f.followed_id != member.id
end
can :destroy, Follow
cannot :destroy, Follow, followed_id: member.id # can't unfollow yourself
if member.has_role? :admin if member.has_role? :admin

View File

@@ -1,14 +1,15 @@
class Account < ActiveRecord::Base class Account < ActiveRecord::Base
attr_accessible :account_type_id, :member_id, :paid_until
belongs_to :member belongs_to :member
belongs_to :account_type belongs_to :account_type
validates :member_id, uniqueness: { validates :member_id, :uniqueness => {
message: 'already has account details associated with it' :message => 'already has account details associated with it'
} }
before_create do |account| before_create do |account|
unless account.account_type unless account.account_type
account.account_type = AccountType.find_or_create_by(name: account.account_type = AccountType.find_or_create_by_name(
Growstuff::Application.config.default_account_type Growstuff::Application.config.default_account_type
) )
end end

View File

@@ -1,4 +1,5 @@
class AccountType < ActiveRecord::Base class AccountType < ActiveRecord::Base
attr_accessible :is_paid, :is_permanent_paid, :name
has_many :products has_many :products
def to_s def to_s

View File

@@ -1,5 +1,5 @@
class AlternateName < ActiveRecord::Base class AlternateName < ActiveRecord::Base
after_commit { |an| an.crop.__elasticsearch__.index_document if an.crop && ENV['GROWSTUFF_ELASTICSEARCH'] == "true" } attr_accessible :crop_id, :name, :creator_id
belongs_to :crop belongs_to :crop
belongs_to :creator, class_name: 'Member' belongs_to :creator, :class_name => 'Member'
end end

View File

@@ -1,3 +1,4 @@
class Authentication < ActiveRecord::Base class Authentication < ActiveRecord::Base
belongs_to :member belongs_to :member
attr_accessible :provider, :uid, :token, :secret, :name
end end

View File

@@ -1,9 +1,10 @@
class Comment < ActiveRecord::Base class Comment < ActiveRecord::Base
belongs_to :author, class_name: 'Member' attr_accessible :author_id, :body, :post_id
belongs_to :author, :class_name => 'Member'
belongs_to :post belongs_to :post
default_scope { order("created_at DESC") } default_scope order("created_at DESC")
scope :post_order, -> { reorder("created_at ASC") } # for display on post page scope :post_order, reorder("created_at ASC") # for display on post page
after_create do after_create do
recipient = self.post.author.id recipient = self.post.author.id
@@ -11,11 +12,11 @@ class Comment < ActiveRecord::Base
# don't send notifications to yourself # don't send notifications to yourself
if recipient != sender if recipient != sender
Notification.create( Notification.create(
recipient_id: recipient, :recipient_id => recipient,
sender_id: sender, :sender_id => sender,
subject: "#{self.author} commented on #{self.post.subject}", :subject => "#{self.author} commented on #{self.post.subject}",
body: self.body, :body => self.body,
post_id: self.post.id :post_id => self.post.id
) )
end end
end end

View File

@@ -0,0 +1,15 @@
class CommentSweeper < ActionController::Caching::Sweeper
observe Comment
def after_create(comment)
expire_fragment('recent_posts')
end
def after_update(comment)
end
def after_destroy(comment)
expire_fragment('recent_posts')
end
end

View File

@@ -1,122 +1,45 @@
class Crop < ActiveRecord::Base class Crop < ActiveRecord::Base
extend FriendlyId extend FriendlyId
friendly_id :name, use: [:slugged, :finders] friendly_id :name, use: :slugged
attr_accessible :en_wikipedia_url, :name, :parent_id, :creator_id, :scientific_names_attributes
has_many :scientific_names, after_add: :update_index, after_remove: :update_index has_many :scientific_names
accepts_nested_attributes_for :scientific_names, accepts_nested_attributes_for :scientific_names,
allow_destroy: true, :allow_destroy => true,
reject_if: :all_blank :reject_if => :all_blank
has_many :alternate_names, after_add: :update_index, after_remove: :update_index, dependent: :destroy has_many :alternate_names
has_many :plantings has_many :plantings
has_many :photos, through: :plantings has_many :photos, :through => :plantings
has_many :seeds has_many :seeds
has_many :harvests has_many :harvests
has_many :plant_parts, -> { uniq }, through: :harvests has_many :plant_parts, :through => :harvests, :uniq => :true
belongs_to :creator, class_name: 'Member' belongs_to :creator, :class_name => 'Member'
belongs_to :requester, class_name: 'Member'
belongs_to :parent, class_name: 'Crop' belongs_to :parent, :class_name => 'Crop'
has_many :varieties, class_name: 'Crop', foreign_key: 'parent_id' has_many :varieties, :class_name => 'Crop', :foreign_key => 'parent_id'
has_and_belongs_to_many :posts has_and_belongs_to_many :posts
before_destroy {|crop| crop.posts.clear} before_destroy {|crop| crop.posts.clear}
default_scope { order("lower(name) asc") }
scope :recent, -> { where(approval_status: "approved").reorder("created_at desc") }
scope :toplevel, -> { where(approval_status: "approved", parent_id: nil) }
scope :popular, -> { where(approval_status: "approved").reorder("plantings_count desc, lower(name) asc") }
scope :randomized, -> { where(approval_status: "approved").reorder('random()') } # ok on sqlite and psql, but not on mysql
scope :pending_approval, -> { where(approval_status: "pending") }
scope :approved, -> { where(approval_status: "approved") }
scope :rejected, -> { where(approval_status: "rejected") }
## Wikipedia urls are only necessary when approving a crop default_scope order("lower(name) asc")
scope :recent, reorder("created_at desc")
scope :toplevel, where(:parent_id => nil)
scope :popular, reorder("plantings_count desc, lower(name) asc")
scope :randomized, reorder('random()') # ok on sqlite and psql, but not on mysql
validates :en_wikipedia_url, validates :en_wikipedia_url,
format: { :format => {
with: /\Ahttps?:\/\/en\.wikipedia\.org\/wiki/, :with => /^https?:\/\/en\.wikipedia\.org\/wiki/,
message: 'is not a valid English Wikipedia URL' :message => 'is not a valid English Wikipedia URL'
}, }
if: :approved?
## Reasons are only necessary when rejecting
validates :reason_for_rejection, presence: true, if: :rejected?
## This validation addresses a race condition
validate :approval_status_cannot_be_changed_again
validate :must_be_rejected_if_rejected_reasons_present
validate :must_have_meaningful_reason_for_rejection
####################################
# Elastic search configuration
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
# In order to avoid clashing between different environments,
# use Rails.env as a part of index name (eg. development_growstuff)
index_name [Rails.env, "growstuff"].join('_')
settings index: { number_of_shards: 1 },
analysis: {
tokenizer: {
gs_edgeNGram_tokenizer: {
type: "edgeNGram", # edgeNGram: NGram match from the start of a token
min_gram: 3,
max_gram: 10,
# token_chars: Elasticsearch will split on characters
# that dont belong to any of these classes
token_chars: [ "letter", "digit" ]
}
},
analyzer: {
gs_edgeNGram_analyzer: {
tokenizer: "gs_edgeNGram_tokenizer",
filter: ["lowercase"]
}
},
} do
mappings dynamic: 'false' do
indexes :id, type: 'long'
indexes :name, type: 'string', analyzer: 'gs_edgeNGram_analyzer'
indexes :approval_status, type: 'string'
indexes :scientific_names do
indexes :scientific_name,
type: 'string',
analyzer: 'gs_edgeNGram_analyzer',
# Disabling field-length norm (norm). If the norm option is turned on(by default),
# higher weigh would be given for shorter fields, which in our case is irrelevant.
norms: { enabled: false }
end
indexes :alternate_names do
indexes :name, type: 'string', analyzer: 'gs_edgeNGram_analyzer'
end
end
end
def as_indexed_json(options={})
self.as_json(
only: [:id, :name, :approval_status],
include: {
scientific_names: { only: :scientific_name },
alternate_names: { only: :name }
})
end
# update the Elasticsearch index (only if we're using it in this
# environment)
def update_index(name_obj)
if ENV["GROWSTUFF_ELASTICSEARCH"] == "true"
__elasticsearch__.index_document
end
end
# End Elasticsearch section
def to_s def to_s
return name return name
end end
def default_scientific_name def default_scientific_name
if scientific_names.size > 0 if scientific_names.count > 0
return scientific_names.first.scientific_name return scientific_names.first.scientific_name
else else
return nil return nil
@@ -177,38 +100,18 @@ class Crop < ActiveRecord::Base
def interesting? def interesting?
min_plantings = 3 # needs this many plantings to be interesting min_plantings = 3 # needs this many plantings to be interesting
min_photos = 3 # needs this many photos to be interesting min_photos = 3 # needs this many photos to be interesting
return false unless photos.size >= min_photos return false unless photos.count >= min_photos
return false unless plantings_count >= min_plantings return false unless plantings_count >= min_plantings
return true return true
end end
def pending?
approval_status == "pending"
end
def approved?
approval_status == "approved"
end
def rejected?
approval_status == "rejected"
end
def approval_statuses
[ 'rejected', 'pending', 'approved' ]
end
def reasons_for_rejection
[ "already in database", "not edible", "not enough information", "other" ]
end
# Crop.interesting # Crop.interesting
# returns a list of interesting crops, for use on the homepage etc # returns a list of interesting crops, for use on the homepage etc
def Crop.interesting def Crop.interesting
howmany = 12 # max number to find howmany = 12 # max number to find
interesting_crops = Array.new interesting_crops = Array.new
Crop.includes(:photos).randomized.each do |c| Crop.randomized.each do |c|
break if interesting_crops.size == howmany break if interesting_crops.length == howmany
next unless c.interesting? next unless c.interesting?
interesting_crops.push(c) interesting_crops.push(c)
end end
@@ -229,16 +132,16 @@ class Crop < ActiveRecord::Base
cropbot = Member.find_by_login_name('cropbot') cropbot = Member.find_by_login_name('cropbot')
raise "cropbot account not found: run rake db:seed" unless cropbot raise "cropbot account not found: run rake db:seed" unless cropbot
crop = Crop.find_or_create_by(name: name) crop = Crop.find_or_create_by_name(name)
crop.update_attributes( crop.update_attributes(
en_wikipedia_url: en_wikipedia_url, :en_wikipedia_url => en_wikipedia_url,
creator_id: cropbot.id :creator_id => cropbot.id
) )
if parent if parent
parent = Crop.find_by_name(parent) parent = Crop.find_by_name(parent)
if parent if parent
crop.update_attributes(parent_id: parent.id) crop.update_attributes(:parent_id => parent.id)
else else
logger.warn("Warning: parent crop #{parent} not found") logger.warn("Warning: parent crop #{parent} not found")
end end
@@ -264,13 +167,13 @@ class Crop < ActiveRecord::Base
raise "cropbot account not found: run rake db:seed" unless cropbot raise "cropbot account not found: run rake db:seed" unless cropbot
names_to_add.each do |n| names_to_add.each do |n|
if self.scientific_names.exists?(scientific_name: n) if self.scientific_names.exists?(:scientific_name => n)
logger.warn("Warning: skipping duplicate scientific name #{n} for #{self}") logger.warn("Warning: skipping duplicate scientific name #{n} for #{self}")
else else
self.scientific_names.create( self.scientific_names.create(
scientific_name: n, :scientific_name => n,
creator_id: cropbot.id :creator_id => cropbot.id
) )
end end
end end
@@ -286,12 +189,12 @@ class Crop < ActiveRecord::Base
names_to_add = alternate_names.split(%r{,\s*}) names_to_add = alternate_names.split(%r{,\s*})
names_to_add.each do |n| names_to_add.each do |n|
if self.alternate_names.exists?(name: n) if self.alternate_names.exists?(:name => n)
logger.warn("Warning: skipping duplicate alternate name #{n} for #{self}") logger.warn("Warning: skipping duplicate alternate name #{n} for #{self}")
else else
self.alternate_names.create( self.alternate_names.create(
name: n, :name => n,
creator_id: cropbot.id :creator_id => cropbot.id
) )
end end
end end
@@ -299,76 +202,11 @@ class Crop < ActiveRecord::Base
end end
end end
def rejection_explanation
if reason_for_rejection == "other"
return rejection_notes
else
return reason_for_rejection
end
end
# Crop.search(string) # Crop.search(string)
# searches for crops whose names match the string given
# just uses SQL LIKE for now, but can be made fancier later
def self.search(query) def self.search(query)
if ENV['GROWSTUFF_ELASTICSEARCH'] == "true" where("name ILIKE ?", "%#{query}%")
search_str = query.nil? ? "" : query.downcase
response = __elasticsearch__.search( {
# Finds documents which match any field, but uses the _score from
# the best field insead of adding up _score from each field.
query: {
multi_match: {
query: "#{search_str}",
analyzer: "standard",
fields: ["name", "scientific_names.scientific_name", "alternate_names.name"]
}
},
filter: {
term: {approval_status: "approved"}
},
size: 50
}
)
return response.records.to_a
else
# if we don't have elasticsearch, just do a basic SQL query.
# also, make sure it's an actual array not an activerecord
# collection, so it matches what we get from elasticsearch and we can
# manipulate it in the same ways (eg. deleting elements without deleting
# the whole record from the db)
matches = Crop.approved.where("name ILIKE ?", "%#{query}%").to_a
# we want to make sure that exact matches come first, even if not
# using elasticsearch (eg. in development)
exact_match = Crop.approved.find_by_name(query)
if exact_match
matches.delete(exact_match)
matches.unshift(exact_match)
end
return matches
end
end
# Custom validations
def approval_status_cannot_be_changed_again
previous = previous_changes.include?(:approval_status) ? previous_changes.approval_status : {}
if previous.include?(:rejected) || previous.include?(:approved)
errors.add(:approval_status, "has already been set to #{approval_status}")
end
end
def must_be_rejected_if_rejected_reasons_present
unless rejected?
if reason_for_rejection.present? || rejection_notes.present?
errors.add(:approval_status, "must be rejected if a reason for rejection is present")
end
end
end
def must_have_meaningful_reason_for_rejection
if reason_for_rejection == "other" && rejection_notes.blank?
errors.add(:rejection_notes, "must be added if the reason for rejection is \"other\"")
end
end end
end end

View File

@@ -0,0 +1,21 @@
class CropSweeper < ActionController::Caching::Sweeper
observe Crop
def after_create(crop)
expire_fragment('homepage_stats')
expire_fragment('recent_crops')
expire_fragment('full_crop_hierarchy')
end
def after_update(crop)
expire_fragment("crop_image_#{crop.id}")
end
def after_destroy(crop)
expire_fragment('homepage_stats')
expire_fragment('recent_crops')
expire_fragment('full_crop_hierarchy')
end
end

View File

@@ -1,14 +1,15 @@
class Follow < ActiveRecord::Base class Follow < ActiveRecord::Base
attr_accessible :followed_id, :follower_id
belongs_to :follower, class_name: "Member" belongs_to :follower, class_name: "Member"
belongs_to :followed, class_name: "Member" belongs_to :followed, class_name: "Member"
validates :follower_id, uniqueness: { scope: :followed_id } validates :follower_id, uniqueness: { :scope => :followed_id }
after_create do after_create do
Notification.create( Notification.create(
recipient_id: self.followed_id, :recipient_id => self.followed_id,
sender_id: self.follower_id, :sender_id => self.follower_id,
subject: "#{self.follower.login_name} is now following you", :subject => "#{self.follower.login_name} is now following you",
body: "#{self.follower.login_name} just followed you on #{ENV["GROWSTUFF_SITE_NAME"]}. " :body => "#{self.follower.login_name} just followed you on #{ENV["GROWSTUFF_SITE_NAME"]}. "
) )
end end

View File

@@ -1,9 +1,9 @@
class Forum < ActiveRecord::Base class Forum < ActiveRecord::Base
extend FriendlyId extend FriendlyId
friendly_id :name, use: [:slugged, :finders] friendly_id :name, use: :slugged
attr_accessible :description, :name, :owner_id, :slug
has_many :posts has_many :posts
belongs_to :owner, class_name: "Member" belongs_to :owner, :class_name => "Member"
def to_s def to_s
return name return name

View File

@@ -0,0 +1,16 @@
class ForumSweeper < ActionController::Caching::Sweeper
observe Forum
def after_create(forum)
expire_fragment('homepage_forums')
end
def after_update(forum)
expire_fragment('homepage_forums')
end
def after_destroy(forum)
expire_fragment('homepage_forums')
end
end

View File

@@ -1,22 +1,14 @@
class Garden < ActiveRecord::Base class Garden < ActiveRecord::Base
include Geocodable include Geocodable
extend FriendlyId extend FriendlyId
friendly_id :garden_slug, use: [:slugged, :finders] friendly_id :garden_slug, use: :slugged
belongs_to :owner, class_name: 'Member', foreign_key: 'owner_id' attr_accessible :name, :slug, :owner_id, :description, :active,
has_many :plantings, -> { order(created_at: :desc) }, dependent: :destroy :location, :latitude, :longitude, :area, :area_unit
has_many :crops, through: :plantings
has_and_belongs_to_many :photos belongs_to :owner, :class_name => 'Member', :foreign_key => 'owner_id'
has_many :plantings, :order => 'created_at DESC', :dependent => :destroy
before_destroy do |garden| has_many :crops, :through => :plantings
photolist = garden.photos.to_a # save a temp copy of the photo list
garden.photos.clear # clear relationship b/w garden and photo
photolist.each do |photo|
photo.destroy_if_unused
end
end
# set up geocoding # set up geocoding
geocoded_by :location geocoded_by :location
@@ -24,24 +16,18 @@ class Garden < ActiveRecord::Base
after_validation :empty_unwanted_geocodes after_validation :empty_unwanted_geocodes
after_save :mark_inactive_garden_plantings_as_finished after_save :mark_inactive_garden_plantings_as_finished
default_scope { order("lower(name) asc") } default_scope order("lower(name) asc")
scope :active, -> { where(active: true) } scope :active, where(:active => true)
scope :inactive, -> { where(active: false) } scope :inactive, where(:active => false)
validates :location,
length: { maximum: 255 }
validates :name, validates :name,
format: { :format => {
with: /\S/ :with => /\S/
}, }
length: { maximum: 255 }
validates :area, validates :area,
numericality: { :numericality => { :only_integer => false, :greater_than_or_equal_to => 0 },
only_integer: false, :allow_nil => true
greater_than_or_equal_to: 0 },
allow_nil: true
AREA_UNITS_VALUES = { AREA_UNITS_VALUES = {
"square metres" => "square metre", "square metres" => "square metre",
@@ -49,10 +35,10 @@ class Garden < ActiveRecord::Base
"hectares" => "hectare", "hectares" => "hectare",
"acres" => "acre" "acres" => "acre"
} }
validates :area_unit, inclusion: { in: AREA_UNITS_VALUES.values, validates :area_unit, :inclusion => { :in => AREA_UNITS_VALUES.values,
message: "%{value} is not a valid area unit" }, :message => "%{value} is not a valid area unit" },
allow_nil: true, :allow_nil => true,
allow_blank: true :allow_blank => true
after_validation :cleanup_area after_validation :cleanup_area
@@ -100,8 +86,4 @@ class Garden < ActiveRecord::Base
end end
end end
def default_photo
return photos.first
end
end end

View File

@@ -0,0 +1,16 @@
class GardenSweeper < ActionController::Caching::Sweeper
observe Garden
def after_create(garden)
expire_fragment('homepage_stats')
expire_fragment('interesting_members') if garden.owner.interesting?
expire_fragment("member_thumbnail_#{garden.owner.id}")
end
def after_destroy(garden)
expire_fragment('homepage_stats')
expire_fragment('interesting_members') if garden.owner.interesting?
expire_fragment("member_thumbnail_#{garden.owner.id}")
end
end

View File

@@ -1,10 +1,13 @@
class Harvest < ActiveRecord::Base class Harvest < ActiveRecord::Base
include ActionView::Helpers::NumberHelper include ActionView::Helpers::NumberHelper
extend FriendlyId extend FriendlyId
friendly_id :harvest_slug, use: [:slugged, :finders] friendly_id :harvest_slug, use: :slugged
attr_accessible :crop_id, :harvested_at, :description, :owner_id,
:quantity, :unit, :weight_quantity, :weight_unit, :plant_part_id, :slug
belongs_to :crop belongs_to :crop
belongs_to :owner, class_name: 'Member' belongs_to :owner, :class_name => 'Member'
belongs_to :plant_part belongs_to :plant_part
has_and_belongs_to_many :photos has_and_belongs_to_many :photos
@@ -18,19 +21,13 @@ class Harvest < ActiveRecord::Base
end end
end end
default_scope { order('created_at DESC') } default_scope order('created_at DESC')
validates :crop, approved: true validates :crop, :presence => {:message => "must be present and exist in our database"}
validates :crop, presence: {message: "must be present and exist in our database"}
validates :plant_part, presence: {message: "must be present and exist in our database"}
validates :quantity, validates :quantity,
numericality: { :numericality => { :only_integer => false },
only_integer: false, :allow_nil => true
greater_than_or_equal_to: 0 },
allow_nil: true
UNITS_VALUES = { UNITS_VALUES = {
"individual" => "individual", "individual" => "individual",
@@ -44,38 +41,27 @@ class Harvest < ActiveRecord::Base
"baskets" => "basket", "baskets" => "basket",
"bushels" => "bushel" "bushels" => "bushel"
} }
validates :unit, inclusion: { in: UNITS_VALUES.values, validates :unit, :inclusion => { :in => UNITS_VALUES.values,
message: "%{value} is not a valid unit" }, :message => "%{value} is not a valid unit" },
allow_nil: true, :allow_nil => true,
allow_blank: true :allow_blank => true
validates :weight_quantity, validates :weight_quantity,
numericality: { only_integer: false }, :numericality => { :only_integer => false },
allow_nil: true :allow_nil => true
WEIGHT_UNITS_VALUES = { WEIGHT_UNITS_VALUES = {
"kg" => "kg", "kg" => "kg",
"lb" => "lb", "lb" => "lb",
"oz" => "oz" "oz" => "oz"
} }
validates :weight_unit, inclusion: { in: WEIGHT_UNITS_VALUES.values, validates :weight_unit, :inclusion => { :in => WEIGHT_UNITS_VALUES.values,
message: "%{value} is not a valid unit" }, :message => "%{value} is not a valid unit" },
allow_nil: true, :allow_nil => true,
allow_blank: true :allow_blank => true
after_validation :cleanup_quantities after_validation :cleanup_quantities
before_save :set_si_weight
# we're storing the harvest weight in kilograms in the db too
# to make data manipulation easier
def set_si_weight
if self.weight_unit != nil
weight_string = "#{self.weight_quantity} #{self.weight_unit}"
self.si_weight = Unit.new(weight_string).convert_to("kg").to_s("%0.3f").delete(" kg").to_f
end
end
def cleanup_quantities def cleanup_quantities
if quantity == 0 if quantity == 0
self.quantity = nil self.quantity = nil
@@ -104,7 +90,7 @@ class Harvest < ActiveRecord::Base
# 2 buckets of apricots, weighing 10kg # 2 buckets of apricots, weighing 10kg
string = '' string = ''
if self.quantity if self.quantity
string += "#{number_to_human(self.quantity.to_s, strip_insignificant_zeros: true)} " string += "#{number_to_human(self.quantity.to_s, :strip_insignificant_zeros => true)} "
if self.unit == 'individual' if self.unit == 'individual'
string += 'individual ' string += 'individual '
else else
@@ -125,7 +111,7 @@ class Harvest < ActiveRecord::Base
end end
if self.weight_quantity if self.weight_quantity
string += " weighing #{number_to_human(self.weight_quantity, strip_insignificant_zeros: true)} #{self.weight_unit}" string += " weighing #{number_to_human(self.weight_quantity, :strip_insignificant_zeros => true)} #{self.weight_unit}"
end end
return string return string

View File

@@ -2,44 +2,42 @@ class Member < ActiveRecord::Base
include Geocodable include Geocodable
extend FriendlyId extend FriendlyId
friendly_id :login_name, use: [:slugged, :finders] friendly_id :login_name, use: :slugged
has_many :posts, foreign_key: 'author_id' has_many :posts, :foreign_key => 'author_id'
has_many :comments, foreign_key: 'author_id' has_many :comments, :foreign_key => 'author_id'
has_many :forums, foreign_key: 'owner_id' has_many :forums, :foreign_key => 'owner_id'
has_many :gardens, foreign_key: 'owner_id' has_many :gardens, :foreign_key => 'owner_id'
has_many :plantings, foreign_key: 'owner_id' has_many :plantings, :foreign_key => 'owner_id'
has_many :seeds, foreign_key: 'owner_id' has_many :seeds, :foreign_key => 'owner_id'
has_many :harvests, foreign_key: 'owner_id' has_many :harvests, :foreign_key => 'owner_id'
has_and_belongs_to_many :roles has_and_belongs_to_many :roles
has_many :notifications, foreign_key: 'recipient_id' has_many :notifications, :foreign_key => 'recipient_id'
has_many :sent_notifications, foreign_key: 'sender_id' has_many :sent_notifications, :foreign_key => 'sender_id'
has_many :authentications has_many :authentications
has_many :orders has_many :orders
has_one :account has_one :account
has_one :account_type, through: :account has_one :account_type, :through => :account
has_many :photos has_many :photos
has_many :follows, :class_name => "Follow", :foreign_key => "follower_id"
has_many :followed, :through => :follows
default_scope { order("lower(login_name) asc") } has_many :inverse_follows, :class_name => "Follow", :foreign_key => "followed_id"
scope :confirmed, -> { where('confirmed_at IS NOT NULL') } has_many :followers, :through => :inverse_follows, :source => :follower
scope :located, -> { where("location <> '' and latitude IS NOT NULL and longitude IS NOT NULL") }
scope :recently_signed_in, -> { reorder('updated_at DESC') }
scope :recently_joined, -> { reorder("confirmed_at desc") }
scope :wants_newsletter, -> { where(newsletter: true) }
has_many :follows, class_name: "Follow", foreign_key: "follower_id" default_scope order("lower(login_name) asc")
has_many :followed, through: :follows scope :confirmed, where('confirmed_at IS NOT NULL')
scope :located, where("location <> '' and latitude IS NOT NULL and longitude IS NOT NULL")
has_many :inverse_follows, class_name: "Follow", foreign_key: "followed_id" scope :recently_signed_in, reorder('updated_at DESC')
has_many :followers, through: :inverse_follows, source: :follower scope :wants_newsletter, where(:newsletter => true)
# Include default devise modules. Others available are: # Include default devise modules. Others available are:
# :token_authenticatable, :confirmable, # :token_authenticatable, :confirmable,
@@ -48,6 +46,16 @@ class Member < ActiveRecord::Base
:recoverable, :rememberable, :trackable, :validatable, :recoverable, :rememberable, :trackable, :validatable,
:confirmable, :lockable, :timeoutable :confirmable, :lockable, :timeoutable
# Setup accessible (or protected) attributes for your model
attr_accessible :login_name, :email, :password, :password_confirmation,
:remember_me, :login,
# terms of service
:tos_agreement,
# profile stuff
:bio, :location, :latitude, :longitude,
# email settings
:show_email, :newsletter, :send_notification_email, :send_planting_reminder
# set up geocoding # set up geocoding
geocoded_by :location geocoded_by :location
after_validation :geocode after_validation :geocode
@@ -58,34 +66,34 @@ class Member < ActiveRecord::Base
attr_accessor :login attr_accessor :login
# Requires acceptance of the Terms of Service # Requires acceptance of the Terms of Service
validates_acceptance_of :tos_agreement, allow_nil: false, validates_acceptance_of :tos_agreement, :allow_nil => false,
accept: true :accept => true
validates :login_name, validates :login_name,
length: { :length => {
minimum: 2, :minimum => 2,
maximum: 25, :maximum => 25,
message: "should be between 2 and 25 characters long" :message => "should be between 2 and 25 characters long"
}, },
exclusion: { :exclusion => {
in: %w(growstuff admin moderator staff nearby), :in => %w(growstuff admin moderator staff nearby),
message: "name is reserved" :message => "name is reserved"
}, },
format: { :format => {
with: /\A\w+\z/, :with => /^\w+$/,
message: "may only include letters, numbers, or underscores" :message => "may only include letters, numbers, or underscores"
}, },
uniqueness: { :uniqueness => {
case_sensitive: false :case_sensitive => false
} }
# Give each new member a default garden # Give each new member a default garden
after_create {|member| Garden.create(name: "Garden", owner_id: member.id) } after_create {|member| Garden.create(:name => "Garden", :owner_id => member.id) }
# and an account record (for paid accounts etc) # and an account record (for paid accounts etc)
# we use find_or_create to avoid accidentally creating a second one, # we use find_or_create to avoid accidentally creating a second one,
# which can happen sometimes especially with FactoryGirl associations # which can happen sometimes especially with FactoryGirl associations
after_create {|member| Account.find_or_create_by(member_id: member.id) } after_create {|member| Account.find_or_create_by_member_id(:member_id => member.id) }
after_save :update_newsletter_subscription after_save :update_newsletter_subscription
@@ -93,7 +101,7 @@ class Member < ActiveRecord::Base
def self.find_first_by_auth_conditions(warden_conditions) def self.find_first_by_auth_conditions(warden_conditions)
conditions = warden_conditions.dup conditions = warden_conditions.dup
if login = conditions.delete(:login) if login = conditions.delete(:login)
where(conditions).where(["lower(login_name) = :value OR lower(email) = :value", { value: login.downcase }]).first where(conditions).where(["lower(login_name) = :value OR lower(email) = :value", { :value => login.downcase }]).first
else else
where(conditions).first where(conditions).first
end end
@@ -108,7 +116,7 @@ class Member < ActiveRecord::Base
end end
def current_order def current_order
orders.where(completed_at: nil).first orders.where(:completed_at => nil).first
end end
# when purchasing a product that gives you a paid account, this method # when purchasing a product that gives you a paid account, this method
@@ -163,15 +171,15 @@ class Member < ActiveRecord::Base
result = false result = false
if set if set
result = flickr.photosets.getPhotos( result = flickr.photosets.getPhotos(
photoset_id: set, :photoset_id => set,
page: page_num, :page => page_num,
per_page: 30 :per_page => 30
) )
else else
result = flickr.people.getPhotos( result = flickr.people.getPhotos(
user_id: 'me', :user_id => 'me',
page: page_num, :page => page_num,
per_page: 30 :per_page => 30
) )
end end
if result if result
@@ -202,7 +210,7 @@ class Member < ActiveRecord::Base
howmany = 12 # max number to find howmany = 12 # max number to find
interesting_members = Array.new interesting_members = Array.new
Member.confirmed.located.recently_signed_in.each do |m| Member.confirmed.located.recently_signed_in.each do |m|
break if interesting_members.size == howmany break if interesting_members.length == howmany
if m.interesting? if m.interesting?
interesting_members.push(m) interesting_members.push(m)
end end
@@ -235,32 +243,30 @@ class Member < ActiveRecord::Base
end end
end end
def newsletter_subscribe(testing=false) def newsletter_subscribe
return true if (Rails.env.test? && !testing)
gb = Gibbon::API.new gb = Gibbon::API.new
res = gb.lists.subscribe({ res = gb.lists.subscribe({
id: Gibbon::API.api_key, :id => Gibbon::API.api_key,
email: { email: email }, :email => { :email => email },
merge_vars: { login_name: login_name }, :merge_vars => { :login_name => login_name },
double_optin: false # they already confirmed their email with us :double_optin => false # they alredy confirmed their email with us
}) })
end end
def newsletter_unsubscribe(testing=false) def newsletter_unsubscribe
return true if (Rails.env.test? && !testing)
gb = Gibbon::API.new gb = Gibbon::API.new
res = gb.lists.unsubscribe({ res = gb.lists.unsubscribe({
id: ENV['GROWSTUFF_MAILCHIMP_NEWSLETTER_ID'], :id => ENV['GROWSTUFF_MAILCHIMP_NEWSLETTER_ID'],
email: { email: email } :email => { :email => email }
}) })
end end
def already_following?(member) def already_following?(member)
self.follows.exists?(followed_id: member.id) self.follows.exists?(:followed_id => member.id)
end end
def get_follow(member) def get_follow(member)
self.follows.where(followed_id: member.id).first if already_following?(member) self.follows.where(:followed_id => member.id).first if already_following?(member)
end end
end end

View File

@@ -0,0 +1,23 @@
class MemberSweeper < ActionController::Caching::Sweeper
observe Member
def after_create(member)
expire_fragment('homepage_stats')
end
def after_update(member)
expire_fragment('interesting_members') if member.interesting?
expire_fragment("interesting_seeds") if member.seeds.tradable.present?
expire_fragment("member_thumbnail_#{member.id}")
if member.plantings.present?
member.plantings.each do |p|
expire_fragment("plantings_listitem_#{p.id}") if p.interesting?
end
expire_fragment('interesting_plantings')
end
end
end

View File

@@ -1,18 +1,19 @@
class Notification < ActiveRecord::Base class Notification < ActiveRecord::Base
belongs_to :sender, class_name: 'Member' attr_accessible :sender_id, :recipient_id,
belongs_to :recipient, class_name: 'Member' :subject, :body, :post_id, :read
belongs_to :sender, :class_name => 'Member'
belongs_to :recipient, :class_name => 'Member'
belongs_to :post belongs_to :post
validates :subject, length: { maximum: 255 } default_scope order('created_at DESC')
scope :unread, where(:read => false)
default_scope { order('created_at DESC') }
scope :unread, -> { where(read: false) }
before_create :replace_blank_subject before_create :replace_blank_subject
after_create :send_email after_create :send_email
def self.unread_count def self.unread_count
self.unread.size self.unread.count
end end
def replace_blank_subject def replace_blank_subject

View File

@@ -1,13 +1,14 @@
class Order < ActiveRecord::Base class Order < ActiveRecord::Base
attr_accessible :member_id, :completed_at, :referral_code
belongs_to :member belongs_to :member
has_many :order_items, dependent: :destroy has_many :order_items, :dependent => :destroy
default_scope { order('created_at DESC') } default_scope order('created_at DESC')
validates :referral_code, format: { validates :referral_code, :format => {
with: /\A[a-zA-Z0-9 ]*\z/, :with => /\A[a-zA-Z0-9 ]*\z/,
message: "may only include letters and numbers" :message => "may only include letters and numbers"
} }
before_save :standardize_referral_code before_save :standardize_referral_code
@@ -27,9 +28,9 @@ class Order < ActiveRecord::Base
items = [] items = []
order_items.each do |i| order_items.each do |i|
items.push({ items.push({
name: i.product.name, :name => i.product.name,
quantity: i.quantity, :quantity => i.quantity,
amount: i.price :amount => i.price
}) })
end end
return items return items
@@ -87,7 +88,7 @@ class Order < ActiveRecord::Base
end end
when "referral_code" when "referral_code"
# coerce to uppercase # coerce to uppercase
return Order.where(referral_code: args[:for].upcase) return Order.where(:referral_code => args[:for].upcase)
end end
end end
return [] return []

View File

@@ -1,10 +1,12 @@
class OrderItem < ActiveRecord::Base class OrderItem < ActiveRecord::Base
attr_accessible :order_id, :price, :product_id, :quantity
belongs_to :order belongs_to :order
belongs_to :product belongs_to :product
validate :price_must_be_greater_than_minimum validate :price_must_be_greater_than_minimum
validates_uniqueness_of :order_id, message: "may only have one item." validates_uniqueness_of :order_id, :message => "may only have one item."
def price_must_be_greater_than_minimum def price_must_be_greater_than_minimum
@product = Product.find(product_id) @product = Product.find(product_id)

View File

@@ -1,20 +1,20 @@
class Photo < ActiveRecord::Base class Photo < ActiveRecord::Base
belongs_to :owner, class_name: 'Member' attr_accessible :flickr_photo_id, :owner_id, :title, :license_name,
:license_url, :thumbnail_url, :fullsize_url, :link_url
belongs_to :owner, :class_name => 'Member'
has_and_belongs_to_many :plantings has_and_belongs_to_many :plantings
has_and_belongs_to_many :harvests has_and_belongs_to_many :harvests
has_and_belongs_to_many :gardens
before_destroy do |photo| before_destroy do |photo|
photo.plantings.clear photo.plantings.clear
photo.harvests.clear photo.harvests.clear
photo.gardens.clear
end end
default_scope { order("created_at desc") } default_scope order("created_at desc")
# remove photos that aren't used by anything # remove photos that aren't used by anything
def destroy_if_unused def destroy_if_unused
unless plantings.size > 0 or harvests.size > 0 or gardens.size > 0 unless plantings.size > 0 or harvests.size > 0
self.destroy self.destroy
end end
end end
@@ -23,16 +23,16 @@ class Photo < ActiveRecord::Base
# for easier stubbing and testing. # for easier stubbing and testing.
def flickr_metadata def flickr_metadata
flickr = owner.flickr flickr = owner.flickr
info = flickr.photos.getInfo(photo_id: flickr_photo_id) info = flickr.photos.getInfo(:photo_id => flickr_photo_id)
licenses = flickr.photos.licenses.getInfo() licenses = flickr.photos.licenses.getInfo()
license = licenses.find { |l| l.id == info.license } license = licenses.find { |l| l.id == info.license }
return { return {
title: info.title || "Untitled", :title => info.title || "Untitled",
license_name: license.name, :license_name => license.name,
license_url: license.url, :license_url => license.url,
thumbnail_url: FlickRaw.url_q(info), :thumbnail_url => FlickRaw.url_q(info),
fullsize_url: FlickRaw.url_z(info), :fullsize_url => FlickRaw.url_z(info),
link_url: FlickRaw.url_photopage(info) :link_url => FlickRaw.url_photopage(info)
} }
end end

View File

@@ -0,0 +1,13 @@
class PhotoSweeper < ActionController::Caching::Sweeper
observe Photo
def after_create(photo)
expire_fragment('interesting_plantings')
end
def after_destroy(photo)
expire_fragment('interesting_plantings')
end
end

View File

@@ -1,9 +1,11 @@
class PlantPart < ActiveRecord::Base class PlantPart < ActiveRecord::Base
extend FriendlyId extend FriendlyId
friendly_id :name, use: [:slugged, :finders] friendly_id :name, :use => :slugged
has_many :harvests has_many :harvests
has_many :crops, -> { uniq }, through: :harvests has_many :crops, :through => :harvests, :uniq => true
attr_accessible :name, :slug
def to_s def to_s
return name return name

View File

@@ -1,10 +1,14 @@
class Planting < ActiveRecord::Base class Planting < ActiveRecord::Base
extend FriendlyId extend FriendlyId
friendly_id :planting_slug, use: [:slugged, :finders] friendly_id :planting_slug, use: :slugged
attr_accessible :crop_id, :description, :garden_id, :planted_at,
:quantity, :sunniness, :planted_from, :owner_id, :finished,
:finished_at
belongs_to :garden belongs_to :garden
belongs_to :owner, class_name: 'Member', counter_cache: true belongs_to :owner, :class_name => 'Member', :counter_cache => true
belongs_to :crop, counter_cache: true belongs_to :crop, :counter_cache => true
has_and_belongs_to_many :photos has_and_belongs_to_many :photos
@@ -17,34 +21,30 @@ class Planting < ActiveRecord::Base
end end
end end
default_scope { order("created_at desc") } default_scope order("created_at desc")
scope :finished, -> { where(finished: true) } scope :finished, where(:finished => true)
scope :current, -> { where(finished: false) } scope :current, where(:finished => false)
delegate :name, delegate :name,
:en_wikipedia_url, :en_wikipedia_url,
:default_scientific_name, :default_scientific_name,
:plantings_count, :plantings_count,
to: :crop, :to => :crop,
prefix: true :prefix => true
default_scope { order("created_at desc") } default_scope order("created_at desc")
validates :crop, approved: true validates :crop_id, :presence => {:message => "must be present and exist in our database"}
validates :crop, presence: {message: "must be present and exist in our database"}
validates :quantity, validates :quantity,
numericality: { :numericality => { :only_integer => true },
only_integer: true, :allow_nil => true
greater_than_or_equal_to: 0 },
allow_nil: true
SUNNINESS_VALUES = %w(sun semi-shade shade) SUNNINESS_VALUES = %w(sun semi-shade shade)
validates :sunniness, inclusion: { in: SUNNINESS_VALUES, validates :sunniness, :inclusion => { :in => SUNNINESS_VALUES,
message: "%{value} is not a valid sunniness value" }, :message => "%{value} is not a valid sunniness value" },
allow_nil: true, :allow_nil => true,
allow_blank: true :allow_blank => true
PLANTED_FROM_VALUES = [ PLANTED_FROM_VALUES = [
'seed', 'seed',
@@ -59,10 +59,10 @@ class Planting < ActiveRecord::Base
'graft', 'graft',
'layering' 'layering'
] ]
validates :planted_from, inclusion: { in: PLANTED_FROM_VALUES, validates :planted_from, :inclusion => { :in => PLANTED_FROM_VALUES,
message: "%{value} is not a valid planting method" }, :message => "%{value} is not a valid planting method" },
allow_nil: true, :allow_nil => true,
allow_blank: true :allow_blank => true
validate :finished_must_be_after_planted validate :finished_must_be_after_planted
@@ -94,41 +94,6 @@ class Planting < ActiveRecord::Base
return photos.present? return photos.present?
end end
def calculate_days_before_maturity(planting, crop)
p_crop = Planting.where(crop_id: crop).where.not(id: planting)
differences = p_crop.collect do |p|
if p.finished and !p.finished_at.nil?
(p.finished_at - p.planted_at).to_i
end
end
if differences.compact.empty?
nil
else
differences.compact.sum/differences.compact.size
end
end
def planted?(current_date = Date.current)
planted_at.present? && current_date.to_date >= planted_at
end
def percentage_grown(current_date = Date.current)
return nil unless days_before_maturity && planted?(current_date)
days = (current_date.to_date - planted_at.to_date).to_i
return 0 if current_date < planted_at
return 100 if days > days_before_maturity
percent = (days/days_before_maturity*100).to_i
if percent >= 100
percent = 100
end
percent
end
# return a list of interesting plantings, for the homepage etc. # 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 # we can't do this via a scope (as far as we know) so sadly we have to
# do it this way. # do it this way.
@@ -136,8 +101,8 @@ class Planting < ActiveRecord::Base
interesting_plantings = Array.new interesting_plantings = Array.new
seen_owners = Hash.new(false) # keep track of which owners we've seen already seen_owners = Hash.new(false) # keep track of which owners we've seen already
Planting.includes(:photos).each do |p| Planting.all.each do |p|
break if interesting_plantings.size == howmany # got enough yet? break if interesting_plantings.count == howmany # got enough yet?
if require_photo if require_photo
next unless p.photos.present? # skip those without photos, if required next unless p.photos.present? # skip those without photos, if required
end end

View File

@@ -0,0 +1,25 @@
class PlantingSweeper < ActionController::Caching::Sweeper
observe Planting
def after_create(planting)
expire_fragment('homepage_stats')
expire_fragment("member_thumbnail_#{planting.owner.id}")
expire_fragment("interesting_members") if planting.owner.interesting?
expire_fragment("crop_image_#{planting.crop.id}")
end
def after_update(planting)
expire_fragment("planting_listitem_#{planting.id}")
expire_fragment("planting_image_#{planting.id}")
expire_fragment("interesting_plantings")
end
def after_destroy(planting)
expire_fragment('homepage_stats')
expire_fragment("crop_image_#{planting.crop.id}")
expire_fragment('interesting_plantings') if planting.interesting?
expire_fragment("interesting_members") if planting.owner.interesting?
end
end

View File

@@ -1,49 +1,22 @@
class Post < ActiveRecord::Base class Post < ActiveRecord::Base
extend FriendlyId extend FriendlyId
friendly_id :author_date_subject, use: [:slugged, :finders] friendly_id :author_date_subject, use: :slugged
belongs_to :author, class_name: 'Member' attr_accessible :body, :subject, :author_id, :forum_id
belongs_to :author, :class_name => 'Member'
belongs_to :forum belongs_to :forum
has_many :comments, dependent: :destroy has_many :comments, :dependent => :destroy
has_and_belongs_to_many :crops has_and_belongs_to_many :crops
before_destroy {|post| post.crops.clear} before_destroy {|post| post.crops.clear}
after_save :update_crops_posts_association after_save :update_crops_posts_association
# also has_many notifications, but kinda meaningless to get at them # also has_many notifications, but kinda meaningless to get at them
# from this direction, so we won't set up an association for now. # from this direction, so we won't set up an association for now.
after_create do default_scope order("created_at desc")
recipients = Array.new
sender = self.author.id
self.body.scan(Haml::Filters::GrowstuffMarkdown::MEMBER_REGEX) do |m|
# find member case-insensitively and add to list of recipients
member = Member.where('lower(login_name) = ?', $1.downcase).first
recipients << member if member and not recipients.include?(member)
end
self.body.scan(Haml::Filters::GrowstuffMarkdown::MEMBER_AT_REGEX) do |m|
# find member case-insensitively and add to list of recipients
member = Member.where('lower(login_name) = ?', $1[1..-1].downcase).first
recipients << member if member and not recipients.include?(member)
end
# don't send notifications to yourself
recipients.map{ |r| r.id }.each do |recipient|
if recipient != sender
Notification.create(
recipient_id: recipient,
sender_id: sender,
subject: "#{self.author} mentioned you in their post #{self.subject}",
body: self.body,
)
end
end
end
default_scope { order("created_at desc") }
validates :subject, validates :subject,
format: { :format => {
with: /\S/ :with => /\S/
}, }
length: { maximum: 255 }
def author_date_subject def author_date_subject
# slugs are created before created_at is set # slugs are created before created_at is set
@@ -52,7 +25,7 @@ class Post < ActiveRecord::Base
end end
def comment_count def comment_count
self.comments.size self.comments.count
end end
# return the timestamp of the most recent activity on this post # return the timestamp of the most recent activity on this post

View File

@@ -0,0 +1,16 @@
class PostSweeper < ActionController::Caching::Sweeper
observe Post
def after_create(post)
expire_fragment('recent_posts')
end
def after_update(post)
expire_fragment('recent_posts')
end
def after_destroy(post)
expire_fragment('recent_posts')
end
end

View File

@@ -1,12 +1,12 @@
class Product < ActiveRecord::Base class Product < ActiveRecord::Base
attr_accessible :description, :min_price, :recommended_price, :name,
:account_type_id, :paid_months
has_and_belongs_to_many :orders has_and_belongs_to_many :orders
belongs_to :account_type belongs_to :account_type
validates :paid_months, validates :paid_months, :numericality => { :only_integer => true,
numericality: { :allow_nil => true }
only_integer: true,
greater_than_or_equal_to: 0 },
allow_nil: true
def to_s def to_s
name name

View File

@@ -1,7 +1,7 @@
class Role < ActiveRecord::Base class Role < ActiveRecord::Base
extend FriendlyId extend FriendlyId
friendly_id :name, use: [:slugged, :finders] friendly_id :name, use: :slugged
attr_accessible :description, :name, :members, :slug
has_and_belongs_to_many :members has_and_belongs_to_many :members
class << self class << self

View File

@@ -1,5 +1,5 @@
class ScientificName < ActiveRecord::Base class ScientificName < ActiveRecord::Base
after_commit { |sn| sn.crop.__elasticsearch__.index_document if sn.crop && ENV['GROWSTUFF_ELASTICSEARCH'] == "true" } attr_accessible :crop_id, :scientific_name, :creator_id
belongs_to :crop belongs_to :crop
belongs_to :creator, class_name: 'Member' belongs_to :creator, :class_name => 'Member'
end end

View File

@@ -0,0 +1,17 @@
class ScientificNameSweeper < ActionController::Caching::Sweeper
observe ScientificName
def after_create(scientific_name)
expire_fragment("crop_image_#{scientific_name.crop.id}")
end
def after_update(scientific_name)
expire_fragment("crop_image_#{scientific_name.crop.id}")
end
def after_destroy(scientific_name)
expire_fragment("crop_image_#{scientific_name.crop.id}")
end
end

View File

@@ -1,64 +1,27 @@
class Seed < ActiveRecord::Base class Seed < ActiveRecord::Base
extend FriendlyId extend FriendlyId
friendly_id :seed_slug, use: [:slugged, :finders] friendly_id :seed_slug, use: :slugged
attr_accessible :owner_id, :crop_id, :description, :quantity, :plant_before,
:tradable_to, :slug
belongs_to :crop belongs_to :crop
belongs_to :owner, class_name: 'Member', foreign_key: 'owner_id' belongs_to :owner, :class_name => 'Member', :foreign_key => 'owner_id'
default_scope { order("created_at desc") } default_scope order("created_at desc")
validates :crop, approved: true validates :crop, :presence => {:message => "must be present and exist in our database"}
validates :crop, presence: {message: "must be present and exist in our database"}
validates :quantity, validates :quantity,
numericality: { :numericality => { :only_integer => true },
only_integer: true, :allow_nil => true
greater_than_or_equal_to: 0 },
allow_nil: true
validates :days_until_maturity_min,
numericality: {
only_integer: true,
greater_than_or_equal_to: 0 },
allow_nil: true
validates :days_until_maturity_max,
numericality: {
only_integer: true,
greater_than_or_equal_to: 0 },
allow_nil: true
scope :tradable, -> { where("tradable_to != 'nowhere'") } scope :tradable, where("tradable_to != 'nowhere'")
TRADABLE_TO_VALUES = %w(nowhere locally nationally internationally) TRADABLE_TO_VALUES = %w(nowhere locally nationally internationally)
validates :tradable_to, inclusion: { in: TRADABLE_TO_VALUES, validates :tradable_to, :inclusion => { :in => TRADABLE_TO_VALUES,
message: "You may only trade seed nowhere, locally, nationally, or internationally" }, :message => "You may only trade seed nowhere, locally, nationally, or internationally" },
allow_nil: false, :allow_nil => false,
allow_blank: false :allow_blank => false
ORGANIC_VALUES = [
'certified organic',
'non-certified organic',
'conventional/non-organic',
'unknown']
validates :organic, inclusion: { in: ORGANIC_VALUES,
message: "You must say whether the seeds are organic or not, or that you don't know" },
allow_nil: false,
allow_blank: false
GMO_VALUES = [
'certified GMO-free',
'non-certified GMO-free',
'GMO',
'unknown']
validates :gmo, inclusion: { in: GMO_VALUES,
message: "You must say whether the seeds are genetically modified or not, or that you don't know" },
allow_nil: false,
allow_blank: false
HEIRLOOM_VALUES = %w(heirloom hybrid unknown)
validates :heirloom, inclusion: { in: HEIRLOOM_VALUES,
message: "You must say whether the seeds are heirloom, hybrid, or unknown" },
allow_nil: false,
allow_blank: false
def tradable? def tradable?
if self.tradable_to == 'nowhere' if self.tradable_to == 'nowhere'
@@ -82,7 +45,7 @@ class Seed < ActiveRecord::Base
interesting_seeds = Array.new interesting_seeds = Array.new
Seed.tradable.each do |s| Seed.tradable.each do |s|
break if interesting_seeds.size == howmany break if interesting_seeds.length == howmany
if s.interesting? if s.interesting?
interesting_seeds.push(s) interesting_seeds.push(s)
end end

Some files were not shown because too many files have changed in this diff Show More