fias 0.0.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -22
- data/.rubocop.yml +7 -0
- data/.travis.yml +10 -0
- data/Gemfile +1 -1
- data/LICENSE.txt +2 -2
- data/README.md +259 -155
- data/Rakefile +6 -1
- data/config/names.txt +0 -0
- data/config/synonyms.yml +50 -0
- data/examples/create.rb +106 -0
- data/examples/generate_index.rb +63 -0
- data/fias.gemspec +33 -21
- data/lib/fias.rb +197 -10
- data/lib/fias/config.rb +74 -0
- data/lib/fias/import/copy.rb +62 -0
- data/lib/fias/import/dbf.rb +81 -0
- data/lib/fias/import/download_service.rb +37 -0
- data/lib/fias/import/restore_parent_id.rb +51 -0
- data/lib/fias/import/tables.rb +74 -0
- data/lib/fias/name/append.rb +30 -0
- data/lib/fias/name/canonical.rb +42 -0
- data/lib/fias/name/extract.rb +85 -0
- data/lib/fias/name/house_number.rb +71 -0
- data/lib/fias/name/split.rb +60 -0
- data/lib/fias/name/synonyms.rb +93 -0
- data/lib/fias/query.rb +43 -0
- data/lib/fias/query/estimate.rb +67 -0
- data/lib/fias/query/finder.rb +75 -0
- data/lib/fias/query/params.rb +101 -0
- data/lib/fias/railtie.rb +3 -17
- data/lib/fias/version.rb +1 -1
- data/spec/fixtures/ACTSTAT.DBF +0 -0
- data/spec/fixtures/NORDOC99.DBF +0 -0
- data/spec/fixtures/STRSTAT.DBF +0 -0
- data/spec/fixtures/addressing.yml +93 -0
- data/spec/fixtures/query.yml +79 -0
- data/spec/fixtures/query_sanitization.yml +75 -0
- data/spec/fixtures/status_append.yml +60 -0
- data/spec/lib/import/copy_spec.rb +44 -0
- data/spec/lib/import/dbf_spec.rb +28 -0
- data/spec/lib/import/download_service_spec.rb +15 -0
- data/spec/lib/import/restore_parent_id_spec.rb +34 -0
- data/spec/lib/import/tables_spec.rb +26 -0
- data/spec/lib/name/append_spec.rb +14 -0
- data/spec/lib/name/canonical_spec.rb +20 -0
- data/spec/lib/name/extract_spec.rb +67 -0
- data/spec/lib/name/house_number_spec.rb +45 -0
- data/spec/lib/name/query_spec.rb +21 -0
- data/spec/lib/name/split_spec.rb +15 -0
- data/spec/lib/name/synonyms_spec.rb +51 -0
- data/spec/lib/query/params_spec.rb +15 -0
- data/spec/lib/query_spec.rb +27 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/support/db.rb +30 -0
- data/spec/support/query.rb +13 -0
- data/tasks/db.rake +52 -0
- data/tasks/download.rake +15 -0
- metadata +246 -64
- data/lib/fias/active_record/address_object.rb +0 -231
- data/lib/fias/active_record/address_object_type.rb +0 -15
- data/lib/fias/dbf_wrapper.rb +0 -90
- data/lib/fias/importer.rb +0 -30
- data/lib/fias/importer/base.rb +0 -59
- data/lib/fias/importer/pg.rb +0 -81
- data/lib/fias/importer/sqlite.rb +0 -38
- data/lib/generators/fias/migration.rb +0 -34
- data/lib/generators/fias/templates/create_fias_tables.rb +0 -5
- data/tasks/fias.rake +0 -68
@@ -1,231 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
# TODO: Поделить на файлы
|
3
|
-
module Fias
|
4
|
-
class AddressObject < ActiveRecord::Base
|
5
|
-
# TODO: Тут надо понять как префикс передать
|
6
|
-
if defined?(Rails)
|
7
|
-
self.table_name = "#{Rails.application.config.fias.prefix}_address_objects"
|
8
|
-
else
|
9
|
-
self.table_name = "fias_address_objects"
|
10
|
-
end
|
11
|
-
|
12
|
-
self.primary_key = 'aoid'
|
13
|
-
|
14
|
-
alias_attribute :name, :formalname
|
15
|
-
alias_attribute :id, :aoid
|
16
|
-
|
17
|
-
# Родительские объекты (Ленобласть для Лодейнопольского района)
|
18
|
-
# Для "проезд 1-й Конной Лахты 2-й" - Санкт-Петербург и Ленинград.
|
19
|
-
# Блядь, 1-й проезд 2-й Конной Лахты, ебануться!
|
20
|
-
# http://maps.yandex.ru/?text=%D0%A0%D0%BE%D1%81%D1%81%D0%B8%D1%8F%2C%20%D0%A1%D0%B0%D0%BD%D0%BA%D1%82-%D0%9F%D0%B5%D1%82%D0%B5%D1%80%D0%B1%D1%83%D1%80%D0%B3%2C%202-%D0%B9%20%D0%BF%D1%80%D0%BE%D0%B5%D0%B7%D0%B4%201-%D0%B9%20%D0%9A%D0%BE%D0%BD%D0%BD%D0%BE%D0%B9%20%D0%9B%D0%B0%D1%85%D1%82%D1%8B&sll=30.123296%2C60.007056&ll=30.123848%2C60.007475&spn=0.010085%2C0.006017&z=17&l=map
|
21
|
-
has_many :parents,
|
22
|
-
class_name: 'AddressObject',
|
23
|
-
foreign_key: 'aoguid',
|
24
|
-
primary_key: 'parentguid'
|
25
|
-
|
26
|
-
# Дочерние объекты (например, улицы для города)
|
27
|
-
has_many :children,
|
28
|
-
class_name: 'AddressObject',
|
29
|
-
primary_key: 'aoguid',
|
30
|
-
foreign_key: 'parentguid'
|
31
|
-
|
32
|
-
# Предыдущие исторические версии названия (Ленинград для Питера)
|
33
|
-
# Может быть несколько, если произошло слияние
|
34
|
-
has_many :previous_versions,
|
35
|
-
class_name: 'AddressObject',
|
36
|
-
foreign_key: 'aoid',
|
37
|
-
primary_key: 'previd'
|
38
|
-
|
39
|
-
# Следующая исторические версии названия (Питер для Ленинграда)
|
40
|
-
# Может быть несколько, если произошло разделение
|
41
|
-
has_many :next_versions,
|
42
|
-
class_name: 'AddressObject',
|
43
|
-
foreign_key: 'aoid',
|
44
|
-
primary_key: 'nextid'
|
45
|
-
|
46
|
-
# Полное наименование типа объекта (город, улица)
|
47
|
-
belongs_to :address_object_type,
|
48
|
-
class_name: '::Fias::AddressObjectType',
|
49
|
-
foreign_key: 'shortname',
|
50
|
-
primary_key: 'scname'
|
51
|
-
|
52
|
-
# Актуальные записи (активные в настоящий момент)
|
53
|
-
# Проверено, что livestatus уже достаточен для идентификации
|
54
|
-
# актуальных объектов, вопреки показаниям вики.
|
55
|
-
scope :actual, where(livestatus: 1)
|
56
|
-
|
57
|
-
# Выбирает объекты определенного уровня, аргументы - символы из хеша
|
58
|
-
# AOLEVELS
|
59
|
-
scope :leveled, ->(*levels) {
|
60
|
-
levels = Array.wrap(levels).map { |level| AOLEVELS[level] }
|
61
|
-
where(aolevel: levels)
|
62
|
-
}
|
63
|
-
|
64
|
-
scope :sorted, order('formalname ASC')
|
65
|
-
scope :with_types, includes(:address_object_type)
|
66
|
-
scope :matching, ->(name) {
|
67
|
-
scope = if self.connection.adapter_name
|
68
|
-
where(%{ "formalname" @@ ? OR ? @@ "formalname"}, name, name)
|
69
|
-
else
|
70
|
-
where('formalname LIKE ?', "%#{name}%")
|
71
|
-
end
|
72
|
-
|
73
|
-
# Первыми идут центральные и крупные поселения
|
74
|
-
scope.order('aolevel ASC, centstatus DESC')
|
75
|
-
}
|
76
|
-
|
77
|
-
scope :same_region, ->(address_object) {
|
78
|
-
where(regioncode: address_object.regioncode)
|
79
|
-
}
|
80
|
-
|
81
|
-
# Значимые поселения
|
82
|
-
scope :central, where('centstatus > 0')
|
83
|
-
|
84
|
-
def has_history?
|
85
|
-
previd.present?
|
86
|
-
end
|
87
|
-
|
88
|
-
def actual?
|
89
|
-
livestatus == 1
|
90
|
-
end
|
91
|
-
|
92
|
-
# Актуальный родитель. Для 1-го проезда 2-й Конной Лахты - только Питер.
|
93
|
-
def parent
|
94
|
-
parents.actual.first
|
95
|
-
end
|
96
|
-
|
97
|
-
def aolevel_sym
|
98
|
-
AOLEVELS.key(aolevel)
|
99
|
-
end
|
100
|
-
|
101
|
-
# Название с сокращением / полным наименованием города
|
102
|
-
# "г. Санкт-Петербург" / "город Санкт-Петербург"
|
103
|
-
#
|
104
|
-
# Для работы метода требуется загрузить таблицу :address_object_types
|
105
|
-
#
|
106
|
-
# Параметр - режим:
|
107
|
-
# :obviously - очевидный режим". В этом режиме "Дагестан" вместо
|
108
|
-
# "Республика Дагестан", "АО" вместо "Автономная область",
|
109
|
-
# но всегда "Краснодарский край"
|
110
|
-
# :short - дописываются "г." и "респ."
|
111
|
-
# :long - дописываются "город" и "Республика"
|
112
|
-
#
|
113
|
-
# Пример:
|
114
|
-
# .abbrevated(:obviously) # Тульская область, Хакасия, Еврейская Аобл.
|
115
|
-
# .abbrevated(:short) # Тульская область, Респ. Хакасия, Еврейская Аобл.
|
116
|
-
# .abbrevated(:long) # Тульская область, Республика Хакасия, Еврейская автономная область
|
117
|
-
def abbrevated(mode = :obviously)
|
118
|
-
return name if address_object_type.blank? || shortname.blank?
|
119
|
-
|
120
|
-
case aolevel_sym
|
121
|
-
when :region
|
122
|
-
# "Ханты-Мансийский Автономный округ - Югра"
|
123
|
-
return name if regioncode == '86'
|
124
|
-
|
125
|
-
ending = name[-2..-1]
|
126
|
-
|
127
|
-
# Если название кончается на -ая -ий - по правилам русского языка
|
128
|
-
# нужно дописать в конец "область", "край"
|
129
|
-
must_append = SHN_MUST_APPEND_TO_ENDINGS.include?(ending) ||
|
130
|
-
shortname == 'Чувашия'
|
131
|
-
|
132
|
-
must_abbrevate = must_append ||
|
133
|
-
shortname == 'Чувашия' ||
|
134
|
-
mode != :obviously
|
135
|
-
|
136
|
-
return name unless must_abbrevate
|
137
|
-
|
138
|
-
abbr = case mode
|
139
|
-
when :short
|
140
|
-
shortname
|
141
|
-
when :long
|
142
|
-
address_object_type.name
|
143
|
-
when :obviously
|
144
|
-
if SHN_LEAVE_SHORTS_INTACT.include?(shortname)
|
145
|
-
shortname
|
146
|
-
else
|
147
|
-
address_object_type.name
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
# Точка не ставится для АО, края, Чувашии и длинных названий
|
152
|
-
abbr = "#{abbr}." if mode == :short && not(shortname.in?(SHN_MUST_NOT_APPEND_DOT))
|
153
|
-
|
154
|
-
if not(must_append) && must_abbrevate
|
155
|
-
"#{abbr} #{name}"
|
156
|
-
else
|
157
|
-
# "Республика" => "республика", но "АО" остается
|
158
|
-
abbr = abbr.mb_chars.downcase if not(shortname.in?(SHN_LEAVE_SHORTS_INTACT))
|
159
|
-
"#{name} #{abbr}"
|
160
|
-
end
|
161
|
-
else
|
162
|
-
abbr = if mode == :long
|
163
|
-
address_object_type.try(:name)
|
164
|
-
else
|
165
|
-
shortname
|
166
|
-
end
|
167
|
-
"#{abbr} #{name}"
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
class << self
|
172
|
-
# Подробное описание см. в README
|
173
|
-
# TODO: OPERSTATUS
|
174
|
-
def match_existing(scope, fias_key_accessor, title_field_accessor, &block)
|
175
|
-
scope.find_each do |record|
|
176
|
-
aoid = record.send(fias_key_accessor)
|
177
|
-
title = record.send(title_field_accessor)
|
178
|
-
|
179
|
-
if aoid.present?
|
180
|
-
match = scoped.find_by_aoid(aoid)
|
181
|
-
|
182
|
-
if match.present?
|
183
|
-
unless match.actual?
|
184
|
-
next_versions = match.next_versions
|
185
|
-
|
186
|
-
if next_versions.empty?
|
187
|
-
yield(:deleted, record, match)
|
188
|
-
elsif next_versions.count == 1
|
189
|
-
next_version = next_versions.first
|
190
|
-
previous_versions_of_current = next_version.previous_versions
|
191
|
-
|
192
|
-
if previous_versions_of_current.count == 1
|
193
|
-
yield(:updated, record, next_version)
|
194
|
-
else
|
195
|
-
yield(:joined, record, next_version, previous_versions_of_current)
|
196
|
-
end
|
197
|
-
elsif next_versions.count > 1
|
198
|
-
yield(:split, record, next_versions)
|
199
|
-
end
|
200
|
-
end
|
201
|
-
end
|
202
|
-
else
|
203
|
-
matches = scoped.matching(title)
|
204
|
-
yield(:match, record, *matches)
|
205
|
-
end
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
def match_missing(scope, address_object_key_field, &block)
|
210
|
-
scoped.each do |address_object|
|
211
|
-
unless scope.where(address_object_key_field => address_object.aoid).exists?
|
212
|
-
yield(:created, nil, address_object)
|
213
|
-
end
|
214
|
-
end
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
# Коды уровня адресного объекта
|
219
|
-
AOLEVELS = {
|
220
|
-
region: 1, autonomy: 2, district: 3, city: 4,
|
221
|
-
territory: 5, settlement: 6, street: 7,
|
222
|
-
additional_territory: 90, additional_territory_slave: 91
|
223
|
-
}
|
224
|
-
|
225
|
-
# Дописывать сокращения обязательно, "Самарская" выглядит странно,
|
226
|
-
# всегда должно быть "Самарская область", а "Дагестан" понятно и так.
|
227
|
-
SHN_MUST_APPEND_TO_ENDINGS = %w(ая ий)
|
228
|
-
SHN_MUST_NOT_APPEND_DOT = %w(край АО Чувашия) # Не дописывать точку к сокращению
|
229
|
-
SHN_LEAVE_SHORTS_INTACT = %w(АО Аобл Чувашия) # В очевидном режиме не разворачивать "АО" в "Автономная область"
|
230
|
-
end
|
231
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
module Fias
|
3
|
-
class AddressObjectType < ActiveRecord::Base
|
4
|
-
# TODO: Тут надо понять как префикс передать
|
5
|
-
if defined?(Rails)
|
6
|
-
self.table_name = "#{Rails.application.config.fias.prefix}_address_object_types"
|
7
|
-
else
|
8
|
-
self.table_name = "fias_address_object_types"
|
9
|
-
end
|
10
|
-
self.primary_key = 'scname'
|
11
|
-
|
12
|
-
alias_attribute :name, :socrname
|
13
|
-
alias_attribute :abbrevation, :scname
|
14
|
-
end
|
15
|
-
end
|
data/lib/fias/dbf_wrapper.rb
DELETED
@@ -1,90 +0,0 @@
|
|
1
|
-
module Fias
|
2
|
-
# Класс для доступа к DBF-файлам ФИАС.
|
3
|
-
#
|
4
|
-
# Пример:
|
5
|
-
# wrapper = Fias::DbfWrapper.new('tmp/fias')
|
6
|
-
# wrapper.address_objects.record_count
|
7
|
-
# wrapper.address_objects.each { |record| record.attributes }
|
8
|
-
#
|
9
|
-
# TODO: Добавить в инишилайзер tables, чтобы при создании проверять
|
10
|
-
# их наличие на диске
|
11
|
-
class DbfWrapper
|
12
|
-
# Открывает DBF-файлы ФИАСа
|
13
|
-
def initialize(pathspec)
|
14
|
-
unless Dir.exists?(pathspec)
|
15
|
-
raise ArgumentError, 'FIAS database path does not exists'
|
16
|
-
end
|
17
|
-
self.pathspec = pathspec
|
18
|
-
|
19
|
-
DBF_ACCESSORS.each do |accessor, dbf_name|
|
20
|
-
filename = File.join(pathspec, dbf_name)
|
21
|
-
|
22
|
-
if File.exists?(filename)
|
23
|
-
dbf = DBF::Table.new(filename, nil, DEFAULT_ENCODING)
|
24
|
-
send("#{accessor}=", dbf)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
# Возвращает хеш {аксессор dbf-таблицы => таблица}
|
30
|
-
# На входе массив названий таблиц (строки), без параметров - все
|
31
|
-
# Макрос houses вернет все таблицы домов
|
32
|
-
#
|
33
|
-
# Пример:
|
34
|
-
# wrapper = Fias::DbfWrapper.new('tmp/fias')
|
35
|
-
# wrapper.tables(%w(address_objects address_object_types houses))
|
36
|
-
# ... {
|
37
|
-
# ... address_objects: Dbf::Table(...),
|
38
|
-
# ... address_object_types: Dbf::Table(...),
|
39
|
-
# ... house1: Dbf::Table(...),
|
40
|
-
# ... ...
|
41
|
-
# ... house99: Dbf::Table(...)
|
42
|
-
# ... }
|
43
|
-
def tables(*only)
|
44
|
-
only = only.first if only.first.is_a?(Array)
|
45
|
-
only = only.map(&:to_s)
|
46
|
-
|
47
|
-
hash = DBF_ACCESSORS.keys.map do |accessor|
|
48
|
-
accessor_s = accessor.to_s
|
49
|
-
is_houses = only.include?('houses') && accessor_s.starts_with?('house')
|
50
|
-
|
51
|
-
if only.include?(accessor_s) || is_houses
|
52
|
-
[accessor, send(accessor)]
|
53
|
-
end
|
54
|
-
end
|
55
|
-
Hash[*hash.compact.flatten]
|
56
|
-
end
|
57
|
-
|
58
|
-
# { house01: "HOUSE01"..house99: "HOUSE99" }
|
59
|
-
HOUSES_ACCESSORS = Hash[*(1..99).map { |n|
|
60
|
-
[("house%0.2d" % n).to_sym, "HOUSE%0.2d.dbf" % n]
|
61
|
-
}.flatten]
|
62
|
-
|
63
|
-
# Таблица соответствий аттрибутов класса DBF-файлам
|
64
|
-
DBF_ACCESSORS = {
|
65
|
-
address_object_types: 'SOCRBASE.DBF',
|
66
|
-
current_statuses: 'CURENTST.DBF',
|
67
|
-
actual_statuses: 'ACTSTAT.DBF',
|
68
|
-
operation_statuses: 'OPERSTAT.DBF',
|
69
|
-
center_statuses: 'CENTERST.DBF',
|
70
|
-
interval_statuses: 'INTVSTAT.DBF',
|
71
|
-
estate_statues: 'ESTSTAT.DBF',
|
72
|
-
structure_statuses: 'STRSTAT.DBF',
|
73
|
-
address_objects: 'ADDROBJ.DBF',
|
74
|
-
house_intervals: 'HOUSEINT.DBF',
|
75
|
-
landmarks: 'LANDMARK.DBF'
|
76
|
-
}.merge(
|
77
|
-
HOUSES_ACCESSORS
|
78
|
-
)
|
79
|
-
|
80
|
-
DEFAULT_ENCODING = Encoding::CP866
|
81
|
-
|
82
|
-
attr_reader *DBF_ACCESSORS.keys
|
83
|
-
attr_reader :houses
|
84
|
-
|
85
|
-
private
|
86
|
-
attr_accessor :pathspec
|
87
|
-
attr_writer *DBF_ACCESSORS.keys
|
88
|
-
attr_writer :houses
|
89
|
-
end
|
90
|
-
end
|
data/lib/fias/importer.rb
DELETED
@@ -1,30 +0,0 @@
|
|
1
|
-
module Fias
|
2
|
-
module Importer
|
3
|
-
# Фактори-метод, возвращает ссылку на объект импортера
|
4
|
-
# Принимает параметры:
|
5
|
-
# :adapter - название адаптера
|
6
|
-
# :connection - прямое соединение с базой данных (connection.raw_connection)
|
7
|
-
def self.build(options = {})
|
8
|
-
adapter = options.delete(:adapter) ||
|
9
|
-
ActiveRecord::Base.connection_config[:adapter]
|
10
|
-
|
11
|
-
connection = options.delete(:connection) ||
|
12
|
-
ActiveRecord::Base.connection.raw_connection
|
13
|
-
|
14
|
-
case adapter
|
15
|
-
when 'postgresql'
|
16
|
-
Pg.new(
|
17
|
-
connection,
|
18
|
-
options
|
19
|
-
)
|
20
|
-
when 'sqlite3'
|
21
|
-
Sqlite.new(
|
22
|
-
connection,
|
23
|
-
options
|
24
|
-
)
|
25
|
-
else
|
26
|
-
raise 'Only postgres & sqlite supported now, fork'
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
data/lib/fias/importer/base.rb
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
module Fias
|
2
|
-
module Importer
|
3
|
-
class Base
|
4
|
-
def initialize(connection, options = {})
|
5
|
-
self.prefix = options.delete(:prefix) || 'fias'
|
6
|
-
self.connection = connection
|
7
|
-
end
|
8
|
-
|
9
|
-
# На входе - хеш (имя таблицы => dbf)
|
10
|
-
def schema(tables)
|
11
|
-
"".tap do |s|
|
12
|
-
tables.each do |name, dbf|
|
13
|
-
if dbf.present?
|
14
|
-
s << schema_for(name, table_name(name), dbf)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def schema_for(name, table_name, dbf)
|
21
|
-
"".tap do |s|
|
22
|
-
s << %{create_table "#{table_name}", id: false do |t|\n}
|
23
|
-
s << schema_columns(name, dbf)
|
24
|
-
s << "end\n"
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def import(tables, &block)
|
29
|
-
tables.each do |name, dbf|
|
30
|
-
if dbf
|
31
|
-
import_table(name, table_name(name), dbf, &block)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def import_table(name, table_name, dbf, &block)
|
37
|
-
raise NotImplementedError, 'Implement this in concrete class'
|
38
|
-
end
|
39
|
-
|
40
|
-
protected
|
41
|
-
attr_accessor :prefix, :connection
|
42
|
-
|
43
|
-
private
|
44
|
-
def table_name(name)
|
45
|
-
"#{prefix}_#{name}"
|
46
|
-
end
|
47
|
-
|
48
|
-
def schema_columns(accessor, table)
|
49
|
-
"".tap do |s|
|
50
|
-
table.columns.each do |column|
|
51
|
-
column_name = column.name.downcase
|
52
|
-
column_def = column.schema_definition
|
53
|
-
s << " t.column #{column_def}"
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
data/lib/fias/importer/pg.rb
DELETED
@@ -1,81 +0,0 @@
|
|
1
|
-
module Fias
|
2
|
-
module Importer
|
3
|
-
# Класс для импорта данных из ФИАС в PostgreSQL.
|
4
|
-
# Используется COPY FROM STDIN.
|
5
|
-
class Pg < Base
|
6
|
-
def schema_for(name, table_name, dbf)
|
7
|
-
super + alter_table_to_pg_uuids(name, table_name)
|
8
|
-
end
|
9
|
-
|
10
|
-
private
|
11
|
-
def alter_table_to_pg_uuids(name, table_name)
|
12
|
-
"".tap do |s|
|
13
|
-
columns = CONVERT_TO_UUID[name]
|
14
|
-
if columns.present?
|
15
|
-
columns.each do |column|
|
16
|
-
s << %{ActiveRecord::Base.connection.execute("ALTER TABLE #{table_name} ALTER COLUMN #{column} TYPE UUID USING CAST (#{column} AS UUID);")\n}
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def import_table(name, table_name, dbf, &block)
|
23
|
-
fields = table_fields(dbf)
|
24
|
-
columns = table_columns(fields)
|
25
|
-
|
26
|
-
truncate_table(table_name)
|
27
|
-
copy_from_stdin(table_name, columns)
|
28
|
-
|
29
|
-
dbf.each_with_index do |record, index|
|
30
|
-
should_import = yield(name, record.attributes, index) if block_given?
|
31
|
-
|
32
|
-
unless should_import === false
|
33
|
-
data = record.to_a
|
34
|
-
put_data(data)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
put_copy_end
|
39
|
-
end
|
40
|
-
|
41
|
-
def truncate_table(table_name)
|
42
|
-
connection.exec "TRUNCATE TABLE #{table_name};"
|
43
|
-
end
|
44
|
-
|
45
|
-
def table_fields(table)
|
46
|
-
table.columns.map(&:name).map(&:downcase)
|
47
|
-
end
|
48
|
-
|
49
|
-
def table_columns(fields)
|
50
|
-
fields.join(', ')
|
51
|
-
end
|
52
|
-
|
53
|
-
def copy_from_stdin(table_name, columns)
|
54
|
-
sql = "COPY #{table_name} (#{columns}) FROM STDIN NULL AS '-nil-'\n"
|
55
|
-
connection.exec(sql)
|
56
|
-
end
|
57
|
-
|
58
|
-
def put_data(data)
|
59
|
-
data.map! { |item| item == "" ? '-nil-' : item }
|
60
|
-
line = data.join("\t") + "\n"
|
61
|
-
connection.put_copy_data(line)
|
62
|
-
end
|
63
|
-
|
64
|
-
def put_copy_end
|
65
|
-
connection.put_copy_end
|
66
|
-
|
67
|
-
while res = connection.get_result
|
68
|
-
result_status = res.res_status(res.result_status)
|
69
|
-
unless result_status == 'PGRES_COMMAND_OK'
|
70
|
-
raise "Import failure: #{result_status}"
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
# Эти поля нужно отконвертировать в тип UUID после создания
|
77
|
-
CONVERT_TO_UUID = {
|
78
|
-
address_objects: %w(aoguid aoid previd nextid parentguid)
|
79
|
-
}
|
80
|
-
end
|
81
|
-
end
|