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 +8 -0
- data/README.markdown +79 -0
- data/Rakefile +10 -0
- data/bin/swissmatch_db +73 -0
- data/lib/swissmatch/activerecord.rb +300 -0
- data/lib/swissmatch/rails.rb +29 -0
- data/lib/swissmatch.rb +216 -0
- data/swissmatch-rails.gemspec +41 -0
- metadata +70 -0
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:
|