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/model.rb
CHANGED
@@ -3,12 +3,17 @@
|
|
3
3
|
require_relative '../app/globals'
|
4
4
|
require_relative 'types/std'
|
5
5
|
|
6
|
-
|
6
|
+
module INat::Data
|
7
|
+
autoload :Entity, 'inat/data/entity'
|
8
|
+
end
|
9
|
+
|
10
|
+
module INat::Data; end
|
7
11
|
|
8
|
-
class Model
|
12
|
+
class INat::Data::Model
|
9
13
|
|
10
|
-
include
|
14
|
+
include INat::App::Logger::DSL
|
11
15
|
|
16
|
+
# @private
|
12
17
|
class Field
|
13
18
|
|
14
19
|
attr_reader :model, :name, :type, :id_field
|
@@ -42,7 +47,10 @@ class Model
|
|
42
47
|
|
43
48
|
end
|
44
49
|
|
45
|
-
|
50
|
+
# @private
|
51
|
+
class ScalarField < Field
|
52
|
+
|
53
|
+
using INat::Types::Std
|
46
54
|
|
47
55
|
attr_reader :index, :unique, :primary_key
|
48
56
|
|
@@ -51,7 +59,7 @@ class Model
|
|
51
59
|
end
|
52
60
|
|
53
61
|
def initialize model, name, type, id_field, required, index, unique, primary_key
|
54
|
-
if Class === type && Entity > type && id_field == nil
|
62
|
+
if Class === type && INat::Data::Entity > type && id_field == nil
|
55
63
|
id_field = "#{ name }_id".intern
|
56
64
|
end
|
57
65
|
super model, name, type, id_field
|
@@ -74,7 +82,7 @@ class Model
|
|
74
82
|
md.define_method "#{ ni }=" do |value|
|
75
83
|
prevalue = instance_variable_get "@#{ ni }"
|
76
84
|
if prevalue != value
|
77
|
-
debug "ASS: #{ self.id }: #{ ni } = #{ prevalue.inspect } <=> #{ value.inspect }" if prevalue != nil && self.class.
|
85
|
+
debug "ASS: #{ self.id }: #{ ni } = #{ prevalue.inspect } <=> #{ value.inspect }" if prevalue != nil && self.class.short_name == 'Taxon'
|
78
86
|
instance_variable_set "@#{ ni }", value
|
79
87
|
instance_variable_set "@saved", false
|
80
88
|
end
|
@@ -92,7 +100,7 @@ class Model
|
|
92
100
|
md.define_method "#{ nm }=" do |value|
|
93
101
|
prevalue = instance_variable_get "@#{ ni }"
|
94
102
|
if prevalue != value&.id
|
95
|
-
debug "ASS: #{ self.id }: #{ nm } / #{ ni } = #{ prevalue.inspect } <=> #{ value.inspect }" if prevalue != nil && self.class.
|
103
|
+
debug "ASS: #{ self.id }: #{ nm } / #{ ni } = #{ prevalue.inspect } <=> #{ value.inspect }" if prevalue != nil && self.class.short_name == 'Taxon'
|
96
104
|
instance_variable_set "@#{ ni }", value&.id
|
97
105
|
instance_variable_set "@saved", false
|
98
106
|
end
|
@@ -105,7 +113,7 @@ class Model
|
|
105
113
|
raise TypeError, "Invalid '#{ nm }' value: #{ value.inspect }!", caller unless tp === value || (value == nil && !rq)
|
106
114
|
prevalue = instance_variable_get "@#{ nm }"
|
107
115
|
if prevalue != value
|
108
|
-
debug "ASS: #{ self.id }: #{ nm } = #{ prevalue.inspect } <=> #{ value.inspect }" if prevalue != nil && self.class.
|
116
|
+
debug "ASS: #{ self.id }: #{ nm } = #{ prevalue.inspect } <=> #{ value.inspect }" if prevalue != nil && self.class.short_name == 'Taxon'
|
109
117
|
instance_variable_set "@#{ nm }", value
|
110
118
|
instance_variable_set "@saved", false
|
111
119
|
end
|
@@ -207,7 +215,10 @@ class Model
|
|
207
215
|
|
208
216
|
end
|
209
217
|
|
210
|
-
|
218
|
+
# @private
|
219
|
+
class ArrayField < Field
|
220
|
+
|
221
|
+
using INat::Types::Std
|
211
222
|
|
212
223
|
attr_reader :back_field
|
213
224
|
|
@@ -223,7 +234,7 @@ class Model
|
|
223
234
|
raise ArgumentError, "Argument 'id_field' is required for name '#{ name }'!", caller[1..]
|
224
235
|
end
|
225
236
|
end
|
226
|
-
back_field = "#{ model.
|
237
|
+
back_field = "#{ model.short_name.downcase }_id".intern if back_field == nil
|
227
238
|
super model, name, type, id_field
|
228
239
|
@owned = owned
|
229
240
|
@back_field = back_field
|
@@ -248,7 +259,7 @@ class Model
|
|
248
259
|
value = value&.sort.uniq
|
249
260
|
end
|
250
261
|
if prevalue != value
|
251
|
-
debug "ASS: #{ self.id }: #{ ni } = #{ prevalue.inspect } <=> #{ value.inspect } :: #{ caller[..2] }" if prevalue != nil && self.class.
|
262
|
+
debug "ASS: #{ self.id }: #{ ni } = #{ prevalue.inspect } <=> #{ value.inspect } :: #{ caller[..2] }" if prevalue != nil && self.class.short_name == 'Taxon'
|
252
263
|
instance_variable_set "@#{ ni }", value
|
253
264
|
instance_variable_set "@saved", false
|
254
265
|
end
|
@@ -274,7 +285,7 @@ class Model
|
|
274
285
|
end
|
275
286
|
prevalue = instance_variable_get("@#{ nm }")
|
276
287
|
if prevalue&.sort != value&.sort
|
277
|
-
debug "ASS: #{ self.id }: #{ nm } = #{ prevalue.inspect } <=> #{ value.inspect } :: #{ caller[..2] }" if prevalue != nil && self.class.
|
288
|
+
debug "ASS: #{ self.id }: #{ nm } = #{ prevalue.inspect } <=> #{ value.inspect } :: #{ caller[..2] }" if prevalue != nil && self.class.short_name == 'Taxon'
|
278
289
|
instance_variable_set "@#{ nm }", value
|
279
290
|
instance_variable_set "@saved", false
|
280
291
|
end
|
@@ -292,13 +303,16 @@ class Model
|
|
292
303
|
|
293
304
|
end
|
294
305
|
|
295
|
-
|
306
|
+
# @private
|
307
|
+
class ManyToManyField < ArrayField
|
308
|
+
|
309
|
+
using INat::Types::Std
|
296
310
|
|
297
311
|
attr_reader :table_name, :link_field, :index
|
298
312
|
|
299
313
|
def initialize model, name, type, id_field, owned, table_name, back_field, link_field, index
|
300
|
-
table_name = "#{ model.
|
301
|
-
link_field = "#{ type.
|
314
|
+
table_name = "#{ model.short_name.downcase }_#{ name }".intern if table_name == nil
|
315
|
+
link_field = "#{ type.short_name.downcase }_id".intern if link_field == nil
|
302
316
|
super model, name, type, id_field, owned, back_field
|
303
317
|
@table_name = table_name
|
304
318
|
@link_field = link_field
|
@@ -327,7 +341,8 @@ class Model
|
|
327
341
|
|
328
342
|
end
|
329
343
|
|
330
|
-
|
344
|
+
# @private
|
345
|
+
class OneToManyField < ArrayField
|
331
346
|
|
332
347
|
def kind
|
333
348
|
:backs
|
@@ -335,7 +350,8 @@ class Model
|
|
335
350
|
|
336
351
|
end
|
337
352
|
|
338
|
-
|
353
|
+
# @private
|
354
|
+
class SpecialField < Field
|
339
355
|
|
340
356
|
def initialize model, name, type, &block
|
341
357
|
raise ArgumentError, "Block is required!", caller[1..] unless block_given?
|
@@ -359,7 +375,8 @@ class Model
|
|
359
375
|
|
360
376
|
end
|
361
377
|
|
362
|
-
|
378
|
+
# @private
|
379
|
+
class IgnoreField < SpecialField
|
363
380
|
|
364
381
|
def initialize model, name
|
365
382
|
super model, name, Object do
|
@@ -433,7 +450,15 @@ class Model
|
|
433
450
|
result.freeze
|
434
451
|
end
|
435
452
|
|
436
|
-
|
453
|
+
# Defines a new field
|
454
|
+
# @param [Symbol] name
|
455
|
+
# @param [Class] type
|
456
|
+
# @return [void]
|
457
|
+
# @!macro [attach] field
|
458
|
+
# @api public
|
459
|
+
# @!attribute [rw]
|
460
|
+
# @return [$2] the +$1+ field
|
461
|
+
def field name, type: nil, id_field: nil, required: false, index: false, unique: false, primary_key: false
|
437
462
|
raise TypeError, "Field name must be a Symbol!", caller unless Symbol === name
|
438
463
|
raise TypeError, "Field type must be a Module!", caller unless Module === type
|
439
464
|
raise TypeError, "Argument 'id_field' must be a Symbol!", caller unless Symbol === id_field || id_field == nil
|
@@ -446,9 +471,17 @@ class Model
|
|
446
471
|
@fields[name].implement
|
447
472
|
end
|
448
473
|
|
449
|
-
|
474
|
+
# Defines a new many-to-many field
|
475
|
+
# @param [Symbol] name
|
476
|
+
# @param [Class] item_type
|
477
|
+
# @return [void]
|
478
|
+
# @!macro [attach] links
|
479
|
+
# @api public
|
480
|
+
# @!attribute [rw]
|
481
|
+
# @return [Array<$2>] the +$1+ field
|
482
|
+
def links name, item_type: nil, ids_field: nil, owned: true, table_name: nil, back_field: nil, link_field: nil, index: false
|
450
483
|
raise TypeError, "Field name must be a Symbol!", caller unless Symbol === name
|
451
|
-
raise TypeError, "Item type must be an Entity subclass!", caller unless Class === item_type && Entity > item_type
|
484
|
+
raise TypeError, "Item type must be an Entity subclass!", caller unless Class === item_type && INat::Entity > item_type
|
452
485
|
raise TypeError, "Argument 'ids_field' must be a Symbol!", caller unless Symbol === ids_field || ids_field == nil
|
453
486
|
raise TypeError, "Argument 'table_name' must be a Symbol!", caller unless Symbol === table_name || table_name == nil
|
454
487
|
raise TypeError, "Argument 'back_field' must be a Symbol!", caller unless Symbol === back_field || back_field == nil
|
@@ -460,9 +493,17 @@ class Model
|
|
460
493
|
@fields[name].implement
|
461
494
|
end
|
462
495
|
|
463
|
-
|
496
|
+
# Defines a new one-to-many field
|
497
|
+
# @param [Symbol] name
|
498
|
+
# @param [Class] item_type
|
499
|
+
# @return [void]
|
500
|
+
# @!macro [attach] backs
|
501
|
+
# @api public
|
502
|
+
# @!attribute [rw]
|
503
|
+
# @return [Array<$2>] the +$1+ field
|
504
|
+
def backs name, item_type: nil, ids_field: nil, owned: true, back_field: nil
|
464
505
|
raise TypeError, "Field name must be a Symbol!", caller unless Symbol === name
|
465
|
-
raise TypeError, "Item type must be an Entity subclass!", caller unless Class === item_type && Entity > item_type
|
506
|
+
raise TypeError, "Item type must be an Entity subclass!", caller unless Class === item_type && INat::Entity > item_type
|
466
507
|
raise TypeError, "Argument 'ids_field' must be a Symbol!", caller unless Symbol === ids_field || ids_field == nil
|
467
508
|
raise TypeError, "Argument 'back_field' must be a Symbol!", caller unless Symbol === back_field || back_field == nil
|
468
509
|
raise TypeError, "Argument 'owned' must be a Boolean!", caller unless Boolean === owned
|
@@ -471,7 +512,15 @@ class Model
|
|
471
512
|
@fields[name].implement
|
472
513
|
end
|
473
514
|
|
474
|
-
|
515
|
+
# Defines a new write-only field
|
516
|
+
# @param [Symbol] name
|
517
|
+
# @param [Class] type
|
518
|
+
# @return [void]
|
519
|
+
# @!macro [attach] block
|
520
|
+
# @api public
|
521
|
+
# @!attribute [w]
|
522
|
+
# @return [$2] the +$1+ field
|
523
|
+
def block name, type: nil, &block
|
475
524
|
raise TypeError, "Field name must be a Symbol!", caller unless Symbol === name
|
476
525
|
raise TypeError, "Field type must be a Module!", caller unless Module === type
|
477
526
|
raise ArgumentError, "Block is required!", caller unless block_given?
|
data/lib/inat/data/query.rb
CHANGED
@@ -14,9 +14,14 @@ require_relative 'types/std'
|
|
14
14
|
require_relative 'types/location'
|
15
15
|
require_relative 'types/extras'
|
16
16
|
|
17
|
-
class Query
|
17
|
+
class INat::Query
|
18
18
|
|
19
|
-
|
19
|
+
using INat::Types::Std
|
20
|
+
|
21
|
+
include INat
|
22
|
+
include INat::App
|
23
|
+
include INat::App::Logger::DSL
|
24
|
+
include INat::Data::Types
|
20
25
|
|
21
26
|
private def parse_accuracy value
|
22
27
|
case value
|
@@ -1077,17 +1082,17 @@ class Query
|
|
1077
1082
|
if mode != UpdateMode::MINIMAL
|
1078
1083
|
actual_time = Time::new - Period::parse(G.config[:data][:update_period])
|
1079
1084
|
end
|
1080
|
-
actuals = Request::from_db_rows(DB.execute("SELECT * FROM requests WHERE time >= ?", actual_time.to_db)).select { |rq| self.in?(rq.query) }
|
1085
|
+
actuals = Entity::Request::from_db_rows(DB.execute("SELECT * FROM requests WHERE time >= ?", actual_time.to_db)).select { |rq| self.in?(rq.query) }
|
1081
1086
|
end
|
1082
1087
|
if actuals.empty? || mode == UpdateMode::FORCE
|
1083
1088
|
# 2. Ищем чего бы обновить
|
1084
|
-
request = Request::from_db_rows(DB.execute("SELECT * FROM requests WHERE query = ?", api_query)).first
|
1089
|
+
request = Entity::Request::from_db_rows(DB.execute("SELECT * FROM requests WHERE query = ?", api_query)).first
|
1085
1090
|
updated_since = nil
|
1086
1091
|
if request == nil
|
1087
1092
|
query_string = api_query
|
1088
1093
|
project_id = @api_params[:project_id]
|
1089
1094
|
project_id = nil unless Integer === project_id
|
1090
|
-
request = Request::create query_string, project_id
|
1095
|
+
request = Entity::Request::create query_string, project_id
|
1091
1096
|
request.save
|
1092
1097
|
else
|
1093
1098
|
updated_since = request.time if mode != UpdateMode::RELOAD
|
@@ -1104,7 +1109,7 @@ class Query
|
|
1104
1109
|
tt = nil
|
1105
1110
|
cc = 0
|
1106
1111
|
current_time = Time::new
|
1107
|
-
API::query(:observations, **params) do |json, total|
|
1112
|
+
INat::API::query(:observations, **params) do |json, total|
|
1108
1113
|
tt ||= total
|
1109
1114
|
cc += 1
|
1110
1115
|
pc = cc * 100 / tt
|
@@ -1113,7 +1118,7 @@ class Query
|
|
1113
1118
|
pe = Period::make seconds: te
|
1114
1119
|
pt = Period::make seconds: (Time::new - current_time).to_i
|
1115
1120
|
Status::status nil, "Query \##{ @int_key } : R\##{ request.id } : parsed #{ format("%d of %d : %3d%% : time %s remain %s", cc, tt, pc, pt.to_hs, pe.to_hs) }"
|
1116
|
-
obs = Observation::parse json
|
1121
|
+
obs = Entity::Observation::parse json
|
1117
1122
|
obs.save
|
1118
1123
|
DB.execute "INSERT OR REPLACE INTO request_observations (request_id, observation_id) VALUES (?, ?);", request.id, obs.id
|
1119
1124
|
end
|
@@ -1123,12 +1128,12 @@ class Query
|
|
1123
1128
|
# TODO: разобраться с удалением устаревшего
|
1124
1129
|
# NEED: разобраться с частичной загрузкой — большие проекты грузятся недопустимо долго
|
1125
1130
|
# возможно, стоит запараллелить обработку
|
1126
|
-
request = Request::read(request.id).first
|
1131
|
+
request = Entity::Request::read(request.id).first
|
1127
1132
|
end
|
1128
1133
|
end
|
1129
1134
|
# TODO: разобраться, где тупня
|
1130
1135
|
sql, sql_args = db_where
|
1131
|
-
result = Observation::from_db_rows(DB.execute("SELECT * FROM observations o#{ sql.empty? && '' || ' WHERE ' }#{ sql };", *sql_args))
|
1136
|
+
result = Entity::Observation::from_db_rows(DB.execute("SELECT * FROM observations o#{ sql.empty? && '' || ' WHERE ' }#{ sql };", *sql_args))
|
1132
1137
|
if !@r_match.empty?
|
1133
1138
|
result = result.filter { |o| self.match?(o) }
|
1134
1139
|
end
|
@@ -3,10 +3,13 @@
|
|
3
3
|
require_relative 'listers'
|
4
4
|
require_relative 'list'
|
5
5
|
|
6
|
-
class DataSet
|
6
|
+
class INat::Report::DataSet
|
7
|
+
|
8
|
+
include INat
|
9
|
+
include INat::Report
|
7
10
|
|
8
11
|
attr_reader :time
|
9
|
-
|
12
|
+
attr_accessor :object # TODO: переделать select так, чтобы не было необходимости во внешнем присваивании
|
10
13
|
attr_reader :observations
|
11
14
|
|
12
15
|
def initialize object, observations, time: Time::new
|
@@ -74,7 +77,8 @@ class DataSet
|
|
74
77
|
alias :=== :include?
|
75
78
|
|
76
79
|
def << observation
|
77
|
-
raise TypeError, "Argument must be an Observation (#{ observation.inspect })!" unless Observation === observation
|
80
|
+
raise TypeError, "Argument must be an Observation (#{ observation.inspect })!" unless Entity::Observation === observation
|
81
|
+
# TODO: добавить массивы и прочее
|
78
82
|
if !self.include?(observation)
|
79
83
|
@observations << observation
|
80
84
|
@by_id[observation.id] = observation
|
@@ -84,17 +88,17 @@ class DataSet
|
|
84
88
|
|
85
89
|
def | other
|
86
90
|
obj = @object == other.object ? @object : nil
|
87
|
-
DataSet::new obj, @observations + other.observations, time: Time::new
|
91
|
+
INat::Report::DataSet::new obj, @observations + other.observations, time: Time::new
|
88
92
|
end
|
89
93
|
|
90
94
|
def & other
|
91
95
|
obj = @object == other.object ? @object : nil
|
92
|
-
DataSet::new obj, @observations.select { |o| other.include?(o) }, time: Time::new
|
96
|
+
INat::Report::DataSet::new obj, @observations.select { |o| other.include?(o) }, time: Time::new
|
93
97
|
end
|
94
98
|
|
95
99
|
def - other
|
96
100
|
obj = @object == other.object ? @object : nil
|
97
|
-
DataSet::new obj, @observations.select { |o| !other.include?(o) }, time: Time::new
|
101
|
+
INat::Report::DataSet::new obj, @observations.select { |o| !other.include?(o) }, time: Time::new
|
98
102
|
end
|
99
103
|
|
100
104
|
def to_a
|
data/lib/inat/data/sets/list.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
module INat::Report
|
4
|
+
autoload :DataSet, 'inat/data/sets/dataset'
|
5
|
+
end
|
6
|
+
|
7
|
+
class INat::Report::List
|
4
8
|
|
5
|
-
|
9
|
+
include INat
|
10
|
+
include INat::Report
|
6
11
|
|
7
12
|
attr_reader :lister, :sorter
|
8
13
|
|
@@ -77,7 +82,7 @@ class List
|
|
77
82
|
|
78
83
|
def << some
|
79
84
|
case some
|
80
|
-
when Observation
|
85
|
+
when Entity::Observation
|
81
86
|
key = @lister.call some
|
82
87
|
if key != nil
|
83
88
|
@data[key] ||= DataSet::new key, [], time: @time
|
@@ -2,14 +2,20 @@
|
|
2
2
|
|
3
3
|
require_relative 'wrappers'
|
4
4
|
|
5
|
-
|
5
|
+
# TODO: возможно, загнать внутрь List?
|
6
|
+
|
7
|
+
module INat::Report::Listers
|
8
|
+
|
9
|
+
include INat::Report
|
10
|
+
include INat::Data::Types
|
6
11
|
|
7
12
|
SPECIES = lambda { |o| o.normalized_taxon(Rank::COMPLEX .. Rank::HYBRID) }
|
8
13
|
GENUS = lambda { |o| o.normalized_taxon(Rank::GENUS) }
|
9
14
|
FAMILY = lambda { |o| o.normalized_taxon(Rank::FAMILY) }
|
10
|
-
YEAR = lambda { |o| Year[o.observed_on]
|
11
|
-
MONTH = lambda { |o| Month[o.observed_on]
|
12
|
-
DAY = lambda { |o| Day[o.observed_on]
|
15
|
+
YEAR = lambda { |o| Period::Year[o.observed_on] }
|
16
|
+
MONTH = lambda { |o| Period::Month[o.observed_on] }
|
17
|
+
DAY = lambda { |o| Period::Day[o.observed_on] }
|
18
|
+
WINTER = lambda { |o| Period::Winter[o.observed_on] }
|
13
19
|
USER = lambda { |o| o.user }
|
14
20
|
|
15
21
|
end
|
@@ -2,136 +2,165 @@
|
|
2
2
|
|
3
3
|
require 'date'
|
4
4
|
|
5
|
-
|
5
|
+
module INat::Report; end
|
6
|
+
|
7
|
+
# TODO: переделать в модуль namespace и Base-класс отдельно
|
8
|
+
|
9
|
+
class INat::Report::Period
|
6
10
|
|
7
11
|
class << self
|
8
12
|
|
9
13
|
private :new
|
10
14
|
|
11
|
-
def []
|
12
|
-
return nil if
|
13
|
-
return
|
14
|
-
|
15
|
-
when Date
|
16
|
-
|
15
|
+
def [] src
|
16
|
+
return nil if src == nil
|
17
|
+
return src if self === src
|
18
|
+
value = case src
|
19
|
+
when Date
|
20
|
+
self.date_to_value src
|
21
|
+
when Time
|
22
|
+
self.date_to_value src.to_date
|
17
23
|
when String
|
18
|
-
date = Date::parse
|
19
|
-
date
|
24
|
+
date = Date::parse src
|
25
|
+
self.date_to_value date
|
20
26
|
when Integer
|
21
|
-
|
27
|
+
src
|
22
28
|
else
|
23
|
-
raise TypeError, "Invalid
|
29
|
+
raise TypeError, "Invalid date: #{ src.inspect }", caller
|
24
30
|
end
|
25
|
-
|
26
|
-
@
|
27
|
-
@
|
31
|
+
return nil if value == nil
|
32
|
+
@values ||= {}
|
33
|
+
@values[value] ||= new value
|
34
|
+
@values[value]
|
28
35
|
end
|
29
36
|
|
30
37
|
end
|
31
38
|
|
32
|
-
attr_reader :
|
39
|
+
attr_reader :value
|
33
40
|
|
34
|
-
def initialize
|
35
|
-
@
|
41
|
+
def initialize value
|
42
|
+
@value = value
|
36
43
|
end
|
37
44
|
|
38
45
|
include Comparable
|
39
46
|
|
40
47
|
def <=> other
|
41
|
-
return nil unless
|
42
|
-
@
|
48
|
+
return nil unless self.class === other
|
49
|
+
@value <=> other.value
|
43
50
|
end
|
44
51
|
|
45
|
-
|
46
|
-
self.class[@year - num]
|
47
|
-
end
|
52
|
+
class Year < INat::Report::Period
|
48
53
|
|
49
|
-
|
50
|
-
"<i class=\"glyphicon glyphicon-calendar\"></i> #{ @year }"
|
51
|
-
end
|
54
|
+
class << self
|
52
55
|
|
53
|
-
|
56
|
+
protected def date_to_value date
|
57
|
+
date.year
|
58
|
+
end
|
54
59
|
|
55
|
-
|
60
|
+
end
|
56
61
|
|
57
|
-
|
62
|
+
alias :year :value
|
58
63
|
|
59
|
-
|
64
|
+
def - num
|
65
|
+
self.class[@value - num]
|
66
|
+
end
|
60
67
|
|
61
|
-
def
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
when String
|
68
|
-
date = Date::parse source
|
69
|
-
date.month
|
70
|
-
when Integer
|
71
|
-
source
|
72
|
-
else
|
73
|
-
raise TypeError, "Invalid month key: #{ source.inspect }!", caller
|
74
|
-
end
|
75
|
-
@months ||= {}
|
76
|
-
@months[month] ||= new month
|
77
|
-
@months[month]
|
68
|
+
def to_s
|
69
|
+
"<i class=\"glyphicon glyphicon-calendar\"></i> #{ @value } год"
|
70
|
+
end
|
71
|
+
|
72
|
+
def query_params
|
73
|
+
"year=#{ @value }"
|
78
74
|
end
|
79
75
|
|
80
76
|
end
|
81
77
|
|
82
|
-
|
78
|
+
class Month < INat::Report::Period
|
83
79
|
|
84
|
-
|
85
|
-
@month = month
|
86
|
-
end
|
80
|
+
class << self
|
87
81
|
|
88
|
-
|
82
|
+
protected def date_to_value date
|
83
|
+
date.month
|
84
|
+
end
|
89
85
|
|
90
|
-
|
91
|
-
return nil unless Month === other
|
92
|
-
@month <=> other.month
|
93
|
-
end
|
86
|
+
end
|
94
87
|
|
95
|
-
|
88
|
+
alias :month :value
|
89
|
+
|
90
|
+
NAMES = {
|
91
|
+
1 => 'Январь',
|
92
|
+
2 => 'Февраль',
|
93
|
+
3 => 'Март',
|
94
|
+
4 => 'Апрель',
|
95
|
+
5 => 'Май',
|
96
|
+
6 => 'Июнь',
|
97
|
+
7 => 'Июль',
|
98
|
+
8 => 'Август',
|
99
|
+
9 => 'Сентябрь',
|
100
|
+
10 => 'Октябрь',
|
101
|
+
11 => 'Ноябрь',
|
102
|
+
12 => 'Декабрь'
|
103
|
+
}
|
104
|
+
|
105
|
+
def to_s
|
106
|
+
"<i class=\"glyphicon glyphicon-calendar\"></i> #{ NAMES[@value] }"
|
107
|
+
end
|
96
108
|
|
97
|
-
|
109
|
+
def query_params
|
110
|
+
"month=#{ @value }"
|
111
|
+
end
|
98
112
|
|
99
|
-
|
113
|
+
end
|
100
114
|
|
101
|
-
|
115
|
+
class Day < INat::Report::Period
|
102
116
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
day = case source
|
107
|
-
when Date, Time
|
108
|
-
source.day
|
109
|
-
when String
|
110
|
-
date = Date::parse source
|
117
|
+
class << self
|
118
|
+
|
119
|
+
protected def date_to_value date
|
111
120
|
date.day
|
112
|
-
when Integer
|
113
|
-
source
|
114
|
-
else
|
115
|
-
raise TypeError, "Invalid day key: #{ source.inspect }!", caller
|
116
121
|
end
|
117
|
-
|
118
|
-
@days[day] ||= new day
|
119
|
-
@days[day]
|
122
|
+
|
120
123
|
end
|
121
124
|
|
122
|
-
|
125
|
+
alias :day :value
|
126
|
+
|
127
|
+
def to_s
|
128
|
+
"<i class=\"glyphicon glyphicon-calendar\"></i> #{ @value }"
|
129
|
+
end
|
123
130
|
|
124
|
-
|
131
|
+
def query_params
|
132
|
+
"day=#{ @value }"
|
133
|
+
end
|
125
134
|
|
126
|
-
def initialize day
|
127
|
-
@day = day
|
128
135
|
end
|
129
136
|
|
130
|
-
|
137
|
+
class Winter < INat::Report::Period
|
138
|
+
|
139
|
+
class << self
|
140
|
+
|
141
|
+
protected def date_to_value date
|
142
|
+
month = date.month
|
143
|
+
if month <= 4
|
144
|
+
date.year
|
145
|
+
elsif month >= 10
|
146
|
+
date.year + 1
|
147
|
+
else
|
148
|
+
nil
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
alias :winter :value
|
155
|
+
|
156
|
+
def to_s
|
157
|
+
"<i class=\"glyphicon glyphicon-calendar\"></i> Зима #{ @value - 1 }–#{ @value }"
|
158
|
+
end
|
159
|
+
|
160
|
+
def query_params
|
161
|
+
"d1=#{ @value - 1 }-10-01&d2=#{ @value }-04-30"
|
162
|
+
end
|
131
163
|
|
132
|
-
def <=> other
|
133
|
-
return nil unless Day === other
|
134
|
-
@day <=> other.day
|
135
164
|
end
|
136
165
|
|
137
166
|
end
|