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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5bf986ec7c563cd8c2d4a80b0c47ba754995828ebdf137095504f0cd483cd31a
4
+ data.tar.gz: b67e43e78fcfc7990d6e0517de6fe13e2911ff018544b33d5af3c24d90099578
5
+ SHA512:
6
+ metadata.gz: 44b8b346ca204d88afab5edeac9cd65e07490f49b8b394b873295df151182092573af0573ef290a2da67b31fa0be8d83cfcd78bb91da7adfe2fb1a70d4205250
7
+ data.tar.gz: cc33f0f8be30c3baf48092763cb11b26d6b6d6429a4c944512e3b9a1d1e393a220230b8801c47eb942d089327eac5a5f657856448f237474664a2734bc6a465f
data/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ CHANGELOG of geo\_labels
2
+ ============================
3
+
4
+ 2023-02-10 v0.2.0
5
+ -------------------------
6
+ * Add importer link to menu
7
+ * Add importer view
8
+ * Add import file action
9
+ * Add importer spec
10
+ * Add exporter spec
11
+ * Better labels tree on it\'s index page
12
+
13
+ 2022-10-19 Initial setup
14
+ -------------------------
15
+ The base code
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2022 Benjamin ter Kuile
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # GeoLabels
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem "geo_labels"
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install geo_labels
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
@@ -0,0 +1,2 @@
1
+ //= link geo_labels/application.css
2
+ //= link geo_labels/application.js
@@ -0,0 +1,29 @@
1
+ #= require jquery
2
+ #= require jquery_ujs
3
+ #= require semantic-ui
4
+
5
+ window.initMap = ->
6
+ if map_canvas = document.getElementById('map-canvas')
7
+ window.google_map = new google.maps.Map map_canvas,
8
+ center: map_points[0]
9
+ zoom: 9
10
+
11
+ map_points.forEach (map_point) ->
12
+ marker = new google.maps.Marker
13
+ position: map_point
14
+ map: google_map
15
+ title: map_point.title
16
+ false
17
+ $ ->
18
+ $('.ui.dropdown').dropdown() # assume fomantic that has the clearable class option, a lot better than the below custom code
19
+ $('.message .close').on 'click', ->
20
+ $(@).closest('.message').transition('fade')
21
+ $('.accordion').accordion()
22
+ $('.and-or-switch-buttons .button').on 'click', ->
23
+ #debugger
24
+ container = @parentNode
25
+ button.classList.remove('secondary') for button in container.getElementsByClassName('button')
26
+ @classList.add('secondary')
27
+ container.querySelector('input').value = @dataset.predicate
28
+ #$(@).find('button').removeClass ''
29
+ false
@@ -0,0 +1,10 @@
1
+ @import "semantic-ui"
2
+ main
3
+ min-height: calc(100% - 57px - 1rem) // 34px for footer inclusion
4
+ padding-top: 40px
5
+ > .page-title
6
+ padding-top: 12px
7
+ > .container .page-title
8
+ padding-top: 12px
9
+ #map-canvas
10
+ height: 600px
@@ -0,0 +1,21 @@
1
+ module GeoLabels
2
+ module SharedTemplateAndInstanceMethods
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ helper_method :record_class
7
+ end
8
+
9
+ private
10
+
11
+ def record_class
12
+ result = controller_path.classify.safe_constantize
13
+ raise "Cannot determine record_class from controller_path: #{controller_path}" unless result
14
+ result
15
+ end
16
+
17
+ def record_params
18
+ params.require(record_class.name.demodulize.underscore).permit!
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ module GeoLabels
2
+ class ApplicationController < ::ApplicationController
3
+ helper_method :query
4
+
5
+ def main
6
+ end
7
+
8
+ private
9
+
10
+ def query
11
+ params[:q]
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,56 @@
1
+ module GeoLabels
2
+ class ContactsController < ApplicationController
3
+ include SharedTemplateAndInstanceMethods
4
+ respond_to :html
5
+
6
+ def index
7
+ authorize! :index, record_class
8
+ @q = record_class.ransack query
9
+ @q.sorts = 'name asc' if @q.sorts.empty?
10
+ @records = @q.result.page(params[:page]).per(100)
11
+ respond_with(@records)
12
+ end
13
+
14
+ def new
15
+ @record = record_class.new
16
+ authorize! :create, @record
17
+ end
18
+
19
+ def create
20
+ @record = record_class.new record_params
21
+ authorize! :create, @record
22
+ if @record.save
23
+ redirect_to @record, status: :see_other
24
+ else
25
+ render action: 'new'
26
+ end
27
+ end
28
+
29
+ def show
30
+ @record = record_class.find params[:id]
31
+ authorize! :show, @record
32
+ end
33
+
34
+ def edit
35
+ @record = record_class.find(params[:id])
36
+ authorize! :edit, @record
37
+ end
38
+
39
+ def update
40
+ @record = record_class.find params[:id]
41
+ authorize! :edit, @record
42
+ if @record.update record_params
43
+ redirect_to @record, status: :see_other
44
+ else
45
+ render action: 'edit'
46
+ end
47
+ end
48
+
49
+ def destroy
50
+ @record = record_class.find params[:id]
51
+ authorize! :destroy, @record
52
+ @record.destroy
53
+ redirect_to record_class, status: :see_other
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,33 @@
1
+ module GeoLabels
2
+ class DashboardController < ApplicationController
3
+ def main
4
+ if params[:label_ids].present?
5
+ label_ids = params[:label_ids]
6
+ # fomantic-ui uses comma separated id values
7
+ label_ids = label_ids.to_s.split(',') unless label_ids.is_a?(Array)
8
+ @contacts = Contact.geocoded.for_label_ids(label_ids, predication: params[:predication])
9
+ end
10
+ end
11
+
12
+ def export
13
+ end
14
+
15
+ def export_file
16
+ timestamp = Time.now.utc.iso8601.first(19).gsub(/-|:/, '')
17
+ send_data GeoLabels::Exporter.export_str,
18
+ filename: "GeoLabels-export-#{timestamp}.yml",
19
+ type: :text,
20
+ disposition: :attachment
21
+ end
22
+
23
+ def import
24
+ end
25
+
26
+ def import_file
27
+ Importer.import params[:file]
28
+ redirect_to root_path, notice: 'File imported'
29
+ rescue => e
30
+ redirect_to import_path, alert: e.message
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,56 @@
1
+ module GeoLabels
2
+ class LabelsController < ApplicationController
3
+ include SharedTemplateAndInstanceMethods
4
+ respond_to :html
5
+
6
+ def index
7
+ authorize! :index, record_class
8
+ @q = record_class.ransack query
9
+ @q.sorts = 'name asc' if @q.sorts.empty?
10
+ @records = @q.result.page(params[:page]).per(100)
11
+ respond_with(@records)
12
+ end
13
+
14
+ def new
15
+ @record = record_class.new
16
+ authorize! :create, @record
17
+ end
18
+
19
+ def create
20
+ @record = record_class.new record_params
21
+ authorize! :create, @record
22
+ if @record.save
23
+ redirect_to @record, status: :see_other
24
+ else
25
+ render action: 'new'
26
+ end
27
+ end
28
+
29
+ def show
30
+ @record = record_class.find params[:id]
31
+ authorize! :show, @record
32
+ end
33
+
34
+ def edit
35
+ @record = record_class.find(params[:id])
36
+ authorize! :edit, @record
37
+ end
38
+
39
+ def update
40
+ @record = record_class.find params[:id]
41
+ authorize! :edit, @record
42
+ if @record.update record_params
43
+ redirect_to @record, status: :see_other
44
+ else
45
+ render action: 'edit'
46
+ end
47
+ end
48
+
49
+ def destroy
50
+ @record = record_class.find params[:id]
51
+ authorize! :destroy, @record
52
+ @record.destroy
53
+ redirect_to record_class, status: :see_other
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,197 @@
1
+ module GeoLabels
2
+ module ApplicationHelper
3
+ def link_to_labels_tree
4
+ #output = "<ul class='ui list'>"
5
+ output = tag.ul(class: 'ui list')[0..-6]
6
+ add_punctuation = '*'
7
+ tree_formatter = Proc.new do |items, lead_space|
8
+ items.each do |item|
9
+ output += "#{lead_space}#{tag.li link_to(item[:name], label_path(item[:id]))}\n".html_safe
10
+ if item[:children].present?
11
+ output += "#{lead_space}<ul>".html_safe
12
+ tree_formatter.call(item[:children], lead_space + ' ')
13
+ output += "#{lead_space}</ul>".html_safe
14
+ end
15
+ end
16
+ end
17
+ tree_formatter.call(GeoLabels::Exporter.labels_tree_h, '')
18
+ #output.gsub!(/^(\s*) /, "\1#{add_punctuation} ") if add_punctuation.present?
19
+ output += '</ul>'.html_safe
20
+ output.html_safe
21
+ end
22
+ # Return active or nil based on the given route spec
23
+ # %li{ class: active_class('users') # true if controller is users, false otherwise
24
+ # %li{ class: active_class('pages#about') # true if controller is pages and action is about
25
+ #NOTE: Taken from the dunlop-core gem. That is the best maintained version
26
+ def active_class(*route_specs)
27
+ options = route_specs.extract_options!
28
+ return nil if Array.wrap(options[:except]).any?{|exception| current_route_spec?(exception) }
29
+ return 'active' if route_specs.any?{|rs| current_route_spec?(rs, options) }
30
+ nil
31
+ end
32
+
33
+ # Check if the current route matches the route given as argument.
34
+ # The syntax is meant to be a bit similar to specifying routes in
35
+ # `config/routes.rb`.
36
+ # current_route_spec?('products') #=> true if controller name is products, false otherwise
37
+ # current_route_spec?('products#show') #=> true if controller_name is products AND action_name is show
38
+ # current_route_spec?('#show') #=> true if action_name is show, false otherwise
39
+ #NOTE: this helper is tested through the active_class helper
40
+ #NOTE: Taken from the dunlop-core gem. That is the best maintained version
41
+ def current_route_spec?(route_spec, options = {})
42
+ return route_spec.match([controller_path, action_name].join('#')) if route_spec.is_a?(Regexp)
43
+ controller, action = route_spec.split('#')
44
+ return action == params[:id] if controller_path == 'high_voltage/pages'
45
+ actual_controller_parts = controller_path.split('/')
46
+ if controller #and controller_path == controller
47
+ tested_controller_parts = controller.split('/')
48
+ return if tested_controller_parts.size > actual_controller_parts.size
49
+ if actual_controller_parts[0...tested_controller_parts.size] == tested_controller_parts
50
+ # controller spec matches
51
+ return true unless action
52
+ action_name == action
53
+ end
54
+ else
55
+ action_name == action
56
+ end
57
+ end
58
+
59
+ #NOTE: Taken from the dunlop-core gem. That is the best maintained version
60
+ def page_title(*args, &blk)
61
+ extra_content = capture(&blk) if blk.present?
62
+ if resource_title?(args)
63
+ @scope_model = args[1].is_a?(ActiveRecord::Base) ? args[1].class : args[1]
64
+ content = page_title_for_resource(args)
65
+ else
66
+ content = page_title_for_string(args)
67
+ end
68
+ content += extra_content if extra_content.present?
69
+ title_tag = content_tag(:h2, content, class: 'page-title ui header')
70
+ content_for :page_title, title_tag
71
+ title_tag
72
+ end
73
+
74
+ def resource_title?(args)
75
+ args.first.is_a?(Symbol) &&
76
+ (args[1].respond_to?(:model_name) || args[1].class.respond_to?(:model_name))
77
+ end
78
+
79
+ def page_title_for_string(args)
80
+ title = html_escape(args.first)
81
+ options = args.last.is_a?(Hash) ? args.last : {}
82
+ if back_options = options[:back]
83
+ url =
84
+ case back_options
85
+ when Array then polymorphic_path(back_options)
86
+ when true then :back
87
+ else back_options
88
+ end
89
+ if url
90
+ back_link = link_to content_tag(:i, nil, class: 'left arrow icon'), url, class: 'title-back-link'
91
+ title = back_link.safe_concat(title)
92
+ end
93
+ end
94
+ title
95
+ end
96
+
97
+ def page_title_for_resource(args)
98
+ options = args.extract_options!
99
+ model = args[1].respond_to?(:model_name) ? args[1] : args[1].class
100
+ if args.first == :index
101
+ title = t('action.index.label', models: model.model_name.human_plural).html_safe
102
+ else
103
+ title = t("action.#{args.first}.label", model: model.model_name.human).html_safe
104
+ end
105
+ if back_options = options[:back]
106
+ url =
107
+ case back_options
108
+ when Array then polymorphic_path(back_options)
109
+ when true then :back
110
+ else back_options
111
+ end
112
+ if url
113
+ back_link = link_to content_tag(:i, nil, class: 'left arrow icon'), url, class: 'title-back-link'
114
+ title = back_link.safe_concat(title)
115
+ end
116
+ end
117
+ title
118
+ end
119
+
120
+ def search_result_info(records)
121
+ from_item = (records.current_page - 1) * records.limit_value + 1
122
+ to_item = [from_item + records.size - 1, records.total_count].min
123
+ from_item = 0 if records.total_count.zero?
124
+ "#{from_item} - #{to_item} / #{records.total_count}"
125
+ end
126
+
127
+ def at(attribute_name, scope_model=nil)
128
+ scope_model ||= @scope_model
129
+ scope_model.human_attribute_name(attribute_name)
130
+ end
131
+
132
+ # Search helpers from dunlop
133
+ def search_row(options = {}, &blk)
134
+ classes = Array.wrap(options[:class])
135
+ classes |= ['search']
136
+ classes << 'conditions-present' if @q.conditions.present?
137
+ content = capture(&blk)
138
+ content_tag(:tr, content, class: classes )
139
+ end
140
+
141
+ # This helper returns the link for showing a record inside a table
142
+ def table_show_link(record, path = nil, options = {})
143
+ if options.has_key?(:authorized)
144
+ return unless options[:authorized]
145
+ else
146
+ return unless can? :show, record
147
+ end
148
+ link_to(content_tag(:i,nil, class: 'folder open icon'), path || record, class: 'table-link show ui mini basic primary icon button')
149
+ end
150
+
151
+ # This helper returns the link for showing a record inside a table
152
+ def table_download_link(record, path = nil, options = {})
153
+ if options.has_key?(:authorized)
154
+ return unless options[:authorized]
155
+ else
156
+ return unless can? :download, record
157
+ end
158
+ link_to(content_tag(:i,nil, class: 'download icon'), path || [:download, record], class: 'table-link download ui mini violet icon button')
159
+ end
160
+
161
+ # This helper returns the link for editing a record inside a table
162
+ def table_edit_link(record, path = nil, options = {})
163
+ if options.has_key?(:authorized)
164
+ return unless options[:authorized]
165
+ else
166
+ return unless can? :update, record
167
+ end
168
+ link_to(content_tag(:i, nil, class: 'write icon'), path || [:edit, record], class: 'table-link edit ui mini basic yellow icon button')
169
+ end
170
+
171
+ def table_destroy_link(record, path = nil, options = {})
172
+ if options.has_key?(:authorized)
173
+ return unless options[:authorized]
174
+ else
175
+ return unless can? :destroy, record
176
+ end
177
+ confirm_text = "Are you sure you want to delete #{record.class.model_name.human}"
178
+ record_name = nil
179
+ record_name = record.presentation_name if record.respond_to?(:presentation_name)
180
+ record_name ||= record.name if record.respond_to?(:name)
181
+ record_name ||= record.title if record.respond_to?(:title)
182
+ confirm_text << " #{record_name}" if record_name.present?
183
+ confirm_text << "?"
184
+ link_to(content_tag(:i, nil, class: 'trash icon'), path || record, method: :delete, data: { confirm: confirm_text }, class: 'table-link destroy ui mini negative icon button')
185
+ end
186
+
187
+ # https://coderwall.com/p/7gqmog/display-flash-messages-with-semantic-ui-in-rails
188
+ def flash_class(level)
189
+ case level.to_sym
190
+ when :success then "ui positive message"
191
+ when :error, :alert then "ui negative message"
192
+ when :notice then "ui info message"
193
+ else "ui #{level} message"
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,4 @@
1
+ module GeoLabels
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module GeoLabels
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module GeoLabels
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,64 @@
1
+ module GeoLabels
2
+ class Contact < ApplicationRecord
3
+ extend Geocoder::Model::ActiveRecord
4
+ has_many :contact_labels, dependent: :delete_all
5
+ has_many :labels, through: :contact_labels
6
+
7
+ geocoded_by :address
8
+
9
+ after_validation :geocode_if_necessary
10
+
11
+ def self.for_label_ids(label_ids, predication: 'and')
12
+ #predication = %w[and or].include?(predication) ? predication : 'and' #whitlelisting
13
+ if predication == 'or'
14
+ joins(:labels).merge(Label.descendants_for_ids(label_ids)).distinct
15
+ else
16
+ where id: ContactLabel.contact_ids_for_label_ids(label_ids)
17
+ end
18
+ end
19
+
20
+ def label_ids=(array_or_string)
21
+ super array_or_string.is_a?(Array) ? array_or_string : array_or_string.split(',')
22
+ end
23
+
24
+ def address
25
+ [street, city, state, country].map(&:presence).compact.join(', ')
26
+ end
27
+
28
+ def address_changed?
29
+ street_changed? or city_changed? or state_changed? or country_changed?
30
+ end
31
+
32
+ def full_name
33
+ [first_name, middle_name, last_name].map(&:presence).compact.join(' ')
34
+ end
35
+
36
+ ransacker :full_name do |parent|
37
+ Arel::Nodes::InfixOperation.new('||', Arel::Nodes::InfixOperation.new('||', parent.table[:first_name], parent.table[:middle_name]), parent.table[:last_name])
38
+ end
39
+
40
+ def map_title
41
+ full_name
42
+ end
43
+
44
+ def map_attributes
45
+ return {} unless geocoded?
46
+ {
47
+ lat: latitude,
48
+ lng: longitude,
49
+ title: map_title
50
+ }
51
+ end
52
+
53
+ def geocode_if_necessary
54
+ return if new_record? and latitude.present? and longitude.present?
55
+ if address_changed?
56
+ self.latitude = nil
57
+ self.longitude = nil
58
+ # Geocode addresses with at least 2 commas
59
+ geocode if address.count(',') > 1
60
+ end
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,40 @@
1
+ module GeoLabels
2
+ class ContactLabel < ApplicationRecord
3
+ belongs_to :contact
4
+ belongs_to :label
5
+
6
+ ### The strategy
7
+ # Join all possible collection label matches
8
+ # Then expec the amount of found matches to be the amount of requested labels (match all label groups).
9
+ # Sadly this stragegy fails when a contact has to labels assigned (Indonesian AND Vietnamese food), and
10
+ # the query is for all food categories (parent).
11
+ # Works for simple and concice situations. Not for multi label definitions
12
+ def self.contact_ids_for_label_ids2(label_ids)
13
+ label_ids = label_ids.to_s.split(',') unless label_ids.is_a?(Array)
14
+ scope = joins(:label).merge(Label.descendants_for_ids(label_ids)).select(:contact_id).group(:contact_id)
15
+ scope = scope.having(arel_table[:contact_id].count.eq label_ids.size).reorder('')
16
+ scope
17
+ end
18
+
19
+ ### The strategy:
20
+ # Create a subquery for each label collection possible situation
21
+ # Then join all these subqueries on the contact_id to remain only
22
+ # with the records having the contact_id in each of these subqueries
23
+ def self.contact_ids_for_label_ids(label_ids)
24
+ label_ids = label_ids.to_s.split(',') unless label_ids.is_a?(Array)
25
+
26
+ records = Label.find(label_ids)
27
+
28
+ scope = joins(%|INNER JOIN "geo_labels_labels" ON "geo_labels_labels"."id" = "geo_labels_contact_labels"."label_id"|).merge(records.shift.self_and_descendants).reorder('').select(:contact_id)
29
+ records.each do |record|
30
+ record_scope = joins(:label).merge(record.self_and_descendants).reorder('').select(:contact_id)
31
+ join_table_name = "labeltable#{record.id}"
32
+ join_table_contact_id = Arel::Nodes::SqlLiteral.new("#{join_table_name}.contact_id")
33
+ query_str = %|INNER JOIN (#{record_scope.to_sql}) #{join_table_name} ON #{arel_table[:contact_id].eq(join_table_contact_id).to_sql}|
34
+ scope = scope.joins(query_str)
35
+ #descendants_scope = descendants_scope.or(record.self_and_descendants)
36
+ end
37
+ scope
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,15 @@
1
+ module GeoLabels
2
+ class Label < ApplicationRecord
3
+ has_many :contact_labels, dependent: :delete_all
4
+ acts_as_nested_set
5
+
6
+ def self.descendants_for_ids(ids)
7
+ records = find(ids)
8
+ descendants_scope = records.shift.self_and_descendants
9
+ records.each do |record|
10
+ descendants_scope = descendants_scope.or(record.self_and_descendants)
11
+ end
12
+ descendants_scope
13
+ end
14
+ end
15
+ end
@@ -0,0 +1 @@
1
+ = link_to t('action.destroy.label', model: (record_class).model_name.human), record, class: 'ui red button', method: :delete, data: { confirm: t('general.are_you_sure') }
@@ -0,0 +1 @@
1
+ = link_to t('action.edit.label', model: (record_class).model_name.human), polymorphic_path(record, action: :edit), class: 'ui yellow button'
@@ -0,0 +1 @@
1
+ = link_to t('action.new.label', model: (record_class).model_name.human), polymorphic_path(record_class, action: :new), class: 'ui primary basic button'
@@ -0,0 +1,4 @@
1
+ - flash.each do |key, value|
2
+ .closable.visible class=flash_class(key)
3
+ i.close.icon
4
+ = value