klastera 1.3 → 1.4.0.1
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 +4 -4
- data/app/assets/stylesheets/klastera/clusters.css.scss +36 -1
- data/app/controllers/klastera/clusters_controller.rb +4 -6
- data/app/models/klastera/concerns/cluster.rb +6 -22
- data/app/models/klastera/concerns/clusterizable.rb +3 -2
- data/app/models/klastera/concerns/organization.rb +3 -11
- data/app/models/klastera/concerns/transfer.rb +13 -22
- data/app/models/klastera/concerns/user.rb +7 -4
- data/app/views/klastera/clusters/_form.html.erb +4 -4
- data/app/views/klastera/clusters/_form_transfer.html.erb +2 -2
- data/app/views/klastera/clusters/_table.html.erb +4 -12
- data/app/views/klastera/clusters/index.html.erb +1 -1
- data/app/views/layouts/klastera/_cluster_filter.html.erb +2 -2
- data/app/views/layouts/klastera/_cluster_role.html.erb +23 -2
- data/app/views/layouts/klastera/_cluster_selector.html.erb +6 -14
- data/app/views/layouts/klastera/_nested_cluster_entity.html.erb +1 -1
- data/app/views/layouts/klastera/_options.html.erb +3 -20
- data/config/locales/es.yml +21 -14
- data/lib/klastera.rb +107 -158
- data/lib/klastera/version.rb +2 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a400414db24740d7888bd0627051715a8b81c6d6f481e15b3819958b043a984f
|
4
|
+
data.tar.gz: 782575cb6ffd02a17dc9318878bc9b3b678e98760feb936038f18dd73dd99326
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c5fe5cd793facd2af1ed367485daaac3822b7f2dda768d90d5aa92e5bc2a296860585d220076de1277e4bc0b5716daa4c560f9d93cc1464ff0839fb48cfa4ba8
|
7
|
+
data.tar.gz: 9c01bd79e3df23f2e514ed9aab6b7e0c544a74bde7e2e88f98e18977d8e20833815e05ea0b85d36f9e72818ffe3edf34ee8c9ced19f5d81de7b4876f22a70e98
|
@@ -8,4 +8,39 @@
|
|
8
8
|
background: none;
|
9
9
|
}
|
10
10
|
}
|
11
|
-
}
|
11
|
+
}
|
12
|
+
|
13
|
+
#cluster-remote-table {
|
14
|
+
.cluster-id { width: 70px; }
|
15
|
+
.cluster-order { width: 70px; }
|
16
|
+
.cluster-color { width: 70px; }
|
17
|
+
.odd .colorless { color: #F9F9F9 }
|
18
|
+
.even .colorless { color: #FFF }
|
19
|
+
tr:hover .colorless { color: #444; opacity: 0.2;}
|
20
|
+
}
|
21
|
+
|
22
|
+
#cluster-remote-form {
|
23
|
+
h4.blank-modal-title {
|
24
|
+
margin: 0;
|
25
|
+
line-height: 28px;
|
26
|
+
letter-spacing: 0.05rem;
|
27
|
+
font-size: 21px;
|
28
|
+
font-weight: 400;
|
29
|
+
color: #666;
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
|
34
|
+
ul.klastera-option-help {
|
35
|
+
padding-left: 20px;
|
36
|
+
list-style-type: disc;
|
37
|
+
}
|
38
|
+
|
39
|
+
ul.klastera-option-help li {
|
40
|
+
margin-bottom: 10px;
|
41
|
+
padding-bottom: 5px;
|
42
|
+
border-bottom: 1px solid #DDD;
|
43
|
+
}
|
44
|
+
|
45
|
+
ul.klastera-option-help li b { color: #8A8A8A; font-weight: 400; }
|
46
|
+
ul.klastera-option-help li b:first-child { color: #2E5F9B; font-weight: bold; }
|
@@ -33,13 +33,11 @@ module Klastera
|
|
33
33
|
|
34
34
|
def destroy
|
35
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
|
-
)
|
36
|
+
@transfer = Transfer.new( current_cluster: @cluster, new_cluster_id: new_cluster_id )
|
40
37
|
if @transfer.valid?
|
41
|
-
@transfer.
|
42
|
-
|
38
|
+
if @transfer.apply!
|
39
|
+
@cluster.destroy
|
40
|
+
end
|
43
41
|
set_clusters
|
44
42
|
end
|
45
43
|
end
|
@@ -8,10 +8,13 @@ module Klastera::Concerns::Cluster
|
|
8
8
|
|
9
9
|
attr_reader :last_record
|
10
10
|
|
11
|
-
|
11
|
+
REQUIRED_MODE = :required_suborganization.freeze
|
12
|
+
OPTIONAL_MODE = :optional_suborganization.freeze
|
13
|
+
MODES = [ REQUIRED_MODE, OPTIONAL_MODE ].freeze
|
12
14
|
|
13
15
|
belongs_to :organization, class_name: Klastera.organization_class
|
14
16
|
has_many :cluster_users
|
17
|
+
has_many :cluster_entities, dependent: :destroy
|
15
18
|
|
16
19
|
scope :of, -> (organization,except_ids=[]) {
|
17
20
|
_scope = where(organization: organization)
|
@@ -42,7 +45,7 @@ module Klastera::Concerns::Cluster
|
|
42
45
|
end
|
43
46
|
|
44
47
|
def has_related_entities_using_it?
|
45
|
-
|
48
|
+
self.cluster_entities.size > 0
|
46
49
|
end
|
47
50
|
|
48
51
|
def can_transfer_and_destroy?
|
@@ -55,29 +58,10 @@ module Klastera::Concerns::Cluster
|
|
55
58
|
end
|
56
59
|
|
57
60
|
def display_name_nid
|
58
|
-
"#{name} (#{nid})"
|
61
|
+
"#{name} (#{nid==Klastera::UNCLUSTERED_ENTITY ? I18n.t("klastera.#{ Klastera::UNCLUSTERED_ENTITY}") : nid})"
|
59
62
|
end
|
60
63
|
end
|
61
64
|
|
62
65
|
module ClassMethods
|
63
|
-
def related_entities(attr_needed: :class_name, macro: :has_many)
|
64
|
-
::Cluster.reflections.map do |association_name, reflection|
|
65
|
-
reflection.send(attr_needed) if reflection.macro == macro
|
66
|
-
end.compact
|
67
|
-
end
|
68
|
-
|
69
|
-
def total_records_assign_to(cluster_instance)
|
70
|
-
related_entities(attr_needed: :name).inject(0) do |total, association_name|
|
71
|
-
entity = cluster_instance.send(association_name)
|
72
|
-
if entity.respond_to?(:cluster_id)
|
73
|
-
total+=entity.where(cluster_id: cluster_instance.id).count
|
74
|
-
end
|
75
|
-
total
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def modes_as_strings
|
80
|
-
::Cluster::MODES.map{|m|m.to_s}
|
81
|
-
end
|
82
66
|
end
|
83
67
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
module Klastera::Concerns::Clusterizable
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
included do
|
4
|
-
attr_accessor :lonely_cluster
|
5
4
|
belongs_to :cluster
|
6
5
|
|
7
6
|
has_many :cluster_entities, as: :entity, class_name: Klastera::ClusterEntity
|
@@ -11,6 +10,8 @@ module Klastera::Concerns::Clusterizable
|
|
11
10
|
validate :at_least_one_cluster_entity, if: proc { self.organization.required_suborganization_mode? }
|
12
11
|
validate :uniqueness_of_cluster_entity_record
|
13
12
|
|
13
|
+
scope :includes_cluster, -> { includes(cluster_entities: :cluster) }
|
14
|
+
|
14
15
|
def at_least_one_cluster_entity
|
15
16
|
if cluster_entities.length == 0 || cluster_entities.reject{|cluster_entity| cluster_entity._destroy == true}.empty?
|
16
17
|
return errors.add(:cluster_entities, I18n.t('klastera.messages.at_least_one_cluster_entity'))
|
@@ -43,7 +44,7 @@ module Klastera::Concerns::Clusterizable
|
|
43
44
|
|
44
45
|
module ClassMethods
|
45
46
|
def cluster_entity_params
|
46
|
-
[ :cluster_id,
|
47
|
+
[ :cluster_id, { cluster_entities_attributes: [:id, :cluster_id, :_destroy] } ]
|
47
48
|
end
|
48
49
|
end
|
49
50
|
end
|
@@ -4,7 +4,7 @@ module Klastera::Concerns::Organization
|
|
4
4
|
|
5
5
|
has_many :clusters
|
6
6
|
|
7
|
-
validates :use_cluster_as, inclusion: { in: ::Cluster.
|
7
|
+
validates :use_cluster_as, inclusion: { in: ::Cluster::MODES.map{|m|m.to_s}, message: I18n.t('klastera.clusters.wrong_option') }, if: proc{ use_cluster_as.present? }
|
8
8
|
|
9
9
|
# Return a symbol version of use_cluster_as value
|
10
10
|
def cluster_mode
|
@@ -22,19 +22,11 @@ module Klastera::Concerns::Organization
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def required_suborganization_mode?
|
25
|
-
cluster_mode == ::Cluster::
|
25
|
+
cluster_mode == ::Cluster::REQUIRED_MODE
|
26
26
|
end
|
27
27
|
|
28
28
|
def optional_suborganization_mode?
|
29
|
-
cluster_mode == ::Cluster::
|
30
|
-
end
|
31
|
-
|
32
|
-
def optional_filter_mode?
|
33
|
-
cluster_mode == ::Cluster::MODES.third
|
34
|
-
end
|
35
|
-
|
36
|
-
def optional_mode?
|
37
|
-
optional_suborganization_mode? || optional_filter_mode?
|
29
|
+
cluster_mode == ::Cluster::OPTIONAL_MODE
|
38
30
|
end
|
39
31
|
end
|
40
32
|
|
@@ -7,20 +7,14 @@ module Klastera::Concerns::Transfer
|
|
7
7
|
include ActiveModel::Model
|
8
8
|
include ActiveModel::Validations::Callbacks
|
9
9
|
|
10
|
-
attr_accessor :current_cluster, :new_cluster_id
|
10
|
+
attr_accessor :current_cluster, :new_cluster_id
|
11
11
|
|
12
12
|
validates :current_cluster, presence: true
|
13
13
|
validates :new_cluster_id, presence: true, if: proc { self.required_transfer? }
|
14
14
|
|
15
15
|
validate do
|
16
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
|
-
#
|
17
|
+
|
24
18
|
if current_cluster.class.name != 'Cluster' || current_cluster.try(:is_the_last_record_in_required_suborganization_mode?)
|
25
19
|
errors.add(:current_cluster, I18n.t('klastera.messages.current_cluster.cant_transfer'))
|
26
20
|
elsif self.required_transfer? && new_cluster_id.present? && new_cluster.nil?
|
@@ -33,22 +27,19 @@ module Klastera::Concerns::Transfer
|
|
33
27
|
end
|
34
28
|
end
|
35
29
|
|
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
30
|
def required_transfer?
|
51
31
|
self.current_cluster.required_transfer? && self.current_cluster.has_related_entities_using_it?
|
52
32
|
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# A returned boolean is expected. It should always be true even nothing is
|
36
|
+
# transfered, and it only will return false if creation fails.
|
37
|
+
#
|
38
|
+
def apply!
|
39
|
+
Klastera::ClusterEntity.create(current_cluster.cluster_entities.map{ |relation|
|
40
|
+
next if self.new_cluster_id.blank?
|
41
|
+
{ cluster_id: self.new_cluster_id, entity_id: relation.entity_id, entity_type: relation.entity_type }
|
42
|
+
}.compact)
|
43
|
+
end
|
53
44
|
end
|
54
45
|
end
|
@@ -21,10 +21,6 @@ module Klastera::Concerns::User
|
|
21
21
|
def is_a_cluster_admin?; self.cluster_role == CLUSTER_ADMIN; end
|
22
22
|
def is_a_cluster_user?; self.cluster_role == CLUSTER_USER; end
|
23
23
|
|
24
|
-
def is_not_a_cluster_root?; ! self.is_a_cluster_root?; end
|
25
|
-
def is_not_a_cluster_admin?; ! self.is_a_cluster_admin?; end
|
26
|
-
def is_not_a_cluster_user?; ! self.is_a_cluster_user?; end
|
27
|
-
|
28
24
|
def need_cluster_assignation?
|
29
25
|
self.try(:organization).try(:required_suborganization_mode?) && self.is_a_cluster_user?
|
30
26
|
end
|
@@ -33,6 +29,13 @@ module Klastera::Concerns::User
|
|
33
29
|
self.organization.is_in_cluster_mode? && ( self.is_a_cluster_admin? || self.is_a_cluster_root? )
|
34
30
|
end
|
35
31
|
|
32
|
+
#
|
33
|
+
# It tells if this user should see what they cluster role or organization dictates
|
34
|
+
# Adminers and users from show cluster modes skip cluster clause
|
35
|
+
def cannot_skip_cluster_clause?
|
36
|
+
! ( self.is_a_cluster_admin? || self.is_a_cluster_root? || ( self.organization.optional_suborganization_mode? && self.cluster_users.blank? ) )
|
37
|
+
end
|
38
|
+
|
36
39
|
#
|
37
40
|
# We will try to create a cluster_user record whether
|
38
41
|
# the organization is using force mode (if cluster_id isn't present, it will raise a validation error)
|
@@ -3,16 +3,16 @@
|
|
3
3
|
|
4
4
|
<div class="<%=classes_for_remote_modal_body()%>">
|
5
5
|
<div class="row">
|
6
|
-
<div class="col-xs-
|
6
|
+
<div class="col-xs-6">
|
7
7
|
<%= f.input :name, as: :string, label: t('klastera.cluster_name') %>
|
8
8
|
</div>
|
9
|
-
<div class="col-xs-
|
9
|
+
<div class="col-xs-6">
|
10
10
|
<%= f.input :nid, as: :string, label: t('klastera.cluster_nid') %>
|
11
11
|
</div>
|
12
|
-
<div class="col-xs-
|
12
|
+
<div class="col-xs-6">
|
13
13
|
<%= f.input :color, as: :string, label: t('klastera.cluster_color'), input_html: { class: 'color-picker'} %>
|
14
14
|
</div>
|
15
|
-
<div class="col-xs-
|
15
|
+
<div class="col-xs-6">
|
16
16
|
<%= f.input :order, as: :string, label: t('klastera.cluster_order') %>
|
17
17
|
</div>
|
18
18
|
</div>
|
@@ -2,8 +2,8 @@
|
|
2
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
3
|
<%=render 'layouts/remote_form/header', o: @cluster, title: title, fa: :qrcode %>
|
4
4
|
<div class="<%=classes_for_remote_modal_body()%>">
|
5
|
-
<h4 class="text-center">Esta acción es irreversible
|
6
|
-
<% if
|
5
|
+
<h4 class="blank-modal-title text-center">Esta acción es irreversible<br/>¿Está seguro?</h4>
|
6
|
+
<% if @cluster.cluster_entities.size > 0 %>
|
7
7
|
<br />
|
8
8
|
<div class="row">
|
9
9
|
<% if can_transfer_and_destroy %>
|
@@ -1,4 +1,4 @@
|
|
1
|
-
<table class="table table-striped table-hover table-condensed table-bordered datatable">
|
1
|
+
<table class="table table-striped table-hover table-condensed table-bordered datatable table">
|
2
2
|
<thead>
|
3
3
|
<tr>
|
4
4
|
<th class="text-center cluster-id"><%=t('klastera.cluster_id')%></th>
|
@@ -6,7 +6,7 @@
|
|
6
6
|
<th class="text-center cluster-color"><%=t('klastera.cluster_color')%></th>
|
7
7
|
<th class="text-center cluster-name"><%=t('klastera.cluster_name')%></th>
|
8
8
|
<th class="text-center cluster-nid"><%=t('klastera.cluster_nid')%></th>
|
9
|
-
<th class="cogs-actions
|
9
|
+
<th class="cogs-actions two-icons"><span class="fa fa-cogs"></span></th>
|
10
10
|
</tr>
|
11
11
|
</thead>
|
12
12
|
<tbody>
|
@@ -19,7 +19,7 @@
|
|
19
19
|
</td>
|
20
20
|
<td class="text-center cluster-name"><%= cluster.name %></td>
|
21
21
|
<td class="text-center cluster-nid"><%= cluster.nid %></td>
|
22
|
-
<td class="cogs-actions
|
22
|
+
<td class="cogs-actions two-icons">
|
23
23
|
<%= link_to edit_cluster_path(cluster), class:'btn btn-primary btn-xs', title: t('shared.actions.edit'), "data-toggle": "modal", "data-target": "#remote-modal-block" do %>
|
24
24
|
<span class="fa fa-pencil-square-o"></span>
|
25
25
|
<% end %>
|
@@ -30,12 +30,4 @@
|
|
30
30
|
</tr>
|
31
31
|
<% end %>
|
32
32
|
</tbody>
|
33
|
-
</table>
|
34
|
-
<style type="text/css">
|
35
|
-
.cluster-id { width: 70px; }
|
36
|
-
.cluster-order { width: 70px; }
|
37
|
-
.cluster-color { width: 70px; }
|
38
|
-
.odd .colorless { color: #F9F9F9 }
|
39
|
-
.even .colorless { color: #FFF }
|
40
|
-
tr:hover .colorless { color: #F5F5F5 }
|
41
|
-
</style>
|
33
|
+
</table>
|
@@ -1,4 +1,4 @@
|
|
1
|
-
<%= simple_form_for(@
|
1
|
+
<%= simple_form_for(@cluster_filter, url: url, remote: true) do |f| %>
|
2
2
|
|
3
3
|
<div class="inline-buttons-block top">
|
4
4
|
<%= button_tag(t('klastera.actions.filter'), class: 'btn btn-primary btn-sm float-right', data: { disable_with: "<i class='fa fa-spinner spin'></i>" }) %>
|
@@ -7,7 +7,7 @@
|
|
7
7
|
<%=yield(f) if block_given? %>
|
8
8
|
|
9
9
|
<% if cluster_organization.is_in_cluster_mode? %>
|
10
|
-
<% cluster_collection =
|
10
|
+
<% cluster_collection = cluster_list.map{|c|[c.name,(c.id||c.nid)]} %>
|
11
11
|
<% if cluster_collection.size > 1 %>
|
12
12
|
<div class="inline-label-control-block">
|
13
13
|
<%= f.input :cluster_id, collection: cluster_collection, prompt: t('klastera.clusters.all'), label: false, wrapper: false %>
|
@@ -1,8 +1,27 @@
|
|
1
|
-
<%
|
2
|
-
|
1
|
+
<%
|
2
|
+
user_cluster_roles = user_cluster_roles.nil? ? User::CLUSTER_ROLES : user_cluster_roles
|
3
|
+
other_visibility_reason = other_visibility_reason.nil? ? false : other_visibility_reason
|
4
|
+
if @user.try(:organization).present?
|
5
|
+
normal_user_help = @user.try(:organization).optional_suborganization_mode? ? :optional : :required
|
6
|
+
else
|
7
|
+
normal_user_help = :new
|
8
|
+
end
|
9
|
+
%>
|
3
10
|
<% if @user.try(:organization).try(:is_in_cluster_mode?) || other_visibility_reason %>
|
4
11
|
<div class="form-group">
|
5
12
|
<%= f.label t('klastera.cluster_role') %>
|
13
|
+
<span
|
14
|
+
class="klastera-user-cluster-rol"
|
15
|
+
data-title="<%= t('klastera.help.popover.title')%>"
|
16
|
+
data-toggle="popover"
|
17
|
+
data-content="<%=
|
18
|
+
t('klastera.help.popover.content.cluster_role',
|
19
|
+
m1: t('klastera.help.roles.description', r: t('klastera.roles.admin'), d: t('klastera.help.roles.admin')),
|
20
|
+
m2: t('klastera.help.roles.description', r: t('klastera.roles.user'), d: t("klastera.help.roles.user.#{normal_user_help}"))
|
21
|
+
)%>"
|
22
|
+
>
|
23
|
+
<%= fa_icon 'info-circle', class: 'btn-link' %>
|
24
|
+
</span>
|
6
25
|
<%= f.select :cluster_role, user_cluster_roles.map{|r|[t("klastera.roles.#{r}"),r]}, { include_blank: true }, { class: 'form-control' } %>
|
7
26
|
</div>
|
8
27
|
|
@@ -23,5 +42,7 @@
|
|
23
42
|
});
|
24
43
|
}
|
25
44
|
});
|
45
|
+
var popoverTemplate = '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
|
46
|
+
$('.klastera-user-cluster-rol').popover({ template: popoverTemplate, html: true, trigger: 'click', });
|
26
47
|
</script>
|
27
48
|
<% end %>
|
@@ -4,26 +4,18 @@
|
|
4
4
|
%>
|
5
5
|
<% if cluster_organization.is_in_cluster_mode? || other_visibility_reason %>
|
6
6
|
<%
|
7
|
-
cluster_collection = @clusters_session ||
|
7
|
+
cluster_collection = @clusters_session || cluster_list
|
8
8
|
label = t('klastera.cluster.title')
|
9
9
|
%>
|
10
10
|
<% if f.nil? %>
|
11
11
|
<%= label_tag(:cluster_id, label, class: 'control-label') %>
|
12
12
|
<%= select_tag(:cluster_id, options_from_collection_for_select(cluster_collection, 'id', 'display_name_nid'), class: 'form-control', prompt: 'Seleccione') %>
|
13
13
|
<% elsif f.is_a?(SimpleForm::FormBuilder) %>
|
14
|
-
|
15
|
-
<%= f.input :lonely_cluster, as: :hidden, html_input: { value: true } %>
|
16
|
-
<% else %>
|
17
|
-
<%= f.input :cluster_id, collection: cluster_collection.map{|c|[c.name,c.id]}, label: label %>
|
18
|
-
<% end %>
|
14
|
+
<%= f.input :cluster_id, collection: cluster_collection.map{|c|[c.name,c.id]}, label: label %>
|
19
15
|
<% else %>
|
20
|
-
|
21
|
-
<%= f.
|
22
|
-
|
23
|
-
|
24
|
-
<%= f.label label %>
|
25
|
-
<%= f.select :cluster_id, cluster_collection.map{|c|[c.name,c.id]}, { include_blank: true }, { class: 'form-control' } %>
|
26
|
-
</div>
|
27
|
-
<% end %>
|
16
|
+
<div class="form-group">
|
17
|
+
<%= f.label label %>
|
18
|
+
<%= f.select :cluster_id, cluster_collection.map{|c|[c.name,c.id]}, { include_blank: true }, { class: 'form-control' } %>
|
19
|
+
</div>
|
28
20
|
<% end %>
|
29
21
|
<% end %>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<% if cluster_organization.is_in_cluster_mode? %>
|
2
|
-
<% @clusters_session =
|
2
|
+
<% @clusters_session = cluster_list(false) %>
|
3
3
|
<div class="col-xs-12">
|
4
4
|
<% if hide_title||=false %>
|
5
5
|
<div class="form-group file required <%=f.object.class.name.parameterize%>_cluster_entity<%=' has-error' if f.object.errors.has_key?(:cluster_entities)%>">
|
@@ -5,10 +5,9 @@
|
|
5
5
|
data-title="<%= t('klastera.help.popover.title')%>"
|
6
6
|
data-toggle="popover"
|
7
7
|
data-content="<%=
|
8
|
-
t('klastera.help.popover.content',
|
8
|
+
t('klastera.help.popover.content.user_as_cluster_as',
|
9
9
|
m1:t('klastera.help.required_suborganization', t: t('klastera.required_suborganization'), e: t('klastera.clusters.entities')),
|
10
|
-
m2:t('klastera.help.optional_suborganization', t: t('klastera.optional_suborganization'), e: t('klastera.clusters.entities'))
|
11
|
-
m3:t('klastera.help.optional_filter', t: t('klastera.optional_filter'), e: t('klastera.clusters.entities'))
|
10
|
+
m2:t('klastera.help.optional_suborganization', t: t('klastera.optional_suborganization'), e: t('klastera.clusters.entities'))
|
12
11
|
)%>"
|
13
12
|
>
|
14
13
|
<%= fa_icon 'info-circle', class: 'btn-link' %>
|
@@ -19,20 +18,4 @@
|
|
19
18
|
<script type="text/javascript">
|
20
19
|
var popoverTemplate = '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
|
21
20
|
$('.klastera-cluster-option').popover({ template: popoverTemplate, html: true, trigger: 'click', });
|
22
|
-
</script>
|
23
|
-
|
24
|
-
<style type="text/css">
|
25
|
-
ul.klastera-option-help {
|
26
|
-
padding-left: 20px;
|
27
|
-
list-style-type: disc;
|
28
|
-
}
|
29
|
-
|
30
|
-
ul.klastera-option-help li {
|
31
|
-
margin-bottom: 10px;
|
32
|
-
padding-bottom: 5px;
|
33
|
-
border-bottom: 1px solid #DDD;
|
34
|
-
}
|
35
|
-
|
36
|
-
ul.klastera-option-help li b { color: #8A8A8A; font-weight: 400; }
|
37
|
-
ul.klastera-option-help li b:first-child { color: #2E5F9B; font-weight: bold; }
|
38
|
-
</style>
|
21
|
+
</script>
|
data/config/locales/es.yml
CHANGED
@@ -9,24 +9,23 @@ es:
|
|
9
9
|
you_need_add_at_least_one_cluster: Esta organización usa clusters en modo obligatorio. Asigne un cluster al usuario por favor.
|
10
10
|
assign_cluster_role: Asignar role
|
11
11
|
cluster_root_asignation:
|
12
|
-
title: Asignación
|
13
|
-
text: Este rol es sólo para usuarios
|
12
|
+
title: Asignación Super Admin
|
13
|
+
text: Este rol es sólo para usuarios super admin
|
14
14
|
roles:
|
15
|
-
root:
|
16
|
-
admin: Admin
|
17
|
-
user: Usuario
|
15
|
+
root: Super Admin
|
16
|
+
admin: Admin Cluster
|
17
|
+
user: Usuario Cluster
|
18
18
|
using_cluster_as: Usando cluster como
|
19
19
|
cluster_order: Orden
|
20
20
|
cluster_id: ID
|
21
|
-
cluster_role:
|
21
|
+
cluster_role: Rol de clusters
|
22
22
|
cluster_name: Nombre
|
23
23
|
cluster_nid: Código
|
24
24
|
cluster_color: Color
|
25
25
|
organization_name: Organización
|
26
|
-
use_cluster_as: Activar y usar cluster
|
27
|
-
required_suborganization:
|
28
|
-
optional_suborganization:
|
29
|
-
optional_filter: Filtro opcional
|
26
|
+
use_cluster_as: Activar y usar cluster en modo
|
27
|
+
required_suborganization: Obligatorio
|
28
|
+
optional_suborganization: Opcional
|
30
29
|
clusters:
|
31
30
|
all: Todos
|
32
31
|
title: Clusters
|
@@ -71,10 +70,18 @@ es:
|
|
71
70
|
help:
|
72
71
|
popover:
|
73
72
|
title: Descripción de cada opción
|
74
|
-
content:
|
75
|
-
|
76
|
-
|
77
|
-
|
73
|
+
content:
|
74
|
+
user_as_cluster_as: "<ul class='klastera-option-help'><li>%{m1}</li><li>%{m2}</li></ul>"
|
75
|
+
required_suborganization: "<b>%{t}</b>: Obliga asignar un cluster a las entidades %{e} ."
|
76
|
+
optional_suborganization: "<b>%{t}</b>: Permite asociar un cluster a las entidades %{e}."
|
77
|
+
roles:
|
78
|
+
description: "<b>%{r}</b>: %{d}."
|
79
|
+
root: Ve todos los clusters y no necesita asignación
|
80
|
+
admin: Ve todos los clusters y no necesita asignación
|
81
|
+
user:
|
82
|
+
new: Usuario al que se le puede restringir la cosas que puede ver a través de su asignación con los clusters
|
83
|
+
required: Necesita que se le asigne uno o más clusters, de lo contrario no verá las entidades clusterizadas.
|
84
|
+
optional: Si no tiene clusters asignados verá todas la entidades CON y SIN clusters. Si se le asigna un cluster, verá las entidades de ese cluster + las entidades SIN cluster.
|
78
85
|
activemodel:
|
79
86
|
attributes:
|
80
87
|
'klastera/transfer':
|
data/lib/klastera.rb
CHANGED
@@ -5,114 +5,11 @@ module Klastera
|
|
5
5
|
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
|
-
UNCLUSTERED_ENTITY = 'without_cluster'
|
9
|
-
KLSTR_HELPERS = %i[
|
10
|
-
cluster_user cluster_organization user_has_more_than_one_cluster cluster_scope cluster_of_my_own
|
11
|
-
user_clusters_string_list set_collection_before_group_by_entity
|
12
|
-
]
|
8
|
+
UNCLUSTERED_ENTITY = 'without_cluster'.freeze
|
9
|
+
KLSTR_HELPERS = %i[ cluster_user cluster_organization cluster_list cluster_scope cluster_scope_through_of user_clusters_string_list ].freeze
|
13
10
|
|
14
11
|
class << self
|
15
12
|
|
16
|
-
##
|
17
|
-
#
|
18
|
-
#
|
19
|
-
def set_cluster_entities_attributes!(entity,array_cluster_ids)
|
20
|
-
cluster_entities_attributes = {}
|
21
|
-
entity_cluster_entities = entity.try(:cluster_entities) || []
|
22
|
-
entity_clusters = entity_cluster_entities.map(&:cluster_id).sort
|
23
|
-
array_cluster_ids = array_cluster_ids.map(&:id).sort
|
24
|
-
if array_cluster_ids != entity_clusters
|
25
|
-
now = DateTime.now
|
26
|
-
entity_cluster_entities.each_with_index do |ec,index|
|
27
|
-
remove_cluster = true
|
28
|
-
if array_cluster_ids.include?(ec.cluster_id)
|
29
|
-
remove_cluster = false
|
30
|
-
array_cluster_ids.delete(ec.cluster_id)
|
31
|
-
end
|
32
|
-
cluster_entities_attributes[index] = { id: ec.id, cluster_id: ec.cluster_id, _destroy: remove_cluster }
|
33
|
-
end
|
34
|
-
array_cluster_ids.each_with_index do |cluster_id,index|
|
35
|
-
cluster_entities_attributes[now.to_i+index] = { cluster_id: cluster_id, _destroy: false }
|
36
|
-
end
|
37
|
-
end
|
38
|
-
cluster_entities_attributes
|
39
|
-
end
|
40
|
-
|
41
|
-
##
|
42
|
-
#
|
43
|
-
#
|
44
|
-
def set_collection_before_group_by_entity!(active_record_collection,entity_params,organization)
|
45
|
-
entity_params_keys = [:entity_name,:entity_attribute,:entity_id,:entity_id_attribute,:unamed]
|
46
|
-
entity_params[:entity_id] ||= nil #Ensures the entity_id attribute presence even if there is no filter
|
47
|
-
entity_params = entity_params.slice(*entity_params_keys).values
|
48
|
-
model_class = active_record_collection.model.base_class
|
49
|
-
model_relations = model_class.reflections.keys
|
50
|
-
if model_relations.include?(entity_params[0])
|
51
|
-
entity_params << "#{entity_params[0]}_id".to_sym
|
52
|
-
if entity_params[0] == 'cluster'
|
53
|
-
entity_params << :unclustered if organization.is_in_cluster_mode?
|
54
|
-
active_record_collection = Klastera.filter_clusterized!(
|
55
|
-
active_record_collection,
|
56
|
-
entity_params[2],
|
57
|
-
organization
|
58
|
-
)
|
59
|
-
end
|
60
|
-
yield( active_record_collection, entity_params_keys.zip(entity_params).to_h )
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
#
|
65
|
-
# In order to this work, the active_record_collection argument should be a cluster_scope! return.
|
66
|
-
#
|
67
|
-
def filter_clusterized!(active_record_collection,cluster_id,organization)
|
68
|
-
if organization.is_in_cluster_mode? && cluster_id.present?
|
69
|
-
# Ensures that an array of ids is used in the statement
|
70
|
-
cluster_array = cluster_id.is_a?(Array) ? cluster_id : [cluster_id]
|
71
|
-
# If we receive a UNCLUSTERED_ENTITY request, it means that we should filter by entities without clusters.
|
72
|
-
# The optional_mode?(show/use) condition add to this method an ambivalent use,
|
73
|
-
# where you can filter a unique value or multiple including NULL ones.
|
74
|
-
cluster_array << nil if cluster_array.delete(UNCLUSTERED_ENTITY) || organization.optional_mode?
|
75
|
-
active_record_collection = active_record_collection.where(cluster_entities: { cluster_id: cluster_array.uniq })
|
76
|
-
# You should use a block with clusterable data only
|
77
|
-
yield(active_record_collection) if block_given?
|
78
|
-
end
|
79
|
-
active_record_collection
|
80
|
-
end
|
81
|
-
|
82
|
-
##
|
83
|
-
# If the cluster mode is not active, this returns a scope filtered by clusters of its organization/users/cluster
|
84
|
-
#
|
85
|
-
def cluster_scope!(scope,user,organization,cluster_id=nil)
|
86
|
-
scope_klass = scope_class(scope).where(organization_id: organization)
|
87
|
-
if organization.is_in_cluster_mode?
|
88
|
-
|
89
|
-
if cluster_id.present?
|
90
|
-
# Ensures that an array of id is used in the statement
|
91
|
-
cluster_ids = cluster_id.is_a?(Array) ? cluster_id : [cluster_id]
|
92
|
-
else
|
93
|
-
clusters = cluster_of!(user,organization)
|
94
|
-
cluster_ids = clusters.map(&:id).compact
|
95
|
-
end
|
96
|
-
|
97
|
-
scope_klass = scope_klass.select("DISTINCT ON (#{scope.table_name}.id) #{scope.table_name}.id, #{scope.table_name}.*")
|
98
|
-
|
99
|
-
if organization.required_suborganization_mode?
|
100
|
-
scope_klass = scope_klass.joins(:cluster_entities).where( cluster_entities: { cluster_id: cluster_ids } )
|
101
|
-
else
|
102
|
-
or_these_cluster_ids = cluster_ids.present? ? " OR cluster_entities.cluster_id IN (#{cluster_ids.join(",")})" : ""
|
103
|
-
scope_klass = scope_klass.joins("
|
104
|
-
LEFT OUTER JOIN cluster_entities
|
105
|
-
ON entity_id = #{scope.table_name}.id
|
106
|
-
AND entity_type = '#{scope}'
|
107
|
-
").where("cluster_entities.id IS NULL#{or_these_cluster_ids}")
|
108
|
-
end
|
109
|
-
|
110
|
-
# Provisional fix to avoid unresolved SQL clashes with the main application due to DISTINCT ON clause
|
111
|
-
scope_klass = scope_class(scope).eager_load(:cluster_entities).where(id: scope_klass.map(&:id), organization_id: organization)
|
112
|
-
end
|
113
|
-
scope_klass
|
114
|
-
end
|
115
|
-
|
116
13
|
##
|
117
14
|
# TODO:
|
118
15
|
# Implement a validation to ensure that
|
@@ -125,16 +22,19 @@ module Klastera
|
|
125
22
|
end
|
126
23
|
|
127
24
|
##
|
128
|
-
# Returns a
|
129
|
-
# Use this only to get current_user/organization clusters
|
25
|
+
# Returns which clusters a user can see avoiding unnecessary queries if the cluster restraint doesn't apply
|
130
26
|
#
|
131
|
-
def
|
132
|
-
#
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
27
|
+
def cluster_list!(organization,user,include_unclustered=true)
|
28
|
+
# Only the cluster mode on and the mandatory user-cluster relation will use a join clause to get the clusters list
|
29
|
+
if organization.is_in_cluster_mode? && user.cannot_skip_cluster_clause?
|
30
|
+
active_record_collection = ::ClusterUser.clusters_of(organization,user)
|
31
|
+
else
|
32
|
+
active_record_collection = ::Cluster.where({ organization_id: organization })
|
33
|
+
end
|
34
|
+
|
35
|
+
active_record_collection = active_record_collection.order(order: :asc)
|
36
|
+
|
37
|
+
if include_unclustered && organization.optional_suborganization_mode? # For show and use modes only
|
138
38
|
active_record_collection << ::Cluster.new({nid: UNCLUSTERED_ENTITY, name: I18n.t("klastera.#{UNCLUSTERED_ENTITY}")})
|
139
39
|
end
|
140
40
|
active_record_collection
|
@@ -155,74 +55,124 @@ module Klastera
|
|
155
55
|
end
|
156
56
|
|
157
57
|
##
|
158
|
-
#
|
58
|
+
# cluster_list! needs a user and a organization. that why we perfomed this logic here
|
159
59
|
#
|
160
60
|
def user_clusters_string_list!(user,organization,cluster_entities,separator,attribute=:name)
|
161
|
-
@clusters_session ||=
|
61
|
+
@clusters_session ||= Klastera.cluster_list!(organization,user)
|
162
62
|
self.entity_clusters_string_list!(cluster_entities, separator, attribute, @clusters_session.map(&:id))
|
163
63
|
end
|
164
|
-
end
|
165
64
|
|
166
|
-
|
167
|
-
|
168
|
-
|
65
|
+
#
|
66
|
+
# We will try to avoid cluster clause except when:
|
67
|
+
# 1.- cluster mode is active
|
68
|
+
# AND
|
69
|
+
# 2a.- cluster_filter is present (someone wants to filter by cluster)
|
70
|
+
# OR
|
71
|
+
# 2b.- the current user has some limitations and must checks they cluster relation
|
72
|
+
# - User is having clusters in optional_suborganization mode
|
73
|
+
# - User IS NOT having clusters in required_suborganization mode
|
74
|
+
#
|
75
|
+
# For the other hand, with force_cluster_clause we can skip the previous logic if
|
76
|
+
# cluster_filter_id is present when the optional_suborganization mode is on. BUT!
|
77
|
+
# Be aware that if the cluster_filter is not present, the value of force_cluster_clause
|
78
|
+
# will be overridden by the returned value of cannot_skip_cluster_clause? method.
|
79
|
+
#
|
80
|
+
def cluster_scope!(scope_klass, user, organization, cluster_filter=nil, force_cluster_clause=false)
|
81
|
+
scope_klass = scope_class(scope_klass)
|
82
|
+
if organization.is_in_cluster_mode? && ( cluster_filter.present? || force_cluster_clause = user.cannot_skip_cluster_clause? ) # yes, this is an assignation
|
83
|
+
cluster_ids = []
|
84
|
+
# Set another variable as array to get the cluster id(s)
|
85
|
+
if cluster_filter.present?
|
86
|
+
cluster_ids = cluster_filter.is_a?(Array) ? cluster_filter : [cluster_filter]
|
87
|
+
elsif force_cluster_clause
|
88
|
+
cluster_ids = ::ClusterUser.clusters_of(organization,user).map(&:id).sort
|
89
|
+
end
|
90
|
+
# We will avoid the query unless cluster_ids is having values OR force_cluster_clause is set (see method description)
|
91
|
+
if cluster_ids.present? || force_cluster_clause
|
92
|
+
scope_klass = scope_klass.eager_load(:organization,cluster_entities: :cluster)
|
93
|
+
# We add the unclustered if the value of cluster_filter have the special without_cluster string or as method description says
|
94
|
+
if cluster_ids.delete(UNCLUSTERED_ENTITY) || ( force_cluster_clause && organization.optional_suborganization_mode? )
|
95
|
+
cluster_ids << nil
|
96
|
+
end
|
97
|
+
scope_klass = scope_klass.where( cluster_entities: { cluster_id: cluster_ids } )
|
98
|
+
end
|
99
|
+
end
|
100
|
+
scope_klass.where(organization_id: organization)
|
101
|
+
end
|
169
102
|
|
170
|
-
|
171
|
-
|
172
|
-
|
103
|
+
#
|
104
|
+
# Filter non-clustered entity through a clusterized one
|
105
|
+
#
|
106
|
+
def cluster_scope_through_of!(relation, cluster_entity_klass, scope_klass, user, organization, cluster_filter=nil, force_cluster_clause=false)
|
107
|
+
unclusterized_scope = scope_class(scope_klass)
|
173
108
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
109
|
+
if organization.is_in_cluster_mode? && ( force_cluster_clause || user.cannot_skip_cluster_clause? )
|
110
|
+
unclusterized_scope = unclusterized_scope.joins(relation => :cluster_entities)
|
111
|
+
end
|
112
|
+
|
113
|
+
if scope_klass.respond_to?(:organization)
|
114
|
+
unclusterized_scope = unclusterized_scope.where(organization_id: organization)
|
115
|
+
end
|
178
116
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
117
|
+
unclusterized_scope.where("#{relation}_id" => cluster_scope!(cluster_entity_klass, user, organization, cluster_filter, force_cluster_clause))
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# Returns an array with a clusterized scoped result and its grouped version
|
122
|
+
#
|
123
|
+
def group_by_cluster_scope!(scope_klass, user, organization, cluster_filter=[], scope_scopes=[])
|
124
|
+
cluster_ids = cluster_filter.is_a?(Array) ? cluster_filter : [cluster_filter]
|
125
|
+
kluster_scope = cluster_scope!(scope_klass, user, organization, cluster_ids, organization.optional_suborganization_mode? )
|
126
|
+
scope_scopes.each do |tuple_scope|
|
127
|
+
scope_name, scope_arg = tuple_scope
|
128
|
+
kluster_scope = scope_arg.present? ? kluster_scope.send(scope_name,scope_arg) : kluster_scope.send(scope_name)
|
129
|
+
end
|
130
|
+
[
|
131
|
+
kluster_scope,
|
132
|
+
kluster_scope.order(:cluster_id).group_by do |e|
|
133
|
+
e.cluster.present? ? e.cluster.name : I18n.t("klastera.#{UNCLUSTERED_ENTITY}")
|
134
|
+
end
|
135
|
+
]
|
185
136
|
end
|
186
137
|
end
|
187
138
|
|
188
|
-
|
189
|
-
|
139
|
+
##################################################################################################################################################
|
140
|
+
|
141
|
+
def cluster_user
|
142
|
+
current_user
|
190
143
|
end
|
191
144
|
|
192
|
-
def
|
193
|
-
|
194
|
-
return {} if parameters.blank?
|
195
|
-
parameters.permit(*cluster_filter_permit_params)
|
145
|
+
def cluster_organization
|
146
|
+
current_organization
|
196
147
|
end
|
197
148
|
|
198
|
-
def
|
199
|
-
|
149
|
+
def set_cluster_filter
|
150
|
+
cluster_filter_params = params.require(:cluster_filter) rescue {}
|
151
|
+
@cluster_filter = ::ClusterFilter.new(
|
152
|
+
cluster_filter_params.present? ? cluster_filter_params.permit(
|
153
|
+
[ :cluster_id ].concat( ::ClusterFilter.attributes )
|
154
|
+
) : {}
|
155
|
+
)
|
200
156
|
end
|
201
157
|
|
202
|
-
def
|
203
|
-
Klastera.
|
158
|
+
def cluster_list(include_unclustered=true)
|
159
|
+
Klastera.cluster_list!(cluster_organization, cluster_user, include_unclustered)
|
204
160
|
end
|
205
161
|
|
206
|
-
def cluster_scope(
|
207
|
-
Klastera.cluster_scope!(
|
162
|
+
def cluster_scope(scope_klass, cluster_filter=nil, force_cluster_clause=false)
|
163
|
+
Klastera.cluster_scope!(scope_klass, cluster_user, cluster_organization, cluster_filter, force_cluster_clause)
|
208
164
|
end
|
209
165
|
|
210
|
-
def
|
211
|
-
Klastera.
|
166
|
+
def cluster_scope_through_of(relation, cluster_entity_klass, scope_klass, cluster_filter=nil, force_cluster_clause=false)
|
167
|
+
Klastera.cluster_scope_through_of!(relation, cluster_entity_klass, scope_klass, cluster_user, cluster_organization, cluster_filter, force_cluster_clause)
|
212
168
|
end
|
213
169
|
|
214
|
-
def
|
215
|
-
Klastera.
|
216
|
-
cluster_user,
|
217
|
-
cluster_organization,
|
218
|
-
object_entity.try(:cluster_entities),
|
219
|
-
separator,
|
220
|
-
attribute
|
221
|
-
)
|
170
|
+
def group_by_cluster_scope(scope_klass, cluster_filter=[], scope_scopes=[])
|
171
|
+
Klastera.group_by_cluster_scope!(scope_klass, cluster_user, cluster_organization, cluster_filter, scope_scopes)
|
222
172
|
end
|
223
173
|
|
224
|
-
def
|
225
|
-
Klastera.
|
174
|
+
def user_clusters_string_list(object_entity, separator, attribute=:name)
|
175
|
+
Klastera.user_clusters_string_list!(cluster_user, cluster_organization, object_entity.try(:cluster_entities), separator, attribute)
|
226
176
|
end
|
227
177
|
|
228
178
|
included do
|
@@ -235,6 +185,5 @@ module Klastera
|
|
235
185
|
hide_action("#{helper}=")
|
236
186
|
end
|
237
187
|
end
|
238
|
-
before_action :set_the_lonely_cluster, only: %i[ create update ]
|
239
188
|
end
|
240
189
|
end
|
data/lib/klastera/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
module Klastera
|
2
|
-
VERSION = "1.
|
3
|
-
end
|
2
|
+
VERSION = "1.4.0.1"
|
3
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: klastera
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.4.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gino Barahona
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-07-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -127,7 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
127
|
- !ruby/object:Gem::Version
|
128
128
|
version: '0'
|
129
129
|
requirements: []
|
130
|
-
rubygems_version: 3.
|
130
|
+
rubygems_version: 3.0.8
|
131
131
|
signing_key:
|
132
132
|
specification_version: 4
|
133
133
|
summary: Clusterization Engine
|