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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +15 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +8 -0
- data/app/assets/config/geo_labels_manifest.js +2 -0
- data/app/assets/javascripts/geo_labels/application.coffee +29 -0
- data/app/assets/stylesheets/geo_labels/application.sass +10 -0
- data/app/controllers/concerns/geo_labels/shared_template_and_instance_methods.rb +21 -0
- data/app/controllers/geo_labels/application_controller.rb +14 -0
- data/app/controllers/geo_labels/contacts_controller.rb +56 -0
- data/app/controllers/geo_labels/dashboard_controller.rb +33 -0
- data/app/controllers/geo_labels/labels_controller.rb +56 -0
- data/app/helpers/geo_labels/application_helper.rb +197 -0
- data/app/jobs/geo_labels/application_job.rb +4 -0
- data/app/mailers/geo_labels/application_mailer.rb +6 -0
- data/app/models/geo_labels/application_record.rb +5 -0
- data/app/models/geo_labels/contact.rb +64 -0
- data/app/models/geo_labels/contact_label.rb +40 -0
- data/app/models/geo_labels/label.rb +15 -0
- data/app/views/geo_labels/application/_button-destroy-record.html.slim +1 -0
- data/app/views/geo_labels/application/_button-edit-record.html.slim +1 -0
- data/app/views/geo_labels/application/_button-new-record.html.slim +1 -0
- data/app/views/geo_labels/application/_messages.html.slim +4 -0
- data/app/views/geo_labels/application/_navigation.html.slim +29 -0
- data/app/views/geo_labels/contacts/_form.html.slim +46 -0
- data/app/views/geo_labels/contacts/edit.html.slim +3 -0
- data/app/views/geo_labels/contacts/index.html.slim +31 -0
- data/app/views/geo_labels/contacts/new.html.slim +3 -0
- data/app/views/geo_labels/contacts/show.html.slim +20 -0
- data/app/views/geo_labels/dashboard/export.html.slim +4 -0
- data/app/views/geo_labels/dashboard/import.html.slim +7 -0
- data/app/views/geo_labels/dashboard/main.html.slim +35 -0
- data/app/views/geo_labels/labels/_form.html.slim +13 -0
- data/app/views/geo_labels/labels/edit.html.slim +3 -0
- data/app/views/geo_labels/labels/index.html.slim +47 -0
- data/app/views/geo_labels/labels/new.html.slim +3 -0
- data/app/views/geo_labels/labels/show.html.slim +12 -0
- data/app/views/layouts/geo_labels/application.html.slim +22 -0
- data/config/environment.rb +2 -0
- data/config/initializers/human_plural.rb +16 -0
- data/config/locales/geo_labels.en.yml +5 -0
- data/config/locales/geo_labels.es.yml +5 -0
- data/config/routes.rb +13 -0
- data/db/migrate/20221019150722_create_geo_labels_contacts.rb +20 -0
- data/db/migrate/20221020180213_create_geo_labels_labels.rb +16 -0
- data/db/migrate/20221020195346_create_geo_labels_contact_labels.rb +12 -0
- data/lib/geo_labels/engine.rb +36 -0
- data/lib/geo_labels/exporter.rb +48 -0
- data/lib/geo_labels/importer.rb +124 -0
- data/lib/geo_labels/version.rb +3 -0
- data/lib/geo_labels.rb +8 -0
- data/lib/tasks/geo_labels_tasks.rake +4 -0
- metadata +267 -0
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,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,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,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,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'
|