worlddb-models 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+