inat-get 0.8.0.11 → 0.8.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +6 -0
- data/bin/inat-get +1 -1
- data/inat-get.gemspec +6 -6
- data/lib/extra/enum.rb +4 -0
- data/lib/extra/period.rb +15 -0
- data/lib/inat/app/application.rb +4 -3
- data/lib/inat/app/config/messagelevel.rb +3 -1
- data/lib/inat/app/config/shiftage.rb +1 -1
- data/lib/inat/app/config/updatemode.rb +1 -1
- data/lib/inat/app/config.rb +6 -2
- data/lib/inat/app/globals.rb +6 -3
- data/lib/inat/app/info.rb +18 -13
- data/lib/inat/app/logging.rb +3 -3
- data/lib/inat/app/preamble.rb +1 -1
- data/lib/inat/app/status.rb +3 -9
- data/lib/inat/app/task/context.rb +9 -3
- data/lib/inat/app/task/dsl.rb +5 -3
- data/lib/inat/app/task.rb +2 -2
- data/lib/inat/data/api.rb +210 -181
- data/lib/inat/data/db.rb +9 -4
- data/lib/inat/data/ddl.rb +24 -5
- data/lib/inat/data/entity/comment.rb +8 -4
- data/lib/inat/data/entity/flag.rb +7 -3
- data/lib/inat/data/entity/identification.rb +9 -4
- data/lib/inat/data/entity/observation.rb +27 -14
- data/lib/inat/data/entity/observationphoto.rb +7 -3
- data/lib/inat/data/entity/observationsound.rb +6 -7
- data/lib/inat/data/entity/photo.rb +9 -3
- data/lib/inat/data/entity/place.rb +11 -2
- data/lib/inat/data/entity/project.rb +16 -10
- data/lib/inat/data/entity/projectadmin.rb +4 -4
- data/lib/inat/data/entity/projectobservationrule.rb +3 -4
- data/lib/inat/data/entity/request.rb +7 -3
- data/lib/inat/data/entity/sound.rb +7 -2
- data/lib/inat/data/entity/taxon.rb +11 -3
- data/lib/inat/data/entity/user.rb +7 -3
- data/lib/inat/data/entity/vote.rb +7 -3
- data/lib/inat/data/entity.rb +38 -24
- data/lib/inat/data/enums/conservationstatus.rb +3 -3
- data/lib/inat/data/enums/geoprivacy.rb +3 -1
- data/lib/inat/data/enums/iconictaxa.rb +1 -1
- data/lib/inat/data/enums/identificationcategory.rb +1 -1
- data/lib/inat/data/enums/licensecode.rb +5 -2
- data/lib/inat/data/enums/projectadminrole.rb +1 -1
- data/lib/inat/data/enums/projecttype.rb +5 -3
- data/lib/inat/data/enums/qualitygrade.rb +1 -1
- data/lib/inat/data/enums/rank.rb +1 -1
- data/lib/inat/data/model.rb +73 -24
- data/lib/inat/data/query.rb +14 -9
- data/lib/inat/data/sets/dataset.rb +10 -6
- data/lib/inat/data/sets/list.rb +8 -3
- data/lib/inat/data/sets/listers.rb +10 -4
- data/lib/inat/data/sets/wrappers.rb +111 -82
- data/lib/inat/data/types/location.rb +17 -8
- data/lib/inat/data/types/std.rb +171 -176
- data/lib/inat/report/report_dsl.rb +205 -0
- data/lib/inat/report/table.rb +11 -3
- data/lib/inat/utils/deep.rb +25 -19
- metadata +6 -5
- data/lib/inat/data/cache.rb +0 -9
data/lib/inat/data/api.rb
CHANGED
@@ -1,218 +1,247 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
3
|
+
require "json"
|
4
|
+
require "uri"
|
5
|
+
require "net/http"
|
6
6
|
|
7
|
-
require_relative
|
8
|
-
require_relative
|
7
|
+
require_relative "../app/globals"
|
8
|
+
require_relative "../app/status"
|
9
9
|
|
10
|
-
|
10
|
+
class INat::API
|
11
11
|
|
12
12
|
RECORDS_LIMIT = 200
|
13
13
|
FREQUENCY_LIMIT = 1.0
|
14
14
|
|
15
|
-
|
15
|
+
using INat::Types::Std
|
16
16
|
|
17
|
-
|
17
|
+
include INat::App::Logger::DSL
|
18
|
+
include INat::App
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
def initialize
|
21
|
+
@mutex = Mutex::new
|
22
|
+
end
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
def get path, part, limit, *ids
|
25
|
+
return [] if ids.empty?
|
26
|
+
if ids.size > limit
|
27
|
+
rest = ids.dup
|
28
|
+
head = rest.shift limit
|
29
|
+
return get(path, *head) + get(path, *rest)
|
30
|
+
end
|
31
|
+
result = []
|
32
|
+
Status::status "[api]", "#{path} ..."
|
33
|
+
@mutex.synchronize do
|
34
|
+
now = Time::new
|
35
|
+
if @last_call && now - @last_call < FREQUENCY_LIMIT
|
36
|
+
sleep FREQUENCY_LIMIT - (now - @last_call)
|
29
37
|
end
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
data = JSON.parse(response.body)
|
72
|
-
result = data['results']
|
73
|
-
total = data['total_results']
|
74
|
-
paged = data['per_page']
|
75
|
-
time_diff = Time::new - last_time
|
76
|
-
debug "GET OK: total = #{ total } paged = #{ paged } time = #{ time_diff } "
|
77
|
-
# Status::status 'GET', uri.to_s + ' DONE'
|
78
|
-
else
|
79
|
-
error "Bad response om #{ uri.path }#{ uri.query && !uri.query.empty? && '?' + uri.query || '' }: #{ response.inspect }!"
|
80
|
-
result = [ { 'id' => ids.first } ]
|
81
|
-
# Status::status 'GET', uri.to_s + ' ERROR'
|
82
|
-
end
|
83
|
-
end
|
84
|
-
answered = true
|
85
|
-
rescue Exception
|
86
|
-
if answer_count > 0
|
87
|
-
answer_count -= 1
|
88
|
-
answered = false
|
89
|
-
error "Error in HTTP request: #{ $!.inspect }, retry: #{ answer_count }."
|
90
|
-
# Status::status "Error in HTTP request: #{ $!.inspect }, retry: #{ answer_count }."
|
91
|
-
sleep 2.0
|
38
|
+
case part
|
39
|
+
when :query
|
40
|
+
url = G.config[:api][:root] + path.to_s + "?id=#{ids.join(",")}"
|
41
|
+
url += "&per_page=#{limit}"
|
42
|
+
locale = G.config[:api][:locale]
|
43
|
+
url += "&locale=#{locale}" if locale
|
44
|
+
preferred_place_id = G.config[:api][:preferred_place_id]
|
45
|
+
url += "&preferred_place_id=#{preferred_place_id}" if preferred_place_id
|
46
|
+
when :path
|
47
|
+
url = G.config[:api][:root] + path.to_s + "/#{ids.join(",")}"
|
48
|
+
else
|
49
|
+
raise ArgumentError, "Invalid 'part' argument: #{part.inspect}!", caller
|
50
|
+
end
|
51
|
+
uri = URI(url)
|
52
|
+
info "GET: URI = #{uri.inspect}"
|
53
|
+
# Status::status 'GET', uri.to_s
|
54
|
+
https = uri.scheme == "https"
|
55
|
+
open_timeout = G.config[:api][:open_timeout]
|
56
|
+
read_timeout = G.config[:api][:read_timeout]
|
57
|
+
http_options = {
|
58
|
+
use_ssl: https,
|
59
|
+
}
|
60
|
+
http_options[:open_timeout] = open_timeout if open_timeout
|
61
|
+
http_options[:read_timeout] = read_timeout if read_timeout
|
62
|
+
answered = false
|
63
|
+
answer_count = 50
|
64
|
+
last_time = Time::new
|
65
|
+
until answered
|
66
|
+
begin
|
67
|
+
Net::HTTP::start uri.host, uri.port, **http_options do |http|
|
68
|
+
request = Net::HTTP::Get::new uri
|
69
|
+
request["User-Agent"] = G.config[:api][:user_agent] || "INat::Get // Unknown Instance"
|
70
|
+
response = http.request request
|
71
|
+
if Net::HTTPSuccess === response
|
72
|
+
data = JSON.parse(response.body)
|
73
|
+
result = data["results"]
|
74
|
+
total = data["total_results"]
|
75
|
+
paged = data["per_page"]
|
76
|
+
time_diff = Time::new - last_time
|
77
|
+
debug "GET OK: total = #{total} paged = #{paged} time = #{time_diff} "
|
78
|
+
# Status::status 'GET', uri.to_s + ' DONE'
|
92
79
|
else
|
93
|
-
|
94
|
-
|
95
|
-
# Status::status
|
80
|
+
error "Bad response om #{uri.path}#{uri.query && !uri.query.empty? && "?" + uri.query || ""}: #{response.inspect}!"
|
81
|
+
result = [{ "id" => ids.first }]
|
82
|
+
# Status::status 'GET', uri.to_s + ' ERROR'
|
96
83
|
end
|
97
84
|
end
|
85
|
+
answered = true
|
86
|
+
rescue Exception
|
87
|
+
if answer_count > 0
|
88
|
+
answer_count -= 1
|
89
|
+
answered = false
|
90
|
+
error "Error in HTTP request: #{$!.inspect}, retry: #{answer_count}."
|
91
|
+
# Status::status "Error in HTTP request: #{ $!.inspect }, retry: #{ answer_count }."
|
92
|
+
sleep 2.0
|
93
|
+
else
|
94
|
+
answered = true
|
95
|
+
error "Error in HTTP request: #{$!.inspect}!"
|
96
|
+
# Status::status "Error in HTTP request: #{ $!.inspect }!"
|
97
|
+
end
|
98
98
|
end
|
99
|
-
@last_call = Time::new
|
100
99
|
end
|
101
|
-
|
102
|
-
result
|
100
|
+
@last_call = Time::new
|
103
101
|
end
|
102
|
+
Status::status "[api]", "#{path} DONE"
|
103
|
+
result
|
104
|
+
end
|
104
105
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
end
|
115
|
-
query << query_param
|
116
|
-
end
|
117
|
-
locale = G.config[:api][:locale]
|
118
|
-
query << "locale=#{ locale }" if locale
|
119
|
-
preferred_place_id = G.config[:api][:preferred_place_id]
|
120
|
-
query << "preferred_place_id=#{ preferred_place_id }" if preferred_place_id
|
121
|
-
if !query.empty?
|
122
|
-
url += "?" + query.join('&')
|
106
|
+
private def make_url path, **params
|
107
|
+
url = G.config[:api][:root] + path.to_s
|
108
|
+
query = []
|
109
|
+
params.each do |key, value|
|
110
|
+
query_param = "#{key}="
|
111
|
+
if Array === value
|
112
|
+
query_param += value.map(&:to_query).join(",")
|
113
|
+
else
|
114
|
+
query_param += value.to_query
|
123
115
|
end
|
124
|
-
|
116
|
+
query << query_param
|
117
|
+
end
|
118
|
+
locale = G.config[:api][:locale]
|
119
|
+
query << "locale=#{locale}" if locale
|
120
|
+
preferred_place_id = G.config[:api][:preferred_place_id]
|
121
|
+
query << "preferred_place_id=#{preferred_place_id}" if preferred_place_id
|
122
|
+
if !query.empty?
|
123
|
+
url += "?" + query.join("&")
|
125
124
|
end
|
125
|
+
url
|
126
|
+
end
|
126
127
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
end
|
175
|
-
else
|
176
|
-
raise RuntimeError, "Invalid response: #{response.inspect}"
|
128
|
+
def query path, first_only: false, **params, &block
|
129
|
+
Status::status "[api]", "#{path} ..."
|
130
|
+
para = params.dup
|
131
|
+
para.delete_if { |key, _| key.intern == :page }
|
132
|
+
para[:per_page] = RECORDS_LIMIT
|
133
|
+
para[:order_by] = :id
|
134
|
+
para[:order] = :asc
|
135
|
+
result = []
|
136
|
+
rest = nil
|
137
|
+
total = 0
|
138
|
+
@mutex.synchronize do
|
139
|
+
now = Time::new
|
140
|
+
if @last_call && now - @last_call < FREQUENCY_LIMIT
|
141
|
+
sleep FREQUENCY_LIMIT - (now - @last_call)
|
142
|
+
end
|
143
|
+
url = make_url path, **para
|
144
|
+
uri = URI(url)
|
145
|
+
info "QUERY: URI = #{uri.inspect}"
|
146
|
+
# Status::status 'QUERY', uri.to_s
|
147
|
+
https = uri.scheme == "https"
|
148
|
+
open_timeout = G.config[:api][:open_timeout]
|
149
|
+
read_timeout = G.config[:api][:read_timeout]
|
150
|
+
http_options = {
|
151
|
+
use_ssl: https,
|
152
|
+
}
|
153
|
+
http_options[:open_timeout] = open_timeout if open_timeout
|
154
|
+
http_options[:read_timeout] = read_timeout if read_timeout
|
155
|
+
answered = false
|
156
|
+
answer_count = 50
|
157
|
+
last_time = Time::new
|
158
|
+
until answered
|
159
|
+
begin
|
160
|
+
Net::HTTP::start uri.host, uri.port, **http_options do |http|
|
161
|
+
request = Net::HTTP::Get::new uri
|
162
|
+
request["User-Agent"] = G.config[:api][:user_agent] || "INat::Get // Unknown Instance"
|
163
|
+
response = http.request request
|
164
|
+
if Net::HTTPSuccess === response
|
165
|
+
data = JSON.parse(response.body)
|
166
|
+
result = data["results"]
|
167
|
+
total = data["total_results"]
|
168
|
+
paged = data["per_page"]
|
169
|
+
time_diff = Time::new - last_time
|
170
|
+
debug "QUERY OK: total = #{total} paged = #{paged} time = #{time_diff} "
|
171
|
+
if total > paged && !first_only
|
172
|
+
max = result.map { |o| o["id"] }.max
|
173
|
+
rest = para
|
174
|
+
rest[:id_above] = max
|
177
175
|
end
|
178
|
-
end
|
179
|
-
answered = true
|
180
|
-
rescue Exception
|
181
|
-
if answer_count > 0
|
182
|
-
answer_count -= 1
|
183
|
-
answered = false
|
184
|
-
error "Error in HTTP request: #{ $!.inspect }, retry: #{ answer_count }."
|
185
|
-
# Status::status "Error in HTTP request: #{ $!.inspect }, retry: #{ answer_count }."
|
186
|
-
sleep 2.0
|
187
176
|
else
|
188
|
-
raise
|
177
|
+
raise RuntimeError, "Invalid response: #{response.inspect}"
|
189
178
|
end
|
190
179
|
end
|
180
|
+
answered = true
|
181
|
+
rescue Exception
|
182
|
+
if answer_count > 0
|
183
|
+
answer_count -= 1
|
184
|
+
answered = false
|
185
|
+
error "Error in HTTP request: #{$!.inspect}, retry: #{answer_count}."
|
186
|
+
# Status::status "Error in HTTP request: #{ $!.inspect }, retry: #{ answer_count }."
|
187
|
+
sleep 2.0
|
188
|
+
else
|
189
|
+
raise
|
190
|
+
end
|
191
191
|
end
|
192
|
-
@last_call = Time::new
|
193
192
|
end
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
rr
|
202
|
-
rr
|
203
|
-
else
|
204
|
-
result += query(path, **rest) if rest
|
205
|
-
result
|
193
|
+
@last_call = Time::new
|
194
|
+
end
|
195
|
+
Status::status "[api]", "#{path} DONE"
|
196
|
+
# TODO: переделать рекурсию в итерации
|
197
|
+
if block_given?
|
198
|
+
rr = []
|
199
|
+
result.each do |js_object|
|
200
|
+
rr << yield(js_object, total)
|
206
201
|
end
|
202
|
+
rr += query(path, **rest, &block) if rest
|
203
|
+
rr
|
204
|
+
else
|
205
|
+
result += query(path, **rest) if rest
|
206
|
+
result
|
207
207
|
end
|
208
|
+
end
|
208
209
|
|
210
|
+
# @api private
|
211
|
+
def load_file filename
|
212
|
+
data = JSON.parse File.read(filename)
|
213
|
+
data["results"]
|
214
|
+
end
|
215
|
+
|
216
|
+
class << self
|
217
|
+
|
218
|
+
private :new
|
219
|
+
|
220
|
+
# @api application
|
221
|
+
# Singleton instance
|
222
|
+
# @return [API]
|
223
|
+
def instance
|
224
|
+
@instance ||= new
|
225
|
+
@instance
|
226
|
+
end
|
227
|
+
|
228
|
+
# @api application
|
229
|
+
# Get one or more objects by id
|
230
|
+
# @return [Array<Hash>]
|
231
|
+
def get path, part, limit, *ids
|
232
|
+
instance.get path, part, limit, *ids
|
233
|
+
end
|
234
|
+
|
235
|
+
# @api application
|
236
|
+
def query path, first_only: false, **params, &block
|
237
|
+
instance.query path, first_only: first_only, **params, &block
|
238
|
+
end
|
239
|
+
|
240
|
+
# @api private
|
209
241
|
def load_file filename
|
210
|
-
|
211
|
-
data['results']
|
242
|
+
instance.load_file filename
|
212
243
|
end
|
213
244
|
|
214
245
|
end
|
215
246
|
|
216
247
|
end
|
217
|
-
|
218
|
-
API::init
|
data/lib/inat/data/db.rb
CHANGED
@@ -6,10 +6,13 @@ require 'sqlite3'
|
|
6
6
|
require_relative '../app/globals'
|
7
7
|
require_relative 'ddl'
|
8
8
|
|
9
|
-
class DB
|
9
|
+
class INat::DB
|
10
10
|
|
11
|
-
include
|
11
|
+
include INat
|
12
|
+
include INat::App
|
13
|
+
include INat::App::Logger::DSL
|
12
14
|
|
15
|
+
# @private
|
13
16
|
def self.get_finalizer *dbs
|
14
17
|
proc do
|
15
18
|
dbs.each { |db| db.close }
|
@@ -27,7 +30,7 @@ class DB
|
|
27
30
|
@data.auto_vacuum = 1
|
28
31
|
@data.results_as_hash = true
|
29
32
|
@data.foreign_keys = true
|
30
|
-
@data.execute_batch DDL.DDL
|
33
|
+
@data.execute_batch Data::DDL.DDL
|
31
34
|
end
|
32
35
|
ObjectSpace.define_finalizer self, self.class.get_finalizer(@data)
|
33
36
|
end
|
@@ -65,8 +68,10 @@ class DB
|
|
65
68
|
|
66
69
|
class << self
|
67
70
|
|
71
|
+
private :new
|
72
|
+
|
68
73
|
def instance
|
69
|
-
@instance ||=
|
74
|
+
@instance ||= new
|
70
75
|
@instance
|
71
76
|
end
|
72
77
|
|
data/lib/inat/data/ddl.rb
CHANGED
@@ -1,16 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
# TODO: подумать и заменить константой, возможно
|
4
|
+
|
5
|
+
class INat::Data::DDL
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@models = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def <<(model)
|
12
|
+
@models << model
|
13
|
+
end
|
14
|
+
|
15
|
+
def DDL
|
16
|
+
@models.map(&:DDL).join("\n")
|
17
|
+
end
|
4
18
|
|
5
19
|
class << self
|
6
20
|
|
7
|
-
|
8
|
-
|
9
|
-
|
21
|
+
private :new
|
22
|
+
|
23
|
+
def instance
|
24
|
+
@instance ||= new
|
25
|
+
end
|
26
|
+
|
27
|
+
def <<(model)
|
28
|
+
instance << model
|
10
29
|
end
|
11
30
|
|
12
31
|
def DDL
|
13
|
-
|
32
|
+
instance.DDL
|
14
33
|
end
|
15
34
|
|
16
35
|
end
|
@@ -4,11 +4,15 @@ require_relative '../types/std'
|
|
4
4
|
require_relative '../types/extras'
|
5
5
|
require_relative '../entity'
|
6
6
|
|
7
|
-
|
8
|
-
autoload :
|
9
|
-
autoload :
|
7
|
+
module INat::Entity
|
8
|
+
autoload :Observation, 'inat/data/entity/observation'
|
9
|
+
autoload :User, 'inat/data/entity/user'
|
10
|
+
autoload :Flag, 'inat/data/entity/flag'
|
11
|
+
end
|
12
|
+
|
13
|
+
class INat::Entity::Comment < INat::Data::Entity
|
10
14
|
|
11
|
-
|
15
|
+
include INat::Entity
|
12
16
|
|
13
17
|
table :comments
|
14
18
|
|
@@ -4,10 +4,14 @@ require_relative '../types/std'
|
|
4
4
|
require_relative '../types/extras'
|
5
5
|
require_relative '../entity'
|
6
6
|
|
7
|
-
|
8
|
-
autoload :
|
7
|
+
module INat::Entity
|
8
|
+
autoload :Observation, 'inat/data/entity/observation'
|
9
|
+
autoload :User, 'inat/data/entity/user'
|
10
|
+
end
|
11
|
+
|
12
|
+
class INat::Entity::Flag < INat::Data::Entity
|
9
13
|
|
10
|
-
|
14
|
+
include INat::Entity
|
11
15
|
|
12
16
|
table :flags
|
13
17
|
|
@@ -8,11 +8,16 @@ require_relative '../entity'
|
|
8
8
|
|
9
9
|
require_relative '../enums/identificationcategory'
|
10
10
|
|
11
|
-
|
12
|
-
autoload :
|
13
|
-
autoload :
|
11
|
+
module INat::Entity
|
12
|
+
autoload :Observation, 'inat/data/entity/observation'
|
13
|
+
autoload :Taxon, 'inat/data/entity/taxon'
|
14
|
+
autoload :Flag, 'inat/data/entity/flag'
|
15
|
+
end
|
16
|
+
|
17
|
+
class INat::Entity::Identification < INat::Data::Entity
|
14
18
|
|
15
|
-
|
19
|
+
include INat::Data::Types
|
20
|
+
include INat::Entity
|
16
21
|
|
17
22
|
api_path :identifications
|
18
23
|
api_part :query
|
@@ -8,20 +8,25 @@ require_relative '../enums/qualitygrade'
|
|
8
8
|
require_relative '../enums/licensecode'
|
9
9
|
require_relative '../enums/geoprivacy'
|
10
10
|
|
11
|
-
|
12
|
-
autoload :
|
13
|
-
autoload :
|
14
|
-
autoload :
|
15
|
-
autoload :
|
16
|
-
autoload :
|
17
|
-
autoload :
|
18
|
-
autoload :
|
19
|
-
autoload :
|
20
|
-
autoload :
|
21
|
-
autoload :
|
22
|
-
autoload :
|
23
|
-
|
24
|
-
|
11
|
+
module INat::Entity
|
12
|
+
autoload :Taxon, 'inat/data/entity/taxon'
|
13
|
+
autoload :User, 'inat/data/entity/user'
|
14
|
+
autoload :Flag, 'inat/data/entity/flag'
|
15
|
+
autoload :ObservationSound, 'inat/data/entity/observationsound'
|
16
|
+
autoload :Sound, 'inat/data/entity/sound'
|
17
|
+
autoload :ObservationPhoto, 'inat/data/entity/observationphoto'
|
18
|
+
autoload :Photo, 'inat/data/entity/photo'
|
19
|
+
autoload :Place, 'inat/data/entity/place'
|
20
|
+
autoload :Project, 'inat/data/entity/project'
|
21
|
+
autoload :Comment, 'inat/data/entity/comment'
|
22
|
+
autoload :Identification, 'inat/data/entity/identification'
|
23
|
+
autoload :Vote, 'inat/data/entity/vote'
|
24
|
+
end
|
25
|
+
|
26
|
+
class INat::Entity::Observation < INat::Data::Entity
|
27
|
+
|
28
|
+
include INat::Data::Types
|
29
|
+
include INat::Entity
|
25
30
|
|
26
31
|
api_path :observations
|
27
32
|
api_part :query
|
@@ -29,6 +34,8 @@ class Observation < Entity
|
|
29
34
|
|
30
35
|
table :observations
|
31
36
|
|
37
|
+
# @!group Fields
|
38
|
+
|
32
39
|
field :quality_grade, type: QualityGrade, required: true, index: true
|
33
40
|
field :uuid, type: UUID, unique: true
|
34
41
|
field :species_guess, type: String
|
@@ -101,6 +108,10 @@ class Observation < Entity
|
|
101
108
|
field :photo_licensed, type: Boolean, index: true
|
102
109
|
field :rank, type: Rank, index: true
|
103
110
|
|
111
|
+
# @!endgroup
|
112
|
+
|
113
|
+
# @!group Callbacks
|
114
|
+
|
104
115
|
def post_update
|
105
116
|
t = self.taxon
|
106
117
|
if t
|
@@ -116,6 +127,8 @@ class Observation < Entity
|
|
116
127
|
end
|
117
128
|
end
|
118
129
|
|
130
|
+
# @!endgroup
|
131
|
+
|
119
132
|
def normalized_taxon ranks
|
120
133
|
ranks = (ranks .. ranks) if Rank === ranks
|
121
134
|
raise TypeError, "Invalid type for ranks: #{ ranks.inspect }!", caller unless Range === ranks
|
@@ -9,10 +9,14 @@ require_relative '../entity'
|
|
9
9
|
# require_relative 'observation'
|
10
10
|
# require_relative 'photo'
|
11
11
|
|
12
|
-
|
13
|
-
autoload :
|
12
|
+
module INat::Entity
|
13
|
+
autoload :Observation, 'inat/data/entity/observation'
|
14
|
+
autoload :Photo, 'inat/data/entity/photo'
|
15
|
+
end
|
16
|
+
|
17
|
+
class INat::Entity::ObservationPhoto < INat::Data::Entity
|
14
18
|
|
15
|
-
|
19
|
+
include INat::Entity
|
16
20
|
|
17
21
|
table :observation_photos
|
18
22
|
|