geo_labels 0.2.0 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/README.md +127 -6
  4. data/Rakefile +6 -5
  5. data/app/assets/javascripts/geo_labels/application.coffee +13 -2
  6. data/app/assets/javascripts/geo_labels/ratings.coffee +80 -0
  7. data/app/assets/stylesheets/geo_labels/application.sass +21 -0
  8. data/app/controllers/concerns/geo_labels/shared_template_and_instance_methods.rb +3 -0
  9. data/app/controllers/geo_labels/application_controller.rb +3 -0
  10. data/app/controllers/geo_labels/contacts_controller.rb +18 -0
  11. data/app/controllers/geo_labels/dashboard_controller.rb +14 -9
  12. data/app/controllers/geo_labels/labels_controller.rb +3 -1
  13. data/app/controllers/geo_labels/ratings_controller.rb +30 -0
  14. data/app/helpers/geo_labels/application_helper.rb +73 -44
  15. data/app/helpers/geo_labels/rating_helper.rb +15 -0
  16. data/app/jobs/geo_labels/application_job.rb +1 -0
  17. data/app/mailers/geo_labels/application_mailer.rb +4 -2
  18. data/app/models/concerns/geo_labels/ratings_support.rb +102 -0
  19. data/app/models/geo_labels/application_record.rb +22 -0
  20. data/app/models/geo_labels/contact.rb +62 -20
  21. data/app/models/geo_labels/contact_label.rb +43 -7
  22. data/app/models/geo_labels/label.rb +13 -0
  23. data/app/views/geo_labels/contacts/_form.html.slim +22 -6
  24. data/app/views/geo_labels/contacts/index.html.slim +19 -7
  25. data/app/views/geo_labels/contacts/show.html.slim +24 -2
  26. data/app/views/geo_labels/dashboard/main.html.slim +27 -5
  27. data/app/views/geo_labels/labels/index.html.slim +10 -6
  28. data/app/views/geo_labels/labels/show.html.slim +15 -1
  29. data/app/views/layouts/geo_labels/application.html.slim +2 -0
  30. data/config/environment.rb +2 -0
  31. data/config/initializers/human_plural.rb +8 -5
  32. data/config/locales/geo_labels.en.yml +8 -0
  33. data/config/locales/geo_labels.es.yml +17 -0
  34. data/config/routes.rb +12 -1
  35. data/db/migrate/20221019150722_create_geo_labels_contacts.rb +5 -3
  36. data/db/migrate/20221020180213_create_geo_labels_labels.rb +2 -0
  37. data/db/migrate/20221020195346_create_geo_labels_contact_labels.rb +4 -2
  38. data/db/migrate/20230602182522_geo_labels_contacts_complex_name_to_name.rb +9 -0
  39. data/db/migrate/20230726154822_add_state_to_geo_labels_contacts.rb +8 -0
  40. data/db/migrate/20230801145902_add_food_rating_to_geo_labels_contacts.rb +7 -0
  41. data/lib/geo_labels/engine.rb +17 -7
  42. data/lib/geo_labels/exporter.rb +24 -10
  43. data/lib/geo_labels/importer.rb +31 -27
  44. data/lib/geo_labels/version.rb +3 -1
  45. data/lib/geo_labels.rb +2 -0
  46. data/lib/tasks/geo_labels_tasks.rake +1 -0
  47. metadata +37 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5bf986ec7c563cd8c2d4a80b0c47ba754995828ebdf137095504f0cd483cd31a
4
- data.tar.gz: b67e43e78fcfc7990d6e0517de6fe13e2911ff018544b33d5af3c24d90099578
3
+ metadata.gz: 3d8a1e97c9ef4f47446cd94e6077b844c2a8bd7db58f28163330c0f6188592d1
4
+ data.tar.gz: a6378b02037f5f5b39fa10bdb6e761f487889a15b03f3c33bb40bc015bd19153
5
5
  SHA512:
