geo_labels 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +15 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +8 -0
- data/app/assets/config/geo_labels_manifest.js +2 -0
- data/app/assets/javascripts/geo_labels/application.coffee +29 -0
- data/app/assets/stylesheets/geo_labels/application.sass +10 -0
- data/app/controllers/concerns/geo_labels/shared_template_and_instance_methods.rb +21 -0
- data/app/controllers/geo_labels/application_controller.rb +14 -0
- data/app/controllers/geo_labels/contacts_controller.rb +56 -0
- data/app/controllers/geo_labels/dashboard_controller.rb +33 -0
- data/app/controllers/geo_labels/labels_controller.rb +56 -0
- data/app/helpers/geo_labels/application_helper.rb +197 -0
- data/app/jobs/geo_labels/application_job.rb +4 -0
- data/app/mailers/geo_labels/application_mailer.rb +6 -0
- data/app/models/geo_labels/application_record.rb +5 -0
- data/app/models/geo_labels/contact.rb +64 -0
- data/app/models/geo_labels/contact_label.rb +40 -0
- data/app/models/geo_labels/label.rb +15 -0
- data/app/views/geo_labels/application/_button-destroy-record.html.slim +1 -0
- data/app/views/geo_labels/application/_button-edit-record.html.slim +1 -0
- data/app/views/geo_labels/application/_button-new-record.html.slim +1 -0
- data/app/views/geo_labels/application/_messages.html.slim +4 -0
- data/app/views/geo_labels/application/_navigation.html.slim +29 -0
- data/app/views/geo_labels/contacts/_form.html.slim +46 -0
- data/app/views/geo_labels/contacts/edit.html.slim +3 -0
- data/app/views/geo_labels/contacts/index.html.slim +31 -0
- data/app/views/geo_labels/contacts/new.html.slim +3 -0
- data/app/views/geo_labels/contacts/show.html.slim +20 -0
- data/app/views/geo_labels/dashboard/export.html.slim +4 -0
- data/app/views/geo_labels/dashboard/import.html.slim +7 -0
- data/app/views/geo_labels/dashboard/main.html.slim +35 -0
- data/app/views/geo_labels/labels/_form.html.slim +13 -0
- data/app/views/geo_labels/labels/edit.html.slim +3 -0
- data/app/views/geo_labels/labels/index.html.slim +47 -0
- data/app/views/geo_labels/labels/new.html.slim +3 -0
- data/app/views/geo_labels/labels/show.html.slim +12 -0
- data/app/views/layouts/geo_labels/application.html.slim +22 -0
- data/config/environment.rb +2 -0
- data/config/initializers/human_plural.rb +16 -0
- data/config/locales/geo_labels.en.yml +5 -0
- data/config/locales/geo_labels.es.yml +5 -0
- data/config/routes.rb +13 -0
- data/db/migrate/20221019150722_create_geo_labels_contacts.rb +20 -0
- data/db/migrate/20221020180213_create_geo_labels_labels.rb +16 -0
- data/db/migrate/20221020195346_create_geo_labels_contact_labels.rb +12 -0
- data/lib/geo_labels/engine.rb +36 -0
- data/lib/geo_labels/exporter.rb +48 -0
- data/lib/geo_labels/importer.rb +124 -0
- data/lib/geo_labels/version.rb +3 -0
- data/lib/geo_labels.rb +8 -0
- data/lib/tasks/geo_labels_tasks.rake +4 -0
- metadata +267 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
.ui.inverted.menu.fixed
|
2
|
+
= link_to main_app.root_path, class: 'header item'
|
3
|
+
= Rails.application.config.x.geo_labels.link_home_content.call.to_s.html_safe
|
4
|
+
= link_to geo_labels.root_path, class: 'header item'
|
5
|
+
.ui.icon
|
6
|
+
i.map.icon
|
7
|
+
= Rails.application.config.x.geo_labels.application_title.call.to_s.html_safe
|
8
|
+
- if current_user.present?
|
9
|
+
- if can? :read, GeoLabels::Contact
|
10
|
+
= link_to GeoLabels::Contact.model_name.human_plural, [GeoLabels::Contact], class: ['item', active_class('geo_labels/contacts')]
|
11
|
+
- if can? :read, GeoLabels::Label
|
12
|
+
= link_to GeoLabels::Label.model_name.human_plural, [GeoLabels::Label], class: ['item', active_class('geo_labels/labels')]
|
13
|
+
.right.menu
|
14
|
+
- if current_user.present?
|
15
|
+
.ui.dropdown.item
|
16
|
+
span= current_user.respond_to?(:nickname) ? current_user.nickname : current_user.email
|
17
|
+
i.dropdown.icon
|
18
|
+
.menu
|
19
|
+
= link_to geo_labels.export_path, class: 'item'
|
20
|
+
i.file.export.icon
|
21
|
+
span.text= t('geo_labels.export')
|
22
|
+
= link_to geo_labels.import_path, class: 'item'
|
23
|
+
i.file.import.icon
|
24
|
+
span.text= t('geo_labels.import')
|
25
|
+
= link_to main_app.destroy_user_session_path, method: :delete, class: 'item'
|
26
|
+
i.sign.out.icon
|
27
|
+
span.text= t 'user.sign_out'
|
28
|
+
- else
|
29
|
+
= link_to t('devise.sessions.new.sign_in'), main_app.new_user_session_path, class: 'item'
|
@@ -0,0 +1,46 @@
|
|
1
|
+
/table.ui.attached.compact.definition.table
|
2
|
+
= form_with model: @record, class: 'ui form' do |f|
|
3
|
+
.ui.top.attached.segment
|
4
|
+
.field
|
5
|
+
label= t 'form.name'
|
6
|
+
.three.fields
|
7
|
+
.field= f.text_field :first_name, placeholder: at(:first_name)
|
8
|
+
.field= f.text_field :middle_name, placeholder: at(:middle_name)
|
9
|
+
.field= f.text_field :last_name, placeholder: at(:last_name)
|
10
|
+
.field
|
11
|
+
label= t 'form.address'
|
12
|
+
.two.fields
|
13
|
+
.field= f.text_field :street, placeholder: at(:street)
|
14
|
+
.field= f.text_field :subsection, placeholder: at(:subsection)
|
15
|
+
.three.fields
|
16
|
+
.field
|
17
|
+
label= at :city
|
18
|
+
= f.text_field :city, placeholder: at(:city)
|
19
|
+
.field
|
20
|
+
label= at :state
|
21
|
+
= f.text_field :state, placeholder: at(:state)
|
22
|
+
.field
|
23
|
+
label= at :country
|
24
|
+
.ui.fluid.search.selection.dropdown
|
25
|
+
= f.hidden_field :country
|
26
|
+
i.dropdown.icon
|
27
|
+
.default.text= at(:country)
|
28
|
+
.menu
|
29
|
+
- ISO3166::Country.all.each do |country|
|
30
|
+
.item data-value=country.iso_short_name
|
31
|
+
i.flag class=country.alpha2.downcase
|
32
|
+
= country.iso_short_name
|
33
|
+
.ui.attached.segment
|
34
|
+
.ui.fluid.multiple.search.selection.dropdown
|
35
|
+
= f.hidden_field :label_ids, value: @record.label_ids.join(',')
|
36
|
+
/= hidden_field_tag 'label_ids', params[:label_ids]
|
37
|
+
i.dropdown.icon
|
38
|
+
.default.text= GeoLabels::Label.model_name.human_plural
|
39
|
+
.menu
|
40
|
+
- nested_set_options(GeoLabels::Label) {|i| "#{'-' * i.level} #{i.name}" }.each do |(label_name, label_id)|
|
41
|
+
.item data-value=label_id
|
42
|
+
= label_name
|
43
|
+
.ui.bottom.attached.segment
|
44
|
+
= f.submit class: 'ui primary button'
|
45
|
+
- unless @record.new_record?
|
46
|
+
= render 'button-destroy-record', record: @record
|
@@ -0,0 +1,31 @@
|
|
1
|
+
.ui.container
|
2
|
+
= page_title :index, record_class
|
3
|
+
- if @records.any? or @q.conditions.present?
|
4
|
+
= search_form_for(@q, html: {class: 'ui form'}) do |f|
|
5
|
+
= hidden_field_tag :per_page, params[:per_page], id: 'q_per_page'
|
6
|
+
table.ui.compact.top.attached.striped.table
|
7
|
+
thead
|
8
|
+
tr
|
9
|
+
th= sort_link @q, :full_name, at(:full_name), default_order: :asc
|
10
|
+
th.column-filter.actions
|
11
|
+
= search_result_info @records
|
12
|
+
= search_row class: 'ui mini form' do
|
13
|
+
th= f.text_field :full_name_cont, placeholder: at(:full_name)
|
14
|
+
th.column-filter.actions
|
15
|
+
button.ui.mini.primary.icon.button
|
16
|
+
i.filter.icon
|
17
|
+
= link_to polymorphic_path(record_class, reset_q: true), class: 'clear-filters-button ui mini basic yellow icon button' do
|
18
|
+
i.close.icon
|
19
|
+
tbody
|
20
|
+
- @records.each do |record|
|
21
|
+
tr
|
22
|
+
td= link_to record.full_name, record
|
23
|
+
td.actions
|
24
|
+
= table_show_link record
|
25
|
+
= table_edit_link record
|
26
|
+
.ui.bottom.attached.segment
|
27
|
+
= render 'button-new-record'
|
28
|
+
= paginate @records
|
29
|
+
- else
|
30
|
+
p= t 'collection.empty', models: record_class.model_name.human_plural
|
31
|
+
= render 'button-new-record'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
.ui.container
|
2
|
+
= page_title :show, @record, back: [record_class]
|
3
|
+
table.ui.top.attached.compact.definition.table
|
4
|
+
tbody
|
5
|
+
tr
|
6
|
+
td= t 'form.name'
|
7
|
+
td= @record.full_name
|
8
|
+
tr
|
9
|
+
td= GeoLabels::Label.model_name.human_plural
|
10
|
+
td= @record.labels.map(&:name).join(', ')
|
11
|
+
- if @record.geocoded?
|
12
|
+
tr
|
13
|
+
td Location
|
14
|
+
td
|
15
|
+
#map-canvas
|
16
|
+
javascript:
|
17
|
+
window.map_points = #{[@record.map_attributes].to_json.html_safe};
|
18
|
+
script src="https://maps.googleapis.com/maps/api/js?key=#{Rails.application.config.x.geo_labels.google_api_key}&callback=initMap&v=weekly" defer
|
19
|
+
.ui.bottom.attached.segment
|
20
|
+
= render 'button-edit-record', record: @record
|
@@ -0,0 +1,7 @@
|
|
1
|
+
/.ui.grid.container
|
2
|
+
= form_tag geo_labels.import_file_path, method: :post, multipart: true, class: 'ui grid container'
|
3
|
+
= page_title t('geo_labels.import')
|
4
|
+
.ui.top.attached.segment
|
5
|
+
= file_field_tag :file
|
6
|
+
.ui.bottom.attached.segment
|
7
|
+
= submit_tag t('geo_labels.import'), class: 'ui primary button'
|
@@ -0,0 +1,35 @@
|
|
1
|
+
.ui.segment
|
2
|
+
/= select_tag 'label_ids[]', options_for_select(nested_set_options(GeoLabels::Label) {|i| "#{'-' * i.level} #{i.name}" } )
|
3
|
+
form.ui.form method='GET'
|
4
|
+
.field
|
5
|
+
/label Blabla
|
6
|
+
.fields
|
7
|
+
.eleven.wide.field
|
8
|
+
.ui.fluid.multiple.search.selection.dropdown
|
9
|
+
= hidden_field_tag 'label_ids', params[:label_ids]
|
10
|
+
i.dropdown.icon
|
11
|
+
.default.text= GeoLabels::Label.model_name.human_plural
|
12
|
+
.menu
|
13
|
+
/- ISO3166::Country.all.each do |country|
|
14
|
+
- nested_set_options(GeoLabels::Label) {|i| "#{'-' * i.level} #{i.name}" }.each do |(label_name, label_id)|
|
15
|
+
.item data-value=label_id
|
16
|
+
= label_name
|
17
|
+
.four.wide.field
|
18
|
+
.ui.buttons.fluid.and-or-switch-buttons
|
19
|
+
= hidden_field_tag 'predication', params[:predication].presence || 'and'
|
20
|
+
/= text_field_tag 'predication', 'and'
|
21
|
+
.ui.button class=(params[:predication] == 'or' ? '' : 'secondary') data-predicate='and' AND
|
22
|
+
.or
|
23
|
+
.ui.button class=(params[:predication] == 'or' ? 'secondary' : '') data-predicate='or' OR
|
24
|
+
.one.wide.field
|
25
|
+
button.ui.blue.icon.button
|
26
|
+
i.search.icon
|
27
|
+
|
28
|
+
|
29
|
+
- if @contacts.present?
|
30
|
+
.ui.horizontal.divider= GeoLabels::Contact.model_name.human_plural
|
31
|
+
.ui.segment
|
32
|
+
#map-canvas
|
33
|
+
javascript:
|
34
|
+
window.map_points = #{@contacts.map(&:map_attributes).to_json.html_safe};
|
35
|
+
script src="https://maps.googleapis.com/maps/api/js?key=#{Rails.application.config.x.geo_labels.google_api_key}&callback=initMap&v=weekly" defer
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/table.ui.attached.compact.definition.table
|
2
|
+
= form_with model: @record, class: 'ui form' do |f|
|
3
|
+
.ui.top.attached.segment
|
4
|
+
.field
|
5
|
+
label= at :name
|
6
|
+
= f.text_field :name, placeholder: at(:name)
|
7
|
+
.field
|
8
|
+
label= t 'form.parent'
|
9
|
+
= f.select :parent_id, nested_set_options(record_class, @record) {|i| "#{'-' * i.level} #{i.name}" }, {include_blank: true}, class: 'ui clearable selection search dropdown'
|
10
|
+
.ui.bottom.attached.segment
|
11
|
+
= f.submit class: 'ui primary button'
|
12
|
+
- unless @record.new_record?
|
13
|
+
= render 'button-destroy-record', record: @record
|
@@ -0,0 +1,47 @@
|
|
1
|
+
.ui.container
|
2
|
+
= page_title :index, record_class
|
3
|
+
- if @records.any? or @q.conditions.present?
|
4
|
+
= search_form_for(@q, html: {class: 'ui form'}) do |f|
|
5
|
+
= hidden_field_tag :per_page, params[:per_page], id: 'q_per_page'
|
6
|
+
table.ui.compact.top.attached.striped.table
|
7
|
+
thead
|
8
|
+
tr
|
9
|
+
th= sort_link @q, :name, at(:name), default_order: :asc
|
10
|
+
th.column-filter.actions
|
11
|
+
= search_result_info @records
|
12
|
+
= search_row class: 'ui mini form' do
|
13
|
+
th= f.text_field :name_cont, placeholder: at(:name)
|
14
|
+
th.column-filter.actions
|
15
|
+
button.ui.mini.primary.icon.button
|
16
|
+
i.filter.icon
|
17
|
+
= link_to polymorphic_path(record_class, reset_q: true), class: 'clear-filters-button ui mini basic yellow icon button' do
|
18
|
+
i.close.icon
|
19
|
+
tbody
|
20
|
+
- @records.each do |record|
|
21
|
+
tr
|
22
|
+
td= link_to record.name, record
|
23
|
+
td.actions
|
24
|
+
= table_show_link record
|
25
|
+
= table_edit_link record
|
26
|
+
.ui.bottom.attached.segment
|
27
|
+
= render 'button-new-record'
|
28
|
+
= paginate @records
|
29
|
+
- else
|
30
|
+
p= t 'collection.empty', models: record_class.model_name.human_plural
|
31
|
+
= render 'button-new-record'
|
32
|
+
|
33
|
+
.ui.styled.accordion
|
34
|
+
.title.active
|
35
|
+
i.dropdown.icon
|
36
|
+
= t 'geo_labels.tree_title'
|
37
|
+
.content.active= link_to_labels_tree
|
38
|
+
/
|
39
|
+
.ui.dividing.header= t 'geo_labels.tree_title'
|
40
|
+
.ui.segment
|
41
|
+
- record_class.nested_set_scope.each do |record|
|
42
|
+
.record-in-tree= link_to "#{' ' * record.level + '-'} #{record.name}".html_safe, record
|
43
|
+
- nested_set_options(record_class){|i| "#{' ' * i.level + '-'} #{i.name}".html_safe }.each do |option|
|
44
|
+
.record-in-tree= option.first
|
45
|
+
.ui.segment= link_to_labels_tree
|
46
|
+
|
47
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
.ui.container
|
2
|
+
= page_title :show, @record, back: [record_class]
|
3
|
+
table.ui.top.attached.compact.definition.table
|
4
|
+
tbody
|
5
|
+
tr
|
6
|
+
td= at :name
|
7
|
+
td= @record.name
|
8
|
+
tr
|
9
|
+
td Parents
|
10
|
+
td= @record.ancestors.map(&:name).join(' -> ')
|
11
|
+
.ui.bottom.attached.segment
|
12
|
+
= render 'button-edit-record', record: @record
|
@@ -0,0 +1,22 @@
|
|
1
|
+
doctype html
|
2
|
+
html lang="en"
|
3
|
+
head
|
4
|
+
title= content_for?(:title) ? yield(:title) : 'GeoLabels Engine'
|
5
|
+
meta name="description" content="#{content_for?(:description) ? yield(:description) : 'Application'}"
|
6
|
+
= csrf_meta_tags
|
7
|
+
= csp_meta_tag
|
8
|
+
|
9
|
+
meta charset="utf-8"
|
10
|
+
meta content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" name="viewport"
|
11
|
+
|
12
|
+
= stylesheet_link_tag 'geo_labels/application', media: 'all'
|
13
|
+
= javascript_include_tag 'geo_labels/application'
|
14
|
+
= content_for :head
|
15
|
+
body
|
16
|
+
header= render 'navigation'
|
17
|
+
span= Rails.env
|
18
|
+
main role="main"
|
19
|
+
= render 'messages'
|
20
|
+
= yield
|
21
|
+
hr
|
22
|
+
p= Rails.application.config.x.geo_labels.application_title.call.to_s.html_safe
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
class Name
|
3
|
+
def human_plural
|
4
|
+
# Try to find the plural name of a model. If none can be found try to find a non namespaced version and return that one
|
5
|
+
default = Proc.new do |path|
|
6
|
+
if path.present? and path['/'] # non namespaced model a/b/c => a
|
7
|
+
unnamespaced_path = path.sub(/\.(\w+)\/[\w\\]+/, '.\1')
|
8
|
+
I18n.t(unnamespaced_path, default: Proc.new{ human.pluralize })
|
9
|
+
else
|
10
|
+
human.pluralize
|
11
|
+
end
|
12
|
+
end
|
13
|
+
I18n.t("#{@klass.i18n_scope}.models.plural.#{i18n_key}", default: default )
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
class CreateGeoLabelsContacts < ActiveRecord::Migration[7.0]
|
2
|
+
def change
|
3
|
+
create_table :geo_labels_contacts do |t|
|
4
|
+
t.string :first_name
|
5
|
+
t.string :middle_name
|
6
|
+
t.string :last_name
|
7
|
+
t.string :street
|
8
|
+
t.string :subsection
|
9
|
+
t.string :city
|
10
|
+
t.string :state
|
11
|
+
t.string :country
|
12
|
+
t.text :description
|
13
|
+
t.float :latitude
|
14
|
+
t.float :longitude
|
15
|
+
|
16
|
+
t.timestamps
|
17
|
+
t.index [:latitude, :longitude]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class CreateGeoLabelsLabels < ActiveRecord::Migration[7.0]
|
2
|
+
def change
|
3
|
+
create_table :geo_labels_labels do |t|
|
4
|
+
t.string :name
|
5
|
+
t.integer :parent_id, null: true, index: true
|
6
|
+
t.integer :lft, null: false, index: true
|
7
|
+
t.integer :rgt, null: false, index: true
|
8
|
+
|
9
|
+
# optional fields
|
10
|
+
t.integer :depth, null: false, default: 0
|
11
|
+
t.integer :children_count, null: false, default: 0
|
12
|
+
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class CreateGeoLabelsContactLabels < ActiveRecord::Migration[7.0]
|
2
|
+
def change
|
3
|
+
create_table :geo_labels_contact_labels, id: false do |t|
|
4
|
+
#t.belongs_to :contact, null: false, foreign_key: true
|
5
|
+
#t.belongs_to :label, null: false, foreign_key: true
|
6
|
+
t.integer :contact_id, index: true
|
7
|
+
t.integer :label_id, index: true
|
8
|
+
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'geocoder'
|
2
|
+
require 'slim-rails'
|
3
|
+
require 'coffee-rails'
|
4
|
+
require 'sassc-rails'
|
5
|
+
require 'jquery-rails'
|
6
|
+
require 'cancancan'
|
7
|
+
require 'ransack'
|
8
|
+
require 'kaminari'
|
9
|
+
require 'countries/global'
|
10
|
+
require 'awesome_nested_set'
|
11
|
+
|
12
|
+
module GeoLabels
|
13
|
+
class Engine < ::Rails::Engine
|
14
|
+
isolate_namespace GeoLabels
|
15
|
+
|
16
|
+
initializer "geo_labels.assets.precompile" do |main_app|
|
17
|
+
#app.config.assets.precompile << "config/engine_name_manifest.js"
|
18
|
+
main_app.config.assets.precompile << "geo_labels/application.css"
|
19
|
+
main_app.config.assets.precompile << "geo_labels/application.js"
|
20
|
+
|
21
|
+
# Engine specific config
|
22
|
+
main_app.config.x.geo_labels.application_title ||= -> { 'GEO-labels' }
|
23
|
+
main_app.config.x.geo_labels.link_home_content ||= -> { '<i class="arrow left icon"></i> Back' }
|
24
|
+
main_app.config.x.geo_labels.google_api_key ||= "GOOGLE_API_KEY"
|
25
|
+
end
|
26
|
+
|
27
|
+
# add migrations to containing application
|
28
|
+
initializer 'geo_labels.append_migrations' do |app|
|
29
|
+
unless app.root.to_s.match root.to_s
|
30
|
+
config.paths["db/migrate"].expanded.each do |expanded_path|
|
31
|
+
app.config.paths["db/migrate"] << expanded_path
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
module GeoLabels
|
3
|
+
class Exporter
|
4
|
+
def self.export_h
|
5
|
+
obj = {
|
6
|
+
'labels' => labels_tree,
|
7
|
+
'contacts' => GeoLabels::Contact.includes(:labels).map{ |contact|
|
8
|
+
contact.attributes.merge('labels' => contact.labels.map(&:name)).select{ |attr, val|
|
9
|
+
val.present? and not %w[id created_at updated_at].include?(attr)
|
10
|
+
}
|
11
|
+
}
|
12
|
+
}
|
13
|
+
obj
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.export_str
|
17
|
+
export_h.to_yaml
|
18
|
+
end
|
19
|
+
|
20
|
+
# Formats the tree structure from labels_tree_h to a string output
|
21
|
+
# can be called with add_punctuation: '-' to add dashes
|
22
|
+
def self.labels_tree(add_punctuation: nil)
|
23
|
+
output = ""
|
24
|
+
tree_formatter = Proc.new do |items, lead_space|
|
25
|
+
items.each do |item|
|
26
|
+
output += "#{lead_space}#{item[:name]}\n"
|
27
|
+
tree_formatter.call(item[:children], lead_space + ' ') if item[:children].present?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
tree_formatter.call(labels_tree_h, '')
|
31
|
+
output.gsub!(/^(\s*) /, "\1#{add_punctuation} ") if add_punctuation.present?
|
32
|
+
output
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns an array having the tree structure as ruby objects
|
36
|
+
def self.labels_tree_h
|
37
|
+
labels = GeoLabels::Label.order(:name).to_a
|
38
|
+
tree = Hash.new { |h, k| h[k] = {name: nil, id: nil, children: []} }
|
39
|
+
labels.each do |label|
|
40
|
+
id, name, parent_id = label.attributes.values_at('id', 'name', 'parent_id')
|
41
|
+
tree[id][:name] = name
|
42
|
+
tree[id][:id] = id
|
43
|
+
tree[parent_id][:children].push tree[id]
|
44
|
+
end
|
45
|
+
tree[nil][:children]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module GeoLabels
|
2
|
+
module Importer
|
3
|
+
def self.import(subject)
|
4
|
+
yaml_h = case subject
|
5
|
+
when String
|
6
|
+
if subject.start_with?('/') or subject.start_with?('./')
|
7
|
+
YAML.load_file(subject)
|
8
|
+
else
|
9
|
+
YAML.load(subject)
|
10
|
+
end
|
11
|
+
when File then YAML.load(subject.read)
|
12
|
+
when Pathname then YAML.load(subject.read)
|
13
|
+
when ActionDispatch::Http::UploadedFile then YAML.load(subject.read)
|
14
|
+
else
|
15
|
+
raise "Argument not recognized"
|
16
|
+
end
|
17
|
+
GeoLabels::Contact.delete_all
|
18
|
+
GeoLabels::Label.delete_all
|
19
|
+
GeoLabels::ContactLabel.delete_all
|
20
|
+
labels_dict = persist_labels_tree yaml_h['labels']
|
21
|
+
yaml_h['contacts'].each do |contact_attributes|
|
22
|
+
label_names = Array(contact_attributes.delete 'labels')
|
23
|
+
contact = GeoLabels::Contact.new(contact_attributes)
|
24
|
+
contact.labels = label_names.map{labels_dict[_1]}.compact
|
25
|
+
contact.save
|
26
|
+
end
|
27
|
+
GeoLabels::Contact.count
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.labels_text_to_tree(tree_text)
|
31
|
+
txt = tree_text.gsub("\n\u0001", "\n") # remove START OF HEADING TRASH
|
32
|
+
txt.gsub! /^(\s*)[\-\*] /, '\1 ' # remove list punctuations and interpret just the spaces
|
33
|
+
lines = txt.split("\n")
|
34
|
+
root_label, current_indentation = ImportLabel.new('root'), -1
|
35
|
+
working_object = root_label
|
36
|
+
lines.each do |line|
|
37
|
+
line_indentation = (line.match(/\s+/).try(:[], 0).try(:size) || 0) / 2
|
38
|
+
#name = line[(2*line_indentation)..] # strip the leading spaces
|
39
|
+
name = line.strip
|
40
|
+
label = ImportLabel.new(name)
|
41
|
+
if line_indentation > current_indentation # level deeper
|
42
|
+
working_object = working_object.add_child(label)
|
43
|
+
elsif line_indentation < current_indentation
|
44
|
+
(current_indentation - line_indentation).times do
|
45
|
+
working_object = working_object.parent unless working_object.parent.is_root?
|
46
|
+
end
|
47
|
+
working_object = working_object.add_sibling(label)
|
48
|
+
else
|
49
|
+
working_object = working_object.add_sibling label
|
50
|
+
end
|
51
|
+
current_indentation = line_indentation
|
52
|
+
end
|
53
|
+
root_label
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.persist_labels_tree(tree_or_text = nil)
|
57
|
+
tree = case tree_or_text
|
58
|
+
when ImportLabel then tree_or_text
|
59
|
+
when String then labels_text_to_tree(tree_or_text)
|
60
|
+
else
|
61
|
+
raise "Must provide a tree label object or tree text"
|
62
|
+
end
|
63
|
+
record_lookup_dict = {}
|
64
|
+
persister = Proc.new do |items, parent|
|
65
|
+
items.each do |item|
|
66
|
+
record = Label.create name: item.name, parent: parent
|
67
|
+
record_lookup_dict[item.name] = record
|
68
|
+
persister.call item.children, record if item.children.present?
|
69
|
+
end
|
70
|
+
end
|
71
|
+
persister.call(tree.children, nil)
|
72
|
+
record_lookup_dict
|
73
|
+
end
|
74
|
+
|
75
|
+
class LabelList < Array
|
76
|
+
attr_accessor :parent
|
77
|
+
end
|
78
|
+
|
79
|
+
class ImportLabel
|
80
|
+
attr_accessor :name, :children, :parent
|
81
|
+
def initialize(name)
|
82
|
+
@name = name
|
83
|
+
end
|
84
|
+
|
85
|
+
def is_root?
|
86
|
+
name == 'root'
|
87
|
+
end
|
88
|
+
|
89
|
+
def children
|
90
|
+
@children ||= LabelList.new
|
91
|
+
end
|
92
|
+
|
93
|
+
def add_sibling(label)
|
94
|
+
label.parent = parent
|
95
|
+
parent.children.push label
|
96
|
+
label
|
97
|
+
end
|
98
|
+
|
99
|
+
def add_child(label)
|
100
|
+
children.push label
|
101
|
+
label.parent = self
|
102
|
+
label
|
103
|
+
end
|
104
|
+
|
105
|
+
def inspect
|
106
|
+
#"#{name} => #{children.inspect}"
|
107
|
+
#children.any? ? "<#{name}> => #{children.inpsect}" : "#{name}"
|
108
|
+
base = "#{name}"
|
109
|
+
base = "#{base} => #{children.inspect}" if children.any?
|
110
|
+
base
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_h
|
114
|
+
if is_root?
|
115
|
+
{labels: children.map(&:to_h)}
|
116
|
+
else
|
117
|
+
r = {name: name}
|
118
|
+
r[:children] = children.map(&:to_h) if children.any?
|
119
|
+
r
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/lib/geo_labels.rb
ADDED