inat-get 0.8.0.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +674 -0
  3. data/README.md +16 -0
  4. data/Rakefile +4 -0
  5. data/bin/inat-get +59 -0
  6. data/docs/logo.png +0 -0
  7. data/inat-get.gemspec +33 -0
  8. data/lib/extra/enum.rb +184 -0
  9. data/lib/extra/period.rb +252 -0
  10. data/lib/extra/uuid.rb +90 -0
  11. data/lib/inat/app/application.rb +50 -0
  12. data/lib/inat/app/config/messagelevel.rb +22 -0
  13. data/lib/inat/app/config/shiftage.rb +24 -0
  14. data/lib/inat/app/config/updatemode.rb +20 -0
  15. data/lib/inat/app/config.rb +296 -0
  16. data/lib/inat/app/globals.rb +80 -0
  17. data/lib/inat/app/info.rb +21 -0
  18. data/lib/inat/app/logging.rb +35 -0
  19. data/lib/inat/app/preamble.rb +27 -0
  20. data/lib/inat/app/status.rb +74 -0
  21. data/lib/inat/app/task/context.rb +47 -0
  22. data/lib/inat/app/task/dsl.rb +24 -0
  23. data/lib/inat/app/task.rb +75 -0
  24. data/lib/inat/data/api.rb +218 -0
  25. data/lib/inat/data/cache.rb +9 -0
  26. data/lib/inat/data/db.rb +87 -0
  27. data/lib/inat/data/ddl.rb +18 -0
  28. data/lib/inat/data/entity/comment.rb +29 -0
  29. data/lib/inat/data/entity/flag.rb +22 -0
  30. data/lib/inat/data/entity/identification.rb +45 -0
  31. data/lib/inat/data/entity/observation.rb +172 -0
  32. data/lib/inat/data/entity/observationphoto.rb +25 -0
  33. data/lib/inat/data/entity/observationsound.rb +26 -0
  34. data/lib/inat/data/entity/photo.rb +31 -0
  35. data/lib/inat/data/entity/place.rb +57 -0
  36. data/lib/inat/data/entity/project.rb +94 -0
  37. data/lib/inat/data/entity/projectadmin.rb +21 -0
  38. data/lib/inat/data/entity/projectobservationrule.rb +50 -0
  39. data/lib/inat/data/entity/request.rb +58 -0
  40. data/lib/inat/data/entity/sound.rb +27 -0
  41. data/lib/inat/data/entity/taxon.rb +94 -0
  42. data/lib/inat/data/entity/user.rb +67 -0
  43. data/lib/inat/data/entity/vote.rb +22 -0
  44. data/lib/inat/data/entity.rb +291 -0
  45. data/lib/inat/data/enums/conservationstatus.rb +30 -0
  46. data/lib/inat/data/enums/geoprivacy.rb +14 -0
  47. data/lib/inat/data/enums/iconictaxa.rb +23 -0
  48. data/lib/inat/data/enums/identificationcategory.rb +13 -0
  49. data/lib/inat/data/enums/licensecode.rb +16 -0
  50. data/lib/inat/data/enums/projectadminrole.rb +11 -0
  51. data/lib/inat/data/enums/projecttype.rb +37 -0
  52. data/lib/inat/data/enums/qualitygrade.rb +12 -0
  53. data/lib/inat/data/enums/rank.rb +60 -0
  54. data/lib/inat/data/model.rb +551 -0
  55. data/lib/inat/data/query.rb +1145 -0
  56. data/lib/inat/data/sets/dataset.rb +104 -0
  57. data/lib/inat/data/sets/list.rb +190 -0
  58. data/lib/inat/data/sets/listers.rb +15 -0
  59. data/lib/inat/data/sets/wrappers.rb +137 -0
  60. data/lib/inat/data/types/extras.rb +88 -0
  61. data/lib/inat/data/types/location.rb +89 -0
  62. data/lib/inat/data/types/std.rb +293 -0
  63. data/lib/inat/report/table.rb +135 -0
  64. data/lib/inat/utils/deep.rb +30 -0
  65. metadata +137 -0
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'extra/uuid'
4
+
5
+ require_relative '../types/std'
6
+ require_relative '../types/extras'
7
+ require_relative '../entity'
8
+
9
+ # require_relative 'observation'
10
+ # require_relative 'photo'
11
+
12
+ autoload :Observation, 'inat/data/entity/observation'
13
+ autoload :Photo, 'inat/data/entity/photo'
14
+
15
+ class ObservationPhoto < Entity
16
+
17
+ table :observation_photos
18
+
19
+ field :observation, type: Observation, index: true
20
+
21
+ field :uuid, type: UUID, unique: true
22
+ field :position, type: Integer, index: true
23
+ field :photo, type: Photo, required: true
24
+
25
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'extra/uuid'
4
+
5
+ require_relative '../types/std'
6
+ require_relative '../types/extras'
7
+ require_relative '../entity'
8
+
9
+ # require_relative 'observation'
10
+ # require_relative 'sound'
11
+
12
+ # class Observation < Entity; end
13
+
14
+ autoload :Observation, 'inat/data/entity/observation'
15
+ autoload :Sound, 'inat/data/entity/sound'
16
+
17
+ class ObservationSound < Entity
18
+
19
+ table :observation_sounds
20
+
21
+ field :observation, type: Observation, index: true
22
+
23
+ field :uuid, type: UUID, unique: true
24
+ field :sound, type: Sound, required: true
25
+
26
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../types/std'
4
+ require_relative '../types/extras'
5
+ require_relative '../entity'
6
+
7
+ autoload :Observation, 'inat/data/entity/observation'
8
+
9
+ class Photo < Entity
10
+
11
+ table :photos
12
+
13
+ field :license_code, type: Symbol, index: true
14
+ field :url, type: URI, required: true
15
+ field :square_url, type: URI
16
+ field :medium_url, type: URI
17
+ field :small_url, type: URI
18
+ field :large_url, type: URI
19
+ field :original_url, type: URI
20
+ field :attribution, type: String
21
+ field :hidden, type: Boolean, index: true
22
+
23
+ links :flags, item_type: Flag
24
+
25
+ ignore :original_dimensions # TODO: сделать нормальный тип
26
+
27
+ ignore :moderator_actions # TODO: разобраться
28
+ field :native_page_url, type: URI
29
+ ignore :native_photo_id
30
+
31
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../types/std'
4
+ require_relative '../types/extras'
5
+ require_relative '../types/location'
6
+ require_relative '../entity'
7
+
8
+ autoload :Observation, 'inat/data/entity/observation'
9
+
10
+ class Place < Entity
11
+
12
+ extend BySLUG
13
+
14
+ api_path :places
15
+ api_part :path
16
+ api_limit 500
17
+
18
+ table :places
19
+
20
+ field :uuid, type: UUID, unique: true
21
+ field :name, type: String, index: true, required: true
22
+ field :slug, type: Symbol, index: true
23
+ field :display_name, type: String, index: true
24
+ field :bbox_area, type: Float
25
+ field :admin_level, type: Integer, index: true
26
+ field :place_type, type: Integer, index: true
27
+ field :location, type: Location
28
+
29
+ ignore :geometry_geojson # TODO: implement
30
+ ignore :bounding_box_geojson
31
+
32
+ links :ancestor_places, item_type: Place, table_name: :place_ancestors, link_field: :ancestor_id, index: true
33
+
34
+ def === other
35
+ self.id == other.id && other.ancestor_place_ids.include?(self.id)
36
+ end
37
+
38
+ def sort_key
39
+ display_name
40
+ end
41
+
42
+ class << self
43
+
44
+ def DDL
45
+ super +
46
+ "INSERT OR REPLACE INTO places (id, uuid, name, display_name) VALUES (59614, '00000000-0000-0000-0000-000000000000', 'World', 'Весь мир');\n"
47
+ # Если верить API, то идентификатор такой есть, а записи для него нет.
48
+ # Вставляем ему искусственно некоторые данные для того, чтобы не запрашивать впустую каждый раз, как он встречается.
49
+ end
50
+
51
+ end
52
+
53
+ def to_s
54
+ "<a href=\"https://www.inaturalist.org/places/#{ id }\"><i class=\"fa fa-globe\"></i>  #{ display_name || name }</a>"
55
+ end
56
+
57
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../types/std'
4
+ require_relative '../types/extras'
5
+ require_relative '../types/location'
6
+ require_relative '../entity'
7
+ require_relative '../enums/projecttype'
8
+
9
+ # require_relative 'projectadmin'
10
+ # require_relative 'projectobservationrule'
11
+
12
+ autoload :Observation, 'inat/data/entity/observation'
13
+ autoload :User, 'inat/data/entity/user'
14
+ autoload :Place, 'inat/data/entity/place'
15
+ autoload :ProjectAdmin, 'inat/data/entity/projectadmin'
16
+ autoload :ProjectObservationRule, 'inat/data/entity/projectobservationrule'
17
+ autoload :Flag, 'inat/data/entity/flag'
18
+ autoload :Taxon, 'inat/data/entity/taxon'
19
+
20
+ class Project < Entity
21
+
22
+ extend BySLUG
23
+
24
+ api_path :projects
25
+ api_part :query
26
+ api_limit 200
27
+
28
+ table :projects
29
+
30
+ field :slug, type: Symbol, index: true
31
+ field :title, type: String, index: true, required: true
32
+ field :description, type: String
33
+ field :project_type, type: ProjectType, index: true
34
+ field :is_umbrella, type: Boolean, index: true
35
+ field :created_at, type: Time
36
+ field :updated_at, type: Time
37
+ field :observation_requirements_updated_at, type: Time
38
+ field :prefers_user_trust, type: Boolean
39
+ field :icon, type: URI
40
+ field :icon_file_name, type: String
41
+ field :header_image_url, type: URI
42
+ field :header_image_file_name, type: String
43
+ field :header_image_contain, type: Boolean
44
+ field :banner_color, type: String # TODO: сделать тип
45
+ field :hide_title, type: Boolean
46
+ field :user, type: User, index: true
47
+ field :location, type: Location
48
+
49
+ field :place, type: Place, index: true
50
+
51
+ # links :users, item_type: User
52
+ # TODO: разобраться как грузить пачками
53
+ ignore :users
54
+ ignore :user_ids
55
+
56
+ backs :admins, item_type: ProjectAdmin, owned: true
57
+ backs :project_observation_rules, item_type: ProjectObservationRule, owned: true
58
+
59
+ links :flags, item_type: Flag, index: true
60
+
61
+ links :subprojects, item_type: Project, table_name: :project_children, link_field: :child_id, owned: false
62
+
63
+ links :included_taxa, item_type: Taxon, ids_field: :included_taxon_ids, table_name: :project_rule_taxa, owned: false
64
+ links :excluded_taxa, item_type: Taxon, ids_field: :excluded_taxon_ids, table_name: :project_rule_excluded_taxa, owned: false
65
+ links :included_places, item_type: Place, ids_field: :included_place_ids, table_name: :project_rule_places, owned: false
66
+ links :excluded_places, item_type: Place, ids_field: :excluded_places_ids, table_name: :project_rule_excluded_places, owned: false
67
+
68
+ links :observations, item_type: Observation, table_name: :project_observations, owned: false
69
+
70
+ ignore :latitude
71
+ ignore :longitude
72
+
73
+ # block :latitude, type: Float do |value|
74
+ # @location ||= Location::new nil, nil
75
+ # @location.latitude = value
76
+ # end
77
+
78
+ # block :longitude, type: Float do |value|
79
+ # @location ||= Location::new nil, nil
80
+ # @location.longitude = value
81
+ # end
82
+
83
+ # # TODO: разобраться и сделать связи с местами и таксонами
84
+ ignore :project_observation_fields
85
+ ignore :site_features
86
+ ignore :terms
87
+ ignore :search_parameters
88
+ ignore :rule_preferences
89
+
90
+ def to_s
91
+ "<a href=\"https://www.inaturalist.org/projects/#{ id }\"><i class=\"fa fa-briefcase\"></i>  #{ title }</a>"
92
+ end
93
+
94
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../types/std'
4
+ require_relative '../types/extras'
5
+ require_relative '../entity'
6
+ require_relative '../enums/projectadminrole'
7
+
8
+ # require_relative 'project'
9
+ # autoload :Project, 'inat/data/entity/project'
10
+
11
+ class Project < Entity; end
12
+
13
+ class ProjectAdmin < Entity
14
+
15
+ table :project_admins
16
+
17
+ field :project, type: Project, index: true
18
+ field :role, type: ProjectAdminRole, index: true, required: true
19
+ field :user, type: User, index: true, required: true
20
+
21
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../types/std'
4
+ require_relative '../types/extras'
5
+ require_relative '../entity'
6
+
7
+ # require_relative 'project'
8
+ # autoload :Project, 'inat/data/entity/project'
9
+
10
+ class Project < Entity; end
11
+
12
+ class ProjectObservationRule < Entity
13
+
14
+ table :project_observation_rules
15
+
16
+ field :project, type: Project, index: true
17
+
18
+ field :operator, type: Symbol, index: true, required: true
19
+ field :operand_type, type: Symbol, index: true
20
+ field :operand_id, type: Integer, index: true
21
+
22
+ class << self
23
+
24
+ def DDL
25
+ super +
26
+ "CREATE VIEW IF NOT EXISTS project_children AS\n" +
27
+ " SELECT project_id, operand_id as child_id\n" +
28
+ " FROM project_observation_rules\n" +
29
+ " WHERE operator = 'in_project?' AND operand_type = 'Project';\n" +
30
+ "CREATE VIEW IF NOT EXISTS project_rule_taxa AS\n" +
31
+ " SELECT project_id, operand_id as taxon_id\n" +
32
+ " FROM project_observation_rules\n" +
33
+ " WHERE operator = 'in_taxon?' AND operand_type = 'Taxon';\n" +
34
+ "CREATE VIEW IF NOT EXISTS project_rule_places AS\n" +
35
+ " SELECT project_id, operand_id as place_id\n" +
36
+ " FROM project_observation_rules\n" +
37
+ " WHERE operator = 'observed_in_place?' AND operand_type = 'Place';\n" +
38
+ "CREATE VIEW IF NOT EXISTS project_rule_excluded_taxa AS\n" +
39
+ " SELECT project_id, operand_id as taxon_id\n" +
40
+ " FROM project_observation_rules\n" +
41
+ " WHERE operator = 'not_in_taxon?' AND operand_type = 'Taxon';\n" +
42
+ "CREATE VIEW IF NOT EXISTS project_rule_excluded_places AS\n" +
43
+ " SELECT project_id, operand_id as place_id\n" +
44
+ " FROM project_observation_rules\n" +
45
+ " WHERE operator = 'not_observed_in_place?' AND operand_type = 'Place';\n"
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../types/std'
4
+ require_relative '../types/extras'
5
+ require_relative '../entity'
6
+ require_relative '../db'
7
+
8
+ autoload :Observation, 'inat/data/entity/observation'
9
+ autoload :Project, 'inat/data/entity/project'
10
+
11
+ class Request < Entity
12
+
13
+ table :requests
14
+
15
+ field :time, type: Time, index: true
16
+ field :query, type: String, unique: true
17
+
18
+ field :project, type: Project, index: true
19
+
20
+ links :observations, item_type: Observation, index: true
21
+
22
+ class << self
23
+
24
+ def DDL
25
+ super +
26
+ "CREATE VIEW IF NOT EXISTS project_observations AS\n" +
27
+ " SELECT r.project_id, ro.observation_id\n" +
28
+ " FROM requests r, request_observations ro\n" +
29
+ " WHERE r.id = ro.request_id AND r.project_id IS NOT NULL;\n"
30
+ end
31
+
32
+ def create query_string, project_id
33
+ update do
34
+ max_id = DB.execute("SELECT max(id) AS id FROM requests;").map{ |r| r['id'] }.first
35
+ new_id = if max_id == nil
36
+ 1
37
+ else
38
+ max_id + 1
39
+ end
40
+ @entities ||= {}
41
+ @entities[new_id] ||= new new_id
42
+ @entities[new_id].time = Time::at(0)
43
+ @entities[new_id].query = query_string
44
+ @entities[new_id].project_id = project_id
45
+ @entities[new_id].save
46
+ end
47
+ end
48
+
49
+ end
50
+
51
+ attr_accessor :active
52
+
53
+ def initialize id
54
+ super(id)
55
+ @active = false
56
+ end
57
+
58
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../types/std'
4
+ require_relative '../types/extras'
5
+ require_relative '../entity'
6
+
7
+ autoload :Observation, 'inat/data/entity/observation'
8
+
9
+ class Sound < Entity
10
+
11
+ table :sounds
12
+
13
+ field :license_code, type: LicenseCode, index: true
14
+ field :attribution, type: String
15
+ field :file_url, type: URI
16
+ field :file_content_type, type: Symbol, index: true
17
+ field :play_local, type: Boolean
18
+ field :subtype, type: Symbol, index: true
19
+ field :hidden, type: Boolean, index: true
20
+
21
+ links :flags, item_type: Flag
22
+
23
+ ignore :native_sound_id # TODO: разобраться
24
+ ignore :secret_token
25
+ ignore :moderator_actions
26
+
27
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../types/std'
4
+ require_relative '../types/extras'
5
+ require_relative '../entity'
6
+
7
+ require_relative '../enums/rank'
8
+ require_relative '../enums/iconictaxa'
9
+
10
+ require_relative 'photo'
11
+
12
+ autoload :Observation, 'inat/data/entity/observation'
13
+ # autoload :Photo, 'inat/data/entity/photo'
14
+
15
+ class Taxon < Entity
16
+
17
+ api_path :taxa
18
+ api_part :query
19
+ api_limit 200
20
+
21
+ table :taxa
22
+
23
+ field :is_active, type: Boolean, index: true
24
+ # field :ancestry, type: Ancestry
25
+ # field :min_species_ancestry, type: Ancestry
26
+ field :endemic, type: Boolean
27
+ field :iconic_taxon, type: Taxon
28
+ field :min_species_taxon, type: Taxon
29
+ field :threatened, type: Boolean, index: true
30
+ field :rank_level, type: Float, index: true
31
+ field :introduced, type: Boolean
32
+ field :native, type: Boolean
33
+ field :parent, type: Taxon
34
+ field :name, type: String, index: true, required: true
35
+ field :rank, type: Rank, index: true
36
+ field :extinct, type: Boolean
37
+ field :created_at, type: Time
38
+ field :default_photo, type: Photo
39
+ field :taxon_changes_count, type: Integer
40
+ field :taxon_schemes_count, type: Integer
41
+ # field :observations_count, type: Integer
42
+ field :photos_locked, type: Boolean
43
+ # field :universal_search_rank, type: Integer
44
+ field :wikipedia_url, type: URI
45
+ field :wikipedia_summary, type: String
46
+ field :iconic_taxon_name, type: IconicTaxa, index: true
47
+ # field :iconic_taxon, type: Taxon, index: true
48
+ field :preferred_common_name, type: String
49
+ field :english_common_name, type: String
50
+ field :vision, type: Boolean
51
+
52
+ links :ancestors, item_type: Taxon, link_field: :ancestor_id
53
+ links :taxon_photos, item_type: Photo, table_name: :taxon_photos
54
+
55
+ backs :children, item_type: Taxon, ids_field: :child_ids, back_field: :parent_id, owned: false
56
+
57
+ ignore :flag_counts # TODO: разобраться
58
+ ignore :current_synonymous_taxon_ids
59
+ ignore :atlas_id
60
+ ignore :complete_species_count
61
+ ignore :conservation_statuses
62
+ ignore :conservation_status
63
+ ignore :listed_taxa
64
+ ignore :complete_rank
65
+
66
+ ignore :ancestry # TODO: подумать и сделать обязательно
67
+ ignore :min_species_ancestry
68
+ ignore :establishment_means
69
+ ignore :preferred_establishment_means
70
+
71
+ ignore :observations_count
72
+ ignore :universal_search_rank
73
+
74
+ def === other
75
+ other.id == self.id || other.ancestor_ids.include?(self.id)
76
+ end
77
+
78
+ def sort_key
79
+ [ iconic_taxon_name || IconicTaxa::UNKNOWN, name ]
80
+ end
81
+
82
+ private def prepare_name name
83
+ name.downcase.gsub(' ', ' ').capitalize
84
+ end
85
+
86
+ def to_s
87
+ if preferred_common_name
88
+ "<a href=\"https://www.inaturalist.org/taxa/#{ id }\"><i class=\"icon-iconic-#{ (iconic_taxon_name || IconicTaxa::UNKNOWN).to_s.downcase }\" style=\"font-size:1.5em;height:1em;line-height:1em;\"></i> #{ prepare_name(preferred_common_name) } <i>(#{ prepare_name(name) })</i></a>"
89
+ else
90
+ "<a href=\"https://www.inaturalist.org/taxa/#{ id }\"><i class=\"icon-iconic-#{ (iconic_taxon_name || IconicTaxa::UNKNOWN).to_s.downcase }\" style=\"font-size:1.5em;height:1em;line-height:1em;\"></i> <i>#{ prepare_name(name) }</i></a>"
91
+ end
92
+ end
93
+
94
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../types/std'
4
+ require_relative '../types/extras'
5
+ require_relative '../entity'
6
+
7
+ autoload :Observation, 'inat/data/entity/observation'
8
+
9
+ class User < Entity
10
+
11
+ api_path :users
12
+ api_part :path
13
+ api_limit 1
14
+
15
+ table :users
16
+
17
+ field :login, type: String, index: true, required: true
18
+ field :spam, type: Boolean
19
+ field :suspended, type: Boolean, index: true
20
+ field :created_at, type: Time, index: true
21
+ field :site_id, type: Integer, index: true
22
+ field :login_autocomplete, type: String
23
+ field :login_exact, type: String
24
+ field :name, type: String, index: true
25
+ field :name_autocomplete, type: String
26
+ field :orcid, type: URI
27
+ field :icon, type: URI
28
+ field :icon_url, type: URI
29
+ field :observations_count, type: Integer
30
+ field :identifications_count, type: Integer
31
+ field :journal_posts_count, type: Integer
32
+ field :activity_count, type: Integer
33
+ field :species_count, type: Integer
34
+ field :universal_search_rank, type: Integer
35
+
36
+ ignore :roles # TODO: разобраться
37
+ ignore :preferences
38
+
39
+ def sort_key
40
+ login
41
+ end
42
+
43
+ def self.by_login login
44
+ @entities ||= {}
45
+ results = @entities.values.select { |e| e.login == login.to_s }
46
+ if results.empty?
47
+ data = DB.execute "SELECT * FROM users WHERE login = ?", login.to_s
48
+ results = from_db_rows data
49
+ end
50
+ if results.empty?
51
+ data = API.query 'users/autocomplete', first_only: true, q: login
52
+ results = data.select { |u| u['login'] == login.to_s }.map { |d| parse(d) }
53
+ end
54
+ if results.empty?
55
+ nil
56
+ else
57
+ results.first
58
+ end
59
+ end
60
+
61
+ def to_s
62
+ title = ''
63
+ title = " title=\"#{ name }\"" if name
64
+ "<a#{ title } href=\"https://www.inaturalist.org/people/#{ id }\"><i class=\"glyphicon glyphicon-user\"></i></a> @#{ login }"
65
+ end
66
+
67
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../types/std'
4
+ require_relative '../types/extras'
5
+ require_relative '../entity'
6
+
7
+ autoload :Observation, 'inat/data/entity/observation'
8
+ autoload :User, 'inat/data/entity/user'
9
+
10
+ class Vote < Entity
11
+
12
+ table :votes
13
+
14
+ # field :observation, type: Observation, index: true, required: true
15
+
16
+ field :created_at, type: Time, index: true, required: true
17
+ field :vote_flag, type: Boolean, index: true
18
+ field :user, type: User, index: true
19
+
20
+ ignore :vote_scope # TODO: разобраться
21
+
22
+ end