geo_labels 0.3.3 → 0.4.3

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/README.md +1 -0
  4. data/app/assets/javascripts/geo_labels/application.coffee +1 -1
  5. data/app/controllers/geo_labels/application_controller.rb +5 -0
  6. data/app/controllers/geo_labels/contacts_controller.rb +1 -1
  7. data/app/controllers/geo_labels/dashboard_controller.rb +30 -8
  8. data/app/helpers/geo_labels/application_helper.rb +1 -0
  9. data/app/models/geo_labels/contact.rb +15 -1
  10. data/app/models/geo_labels/contact_label.rb +1 -0
  11. data/app/models/geo_labels/label.rb +14 -0
  12. data/app/views/geo_labels/contacts/_form.html.slim +9 -2
  13. data/app/views/geo_labels/contacts/index.html.slim +6 -0
  14. data/app/views/geo_labels/contacts/show.html.slim +5 -1
  15. data/app/views/geo_labels/dashboard/about.html.slim +3 -0
  16. data/app/views/geo_labels/dashboard/export.html.slim +7 -3
  17. data/app/views/geo_labels/dashboard/main.html.slim +8 -1
  18. data/app/views/geo_labels/menu/_navigation.html.slim +23 -0
  19. data/app/views/geo_labels/menu/_right_menu_general.html.slim +11 -0
  20. data/app/views/geo_labels/users/_abilities.html.slim +20 -0
  21. data/app/views/layouts/geo_labels/application.html.slim +1 -1
  22. data/config/locales/geo_labels.en.yml +17 -0
  23. data/config/locales/geo_labels.es.yml +20 -0
  24. data/config/routes.rb +2 -0
  25. data/db/migrate/20231206183544_add_url_to_geo_labels_contacts.rb +5 -0
  26. data/lib/geo_labels/exporter.rb +5 -2
  27. data/lib/geo_labels/importer/csv_data.rb +43 -0
  28. data/lib/geo_labels/importer/yaml_data.rb +116 -0
  29. data/lib/geo_labels/importer.rb +33 -112
  30. data/lib/geo_labels/version.rb +1 -1
  31. data/lib/geo_labels.rb +2 -0
  32. metadata +12 -6
  33. data/app/views/geo_labels/application/_navigation.html.slim +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5dc60b00eeb1b554f6ee06e242c3c3a31586cdd609552a8ca91172fcb18de91f
4
- data.tar.gz: 39bfc36d4ff1551fb0ddd93596050f2381719e579f64df0398b1025f0866510f
3
+ metadata.gz: 30768188b560ce1db38ddd61a8f06c37eb44334bba0ea8309efbf1f0b6b809fe
4
+ data.tar.gz: b295252da7f1b305a7a7029884b3e50c6c239ba54b3914727b51469d38021f4f
5
5
  SHA512:
6
- metadata.gz: b39847ef962187e6f3d81cfe80bb3b71cab106aad5328ade39740aaf7f58a72bb4e296dc596ecad94633722358941971c6da62e1037f1accd7e7ac7c75f40580
7
- data.tar.gz: 4ea54296bf0b43579cd46deb8a58528b277eca56d12846ce3e3617a37f044ede01bc9e9901b6b402fd8a75e8c59fe76bea629fde46ab714f72ae75cb1afbc8f2
6
+ metadata.gz: d145b7d63a43d47d771b1a76c06b17afa032d035d385bc64f89fb0d565b1809b3021f6d970fb018e1ba9983c8a270443d41b5600261046cb612a6cb2e14ef209
7
+ data.tar.gz: fe7b5b251a2a7e51f76f4a39a0aafa8ac308f830c0ad63544cb7fad9d469a96162ea212c07cc41bdc3f48c3dcde8c49d744656a7c20420c33188c6267a33d34f
data/CHANGELOG.md CHANGED
@@ -1,6 +1,31 @@
1
1
  CHANGELOG of geo\_labels
2
2
  ============================
3
3
 
4
+ 2023-12-23 v0.4.3
5
+ -------------------------
6
+ * Optimize nested label representation to sort of N+1 query strategy
7
+ * Show labels on main query result page of contacts
8
+
9
+ 2023-10-25 v0.4.2
10
+ -------------------------
11
+ * Truncate label names to prevent ugly outputting
12
+
13
+ 2023-10-25 v0.4.1
14
+ -------------------------
15
+ * Add CSV import options
16
+ * Add authorization options
17
+ * Allow unsecure access by explicit authorization in `app/models/ability.rb`
18
+ <code>
19
+ can :manage, GeoLabels::Engine
20
+ can :manage, GeoLabels::Contact
21
+ can :manage, GeoLabels::Label
22
+ </code>
23
+
24
+ 2023-10-24 v0.4.0
25
+ -------------------------
26
+ * Set default map zoom level to 13
27
+ * Add CSV export option
28
+
4
29
  2023-09-07 v0.3.3
5
30
  -------------------------
6
31
  * OpenStreetMap gives coordinates in the url after double click. Support copy paste of that format
