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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -0
  3. data/bin/inat-get +1 -1
  4. data/inat-get.gemspec +6 -6
  5. data/lib/extra/enum.rb +4 -0
  6. data/lib/extra/period.rb +15 -0
  7. data/lib/inat/app/application.rb +4 -3
  8. data/lib/inat/app/config/messagelevel.rb +3 -1
  9. data/lib/inat/app/config/shiftage.rb +1 -1
  10. data/lib/inat/app/config/updatemode.rb +1 -1
  11. data/lib/inat/app/config.rb +6 -2
  12. data/lib/inat/app/globals.rb +6 -3
  13. data/lib/inat/app/info.rb +18 -13
  14. data/lib/inat/app/logging.rb +3 -3
  15. data/lib/inat/app/preamble.rb +1 -1
  16. data/lib/inat/app/status.rb +3 -9
  17. data/lib/inat/app/task/context.rb +9 -3
  18. data/lib/inat/app/task/dsl.rb +5 -3
  19. data/lib/inat/app/task.rb +2 -2
  20. data/lib/inat/data/api.rb +210 -181
  21. data/lib/inat/data/db.rb +9 -4
  22. data/lib/inat/data/ddl.rb +24 -5
  23. data/lib/inat/data/entity/comment.rb +8 -4
  24. data/lib/inat/data/entity/flag.rb +7 -3
  25. data/lib/inat/data/entity/identification.rb +9 -4
  26. data/lib/inat/data/entity/observation.rb +27 -14
  27. data/lib/inat/data/entity/observationphoto.rb +7 -3
  28. data/lib/inat/data/entity/observationsound.rb +6 -7
  29. data/lib/inat/data/entity/photo.rb +9 -3
  30. data/lib/inat/data/entity/place.rb +11 -2
  31. data/lib/inat/data/entity/project.rb +16 -10
  32. data/lib/inat/data/entity/projectadmin.rb +4 -4
  33. data/lib/inat/data/entity/projectobservationrule.rb +3 -4
  34. data/lib/inat/data/entity/request.rb +7 -3
  35. data/lib/inat/data/entity/sound.rb +7 -2
  36. data/lib/inat/data/entity/taxon.rb +11 -3
  37. data/lib/inat/data/entity/user.rb +7 -3
  38. data/lib/inat/data/entity/vote.rb +7 -3
  39. data/lib/inat/data/entity.rb +38 -24
  40. data/lib/inat/data/enums/conservationstatus.rb +3 -3
  41. data/lib/inat/data/enums/geoprivacy.rb +3 -1
  42. data/lib/inat/data/enums/iconictaxa.rb +1 -1
  43. data/lib/inat/data/enums/identificationcategory.rb +1 -1
  44. data/lib/inat/data/enums/licensecode.rb +5 -2
  45. data/lib/inat/data/enums/projectadminrole.rb +1 -1
  46. data/lib/inat/data/enums/projecttype.rb +5 -3
  47. data/lib/inat/data/enums/qualitygrade.rb +1 -1
  48. data/lib/inat/data/enums/rank.rb +1 -1
  49. data/lib/inat/data/model.rb +73 -24
  50. data/lib/inat/data/query.rb +14 -9
  51. data/lib/inat/data/sets/dataset.rb +10 -6
  52. data/lib/inat/data/sets/list.rb +8 -3
  53. data/lib/inat/data/sets/listers.rb +10 -4
  54. data/lib/inat/data/sets/wrappers.rb +111 -82
  55. data/lib/inat/data/types/location.rb +17 -8
  56. data/lib/inat/data/types/std.rb +171 -176
  57. data/lib/inat/report/report_dsl.rb +205 -0
  58. data/lib/inat/report/table.rb +11 -3
  59. data/lib/inat/utils/deep.rb +25 -19
  60. metadata +6 -5
  61. 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 'json'