6
- metadata.gz: 44b8b346ca204d88afab5edeac9cd65e07490f49b8b394b873295df151182092573af0573ef290a2da67b31fa0be8d83cfcd78bb91da7adfe2fb1a70d4205250
7
- data.tar.gz: cc33f0f8be30c3baf48092763cb11b26d6b6d6429a4c944512e3b9a1d1e393a220230b8801c47eb942d089327eac5a5f657856448f237474664a2734bc6a465f
6
+ metadata.gz: d78074f90bdace9bdb52da398d2f9a3bf057b6ef243e9eb4fdafc4397e2a62ad751bafeb605ba96a8900bdbfa7e20dbbbd953099793944550de3b5b8fa50b62c
7
+ data.tar.gz: 994ea71eecb76637ad3711b3afbf7e2c166ed6c189e2e89664f8f76a2578995574926e729adb3a0885d80e0e75eb05327e6bf4e441d506d0282011dfb6217d15
data/CHANGELOG.md CHANGED
@@ -1,6 +1,30 @@
1
1
  CHANGELOG of geo\_labels
2
2
  ============================
3
3
 
4
+ 2023-09-07 v0.3.2
5
+ -------------------------
6
+ * Add manual Google maps copy-paste string input option for complex locations
7
+
8
+ 2023-09-05 v0.3.1
9
+ -------------------------
10
+ * Better navigation connections
11
+ * Fix the contact show map feature
12
+ * Show label's connected contacts
13
+
14
+ 2023-09-04 v0.3.0
15
+ -------------------------
16
+ * Better README
17
+ * Fuzzy search for all dropdowns
18
+ * Change complex name to just name for contacts
19
+ * Make main page contacts list first, map optional
20
+ * Better label management GUI
21
+ * Spanish translation for models
22
+ * Comply with some rubocop suggestions
23
+
24
+ 2023-02-14 v0.2.1
25
+ -------------------------
26
+ * Default to ENV variable for google api key
27
+
4
28
  2023-02-10 v0.2.0
5
29
  -------------------------
6
30
  * Add importer link to menu
data/README.md CHANGED
@@ -1,10 +1,21 @@
1
1
  # GeoLabels
2
- Short description and motivation.
2
+ GeoLabels is a `rails` `engine` that allows you to create entries that will be resolved to
3
+ their geographic location so that they can be shown on a map.
3
4
 
4
- ## Usage
5
- How to use my plugin.
5
+ The idea is to also create a hierarchical structure of labels and assign the lowest level applicable
6
+ label to the contact entry.
7
+
8
+ Now interesting queries can be made based on this organization. Examples:
9
+
10
+ * Give me the good coffee places in the state of Texas
11
+ * Give me the Vietnamese food places in Miami
12
+
13
+ The geo-contacts and their labels can be managed in the engine's GUI, but also using a text format. This can be a useful,
14
+ but also dangarous feature.
6
15
 
7
16
  ## Installation
17
+
18
+ ### Add the engine to your `rails` app
8
19
  Add this line to your application's Gemfile:
9
20
 
10
21
  ```ruby
@@ -14,15 +25,125 @@ gem "geo_labels"
14
25
  And then execute:
15
26
  ```bash
16
27
  $ bundle
28
+ $ bundle exec rails db:migrate
29
+ ```
30
+
31
+ ### Mount the engine to your routes
32
+ In your `config/routes.rb` file add:
33
+ ```ruby
34
+ mount GeoLabels::Engine => '/geo-labels'
17
35
  ```
18
36
 
19
- Or install it yourself as:
37
+ ### Add the authorizations
38
+ In your `app/models/ability.rb` file add the authorizations.
39
+ This is a custom operation that you have to adjust to your needs.
40
+ To allow all users full controll to the contracts add:
41
+ ```ruby
42
+ can :manage, GeoLabels::Contact
43
+ can :manage, GeoLabels::ContactLabel
44
+ ```
45
+
46
+ If the `Ability` file does not yet exist, generate it using:
20
47
  ```bash
