district_cn 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +21 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +116 -0
- data/Rakefile +6 -0
- data/district_cn.gemspec +29 -0
- data/lib/areas.json +6 -0
- data/lib/district_cn/act_as_area_field.rb +24 -0
- data/lib/district_cn/as_options.rb +46 -0
- data/lib/district_cn/code.rb +107 -0
- data/lib/district_cn/db.rb +81 -0
- data/lib/district_cn/version.rb +3 -0
- data/lib/district_cn.rb +40 -0
- data/spec/act_as_area_field_spec.rb +67 -0
- data/spec/code_spec.rb +104 -0
- data/spec/db_spec.rb +32 -0
- data/spec/district_cn_spec.rb +22 -0
- data/spec/spec_helper.rb +10 -0
- metadata +182 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
module DistrictCn
|
2
|
+
module ActAsAreaField
|
3
|
+
module ActiveRecord
|
4
|
+
|
5
|
+
def act_as_area_field(*attributes)
|
6
|
+
attributes.each do |attribute|
|
7
|
+
class_eval <<-EVAL
|
8
|
+
alias_method :_#{attribute}, :#{attribute}
|
9
|
+
def #{attribute}
|
10
|
+
val = _#{attribute}
|
11
|
+
return val if val.blank?
|
12
|
+
|
13
|
+
unless @_#{attribute} && val.eql?(@_#{attribute}.value)
|
14
|
+
@_#{attribute} = DistrictCn.code(val)
|
15
|
+
end
|
16
|
+
@_#{attribute}
|
17
|
+
end
|
18
|
+
EVAL
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module DistrictCn
|
2
|
+
module AsOptions
|
3
|
+
OPTIONS = {
|
4
|
+
:selected_province => nil,
|
5
|
+
:selected_city => nil,
|
6
|
+
:selected_district => nil,
|
7
|
+
:selected_provinces => [],
|
8
|
+
:selected_cities => [],
|
9
|
+
:selected_districts => []
|
10
|
+
}
|
11
|
+
|
12
|
+
def as_options
|
13
|
+
options = OPTIONS.dup
|
14
|
+
options[:selected_province] = province && [province_name, province_id]
|
15
|
+
options[:selected_city] = city && [city_name,city_id]
|
16
|
+
options[:selected_district] = district && [district_name, district_id]
|
17
|
+
|
18
|
+
options[:selected_provinces] = selected_provinces
|
19
|
+
options[:selected_cities] = selected_cities
|
20
|
+
options[:selected_districts] = selected_districts
|
21
|
+
options
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def selected_provinces
|
26
|
+
self.class.data.map do |province_id,province_hash|
|
27
|
+
[province_hash[:text],province_id]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def selected_cities
|
32
|
+
return [] unless province
|
33
|
+
province[:children].map do |city_id,city_hash|
|
34
|
+
[city_hash[:text],city_id]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def selected_districts
|
39
|
+
return [] unless city
|
40
|
+
city[:children].map do |district_id,district_hash|
|
41
|
+
[district_hash[:text],district_id]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module DistrictCn
|
2
|
+
class Code
|
3
|
+
include AsOptions
|
4
|
+
|
5
|
+
REGULAR = /(\d{2})(\d{2})(\d{2})/
|
6
|
+
attr_reader :value,:id
|
7
|
+
|
8
|
+
def initialize(id)
|
9
|
+
@value = id
|
10
|
+
@id = id.to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
get && get[:text]
|
15
|
+
end
|
16
|
+
|
17
|
+
def area_name(mark="-")
|
18
|
+
[province_name,city_name,district_name].compact.join(mark)
|
19
|
+
end
|
20
|
+
|
21
|
+
def children
|
22
|
+
return [] unless get && get[:children]
|
23
|
+
|
24
|
+
get[:children].map{|id,attr| [attr[:text],id] }
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def get
|
29
|
+
@get ||= province? && province
|
30
|
+
@get ||= city? && city
|
31
|
+
@get ||= district? && district
|
32
|
+
@get || nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def province?
|
36
|
+
!!(province_id && city_id.nil?)
|
37
|
+
end
|
38
|
+
|
39
|
+
def city?
|
40
|
+
!!(province_id && city_id && district_id.nil?)
|
41
|
+
end
|
42
|
+
|
43
|
+
def district?
|
44
|
+
!!(province_id && city_id && district_id)
|
45
|
+
end
|
46
|
+
|
47
|
+
def province
|
48
|
+
@province ||=
|
49
|
+
province_id && self.class.data && self.class.data[province_id]
|
50
|
+
end
|
51
|
+
|
52
|
+
def city
|
53
|
+
city_id && province && province[:children][city_id]
|
54
|
+
end
|
55
|
+
|
56
|
+
def district
|
57
|
+
district_id && city && city[:children][district_id]
|
58
|
+
end
|
59
|
+
|
60
|
+
def province_name
|
61
|
+
province && province[:text]
|
62
|
+
end
|
63
|
+
|
64
|
+
def city_name
|
65
|
+
city && city[:text]
|
66
|
+
end
|
67
|
+
|
68
|
+
def district_name
|
69
|
+
district && district[:text]
|
70
|
+
end
|
71
|
+
|
72
|
+
def province_id
|
73
|
+
@province_id ||=
|
74
|
+
if match && segment_blank?(match[1])
|
75
|
+
match[1].ljust(6, '0')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def city_id
|
80
|
+
@city_id ||=
|
81
|
+
if match && segment_blank?(match[2])
|
82
|
+
"#{match[1]}#{match[2]}".ljust(6, '0')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def district_id
|
87
|
+
@district_id ||=
|
88
|
+
if match && segment_blank?(match[3])
|
89
|
+
id
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
def match
|
95
|
+
@match ||= id.match(REGULAR)
|
96
|
+
end
|
97
|
+
|
98
|
+
def segment_blank?(segment)
|
99
|
+
segment != "00"
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.data
|
103
|
+
@data ||= Db::File.instance.tree
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'json'
|
2
|
+
module DistrictCn
|
3
|
+
module Db
|
4
|
+
class File
|
5
|
+
attr_reader :tree,:list
|
6
|
+
|
7
|
+
def provinces
|
8
|
+
@provinces ||= @tree.map{|pvn_id,pvn_hash| [pvn_hash[:text],pvn_id]}
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@tree = {}
|
13
|
+
@list = {}
|
14
|
+
parse
|
15
|
+
end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def instance
|
19
|
+
@instance ||= new
|
20
|
+
end
|
21
|
+
|
22
|
+
def provinces
|
23
|
+
instance.provinces
|
24
|
+
end
|
25
|
+
|
26
|
+
def tree
|
27
|
+
instance.tree
|
28
|
+
end
|
29
|
+
|
30
|
+
def list
|
31
|
+
instance.list
|
32
|
+
end
|
33
|
+
|
34
|
+
private :new
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def code_regular
|
39
|
+
DistrictCn::Code::REGULAR
|
40
|
+
end
|
41
|
+
|
42
|
+
def path
|
43
|
+
::File.expand_path("../../areas.json", __FILE__)
|
44
|
+
end
|
45
|
+
|
46
|
+
def json_data
|
47
|
+
@json_data ||= JSON.parse(::File.read(path))
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse
|
51
|
+
json_data["province"].each do |province|
|
52
|
+
@tree[province["id"]] = {:text => province["text"],:children => {}}
|
53
|
+
|
54
|
+
@list[province["id"]] = province["text"]
|
55
|
+
end
|
56
|
+
|
57
|
+
json_data["city"].each do |city|
|
58
|
+
city["id"] =~ (code_regular)
|
59
|
+
province_id = $1.ljust(6, '0')
|
60
|
+
if @tree[province_id]
|
61
|
+
@tree[province_id][:children][city["id"]] = {:text => city["text"], :children => {}}
|
62
|
+
|
63
|
+
@list[city["id"]] = city["text"]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
json_data["district"].each do |district|
|
68
|
+
district["id"] =~ (code_regular)
|
69
|
+
province_id = $1.ljust(6, '0')
|
70
|
+
city_id = "#{$1}#{$2}".ljust(6, '0')
|
71
|
+
if @tree[province_id] && @tree[province_id][:children][city_id]
|
72
|
+
@tree[province_id][:children][city_id][:children][district["id"]] = {:text => district["text"]}
|
73
|
+
|
74
|
+
@list[district["id"]] = district["text"]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/district_cn.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require "district_cn/version"
|
2
|
+
require "active_support/core_ext/module/delegation"
|
3
|
+
require "active_support/core_ext/object/blank"
|
4
|
+
|
5
|
+
module DistrictCn
|
6
|
+
autoload :AsOptions, 'district_cn/as_options'
|
7
|
+
autoload :Code, 'district_cn/code'
|
8
|
+
autoload :Db, 'district_cn/db'
|
9
|
+
|
10
|
+
begin
|
11
|
+
if defined?(ActiveRecord)
|
12
|
+
autoload :ActAsAreaField,'district_cn/act_as_area_field'
|
13
|
+
ActiveRecord::Base.extend(ActAsAreaField::ActiveRecord)
|
14
|
+
end
|
15
|
+
rescue
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
|
20
|
+
def code(id)
|
21
|
+
return id if id.blank?
|
22
|
+
Code.new(id)
|
23
|
+
end
|
24
|
+
|
25
|
+
def search(text,limit=10)
|
26
|
+
results = []
|
27
|
+
list.each do |id,name|
|
28
|
+
break if results.size.eql?(limit)
|
29
|
+
|
30
|
+
if name =~ /#{text}/
|
31
|
+
results << Code.new(id)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
results
|
35
|
+
end
|
36
|
+
|
37
|
+
delegate :provinces,:tree,:list, :to => Db::File
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
silence_warnings do
|
4
|
+
ActiveRecord::Migration.verbose = false
|
5
|
+
ActiveRecord::Base.logger = Logger.new(nil)
|
6
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
7
|
+
end
|
8
|
+
|
9
|
+
ActiveRecord::Base.connection.instance_eval do
|
10
|
+
create_table :companies do |t|
|
11
|
+
t.string :name
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Company < ActiveRecord::Base
|
17
|
+
attr_accessor :region_code
|
18
|
+
attr_accessor :loc_code
|
19
|
+
attr_accessible :region_code
|
20
|
+
|
21
|
+
act_as_area_field :region_code
|
22
|
+
validates :region_code, presence: true
|
23
|
+
end
|
24
|
+
|
25
|
+
describe DistrictCn::ActAsAreaField do
|
26
|
+
subject { Company.new }
|
27
|
+
it "should return nil when attribute region_code is nil" do
|
28
|
+
expect(subject.region_code).to be_nil
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should return \"\" when attribute region_code eql \"\"" do
|
32
|
+
subject.region_code = ""
|
33
|
+
expect(subject.region_code).to eq("")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should return DistrictCn::Code instance" do
|
37
|
+
subject.region_code = 331002
|
38
|
+
expect(subject.region_code).to be_instance_of(DistrictCn::Code)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should cached" do
|
42
|
+
subject.region_code = 331002
|
43
|
+
expect(subject.region_code.object_id).to eq(subject.region_code.object_id)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "331002 and \"\"331002\"\" shoud have diff cache " do
|
47
|
+
subject.region_code = 331002
|
48
|
+
cache1 = subject.region_code
|
49
|
+
subject.region_code = "331002"
|
50
|
+
cache2 = subject.region_code
|
51
|
+
expect(cache1.object_id).not_to eq(cache2.object_id)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should return real value without acts_as_area_field" do
|
55
|
+
subject.loc_code = 331002
|
56
|
+
expect(subject.loc_code).to eq(331002)
|
57
|
+
expect(subject.loc_code).not_to be_instance_of(DistrictCn::Code)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should be unvalid when region_code is blank" do
|
61
|
+
subject.region_code = nil
|
62
|
+
expect(subject).not_to be_valid
|
63
|
+
expect(subject.save).to be_false
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
data/spec/code_spec.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
describe DistrictCn::Code do
|
4
|
+
let!(:code_331002){ DistrictCn::Code.new(331002) }
|
5
|
+
|
6
|
+
it ".value should return real value" do
|
7
|
+
expect(code_331002.value).to eq(331002)
|
8
|
+
expect(DistrictCn::Code.new("331002").value).to eq("331002")
|
9
|
+
end
|
10
|
+
|
11
|
+
it ".id should return region_code.to_s" do
|
12
|
+
expect(code_331002.id).to eq("331002")
|
13
|
+
expect(DistrictCn::Code.new("331002").id).to eq("331002")
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should return province_id" do
|
17
|
+
expect(DistrictCn::Code.new("00000a").province_id).to be_nil
|
18
|
+
expect(DistrictCn::Code.new("000000").province_id).to be_nil
|
19
|
+
expect(DistrictCn::Code.new("330000").province_id).to eq("330000")
|
20
|
+
expect(DistrictCn::Code.new("331000").province_id).to eq("330000")
|
21
|
+
expect(DistrictCn::Code.new("331002").province_id).to eq("330000")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should return city_id" do
|
25
|
+
expect(DistrictCn::Code.new("00000a").city_id).to be_nil
|
26
|
+
expect(DistrictCn::Code.new("000000").city_id).to be_nil
|
27
|
+
expect(DistrictCn::Code.new("330000").city_id).to be_nil
|
28
|
+
expect(DistrictCn::Code.new("331000").city_id).to eq("331000")
|
29
|
+
expect(DistrictCn::Code.new("331002").city_id).to eq("331000")
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should return district_id" do
|
33
|
+
expect(DistrictCn::Code.new("00000a").district_id).to be_nil
|
34
|
+
expect(DistrictCn::Code.new("000000").district_id).to be_nil
|
35
|
+
expect(DistrictCn::Code.new("330000").district_id).to be_nil
|
36
|
+
expect(DistrictCn::Code.new("331000").district_id).to be_nil
|
37
|
+
expect(DistrictCn::Code.new("331002").district_id).to eq("331002")
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should return area type" do
|
41
|
+
expect(DistrictCn::Code.new("330000")).to be_province
|
42
|
+
expect(DistrictCn::Code.new("330000")).not_to be_city
|
43
|
+
expect(DistrictCn::Code.new("330000")).not_to be_district
|
44
|
+
|
45
|
+
expect(DistrictCn::Code.new("331000")).to be_city
|
46
|
+
expect(DistrictCn::Code.new("331000")).not_to be_province
|
47
|
+
expect(DistrictCn::Code.new("331000")).not_to be_district
|
48
|
+
|
49
|
+
expect(DistrictCn::Code.new("331002")).to be_district
|
50
|
+
expect(DistrictCn::Code.new("331002")).not_to be_city
|
51
|
+
expect(DistrictCn::Code.new("331002")).not_to be_province
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should return privince,city,district name" do
|
55
|
+
expect(DistrictCn::Code.new("331002").province_name).to eq("浙江省")
|
56
|
+
expect(DistrictCn::Code.new("331002").city_name).to eq("台州市")
|
57
|
+
expect(DistrictCn::Code.new("331002").district_name).to eq("椒江区")
|
58
|
+
|
59
|
+
expect(DistrictCn::Code.new("330000").city_name).to be_nil
|
60
|
+
expect(DistrictCn::Code.new("330000").district_name).to be_nil
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should get detail area" do
|
64
|
+
expect(DistrictCn::Code.new("330000").get[:children]).not_to be_empty
|
65
|
+
expect(DistrictCn::Code.new("331000").get[:children]).not_to be_empty
|
66
|
+
expect(DistrictCn::Code.new("331002").get[:children]).to be_nil
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should get children" do
|
70
|
+
expect(DistrictCn::Code.new("330000").children).to be_include(["台州市", "331000"])
|
71
|
+
expect(DistrictCn::Code.new("330000").children).not_to be_include(["椒江区", "331002"])
|
72
|
+
|
73
|
+
expect(DistrictCn::Code.new("331000").children).to be_include(["椒江区", "331002"])
|
74
|
+
expect(DistrictCn::Code.new("331002").children).to be_empty
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should get area name" do
|
78
|
+
expect(DistrictCn::Code.new("330000").area_name).to eq("浙江省")
|
79
|
+
expect(DistrictCn::Code.new("331000").area_name).to eq("浙江省-台州市")
|
80
|
+
expect(DistrictCn::Code.new("331002").area_name).to eq("浙江省-台州市-椒江区")
|
81
|
+
end
|
82
|
+
|
83
|
+
context "as options" do
|
84
|
+
it "should return selected" do
|
85
|
+
options = DistrictCn::Code.new("330000").as_options
|
86
|
+
expect(options[:selected_provinces]).not_to be_empty
|
87
|
+
expect(options[:selected_cities]).not_to be_empty
|
88
|
+
expect(options[:selected_districts]).to be_empty
|
89
|
+
|
90
|
+
options = DistrictCn::Code.new("331000").as_options
|
91
|
+
expect(options[:selected_provinces]).not_to be_empty
|
92
|
+
expect(options[:selected_cities]).not_to be_empty
|
93
|
+
expect(options[:selected_districts]).not_to be_empty
|
94
|
+
|
95
|
+
options = DistrictCn::Code.new("331002").as_options
|
96
|
+
expect(options[:selected_provinces]).not_to be_empty
|
97
|
+
expect(options[:selected_cities]).not_to be_empty
|
98
|
+
expect(options[:selected_districts]).not_to be_empty
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
data/spec/db_spec.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
describe DistrictCn::Db do
|
4
|
+
context "file" do
|
5
|
+
subject { DistrictCn::Db::File.instance }
|
6
|
+
|
7
|
+
it "should be cached" do
|
8
|
+
expect(subject.object_id).to eq(DistrictCn::Db::File.instance.object_id)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "tree should be present" do
|
12
|
+
expect(subject.tree).not_to be_empty
|
13
|
+
expect(subject.tree["330000"]).not_to be_nil
|
14
|
+
expect(subject.tree['330000'][:text]).to eq("浙江省")
|
15
|
+
expect(subject.tree['330000'][:children]).not_to be_empty
|
16
|
+
end
|
17
|
+
|
18
|
+
it "list should be present" do
|
19
|
+
expect(subject.list).not_to be_empty
|
20
|
+
expect(subject.list["330000"]).to eq("浙江省")
|
21
|
+
expect(subject.list["331000"]).to eq("台州市")
|
22
|
+
expect(subject.list["331002"]).to eq("椒江区")
|
23
|
+
end
|
24
|
+
|
25
|
+
it "provinces should be present" do
|
26
|
+
expect(subject.provinces).not_to be_empty
|
27
|
+
expect(subject.provinces).to be_include(["浙江省","330000"])
|
28
|
+
expect(subject.provinces).not_to be_include(["台州市","331000"])
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
describe DistrictCn do
|
4
|
+
it ".code should return \"\" or nil" do
|
5
|
+
expect(DistrictCn.code(nil)).to be_nil
|
6
|
+
expect(DistrictCn.code(" ")).to be_blank
|
7
|
+
end
|
8
|
+
|
9
|
+
it ".code should build DistrictCn::Code" do
|
10
|
+
expect(DistrictCn.code("331002")).to be_an_instance_of(DistrictCn::Code)
|
11
|
+
expect(DistrictCn.code("33100a")).to be_an_instance_of(DistrictCn::Code)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "search" do
|
15
|
+
codes = DistrictCn.search("浙江")
|
16
|
+
expect(codes.first).to be_an_instance_of(DistrictCn::Code)
|
17
|
+
expect(codes.first.name).to eq("浙江省")
|
18
|
+
|
19
|
+
codes = DistrictCn.search("浙江a")
|
20
|
+
expect(codes).to be_blank
|
21
|
+
end
|
22
|
+
end
|
data/spec/spec_helper.rb
ADDED