data/README.md CHANGED
@@ -35,6 +35,7 @@ In your `config/routes.rb` file add:
35
35
  ```
36
36
 
37
37
  ### Add the authorizations
38
+ Authorization is handled by [cancan](https://github.com/CanCanCommunity/cancancan).
38
39
  In your `app/models/ability.rb` file add the authorizations.
39
40
  This is a custom operation that you have to adjust to your needs.
40
41
  To allow all users full controll to the contracts add:
@@ -7,7 +7,7 @@ window.initMap = ->
7
7
  if map_canvas = document.getElementById('map-canvas')
8
8
  window.google_map = new google.maps.Map map_canvas,
9
9
  center: map_points[0]
10
- zoom: 9
10
+ zoom: 13
11
11
 
12
12
  map_points.forEach (map_point) ->
13
13
  marker = new google.maps.Marker
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module GeoLabels
3
3
  class ApplicationController < ::ApplicationController
4
+ before_action :authorize_engine!
4
5
 
5
6
  helper_method :query
6
7
 
@@ -13,5 +14,9 @@ module GeoLabels
13
14
  def query
14
15
  params[:q]
15
16
  end
17
+
18
+ def authorize_engine!
19
+ authorize! :read, GeoLabels::Engine
20
+ end
16
21
  end
17
22
  end
@@ -9,7 +9,7 @@ module GeoLabels
9
9
  authorize! :index, record_class
10
10
  @q = record_class.ransack query
11
11
  @q.sorts = 'name asc' if @q.sorts.empty?
12
- @records = @q.result.page(params[:page]).per(100)
12
+ @records = @q.result.includes(:labels).page(params[:page]).per(100)
13
13
  respond_with(@records)
14
14
  end
15
15
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GeoLabels
3
4
  class DashboardController < ApplicationController
4
5
 
@@ -9,27 +10,48 @@ module GeoLabels
9
10
  # fomantic-ui uses comma separated id values
10
11
  label_ids = label_ids.to_s.split(',') unless label_ids.is_a?(Array)
11
12
  states_array = (params[:states].presence || 'approved').split(',')
12
- @contacts = Contact.geocoded.for_label_ids(label_ids, predication: params[:predication], states: states_array)
13
+ @contacts = Contact.geocoded.includes(:labels).for_label_ids(label_ids, predication: params[:predication], states: states_array)
13
14
  end
14
15
 
16
+ def about; end
17
+
18
+ # SHOW action for the view to call the export_file action
15
19
  def export
16
- # SHOW action for the view to call the export_file action
20
+ authorize! :read, GeoLabels::Contact
21
+ authorize! :read, GeoLabels::Label
17
22
  end
18
23
 
19
24
  def export_file
25
+ authorize! :read, GeoLabels::Contact
26
+ authorize! :read, GeoLabels::Label
27
+
20
28
  timestamp = Time.now.utc.iso8601.first(19).gsub(/-|:/, '')
21
- send_data GeoLabels::Exporter.export_str,
22
- filename: "GeoLabels-export-#{timestamp}.yml",
23
- type: :text,
24
- disposition: :attachment
29
+ respond_to do |format|
30
+ format.yaml do
31
+ send_data GeoLabels::Exporter.export_str,
32
+ filename: "GeoLabels-export-#{timestamp}.yml",
33
+ type: :text,
34
+ disposition: :attachment
35
+ end
36
+ format.csv do
37
+ send_data GeoLabels::Contact.to_csv,
38
+ filename: "GeoLabels-export-#{timestamp}.csv",
39
+ type: :text,
40
+ disposition: :attachment
41
+ end
42
+ end
25
43
  end
26
44
 
27
45
  def import
28
- # SHOW action for the view to call the import_file action
46
+ authorize! :update, GeoLabels::Contact
47
+ authorize! :update, GeoLabels::Label
29
48
  end
30
49
 
31
50
  def import_file
32
- Importer.import params[:file]
51
+ authorize! :update, GeoLabels::Contact
52
+ authorize! :update, GeoLabels::Label
53
+
54
+ GeoLabels::Importer.import params[:file]
33
55
  redirect_to root_path, notice: 'File imported'
34
56
  rescue => e
35
57
  redirect_to import_path, alert: e.message
@@ -13,6 +13,7 @@ module GeoLabels
13
13
  label_name = link_to(item[:name], label_path(item[:id]))
14
14
  edit_link = table_edit_link(item, geo_labels.edit_label_path(item[:id]))
15
15
  add_child_link = link_to(tag.i(class: 'plus icon'), geo_labels.new_label_path(parent_id: item[:id]))
16
+ add_child_link = edit_link = '' unless can? :edit, GeoLabels::Label
16
17
  output += "#{lead_space}#{tag.li(label_name + add_child_link + edit_link)}\n".html_safe
17
18
  next unless item[:children].present?
18
19
 
@@ -7,7 +7,7 @@ module GeoLabels
7
7
  include RatingsSupport
8
8
 
9
9
  RANSACKABLE_ATTRIBUTES = %w[
10
- city country created_at department description
10
+ city country created_at department url description
11
11
  id latitude longitude name state street
12
12
  subsection updated_at
13
13
  ].freeze
@@ -42,6 +42,20 @@ module GeoLabels
42
42
  end
43
43
  end
44
44
 
45
+ def self.to_csv
46
+ require 'csv'
47
+ contacts = includes(:labels).all
48
+ additional_attributes = %w[description state street subsection city department country url latitude longitude]
49
+ CSV.generate do |csv|
50
+ csv << %w[name labels] + additional_attributes
51
+ contacts.each do |contact|
52
+ csv << [contact.name, contact.labels.map(&:name).compact.sort.join('|')] +
53
+ contact.attributes.fetch_values(*additional_attributes)
54
+ end
55
+ end
56
+ end
57
+
58
+
45
59
  def label_ids=(array_or_string)
46
60
  super array_or_string.is_a?(Array) ? array_or_string : array_or_string.split(',')
47
61
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GeoLabels
3
4
  class ContactLabel < ApplicationRecord
4
5
 
@@ -9,6 +9,7 @@ module GeoLabels
9
9
 
10
10
  acts_as_nested_set
11
11
 
12
+ normalizes :name, with: ->(name) { name.strip }
12
13
  validates :name, presence: true
13
14
 
14
15
  def self.descendants_for_ids(ids)
@@ -20,9 +21,22 @@ module GeoLabels
20
21
  descendants_scope
21
22
  end
22
23
 
24
+ def self.each_h_with_level(&blk)
25
+ returner = proc do |label, level|
26
+ blk.call(label, level)
27
+ label[:children].try :each do |child|
28
+ returner.call(child, level + 1)
29
+ end
30
+ end
31
+ Exporter.labels_tree_h.each do |label| # {name: 'Food', id: 3, children: [{name: 'Italian'...}]}
32
+ returner.call(label, 0)
33
+ end
34
+ end
35
+
23
36
  def associated_contacts
24
37
  ids = ContactLabel.joins(:label).merge(self_and_descendants).pluck(:contact_id).uniq
25
38
  Contact.order(:name).find(ids)
26
39
  end
40
+
27
41
  end
28
42
  end
@@ -36,9 +36,12 @@
36
36
  .menu
37
37
  - GeoLabels::Contact::STATE_OPTIONS.each do |option|
38
38
  .item data-value=option = option
39
+ .field
40
+ label= at :url
41
+ = f.text_field :url
39
42
  .field
40
43
  label= at :description
41
- = f.text_field :description
44
+ = f.text_area :description, rows: 3
42
45
  .field
43
46
  .ui.compact.accordion
44
47
  .title
@@ -53,7 +56,11 @@
53
56
  i.dropdown.icon
54
57
  .default.text= GeoLabels::Label.model_name.human_plural
55
58
  .menu
56
- - nested_set_options(GeoLabels::Label) {|i| "#{'-' * i.level} #{i.name}" }.each do |(label_name, label_id)|
59
+ - GeoLabels::Label.each_h_with_level do |label_h, level|
60
+ .item data-value=label_h[:id]
61
+ => '-' * level
62
+ = label_h[:name]
63
+ /- nested_set_options(GeoLabels::Label) {|i| "#{'-' * i.level} #{i.name}" }.each do |(label_name, label_id)|
57
64
  .item data-value=label_id
58
65
  = label_name
59
66
  .ui.bottom.attached.segment
@@ -11,6 +11,7 @@
11
11
  th= sort_link @q, :city, at(:city), default_order: :asc
12
12
  th= sort_link @q, :department, at(:department), default_order: :asc
13
13
  th= sort_link @q, :country, at(:country), default_order: :asc
14
+ th= GeoLabels::Label.model_name.human_plural
14
15
  th.collapsing.column-filter.actions
15
16
  = search_result_info @records
16
17
  = search_row class: 'ui mini form' do
@@ -18,6 +19,7 @@
18
19
  th= f.search_field :city_cont, placeholder: at(:city)
19
20
  th= f.search_field :department_cont, placeholder: at(:department)
20
21
  th= f.search_field :country_cont, placeholder: at(:country)
22
+ th
21
23
  th.collapsing.column-filter.actions
22
24
  button.ui.mini.primary.icon.button
23
25
  i.filter.icon
@@ -32,6 +34,10 @@
32
34
  td= record.city
33
35
  td= record.department
34
36
  td= record.country
37
+ td
38
+ .ui.horizontal.divided.list
39
+ - record.labels.each do |label|
40
+ = link_to label.name, label, class: 'item'
35
41
  td.collapsing.actions
36
42
  = table_show_link record
37
43
  = table_edit_link record
@@ -27,9 +27,13 @@
27
27
  = link_to 'Approve', geo_labels.approve_contact_path(@record.id), method: :put, class: 'ui right floated button positive'
28
28
  .ui.basic.label class=(@record.state == 'rejected' ? 'red' : (@record.state == 'approved' ? 'green' : 'yelow'))
29
29
  = @record.state
30
+ tr
31
+ td= at :url
32
+ td
33
+ a href=@record.url target='_blank' = @record.url
30
34
  tr
31
35
  td= at :description
32
- td= @record.description
36
+ td== @record.description
33
37
  - if @record.geocoded?
34
38
  tr
35
39
  td Location
@@ -0,0 +1,3 @@
1
+ .ui.container
2
+ = page_title t('geo_labels.about.title')
3
+ .ui.segment== t('geo_labels.about.content')
@@ -1,4 +1,8 @@
1
- .ui.grid.container
1
+ .ui.container
2
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'
3
+ .ui.top.attached.segment
4
+ = link_to t('geo_labels.export_yml.title'), geo_labels.export_file_path(format: :yml), class: 'ui primary button'
5
+ .ui.basic.left.pointing.label= t 'geo_labels.export_yml.explanation'
6
+ .ui.bottom.attached.segment
7
+ = link_to t('geo_labels.export_csv.title'), geo_labels.export_file_path(format: :csv), class: 'ui primary button'
8
+ .ui.basic.left.pointing.label= t 'geo_labels.export_csv.explanation'
@@ -11,7 +11,11 @@
11
11
  .default.text= GeoLabels::Label.model_name.human_plural
12
12
  .menu
13
13
  /- ISO3166::Country.all.each do |country|
14
- - nested_set_options(GeoLabels::Label) {|i| "#{'-' * i.level} #{i.name}" }.each do |(label_name, label_id)|
14
+ - GeoLabels::Label.each_h_with_level do |label_h, level|
15
+ .item data-value=label_h[:id]
16
+ => '-' * level
17
+ = label_h[:name]
18
+ /- nested_set_options(GeoLabels::Label) {|i| "#{'-' * i.level} #{i.name}" }.each do |(label_name, label_id)|
15
19
  .item data-value=label_id
16
20
  = label_name
17
21
  .four.wide.field
@@ -47,6 +51,9 @@
47
51
  .ui.item
48
52
  = link_to contact.name, contact
49
53
  = address_tag contact.address, title: contact.description
54
+ - contact.labels.sort_by(&:name).each do |label|
55
+ span.ui.label= label.name
56
+
50
57
  .ui.bottom.attached.tab.segment data-tab='map'
51
58
  #map-canvas
52
59
  /.ui.horizontal.divider= GeoLabels::Contact.model_name.human_plural
@@ -0,0 +1,23 @@
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 can? :read, GeoLabels::Contact
9
+ = link_to GeoLabels::Contact.model_name.human_plural, [GeoLabels::Contact], class: ['item', active_class('geo_labels/contacts')]
10
+ - if can? :read, GeoLabels::Label
11
+ = link_to GeoLabels::Label.model_name.human_plural, [GeoLabels::Label], class: ['item', active_class('geo_labels/labels')]
12
+ .right.menu
13
+ .ui.dropdown.item
14
+ span= current_user.try(:nickname) || current_user.try(:email) || 'Menu'
15
+ i.dropdown.icon
16
+ .menu
17
+ = render 'geo_labels/menu/right_menu_general'
18
+ - if current_user.present?
19
+ = link_to main_app.destroy_user_session_path, method: :delete, class: 'item'
20
+ i.sign.out.icon
21
+ span.text= t 'user.sign_out'
22
+ - else
23
+ = link_to t('devise.sessions.new.sign_in'), main_app.new_user_session_path, class: 'item'
@@ -0,0 +1,11 @@
1
+ = link_to geo_labels.about_path, class: 'item'
2
+ i.question.circle.outline.icon
3
+ span.text= t 'geo_labels.about.title'
4
+ - if can? :read, GeoLabels::Contact and can? :read, GeoLabels::Label
5
+ = link_to geo_labels.export_path, class: 'item'
6
+ i.file.export.icon
7
+ span.text= t('geo_labels.export')
8
+ - if can? :update, GeoLabels::Contact and can? :update, GeoLabels::Label
9
+ = link_to geo_labels.import_path, class: 'item'
10
+ i.file.import.icon
11
+ span.text= t('geo_labels.import')
@@ -0,0 +1,20 @@
1
+ .ui.top.attached.block.header= Rails.application.config.x.geo_labels.application_title.call
2
+ table.roles.ui.bottom.attached.compact.celled.table
3
+ thead
4
+ /th= HawkEye::Analysis.model_name.human
5
+ th
6
+ th read
7
+ th manage
8
+ tbody
9
+ tr
10
+ td Engine
11
+ td= dunlop_ability :read, 'engine', 'geo_labels'
12
+ td= dunlop_ability :manage, 'engine', 'geo_labels'
13
+ tr
14
+ td= GeoLabels::Contact.model_name.human
15
+ td= dunlop_class_ability :read, GeoLabels::Contact
16
+ td= dunlop_class_ability :manage, GeoLabels::Contact
17
+ tr
18
+ td= GeoLabels::Label.model_name.human
19
+ td= dunlop_class_ability :read, GeoLabels::Label
20
+ td= dunlop_class_ability :manage, GeoLabels::Label
@@ -15,7 +15,7 @@ html lang="en"
15
15
  window.geo_labels = #{{paths: GeoLabels::Engine.client_paths}.to_json.html_safe};
16
16
  = content_for :head
17
17
  body
18
- header= render 'navigation'
18
+ header= render 'geo_labels/menu/navigation'
19
19
  span= Rails.env
20
20
  main role="main"
21
21
  = render 'messages'
@@ -1,6 +1,23 @@
1
1
  en:
2
+ activerecord:
3
+ models:
4
+ geo_labels/contact: Contact
5
+ geo_labels/label: Label
6
+ plural:
7
+ geo_labels/contact: Contacts
8
+ geo_labels/label: Labels
9
+ attributes:
10
+ geo_labels/contact:
11
+ url: 'URL'
12
+
2
13
  geo_labels:
3
14
  export: Export
15
+ export_yml:
16
+ title: 'Export backup file (yaml)'
17
+ explanation: 'This export can be used to change and rebuild the data'
18
+ export_csv:
19
+ title: 'Export CSV'
20
+ explanation: 'This file is for spreadsheet views'
4
21
  import: Import
5
22
  tree_title: Tree
6
23
  select_labels_text: Select %{models} and search...
@@ -6,9 +6,19 @@ es:
6
6
  plural:
7
7
  geo_labels/contact: Contactos
8
8
  geo_labels/label: Etiquetas
9
+ attributes:
10
+ geo_labels/contact:
11
+ url: 'URL'
12
+
9
13
 
10
14
  geo_labels:
11
15
  export: Exportar
16
+ export_yml:
17
+ title: 'Exportar archivo de restaurar (yaml)'
18
+ explanation: 'Ese archivo se puede utilizar en rehacer el sistema'
19
+ export_csv:
20
+ title: 'Exportar CSV'
21
+ explanation: 'Ese archivo es para ver los datos en hoja de cálculo'
12
22
  import: Importar
13
23
  tree_title: Árbol
14
24
  select_labels_text: Seleccionar %{models} y busca...
@@ -20,3 +30,13 @@ es:
20
30
  Pues en Google maps se puede copiar y pegar<br>
21
31
  coordinatos manuál. Ejemplo&colon;<br>
22
32
  <code>4.973122, -75.623981</code>
33
+ about:
34
+ title: 'Sobre GEO'
35
+ content: >
36
+ <p>El sistema para manegar listas de contactos y poner etiquetas para buscarlos tiene
37
+ algunas opciones</p>
38
+
39
+ <h3>Manegar etiquetas</h3>
40
+ <p>Las etiquetas son flexibles en como estructurar los contactos. Etiquetas pueden tener
41
+ una jerarquia.
42
+ </p>
data/config/routes.rb CHANGED
@@ -7,6 +7,8 @@ GeoLabels::Engine.routes.draw do
7
7
  get :export_file
8
8
  get :import
9
9
  post :import_file
10
+
11
+ get :about
10
12
  end
11
13
 
12
14
  scope 'ratings', controller: :ratings do
@@ -0,0 +1,5 @@
1
+ class AddUrlToGeoLabelsContacts < ActiveRecord::Migration[7.1]
2
+ def change
3
+ add_column :geo_labels_contacts, :url, :string
4
+ end
5
+ end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'yaml'
3
4
  module GeoLabels
5
+ # Yaml exporter of the engine
4
6
  class Exporter
5
7
 
6
8
  def self.export_h
@@ -20,7 +22,7 @@ module GeoLabels
20
22
  output = ''
21
23
  tree_formatter = proc do |items, lead_space|
22
24
  items.each do |item|
23
- output += "#{lead_space}#{item[:name]}\n"
25
+ output = "#{output}#{lead_space}#{item[:name]}\n"
24
26
  tree_formatter.call(item[:children], "#{lead_space} ") if item[:children].present?
25
27
  end
26
28
  end
@@ -33,7 +35,7 @@ module GeoLabels
33
35
  GeoLabels::Contact.includes(:labels).map do |contact|
34
36
  contact
35
37
  .attributes
36
- .slice(*%w[name street subsection city department country state description latitude longitude])
38
+ .slice(*%w[name street subsection city department country state url description latitude longitude])
37
39
  .merge('labels' => contact.labels.map(&:name))
38
40
  .select { |_, v| v.present? }
39
41
  .transform_values do |value|
@@ -58,5 +60,6 @@ module GeoLabels
58
60
  end
59
61
  tree[nil][:children]
60
62
  end
63
+
61
64
  end
62
65
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ require 'csv'
3
+
4
+ module GeoLabels
5
+ module Importer
6
+ class CsvData
7
+ attr_reader :new_contacts
8
+ ROW_ATTRIBUTES = %w[name description state street subsection city department country url latitude longitude]
9
+
10
+ def import(data)
11
+ @new_contacts = []
12
+
13
+ counter = 2 # start counting from 1 and skip the header. Works with Apple -> Numbers application
14
+ CSV.parse(data, headers: true).each do |row|
15
+ name = row['name']
16
+ raise "Row #{counter} has no name" unless name.present?
17
+
18
+ label_names = row['labels'].to_s.strip.split('|').map(&:strip).select(&:present?)
19
+ raise "Row #{counter} (#{name}) has no labels" unless label_names.any?
20
+ labels = label_names.map do |label_name|
21
+ labels_h[label_name] or raise "Row #{counter} has a label: '#{label_name}' that is not yet in the system. Please add before using it in an import"
22
+ end
23
+
24
+ contact = Contact.new(row.to_h.slice(*ROW_ATTRIBUTES))
25
+ contact.labels = labels
26
+ raise "Row #{counter} creates an invalid contact. Errors: #{contact.errors.full_messages.to_sentence}" unless contact.valid?
27
+ new_contacts.push contact
28
+ counter += 1
29
+ end
30
+
31
+ Contact.delete_all
32
+ ContactLabel.delete_all
33
+ new_contacts.each do |contact|
34
+ contact.save!
35
+ end
36
+ end
37
+
38
+ def labels_h
39
+ @labels_h ||= Label.all.map{ |l| [l.name, l] }.to_h
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GeoLabels
4
+ module Importer
5
+ class YamlData
6
+ def self.import(yaml_h)
7
+ GeoLabels::Contact.delete_all
8
+ GeoLabels::Label.delete_all
9
+ GeoLabels::ContactLabel.delete_all
10
+ labels_dict = persist_labels_tree yaml_h['labels']
11
+ yaml_h['contacts'].each do |contact_attributes|
12
+ label_names = Array(contact_attributes.delete('labels'))
13
+ contact = GeoLabels::Contact.new(contact_attributes)
14
+ contact.labels = label_names.map{ labels_dict[_1] }.compact
15
+ contact.save
16
+ end
17
+ GeoLabels::Contact.count
18
+ end
19
+
20
+ def self.labels_text_to_tree(tree_text)
21
+ txt = tree_text.gsub("\n\u0001", "\n") # remove START OF HEADING TRASH
22
+ txt.gsub! /^(\s*)[-*] /, '\1 ' # remove list punctuations and interpret just the spaces
23
+ lines = txt.split("\n")
24
+ root_label, current_indentation = ImportLabel.new('root'), -1
25
+ working_object = root_label
26
+ lines.each do |line|
27
+ line_indentation = (line.match(/\s+/).try(:[], 0).try(:size) || 0) / 2
28
+ # name = line[(2*line_indentation)..] # strip the leading spaces
29
+ name = line.strip
30
+ label = ImportLabel.new(name)
31
+ if line_indentation > current_indentation # level deeper
32
+ working_object = working_object.add_child(label)
33
+ elsif line_indentation < current_indentation
34
+ (current_indentation - line_indentation).times do
35
+ working_object = working_object.parent unless working_object.parent.is_root?
36
+ end
37
+ working_object = working_object.add_sibling(label)
38
+ else
39
+ working_object = working_object.add_sibling label
40
+ end
41
+ current_indentation = line_indentation
42
+ end
43
+ root_label
44
+ end
45
+
46
+ def self.persist_labels_tree(tree_or_text = nil)
47
+ tree = case tree_or_text
48
+ when ImportLabel then tree_or_text
49
+ when String then labels_text_to_tree(tree_or_text)
50
+ else
51
+ raise 'Must provide a tree label object or tree text'
52
+ end
53
+ record_lookup_dict = {}
54
+ persister = proc do |items, parent|
55
+ items.each do |item|
56
+ record = Label.create name: item.name, parent: parent
57
+ record_lookup_dict[item.name] = record
58
+ persister.call item.children, record if item.children.present?
59
+ end
60
+ end
61
+ persister.call(tree.children, nil)
62
+ record_lookup_dict
63
+ end
64
+
65
+ class LabelList < Array
66
+ attr_accessor :parent
67
+ end
68
+
69
+ class ImportLabel
70
+ attr_accessor :name, :parent
71
+
72
+ def initialize(name)
73
+ @name = name
74
+ end
75
+
76
+ def is_root?
77
+ name == 'root'
78
+ end
79
+
80
+ def children
81
+ @children ||= LabelList.new
82
+ end
83
+
84
+ def add_sibling(label)
85
+ label.parent = parent
86
+ parent.children.push label
87
+ label
88
+ end
89
+
90
+ def add_child(label)
91
+ children.push label
92
+ label.parent = self
93
+ label
94
+ end
95
+
96
+ def inspect
97
+ # "#{name} => #{children.inspect}"
98
+ # children.any? ? "<#{name}> => #{children.inpsect}" : "#{name}"
99
+ base = name.to_s
100
+ base = "#{base} => #{children.inspect}" if children.any?
101
+ base
102
+ end
103
+
104
+ def to_h
105
+ if is_root?
106
+ {labels: children.map(&:to_h)}
107
+ else
108
+ r = {name: name}
109
+ r[:children] = children.map(&:to_h) if children.any?
110
+ r
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -1,128 +1,49 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GeoLabels
3
4
  module Importer
4
5
  def self.import(subject)
5
- yaml_h = case subject
6
- when String
7
- if subject.start_with?('/') or subject.start_with?('./')
8
- YAML.load_file(subject)
9
- else
10
- YAML.safe_load(subject)
11
- end
12
- when File then YAML.safe_load(subject.read)
13
- when Pathname then YAML.safe_load(subject.read)
14
- when ActionDispatch::Http::UploadedFile then YAML.safe_load(subject.read)
15
- else
16
- raise 'Argument not recognized'
17
- end
18
- GeoLabels::Contact.delete_all
19
- GeoLabels::Label.delete_all
20
- GeoLabels::ContactLabel.delete_all
21
- labels_dict = persist_labels_tree yaml_h['labels']
22
- yaml_h['contacts'].each do |contact_attributes|
23
- label_names = Array(contact_attributes.delete('labels'))
24
- contact = GeoLabels::Contact.new(contact_attributes)
25
- contact.labels = label_names.map{ labels_dict[_1] }.compact
26
- contact.save
27
- end
28
- GeoLabels::Contact.count
29
- end
6
+ type, data = get_type_and_data(subject)
30
7
 
31
- def self.labels_text_to_tree(tree_text)
32
- txt = tree_text.gsub("\n\u0001", "\n") # remove START OF HEADING TRASH
33
- txt.gsub! /^(\s*)[-*] /, '\1 ' # remove list punctuations and interpret just the spaces
34
- lines = txt.split("\n")
35
- root_label, current_indentation = ImportLabel.new('root'), -1
36
- working_object = root_label
37
- lines.each do |line|
38
- line_indentation = (line.match(/\s+/).try(:[], 0).try(:size) || 0) / 2
39
- # name = line[(2*line_indentation)..] # strip the leading spaces
40
- name = line.strip
41
- label = ImportLabel.new(name)
42
- if line_indentation > current_indentation # level deeper
43
- working_object = working_object.add_child(label)
44
- elsif line_indentation < current_indentation
45
- (current_indentation - line_indentation).times do
46
- working_object = working_object.parent unless working_object.parent.is_root?
47
- end
48
- working_object = working_object.add_sibling(label)
49
- else
50
- working_object = working_object.add_sibling label
51
- end
52
- current_indentation = line_indentation
8
+ if type == :yml
9
+ GeoLabels::Importer::YamlData.import(YAML.safe_load(data))
10
+ elsif type == :csv
11
+ GeoLabels::Importer::CsvData.new.import(data)
12
+ else
13
+ raise 'Argument not recognized'
53
14
  end
54
- root_label
55
15
  end
56
16
 
57
- def self.persist_labels_tree(tree_or_text = nil)
58
- tree = case tree_or_text
59
- when ImportLabel then tree_or_text
60
- when String then labels_text_to_tree(tree_or_text)
61
- else
62
- raise 'Must provide a tree label object or tree text'
63
- end
64
- record_lookup_dict = {}
65
- persister = proc do |items, parent|
66
- items.each do |item|
67
- record = Label.create name: item.name, parent: parent
68
- record_lookup_dict[item.name] = record
69
- persister.call item.children, record if item.children.present?
17
+ def self.get_type_and_data(subject)
18
+ case subject
19
+ when String
20
+ if subject.start_with?('/') or subject.start_with?('./') # is a path spec
21
+ return recognize_path(subject), File.read(subject)
22
+ else
23
+ if subject.start_with? "---\n" or subject.start_with?("labels: |\n")
24
+ return [:yml, subject]
25
+ elsif subject.start_with?('name,labels,')
26
+ return [:csv, subject]
27
+ else
28
+ raise 'The import string could not be recognized as either YAML or CSV'
29
+ end
70
30
  end
31
+ when File
32
+ return recognize_path(subject.path), subject.read
33
+ when Pathname
34
+ return recognize_path(subject.to_s), subject.read
35
+ when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
36
+ return recognize_path(subject.original_filename), subject.read
37
+ else
38
+ raise 'Format of import not recognized'
71
39
  end
72
- persister.call(tree.children, nil)
73
- record_lookup_dict
74
- end
75
-
76
- class LabelList < Array
77
-
78
- attr_accessor :parent
79
40
  end
80
41
 
81
- class ImportLabel
82
-
83
- attr_accessor :name, :parent
84
-
85
- def initialize(name)
86
- @name = name
87
- end
42
+ def self.recognize_path(path)
43
+ return :yml if path.end_with?('.yml')
44
+ return :csv if path.end_with?('.csv')
88
45
 
89
- def is_root?
90
- name == 'root'
91
- end
92
-
93
- def children
94
- @children ||= LabelList.new
95
- end
96
-
97
- def add_sibling(label)
98
- label.parent = parent
99
- parent.children.push label
100
- label
101
- end
102
-
103
- def add_child(label)
104
- children.push label
105
- label.parent = self
106
- label
107
- end
108
-
109
- def inspect
110
- # "#{name} => #{children.inspect}"
111
- # children.any? ? "<#{name}> => #{children.inpsect}" : "#{name}"
112
- base = name.to_s
113
- base = "#{base} => #{children.inspect}" if children.any?
114
- base
115
- end
116
-
117
- def to_h
118
- if is_root?
119
- {labels: children.map(&:to_h)}
120
- else
121
- r = {name: name}
122
- r[:children] = children.map(&:to_h) if children.any?
123
- r
124
- end
125
- end
46
+ raise 'Imported path is neither YAML or CSV file'
126
47
  end
127
48
  end
128
49
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GeoLabels
4
- VERSION = '0.3.3'
4
+ VERSION = '0.4.3'
5
5
  end
data/lib/geo_labels.rb CHANGED
@@ -2,6 +2,8 @@
2
2
  require 'geo_labels/version'
3
3
  require 'geo_labels/engine'
4
4
  require 'geo_labels/importer'
5
+ require 'geo_labels/importer/csv_data'
6
+ require 'geo_labels/importer/yaml_data'
5
7
  require 'geo_labels/exporter'
6
8
  require 'fomantic-ui-sass'
7
9
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: geo_labels
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin ter Kuile
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-09-07 00:00:00.000000000 Z
11
+ date: 2023-12-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: awesome_nested_set
@@ -128,14 +128,14 @@ dependencies:
128
128
  requirements:
129
129
  - - ">="
130
130
  - !ruby/object:Gem::Version
131
- version: '0'
131
+ version: '7.1'
132
132
  type: :runtime
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
- version: '0'
138
+ version: '7.1'
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: ransack
141
141
  requirement: !ruby/object:Gem::Requirement
@@ -228,12 +228,12 @@ files:
228
228
  - app/views/geo_labels/application/_button-edit-record.html.slim
229
229
  - app/views/geo_labels/application/_button-new-record.html.slim
230
230
  - app/views/geo_labels/application/_messages.html.slim
231
- - app/views/geo_labels/application/_navigation.html.slim
232
231
  - app/views/geo_labels/contacts/_form.html.slim
233
232
  - app/views/geo_labels/contacts/edit.html.slim
234
233
  - app/views/geo_labels/contacts/index.html.slim
235
234
  - app/views/geo_labels/contacts/new.html.slim
236
235
  - app/views/geo_labels/contacts/show.html.slim
236
+ - app/views/geo_labels/dashboard/about.html.slim
237
237
  - app/views/geo_labels/dashboard/export.html.slim
238
238
  - app/views/geo_labels/dashboard/import.html.slim
239
239
  - app/views/geo_labels/dashboard/main.html.slim
@@ -242,6 +242,9 @@ files:
242
242
  - app/views/geo_labels/labels/index.html.slim
243
243
  - app/views/geo_labels/labels/new.html.slim
244
244
  - app/views/geo_labels/labels/show.html.slim
245
+ - app/views/geo_labels/menu/_navigation.html.slim
246
+ - app/views/geo_labels/menu/_right_menu_general.html.slim
247
+ - app/views/geo_labels/users/_abilities.html.slim
245
248
  - app/views/layouts/geo_labels/application.html.slim
246
249
  - config/environment.rb
247
250
  - config/initializers/human_plural.rb
@@ -254,10 +257,13 @@ files:
254
257
  - db/migrate/20230602182522_geo_labels_contacts_complex_name_to_name.rb
255
258
  - db/migrate/20230726154822_add_state_to_geo_labels_contacts.rb
256
259
  - db/migrate/20230801145902_add_food_rating_to_geo_labels_contacts.rb
260
+ - db/migrate/20231206183544_add_url_to_geo_labels_contacts.rb
257
261
  - lib/geo_labels.rb
258
262
  - lib/geo_labels/engine.rb
259
263
  - lib/geo_labels/exporter.rb
260
264
  - lib/geo_labels/importer.rb
265
+ - lib/geo_labels/importer/csv_data.rb
266
+ - lib/geo_labels/importer/yaml_data.rb
261
267
  - lib/geo_labels/version.rb
262
268
  - lib/tasks/geo_labels_tasks.rake
263
269
  homepage: https://gitlab.com/benja-2/geo_labels
@@ -282,7 +288,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
282
288
  - !ruby/object:Gem::Version
283
289
  version: '0'
284
290
  requirements: []
285
- rubygems_version: 3.4.10
291
+ rubygems_version: 3.3.26
286
292
  signing_key:
287
293
  specification_version: 4
288
294
  summary: Rails mountable engine to label search locations
@@ -1,29 +0,0 @@
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'