mirror of
https://github.com/Growstuff/growstuff.git
synced 2025-12-23 17:47:49 -05:00
Add GBIF to our scientific names, so that our crops can associate creative commons photos (#3559)
* Add GBIF cient * Add lookup * Add autocomplete for GBIF lookup * Add extra detail to scientific names * Autocomplete * Add routes * Rmeove mapping * Add autocomplete * Update GBIF data on save * db/schema * Style * Extract service * Add concern * Add concern * Save photos * Initial coverage * Coverage * Add coverage * Shut up, codeclimate * Shut up, codeclimate * Unused * Shut up, codeclimate * Apply suggestions from code review * Remove localhost * Fix rubocop * Fix rubocop * Add UI links * Add rake * Indent * Update Gemfile.lock * Update lib/tasks/gbif.rake * Update app/views/crops/_scientific_names.html.haml * Rubocop * Expand edit photo form * Fix error * Add model validations * Skip photos without backlinks * Fix tests * Add photo words * Allow blank * Rubocop and handle invalid legacy data * Apply suggestions from code review * Update lib/tasks/gbif.rake
This commit is contained in:
5
Gemfile
5
Gemfile
@@ -128,6 +128,9 @@ gem 'faraday_middleware'
|
||||
|
||||
gem 'rack-cors'
|
||||
|
||||
# External APIs for data
|
||||
gem "gbifrb"
|
||||
|
||||
group :production do
|
||||
gem 'bonsai-elasticsearch-rails' # Integration with Bonsa-Elasticsearch on heroku
|
||||
gem 'dalli'
|
||||
@@ -153,6 +156,7 @@ group :development, :test do
|
||||
gem 'factory_bot_rails' # for creating test data
|
||||
gem 'faker'
|
||||
gem 'haml-rails' # HTML templating language
|
||||
gem 'pry'
|
||||
gem 'query_diet'
|
||||
gem 'rspec-activemodel-mocks'
|
||||
gem 'rspec-rails' # unit testing framework
|
||||
@@ -176,6 +180,7 @@ group :test do
|
||||
gem 'rails-controller-testing'
|
||||
gem 'selenium-webdriver'
|
||||
gem 'timecop'
|
||||
gem 'vcr'
|
||||
end
|
||||
|
||||
group :travis do
|
||||
|
||||
@@ -163,6 +163,7 @@ GEM
|
||||
chartkick (5.0.5)
|
||||
codeclimate-test-reporter (1.0.9)
|
||||
simplecov (<= 0.13)
|
||||
coderay (1.1.3)
|
||||
coffee-rails (5.0.0)
|
||||
coffee-script (>= 2.2.0)
|
||||
railties (>= 5.2.0)
|
||||
@@ -254,6 +255,7 @@ GEM
|
||||
sassc (>= 1.11)
|
||||
friendly_id (5.5.1)
|
||||
activerecord (>= 4.0.0)
|
||||
gbifrb (0.2.0)
|
||||
geocoder (1.8.2)
|
||||
gibbon (1.2.1)
|
||||
httparty
|
||||
@@ -412,6 +414,9 @@ GEM
|
||||
moneta (~> 1.0.0)
|
||||
rate_throttle_client (~> 0.1.0)
|
||||
popper_js (1.16.1)
|
||||
pry (0.14.2)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
public_suffix (5.0.4)
|
||||
puma (6.4.2)
|
||||
nio4r (~> 2.0)
|
||||
@@ -614,6 +619,7 @@ GEM
|
||||
validate_url (1.0.15)
|
||||
activemodel (>= 3.0.0)
|
||||
public_suffix
|
||||
vcr (6.2.0)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
webrat (0.7.3)
|
||||
@@ -673,6 +679,7 @@ DEPENDENCIES
|
||||
flickraw
|
||||
font-awesome-sass
|
||||
friendly_id
|
||||
gbifrb
|
||||
geocoder
|
||||
gibbon (~> 1.2.0)
|
||||
gravatar-ultimate
|
||||
@@ -701,6 +708,7 @@ DEPENDENCIES
|
||||
percy-capybara (~> 5.0.0)
|
||||
pg
|
||||
platform-api
|
||||
pry
|
||||
puma
|
||||
query_diet
|
||||
rack-cors
|
||||
@@ -731,6 +739,7 @@ DEPENDENCIES
|
||||
uglifier
|
||||
unicorn
|
||||
validate_url
|
||||
vcr
|
||||
webrat
|
||||
will_paginate
|
||||
will_paginate-bootstrap-style
|
||||
|
||||
@@ -5,7 +5,7 @@ jQuery ->
|
||||
$(".remove-altname-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_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='scientific-name-auto-suggest form-control' id='sci_name[INDEX]' data-source-url='/scientific_names/gbif_suggest')'></input><span class='help-block'>Scientific name of crop</span><input type='text' id='sci_gbif_key[INDEX]' class=''></div><div class='col-md-2'></div></div>"
|
||||
|
||||
sci_index = $('#scientific_names .template').length + 1
|
||||
|
||||
@@ -21,7 +21,7 @@ jQuery ->
|
||||
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_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
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# TODO: This assumes one autocomplete per page.
|
||||
# Needs to be a function so that when we append one of these, it gets uniquely associated with the hidden controls.
|
||||
jQuery ->
|
||||
|
||||
if el = $( '.scientific-name-auto-suggest' )
|
||||
|
||||
id = $( '.scientific-name-auto-suggest-id' )
|
||||
|
||||
el.autocomplete
|
||||
minLength: 3,
|
||||
source: el.attr( 'data-source-url' ),
|
||||
focus: ( event, ui ) ->
|
||||
el.val( ui.item.canonicalName )
|
||||
id.val( ui.item.nameKey )
|
||||
false
|
||||
select: ( event, ui ) ->
|
||||
el.val( ui.item.canonicalName )
|
||||
id.val( ui.item.nameKey )
|
||||
false
|
||||
response: ( event, ui ) ->
|
||||
id.val( "" )
|
||||
for item in ui.content
|
||||
if item.name == el.val()
|
||||
id.val( item.nameKey )
|
||||
|
||||
if el.data( 'uiAutocomplete' )
|
||||
el.data( 'uiAutocomplete' )._renderItem = ( ul, item ) ->
|
||||
$( '<li class="list-group-item"></li>' )
|
||||
.data( 'item.autocomplete', item )
|
||||
.append( "<a>#{item.canonicalName} (#{item.scientificName}) - #{item.rank}</a>" )
|
||||
.appendTo( ul )
|
||||
@@ -46,6 +46,12 @@ class CropsController < ApplicationController
|
||||
respond_with @crop, location: @crop
|
||||
end
|
||||
|
||||
def gbif
|
||||
@crop = Crop.find(params[:crop_slug])
|
||||
@crop.update_gbif_data!
|
||||
respond_with @crop, location: @crop
|
||||
end
|
||||
|
||||
def hierarchy
|
||||
@crops = Crop.toplevel.order(:name)
|
||||
respond_with @crops
|
||||
@@ -120,6 +126,7 @@ class CropsController < ApplicationController
|
||||
if @crop.approval_status_changed?(from: "pending", to: "approved")
|
||||
notifier.deliver_now!
|
||||
@crop.update_openfarm_data!
|
||||
@crop.update_gbif_data!
|
||||
end
|
||||
else
|
||||
@crop.approval_status = @crop.approval_status_was
|
||||
|
||||
@@ -63,7 +63,7 @@ class PhotosController < ApplicationController
|
||||
|
||||
def photo_params
|
||||
params.require(:photo).permit(:source_id, :source, :title, :license_name,
|
||||
:license_url, :thumbnail_url, :fullsize_url, :link_url)
|
||||
:license_url, :thumbnail_url, :fullsize_url, :link_url, :date_taken)
|
||||
end
|
||||
|
||||
# Item with photos attached
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ScientificNamesController < ApplicationController
|
||||
before_action :authenticate_member!, except: %i(index show)
|
||||
load_and_authorize_resource
|
||||
before_action :authenticate_member!, except: %i(index show gbif_suggest)
|
||||
load_and_authorize_resource except: [:gbif_suggest]
|
||||
respond_to :html, :json
|
||||
responders :flash
|
||||
|
||||
@@ -35,7 +35,7 @@ class ScientificNamesController < ApplicationController
|
||||
def create
|
||||
@scientific_name = ScientificName.new(scientific_name_params)
|
||||
@scientific_name.creator = current_member
|
||||
|
||||
gbif_sync!(@scientific_name)
|
||||
@scientific_name.save
|
||||
respond_with(@scientific_name.crop)
|
||||
end
|
||||
@@ -43,7 +43,9 @@ class ScientificNamesController < ApplicationController
|
||||
# PUT /scientific_names/1
|
||||
# PUT /scientific_names/1.json
|
||||
def update
|
||||
@scientific_name.update(scientific_name_params)
|
||||
@scientific_name.assign_attributes(scientific_name_params)
|
||||
gbif_sync!(@scientific_name)
|
||||
@scientific_name.save
|
||||
respond_with(@scientific_name.crop)
|
||||
end
|
||||
|
||||
@@ -56,9 +58,26 @@ class ScientificNamesController < ApplicationController
|
||||
respond_with(@crop)
|
||||
end
|
||||
|
||||
def gbif_suggest
|
||||
render json: gbif_service.suggest(params[:term])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def gbif_sync!(model)
|
||||
return unless model.gbif_key
|
||||
|
||||
result = gbif_service.fetch(model.gbif_key)
|
||||
|
||||
model.gbif_rank = result["rank"]
|
||||
model.gbif_status = result["status"]
|
||||
end
|
||||
|
||||
def scientific_name_params
|
||||
params.require(:scientific_name).permit(:crop_id, :name)
|
||||
params.require(:scientific_name).permit(:crop_id, :name, :gbif_key)
|
||||
end
|
||||
|
||||
def gbif_service
|
||||
GbifService.new
|
||||
end
|
||||
end
|
||||
|
||||
@@ -79,6 +79,7 @@ class Ability
|
||||
can :manage, ScientificName
|
||||
can :manage, AlternateName
|
||||
can :openfarm, Crop
|
||||
can :gbif, Crop
|
||||
end
|
||||
|
||||
# any member can create a crop provisionally
|
||||
|
||||
11
app/models/concerns/gbif_data.rb
Normal file
11
app/models/concerns/gbif_data.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module GbifData
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
def update_gbif_data!
|
||||
GbifService.new.update_crop(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,6 +4,7 @@ class Crop < ApplicationRecord
|
||||
extend FriendlyId
|
||||
include PhotoCapable
|
||||
include OpenFarmData
|
||||
include GbifData
|
||||
include SearchCrops
|
||||
|
||||
friendly_id :name, use: %i(slugged finders)
|
||||
|
||||
@@ -16,8 +16,13 @@ class Photo < ApplicationRecord
|
||||
Crop.distinct.joins(:photo_associations).where(photo_associations: { photo: self })
|
||||
end
|
||||
|
||||
validates :fullsize_url, url: true
|
||||
validates :thumbnail_url, url: true
|
||||
validates :fullsize_url, url: true, presence: true
|
||||
validates :thumbnail_url, url: true, presence: true
|
||||
validates :link_url, url: true, presence: true
|
||||
validates :owner, presence: true
|
||||
validates :title, presence: true
|
||||
validates :license_name, presence: true # Should assert this is one of CC-BY, CC-BY-NC, etc
|
||||
validates :license_url, url: true, allow_blank: true
|
||||
|
||||
# creates a relationship for each assignee type
|
||||
PHOTO_CAPABLE.each do |type|
|
||||
|
||||
193
app/services/gbif_service.rb
Normal file
193
app/services/gbif_service.rb
Normal file
@@ -0,0 +1,193 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'English'
|
||||
class GbifService
|
||||
def initialize
|
||||
@cropbot = Member.find_by(login_name: 'cropbot')
|
||||
@species = Gbif::Species
|
||||
end
|
||||
|
||||
def suggest(term)
|
||||
# Query the GBIF name autocomplete and discover the scientific name.
|
||||
# [
|
||||
# {
|
||||
# "key": 2932942,
|
||||
# "nameKey": 1970347,
|
||||
# "kingdom": "Plantae",
|
||||
# "phylum": "Tracheophyta",
|
||||
# "order": "Solanales",
|
||||
# "family": "Solanaceae",
|
||||
# "genus": "Capsicum",
|
||||
# "species": "Capsicum chinense",
|
||||
# "kingdomKey": 6,
|
||||
# "phylumKey": 7707728,
|
||||
# "classKey": 220,
|
||||
# "orderKey": 1176,
|
||||
# "familyKey": 7717,
|
||||
# "genusKey": 2932937,
|
||||
# "speciesKey": 2932942,
|
||||
# "parent": "Capsicum",
|
||||
# "parentKey": 2932937,
|
||||
# "nubKey": 2932942,
|
||||
# "scientificName": "Capsicum chinense Jacq.",
|
||||
# "canonicalName": "Capsicum chinense",
|
||||
# "rank": "SPECIES",
|
||||
# "status": "ACCEPTED",
|
||||
# "synonym": false,
|
||||
# "higherClassificationMap": {
|
||||
# "6": "Plantae",
|
||||
# "220": "Magnoliopsida",
|
||||
# "1176": "Solanales",
|
||||
# "7717": "Solanaceae",
|
||||
# "2932937": "Capsicum",
|
||||
# "7707728": "Tracheophyta"
|
||||
# },
|
||||
# "class": "Magnoliopsida"
|
||||
# },
|
||||
# {
|
||||
# "key": 12079498,
|
||||
# "nameKey": 81778754,
|
||||
# "kingdom": "Plantae",
|
||||
# "phylum": "Tracheophyta",
|
||||
# "order": "Solanales",
|
||||
# "family": "Solanaceae",
|
||||
# "genus": "Capsicum",
|
||||
# "species": "Capsicum chinense",
|
||||
# "kingdomKey": 6,
|
||||
# "phylumKey": 7707728,
|
||||
# "classKey": 220,
|
||||
# "orderKey": 1176,
|
||||
# "familyKey": 7717,
|
||||
# "genusKey": 2932937,
|
||||
# "speciesKey": 2932942,
|
||||
# "parent": "Capsicum",
|
||||
# "parentKey": 2932937,
|
||||
# "nubKey": 12079498,
|
||||
# "scientificName": "Capsicum annuum var. chinense (Jacq.) Alef.",
|
||||
# "canonicalName": "Capsicum annuum chinense",
|
||||
# "rank": "VARIETY",
|
||||
# "status": "SYNONYM",
|
||||
# "synonym": true,
|
||||
# "higherClassificationMap": {
|
||||
# "6": "Plantae",
|
||||
# "220": "Magnoliopsida",
|
||||
# "1176": "Solanales",
|
||||
# "7717": "Solanaceae",
|
||||
# "2932937": "Capsicum",
|
||||
# "2932942": "Capsicum chinense",
|
||||
# "7707728": "Tracheophyta"
|
||||
# },
|
||||
# "class": "Magnoliopsida"
|
||||
# }
|
||||
# ]
|
||||
|
||||
@species.name_suggest(q: term)
|
||||
end
|
||||
|
||||
def import!
|
||||
Crop.order(updated_at: :desc).each do |crop|
|
||||
Rails.logger.debug { "#{crop.id}, #{crop.name}" }
|
||||
update_crop(crop) if crop.valid?
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
Rails.logger.error($ERROR_INFO.message)
|
||||
end
|
||||
end
|
||||
|
||||
def update_crop(crop)
|
||||
# Attempt to resolve the scientific names via /species/match.
|
||||
gbif_usage_key = crop.scientific_names.detect { |sn| sn.gbif_key.present? }&.gbif_key
|
||||
unless gbif_usage_key
|
||||
crop.scientific_names.each do |sn|
|
||||
result = @species.name_backbone(name: sn.name) # , higherTaxonKey: 6, nameType: 'SCIENTIFIC')
|
||||
next unless result["confidence"] > 95 && result["matchType"] == "EXACT"
|
||||
|
||||
sn.gbif_key = result["usageKey"]
|
||||
sn.gbif_rank = result["rank"]
|
||||
sn.gbif_status = result["status"]
|
||||
sn.save!
|
||||
end
|
||||
|
||||
gbif_usage_key = crop.scientific_names.detect { |sn| sn.gbif_key.present? }&.gbif_key
|
||||
end
|
||||
|
||||
# No match? Fall back to common names
|
||||
unless gbif_usage_key
|
||||
query_results = @species.name_lookup(q: crop.name, higherTaxonKey: 6)
|
||||
|
||||
# We only want one result, otherwise it needs human.
|
||||
return unless query_results["results"].length == 1
|
||||
|
||||
query_result = query_results["results"].first
|
||||
|
||||
gbif_usage_key = query_result["key"]
|
||||
|
||||
crop.scientific_names.create!(gbif_key: gbif_usage_key, name: query_result["canonicalName"], creator: @cropbot)
|
||||
end
|
||||
|
||||
gbif_record = fetch(gbif_usage_key)
|
||||
if gbif_record.present?
|
||||
# crop.update! openfarm_data: gbif_record.fetch('data', false)
|
||||
# save_companions(crop, gbif_record)
|
||||
save_photos(crop, gbif_usage_key)
|
||||
else
|
||||
Rails.logger.debug "\tcrop not found on GBIF"
|
||||
# crop.update!(openfarm_data: false)
|
||||
end
|
||||
end
|
||||
|
||||
def save_photos(crop, key)
|
||||
# https://api.gbif.org/v1/occurrence/search?taxon_key=3084850
|
||||
|
||||
occurrences = Gbif::Occurrences.search(taxonKey: key, mediatype: 'StillImage', limit: 3, hasCoordinate: true)
|
||||
occurrences["results"].each do |result|
|
||||
next unless result["media"]
|
||||
|
||||
media = result["media"].first
|
||||
next unless media["identifier"]
|
||||
|
||||
# Example: "https://inaturalist-open-data.s3.amazonaws.com/photos/250226497/original.jpg"
|
||||
url = media["identifier"]
|
||||
md5 = Digest::MD5.hexdigest(url)
|
||||
width = 200
|
||||
thumbnail = "https://api.gbif.org/v1/image/cache/#{width}x/occurrence/#{result['key']}/media/#{md5}"
|
||||
|
||||
next unless url.start_with? 'http'
|
||||
next if Photo.find_by(source_id: result["key"], source: 'gbif')
|
||||
next if media["references"].blank?
|
||||
|
||||
photo = Photo.new(
|
||||
# This is for the overall observation which may technically have multiple media. However, we're only taking the first.
|
||||
source_id: result["key"],
|
||||
source: 'gbif',
|
||||
owner: @cropbot,
|
||||
thumbnail_url: thumbnail,
|
||||
fullsize_url: url,
|
||||
title: "Photo by #{media['creator']} via #{media['publisher']} (Copyright #{media['rightsHolder']})",
|
||||
license_name: case media["license"]
|
||||
when "http://creativecommons.org/licenses/by/4.0/"
|
||||
"CC BY 4.0"
|
||||
when "http://creativecommons.org/licenses/by-nc/4.0/"
|
||||
"CC BY-NC 4.0"
|
||||
else
|
||||
media["license"]
|
||||
end,
|
||||
license_url: media["license"],
|
||||
link_url: media["references"]
|
||||
)
|
||||
photo.date_taken = DateTime.parse(media["created"]) if media["created"]
|
||||
if photo.valid?
|
||||
Photo.transaction do
|
||||
photo.save
|
||||
PhotoAssociation.find_or_create_by! photo:, photographable: crop
|
||||
end
|
||||
Rails.logger.debug { "\t saved photo #{photo.id} #{photo.source_id}" }
|
||||
else
|
||||
Rails.logger.warn "Photo not valid"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def fetch(key)
|
||||
Gbif::Request.new("species/#{key}", nil, nil, nil).perform
|
||||
end
|
||||
end
|
||||
@@ -65,8 +65,12 @@
|
||||
.col-2
|
||||
= label_tag :scientific_names, "Scientific name #{index + 1}:", class: 'control-label'
|
||||
.col-8
|
||||
= text_field_tag "sci_name[#{index + 1}]", sci.name, id: "sci_name[#{index + 1}]", class: 'form-control'
|
||||
%span.help-block Scientific name of crop.
|
||||
= text_field_tag "sci_name[#{index + 1}]", sci.name, id: "sci_name[#{index + 1}]",
|
||||
class: 'scientific-name-auto-suggest form-control',
|
||||
data: { source_url: gbif_suggest_scientific_names_path }
|
||||
%span.help-block Searches GBIF to determine scientific name of crop.
|
||||
= hidden_field_tag "sci_gbif_key[#{index + 1}]", sci.gbif_key, id: "sci_gbif_key[#{index + 1}]",
|
||||
class: 'scientific-name-auto-suggest-id'
|
||||
%h2 Alternate names
|
||||
= button_tag "+", class: "add-altname-row", type: "button"
|
||||
= button_tag "-", class: "remove-altname-row", type: "button"
|
||||
|
||||
@@ -16,7 +16,13 @@
|
||||
= delete_icon
|
||||
= t('.delete')
|
||||
- else
|
||||
.badge= sn.name
|
||||
- if sn.gbif_key
|
||||
= link_to sn.name, "https://www.gbif.org/species/#{sn.gbif_key}",
|
||||
class: 'card-link',
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer"
|
||||
- else
|
||||
.badge= sn.name
|
||||
|
||||
%p.text-right
|
||||
- if can? :edit, crop
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
= icon 'far', 'update'
|
||||
Fetch data from OpenFarm
|
||||
|
||||
= link_to crop_gbif_path(crop), method: :post, class: 'dropdown-item' do
|
||||
= icon 'far', 'update'
|
||||
Fetch data from GBIF
|
||||
|
||||
- if can? :destroy, crop
|
||||
.dropdown-divider
|
||||
= delete_button(crop, classes: 'dropdown-item text-danger')
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
%h5.ellipsis
|
||||
= photo_icon
|
||||
= link_to photo.title, photo_path(id: photo.id)
|
||||
%i by #{link_to photo.owner_login_name, member_path(slug: photo.owner_slug)}
|
||||
- if photo.owner_slug
|
||||
%i by #{link_to photo.owner_login_name, member_path(slug: photo.owner_slug)}
|
||||
- if photo.date_taken.present?
|
||||
%small.text-muted
|
||||
%time{datetime: photo.date_taken}= I18n.l(photo.date_taken.to_date)
|
||||
|
||||
@@ -5,6 +5,31 @@
|
||||
= form_for(@photo) do |f|
|
||||
.form-group
|
||||
= f.label :title
|
||||
= f.text_field :title, placeholder: "title"
|
||||
= f.text_field :title, placeholder: "title", required: true
|
||||
|
||||
.form-group
|
||||
= f.label :thumbnail_url
|
||||
= f.url_field :thumbnail_url
|
||||
|
||||
.form-group
|
||||
= f.label :fullsize_url
|
||||
= f.url_field :fullsize_url
|
||||
|
||||
.form-group
|
||||
= f.label :link_url
|
||||
= f.url_field :link_url
|
||||
|
||||
.form-group
|
||||
= f.label :license_name
|
||||
= f.text_field :license_name
|
||||
|
||||
.form-group
|
||||
= f.label :license_url
|
||||
= f.text_field :license_url
|
||||
|
||||
.form-group
|
||||
= f.label :date_taken
|
||||
= f.datetime_field :date_taken
|
||||
|
||||
.form-group
|
||||
.form-actions= f.submit 'Save', class: 'btn'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- content_for :title, "New Photo"
|
||||
|
||||
%h1 New Photo
|
||||
%h2 Choose photo for #{link_to @item, @item}
|
||||
%h2 Choose photo for #{link_to @item, @item} from Flickr, or contribute to unique crops to <a href="https://inaturalist.org/" target="_blank">iNaturalist</a> or <a href="https://identify.plantnet.org/" target="_blank">Pl@ntNet</a> via the app.
|
||||
|
||||
- if @please_reconnect_flickr
|
||||
%h2.alert Please reconnect your flickr account
|
||||
|
||||
@@ -10,5 +10,5 @@
|
||||
- else
|
||||
%p No photos.
|
||||
- if can?(:edit, planting) && can?(:create, Photo)
|
||||
%p Add a photo to visually track growth of this planting
|
||||
%p Add a photo to visually track growth of this planting, to Flickr, iNaturalist or Pl@ntNet
|
||||
= add_photo_button(planting)
|
||||
|
||||
@@ -53,7 +53,11 @@ Rails.application.routes.draw do
|
||||
get 'author/:author' => 'posts#index', as: 'by_author', on: :collection
|
||||
end
|
||||
|
||||
resources :scientific_names
|
||||
resources :scientific_names do
|
||||
collection do
|
||||
get :gbif_suggest
|
||||
end
|
||||
end
|
||||
resources :alternate_names
|
||||
resources :plant_parts
|
||||
resources :photos
|
||||
@@ -73,6 +77,7 @@ Rails.application.routes.draw do
|
||||
get 'planted_from' => 'charts/crops#planted_from', constraints: { format: 'json' }
|
||||
get 'harvested_for' => 'charts/crops#harvested_for', constraints: { format: 'json' }
|
||||
post :openfarm
|
||||
post :gbif
|
||||
|
||||
collection do
|
||||
get 'requested'
|
||||
|
||||
10
db/migrate/20240114045751_add_gbif.rb
Normal file
10
db/migrate/20240114045751_add_gbif.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddGbif < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :scientific_names, :gbif_key, :int
|
||||
add_column :scientific_names, :gbif_rank, :string
|
||||
add_column :scientific_names, :gbif_status, :string
|
||||
add_column :scientific_names, :wikidata_id, :string
|
||||
end
|
||||
end
|
||||
26
db/schema.rb
26
db/schema.rb
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2023_03_13_015323) do
|
||||
ActiveRecord::Schema[7.0].define(version: 2024_01_14_045751) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
@@ -543,6 +543,10 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_13_015323) do
|
||||
t.datetime "created_at", precision: nil
|
||||
t.datetime "updated_at", precision: nil
|
||||
t.integer "creator_id"
|
||||
t.integer "gbif_key"
|
||||
t.string "gbif_rank"
|
||||
t.string "gbif_status"
|
||||
t.string "wikidata_id"
|
||||
end
|
||||
|
||||
create_table "seeds", id: :serial, force: :cascade do |t|
|
||||
@@ -567,6 +571,26 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_13_015323) do
|
||||
t.index ["slug"], name: "index_seeds_on_slug", unique: true
|
||||
end
|
||||
|
||||
create_table "weather_observations", force: :cascade do |t|
|
||||
t.string "source"
|
||||
t.datetime "observation_at"
|
||||
t.integer "solar_uv_index"
|
||||
t.decimal "wind_speed_kmh"
|
||||
t.decimal "wind_gust_speed_kmh"
|
||||
t.string "wind_direction"
|
||||
t.decimal "air_temperature_centigrade"
|
||||
t.decimal "relative_humidity"
|
||||
t.decimal "precipitation_probability"
|
||||
t.decimal "dew_point_temperature_centigrade"
|
||||
t.decimal "pressure"
|
||||
t.integer "visibility_distance_metres"
|
||||
t.string "weather_type"
|
||||
t.bigint "owner_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["owner_id"], name: "index_weather_observations_on_owner_id"
|
||||
end
|
||||
|
||||
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
|
||||
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
|
||||
add_foreign_key "harvests", "plantings"
|
||||
|
||||
10
lib/tasks/gbif.rake
Normal file
10
lib/tasks/gbif.rake
Normal file
@@ -0,0 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
namespace :gbif do
|
||||
desc "Retrieve crop info from GBIF"
|
||||
|
||||
task import: :environment do
|
||||
Rails.logger = Logger.new(STDOUT)
|
||||
GbifService.new.import!
|
||||
end
|
||||
end
|
||||
57
spec/cassettes/GbifService/_fetch/fetches_a_given_key.yml
Normal file
57
spec/cassettes/GbifService/_fetch/fetches_a_given_key.yml
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
http_interactions:
|
||||
- request:
|
||||
method: get
|
||||
uri: https://api.gbif.org/v1/species/2930137
|
||||
body:
|
||||
encoding: US-ASCII
|
||||
string: ''
|
||||
headers:
|
||||
User-Agent:
|
||||
- Faraday/v1.10.3 Gbif/v0.2.0
|
||||
X-USER-AGENT:
|
||||
- Faraday/v1.10.3 Gbif/v0.2.0
|
||||
response:
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
headers:
|
||||
vary:
|
||||
- Origin, Access-Control-Request-Method, Access-Control-Request-Headers
|
||||
x-content-type-options:
|
||||
- nosniff
|
||||
x-xss-protection:
|
||||
- 1; mode=block
|
||||
pragma:
|
||||
- no-cache
|
||||
expires:
|
||||
- '0'
|
||||
x-frame-options:
|
||||
- DENY
|
||||
content-type:
|
||||
- application/json
|
||||
date:
|
||||
- Sun, 14 Jan 2024 10:03:58 GMT
|
||||
cache-control:
|
||||
- public, max-age=3601
|
||||
x-varnish:
|
||||
- 952863014 979042621
|
||||
age:
|
||||
- '126'
|
||||
via:
|
||||
- 1.1 varnish (Varnish/6.0)
|
||||
accept-ranges:
|
||||
- bytes
|
||||
content-length:
|
||||
- '938'
|
||||
connection:
|
||||
- keep-alive
|
||||
body:
|
||||
encoding: UTF-8
|
||||
string: '{"key":2930137,"nubKey":2930137,"nameKey":10463714,"taxonID":"gbif:2930137","kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"datasetKey":"d7dddbf4-2cf0-4f39-9b2a-bb099caae36c","constituentKey":"7ddf754f-d193-4cc9-b351-99906754a03b","parentKey":2928997,"parent":"Solanum","scientificName":"Solanum
|
||||
lycopersicum L.","canonicalName":"Solanum lycopersicum","vernacularName":"Garden
|
||||
tomato","authorship":"L.","nameType":"SCIENTIFIC","rank":"SPECIES","origin":"SOURCE","taxonomicStatus":"ACCEPTED","nomenclaturalStatus":[],"remarks":"","publishedIn":"L.
|
||||
(1753). In: Sp. Pl. 185.","numDescendants":23,"lastCrawled":"2023-08-22T23:20:59.545+00:00","lastInterpreted":"2023-08-22T23:12:05.487+00:00","issues":[],"class":"Magnoliopsida"}'
|
||||
recorded_at: Sun, 14 Jan 2024 10:06:05 GMT
|
||||
recorded_with: VCR 6.2.0
|
||||
103
spec/cassettes/GbifService/_suggest/matches.yml
Normal file
103
spec/cassettes/GbifService/_suggest/matches.yml
Normal file
@@ -0,0 +1,103 @@
|
||||
---
|
||||
http_interactions:
|
||||
- request:
|
||||
method: get
|
||||
uri: https://api.gbif.org/v1/species/suggest?limit=100&q=Solanum+lycopersicum
|
||||
body:
|
||||
encoding: US-ASCII
|
||||
string: ''
|
||||
headers:
|
||||
User-Agent:
|
||||
- Faraday/v1.10.3 Gbif/v0.2.0
|
||||
X-USER-AGENT:
|
||||
- Faraday/v1.10.3 Gbif/v0.2.0
|
||||
response:
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
headers:
|
||||
vary:
|
||||
- Origin, Access-Control-Request-Method, Access-Control-Request-Headers
|
||||
x-content-type-options:
|
||||
- nosniff
|
||||
x-xss-protection:
|
||||
- 1; mode=block
|
||||
pragma:
|
||||
- no-cache
|
||||
expires:
|
||||
- '0'
|
||||
x-frame-options:
|
||||
- DENY
|
||||
content-type:
|
||||
- application/json
|
||||
date:
|
||||
- Sun, 14 Jan 2024 10:06:04 GMT
|
||||
cache-control:
|
||||
- public, max-age=3601
|
||||
x-varnish:
|
||||
- '993984559'
|
||||
age:
|
||||
- '0'
|
||||
via:
|
||||
- 1.1 varnish (Varnish/6.0)
|
||||
accept-ranges:
|
||||
- bytes
|
||||
content-length:
|
||||
- '10730'
|
||||
connection:
|
||||
- keep-alive
|
||||
body:
|
||||
encoding: UTF-8
|
||||
string: '[{"key":2930137,"nameKey":10463714,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum","parentKey":2928997,"nubKey":2930137,"scientificName":"Solanum
|
||||
lycopersicum L.","canonicalName":"Solanum lycopersicum","rank":"SPECIES","status":"ACCEPTED","synonym":false,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum"},"class":"Magnoliopsida"},{"key":7815295,"nameKey":31973001,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":7815295,"parent":"Solanum","parentKey":2928997,"nubKey":7815295,"scientificName":"Solanum
|
||||
lycopersicum Blanco, 1837","canonicalName":"Solanum lycopersicum","rank":"SPECIES","status":"DOUBTFUL","synonym":false,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum"},"class":"Magnoliopsida"},{"key":8586238,"nameKey":6531517,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Lycopersicum
|
||||
solanum-lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":8586238,"parent":"Solanum","parentKey":2928997,"nubKey":8586238,"scientificName":"Lycopersicum
|
||||
solanum-lycopersicum Hill","canonicalName":"Lycopersicum solanum-lycopersicum","rank":"SPECIES","status":"DOUBTFUL","synonym":false,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum"},"class":"Magnoliopsida"},{"key":8640337,"nameKey":6531515,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Lycopersicum
|
||||
solanum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":8640337,"parent":"Solanum","parentKey":2928997,"nubKey":8640337,"scientificName":"Lycopersicum
|
||||
solanum Medik.","canonicalName":"Lycopersicum solanum","rank":"SPECIES","status":"DOUBTFUL","synonym":false,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum"},"class":"Magnoliopsida"},{"key":7608359,"nameKey":6531353,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum","parentKey":2928997,"nubKey":7608359,"scientificName":"Lycopersicon
|
||||
solanum-lycopersicum Hill","canonicalName":"Lycopersicon solanum-lycopersicum","rank":"SPECIES","status":"SYNONYM","synonym":true,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"},{"key":11519041,"nameKey":97469846,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum
|
||||
lycopersicum","parentKey":2930137,"nubKey":11519041,"scientificName":"Solanum
|
||||
lycopersicum subsp. lycopersicum","canonicalName":"Solanum lycopersicum lycopersicum","rank":"SUBSPECIES","status":"ACCEPTED","synonym":false,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"},{"key":11760453,"nameKey":97469847,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum
|
||||
lycopersicum","parentKey":2930137,"nubKey":11760453,"scientificName":"Solanum
|
||||
lycopersicum subsp. cerasiforme (Alef.) Voss","canonicalName":"Solanum lycopersicum
|
||||
cerasiforme","rank":"SUBSPECIES","status":"ACCEPTED","synonym":false,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"},{"key":7904703,"nameKey":10463761,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum
|
||||
lycopersicum","parentKey":2930137,"nubKey":7904703,"scientificName":"Solanum
|
||||
lycopersicum var. cerasiforme (Alef.) Voss","canonicalName":"Solanum lycopersicum
|
||||
cerasiforme","rank":"VARIETY","status":"ACCEPTED","synonym":false,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"},{"key":11014233,"nameKey":36721070,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum","parentKey":2928997,"nubKey":11014233,"scientificName":"Solanum
|
||||
lycopersicum var. piriforme (Alef.) Voss","canonicalName":"Solanum lycopersicum
|
||||
piriforme","rank":"VARIETY","status":"SYNONYM","synonym":true,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"},{"key":10798260,"nameKey":36720428,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum","parentKey":2928997,"nubKey":10798260,"scientificName":"Solanum
|
||||
lycopersicum var. ribisiodes Voss","canonicalName":"Solanum lycopersicum ribisiodes","rank":"VARIETY","status":"SYNONYM","synonym":true,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"},{"key":2930169,"nameKey":10463787,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum","parentKey":2928997,"nubKey":2930169,"scientificName":"Solanum
|
||||
lycopersicum var. esculentum (Mill.) Voss","canonicalName":"Solanum lycopersicum
|
||||
esculentum","rank":"VARIETY","status":"SYNONYM","synonym":true,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"},{"key":4274699,"nameKey":10463795,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum","parentKey":2928997,"nubKey":4274699,"scientificName":"Solanum
|
||||
lycopersicum var. lycopersicum","canonicalName":"Solanum lycopersicum lycopersicum","rank":"VARIETY","status":"SYNONYM","synonym":true,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"},{"key":8118741,"nameKey":10463759,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum","parentKey":2928997,"nubKey":8118741,"scientificName":"Solanum
|
||||
lycopersicum var. cerasiforme (Alef.) Fosberg","canonicalName":"Solanum lycopersicum
|
||||
cerasiforme","rank":"VARIETY","status":"SYNONYM","synonym":true,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"},{"key":2930179,"nameKey":10463798,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum","parentKey":2928997,"nubKey":2930179,"scientificName":"Solanum
|
||||
lycopersicum var. oviforme Voss","canonicalName":"Solanum lycopersicum oviforme","rank":"VARIETY","status":"SYNONYM","synonym":true,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"},{"key":2930165,"nameKey":10463770,"kingdom":"Plantae","phylum":"Tracheophyta","order":"Solanales","family":"Solanaceae","genus":"Solanum","species":"Solanum
|
||||
lycopersicum","kingdomKey":6,"phylumKey":7707728,"classKey":220,"orderKey":1176,"familyKey":7717,"genusKey":2928997,"speciesKey":2930137,"parent":"Solanum","parentKey":2928997,"nubKey":2930165,"scientificName":"Solanum
|
||||
lycopersicum var. cerasiforme (Dunal) D.M.Spooner, G.J.Anderson & R.K.Jansen","canonicalName":"Solanum
|
||||
lycopersicum cerasiforme","rank":"VARIETY","status":"SYNONYM","synonym":true,"higherClassificationMap":{"6":"Plantae","7707728":"Tracheophyta","220":"Magnoliopsida","1176":"Solanales","7717":"Solanaceae","2928997":"Solanum","2930137":"Solanum
|
||||
lycopersicum"},"class":"Magnoliopsida"}]'
|
||||
recorded_at: Sun, 14 Jan 2024 10:06:04 GMT
|
||||
recorded_with: VCR 6.2.0
|
||||
108
spec/cassettes/GbifService/_update_crop/gets_photos.yml
Normal file
108
spec/cassettes/GbifService/_update_crop/gets_photos.yml
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -114,6 +114,8 @@ RSpec.configure do |config|
|
||||
|
||||
# Prevent Poltergeist from fetching external URLs during feature tests
|
||||
config.before(:each, :js) do
|
||||
|
||||
# TODO: Why are we setting this page size then straight afterwards, maximising?
|
||||
width = 1280
|
||||
height = 1280
|
||||
Capybara.current_session.driver.browser.manage.window.resize_to(width, height)
|
||||
|
||||
72
spec/services/gbif_service_spec.rb
Normal file
72
spec/services/gbif_service_spec.rb
Normal file
@@ -0,0 +1,72 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe GbifService, :vcr, type: :service do
|
||||
let(:scientific_name_tomato) { create(:solanum_lycopersicum) }
|
||||
let(:tomato) { scientific_name_tomato.crop }
|
||||
|
||||
let(:gbif_service) { described_class.new }
|
||||
|
||||
# TODO: Find places where we should just use dependency injection to insert the cropbot user.
|
||||
before do
|
||||
# don't use 'let' for this -- we need to actually create it,
|
||||
# regardless of whether it's used.
|
||||
@cropbot = FactoryBot.create(:cropbot)
|
||||
end
|
||||
|
||||
describe "#fetch" do
|
||||
it "fetches a given key" do
|
||||
result = gbif_service.fetch(2_930_137)
|
||||
expect(result["key"]).to eq 2_930_137
|
||||
expect(result["family"]).to eq "Solanaceae"
|
||||
end
|
||||
end
|
||||
|
||||
describe "#suggest" do
|
||||
it "matches" do
|
||||
results = gbif_service.suggest(scientific_name_tomato.name)
|
||||
expect(results[0]["key"]).to eq 2_930_137
|
||||
expect(results[0]["family"]).to eq "Solanaceae"
|
||||
end
|
||||
end
|
||||
|
||||
describe "#import!"
|
||||
describe "#update_crop" do
|
||||
it "resolves scientific names" do
|
||||
gbif_service.update_crop(tomato)
|
||||
|
||||
scientific_name_tomato.reload
|
||||
expect(scientific_name_tomato.gbif_key).to eq 2_930_137
|
||||
end
|
||||
|
||||
it "resolves common names" do
|
||||
crop = create(:crop, name: "Habanero")
|
||||
|
||||
gbif_service.update_crop(crop)
|
||||
crop.reload
|
||||
|
||||
expect(crop.scientific_names.first.name).to eq "Capsicum chinense"
|
||||
end
|
||||
|
||||
it "gets photos" do
|
||||
scientific_name_tomato.update(gbif_key: "2930137")
|
||||
|
||||
gbif_service.update_crop(tomato)
|
||||
|
||||
tomato.reload
|
||||
expect(tomato.photos.count).to eq 3
|
||||
|
||||
photo = tomato.photos.order(:id)[0]
|
||||
expect(photo.fullsize_url).to eq "https://inaturalist-open-data.s3.amazonaws.com/photos/343874350/original.jpeg"
|
||||
expect(photo.thumbnail_url).to eq "https://api.gbif.org/v1/image/cache/200x/occurrence/4507688130/media/7bc2c1b87c7110b785674bfc198d891c"
|
||||
expect(photo.title).to eq "Photo by Ingeborg van Leeuwen via iNaturalist (Copyright Ingeborg van Leeuwen)"
|
||||
expect(photo.license_name).to eq "CC BY-NC 4.0"
|
||||
expect(photo.license_url).to eq "http://creativecommons.org/licenses/by-nc/4.0/"
|
||||
expect(photo.link_url).to eq "https://www.inaturalist.org/photos/343874350"
|
||||
expect(photo.source_id).to eq("4507688130")
|
||||
expect(photo.source).to eq("gbif")
|
||||
expect(photo.date_taken).to eq("2024-01-01T11:07:00.000+00:00")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -18,6 +18,14 @@
|
||||
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
||||
require 'simplecov'
|
||||
require 'percy/capybara'
|
||||
require 'vcr'
|
||||
|
||||
VCR.configure do |c|
|
||||
c.ignore_host "elasticsearch", "localhost"
|
||||
c.cassette_library_dir = 'spec/cassettes'
|
||||
c.hook_into :faraday
|
||||
c.configure_rspec_metadata!
|
||||
end
|
||||
|
||||
SimpleCov.start
|
||||
|
||||
|
||||
Reference in New Issue
Block a user