geo_labels 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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