diff --git a/.travis.yml b/.travis.yml index df55deca37..2cec41620b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -65,3 +65,4 @@ after_deploy: addons: code_climate: repo_token: 462e015bbdaabfb20910fc07f2fea253410ecb131444e00f97dbf32dc6789ca6 + postgresql: "9.4" \ No newline at end of file diff --git a/Gemfile b/Gemfile index 965398f791..eaaca68c28 100644 --- a/Gemfile +++ b/Gemfile @@ -85,6 +85,9 @@ gem "hashie", ">= 3.5.3" gem 'rake', '>= 10.0.0' +# For https://github.com/Growstuff/growstuff/issues/863 +gem 'open-weather' + # # CMS # gem 'comfortable_mexican_sofa', '~> 1.12.0' diff --git a/Gemfile.lock b/Gemfile.lock index 03dc310404..06c86fb4cb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -330,6 +330,8 @@ GEM omniauth-twitter (1.4.0) omniauth-oauth (~> 1.1) rack + open-weather (0.11.0) + json orm_adapter (0.5.0) paperclip (5.1.0) activemodel (>= 4.2.0) @@ -570,6 +572,7 @@ DEPENDENCIES omniauth-facebook omniauth-flickr (>= 0.0.15) omniauth-twitter (~> 1.2) + open-weather pg poltergeist pry diff --git a/app/controllers/likes_controller.rb b/app/controllers/likes_controller.rb index 5f7c5dac94..613c72f55f 100644 --- a/app/controllers/likes_controller.rb +++ b/app/controllers/likes_controller.rb @@ -43,7 +43,7 @@ def success(like, liked_by_member: nil, status_code: nil) def failed(like, message) respond_to do |format| - format.json { render(json: { 'error': message }, status: :forbidden) } + format.json { render(json: { error: message }, status: :forbidden) } format.html do flash[:error] = message if like && like.likeable diff --git a/app/models/planting.rb b/app/models/planting.rb index c7d98c7aad..4e8617513c 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -7,6 +7,7 @@ class Planting < ActiveRecord::Base belongs_to :owner, class_name: 'Member', counter_cache: true belongs_to :crop, counter_cache: true has_many :harvests, -> { order(harvested_at: :desc) }, dependent: :destroy + has_many :planting_weather_logs default_scope { order("plantings.created_at desc") } scope :finished, -> { where(finished: true) } diff --git a/app/models/planting_weather_log.rb b/app/models/planting_weather_log.rb new file mode 100644 index 0000000000..cb8d33f728 --- /dev/null +++ b/app/models/planting_weather_log.rb @@ -0,0 +1,50 @@ +# Defines a JSON data type as weather_log +# See http://edgeguides.rubyonrails.org/active_record_postgresql.html#json for how to query it +# See http://openweathermap.org/current for documentation + +# Example call: http://api.openweathermap.org/data/2.5/weather?q=London,uk&appid=df8abc00e3162fbd98bc48063cc6c4b5 +# { +# "coord":{ +# "lon":-0.13, +# "lat":51.51 +# }, +# "weather":[ +# { +# "id":800, +# "main":"Clear", +# "description":"clear sky", +# "icon":"01d" +# } +# ], +# "base":"stations", +# "main":{ +# "temp":281.83, +# "pressure":1025, +# "humidity":61, +# "temp_min":280.15, +# "temp_max":283.15 +# }, +# "visibility":10000, +# "wind":{ +# "speed":5.7, +# "deg":300 +# }, +# "clouds":{ +# "all":0 +# }, +# "dt":1491808800, +# "sys":{ +# "type":1, +# "id":5091, +# "message":0.0097, +# "country":"GB", +# "sunrise":1491801287, +# "sunset":1491850179 +# }, +# "id":2643743, +# "name":"London", +# "cod":200 +# } +class PlantingWeatherLog < ActiveRecord::Base + belongs_to :planting +end diff --git a/config/application.yml.example b/config/application.yml.example index 4aa23e4394..6e72cca370 100644 --- a/config/application.yml.example +++ b/config/application.yml.example @@ -66,6 +66,10 @@ GROWSTUFF_PAYPAL_SIGNATURE: "dummy" GROWSTUFF_FACEBOOK_KEY: "" GROWSTUFF_FACEBOOK_SECRET: "" +# Used to log weather, see https://github.com/Growstuff/growstuff/issues/863 +# Generate by signing up to http://openweathermap.org/ +GROWSTUFF_OPENWEATHER_KEY: "dummy" + # Elasticsearch is used for flexible search and it requires another component # to be installed. To make it easy for people who don't need to test this feature # it's been turned off for test and development environment as a default. diff --git a/db/migrate/20170410080449_planting_weather_log.rb b/db/migrate/20170410080449_planting_weather_log.rb new file mode 100644 index 0000000000..9c4edc4c24 --- /dev/null +++ b/db/migrate/20170410080449_planting_weather_log.rb @@ -0,0 +1,11 @@ +class PlantingWeatherLog < ActiveRecord::Migration + def change + create_table :planting_weather_logs do |t| + t.integer :planting_id + t.timestamps + t.json 'weather_data' + end + + add_index :planting_weather_logs, :planting_id + end +end diff --git a/db/schema.rb b/db/schema.rb index b33f49830e..b742d14370 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,13 +11,13 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170104035248) do +ActiveRecord::Schema.define(version: 20170410080449) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" create_table "account_types", force: :cascade do |t| - t.string "name", null: false + t.string "name", limit: 255, null: false t.boolean "is_paid" t.boolean "is_permanent_paid" t.datetime "created_at" @@ -33,31 +33,31 @@ end create_table "alternate_names", force: :cascade do |t| - t.string "name", null: false - t.integer "crop_id", null: false - t.integer "creator_id", null: false + t.string "name", limit: 255, null: false + t.integer "crop_id", null: false + t.integer "creator_id", null: false t.datetime "created_at" t.datetime "updated_at" end create_table "authentications", force: :cascade do |t| - t.integer "member_id", null: false - t.string "provider", null: false - t.string "uid" - t.string "token" - t.string "secret" + t.integer "member_id", null: false + t.string "provider", limit: 255, null: false + t.string "uid", limit: 255 + t.string "token", limit: 255 + t.string "secret", limit: 255 t.datetime "created_at" t.datetime "updated_at" - t.string "name" + t.string "name", limit: 255 end add_index "authentications", ["member_id"], name: "index_authentications_on_member_id", using: :btree create_table "comfy_cms_blocks", force: :cascade do |t| - t.string "identifier", null: false + t.string "identifier", limit: 255, null: false t.text "content" t.integer "blockable_id" - t.string "blockable_type" + t.string "blockable_type", limit: 255 t.datetime "created_at" t.datetime "updated_at" end @@ -66,17 +66,17 @@ add_index "comfy_cms_blocks", ["identifier"], name: "index_comfy_cms_blocks_on_identifier", using: :btree create_table "comfy_cms_categories", force: :cascade do |t| - t.integer "site_id", null: false - t.string "label", null: false - t.string "categorized_type", null: false + t.integer "site_id", null: false + t.string "label", limit: 255, null: false + t.string "categorized_type", limit: 255, null: false end add_index "comfy_cms_categories", ["site_id", "categorized_type", "label"], name: "index_cms_categories_on_site_id_and_cat_type_and_label", unique: true, using: :btree create_table "comfy_cms_categorizations", force: :cascade do |t| - t.integer "category_id", null: false - t.string "categorized_type", null: false - t.integer "categorized_id", null: false + t.integer "category_id", null: false + t.string "categorized_type", limit: 255, null: false + t.integer "categorized_id", null: false end add_index "comfy_cms_categorizations", ["category_id", "categorized_type", "categorized_id"], name: "index_cms_categorizations_on_cat_id_and_catd_type_and_catd_id", unique: true, using: :btree @@ -84,9 +84,9 @@ create_table "comfy_cms_files", force: :cascade do |t| t.integer "site_id", null: false t.integer "block_id" - t.string "label", null: false - t.string "file_file_name", null: false - t.string "file_content_type", null: false + t.string "label", limit: 255, null: false + t.string "file_file_name", limit: 255, null: false + t.string "file_content_type", limit: 255, null: false t.integer "file_file_size", null: false t.string "description", limit: 2048 t.integer "position", default: 0, null: false @@ -100,16 +100,16 @@ add_index "comfy_cms_files", ["site_id", "position"], name: "index_comfy_cms_files_on_site_id_and_position", using: :btree create_table "comfy_cms_layouts", force: :cascade do |t| - t.integer "site_id", null: false + t.integer "site_id", null: false t.integer "parent_id" - t.string "app_layout" - t.string "label", null: false - t.string "identifier", null: false + t.string "app_layout", limit: 255 + t.string "label", limit: 255, null: false + t.string "identifier", limit: 255, null: false t.text "content" t.text "css" t.text "js" - t.integer "position", default: 0, null: false - t.boolean "is_shared", default: false, null: false + t.integer "position", default: 0, null: false + t.boolean "is_shared", default: false, null: false t.datetime "created_at" t.datetime "updated_at" end @@ -118,18 +118,18 @@ add_index "comfy_cms_layouts", ["site_id", "identifier"], name: "index_comfy_cms_layouts_on_site_id_and_identifier", unique: true, using: :btree create_table "comfy_cms_pages", force: :cascade do |t| - t.integer "site_id", null: false + t.integer "site_id", null: false t.integer "layout_id" t.integer "parent_id" t.integer "target_page_id" - t.string "label", null: false - t.string "slug" - t.string "full_path", null: false + t.string "label", limit: 255, null: false + t.string "slug", limit: 255 + t.string "full_path", limit: 255, null: false t.text "content_cache" - t.integer "position", default: 0, null: false - t.integer "children_count", default: 0, null: false - t.boolean "is_published", default: true, null: false - t.boolean "is_shared", default: false, null: false + t.integer "position", default: 0, null: false + t.integer "children_count", default: 0, null: false + t.boolean "is_published", default: true, null: false + t.boolean "is_shared", default: false, null: false t.datetime "created_at" t.datetime "updated_at" end @@ -138,8 +138,8 @@ add_index "comfy_cms_pages", ["site_id", "full_path"], name: "index_comfy_cms_pages_on_site_id_and_full_path", using: :btree create_table "comfy_cms_revisions", force: :cascade do |t| - t.string "record_type", null: false - t.integer "record_id", null: false + t.string "record_type", limit: 255, null: false + t.integer "record_id", null: false t.text "data" t.datetime "created_at" end @@ -147,24 +147,24 @@ add_index "comfy_cms_revisions", ["record_type", "record_id", "created_at"], name: "index_cms_revisions_on_rtype_and_rid_and_created_at", using: :btree create_table "comfy_cms_sites", force: :cascade do |t| - t.string "label", null: false - t.string "identifier", null: false - t.string "hostname", null: false - t.string "path" - t.string "locale", default: "en", null: false - t.boolean "is_mirrored", default: false, null: false + t.string "label", limit: 255, null: false + t.string "identifier", limit: 255, null: false + t.string "hostname", limit: 255, null: false + t.string "path", limit: 255 + t.string "locale", limit: 255, default: "en", null: false + t.boolean "is_mirrored", default: false, null: false end add_index "comfy_cms_sites", ["hostname"], name: "index_comfy_cms_sites_on_hostname", using: :btree add_index "comfy_cms_sites", ["is_mirrored"], name: "index_comfy_cms_sites_on_is_mirrored", using: :btree create_table "comfy_cms_snippets", force: :cascade do |t| - t.integer "site_id", null: false - t.string "label", null: false - t.string "identifier", null: false + t.integer "site_id", null: false + t.string "label", limit: 255, null: false + t.string "identifier", limit: 255, null: false t.text "content" - t.integer "position", default: 0, null: false - t.boolean "is_shared", default: false, null: false + t.integer "position", default: 0, null: false + t.boolean "is_shared", default: false, null: false t.datetime "created_at" t.datetime "updated_at" end @@ -181,16 +181,16 @@ end create_table "crops", force: :cascade do |t| - t.string "name", null: false - t.string "en_wikipedia_url" + t.string "name", limit: 255, null: false + t.string "en_wikipedia_url", limit: 255 t.datetime "created_at" t.datetime "updated_at" - t.string "slug" + t.string "slug", limit: 255 t.integer "parent_id" - t.integer "plantings_count", default: 0 + t.integer "plantings_count", default: 0 t.integer "creator_id" t.integer "requester_id" - t.string "approval_status", default: "approved" + t.string "approval_status", limit: 255, default: "approved" t.text "reason_for_rejection" t.text "request_notes" t.text "rejection_notes" @@ -216,29 +216,29 @@ end create_table "forums", force: :cascade do |t| - t.string "name", null: false - t.text "description", null: false - t.integer "owner_id", null: false + t.string "name", limit: 255, null: false + t.text "description", null: false + t.integer "owner_id", null: false t.datetime "created_at" t.datetime "updated_at" - t.string "slug" + t.string "slug", limit: 255 end add_index "forums", ["slug"], name: "index_forums_on_slug", unique: true, using: :btree create_table "gardens", force: :cascade do |t| - t.string "name", null: false + t.string "name", limit: 255, null: false t.integer "owner_id" - t.string "slug", null: false + t.string "slug", limit: 255, null: false t.datetime "created_at" t.datetime "updated_at" t.text "description" - t.boolean "active", default: true - t.string "location" + t.boolean "active", default: true + t.string "location", limit: 255 t.float "latitude" t.float "longitude" t.decimal "area" - t.string "area_unit" + t.string "area_unit", limit: 255 end add_index "gardens", ["owner_id"], name: "index_gardens_on_owner_id", using: :btree @@ -252,17 +252,17 @@ add_index "gardens_photos", ["garden_id", "photo_id"], name: "index_gardens_photos_on_garden_id_and_photo_id", using: :btree create_table "harvests", force: :cascade do |t| - t.integer "crop_id", null: false - t.integer "owner_id", null: false + t.integer "crop_id", null: false + t.integer "owner_id", null: false t.date "harvested_at" t.decimal "quantity" - t.string "unit" + t.string "unit", limit: 255 t.text "description" t.datetime "created_at" t.datetime "updated_at" - t.string "slug" + t.string "slug", limit: 255 t.decimal "weight_quantity" - t.string "weight_unit" + t.string "weight_unit", limit: 255 t.integer "plant_part_id" t.float "si_weight" t.integer "planting_id" @@ -280,49 +280,49 @@ create_table "likes", force: :cascade do |t| t.integer "member_id" t.integer "likeable_id" - t.string "likeable_type" - t.string "categories", array: true + t.string "likeable_type", limit: 255 + t.string "categories", limit: 255, array: true t.datetime "created_at" t.datetime "updated_at" end + add_index "likes", ["likeable_id", "likeable_type"], name: "index_likes_on_likeable_id_and_likeable_type", using: :btree add_index "likes", ["likeable_id"], name: "index_likes_on_likeable_id", using: :btree - add_index "likes", ["likeable_type", "likeable_id"], name: "index_likes_on_likeable_type_and_likeable_id", using: :btree add_index "likes", ["member_id"], name: "index_likes_on_member_id", using: :btree create_table "members", force: :cascade do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" + t.string "email", limit: 255, default: "", null: false + t.string "encrypted_password", limit: 255, default: "", null: false + t.string "reset_password_token", limit: 255 t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" - t.string "current_sign_in_ip" - t.string "last_sign_in_ip" - t.string "confirmation_token" + t.string "current_sign_in_ip", limit: 255 + t.string "last_sign_in_ip", limit: 255 + t.string "confirmation_token", limit: 255 t.datetime "confirmed_at" t.datetime "confirmation_sent_at" - t.string "unconfirmed_email" - t.integer "failed_attempts", default: 0 - t.string "unlock_token" + t.string "unconfirmed_email", limit: 255 + t.integer "failed_attempts", default: 0 + t.string "unlock_token", limit: 255 t.datetime "locked_at" t.datetime "created_at" t.datetime "updated_at" - t.string "login_name" - t.string "slug" + t.string "login_name", limit: 255 + t.string "slug", limit: 255 t.boolean "tos_agreement" t.boolean "show_email" - t.string "location" + t.string "location", limit: 255 t.float "latitude" t.float "longitude" - t.boolean "send_notification_email", default: true + t.boolean "send_notification_email", default: true t.text "bio" t.integer "plantings_count" t.boolean "newsletter" - t.boolean "send_planting_reminder", default: true - t.string "preferred_avatar_uri" + t.boolean "send_planting_reminder", default: true + t.string "preferred_avatar_uri", limit: 255 end add_index "members", ["confirmation_token"], name: "index_members_on_confirmation_token", unique: true, using: :btree @@ -338,10 +338,10 @@ create_table "notifications", force: :cascade do |t| t.integer "sender_id" - t.integer "recipient_id", null: false - t.string "subject" + t.integer "recipient_id", null: false + t.string "subject", limit: 255 t.text "body" - t.boolean "read", default: false + t.boolean "read", default: false t.integer "post_id" t.datetime "created_at" t.datetime "updated_at" @@ -361,9 +361,9 @@ t.datetime "updated_at" t.datetime "completed_at" t.integer "member_id" - t.string "paypal_express_token" - t.string "paypal_express_payer_id" - t.string "referral_code" + t.string "paypal_express_token", limit: 255 + t.string "paypal_express_payer_id", limit: 255 + t.string "referral_code", limit: 255 end create_table "orders_products", id: false, force: :cascade do |t| @@ -372,16 +372,16 @@ end create_table "photos", force: :cascade do |t| - t.integer "owner_id", null: false - t.string "thumbnail_url", null: false - t.string "fullsize_url", null: false + t.integer "owner_id", null: false + t.string "thumbnail_url", limit: 255, null: false + t.string "fullsize_url", limit: 255, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "title", null: false - t.string "license_name", null: false - t.string "license_url" - t.string "link_url", null: false - t.string "flickr_photo_id" + t.string "title", limit: 255, null: false + t.string "license_name", limit: 255, null: false + t.string "license_url", limit: 255 + t.string "link_url", limit: 255, null: false + t.string "flickr_photo_id", limit: 255 end create_table "photos_plantings", id: false, force: :cascade do |t| @@ -397,25 +397,34 @@ add_index "photos_seeds", ["seed_id", "photo_id"], name: "index_photos_seeds_on_seed_id_and_photo_id", using: :btree create_table "plant_parts", force: :cascade do |t| - t.string "name" + t.string "name", limit: 255 + t.datetime "created_at" + t.datetime "updated_at" + t.string "slug", limit: 255 + end + + create_table "planting_weather_logs", force: :cascade do |t| + t.integer "planting_id" t.datetime "created_at" t.datetime "updated_at" - t.string "slug" + t.json "weather_data" end + add_index "planting_weather_logs", ["planting_id"], name: "index_planting_weather_logs_on_planting_id", using: :btree + create_table "plantings", force: :cascade do |t| - t.integer "garden_id", null: false - t.integer "crop_id", null: false + t.integer "garden_id", null: false + t.integer "crop_id", null: false t.date "planted_at" t.integer "quantity" t.text "description" t.datetime "created_at" t.datetime "updated_at" - t.string "slug" - t.string "sunniness" - t.string "planted_from" + t.string "slug", limit: 255 + t.string "sunniness", limit: 255 + t.string "planted_from", limit: 255 t.integer "owner_id" - t.boolean "finished", default: false + t.boolean "finished", default: false t.date "finished_at" t.integer "days_before_maturity" end @@ -423,12 +432,12 @@ add_index "plantings", ["slug"], name: "index_plantings_on_slug", unique: true, using: :btree create_table "posts", force: :cascade do |t| - t.integer "author_id", null: false - t.string "subject", null: false - t.text "body", null: false + t.integer "author_id", null: false + t.string "subject", limit: 255, null: false + t.text "body", null: false t.datetime "created_at" t.datetime "updated_at" - t.string "slug" + t.string "slug", limit: 255 t.integer "forum_id" end @@ -436,9 +445,9 @@ add_index "posts", ["slug"], name: "index_posts_on_slug", unique: true, using: :btree create_table "products", force: :cascade do |t| - t.string "name", null: false - t.text "description", null: false - t.integer "min_price", null: false + t.string "name", limit: 255, null: false + t.text "description", null: false + t.integer "min_price", null: false t.datetime "created_at" t.datetime "updated_at" t.integer "account_type_id" @@ -447,38 +456,55 @@ end create_table "roles", force: :cascade do |t| - t.string "name", null: false + t.string "name", limit: 255, null: false t.text "description" t.datetime "created_at" t.datetime "updated_at" - t.string "slug" + t.string "slug", limit: 255 end add_index "roles", ["slug"], name: "index_roles_on_slug", unique: true, using: :btree create_table "scientific_names", force: :cascade do |t| - t.string "name", null: false - t.integer "crop_id", null: false + t.string "name", limit: 255, null: false + t.integer "crop_id", null: false t.datetime "created_at" t.datetime "updated_at" t.integer "creator_id" end + create_table "seed_trades", force: :cascade do |t| + t.text "message" + t.text "address" + t.datetime "requested_date" + t.datetime "accepted_date" + t.datetime "declined_date" + t.datetime "sent_date" + t.datetime "received_date" + t.integer "seed_id" + t.integer "requester_id" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "seed_trades", ["requester_id"], name: "index_seed_trades_on_requester_id", using: :btree + add_index "seed_trades", ["seed_id"], name: "index_seed_trades_on_seed_id", using: :btree + create_table "seeds", force: :cascade do |t| - t.integer "owner_id", null: false - t.integer "crop_id", null: false + t.integer "owner_id", null: false + t.integer "crop_id", null: false t.text "description" t.integer "quantity" t.date "plant_before" t.datetime "created_at" t.datetime "updated_at" - t.string "tradable_to", default: "nowhere" - t.string "slug" + t.string "tradable_to", limit: 255, default: "nowhere" + t.string "slug", limit: 255 t.integer "days_until_maturity_min" t.integer "days_until_maturity_max" - t.text "organic", default: "unknown" - t.text "gmo", default: "unknown" - t.text "heirloom", default: "unknown" + t.text "organic", default: "unknown" + t.text "gmo", default: "unknown" + t.text "heirloom", default: "unknown" end add_index "seeds", ["slug"], name: "index_seeds_on_slug", unique: true, using: :btree diff --git a/lib/tasks/planting.rake b/lib/tasks/planting.rake new file mode 100644 index 0000000000..7d8adbf70d --- /dev/null +++ b/lib/tasks/planting.rake @@ -0,0 +1,29 @@ +namespace :planting do + desc "For all active plantings with locations, work out the current weather" + task determine_current_weather: :environment do + require 'open_weather' + + plantings = Planting.current + .joins(:garden) + .where('gardens.location IS NOT NULL') + .order('gardens.location') + .limit(1000) + + locations = {} + options = { units: "metric", APPID: ENV['GROWSTUFF_OPENWEATHER_KEY'] } + plantings.each do |planting| + unless locations[planting.garden.location] + locations[planting.garden.location] = OpenWeather::Current.city(planting.garden.location, options) + sleep(2) # API Limit: 60/minute; so we want to cool our jets a little + end + + log = PlantingWeatherLog.create(planting_id: planting.id, weather_data: locations[planting.garden.location]) + + puts [ + "Weather for #{planting.garden.location} is", + "#{log.weather_data['main']['temp']} degree(s) celcius and", + log.weather_data['weather'].first['main'].to_s + ].join(" ") + end + end +end