geo_labels 0.2.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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +15 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +28 -0
  5. data/Rakefile +8 -0
  6. data/app/assets/config/geo_labels_manifest.js +2 -0
  7. data/app/assets/javascripts/geo_labels/application.coffee +29 -0
  8. data/app/assets/stylesheets/geo_labels/application.sass +10 -0
  9. data/app/controllers/concerns/geo_labels/shared_template_and_instance_methods.rb +21 -0
  10. data/app/controllers/geo_labels/application_controller.rb +14 -0
  11. data/app/controllers/geo_labels/contacts_controller.rb +56 -0
  12. data/app/controllers/geo_labels/dashboard_controller.rb +33 -0
  13. data/app/controllers/geo_labels/labels_controller.rb +56 -0
  14. data/app/helpers/geo_labels/application_helper.rb +197 -0
  15. data/app/jobs/geo_labels/application_job.rb +4 -0
  16. data/app/mailers/geo_labels/application_mailer.rb +6 -0
  17. data/app/models/geo_labels/application_record.rb +5 -0
  18. data/app/models/geo_labels/contact.rb +64 -0
  19. data/app/models/geo_labels/contact_label.rb +40 -0
  20. data/app/models/geo_labels/label.rb +15 -0
  21. data/app/views/geo_labels/application/_button-destroy-record.html.slim +1 -0
  22. data/app/views/geo_labels/application/_button-edit-record.html.slim +1 -0
  23. data/app/views/geo_labels/application/_button-new-record.html.slim +1 -0
  24. data/app/views/geo_labels/application/_messages.html.slim +4 -0
  25. data/app/views/geo_labels/application/_navigation.html.slim +29 -0
  26. data/app/views/geo_labels/contacts/_form.html.slim +46 -0
  27. data/app/views/geo_labels/contacts/edit.html.slim +3 -0
  28. data/app/views/geo_labels/contacts/index.html.slim +31 -0
  29. data/app/views/geo_labels/contacts/new.html.slim +3 -0
  30. data/app/views/geo_labels/contacts/show.html.slim +20 -0
  31. data/app/views/geo_labels/dashboard/export.html.slim +4 -0
  32. data/app/views/geo_labels/dashboard/import.html.slim +7 -0
  33. data/app/views/geo_labels/dashboard/main.html.slim +35 -0
  34. data/app/views/geo_labels/labels/_form.html.slim +13 -0
  35. data/app/views/geo_labels/labels/edit.html.slim +3 -0
  36. data/app/views/geo_labels/labels/index.html.slim +47 -0
  37. data/app/views/geo_labels/labels/new.html.slim +3 -0
  38. data/app/views/geo_labels/labels/show.html.slim +12 -0
  39. data/app/views/layouts/geo_labels/application.html.slim +22 -0
  40. data/config/environment.rb +2 -0
  41. data/config/initializers/human_plural.rb +16 -0
  42. data/config/locales/geo_labels.en.yml +5 -0
  43. data/config/locales/geo_labels.es.yml +5 -0
  44. data/config/routes.rb +13 -0
  45. data/db/migrate/20221019150722_create_geo_labels_contacts.rb +20 -0
  46. data/db/migrate/20221020180213_create_geo_labels_labels.rb +16 -0
  47. data/db/migrate/20221020195346_create_geo_labels_contact_labels.rb +12 -0
  48. data/lib/geo_labels/engine.rb +36 -0
  49. data/lib/geo_labels/exporter.rb +48 -0
  50. data/lib/geo_labels/importer.rb +124 -0
  51. data/lib/geo_labels/version.rb +3 -0
  52. data/lib/geo_labels.rb +8 -0
  53. data/lib/tasks/geo_labels_tasks.rake +4 -0
  54. 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,3 @@
1
+ .ui.container
2
+ = page_title :edit, @record, back: @record
3
+ = render 'form'
@@ -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,3 @@
1
+ .ui.container
2
+ = page_title :new, record_class, back: [record_class]
3
+ = render 'form'
@@ -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,4 @@
1
+ .ui.grid.container
2
+ = page_title t('geo_labels.export')
3
+ .ui.segment
4
+ p= link_to t('geo_labels.export'), geo_labels.export_file_path, class: 'ui primary button'
@@ -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,3 @@
1
+ .ui.container
2
+ = page_title :edit, @record, back: @record
3
+ = render 'form'
@@ -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,3 @@
1
+ .ui.container
2
+ = page_title :new, record_class, back: [record_class]
3
+ = render 'form'
@@ -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,2 @@
1
+ ### NOTE:
2
+ # This is a dummy file to let the vim-rails vim plugin work with the rails engine
@@ -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
@@ -0,0 +1,5 @@
1
+ en:
2
+ geo_labels:
3
+ export: Export
4
+ import: Import
5
+ tree_title: Tree
@@ -0,0 +1,5 @@
1
+ es:
2
+ geo_labels:
3
+ export: Exportar
4
+ import: Importar
5
+ tree_title: Árbol
data/config/routes.rb ADDED
@@ -0,0 +1,13 @@
1
+ GeoLabels::Engine.routes.draw do
2
+ root 'dashboard#main'
3
+
4
+ controller :dashboard do
5
+ get :export
6
+ get :export_file
7
+ get :import
8
+ post :import_file
9
+ end
10
+
11
+ resources :contacts
12
+ resources :labels
13
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ module GeoLabels
2
+ VERSION = "0.2.0"
3
+ end
data/lib/geo_labels.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'geo_labels/version'
2
+ require 'geo_labels/engine'
3
+ require 'geo_labels/importer'
4
+ require 'geo_labels/exporter'
5
+
6
+ module GeoLabels
7
+ # Your code goes here...
8
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :geo_labels do
3
+ # # Task goes here
4
+ # end