21
- $ gem install geo_labels
48
+ rails generate cancan:ability
49
+ ```
50
+
51
+ ### Other languages (i18n)
52
+ To use for a lets say Spanish based website add [rails-i18n](https://github.com/svenfuchs/rails-i18n) to your `Gemfile`
53
+
54
+ ```ruby
55
+ gem 'rails-i18n'
22
56
  ```
23
57
 
58
+ And configure your application in `config/application.rb` to handle the languages:
59
+
60
+ ```ruby
61
+ config.i18n.available_locales = %i[en es]
62
+ config.i18n.default_locale = :en
63
+ ```
64
+
65
+ ## Customization
66
+
67
+ ### The main page
68
+ The main page of this engine is the query page where the created structure can be queried.
69
+
70
+ ### The link home content
71
+ The default value for `config/application.rb` is:
72
+
73
+ ```ruby
74
+ config.x.geo_labels.link_home_content = -> { '<i class="arrow left icon"></i> Back' }
75
+ ```
76
+ To change for example the icon, see the options at the [fomantic-ui](https://fomantic-ui.com/elements/icon.html) site.
77
+ Note that the value is a `lambda` to allow the use of for example `I18n`.
78
+
24
79
  ## Contributing
25
- Contribution directions go here.
80
+ There are many ways to contribute. Here some example steps that should work.
81
+
82
+ ### 1. Fork the repository
83
+ Go to the original repository at https://gitlab.com/benja-2/geo\_labels and [fork](https://gitlab.com/benja-2/geo_labels/-/forks/new) the project.
84
+ Then `git clone` your code on your local computer.
85
+
86
+ If you are in the git repository directory you can tell your system to use the local code when actually the
87
+ gitlab repository is specified for faster debugging. To achieve this type:
88
+
89
+ ```bash
90
+ bundle config local.geo_labels .
91
+ ```
92
+
93
+ ### 2. Add your forked codebase to a project
94
+ To start from zero, create a new rails (> 7) project and add the `geo_labels` gem configured to use `gitlab` as a base:
95
+
96
+ ```bash
97
+ rails new my_geo_labels_project
98
+ cd my_geo_labels_project
99
+ ```
100
+
101
+ Then in the `Gemfile`
102
+ ```ruby
103
+ git_source(:gitlab) do |repo_name|
104
+ repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
105
+ "git@gitlab.com:#{repo_name}.git"
106
+ end
107
+
108
+ gem 'geo_labels', gitlab: '<your gitlab name>/geo_labels', branch: :master
109
+ ```
110
+
111
+ ## TESTING
112
+ ### Testing against other databases
113
+ Since there are some more advanced queries in this gem and was discovered that `mysql` does not support
114
+ the `IN (SELECT ...)` syntax, support for testing against multiple databases was added.
115
+
116
+ The default test (`rspec spec`) database is `sqlite`.
117
+
118
+ #### Test against a mysql database
119
+ To setup the mysql environment type (optional new window after the `docker-compose` command):
120
+ ```bash
121
+ docker-compose test_mysql up
122
+ DATABASE_URL=mysql2://root:password@127.0.0.1:33062/test rails db:create
123
+ DATABASE_URL=mysql2://root:password@127.0.0.1:33062/test rails db:migrate
124
+ ```
125
+
126
+ then run the specs against the mysql database:
127
+ ```bash
128
+ DATABASE_URL=mysql2://root:password@127.0.0.1:33062/test rspec spec
129
+ ```
130
+
131
+ #### Test against postgresql database
132
+ To setup the postgresql environment type (optional new window after the `docker-compose` command):
133
+ ```bash
134
+ docker-compose test_postgresql up
135
+ DATABASE_URL=postgres://pguser:pgpassword@127.0.0.1:54321/test rails db:create
136
+ DATABASE_URL=postgres://pguser:pgpassword@127.0.0.1:54321/test rails db:migrate
137
+ ```
138
+
139
+ then run the specs against the mysql database:
140
+ ```bash
141
+ DATABASE_URL=postgres://pguser:pgpassword@127.0.0.1:54321/test rspec spec
142
+ ```
143
+
144
+ ## CHANGELOG
145
+ The CHANGELOG can be found using
146
+ [CHANGELOG.md](/CHANGELOG.md)
26
147
 
27
148
  ## License
28
149
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -1,8 +1,9 @@
1
- require "bundler/setup"
1
+ # frozen_string_literal: true
2
+ require 'bundler/setup'
2
3
 
3
- APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
4
- load "rails/tasks/engine.rake"
4
+ APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
5
+ load 'rails/tasks/engine.rake'
5
6
 
6
- load "rails/tasks/statistics.rake"
7
+ load 'rails/tasks/statistics.rake'
7
8
 
8
- require "bundler/gem_tasks"
9
+ require 'bundler/gem_tasks'
@@ -1,6 +1,7 @@
1
1
  #= require jquery
2
2
  #= require jquery_ujs
3
3
  #= require semantic-ui
4
+ #= require ./ratings
4
5
 
5
6
  window.initMap = ->
6
7
  if map_canvas = document.getElementById('map-canvas')
@@ -15,10 +16,20 @@ window.initMap = ->
15
16
  title: map_point.title
16
17
  false
17
18
  $ ->
18
- $('.ui.dropdown').dropdown() # assume fomantic that has the clearable class option, a lot better than the below custom code
19
+ $('.ui.dropdown').dropdown
20
+ fullTextSearch: true
21
+
22
+ $('.tabular.menu .item').tab
23
+ onFirstLoad: (name) ->
24
+ initMap() if name is 'map'
25
+ true
26
+ $('.accordion').accordion()
27
+
28
+ $('[data-content],[data-html]').popup()
29
+
19
30
  $('.message .close').on 'click', ->
20
31
  $(@).closest('.message').transition('fade')
21
- $('.accordion').accordion()
32
+
22
33
  $('.and-or-switch-buttons .button').on 'click', ->
23
34
  #debugger
24
35
  container = @parentNode
@@ -0,0 +1,80 @@
1
+ # Supply a global initialization access point to load for custom actions
2
+ window.divmod = (x, y) -> [Math.floor(x / y), x % y]
3
+ window.setupRatings = ->
4
+ # For now jQuery based, but setup for easy future change to non jQuery systems
5
+ setClassForRatingDivmod = (star, whole_stars, half_star) ->
6
+ star_number = Number(star.dataset.starNumber)
7
+ if star_number + 1 > whole_stars + half_star
8
+ star.classList.add 'outline'
9
+ else
10
+ star.classList.remove 'outline'
11
+ star.classList.toggle 'half', star_number is whole_stars and half_star
12
+ #star.classList.toggle 'half', half_star
13
+ #
14
+ getStarEventRating = (star, event) ->
15
+ rect = event.target.getBoundingClientRect()
16
+ leftHalf = event.clientX < rect.x + rect.width / 2
17
+ hoverRating = event.target.dataset.starNumber * 2
18
+ hoverRating += if leftHalf then 1 else 2
19
+ hoverRating
20
+
21
+ mousemove = (event) ->
22
+ [whole_stars, half_star] = divmod(getStarEventRating(event.target, event), 2)
23
+ Array.from(event.target.parentElement.childNodes).forEach (star) ->
24
+ setClassForRatingDivmod star, whole_stars, half_star
25
+ star.classList.remove 'yellow'
26
+ star.classList.add 'orange'
27
+
28
+ mouseleave = (event) ->
29
+ rating = Number(event.target.parentElement.dataset.rating)
30
+ [whole_stars, half_star] = divmod(rating, 2)
31
+ Array.from(event.target.parentElement.childNodes).forEach (star) ->
32
+ setClassForRatingDivmod star, whole_stars, half_star
33
+ star.classList.remove 'orange'
34
+ star.classList.remove 'green'
35
+ star.classList.add 'yellow'
36
+
37
+ mouseclick = (event) ->
38
+ clickValue = getStarEventRating(event.target, event)
39
+ [whole_stars, half_star] = divmod(clickValue, 2)
40
+ Array.from(event.target.parentElement.childNodes).forEach (star) ->
41
+ star.classList.remove 'yellow'
42
+ star.classList.remove 'orange'
43
+ cdata = event.target.parentNode.dataset
44
+ post_data =
45
+ record: cdata.record
46
+ record_id: cdata.recordId
47
+ topic: cdata.topic
48
+ value: clickValue
49
+ jQuery.post geo_labels.paths.set_rating, post_data, (response, status, jqXHR) ->
50
+ event.target.parentNode.dataset.rating = clickValue
51
+ Array.from(event.target.parentElement.childNodes).forEach (star) ->
52
+ star.classList.add 'green'
53
+ setTimeout ->
54
+ mouseleave(event)
55
+ , 1000
56
+ .fail (jqXHR) ->
57
+ # jqXHR.status is probably 422 or 403
58
+ alert "The rating cannot be updated"
59
+ mouseleave(event)
60
+
61
+
62
+ jQuery(document).ready ->
63
+ containers = document.getElementsByClassName 'rating-container'
64
+ for container in containers
65
+ container.innerHTML = ''
66
+ data = container.dataset
67
+ [whole_stars, half_star] = divmod(Number(data.rating), 2)
68
+ for star_index in [0...5]
69
+ icon = document.createElement 'i'
70
+ icon.className = 'yellow star icon'
71
+ #icon.classList.add 'half' if star_index is whole_stars and half_star
72
+ #if star_index > whole_stars
73
+ # icon.classList.add 'outline'
74
+ icon.dataset.starNumber = star_index
75
+ setClassForRatingDivmod icon, whole_stars, half_star
76
+ icon.onmousemove = (event) -> mousemove(event)
77
+ icon.onmouseleave = (event) -> mouseleave(event)
78
+ icon.addEventListener 'click', (event) -> mouseclick(event)
79
+ container.appendChild icon
80
+ false
@@ -6,5 +6,26 @@ main
6
6
  padding-top: 12px
7
7
  > .container .page-title
8
8
  padding-top: 12px
9
+ .page-title
10
+ .primary.button
11
+ float: right
12
+ .rating-container i.icon.hidden
13
+ display: none
14
+
15
+ .labels-tree
16
+ li
17
+ &:hover
18
+ background-color: #ddd
19
+ .icon
20
+ display: inline-block
21
+ .icon
22
+ float: right
23
+ margin-left: 10px
24
+ display: none
25
+ .table-link.edit
26
+ font-size: 0.5rem !important
27
+ @media (hover: none)
28
+ .labels-tree li .icon
29
+ display: inline-block
9
30
  #map-canvas
10
31
  height: 600px
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GeoLabels
2
4
  module SharedTemplateAndInstanceMethods
3
5
  extend ActiveSupport::Concern
@@ -11,6 +13,7 @@ module GeoLabels
11
13
  def record_class
12
14
  result = controller_path.classify.safe_constantize
13
15
  raise "Cannot determine record_class from controller_path: #{controller_path}" unless result
16
+
14
17
  result
15
18
  end
16
19
 
@@ -1,8 +1,11 @@
1
+ # frozen_string_literal: true
1
2
  module GeoLabels
2
3
  class ApplicationController < ::ApplicationController
4
+
3
5
  helper_method :query
4
6
 
5
7
  def main
8
+ # root landing action
6
9
  end
7
10
 
8
11
  private
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
1
2
  module GeoLabels
2
3
  class ContactsController < ApplicationController
4
+
3
5
  include SharedTemplateAndInstanceMethods
4
6
  respond_to :html
5
7
 
@@ -52,5 +54,21 @@ module GeoLabels
52
54
  @record.destroy
53
55
  redirect_to record_class, status: :see_other
54
56
  end
57
+
58
+ # PUT /contacts/:id/reject
59
+ def reject
60
+ # redirect_to record_class, alert: "Only contacts of state recomm"
61
+ @record = record_class.find params[:id]
62
+ @record.update state: 'rejected'
63
+ redirect_to @record, notice: 'Rejected'
64
+ end
65
+
66
+ # PUT /contacts/:id/reject
67
+ def approve
68
+ # redirect_to record_class, alert: "Only contacts of state recomm"
69
+ @record = record_class.find params[:id]
70
+ @record.update state: 'approved'
71
+ redirect_to @record, notice: 'Approved'
72
+ end
55
73
  end
56
74
  end
@@ -1,26 +1,31 @@
1
+ # frozen_string_literal: true
1
2
  module GeoLabels
2
3
  class DashboardController < ApplicationController
4
+
3
5
  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
6
+ return unless params[:label_ids].present?
7
+
8
+ label_ids = params[:label_ids]
9
+ # fomantic-ui uses comma separated id values
10
+ label_ids = label_ids.to_s.split(',') unless label_ids.is_a?(Array)
11
+ states_array = (params[:states].presence || 'approved').split(',')
12
+ @contacts = Contact.geocoded.for_label_ids(label_ids, predication: params[:predication], states: states_array)
10
13
  end
11
14
 
12
15
  def export
16
+ # SHOW action for the view to call the export_file action
13
17
  end
14
18
 
15
19
  def export_file
16
20
  timestamp = Time.now.utc.iso8601.first(19).gsub(/-|:/, '')
17
21
  send_data GeoLabels::Exporter.export_str,
18
- filename: "GeoLabels-export-#{timestamp}.yml",
19
- type: :text,
20
- disposition: :attachment
22
+ filename: "GeoLabels-export-#{timestamp}.yml",
23
+ type: :text,
24
+ disposition: :attachment
21
25
  end
22
26
 
23
27
  def import
28
+ # SHOW action for the view to call the import_file action
24
29
  end
25
30
 
26
31
  def import_file
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
1
2
  module GeoLabels
2
3
  class LabelsController < ApplicationController
4
+
3
5
  include SharedTemplateAndInstanceMethods
4
6
  respond_to :html
5
7
 
@@ -12,7 +14,7 @@ module GeoLabels
12
14
  end
13
15
 
14
16
  def new
15
- @record = record_class.new
17
+ @record = record_class.new(parent_id: params[:parent_id])
16
18
  authorize! :create, @record
17
19
  end
18
20
 
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Separate ratings controller for easyer future extraction of functionality
4
+ # posibly gem extraction if used sufficiently
5
+ module GeoLabels
6
+ class RatingsController < ApplicationController
7
+
8
+ # Still has no application
9
+ def get
10
+ render plain: 'TODO app/controllers/geo_labels/ratings_controller.rb'
11
+ end
12
+
13
+ # PUT /geo-labels/ratings/set?record=GeoLabels::Contact&record_id=3&topic=food&value=7
14
+ def set
15
+ model = params[:record].safe_constantize
16
+ return render plain: '', status: :unprocessable_entity unless model&.include?(GeoLabels::RatingsSupport) # 422
17
+ return render plain: '', status: :unprocessable_entity unless model.rating_topics.include? params[:topic].to_sym
18
+
19
+ record = model.find(params[:record_id])
20
+ #TODO: add more sophisticated safeguard
21
+ record.rating[params[:topic]] = params[:value]
22
+
23
+ if record.save
24
+ render plain: ''
25
+ else
26
+ render plain: '', status: :unprocessable_entity
27
+ end
28
+ end
29
+ end
30
+ end