swissmatch-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.txt ADDED
@@ -0,0 +1,8 @@
1
+ Copyright (c) 2012, Stefan Rusterholz <stefan.rusterholz@gmail.com>
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+
6
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.markdown ADDED
@@ -0,0 +1,79 @@
1
+ README
2
+ ======
3
+
4
+
5
+ Summary
6
+ -------
7
+ Adds ActiveRecord models and javascript assets for rails to swissmatch.
8
+
9
+
10
+ Installation
11
+ ------------
12
+ Install the gem: `gem install swissmatch-rails`
13
+ Depending on how you installed rubygems, you have to use `sudo`:
14
+ `sudo gem install swissmatch-rails`
15
+ In Ruby: `require 'swissmatch/rails'`
16
+
17
+
18
+ Usage
19
+ -----
20
+ ### Gemfile
21
+ To use swissmatch-rails, the best way is to put the following line into your Gemfile:
22
+
23
+ gem 'swissmatch', :require => 'swissmatch/rails'
24
+
25
+
26
+ ### SwissMatch and Databases
27
+ If you want to load the data into your database, you can use:
28
+
29
+ swissmatch_db create
30
+ swissmatch_db seed
31
+
32
+ This needs active\_record 3.2+ to be installed, and you should either be in a rails project, or
33
+ use the -c option to specify a database configuration file.
34
+ The models used for that can be loaded by `require 'swissmatch/active_record'`.
35
+ See SwissMatch::ActiveRecord::Canton, SwissMatch::ActiveRecord::Community and
36
+ SwissMatch::ActiveRecord::ZipCode
37
+
38
+ ### Configuration
39
+ The swissmatch-rails gem loads the configuration from PROJECT_ROOT/config/swissmatch.yml.
40
+ The file should have the following structure:
41
+
42
+ global:
43
+ telsearch_key: "your telsearch API key"
44
+ data_directory: "A path to where you want your data files stored, relative paths are relative to PROJECT_ROOT"
45
+ cache_directory: "A path to where swissmatch should store its cache"
46
+ development:
47
+ # same keys as for global, you can have environment specific settings here
48
+
49
+ The key 'global' will be used as the base for every environment.
50
+
51
+
52
+ Relevant Classes and Modules
53
+ ----------------------------
54
+ * __{SwissMatch::ActiveRecord}__
55
+ Container for all ActiveRecord models
56
+
57
+
58
+ Links
59
+ -----
60
+
61
+ * [Main Project](https://github.com/apeiros/swissmatch)
62
+ * [Online API Documentation](http://rdoc.info/github/apeiros/swissmatch-directories/)
63
+ * [Public Repository](https://github.com/apeiros/swissmatch-directories)
64
+ * [Bug Reporting](https://github.com/apeiros/swissmatch-directories/issues)
65
+ * [RubyGems Site](https://rubygems.org/gems/swissmatch-directories)
66
+ * [Swiss Posts MAT[CH]](http://www.post.ch/match)
67
+
68
+
69
+ License
70
+ -------
71
+
72
+ You can use this code under the {file:LICENSE.txt BSD-2-Clause License}, free of charge.
73
+ If you need a different license, please ask the author.
74
+
75
+
76
+ Credits
77
+ -------
78
+
79
+ * [AWD Switzerland](http://www.awd.ch/) for donating time to work on this gem.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../rake/lib', __FILE__))
2
+ Dir.glob(File.expand_path('../rake/tasks/**/*.{rake,task,rb}', __FILE__)) do |task_file|
3
+ begin
4
+ import task_file
5
+ rescue LoadError => e
6
+ warn "Failed to load task file #{task_file}"
7
+ warn " #{e.class} #{e.message}"
8
+ warn " #{e.backtrace.first}"
9
+ end
10
+ end
data/bin/swissmatch_db ADDED
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ lib_dir = File.expand_path('../../lib', __FILE__)
5
+ $LOAD_PATH << lib_dir if File.directory?(lib_dir)
6
+
7
+ require 'active_record'
8
+ require 'swissmatch/autoload'
9
+ require 'swissmatch/activerecord'
10
+ require 'optparse'
11
+ require 'logger'
12
+
13
+ environment = ENV["RAILS_ENV"] || 'development'
14
+ log_file = ENV["LOG"]
15
+ config_file = ENV["SWISSMATCH_DB_CONFIG"] || 'config/database.yml'
16
+
17
+ parser = OptionParser.new do |opts|
18
+ opts.banner = "Usage: #{$0} [options] command"
19
+
20
+ opts.on("-h", "--help", "Print help and exit") do
21
+ help
22
+ exit
23
+ end
24
+
25
+ opts.on("-e", "--environment NAME", "Set the environment, used to determine the configuration (uses RAILS_ENV env variable, defaults to 'development')") do |name|
26
+ environment = name
27
+ end
28
+
29
+ opts.on("-c", "--configuration PATH", "Path to the configuration file (uses SWISSMATCH_DB_CONFIG env variable, defaults to 'config/database.yml')") do |path|
30
+ config_file = path
31
+ end
32
+
33
+ opts.on("-l", "--log PATH", "Path to the log file. Use - for stdout and _ to suppress. (uses LOG env variable, defaults to 'log/ENVIRONMENT.log')") do |path|
34
+ log_file = path
35
+ end
36
+ end
37
+ parser.parse!
38
+
39
+ # determine log-file
40
+ log_file ||= "log/#{environment}.log" if File.writable?("log")
41
+ log_file = nil if log_file == '_'
42
+ log_file = $stdout if log_file == '-'
43
+
44
+ # establish connection and setup logging
45
+ abort("Configuration file not found, use -c switch to specify one. Tried '#{config_file}'") unless File.readable?(config_file)
46
+ SwissMatch::ActiveRecord.connect_from_config(config_file, environment)
47
+ ActiveRecord::Base.logger = Logger.new(log_file) if log_file
48
+
49
+ # handle commands
50
+ case ARGV.first
51
+ when "create"
52
+ SwissMatch::ActiveRecord::Migration.migrate(:up)
53
+ when "destroy"
54
+ SwissMatch::ActiveRecord::Migration.migrate(:down)
55
+ when "seed"
56
+ SwissMatch::ActiveRecord.seed
57
+ when "help"
58
+ help
59
+ when nil
60
+ puts "No command given"
61
+ help
62
+ abort "Aborting"
63
+ else
64
+ puts "Unknown command: #{ARGV.first}"
65
+ help
66
+ abort "Aborting"
67
+ end
68
+
69
+ BEGIN {
70
+ def help
71
+ puts "Available commands are:", " create", " seed", " destroy"
72
+ end
73
+ }
@@ -0,0 +1,300 @@
1
+ # encoding: utf-8
2
+
3
+
4
+
5
+ require 'swissmatch'
6
+ require 'active_record'
7
+
8
+
9
+
10
+ module SwissMatch
11
+
12
+ # ActiveRecord models for swissmatch
13
+ module ActiveRecord
14
+ LanguageToCode = {
15
+ :de => 1,
16
+ :fr => 2,
17
+ :it => 3,
18
+ :rt => 4,
19
+ }
20
+ CodeToLanguage = LanguageToCode.invert
21
+
22
+ def self.connect_from_config(config_file, environment=nil)
23
+ config = YAML.load_file(config_file)
24
+ config = config[environment] if config[environment]
25
+ config = Hash[config.map { |k,v| [k.to_sym, v] }]
26
+ ::ActiveRecord::Base.establish_connection(config)
27
+ end
28
+
29
+ def self.delete_all
30
+ SwissMatch::ActiveRecord::ZipCodeName.delete_all
31
+ SwissMatch::ActiveRecord::ZipCode.delete_all
32
+ SwissMatch::ActiveRecord::Community.delete_all
33
+ SwissMatch::ActiveRecord::Canton.delete_all
34
+ end
35
+
36
+ def self.cursor_hidden(io=$stdout)
37
+ io.printf "\e[?25l"
38
+ io.flush
39
+ yield
40
+ ensure
41
+ io.printf "\e[?25h"
42
+ io.flush
43
+ end
44
+
45
+ def self.print_progress(progress, total, width=80, io=$stdout)
46
+ bar_width = width-8
47
+ percent = progress.fdiv(total)
48
+ filled = (percent*bar_width).round
49
+ empty = bar_width - filled
50
+ io.printf "\r\e[1m %5.1f%%\e[0m \e[44m%*s\e[46m%*s\e[0m", percent*100, filled, '', empty, ''
51
+ io.flush
52
+ end
53
+
54
+ def self.seed(data_source=SwissMatch.data)
55
+ canton2id = {}
56
+ total = data_source.cantons.size +
57
+ data_source.communities.size +
58
+ data_source.zip_codes.size*2 +
59
+ 10
60
+ progress = 0
61
+
62
+ cursor_hidden do
63
+ print_progress(progress, total)
64
+
65
+ ::ActiveRecord::Base.transaction do
66
+ delete_all
67
+ print_progress(progress+=10, total)
68
+
69
+ data_source.cantons.each do |canton|
70
+ canton2id[canton.license_tag] = SwissMatch::ActiveRecord::Canton.create!(canton.to_hash, :without_protection => true).id
71
+ print_progress(progress+=1, total)
72
+ end
73
+ data_source.communities.partition do |community|
74
+ hash = community.to_hash
75
+ hash[:canton_id] = canton2id[hash.delete(:canton)]
76
+ hash[:agglomeration_id] = hash.delete(:agglomeration)
77
+ SwissMatch::ActiveRecord::Community.create!(hash, :without_protection => true)
78
+ print_progress(progress+=1, total)
79
+ end
80
+ self_delivered, others = data_source.zip_codes.partition { |code| code.delivery_by.nil? || code.delivery_by == code }
81
+ process_code = proc do |zip_code|
82
+ hash = zip_code.to_hash
83
+ hash[:id] = hash.delete(:ordering_number)
84
+ hash[:delivery_by_id] = hash.delete(:delivery_by)
85
+ hash[:canton_id] = canton2id[hash.delete(:canton)]
86
+ hash[:language] = LanguageToCode[hash.delete(:language)]
87
+ hash[:language_alternative] = LanguageToCode[hash.delete(:language_alternative)]
88
+ hash[:community_id] = hash.delete(:community)
89
+ hash.update(
90
+ :suggested_name_de => zip_code.suggested_name(:de),
91
+ :suggested_name_fr => zip_code.suggested_name(:fr),
92
+ :suggested_name_it => zip_code.suggested_name(:it),
93
+ :suggested_name_rt => zip_code.suggested_name(:rt)
94
+ )
95
+ hash.delete(:name_short) # not used
96
+
97
+ # FIXME: work around, should be replaced by a proper mechanism
98
+ hash.delete(:valid_from)
99
+ hash.delete(:valid_until)
100
+ hash[:active] = true
101
+
102
+ SwissMatch::ActiveRecord::ZipCode.create!(hash, :without_protection => true)
103
+ zip_code.names.each do |name|
104
+ hash = name.to_hash
105
+ hash[:language] = LanguageToCode[hash.delete(:language)]
106
+ hash[:zip_code_id] = zip_code.ordering_number
107
+ hash[:designation] = 2 # designation of type 3 is not currently in the system
108
+ SwissMatch::ActiveRecord::ZipCodeName.create!(hash, :without_protection => true)
109
+ end
110
+ print_progress(progress+=2, total)
111
+ end
112
+
113
+ self_delivered.each(&process_code)
114
+ others.each(&process_code)
115
+ end
116
+ end
117
+ puts "","Done"
118
+ end
119
+
120
+ def self.update(data_source=SwissMatch.data)
121
+ end
122
+
123
+ class Migration < ::ActiveRecord::Migration
124
+ def down
125
+ try_drop_table :swissmatch_zip_code_names
126
+ try_drop_table :swissmatch_zip_codes
127
+ try_drop_table :swissmatch_communities
128
+ try_drop_table :swissmatch_cantons
129
+ try_execute "No view support, did not drop view swissmatch_named_zip_codes", "DROP VIEW swissmatch_named_zip_codes"
130
+ end
131
+
132
+ def try_drop_table(name)
133
+ drop_table name
134
+ rescue => e
135
+ warn "Could not drop #{name}: #{e}"
136
+ end
137
+
138
+ def try_execute(failure_message, *sqls)
139
+ sqls.each do |sql_statements|
140
+ sql_statements.split(/;\n/).each do |sql_statement|
141
+ execute(sql_statement.chomp(';'))
142
+ end
143
+ end
144
+ rescue => e
145
+ warn "#{failure_message} (#{e})"
146
+ end
147
+
148
+ def up
149
+ create_table :swissmatch_cantons, :comment => 'All swiss cantons as needed by swiss posts MAT[CH], includes the non-cantons DE and IT.' do |t|
150
+ t.integer :id, :null => false, :limit => 6, :comment => 'A unique ID, unrelated to the swiss postal service data.'
151
+ t.string :license_tag, :null => false, :limit => 2, :comment => 'The two letter abbreviation of the cantons name as used on license plates.'
152
+ t.string :name, :null => false, :limit => 32, :comment => 'The canonical name of the canton.'
153
+ t.string :name_de, :null => false, :limit => 32, :comment => 'The name of the canton in german.'
154
+ t.string :name_fr, :null => false, :limit => 32, :comment => 'The name of the canton in french.'
155
+ t.string :name_it, :null => false, :limit => 32, :comment => 'The name of the canton in italian.'
156
+ t.string :name_rt, :null => false, :limit => 32, :comment => 'The name of the canton in rheto-romanic.'
157
+
158
+ t.timestamps
159
+ end
160
+
161
+ create_table :swissmatch_communities, :comment => 'The swiss communities as per plz_c file from the swiss posts MAT[CH].' do |t|
162
+ t.integer :id, :null => false, :limit => 6, :comment => 'A unique, never recycled identification number. Also known as BFSNR.'
163
+ t.string :name, :null => false, :limit => 32, :comment => 'The canonical name of the community.'
164
+ t.integer :canton_id, :null => false, :limit => 6, :comment => 'The canton this community belongs to.'
165
+ t.integer :agglomeration_id, :null => true, :limit => 6, :comment => 'The community this community is considered to be an agglomeration of. Note that a main community will reference itself.'
166
+
167
+ t.timestamps
168
+ end
169
+
170
+ create_table :swissmatch_zip_codes, :comment => 'The swiss zip codes as per plz_p1 and plz_p2 files from the swiss posts MAT[CH].' do |t|
171
+ t.integer :id, :null => false, :limit => 6, :comment => 'The postal ordering number, also known as ONRP. Unique and never recycled.'
172
+ t.integer :type, :null => false, :limit => 16, :comment => 'The type of the entry. One of 10 (Domizil- und Fachadressen), 20 (Nur Domiziladressen), 30 (Nur Fach-PLZ), 40 (Firmen-PLZ) or 80 (Postinterne PLZ).'
173
+ t.integer :code, :null => false, :limit => 16, :comment => 'The 4 digit numeric zip code. Note that the 4 digit code alone does not uniquely identify a zip code record.'
174
+ t.integer :add_on, :null => false, :limit => 16, :comment => 'The 2 digit numeric code addition, to distinguish zip codes with the same 4 digit code.'
175
+ t.integer :canton_id, :null => false, :limit => 6, :comment => 'The canton this zip code belongs to.'
176
+ t.string :name, :null => false, :limit => 27, :comment => 'The canonical name (city) that belongs to this zip code.'
177
+ t.string :suggested_name_de, :null => false, :limit => 27, :comment => 'The suggested name of the zip code (city) for german.'
178
+ t.string :suggested_name_fr, :null => false, :limit => 27, :comment => 'The suggested name of the zip code (city) for french.'
179
+ t.string :suggested_name_it, :null => false, :limit => 27, :comment => 'The suggested name of the zip code (city) for italian.'
180
+ t.string :suggested_name_rt, :null => false, :limit => 27, :comment => 'The suggested name of the zip code (city) for rheto-romanic.'
181
+ t.integer :language, :null => false, :limit => 2, :comment => 'The main language in the area of this zip code. 1 = de, 2 = fr, 3 = it, 4 = rt.'
182
+ t.integer :language_alternative, :null => true, :limit => 2, :comment => 'The second most used language in the area of this zip code. 1 = de, 2 = fr, 3 = it, 4 = rt.'
183
+ t.boolean :sortfile_member, :null => true, :comment => 'Whether this ZipCode instance is included in the MAT[CH]sort sortfile.'
184
+ t.integer :delivery_by_id, :null => true, :limit => 6, :comment => 'By which postal office delivery of letters is usually taken care of.'
185
+ t.integer :community_id, :null => false, :limit => 6, :comment => 'The community this zip code belongs to.'
186
+ t.boolean :active, :null => false, :comment => 'Whether this record is currently active.'
187
+ # t.date :valid_from, :comment => 'The date from which on this zip code starts to be in use.'
188
+ # t.date :valid_until, :comment => '[CURRENTLY NOT USED] The date until which this zip code is in use.'
189
+
190
+ t.timestamps
191
+ end
192
+ add_index :swissmatch_zip_codes, [:code]
193
+
194
+ create_table :swissmatch_zip_code_names, :comment => 'Contains all primary and alternative names of zip codes.' do |t|
195
+ t.integer :id, :null => false, :limit => 6, :comment => 'An internal ID.'
196
+ t.integer :zip_code_id, :null => false, :limit => 6, :comment => 'The postal ordering number to which this name belongs.'
197
+ t.string :name, :null => false, :limit => 27, :comment => 'The name (city) that belongs to this zip code. At a maximum 27 characters long.'
198
+ t.integer :language, :null => false, :limit => 2, :comment => 'The main language in the area of this zip code. 1 = de, 2 = fr, 3 = it, 4 = rt.'
199
+ t.integer :sequence_number, :null => false, :limit => 3, :comment => 'The sequence number of names unique for a single ONRP, a deleted sequence number is never reused.'
200
+ t.integer :designation, :null => false, :limit => 2, :comment => 'The way this name is to be used. Valid values are 2 or 3, 2 2 means this name can be used instead of the zip_code name, 3 means this can be used in addition to the zip_code name.'
201
+
202
+ t.timestamps
203
+ end
204
+ add_index :swissmatch_zip_code_names, [:name]
205
+
206
+ # not every db supports views
207
+ try_execute "No view support, did not create view swissmatch_named_zip_codes", <<-SQL
208
+ CREATE VIEW swissmatch_named_zip_codes AS
209
+ SELECT
210
+ z.id zip_code_id,
211
+ z.type type,
212
+ n.name name,
213
+ n.language name_language,
214
+ n.sequence_number sequence_number,
215
+ z.code code,
216
+ z.add_on add_on,
217
+ z.canton_id canton_id,
218
+ z.language language,
219
+ z.language_alternative language_alternative,
220
+ z.sortfile_member sortfile_member,
221
+ z.delivery_by_id delivery_by_id,
222
+ z.community_id community_id,
223
+ z.active active
224
+ FROM swissmatch_zip_codes z
225
+ JOIN swissmatch_zip_code_names n ON n.zip_code_id = z.id
226
+ WHERE n.designation = 2
227
+ SQL
228
+
229
+ # not every db supports comments
230
+ try_execute "No comment support, did comment on swissmatch_named_zip_codes", <<-SQL
231
+ COMMENT ON TABLE swissmatch_named_zip_codes IS 'Lists all zip-code/name combinations, an ONRP can occur multiple times.';
232
+ COMMENT ON COLUMN swissmatch_named_zip_codes.zip_code_id IS 'The ONRP of the zip code (swissmatch_zip_codes.id)';
233
+ COMMENT ON COLUMN swissmatch_named_zip_codes.type IS 'The type of the entry. One of 10 (Domizil- und Fachadressen), 20 (Nur Domiziladressen), 30 (Nur Fach-PLZ), 40 (Firmen-PLZ) or 80 (Postinterne PLZ).';
234
+ COMMENT ON COLUMN swissmatch_named_zip_codes.zip_code_id IS 'The postal ordering number to which this name belongs.';
235
+ COMMENT ON COLUMN swissmatch_named_zip_codes.name IS 'The name (city) that belongs to this zip code. At a maximum 27 characters long.';
236
+ COMMENT ON COLUMN swissmatch_named_zip_codes.name_language IS 'The main language in the area of this zip code. 1 = de, 2 = fr, 3 = it, 4 = rt.';
237
+ COMMENT ON COLUMN swissmatch_named_zip_codes.sequence_number IS 'The sequence number of names unique for a single ONRP, a deleted sequence number is never reused.';
238
+ COMMENT ON COLUMN swissmatch_named_zip_codes.code IS 'The 4 digit numeric zip code. Note that the 4 digit code alone does not uniquely identify a zip code record.';
239
+ COMMENT ON COLUMN swissmatch_named_zip_codes.add_on IS 'The 2 digit numeric code addition, to distinguish zip codes with the same 4 digit code.';
240
+ COMMENT ON COLUMN swissmatch_named_zip_codes.canton_id IS 'The canton this zip code belongs to.';
241
+ COMMENT ON COLUMN swissmatch_named_zip_codes.name IS 'The name (city) that belongs to this zip code. At a maximum 27 characters long.';
242
+ COMMENT ON COLUMN swissmatch_named_zip_codes.language IS 'The main language in the area of this zip code. 1 = de, 2 = fr, 3 = it, 4 = rt.';
243
+ COMMENT ON COLUMN swissmatch_named_zip_codes.language_alternative IS 'The second most used language in the area of this zip code. 1 = de, 2 = fr, 3 = it, 4 = rt.';
244
+ COMMENT ON COLUMN swissmatch_named_zip_codes.sortfile_member IS 'Whether this ZipCode instance is included in the MAT[CH]sort sortfile.';
245
+ COMMENT ON COLUMN swissmatch_named_zip_codes.delivery_by_id IS 'By which postal office delivery of letters is usually taken care of.';
246
+ COMMENT ON COLUMN swissmatch_named_zip_codes.community_id IS 'The community this zip code belongs to.';
247
+ COMMENT ON COLUMN swissmatch_named_zip_codes.active IS 'Whether this record is currently active.';
248
+ SQL
249
+
250
+ # not every db supports foreign key constraints, and maybe there are also syntax differences,
251
+ try_execute "No foreign key support, did not create foreign key constraints", <<-SQL1, <<-SQL2, <<-SQL3
252
+ ALTER TABLE swissmatch_communities
253
+ ADD CONSTRAINT fk_sm_com_0001 FOREIGN KEY (canton_id) REFERENCES swissmatch_cantons(id)
254
+ ADD CONSTRAINT fk_sm_com_0002 FOREIGN KEY (agglomeration_id) REFERENCES swissmatch_communities(id)
255
+ SQL1
256
+ ALTER TABLE swissmatch_zip_codes
257
+ ADD CONSTRAINT fk_sm_zip_0001 FOREIGN KEY (canton_id) REFERENCES swissmatch_cantons(id)
258
+ ADD CONSTRAINT fk_sm_zip_0002 FOREIGN KEY (community_id) REFERENCES swissmatch_communities(id)
259
+ ADD CONSTRAINT fk_sm_zip_0003 FOREIGN KEY (delivery_by_id) REFERENCES swissmatch_zip_codes(id)
260
+ SQL2
261
+ ALTER TABLE swissmatch_zip_code_names
262
+ ADD CONSTRAINT fk_sm_nam_0001 FOREIGN KEY (zip_code_id) REFERENCES swissmatch_zip_codes(id)
263
+ SQL3
264
+ end
265
+ end
266
+
267
+ class ZipCode < ::ActiveRecord::Base
268
+ self.table_name = "swissmatch_zip_codes"
269
+ self.inheritance_column = "no_sti_in_this_model" # set it to something unused, so 'type' can be used as column name
270
+
271
+ alias_attribute :ordering_number, :id
272
+
273
+ belongs_to :canton, :class_name => 'SwissMatch::ActiveRecord::Canton'
274
+ belongs_to :community, :class_name => 'SwissMatch::ActiveRecord::Community'
275
+ belongs_to :delivery_by, :class_name => 'SwissMatch::ActiveRecord::ZipCode'
276
+ has_many :delivers, :class_name => 'SwissMatch::ActiveRecord::ZipCode', :foreign_key => 'delivery_by_id'
277
+ has_many :names, :class_name => 'SwissMatch::ActiveRecord::ZipCodeName'
278
+ end
279
+ class ZipCodeName < ::ActiveRecord::Base
280
+ self.table_name = "swissmatch_zip_code_names"
281
+
282
+ belongs_to :zip_code, :class_name => 'SwissMatch::ActiveRecord::ZipCode'
283
+ end
284
+ class Community < ::ActiveRecord::Base
285
+ self.table_name = "swissmatch_communities"
286
+
287
+ alias_attribute :community_number, :id
288
+
289
+ belongs_to :canton, :class_name => 'SwissMatch::ActiveRecord::Canton'
290
+ belongs_to :agglomeration, :class_name => 'SwissMatch::ActiveRecord::Community'
291
+ has_many :zip_codes, :class_name => 'SwissMatch::ActiveRecord::ZipCode'
292
+ end
293
+ class Canton < ::ActiveRecord::Base
294
+ self.table_name = "swissmatch_cantons"
295
+
296
+ has_many :zip_codes, :class_name => 'SwissMatch::ActiveRecord::ZipCode'
297
+ has_many :communities, :class_name => 'SwissMatch::ActiveRecord::Community'
298
+ end
299
+ end
300
+ end
@@ -0,0 +1,29 @@
1
+ # To use SwissMatch in rails, best use this line in your Gemfile:
2
+ # gem 'swissmatch', :require => 'swissmatch/rails'
3
+
4
+
5
+
6
+ raise "This file should be required after rails has been loaded" unless defined?(ActiveSupport)
7
+
8
+ ActiveSupport.on_load(:before_initialize) do
9
+ require 'swissmatch'
10
+
11
+ # Load environment specific configuration
12
+ config_path = Rails.root.join('config/swissmatch.yml')
13
+ configuration_all = File.exist?(config_path) ? YAML.load_file(config_path) : {}
14
+ configuration = configuration_all['global'] || {}
15
+ configuration.merge!(configuration_all[Rails.env] || {})
16
+
17
+ # Load zip-code data
18
+ if configuration['data_directory'] then
19
+ SwissMatch.load(SwissMatch::DataFiles.new(configuration['data_directory']))
20
+ else
21
+ SwissMatch.load
22
+ end
23
+
24
+ # Load directory services
25
+ if configuration['telsearch_key'] then
26
+ require 'swissmatch/telsearch'
27
+ SwissMatch.directory_service = SwissMatch::TelSearch.new(configuration['telsearch_key'])
28
+ end
29
+ end
data/lib/swissmatch.rb ADDED
@@ -0,0 +1,216 @@
1
+ # encoding: utf-8
2
+
3
+
4
+
5
+ require 'date'
6
+ require 'swissmatch/ruby'
7
+ require 'swissmatch/canton'
8
+ require 'swissmatch/cantons'
9
+ require 'swissmatch/community'
10
+ require 'swissmatch/communities'
11
+ require 'swissmatch/datafiles'
12
+ require 'swissmatch/version'
13
+ require 'swissmatch/zipcode'
14
+ require 'swissmatch/zipcodes'
15
+
16
+
17
+
18
+ # SwissMatch
19
+ # Deal with swiss zip codes, cities, communities and cantons.
20
+ #
21
+ # Notice that all strings passed to SwissMatch are expected to be utf-8. All strings
22
+ # returned by SwissMatch are also in utf-8.
23
+ #
24
+ # @example Load the data
25
+ # require 'swissmatch'
26
+ # SwissMatch.load
27
+ # # alternatively, just require 'swissmatch/autoload'
28
+ #
29
+ # @example Get the ONRP for a given zip-code + city
30
+ # require 'swissmatch/autoload'
31
+ # SwissMatch.zip_code(8000, 'Zürich').ordering_number # =>
32
+ module SwissMatch
33
+ @data = nil
34
+ @directory_service = nil
35
+
36
+ class <<self
37
+ # @return [SwissMatch::DataFiles, nil] The data source used
38
+ attr_reader :data
39
+
40
+ # @return [SwissMatch::DirectoryService, nil]
41
+ # The directory service used to search for addresses
42
+ attr_accessor :directory_service
43
+ end
44
+
45
+ def self.canton(name_or_plate)
46
+ @data.cantons[name_or_plate]
47
+ end
48
+
49
+ def self.cantons
50
+ @data.cantons
51
+ end
52
+
53
+ def self.community(key)
54
+ @data.communities.by_community_number(key)
55
+ end
56
+
57
+ def self.communities(name=nil)
58
+ name ? @data.communities.by_name(name) : @data.communities
59
+ end
60
+
61
+ # @param [String, Integer] code_or_name
62
+ # Either the 4 digit zip code as Integer or String, or the city name as a String in
63
+ # utf-8.
64
+ #
65
+ # @return [Array<SwissMatch::ZipCode>]
66
+ # A list of zip codes with the given code or name.
67
+ def self.zip_codes(code_or_name=nil)
68
+ case code_or_name
69
+ when Integer, /\A\d{4}\z/
70
+ @data.zip_codes.by_code(code_or_name.to_i)
71
+ when String
72
+ @data.zip_codes.by_name(code_or_name)
73
+ when nil
74
+ @data.zip_codes
75
+ else
76
+ raise ArgumentError, "Invalid argument, must be a ZipCode#code (Integer or String) or ZipCode#name (String)"
77
+ end
78
+ end
79
+
80
+ # Returns a single zip code. A zip code can be uniquely identified by any of:
81
+ # * Its ordering_number (ONRP, a 4 digit Integer)
82
+ # * Its zip code (4 digit Integer) and add-on (2 digit Integer)
83
+ # * Its zip code (4 digit Integer) and any official name (String)
84
+ # The data can be passed in different ways, e.g. all numbers can be passed either
85
+ # as a String or as an Integer. The identification by zip code and add-on can be done
86
+ # by either using a combined 6 digit number (e.g. 800000 for "8000 Zürich"), or by
87
+ # passing 2 arguments, passing the zip code and the add-on separately.
88
+ #
89
+ # === IMPORTANT
90
+ # You must be aware, that passing a single 4-digit code to SwissMatch::zip_code uses
91
+ # the ONRP, and NOT the zip-code. The 4 digit zip code alone does NOT uniquely identify
92
+ # a zip code.
93
+ #
94
+ #
95
+ # @example Get a zip code by ONRP
96
+ # SwissMatch.zip_code(4384) # => #<SwissMatch::ZipCode:003ff996cf8d3c 8000 Zürich>
97
+ #
98
+ # @example Get a zip code by 4-digit code and add-on
99
+ # SwissMatch.zip_code(8000, 0) # => #<SwissMatch::ZipCode:003ff996cf8d3c 8000 Zürich>
100
+ # SwissMatch.zip_code("8000", "00") # => #<SwissMatch::ZipCode:003ff996cf8d3c 8000 Zürich>
101
+ # SwissMatch.zip_code(800000) # => #<SwissMatch::ZipCode:003ff996cf8d3c 8000 Zürich>
102
+ # SwissMatch.zip_code("800000") # => #<SwissMatch::ZipCode:003ff996cf8d3c 8000 Zürich>
103
+ #
104
+ # @example Get a zip code by 4-digit code and name
105
+ # SwissMatch.zip_code(8000, "Zürich") # => #<SwissMatch::ZipCode:003ff996cf8d3c 8000 Zürich>
106
+ # SwissMatch.zip_code(8000, "Zurigo") # => #<SwissMatch::ZipCode:003ff996cf8d3c 8000 Zürich>
107
+ #
108
+ #
109
+ # @param [String, Integer] code
110
+ # The 4 digit zip code as Integer or String
111
+ # @param [String, Integer] city_or_add_on
112
+ # Either the 2 digit zip-code add-on as string or integer, or the city name as a
113
+ # String in utf-8.
114
+ #
115
+ # @return [SwissMatch::ZipCode]
116
+ # The zip codes with the given code and the given add-on or name.
117
+ def self.zip_code(code, city_or_add_on=nil)
118
+ case city_or_add_on
119
+ when nil
120
+ @data.zip_codes.by_ordering_number(code.to_i)
121
+ when Integer, /\A\d\d\z/
122
+ @data.zip_codes.by_code_and_add_on(code.to_i, city_or_add_on.to_i)
123
+ when String
124
+ @data.zip_codes.by_code_and_name(code.to_i, city_or_add_on)
125
+ else
126
+ raise ArgumentError, "Invalid second argument, must be nil, ZipCode#add_on or ZipCode#name"
127
+ end
128
+ end
129
+
130
+ # @param [String] name
131
+ # The name for which to return matching zip codes
132
+ #
133
+ # @return [Array<SwissMatch::ZipCode>]
134
+ # Zip codes whose name equals the given name
135
+ def self.city(name)
136
+ @data.zip_codes.by_name(name)
137
+ end
138
+
139
+ # @param [String, Integer] code
140
+ # The 4 digit zip code
141
+ # @param [nil, Array<Integer>] only_types
142
+ # An array of zip code types (see ZipCode#type) which the returned zip codes must match.
143
+ # @param [nil, Symbol] locale
144
+ # Return the names in the given locale, defaults to nil/:native (nil and :native are
145
+ # treated the same and will return the native names)
146
+ #
147
+ # @return [Array<String>]
148
+ # A list of unique names matching the parameters (4 digit code, type, locale).
149
+ def self.cities_for_zip_code(code, only_types=nil, locale=nil)
150
+ codes = @data.zip_codes.by_code(code.to_i)
151
+ return [] unless codes
152
+ codes = codes.select { |code| only_types.include?(code.type) } if only_types
153
+ names = case locale
154
+ when :native,nil then codes.map(&:name)
155
+ when :de then codes.map(&:name_de)
156
+ when :fr then codes.map(&:name_fr)
157
+ when :it then codes.map(&:name_it)
158
+ when :rt then codes.map(&:name_rt)
159
+ else raise ArgumentError, "Invalid locale #{locale}"
160
+ end
161
+
162
+ names.uniq
163
+ end
164
+
165
+ def self.load(data_source=nil)
166
+ @data = data_source || DataFiles.new
167
+ @data.load!
168
+ end
169
+
170
+ # @private
171
+ # Used to transliterate city names
172
+ Transliteration1 = {
173
+ "à" => "a",
174
+ "â" => "a",
175
+ "ä" => "a",
176
+ "è" => "e",
177
+ "é" => "e",
178
+ "ê" => "e",
179
+ "ë" => "e",
180
+ "ì" => "i",
181
+ "î" => "i",
182
+ "ï" => "i",
183
+ "ô" => "o",
184
+ "ö" => "o",
185
+ "ù" => "u",
186
+ "ü" => "u",
187
+ }
188
+
189
+ # @private
190
+ # Used to transliterate city names
191
+ Transliteration2 = Transliteration1.merge({
192
+ "ä" => "ae",
193
+ "ö" => "oe",
194
+ "ü" => "ue",
195
+ })
196
+
197
+ # @private
198
+ # Used to transliterate city names
199
+ TransMatch1 = /#{Transliteration1.keys.map { |k| Regexp.escape(k) }.join("|")}/
200
+
201
+ # @private
202
+ # Used to transliterate city names
203
+ TransMatch2 = /#{Transliteration2.keys.map { |k| Regexp.escape(k) }.join("|")}/
204
+
205
+ # @private
206
+ # Used to transliterate city names
207
+ def self.transliterate1(word)
208
+ word.gsub(TransMatch1, Transliteration1).delete("^ A-Za-z").downcase
209
+ end
210
+
211
+ # @private
212
+ # Used to transliterate city names
213
+ def self.transliterate2(word)
214
+ word.gsub(TransMatch2, Transliteration2).delete("^ A-Za-z").downcase
215
+ end
216
+ end
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "swissmatch-rails"
5
+ s.version = "0.0.1"
6
+ s.authors = "Stefan Rusterholz"
7
+ s.email = "stefan.rusterholz@gmail.com"
8
+ s.homepage = "http://github.com/apeiros/swissmatch-rails"
9
+
10
+ s.description = <<-DESCRIPTION.gsub(/^ /, '').chomp
11
+ Adds ActiveRecord models and javascript assets for rails to swissmatch.
12
+ DESCRIPTION
13
+
14
+ s.summary = <<-SUMMARY.gsub(/^ /, '').chomp
15
+ Adds ActiveRecord models and javascript assets for rails to swissmatch.
16
+ SUMMARY
17
+
18
+ s.files =
19
+ Dir['bin/**/*'] +
20
+ Dir['data/**/*'] +
21
+ Dir['lib/**/*'] +
22
+ Dir['rake/**/*'] +
23
+ Dir['test/**/*'] +
24
+ Dir['*.gemspec'] +
25
+ %w[
26
+ LICENSE.txt
27
+ Rakefile
28
+ README.markdown
29
+ ]
30
+
31
+ if File.directory?('bin') then
32
+ executables = Dir.chdir('bin') { Dir.glob('**/*').select { |f| File.executable?(f) } }
33
+ s.executables = executables unless executables.empty?
34
+ end
35
+
36
+ s.add_dependency "swissmatch"
37
+
38
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1")
39
+ s.rubygems_version = "1.3.1"
40
+ s.specification_version = 3
41
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: swissmatch-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Stefan Rusterholz
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: swissmatch
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: Adds ActiveRecord models and javascript assets for rails to swissmatch.
31
+ email: stefan.rusterholz@gmail.com
32
+ executables:
33
+ - swissmatch_db
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - bin/swissmatch_db
38
+ - lib/swissmatch/activerecord.rb
39
+ - lib/swissmatch/rails.rb
40
+ - lib/swissmatch.rb
41
+ - swissmatch-rails.gemspec
42
+ - LICENSE.txt
43
+ - Rakefile
44
+ - README.markdown
45
+ homepage: http://github.com/apeiros/swissmatch-rails
46
+ licenses: []
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>'
61
+ - !ruby/object:Gem::Version
62
+ version: 1.3.1
63
+ requirements: []
64
+ rubyforge_project:
65
+ rubygems_version: 1.8.24
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: Adds ActiveRecord models and javascript assets for rails to swissmatch.
69
+ test_files: []
70
+ has_rdoc: