worlddb-models 2.1.0

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 (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gemtest +0 -0
  3. data/HISTORY.md +4 -0
  4. data/Manifest.txt +43 -0
  5. data/README.md +85 -0
  6. data/Rakefile +44 -0
  7. data/lib/worlddb/deleter.rb +32 -0
  8. data/lib/worlddb/matcher.rb +143 -0
  9. data/lib/worlddb/models/city.rb +240 -0
  10. data/lib/worlddb/models/city_comp.rb +27 -0
  11. data/lib/worlddb/models/continent.rb +41 -0
  12. data/lib/worlddb/models/continent_comp.rb +24 -0
  13. data/lib/worlddb/models/country.rb +328 -0
  14. data/lib/worlddb/models/country_code.rb +41 -0
  15. data/lib/worlddb/models/country_comp.rb +35 -0
  16. data/lib/worlddb/models/forward.rb +57 -0
  17. data/lib/worlddb/models/lang.rb +18 -0
  18. data/lib/worlddb/models/lang_comp.rb +23 -0
  19. data/lib/worlddb/models/name.rb +13 -0
  20. data/lib/worlddb/models/place.rb +16 -0
  21. data/lib/worlddb/models/region.rb +176 -0
  22. data/lib/worlddb/models/region_comp.rb +26 -0
  23. data/lib/worlddb/models/tagdb/tag.rb +16 -0
  24. data/lib/worlddb/models/tagdb/tagging.rb +15 -0
  25. data/lib/worlddb/models/usage.rb +17 -0
  26. data/lib/worlddb/models.rb +200 -0
  27. data/lib/worlddb/patterns.rb +54 -0
  28. data/lib/worlddb/reader.rb +224 -0
  29. data/lib/worlddb/reader_file.rb +86 -0
  30. data/lib/worlddb/reader_zip.rb +160 -0
  31. data/lib/worlddb/readers/city.rb +81 -0
  32. data/lib/worlddb/readers/country.rb +78 -0
  33. data/lib/worlddb/readers/lang.rb +107 -0
  34. data/lib/worlddb/readers/region.rb +79 -0
  35. data/lib/worlddb/readers/usage.rb +98 -0
  36. data/lib/worlddb/schema.rb +202 -0
  37. data/lib/worlddb/stats.rb +31 -0
  38. data/lib/worlddb/version.rb +23 -0
  39. data/test/helper.rb +26 -0
  40. data/test/test_fixture_matchers.rb +112 -0
  41. data/test/test_model_city.rb +60 -0
  42. data/test/test_model_comp.rb +48 -0
  43. data/test/test_model_country.rb +53 -0
  44. data/test/test_model_region.rb +50 -0
  45. data/test/test_models.rb +35 -0
  46. metadata +252 -0
@@ -0,0 +1,176 @@
1
+ # encoding: UTF-8
2
+
3
+ module WorldDb
4
+ module Model
5
+
6
+
7
+ class Region < ActiveRecord::Base
8
+
9
+ extend TextUtils::TagHelper # will add self.find_tags, self.find_tags_in_attribs!, etc.
10
+
11
+ # NB: use extend - is_<type>? become class methods e.g. self.is_<type>? for use in
12
+ # self.create_or_update_from_values
13
+ extend TextUtils::ValueHelper # e.g. is_year?, is_region?, is_address?, is_taglist? etc.
14
+
15
+ belongs_to :place, class_name: 'Place', foreign_key: 'place_id'
16
+ belongs_to :country, class_name: 'Country', foreign_key: 'country_id'
17
+
18
+ has_many :cities, class_name: 'City', foreign_key: 'region_id'
19
+
20
+ has_many_tags
21
+
22
+ validates :key, format: { with: /#{REGION_KEY_PATTERN}/, message: REGION_KEY_PATTERN_MESSAGE }
23
+ validates :code, format: { with: /#{REGION_CODE_PATTERN}/, message: REGION_CODE_PATTERN_MESSAGE }, allow_nil: true
24
+
25
+
26
+ before_create :on_create
27
+ before_update :on_update
28
+
29
+ def on_create
30
+ place_rec = Place.create!( name: name, kind: place_kind )
31
+ self.place_id = place_rec.id
32
+ end
33
+
34
+ def on_update
35
+ ## fix/todo: check - if name or kind changed - only update if changed ?? why? why not??
36
+ place.update_attributes!( name: name, kind: place_kind )
37
+ end
38
+
39
+ def place_kind # use place_kind_of_code ??
40
+ 'ADM1'
41
+ end
42
+
43
+
44
+ def all_names( opts={} )
45
+ ### fix:
46
+ ## allow to passing in sep or separator e.g. | or other
47
+
48
+ return name if alt_names.blank?
49
+
50
+ buf = ''
51
+ buf << name
52
+ buf << ' | '
53
+ buf << alt_names.split('|').join(' | ')
54
+ buf
55
+ end
56
+
57
+
58
+
59
+ def self.create_or_update_from_values( values, more_attribs={} )
60
+
61
+ ## key & title & country required
62
+ attribs, more_values = find_key_n_title( values )
63
+ attribs = attribs.merge( more_attribs )
64
+
65
+ ## check for optional values
66
+ Region.create_or_update_from_attribs( attribs, more_values )
67
+ end
68
+
69
+
70
+ def self.create_or_update_from_attribs( new_attributes, values, opts={} )
71
+
72
+ ## opts e.g. :skip_tags true|false
73
+
74
+ ## fix: add/configure logger for ActiveRecord!!!
75
+ logger = LogKernel::Logger.root
76
+
77
+ value_numbers = []
78
+ value_tag_keys = []
79
+ value_cities = []
80
+
81
+ ### check for "default" tags - that is, if present new_attributes[:tags] remove from hash
82
+ value_tag_keys += find_tags_in_attribs!( new_attributes )
83
+
84
+ ## check for optional values
85
+ values.each_with_index do |value,index|
86
+ if match_country( value ) do |country| # country:
87
+ new_attributes[ :country_id ] = country.id
88
+ end
89
+ elsif match_km_squared( value ) do |num| # allow numbers like 453 km²
90
+ value_numbers << num
91
+ end
92
+ elsif match_number( value ) do |num| # numeric (nb: can use any _ or spaces inside digits e.g. 1_000_000 or 1 000 000)
93
+ value_numbers << num
94
+ end
95
+ elsif value =~ /#{REGION_CODE_PATTERN}/ ## assume two or three-letter code
96
+ new_attributes[ :code ] = value
97
+ elsif (values.size==(index+1)) && is_taglist?( value ) # tags must be last entry
98
+ logger.debug " found tags: >>#{value}<<"
99
+ value_tag_keys += find_tags( value )
100
+ else
101
+
102
+ ### assume it is the capital city - mark it for auto add
103
+ value_cities << value
104
+ next
105
+
106
+ # issue warning: unknown type for value
107
+ # logger.warn "unknown type for value >#{value}<"
108
+ end
109
+ end # each value
110
+
111
+ if value_numbers.size > 0
112
+ new_attributes[ :area ] = value_numbers[0]
113
+ new_attributes[ :pop ] = value_numbers[1]
114
+ end # if value_numbers.size > 0
115
+
116
+ ## todo: assert that country_id is present/valid, that is, NOT null
117
+ rec = Region.find_by_key_and_country_id( new_attributes[ :key ], new_attributes[ :country_id] )
118
+
119
+ if rec.present?
120
+ logger.debug "update Region #{rec.id}-#{rec.key}:"
121
+ else
122
+ logger.debug "create Region:"
123
+ rec = Region.new
124
+ end
125
+
126
+ logger.debug new_attributes.to_json
127
+
128
+ rec.update_attributes!( new_attributes )
129
+
130
+ #################
131
+ # auto add capital cities
132
+
133
+ City.create_or_update_from_titles( value_cities,
134
+ region_id: rec.id,
135
+ country_id: rec.country_id )
136
+
137
+ ### todo/fix: add captial ref to country/region
138
+ ## todo/fix: use update_from_title and only allow one capital city
139
+
140
+
141
+ ##################
142
+ # add taggings
143
+
144
+ ## todo/fix: reuse - move add taggings into method etc.
145
+
146
+ if value_tag_keys.size > 0
147
+
148
+ if opts[:skip_tags].present?
149
+ logger.debug " skipping add taggings (flag skip_tag)"
150
+ else
151
+ value_tag_keys.uniq! # remove duplicates
152
+ logger.debug " adding #{value_tag_keys.size} taggings: >>#{value_tag_keys.join('|')}<<..."
153
+
154
+ ### fix/todo: check tag_ids and only update diff (add/remove ids)
155
+
156
+ value_tag_keys.each do |key|
157
+ tag = Tag.find_by_key( key )
158
+ if tag.nil? # create tag if it doesn't exit
159
+ logger.debug " creating tag >#{key}<"
160
+ tag = Tag.create!( key: key )
161
+ end
162
+ rec.tags << tag
163
+ end
164
+ end
165
+ end
166
+
167
+ rec
168
+
169
+ end # method create_or_update_from_values
170
+
171
+
172
+
173
+ end # class Region
174
+
175
+ end # module Model
176
+ end # module WorldDb
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+
3
+ module WorldDb
4
+ module Model
5
+
6
+ #############################################################
7
+ # collect depreciated or methods for future removal here
8
+ # - keep for now for commpatibility (for old code)
9
+
10
+ class Region
11
+
12
+ def title() name; end
13
+ def title=(value) self.name = value; end
14
+
15
+ scope :by_title, ->{ order( 'name asc' ) } # order by title (a-z)
16
+
17
+ def synonyms() alt_names; end
18
+ def synonyms=(value) self.alt_names = value; end
19
+
20
+ def title_w_synonyms( opts={} ) all_names( opts ); end # depreciated: use all_names instead
21
+
22
+
23
+ end # class Region
24
+
25
+ end # module Model
26
+ end # module WorldDb
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ module TagDb
4
+ module Model
5
+
6
+ class Tag
7
+
8
+ has_many :cities, :through => :taggings, :source => :taggable, source_type: 'WorldDb::Model::City', class_name: 'WorldDb::Model::City'
9
+ has_many :countries, :through => :taggings, :source => :taggable, source_type: 'WorldDb::Model::Country', class_name: 'WorldDb::Model::Country'
10
+ has_many :regions, :through => :taggings, :source => :taggable, source_type: 'WorldDb::Model::Region', class_name: 'WorldDb::Model::Region'
11
+
12
+ end # class Tag
13
+
14
+ end # module Model
15
+ end # module WorldDb
16
+
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ module TagDb
4
+ module Model
5
+
6
+
7
+ class Tagging
8
+
9
+ ## add some code here
10
+
11
+ end # class Tagging
12
+
13
+
14
+ end # module Model
15
+ end # module WorldDb
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+
3
+ module WorldDb
4
+ module Model
5
+
6
+
7
+ class Usage < ActiveRecord::Base
8
+
9
+ belongs_to :country
10
+ belongs_to :lang
11
+
12
+ end # class Usage
13
+
14
+
15
+ end # module Model
16
+ end # module WorldDb
17
+
@@ -0,0 +1,200 @@
1
+ # encoding: UTF-8
2
+
3
+ # core and stlibs
4
+
5
+ require 'pp'
6
+ require 'fileutils'
7
+ require 'uri'
8
+ require 'erb'
9
+ require 'json'
10
+ require 'yaml'
11
+
12
+
13
+ # 3rd party gems / libs
14
+
15
+ require 'zip' ## rubyzip gem
16
+
17
+ require 'props' # see github.com/rubylibs/props
18
+ require 'logutils' # see github.com/rubylibs/logutils
19
+ require 'textutils' # see github.com/rubylibs/textutils
20
+
21
+
22
+ require 'active_record' ## todo: add sqlite3? etc.
23
+
24
+ ## add more activerecords addons/utils
25
+ require 'tagutils'
26
+ require 'activerecord/utils'
27
+ require 'props/activerecord' # includes ConfDb (ConfDb::Model::Prop, etc.)
28
+ require 'logutils/activerecord' # includes LogDb (LogDb::Model::Log, etc.)
29
+
30
+
31
+
32
+ # our own code
33
+
34
+ require 'worlddb/version' # always goes first
35
+
36
+ require 'worlddb/patterns' # regex patterns (constants)
37
+ require 'worlddb/models/forward'
38
+
39
+ require 'worlddb/models/name'
40
+ require 'worlddb/models/place'
41
+ require 'worlddb/models/continent'
42
+ require 'worlddb/models/continent_comp'
43
+ require 'worlddb/models/country'
44
+ require 'worlddb/models/country_comp'
45
+ require 'worlddb/models/country_code'
46
+ require 'worlddb/models/region'
47
+ require 'worlddb/models/region_comp'
48
+ require 'worlddb/models/city'
49
+ require 'worlddb/models/city_comp'
50
+ require 'worlddb/models/lang'
51
+ require 'worlddb/models/lang_comp'
52
+ require 'worlddb/models/usage'
53
+
54
+ require 'worlddb/models/tagdb/tag'
55
+ require 'worlddb/models/tagdb/tagging'
56
+
57
+
58
+
59
+ require 'worlddb/schema' # NB: requires worlddb/models (include WorldDB::Models)
60
+ require 'worlddb/matcher'
61
+
62
+ require 'worlddb/readers/lang'
63
+ require 'worlddb/readers/usage'
64
+ require 'worlddb/readers/country'
65
+ require 'worlddb/readers/region'
66
+ require 'worlddb/readers/city'
67
+
68
+ require 'worlddb/reader'
69
+ require 'worlddb/reader_file'
70
+ require 'worlddb/reader_zip'
71
+ require 'worlddb/deleter'
72
+ require 'worlddb/stats'
73
+
74
+
75
+ module WorldDb
76
+
77
+ def self.create
78
+ CreateDb.new.up
79
+ ConfDb::Model::Prop.create!( key: 'db.schema.world.version', value: VERSION )
80
+ end
81
+
82
+ def self.create_all
83
+ LogDb.create # add logs table
84
+ ConfDb.create # add props table
85
+ TagDb.create # add tags, taggings table
86
+ WorldDb.create
87
+ end
88
+
89
+
90
+ def self.read( ary, include_path )
91
+ reader = Reader.new( include_path )
92
+ ary.each do |name|
93
+ reader.load( name )
94
+ end
95
+ end
96
+
97
+
98
+ def self.read_setup( setup, include_path, opts={} )
99
+ reader = Reader.new( include_path, opts )
100
+ reader.load_setup( setup )
101
+ end
102
+
103
+ def self.read_setup_from_zip( zip_name, setup, include_path, opts={} ) ## todo/check - use a better (shorter) name ??
104
+ reader = ZipReader.new( zip_name, include_path, opts )
105
+ reader.load_setup( setup )
106
+ reader.close
107
+ end
108
+
109
+ def self.read_all( include_path, opts={} ) # load all builtins (using plain text reader); helper for convenience
110
+ read_setup( 'setups/all', include_path, opts )
111
+ end # method read_all
112
+
113
+
114
+ # delete ALL records (use with care!)
115
+ def self.delete!
116
+ puts '*** deleting world table records/data...'
117
+ Deleter.new.run
118
+ end # method delete!
119
+
120
+ def self.delete_all!( opts={} )
121
+ LogDb.delete!
122
+ ConfDb.delete!
123
+ TagDb.delete!
124
+ WorldDb.delete!
125
+ end
126
+
127
+
128
+ ####
129
+ ## todo: remove stats ??? why? why not? better use .tables
130
+ def self.stats
131
+ Stats.new.tables
132
+ end
133
+
134
+ def self.tables
135
+ Stats.new.tables
136
+ end
137
+
138
+
139
+ def self.connect( db_config={} )
140
+
141
+ if db_config.empty?
142
+ puts "ENV['DATBASE_URL'] - >#{ENV['DATABASE_URL']}<"
143
+
144
+ ### change default to ./sport.db ?? why? why not?
145
+ db = URI.parse( ENV['DATABASE_URL'] || 'sqlite3:///world.db' )
146
+
147
+ if db.scheme == 'postgres'
148
+ config = {
149
+ adapter: 'postgresql',
150
+ host: db.host,
151
+ port: db.port,
152
+ username: db.user,
153
+ password: db.password,
154
+ database: db.path[1..-1],
155
+ encoding: 'utf8'
156
+ }
157
+ else # assume sqlite3
158
+ config = {
159
+ adapter: db.scheme, # sqlite3
160
+ database: db.path[1..-1] # world.db (NB: cut off leading /, thus 1..-1)
161
+ }
162
+ end
163
+ else
164
+ config = db_config # use passed in config hash
165
+ end
166
+
167
+ ## todo/check: use if defined?( JRUBY_VERSION ) instead ??
168
+ if RUBY_PLATFORM =~ /java/ && config[:adapter] == 'sqlite3'
169
+ # quick hack for JRuby sqlite3 support via jdbc
170
+ puts "jruby quick hack - adding jdbc libs for jruby sqlite3 database support"
171
+ require 'jdbc/sqlite3'
172
+ require 'active_record/connection_adapters/jdbc_adapter'
173
+ require 'active_record/connection_adapters/jdbcsqlite3_adapter'
174
+ end
175
+
176
+ puts "Connecting to db using settings: "
177
+ pp config
178
+ ActiveRecord::Base.establish_connection( config )
179
+ # ActiveRecord::Base.logger = Logger.new( STDOUT )
180
+ end
181
+
182
+
183
+ def self.setup_in_memory_db
184
+
185
+ # Database Setup & Config
186
+ ActiveRecord::Base.logger = Logger.new( STDOUT )
187
+ ## ActiveRecord::Base.colorize_logging = false - no longer exists - check new api/config setting?
188
+
189
+ self.connect( adapter: 'sqlite3',
190
+ database: ':memory:' )
191
+
192
+ ## build schema
193
+ WorldDb.create_all
194
+ end # setup_in_memory_db (using SQLite :memory:)
195
+
196
+ end # module WorldDb
197
+
198
+
199
+ # say hello
200
+ puts WorldDb.banner if $DEBUG || (defined?($RUBYLIBS_DEBUG) && $RUBYLIBS_DEBUG)
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+
3
+ module WorldDb
4
+
5
+ # collection of regex patterns for reuse (WorldDb specific)
6
+
7
+ ### todo: add a patterns.md page to github ??
8
+ ## - add regexper pics??
9
+
10
+ ############
11
+ # about ruby regexps
12
+ #
13
+ # try the rubular - Ruby regular expression editor and tester
14
+ # -> http://rubular.com
15
+ # code -> ?? by ??
16
+ #
17
+ #
18
+ # Jeff Avallone's Regexper - Shows State-Automata Diagrams
19
+ # try -> http://regexper.com
20
+ # code -> https://github.com/javallone/regexper
21
+ #
22
+ #
23
+ # Regular Expressions | The Bastards Book of Ruby by Dan Nguyen
24
+ # http://ruby.bastardsbook.com/chapters/regexes/
25
+ #
26
+ # move to notes regex|patterns on geraldb.github.io ??
27
+ #
28
+
29
+ COUNTRY_KEY_PATTERN = '\A[a-z]{2,3}\z' # allow two AND three letter keys e.g. at, mx, eng, sco, etc.
30
+ COUNTRY_KEY_PATTERN_MESSAGE = "expected two or three lowercase letters a-z /#{COUNTRY_KEY_PATTERN}/"
31
+
32
+ COUNTRY_CODE_PATTERN = '\A[A-Z_]{3}\z'
33
+ COUNTRY_CODE_PATTERN_MESSAGE = "expected three uppercase letters A-Z (and _) /#{COUNTRY_CODE_PATTERN}/"
34
+
35
+
36
+ REGION_KEY_PATTERN = '\A[a-z]+\z'
37
+ REGION_KEY_PATTERN_MESSAGE = "expected one or more lowercase letters a-z /#{REGION_KEY_PATTERN}/"
38
+
39
+ REGION_CODE_PATTERN = '\A[A-Z_]{2,3}\z'
40
+ REGION_CODE_PATTERN_MESSAGE = "expected two or three uppercase letters A-Z (and _) /#{REGION_CODE_PATTERN}/"
41
+
42
+
43
+ CITY_KEY_PATTERN = '\A[a-z]{3,}\z'
44
+ CITY_KEY_PATTERN_MESSAGE = "expected three or more lowercase letters a-z' /#{CITY_KEY_PATTERN}/"
45
+
46
+ CITY_CODE_PATTERN = '\A[A-Z_]{3}\z'
47
+ CITY_CODE_PATTERN_MESSAGE = "expected three uppercase letters A-Z (and _)' /#{CITY_CODE_PATTERN}/"
48
+
49
+
50
+ LANG_KEY_PATTERN = '\A[a-z]{2}\z'
51
+ LANG_KEY_PATTERN_MESSAGE = "expected two lowercase letters a-z' /#{LANG_KEY_PATTERN}/"
52
+
53
+ end # module WorldDb
54
+