district_cn 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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