4
- require 'uri'
5
- require 'net/http'
3
+ require "json"
4
+ require "uri"
5
+ require "net/http"
6
6
 
7
- require_relative '../app/globals'
8
- require_relative '../app/status'
7
+ require_relative "../app/globals"
8
+ require_relative "../app/status"
9
9
 
10
- module API
10
+ class INat::API
11
11
 
12
12
  RECORDS_LIMIT = 200
13
13
  FREQUENCY_LIMIT = 1.0
14
14
 
15
- class << self
15
+ using INat::Types::Std
16
16
 
17
- include LogDSL
17
+ include INat::App::Logger::DSL
18
+ include INat::App
18
19
 
19
- def init
20
- @mutex = Mutex::new
21
- end
20
+ def initialize
21
+ @mutex = Mutex::new
22
+ end
22
23
 
23
- def get path, part, limit, *ids
24
- return [] if ids.empty?
25
- if ids.size > limit
26
- rest = ids.dup
27
- head = rest.shift limit
28
- return get(path, *head) + get(path, *rest)
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
- result = []
31
- Status::status '[api]', "#{ path } ..."
32
- @mutex.synchronize do
33
- now = Time::new
34
- if @last_call && now - @last_call < FREQUENCY_LIMIT
35
- sleep FREQUENCY_LIMIT - (now - @last_call)
36
- end
37
- case part
38
- when :query
39
- url = G.config[:api][:root] + path.to_s + "?id=#{ ids.join(',') }"
40
- url += "&per_page=#{ limit }"
41
- locale = G.config[:api][:locale]
42
- url += "&locale=#{ locale }" if locale
43
- preferred_place_id = G.config[:api][:preferred_place_id]
44
- url += "&preferred_place_id=#{ preferred_place_id }" if preferred_place_id
45
- when :path
46
- url = G.config[:api][:root] + path.to_s + "/#{ ids.join(',') }"
47
- else
48
- raise ArgumentError, "Invalid 'part' argument: #{ part.inspect }!", caller
49
- end
50
- uri = URI(url)
51
- info "GET: URI = #{ uri.inspect }"
52
- # Status::status 'GET', uri.to_s
53
- https = uri.scheme == 'https'
54
- open_timeout = G.config[:api][:open_timeout]
55
- read_timeout = G.config[:api][:read_timeout]
56
- http_options = {
57
- use_ssl: https
58
- }
59
- http_options[:open_timeout] = open_timeout if open_timeout
60
- http_options[:read_timeout] = read_timeout if read_timeout
61
- answered = false
62
- answer_count = 50
63
- last_time = Time::new
64
- until answered
65
- begin
66
- Net::HTTP::start uri.host, uri.port, **http_options do |http|
67
- request = Net::HTTP::Get::new uri
68
- request['User-Agent'] = G.config[:api][:user_agent] || "INat::Get // Unknown Instance"
69
- response = http.request request
70
- if Net::HTTPSuccess === response
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
- answered = true
94
- error "Error in HTTP request: #{ $!.inspect }!"
95
- # Status::status "Error in HTTP request: #{ $!.inspect }!"
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
- Status::status '[api]', "#{ path } DONE"
102
- result
100
+ @last_call = Time::new
103
101
  end
102
+ Status::status "[api]", "#{path} DONE"
103
+ result
104
+ end
104
105
 
