klastera 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +37 -0
  5. data/app/assets/javascripts/klastera/application.js +13 -0
  6. data/app/assets/javascripts/klastera/clusters.js +2 -0
  7. data/app/assets/stylesheets/klastera/application.css +15 -0
  8. data/app/assets/stylesheets/klastera/clusters.css +4 -0
  9. data/app/assets/stylesheets/scaffold.css +56 -0
  10. data/app/controllers/klastera/application_controller.rb +33 -0
  11. data/app/controllers/klastera/clusters_controller.rb +66 -0
  12. data/app/helpers/klastera/application_helper.rb +40 -0
  13. data/app/helpers/klastera/clusters_helper.rb +4 -0
  14. data/app/models/klastera/cluster.rb +5 -0
  15. data/app/models/klastera/cluster_user.rb +5 -0
  16. data/app/models/klastera/concerns/cluster.rb +78 -0
  17. data/app/models/klastera/concerns/cluster_user.rb +20 -0
  18. data/app/models/klastera/concerns/organization.rb +46 -0
  19. data/app/models/klastera/concerns/transfer.rb +54 -0
  20. data/app/models/klastera/concerns/user.rb +62 -0
  21. data/app/models/klastera/transfer.rb +5 -0
  22. data/app/views/klastera/clusters/_filter.html.erb +0 -0
  23. data/app/views/klastera/clusters/_form.html.erb +22 -0
  24. data/app/views/klastera/clusters/_form_transfer.html.erb +34 -0
  25. data/app/views/klastera/clusters/_table.html.erb +35 -0
  26. data/app/views/klastera/clusters/create.js.erb +1 -0
  27. data/app/views/klastera/clusters/destroy.js.erb +6 -0
  28. data/app/views/klastera/clusters/edit.html.erb +10 -0
  29. data/app/views/klastera/clusters/index.html.erb +21 -0
  30. data/app/views/klastera/clusters/new.html.erb +10 -0
  31. data/app/views/klastera/clusters/transfer.html.erb +10 -0
  32. data/app/views/klastera/clusters/update.js.erb +1 -0
  33. data/app/views/layouts/klastera/_cluster_role.html.erb +26 -0
  34. data/app/views/layouts/klastera/_cluster_user_fields.html.erb +10 -0
  35. data/app/views/layouts/klastera/_nested_cluster_user.html.erb +20 -0
  36. data/app/views/layouts/klastera/_options.html.erb +38 -0
  37. data/config/locales/es.yml +78 -0
  38. data/config/routes.rb +7 -0
  39. data/db/migrate/20200324203929_create_klastera_clusters.rb +12 -0
  40. data/db/migrate/20200326111219_add_cluster_options_to_organizations.rb +20 -0
  41. data/db/migrate/20200330010551_create_klastera_cluster_users.rb +9 -0
  42. data/lib/klastera/engine.rb +5 -0
  43. data/lib/klastera/version.rb +3 -0
  44. data/lib/klastera.rb +5 -0
  45. data/lib/tasks/klastera_tasks.rake +4 -0
  46. data/test/controllers/klastera/clusters_controller_test.rb +52 -0
  47. data/test/dummy/README.rdoc +28 -0
  48. data/test/dummy/Rakefile +6 -0
  49. data/test/dummy/app/assets/javascripts/application.js +13 -0
  50. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  51. data/test/dummy/app/controllers/application_controller.rb +5 -0
  52. data/test/dummy/app/helpers/application_helper.rb +2 -0
  53. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  54. data/test/dummy/bin/bundle +3 -0
  55. data/test/dummy/bin/rails +4 -0
  56. data/test/dummy/bin/rake +4 -0
  57. data/test/dummy/bin/setup +29 -0
  58. data/test/dummy/config/application.rb +26 -0
  59. data/test/dummy/config/boot.rb +5 -0
  60. data/test/dummy/config/database.yml +1 -0
  61. data/test/dummy/config/environment.rb +5 -0
  62. data/test/dummy/config/environments/development.rb +41 -0
  63. data/test/dummy/config/environments/production.rb +79 -0
  64. data/test/dummy/config/environments/test.rb +42 -0
  65. data/test/dummy/config/initializers/assets.rb +11 -0
  66. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  67. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  68. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  69. data/test/dummy/config/initializers/inflections.rb +16 -0
  70. data/test/dummy/config/initializers/mime_types.rb +4 -0
  71. data/test/dummy/config/initializers/session_store.rb +3 -0
  72. data/test/dummy/config/initializers/to_time_preserves_timezone.rb +10 -0
  73. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  74. data/test/dummy/config/locales/en.yml +23 -0
  75. data/test/dummy/config/routes.rb +4 -0
  76. data/test/dummy/config/secrets.yml +22 -0
  77. data/test/dummy/config.ru +4 -0
  78. data/test/dummy/log/development.log +108 -0
  79. data/test/dummy/log/test.log +0 -0
  80. data/test/dummy/public/404.html +67 -0
  81. data/test/dummy/public/422.html +67 -0
  82. data/test/dummy/public/500.html +66 -0
  83. data/test/dummy/public/favicon.ico +0 -0
  84. data/test/dummy/tmp/pids/server.pid +1 -0
  85. data/test/fixtures/klastera/cluster_users.yml +9 -0
  86. data/test/fixtures/klastera/clusters.yml +11 -0
  87. data/test/integration/navigation_test.rb +8 -0
  88. data/test/klastera_test.rb +7 -0
  89. data/test/models/klastera/cluster_test.rb +9 -0
  90. data/test/models/klastera/cluster_user_test.rb +9 -0
  91. data/test/test_helper.rb +21 -0
  92. metadata +207 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6cd1ffdd78e48cf6ed10e49ca9c25aec28b85687edfab45128c19e0fe355bbe6
