inat-get 0.8.0.12 → 0.8.0.14

Sign up to get free protection for your applications and to get access to all the features.
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 +8 -4
  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 +6 -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 +9 -5
  52. data/lib/inat/data/sets/list.rb +8 -3
  53. data/lib/inat/data/sets/listers.rb +10 -5
  54. data/lib/inat/data/sets/wrappers.rb +79 -75
  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 +18 -12
  58. data/lib/inat/report/table.rb +7 -3
  59. data/lib/inat/utils/deep.rb +25 -19
  60. metadata +5 -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