105
- private def make_url path, **params
106
- url = G.config[:api][:root] + path.to_s
107
- query = []
108
- params.each do |key, value|
109
- query_param = "#{ key }="
110
- if Array === value
111
- query_param += value.map(&:to_query).join(',')
112
- else
113
- query_param += value.to_query
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
- url
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
- def query path, first_only: false, **params, &block
128
- Status::status '[api]', "#{ path } ..."
129
- para = params.dup
130
- para.delete_if { |key, _| key.intern == :page }
131
- para[:per_page] = RECORDS_LIMIT
132
- para[:order_by] = :id
133
- para[:order] = :asc
134
- result = []
135
- rest = nil
136
- total = 0
137
- @mutex.synchronize do
138
- now = Time::new
139
- if @last_call && now - @last_call < FREQUENCY_LIMIT
140
- sleep FREQUENCY_LIMIT - (now - @last_call)
141
- end
142
- url = make_url path, **para
143
- uri = URI(url)
144
- info "QUERY: URI = #{ uri.inspect }"
145
- # Status::status 'QUERY', uri.to_s
146
- https = uri.scheme == 'https'
147
- open_timeout = G.config[:api][:open_timeout]
148
- read_timeout = G.config[:api][:read_timeout]
149
- http_options = {
150
- use_ssl: https
151
- }
152
- http_options[:open_timeout] = open_timeout if open_timeout
153
- http_options[:read_timeout] = read_timeout if read_timeout
154
- answered = false
155
- answer_count = 50
156
- last_time = Time::new
157
- until answered
158
- begin
159
- Net::HTTP::start uri.host, uri.port, **http_options do |http|
160
- request = Net::HTTP::Get::new uri
161
- request["User-Agent"] = G.config[:api][:user_agent] || "INat::Get // Unknown Instance"
162
- response = http.request request
163
- if Net::HTTPSuccess === response
164
- data = JSON.parse(response.body)
165
- result = data["results"]
166
- total = data["total_results"]
167
- paged = data["per_page"]
168
- time_diff = Time::new - last_time
169
- debug "QUERY OK: total = #{ total } paged = #{ paged } time = #{ time_diff } "
170
- if total > paged && !first_only
171
- max = result.map { |o| o["id"] }.max
172
- rest = para
173
- rest[:id_above] = max
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
- Status::status '[api]', "#{ path } DONE"
195
- # TODO: переделать рекурсию в итерации
196
- if block_given?
197
- rr = []
198
- result.each do |js_object|
199
- rr << yield(js_object, total)
200
- end
201
- rr += query(path, **rest, &block) if rest
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
- data = JSON.parse File.read(filename)
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 LogDSL
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 ||= DB::new
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
- module DDL
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
- def << model
8
- @models ||= []
9
- @models << model
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
- @models.map(&:DDL).join("\n")
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
- autoload :Observation, 'inat/data/entity/observation'
8
- autoload :User, 'inat/data/entity/user'
9
- autoload :Flag, 'inat/data/entity/flag'
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
- class Comment < Entity
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
- autoload :Observation, 'inat/data/entity/observation'
8
- autoload :User, 'inat/data/entity/user'
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
- class Flag < Entity
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
- autoload :Observation, 'inat/data/entity/observation'
12
- autoload :Taxon, 'inat/data/entity/taxon'
13
- autoload :Flag, 'inat/data/entity/flag'
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
- class Identification < Entity
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
- autoload :Taxon, 'inat/data/entity/taxon'
12
- autoload :User, 'inat/data/entity/user'
13
- autoload :Flag, 'inat/data/entity/flag'
14
- autoload :ObservationSound, 'inat/data/entity/observationsound'
15
- autoload :Sound, 'inat/data/entity/sound'
16
- autoload :ObservationPhoto, 'inat/data/entity/observationphoto'
17
- autoload :Photo, 'inat/data/entity/photo'
18
- autoload :Place, 'inat/data/entity/place'
19
- autoload :Project, 'inat/data/entity/project'
20
- autoload :Comment, 'inat/data/entity/comment'
21
- autoload :Identification, 'inat/data/entity/identification'
22
- autoload :Vote, 'inat/data/entity/vote'
23
-
24
- class Observation < Entity
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
- autoload :Observation, 'inat/data/entity/observation'
13
- autoload :Photo, 'inat/data/entity/photo'
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
- class ObservationPhoto < Entity
19
+ include INat::Entity
16
20
 
17
21
  table :observation_photos
18
22