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