china_region_fu 0.0.6 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +12 -5
- data/.rspec +2 -1
- data/.rubocop.yml +101 -0
- data/.travis.yml +4 -2
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +10 -4
- data/LICENSE.txt +21 -0
- data/README.en.md +212 -0
- data/README.md +103 -68
- data/Rakefile +1 -1
- data/app/controllers/china_region_fu/fetch_options_controller.rb +64 -28
- data/app/models/city.rb +1 -11
- data/app/models/district.rb +1 -11
- data/app/models/province.rb +4 -4
- data/bin/console +14 -0
- data/bin/rspec +17 -0
- data/bin/setup +8 -0
- data/china_region_fu.gemspec +20 -19
- data/db/migrate/20111111111111_create_china_region_tables.rb +19 -17
- data/lib/china_region_fu.rb +4 -3
- data/lib/china_region_fu/engine.rb +11 -9
- data/lib/china_region_fu/errors.rb +5 -0
- data/lib/china_region_fu/helpers/formtastic.rb +4 -6
- data/lib/china_region_fu/helpers/helpers.rb +44 -53
- data/lib/china_region_fu/helpers/simple_form.rb +15 -14
- data/lib/china_region_fu/helpers/utils.rb +82 -0
- data/lib/china_region_fu/version.rb +1 -1
- data/lib/generators/china_region_fu/models/USAGE +1 -1
- data/lib/generators/china_region_fu/models/models_generator.rb +3 -3
- data/lib/tasks/{region.rake → china_region_fu_tasks.rake} +8 -14
- data/spec/china_region_fu_spec.rb +3 -7
- data/spec/helpers/helpers_spec.rb +113 -0
- data/spec/helpers/utils_spec.rb +87 -0
- data/spec/spec_helper.rb +6 -1
- data/spec/support/models.rb +39 -0
- metadata +50 -61
- data/LICENSE +0 -22
- data/lib/china_region_fu/exceptions.rb +0 -7
- data/lib/china_region_fu/helpers/utilities.rb +0 -54
data/README.md
CHANGED
@@ -1,59 +1,76 @@
|
|
1
|
+
# [English Readme](https://github.com/Xuhao/china_region_fu/blob/master/README.en.md)
|
2
|
+
|
1
3
|
# ChinaRegionFu
|
2
4
|
|
3
|
-
[![
|
4
|
-
[![
|
5
|
+
[![Build Status](https://travis-ci.org/Xuhao/china_region_fu.svg?branch=master)](https://travis-ci.org/Xuhao/china_region_fu)
|
6
|
+
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/85643743367e47d6853b94431f4f503f)](https://www.codacy.com/app/xuhao/china_region_fu?utm_source=github.com&utm_medium=referral&utm_content=Xuhao/china_region_fu&utm_campaign=Badge_Grade)
|
7
|
+
[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/85643743367e47d6853b94431f4f503f)](https://www.codacy.com/app/xuhao/china_region_fu?utm_source=github.com&utm_medium=referral&utm_content=Xuhao/china_region_fu&utm_campaign=Badge_Coverage)
|
8
|
+
[![Gem Version](https://badge.fury.io/rb/china_region_fu.svg)](https://badge.fury.io/rb/china_region_fu)
|
9
|
+
|
10
|
+
ChinaRegionFu 是一个提供中国行政区域信息的rails engine(只适用rails)。使用ChinaRegionFu后,你将拥有全面且准确的中国区域数据,而且你还可以使用其提供的表单helper,轻松将具有联动效果的区域选择器放入你的表单中。
|
11
|
+
|
12
|
+
![Screenshot](https://cloud.githubusercontent.com/assets/324973/19191241/2c5726a8-8cd4-11e6-960d-6edd1fa69427.gif "Screenshot")
|
13
|
+
|
14
|
+
## 运行环境
|
5
15
|
|
6
|
-
|
16
|
+
* CRuby 2.2 及以上
|
17
|
+
* Rails 4.0 及以上
|
18
|
+
* jQuery 1.8 及以上, 若不需要联动效果可忽略
|
7
19
|
|
8
|
-
##
|
20
|
+
## 安装
|
9
21
|
|
10
|
-
|
22
|
+
将 'gem china_region_fu' 放入项目的 Gemfile中:
|
11
23
|
|
12
24
|
gem 'china_region_fu'
|
13
25
|
|
14
|
-
|
26
|
+
运行 bundler 命令来安装:
|
15
27
|
|
16
28
|
bundle install
|
17
29
|
|
18
|
-
|
30
|
+
bundle安装完成后,你需要依次运行以下命令:
|
19
31
|
|
20
|
-
1.
|
32
|
+
1. 复制数据迁移文件到项目中:
|
21
33
|
|
22
|
-
<pre>rake
|
34
|
+
<pre>rake china_region_fu:install:migrations</pre>
|
23
35
|
|
24
|
-
2.
|
36
|
+
2. 运行 db:migrate 来创建数据库表:
|
25
37
|
|
26
38
|
<pre>rake db:migrate</pre>
|
27
39
|
|
28
|
-
3.
|
40
|
+
3. 从[china_region_data](https://raw.github.com/Xuhao/china_region_data)下载最新的<b>[regions.yml](https://raw.github.com/Xuhao/china_region_data/master/regions.yml)</b>:
|
29
41
|
|
30
|
-
<pre>rake
|
42
|
+
<pre>rake china_region_fu:download</pre>
|
31
43
|
|
32
|
-
4.
|
44
|
+
4. 将区域信息导入到数据库:
|
33
45
|
|
34
|
-
<pre>rake
|
46
|
+
<pre>rake china_region_fu:import</pre>
|
35
47
|
|
36
|
-
|
48
|
+
以上每条命令运行完成后,你可以根据需要对生成的文件做一些修改,然后再运行下一条。如果你不需要做自定义修改,也可以通过下面这条命令实现和上面4个命令同样的动作:
|
37
49
|
|
38
|
-
rake
|
50
|
+
rake china_region_fu:setup
|
39
51
|
|
40
|
-
|
52
|
+
区域数据来自[china_region_data](https://github.com/Xuhao/china_region_data), 你可以通过查看这个git库来了解都有哪些区域数据。
|
41
53
|
|
42
|
-
|
54
|
+
ChinaRegionFu通过`ActiveRecord`将各种区域映射成类:
|
55
|
+
* `Province`: 省、自治区、直辖市、特别行政区
|
56
|
+
* `City`: 地区、盟、自治州、地级市
|
57
|
+
* `District`: 县、自治县、旗、自治旗、县级市、市辖区、林区、特区
|
58
|
+
|
59
|
+
如果你想自定义这些类,可以通过以下命令将其复制到你的项目中:
|
43
60
|
|
44
61
|
rails g china_region_fu:models
|
45
62
|
|
46
|
-
|
63
|
+
将会创建:
|
47
64
|
|
48
65
|
create app/models/province.rb
|
49
66
|
create app/models/city.rb
|
50
67
|
create app/models/district.rb
|
51
68
|
|
52
|
-
|
69
|
+
你可以在以上这些类文件中做任何事。
|
53
70
|
|
54
|
-
##
|
71
|
+
## 使用说明
|
55
72
|
|
56
|
-
|
73
|
+
### Model
|
57
74
|
|
58
75
|
```ruby
|
59
76
|
a = Province.last
|
@@ -80,83 +97,98 @@ d.city.name # => "阿勒泰地区"
|
|
80
97
|
d.province.name # => "新疆维吾尔自治区"
|
81
98
|
```
|
82
99
|
|
83
|
-
|
100
|
+
### View
|
84
101
|
|
85
|
-
|
102
|
+
#### Form helpers
|
86
103
|
|
87
104
|
```erb
|
88
|
-
<%= form_for(@
|
105
|
+
<%= form_for(@address) do |f| %>
|
89
106
|
<div class="field">
|
90
107
|
<%= f.label :province, '选择地区:' %><br />
|
91
108
|
|
92
109
|
# FormBuilder
|
93
110
|
<%= f.region_select :city %>
|
94
|
-
<%= f.region_select [:
|
95
|
-
<%= f.region_select [:
|
96
|
-
<%= f.region_select [:
|
97
|
-
<%= f.region_select [:province, :district] %>
|
111
|
+
<%= f.region_select [:province_id, :city_id, :district_id], province_prompt: 'Do', city_prompt: 'it', district_prompt: 'like this' %>
|
112
|
+
<%= f.region_select [:province_id, :city_id], include_blank: true %>
|
113
|
+
<%= f.region_select [:city_id, :district_id] %>
|
98
114
|
|
99
115
|
# FormHelper
|
100
|
-
<%= region_select :
|
101
|
-
<%= region_select :
|
116
|
+
<%= region_select :address, :province_id %>
|
117
|
+
<%= region_select :address, [:province_id, :city_id, :district_id] %>
|
102
118
|
...
|
103
119
|
|
104
120
|
# FormTagHelper
|
105
|
-
<%= region_select_tag :
|
106
|
-
<%= region_select_tag [:
|
121
|
+
<%= region_select_tag :province_id, class: 'province-select', include_blank: true %>
|
122
|
+
<%= region_select_tag [:province_id, :city_id, :district_id], province_prompt: 'Do', city_prompt: 'it', district_prompt: 'like this', class: 'region-select' %>
|
107
123
|
...
|
108
124
|
</div>
|
109
125
|
<% end %>
|
110
126
|
```
|
111
127
|
|
112
|
-
|
128
|
+
#### SimpleForm
|
113
129
|
|
114
130
|
```erb
|
115
|
-
<%= simple_form_for(@
|
116
|
-
<%= f.input :
|
117
|
-
<%= f.input :
|
118
|
-
<%= f.input :
|
119
|
-
<%=
|
131
|
+
<%= simple_form_for(@address) do |f| %>
|
132
|
+
<%= f.input :province_id, as: :region, collection: Province.select('id, name'), sub_region: :city_id %>
|
133
|
+
<%= f.input :city_id, as: :region, collection: City.where(province_id: f.object.province_id).select('id, name'), sub_region: :district_id %>
|
134
|
+
<%= f.input :district_id, as: :region, collection: District.where(city_id: f.object.city_id).select('id, name') %>
|
135
|
+
<%= china_region_fu_js %>
|
120
136
|
<% end %>
|
121
137
|
```
|
122
138
|
|
123
|
-
|
139
|
+
#### Formtastic
|
124
140
|
|
125
141
|
```erb
|
126
|
-
<%= semantic_form_for(@
|
127
|
-
<%= f.input :
|
128
|
-
<%= f.input :
|
129
|
-
<%= f.input :
|
130
|
-
<%=
|
142
|
+
<%= semantic_form_for(@address) do |f| %>
|
143
|
+
<%= f.input :province_id, as: :region, collection: Province.select('id, name'), sub_region: :city_id) %>
|
144
|
+
<%= f.input :city_id, as: :region, collection: City.where(province_id: f.object.province_id).select('id, name'), sub_region: :district_id %>
|
145
|
+
<%= f.input :district_id, as: :region, collection: District.where(city_id: f.object.city_id).select('id, name') %>
|
146
|
+
<%= china_region_fu_js %>
|
131
147
|
<% end %>
|
132
148
|
```
|
133
149
|
|
134
|
-
|
150
|
+
#### 通过AJAX实现联动效果
|
135
151
|
|
136
|
-
|
152
|
+
当选中某个省份时,我们希望城市下拉列表自动显示这个省下属的城市;当选择某个城市时,我们希望区域列表显示该城市下属区域。你需要在你的页面中添加以下helper来实现联动效果:
|
137
153
|
|
138
154
|
```erb
|
139
|
-
<%=
|
155
|
+
<%= china_region_fu_js %>
|
156
|
+
# or
|
157
|
+
<%= content_for :china_region_fu_js %>
|
140
158
|
```
|
141
159
|
|
142
|
-
|
160
|
+
将会渲染:
|
143
161
|
|
144
162
|
```html
|
145
163
|
<script type="text/javascript">
|
146
164
|
//<![CDATA[
|
165
|
+
window.chinaRegionFu = window.chinaRegionFu || {};
|
147
166
|
$(function(){
|
148
|
-
$('body').on('change', '.
|
149
|
-
var self, $targetDom;
|
150
|
-
self = $(event.currentTarget);
|
151
|
-
$
|
152
|
-
if ($
|
153
|
-
$.getJSON('/china_region_fu/fetch_options', {
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
})
|
159
|
-
|
167
|
+
$('body').off('change', '.china-region-select').on('change', '.china-region-select', function(event) {
|
168
|
+
var $self, $targetDom;
|
169
|
+
$self = $(event.currentTarget);
|
170
|
+
$subRegionDom = $('[data-region-name="' + $self.data('sub-region') + '"]');
|
171
|
+
if ($subRegionDom.size() > 0) {
|
172
|
+
$.getJSON('/china_region_fu/fetch_options', {
|
173
|
+
columns: window.chinaRegionFu.fetchColumns || 'id,name',
|
174
|
+
sub_name: $self.data('sub-region'),
|
175
|
+
parent_name: $self.data('region-name'),
|
176
|
+
parent_id: $self.val()
|
177
|
+
}, function(json) {
|
178
|
+
if (window.chinaRegionFu.ajaxDone) {
|
179
|
+
window.chinaRegionFu.ajaxDone(json);
|
180
|
+
} else {
|
181
|
+
var options = [];
|
182
|
+
$self.parent().nextAll().find('.china-region-select > option[value!=""]').remove()
|
183
|
+
$.each(json.data, function(_, value) {
|
184
|
+
options.push("<option value='" + value.id + "'>" + value.name + "</option>");
|
185
|
+
});
|
186
|
+
$subRegionDom.append(options.join(''));
|
187
|
+
}
|
188
|
+
}).fail(function(xhr, textStatus, error) {
|
189
|
+
window.chinaRegionFu.ajaxFail && window.chinaRegionFu.ajaxFail(xhr, textStatus, error);
|
190
|
+
}).always(function(event, xhr, settings) {
|
191
|
+
window.chinaRegionFu.ajaxAlways && window.chinaRegionFu.ajaxAlways(event, xhr, settings);
|
160
192
|
});
|
161
193
|
}
|
162
194
|
});
|
@@ -164,19 +196,22 @@ it will render:
|
|
164
196
|
//]]>
|
165
197
|
</script>
|
166
198
|
```
|
199
|
+
自定义:
|
200
|
+
|
201
|
+
* `window.chinaRegionFu.fetchColumns`: 设定返回栏位数据,默认: `id,name`
|
202
|
+
* `window.chinaRegionFu.ajaxDone(json)`: 自定义ajax `success`事件,默认:重新填充下级选项
|
203
|
+
* `window.chinaRegionFu.ajaxFail(xhr, textStatus, error)`: 自定义ajax `fail`事件
|
204
|
+
* `window.chinaRegionFu.ajaxAlways(event, xhr, settings)`: 自定义ajax `always`事件
|
205
|
+
|
206
|
+
## 在线的例子
|
167
207
|
|
168
|
-
## Online example
|
169
208
|
[医院之家](http://www.yihub.com/ "医院").
|
170
209
|
|
171
210
|
## Contributing
|
172
211
|
|
173
|
-
|
174
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
175
|
-
3. Commit your changes (`git commit -am 'Added some feature'`)
|
176
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
177
|
-
5. Create new Pull Request
|
212
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/xuhao/china_region_fu. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
|
178
213
|
|
179
214
|
## License
|
180
215
|
|
181
|
-
|
216
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
182
217
|
|
data/Rakefile
CHANGED
@@ -1,37 +1,73 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
end
|
1
|
+
# GET: /china_region_fu/fetch_options
|
2
|
+
# Pass parent's id(by `province_id`, `city_id`) to fetch children(cities, districts),
|
3
|
+
# If no parent id passed, return all provinces.
|
4
|
+
#
|
5
|
+
# ==== Paramters
|
6
|
+
#
|
7
|
+
# * <tt>:columns</tt> - Set the columns to return for each one.
|
8
|
+
# * <tt>:parent_name</tt> - Name for parent region
|
9
|
+
# * <tt>:parent_id</tt> - ID for parent region
|
10
|
+
# * <tt>:sub_name</tt> - Name for sub region
|
11
|
+
require 'active_support/core_ext/object/blank'
|
13
12
|
|
14
|
-
|
13
|
+
class ChinaRegionFu::FetchOptionsController < ::ActionController::Metal
|
14
|
+
def index
|
15
|
+
validate_params!
|
16
|
+
regions = sub_klass.order(name: :asc)
|
17
|
+
regions = regions.where(where_value) if where_value
|
18
|
+
regions = regions.reorder(level: :asc) if has_level_column?
|
19
|
+
regions = regions.select(region_fields)
|
20
|
+
self.response_body = { data: regions.as_json(only: region_fields, methods: region_methods) }.to_json
|
21
|
+
rescue => e
|
22
|
+
self.response_body = { error: e.message }.to_json
|
23
|
+
self.status = 500
|
24
|
+
end
|
15
25
|
|
16
|
-
|
17
|
-
klass.column_names.to_a.include?('level')
|
18
|
-
end
|
26
|
+
private
|
19
27
|
|
20
|
-
|
21
|
-
|
22
|
-
|
28
|
+
def has_level_column?
|
29
|
+
sub_klass.column_names.include?('level')
|
30
|
+
end
|
23
31
|
|
24
|
-
|
25
|
-
|
26
|
-
|
32
|
+
# Available fields in DB
|
33
|
+
def region_fields
|
34
|
+
sub_klass.column_names & params_columns
|
35
|
+
end
|
27
36
|
|
28
|
-
|
29
|
-
|
37
|
+
# Methods can be responded to
|
38
|
+
def region_methods
|
39
|
+
params_columns.select { |column| sub_klass.instance_methods(false).include?(column.to_sym) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def params_columns
|
43
|
+
@params_columns ||= params[:columns].to_s.split(',').presence || %w(id name)
|
44
|
+
end
|
45
|
+
|
46
|
+
def sub_klass
|
47
|
+
@sub_klass ||= begin
|
48
|
+
case params[:sub_name]
|
49
|
+
when 'city', 'city_id' then City
|
50
|
+
when 'district', 'district_id' then District
|
51
|
+
end
|
30
52
|
end
|
53
|
+
end
|
31
54
|
|
32
|
-
|
33
|
-
|
55
|
+
def where_value
|
56
|
+
@where_value ||= begin
|
57
|
+
case params[:parent_name]
|
58
|
+
when 'province', 'province_id' then { province_id: params[:parent_id] }
|
59
|
+
when 'city', 'city_id' then { city_id: params[:parent_id] }
|
60
|
+
else nil
|
61
|
+
end
|
34
62
|
end
|
63
|
+
end
|
35
64
|
|
36
|
-
|
37
|
-
|
65
|
+
def validate_params!
|
66
|
+
(params[:parent_name].present? &&
|
67
|
+
params[:parent_id].present? &&
|
68
|
+
params[:sub_name].present? &&
|
69
|
+
%w(city city_id district district_id).include?(params[:sub_name]) &&
|
70
|
+
%w(province province_id city city_id).include?(params[:parent_name])) ||
|
71
|
+
raise('Invalid paramters!')
|
72
|
+
end
|
73
|
+
end
|
data/app/models/city.rb
CHANGED
@@ -1,18 +1,8 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
class City < ActiveRecord::Base
|
3
|
-
if Rails.version < '4.0'
|
4
|
-
attr_accessible :name, :province_id, :level, :zip_code, :pinyin, :pinyin_abbr
|
5
|
-
end
|
6
|
-
|
7
2
|
belongs_to :province
|
8
3
|
has_many :districts, dependent: :destroy
|
9
4
|
|
10
5
|
def short_name
|
11
|
-
@short_name ||= name.gsub(
|
6
|
+
@short_name ||= name.gsub(/市|(回族|哈萨克|柯尔克孜|彝族|蒙古(族)?|白族|朝鲜族|傣族|景颇族|傈僳族|土家族|壮族|苗族|藏族|哈尼族|羌族|侗族|布依)?自治州|地区|特别行政区$/,'')
|
12
7
|
end
|
13
|
-
|
14
|
-
def brothers
|
15
|
-
@brothers ||= City.where("province_id = #{province_id}")
|
16
|
-
end
|
17
|
-
|
18
8
|
end
|
data/app/models/district.rb
CHANGED
@@ -1,18 +1,8 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
class District < ActiveRecord::Base
|
3
|
-
if Rails.version < '4.0'
|
4
|
-
attr_accessible :name, :city_id, :pinyin, :pinyin_abbr
|
5
|
-
end
|
6
|
-
|
7
2
|
belongs_to :city
|
8
3
|
has_one :province, through: :city
|
9
4
|
|
10
5
|
def short_name
|
11
|
-
@short_name ||= name.
|
6
|
+
@short_name ||= name.sub(/区|县|市$/,'')
|
12
7
|
end
|
13
|
-
|
14
|
-
def brothers
|
15
|
-
@brothers ||= District.where("city_id = #{city_id}")
|
16
|
-
end
|
17
|
-
|
18
8
|
end
|
data/app/models/province.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
class Province < ActiveRecord::Base
|
2
|
-
if Rails.version < '4.0'
|
3
|
-
attr_accessible :name, :pinyin, :pinyin_abbr
|
4
|
-
end
|
5
|
-
|
6
2
|
has_many :cities, dependent: :destroy
|
7
3
|
has_many :districts, through: :cities
|
4
|
+
|
5
|
+
def short_name
|
6
|
+
@short_name ||= name.sub(/省|市|(回族|壮族|维吾尔)?自治区|特别行政区&/, '')
|
7
|
+
end
|
8
8
|
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'china_region_fu'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require 'pry'
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start
|
data/bin/rspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
#
|
4
|
+
# This file was generated by Bundler.
|
5
|
+
#
|
6
|
+
# The application 'rspec' is installed as part of a gem, and
|
7
|
+
# this file is here to facilitate running it.
|
8
|
+
#
|
9
|
+
|
10
|
+
require "pathname"
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
12
|
+
Pathname.new(__FILE__).realpath)
|
13
|
+
|
14
|
+
require "rubygems"
|
15
|
+
require "bundler/setup"
|
16
|
+
|
17
|
+
load Gem.bin_path("rspec-core", "rspec")
|