4
+ data.tar.gz: b973e72ea86f49cdafdb2651470a6cf9ce4285ba255db1a83ecc0efffb3bd980
5
+ SHA512:
6
+ metadata.gz: 19db0f35f3a830aaea9e0805a4235ee67ef25713f0cdde55675098d18d2d79ed2eefd9bbfc206b8feba2c4cc0df2d87901a5b2eeae97006aade6142fbcd1f79c
7
+ data.tar.gz: 8a1092f64ada8807fc6f8db2f2903a59a42a30cccb7b399dd151a2e9e274972c57d0d428a8926d2c2c155df49625e81a4b2165c0c9af15e005a802ebd3826495
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2020 Gino Barahona
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.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ = Klastera
2
+
3
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Klastera'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+
37
+ task default: :test
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,2 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -0,0 +1,56 @@
1
+ body { background-color: #fff; color: #333; }
2
+
3
+ body, p, ol, ul, td {
4
+ font-family: verdana, arial, helvetica, sans-serif;
5
+ font-size: 13px;
6
+ line-height: 18px;
7
+ }
8
+
9
+ pre {
10
+ background-color: #eee;
11
+ padding: 10px;
12
+ font-size: 11px;
13
+ }
14
+
15
+ a { color: #000; }
16
+ a:visited { color: #666; }
17
+ a:hover { color: #fff; background-color:#000; }
18
+
19
+ div.field, div.actions {
20
+ margin-bottom: 10px;
21
+ }
22
+
23
+ #notice {
24
+ color: green;
25
+ }
26
+
27
+ .field_with_errors {
28
+ padding: 2px;
29
+ background-color: red;
30
+ display: table;
31
+ }
32
+
33
+ #error_explanation {
34
+ width: 450px;
35
+ border: 2px solid red;
36
+ padding: 7px;
37
+ padding-bottom: 0;
38
+ margin-bottom: 20px;
39
+ background-color: #f0f0f0;
40
+ }
41
+
42
+ #error_explanation h2 {
43
+ text-align: left;
44
+ font-weight: bold;
45
+ padding: 5px 5px 5px 15px;
46
+ font-size: 12px;
47
+ margin: -7px;
48
+ margin-bottom: 0px;
49
+ background-color: #c00;
50
+ color: #fff;
51
+ }
52
+
53
+ #error_explanation ul li {
54
+ font-size: 12px;
55
+ list-style: square;
56
+ }
@@ -0,0 +1,33 @@
1
+ module Klastera
2
+ class ApplicationController < ::ApplicationController
3
+ protect_from_forgery with: :exception
4
+ layout :disable_in_xhr_requests
5
+ before_action :set_xhr_render_session
6
+ before_action :check_access_to_cluster_admin
7
+
8
+ def current_organization
9
+ super
10
+ end
11
+
12
+ def check_access_to_cluster_admin
13
+ unless current_user.can_admin_clusters?
14
+ redirect_to main_app.root_path, flash: { alert: t('klastera.messages.access_cluster_admin') }
15
+ end
16
+ end
17
+
18
+ if respond_to?(:skip_after_action)
19
+ skip_after_action :verify_authorized
20
+ end
21
+
22
+ private
23
+ def disable_in_xhr_requests
24
+ request.xhr? ? false : nil
25
+ end
26
+
27
+ def set_xhr_render_session
28
+ return unless request.get?
29
+ # request.xhr? return nil when this is not a XHR request, but we need a boolean
30
+ session[:xhr_render] = request.xhr?.present?
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,66 @@
1
+ require_dependency "klastera/application_controller"
2
+
3
+ module Klastera
4
+ class ClustersController < ApplicationController
5
+ before_action :set_cluster, only: %i[ edit update destroy transfer ]
6
+ before_action :set_clusters, only: %i[ index ]
7
+
8
+ def index; end
9
+
10
+ def new
11
+ @cluster = ::Cluster.new
12
+ end
13
+
14
+ def edit; end
15
+
16
+ def transfer
17
+ @transfer = Transfer.new
18
+ end
19
+
20
+ def create
21
+ @cluster = ::Cluster.new(cluster_params)
22
+ if @cluster.save
23
+ @cluster = ::Cluster.new
24
+ set_clusters
25
+ end
26
+ end
27
+
28
+ def update
29
+ if @cluster.update(cluster_params)
30
+ set_clusters
31
+ end
32
+ end
33
+
34
+ def destroy
35
+ new_cluster_id = params.require(:transfer).permit(:new_cluster_id).values.first rescue nil
36
+ @transfer = Transfer.new(
37
+ current_cluster: @cluster,
38
+ new_cluster_id: new_cluster_id
39
+ )
40
+ if @transfer.valid?
41
+ @transfer.to!(::Cluster.related_entities.map{|re|re.constantize})
42
+ @cluster.destroy
43
+ set_clusters
44
+ end
45
+ end
46
+
47
+ private
48
+ def set_cluster
49
+ @cluster = ::Cluster.find(params[:id])
50
+ end
51
+
52
+ ###
53
+ # We are assuming that the main application
54
+ # has have implemented current_organization
55
+ ###
56
+ def set_clusters
57
+ @clusters = current_organization.clusters
58
+ end
59
+
60
+ def cluster_params
61
+ parameters = params.require(:cluster).permit(:nid, :name, :color)
62
+ parameters[:organization_id]=current_organization.id
63
+ parameters
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,40 @@
1
+ module Klastera
2
+ module ApplicationHelper
3
+ def ac_errors_as_html_list(errors)
4
+ array_as_html_list(
5
+ errors.messages.inject([]) do |array,(attr,errors_array)|
6
+ array.concat(errors_array)
7
+ array
8
+ end
9
+ )
10
+ end
11
+
12
+ def array_as_html_list(array)
13
+ "<ul style='padding:0;'><li>#{array.join("</li><li>")}</li></ul>".html_safe
14
+ end
15
+
16
+ def method_missing(method, *args, &block)
17
+ if method.to_s.end_with?('_path') or method.to_s.end_with?('_url')
18
+ if main_app.respond_to?(method)
19
+ main_app.send(method, *args)
20
+ else
21
+ super
22
+ end
23
+ else
24
+ super
25
+ end
26
+ end
27
+
28
+ def respond_to?(method, include_priv=false)
29
+ if method.to_s.end_with?('_path') or method.to_s.end_with?('_url')
30
+ if main_app.respond_to?(method,include_priv)
31
+ true
32
+ else
33
+ super
34
+ end
35
+ else
36
+ super
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,4 @@
1
+ module Klastera
2
+ module ClustersHelper
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ module Klastera
2
+ class Cluster < ActiveRecord::Base
3
+ include Klastera::Concerns::Cluster
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Klastera
2
+ class ClusterUser < ActiveRecord::Base
3
+ include Klastera::Concerns::ClusterUser
4
+ end
5
+ end
@@ -0,0 +1,78 @@
1
+ module Klastera::Concerns::Cluster
2
+ extend ActiveSupport::Concern
3
+ # 'included do' causes the included code to be evaluated in the
4
+ # context where it is included (cluster.rb), rather than being
5
+ # executed in the module's context (klastera/concerns/models/cluster).
6
+ included do
7
+ self.table_name = 'clusters'
8
+
9
+ attr_reader :last_record
10
+
11
+ MODES = [ :suborganization, :required_filter, :optional_filter ].freeze
12
+
13
+ default_scope { includes(:organization).order(id: :desc) }
14
+ belongs_to :organization, class_name: Klastera.organization_class
15
+
16
+ scope :all_clusters_of, lambda { |organization,except_ids=[]|
17
+ g=where(organization: organization)
18
+ g.where.not(id: except_ids) unless except_ids.blank?
19
+ }
20
+
21
+ validates :name, presence: true
22
+ validates :nid, presence: true
23
+ validates :organization_id, presence: true
24
+
25
+ validates_uniqueness_of :nid, scope: [:nid, :organization_id]
26
+
27
+ before_destroy do |record|
28
+ self.can_transfer_and_destroy?
29
+ end
30
+
31
+ def siblings
32
+ self.class.all_clusters_of(self.organization,[self.id])
33
+ end
34
+
35
+ def is_the_last_record_in_suborganization_mode?
36
+ self.organization.suborganization_mode? && self.siblings.blank?
37
+ end
38
+
39
+ def required_transfer?
40
+ self.organization.suborganization_mode?
41
+ end
42
+
43
+ def has_related_entities_using_it?
44
+ ::Cluster.total_records_assign_to(self) > 0
45
+ end
46
+
47
+ def can_transfer_and_destroy?
48
+ can_destroy = true
49
+ if is_the_last_record_in_suborganization_mode?
50
+ errors.add(:last_record, I18n.t('klastera.messages.cant_delete_the_last_record_in_suborganization_mode'))
51
+ can_destroy = false
52
+ end
53
+ can_destroy
54
+ end
55
+ end
56
+
57
+ module ClassMethods
58
+ def related_entities(attr_needed: :class_name, macro: :has_many)
59
+ ::Cluster.reflections.map do |association_name, reflection|
60
+ reflection.send(attr_needed) if reflection.macro == macro
61
+ end.compact
62
+ end
63
+
64
+ def total_records_assign_to(cluster_instance)
65
+ related_entities(attr_needed: :name).inject(0) do |total, association_name|
66
+ entity = cluster_instance.send(association_name)
67
+ if entity.respond_to?(:cluster_id)
68
+ total+=entity.where(cluster_id: cluster_instance.id).count
69
+ end
70
+ total
71
+ end
72
+ end
73
+
74
+ def modes_as_strings
75
+ ::Cluster::MODES.map{|m|m.to_s}
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,20 @@
1
+ module Klastera::Concerns::ClusterUser
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ self.table_name = 'cluster_users'
6
+
7
+ belongs_to :user
8
+ belongs_to :cluster
9
+
10
+ validates :user_id, presence: true
11
+ validates :cluster_id, presence: true
12
+
13
+ before_destroy do |record|
14
+ end
15
+
16
+ end
17
+
18
+ module ClassMethods
19
+ end
20
+ end
@@ -0,0 +1,46 @@
1
+ module Klastera::Concerns::Organization
2
+ extend ActiveSupport::Concern
3
+ included do
4
+
5
+ has_many :clusters
6
+
7
+ validates :use_cluster_as, inclusion: { in: ::Cluster.modes_as_strings , message: I18n.t('klastera.clusters.wrong_option') }, if: proc{ use_cluster_as.present? }
8
+
9
+ # Return a symbol version of use_cluster_as value
10
+ def cluster_mode
11
+ self.use_cluster_as.to_s.to_sym
12
+ end
13
+
14
+ ##
15
+ # Return a boolean if one of three of options was set in organization
16
+ # As useless option you can retrieve the value passing false as argument.
17
+ #
18
+ ##
19
+ def is_in_cluster_mode?(return_the_mode=false)
20
+ is_active = cluster_mode.present? && ::Cluster::MODES.include?(cluster_mode)
21
+ ( return_the_mode ? ( is_active ? cluster_mode : false ) : is_active )
22
+ end
23
+
24
+ def suborganization_mode?
25
+ cluster_mode == ::Cluster::MODES.first
26
+ end
27
+
28
+ def required_filter_mode?
29
+ cluster_mode == ::Cluster::MODES.second
30
+ end
31
+
32
+ def optional_filter_mode?
33
+ cluster_mode == ::Cluster::MODES.third
34
+ end
35
+
36
+ def cluster_is_required?
37
+ self.suborganization_mode? || self.required_filter_mode?
38
+ end
39
+ end
40
+
41
+ module ClassMethods
42
+ def cluster_params
43
+ [ :use_cluster_as ]
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,54 @@
1
+
2
+
3
+ module Klastera::Concerns::Transfer
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include ActiveModel::Model
8
+ include ActiveModel::Validations::Callbacks
9
+
10
+ attr_accessor :current_cluster, :new_cluster_id, :entities_transfered
11
+
12
+ validates :current_cluster, presence: true
13
+ validates :new_cluster_id, presence: true, if: proc { self.required_transfer? }
14
+
15
+ validate do
16
+ new_cluster = ::Cluster.find(self.new_cluster_id.to_i) rescue nil
17
+ #
18
+ # In my time, to_a? didnt work
19
+ # current_cluster.class returned:
20
+ # Cluster(id: integer, name: string, nid: text, organization_id: integer, created_at: datetime, updated_at: datetime, color: string)
21
+ #
22
+ # If you see this, please fixe it. Thanks
23
+ #
24
+ if current_cluster.class.name != 'Cluster' || current_cluster.try(:is_the_last_record_in_suborganization_mode?)
25
+ errors.add(:current_cluster, I18n.t('klastera.messages.current_cluster.cant_transfer'))
26
+ elsif self.required_transfer? && new_cluster_id.present? && new_cluster.nil?
27
+ errors.add(:new_cluster_id, I18n.t('klastera.messages.new_cluster_id.nil'))
28
+ elsif current_cluster.id == new_cluster.try(:id)
29
+ errors.add(:new_cluster_id, I18n.t('klastera.messages.new_cluster_id.same'))
30
+ elsif new_cluster.present? && current_cluster.organization_id != new_cluster.organization_id
31
+ # Clusters from another organization do not exist
32
+ errors.add(:new_cluster_id, I18n.t('klastera.messages.new_cluster_id.nil'))
33
+ end
34
+ end
35
+
36
+ ##
37
+ #
38
+ #
39
+ def to!(related_entities)
40
+ self.entities_transfered = 0
41
+ related_entities.each do |entity|
42
+ if entity.is_a?(Class) && entity.respond_to?(:cluster_id)
43
+ self.entities_transfered += entity.where(
44
+ cluster_id: self.current_cluster.id
45
+ ).update_all(cluster_id: self.new_cluster_id)
46
+ end
47
+ end
48
+ end
49
+
50
+ def required_transfer?
51
+ self.current_cluster.required_transfer? && self.current_cluster.has_related_entities_using_it?
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,62 @@
1
+ module Klastera::Concerns::User
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+
6
+ ## @@current_organization = nil
7
+
8
+ CLUSTER_ROOT = 'root'.freeze
9
+ CLUSTER_ADMIN = 'admin'.freeze
10
+ CLUSTER_USER = 'user'.freeze
11
+
12
+ CLUSTER_ROLES = [ CLUSTER_ROOT, CLUSTER_ADMIN, CLUSTER_USER ].freeze
13
+
14
+ has_many :cluster_users, inverse_of: :user, class_name: ::ClusterUser.to_s
15
+ accepts_nested_attributes_for :cluster_users, reject_if: :all_blank, allow_destroy: true
16
+ validates :cluster_role, presence: true, if: proc{ self.organization.is_in_cluster_mode? }
17
+ validates :cluster_role, inclusion: { in: CLUSTER_ROLES , message: I18n.t('klastera.clusters.wrong_option') }, if: proc{ cluster_role.present? }
18
+ validate :at_least_one_role, if: proc{ self.use_cluster? && self.need_cluster_assignation? }
19
+ before_destroy do |record|
20
+ end
21
+
22
+ def use_cluster?; self.cluster_role.present?; end
23
+
24
+ def is_a_cluster_root?; self.cluster_role == CLUSTER_ROOT; end
25
+ def is_a_cluster_admin?; self.cluster_role == CLUSTER_ADMIN; end
26
+ def is_a_cluster_user?; self.cluster_role == CLUSTER_USER; end
27
+
28
+ def is_not_a_cluster_root?; ! self.is_a_cluster_root?; end
29
+ def is_not_a_cluster_admin?; ! self.is_a_cluster_admin?; end
30
+ def is_not_a_cluster_user?; ! self.is_a_cluster_user?; end
31
+
32
+ def need_cluster_assignation?
33
+ self.organization.is_in_cluster_mode? && self.organization.suborganization_mode? && self.is_a_cluster_user?
34
+ end
35
+
36
+ def can_admin_clusters?
37
+ self.organization.is_in_cluster_mode? && ( self.is_a_cluster_admin? || self.is_a_cluster_root? )
38
+ end
39
+
40
+ # def __current_organization
41
+ # @@current_organization
42
+ # end
43
+
44
+ # def __current_organization=(organization)
45
+ # @@current_organization = organization
46
+ # end
47
+
48
+ private
49
+ def at_least_one_role
50
+ if cluster_users.none?
51
+ errors.add(:base, I18n.t('klastera.user.you_need_add_at_least_one_cluster'))
52
+ end
53
+ end
54
+
55
+ end
56
+
57
+ module ClassMethods
58
+ def cluster_params
59
+ [ :cluster_role, { cluster_users_attributes: [:id, :cluster_id, :_destroy] } ]
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,5 @@
1
+ module Klastera
2
+ class Transfer
3
+ include Klastera::Concerns::Transfer
4
+ end
5
+ end
File without changes
@@ -0,0 +1,22 @@
1
+ <%= simple_form_for( @cluster, remote: true, html: { autocomplete: :off, class: 'slim-form-field' } ) do |f| %>
2
+ <%=render 'layouts/remote_form/header', o: @cluster, title: title, fa: :qrcode %>
3
+
4
+ <div class="<%=classes_for_remote_modal_body()%>">
5
+ <div class="row">
6
+ <div class="col-xs-12">
7
+ <%= f.input :name, as: :string, label: t('klastera.cluster_name') %>
8
+ </div>
9
+ <div class="col-xs-12">
10
+ <%= f.input :nid, as: :string, label: t('klastera.cluster_nid') %>
11
+ </div>
12
+ <%# <div class="col-xs-12"> %>
13
+ <%# <%= f.input :organization_id, as: :string, label: t('klastera.organization_name') %>
14
+ <%# </div> %>
15
+ <div class="col-xs-12">
16
+ <%= f.input :color, as: :string, label: t('klastera.cluster_color'), input_html: { class: 'color-picker'} %>
17
+ </div>
18
+ </div>
19
+ </div>
20
+
21
+ <%=render 'layouts/remote_form/footer', o: @cluster %>
22
+ <% end %>
@@ -0,0 +1,34 @@
1
+ <% can_transfer_and_destroy = @cluster.can_transfer_and_destroy? %>
2
+ <%= simple_form_for( @transfer, url: cluster_path(@cluster), method: :delete, remote: true, html: { autocomplete: :off, class: 'destroy-form slim-form-field' } ) do |f| %>
3
+ <%=render 'layouts/remote_form/header', o: @cluster, title: title, fa: :qrcode %>
4
+ <div class="<%=classes_for_remote_modal_body()%>">
5
+ <h4 class="text-center">Esta acción es irreversible.<br/>¿Está seguro?</h4>
6
+ <% if ::Cluster.total_records_assign_to(@cluster) > 0 %>
7
+ <br />
8
+ <div class="row">
9
+ <% if can_transfer_and_destroy %>
10
+ <div class="col-xs-12">
11
+ <div class="alert alert-<%=current_organization.suborganization_mode? ? :warning : :info %> text-center">
12
+ <i class="fa fa-2x fa-<%=current_organization.suborganization_mode? ? :warning : 'info-circle' %> float-none"></i>
13
+ <p class="margin-top-5 fa-1-2x"><%=t("klastera.clusters.transfer.#{current_organization.suborganization_mode? ? :required : :optional}")%></p>
14
+ </div>
15
+ </div>
16
+ <div class="col-xs-12">
17
+ <%= f.input :new_cluster_id, collection: @cluster.siblings.map{|s|[s.name,s.id]}, label: t('klastera.clusters.siblings') %>
18
+ </div>
19
+ <% else %>
20
+ <div class="col-xs-12">
21
+ <div class="alert alert-danger text-center no-margin">
22
+ <i class="fa fa-2x fa-times-circle float-none"></i>
23
+ <br />
24
+ <div class="fa-1-2x">
25
+ <%=ac_errors_as_html_list(@cluster.errors)%>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ <% end %>
30
+ </div>
31
+ <% end %>
32
+ </div>
33
+ <%=render 'layouts/remote_form/footer', o: @cluster, btn_type: :danger, hide_submit_button: (! can_transfer_and_destroy ) %>
34
+ <% end %>