klastera 1.2.4 → 1.3.2
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 +3 -20
- data/app/models/klastera/concerns/cluster_filter.rb +1 -4
- data/app/models/klastera/concerns/cluster_user.rb +20 -36
- data/app/models/klastera/concerns/clusterizable.rb +0 -4
- data/app/models/klastera/concerns/organization.rb +1 -1
- data/app/models/klastera/concerns/transfer.rb +13 -22
- 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_entity_fields.html.erb +1 -1
- data/app/views/layouts/klastera/_cluster_filter.html.erb +1 -1
- data/app/views/layouts/klastera/_cluster_selector.html.erb +1 -1
- data/app/views/layouts/klastera/_nested_cluster_entity.html.erb +2 -2
- data/app/views/layouts/klastera/_options.html.erb +1 -17
- data/lib/klastera.rb +117 -102
- data/lib/klastera/version.rb +2 -2
- data/lib/tasks/klastera_tasks.rake +5 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4fe09e51ca6d275a070d368cd49d916ff2ad2b02f2c06e2afcec708bda66ef88
|
4
|
+
data.tar.gz: 03d542c381f4eaefa9079d8ca47c33c787658faa0cce87127225f52a069bbd46
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 88d80a8309499a0b61d96a44d3a297fcb87fe9352d46021a7e415a66bb1e2e4776400888396478deacb9108c861bfe69755477e23f9d43ed4273b2ce98206bf5
|
7
|
+
data.tar.gz: 836a8472f3a71fda307b77c4c7c69e038bb9c01f2d364b6fb98817cc096960dca2a4ee7d8eb8735b83fbb1a67bd31e4f168f45871fa79362d6e600ac10aed307
|
@@ -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
|
@@ -11,6 +11,8 @@ module Klastera::Concerns::Cluster
|
|
11
11
|
MODES = [ :required_suborganization, :optional_suborganization, :optional_filter ].freeze
|
12
12
|
|
13
13
|
belongs_to :organization, class_name: Klastera.organization_class
|
14
|
+
has_many :cluster_users
|
15
|
+
has_many :cluster_entities, dependent: :destroy
|
14
16
|
|
15
17
|
scope :of, -> (organization,except_ids=[]) {
|
16
18
|
_scope = where(organization: organization)
|
@@ -41,7 +43,7 @@ module Klastera::Concerns::Cluster
|
|
41
43
|
end
|
42
44
|
|
43
45
|
def has_related_entities_using_it?
|
44
|
-
|
46
|
+
self.cluster_entities.size > 0
|
45
47
|
end
|
46
48
|
|
47
49
|
def can_transfer_and_destroy?
|
@@ -59,24 +61,5 @@ module Klastera::Concerns::Cluster
|
|
59
61
|
end
|
60
62
|
|
61
63
|
module ClassMethods
|
62
|
-
def related_entities(attr_needed: :class_name, macro: :has_many)
|
63
|
-
::Cluster.reflections.map do |association_name, reflection|
|
64
|
-
reflection.send(attr_needed) if reflection.macro == macro
|
65
|
-
end.compact
|
66
|
-
end
|
67
|
-
|
68
|
-
def total_records_assign_to(cluster_instance)
|
69
|
-
related_entities(attr_needed: :name).inject(0) do |total, association_name|
|
70
|
-
entity = cluster_instance.send(association_name)
|
71
|
-
if entity.respond_to?(:cluster_id)
|
72
|
-
total+=entity.where(cluster_id: cluster_instance.id).count
|
73
|
-
end
|
74
|
-
total
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def modes_as_strings
|
79
|
-
::Cluster::MODES.map{|m|m.to_s}
|
80
|
-
end
|
81
64
|
end
|
82
65
|
end
|
@@ -4,11 +4,8 @@ module Klastera::Concerns::ClusterFilter
|
|
4
4
|
included do
|
5
5
|
include ActiveModel::Model
|
6
6
|
include ActiveModel::Validations::Callbacks
|
7
|
-
attr_accessor :cluster_id
|
8
7
|
|
9
|
-
|
10
|
-
self.cluster_id == 'without_cluster' ? nil : self.cluster_id
|
11
|
-
end
|
8
|
+
attr_accessor :cluster_id
|
12
9
|
end
|
13
10
|
|
14
11
|
module ClassMethods
|
@@ -3,55 +3,39 @@ module Klastera::Concerns::ClusterUser
|
|
3
3
|
|
4
4
|
included do
|
5
5
|
self.table_name = 'cluster_users'
|
6
|
-
|
7
6
|
belongs_to :user
|
8
7
|
belongs_to :cluster
|
9
|
-
|
10
8
|
validates :cluster_id, presence: true
|
11
|
-
|
12
|
-
scope :of, -> (user,organization) {
|
13
|
-
Rails.logger.warn("DON'T USE THIS SCOPE DIRECTLY: Use ::ClusterUser.clusters_from class method instead!")
|
14
|
-
includes(:cluster).where(user_id: user, "clusters.organization_id": organization)
|
15
|
-
}
|
16
9
|
end
|
17
10
|
|
18
11
|
module ClassMethods
|
12
|
+
##
|
13
|
+
# Return a Cluster::ActiveRecord_Relation of organization (and) user
|
14
|
+
#
|
15
|
+
def clusters_of(organization,and_user=nil)
|
16
|
+
and_user_id = and_user.present? ? { users: { id: and_user } } : {}
|
17
|
+
::Cluster.eager_load(cluster_users: :user).where({ organization_id: organization }.merge(and_user_id) )
|
18
|
+
end
|
19
19
|
|
20
20
|
##
|
21
|
-
|
22
|
-
|
23
|
-
def
|
24
|
-
|
25
|
-
organization_cluster_ids = ::Cluster.of(organization).map(&:id)
|
26
|
-
::ClusterUser.includes(:user).where(cluster_id: organization_cluster_ids).each do |cluster_user|
|
27
|
-
user_id = cluster_user.user_id || :uncluster
|
28
|
-
users_hash[user_id] ||= []
|
29
|
-
users_hash[user_id] << cluster_user.cluster_id
|
30
|
-
end
|
31
|
-
users_hash
|
21
|
+
# Return a User::ActiveRecord_Relation of organization (and) user
|
22
|
+
#
|
23
|
+
def users_of(organization)
|
24
|
+
::User.eager_load(:cluster_users).where(users: { organization_id: organization } )
|
32
25
|
end
|
33
26
|
|
34
27
|
##
|
35
|
-
#
|
28
|
+
# Return a hash of users and its clusters
|
36
29
|
#
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
# TODO: resolve it without quering twice
|
45
|
-
clusters_id = ::ClusterUser.of(user,organization).map(&:cluster_id)
|
46
|
-
clusters = ::Cluster.where(id: clusters_id)
|
47
|
-
# Add a empty cluster instance to handle models without a cluster assignation. Only for use and show modes
|
48
|
-
if organization.optional_mode?
|
49
|
-
clusters << ::Cluster.new({nid: :without_cluster, name: I18n.t('klastera.without_cluster')})
|
50
|
-
end
|
51
|
-
else
|
52
|
-
clusters = ::Cluster.of(organization)
|
30
|
+
def users_hash_of(organization)
|
31
|
+
users = {}
|
32
|
+
rows = self.users_of(organization).pluck("users.id AS user_id","cluster_users.cluster_id").uniq
|
33
|
+
rows.each do |row|
|
34
|
+
user_id = row.first
|
35
|
+
users[user_id] ||= []
|
36
|
+
users[user_id] << row.last
|
53
37
|
end
|
54
|
-
|
38
|
+
users
|
55
39
|
end
|
56
40
|
end
|
57
41
|
end
|
@@ -11,10 +11,6 @@ module Klastera::Concerns::Clusterizable
|
|
11
11
|
validate :at_least_one_cluster_entity, if: proc { self.organization.required_suborganization_mode? }
|
12
12
|
validate :uniqueness_of_cluster_entity_record
|
13
13
|
|
14
|
-
scope :related_clusters, ->() {
|
15
|
-
includes(:cluster_entities).where("cluster_entities.entity_id = #{self.table_name}.id")
|
16
|
-
}
|
17
|
-
|
18
14
|
def at_least_one_cluster_entity
|
19
15
|
if cluster_entities.length == 0 || cluster_entities.reject{|cluster_entity| cluster_entity._destroy == true}.empty?
|
20
16
|
return errors.add(:cluster_entities, I18n.t('klastera.messages.at_least_one_cluster_entity'))
|
@@ -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
|
@@ -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
|
@@ -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,6 +1,6 @@
|
|
1
1
|
<tr class="nested-fields">
|
2
2
|
<td class="field">
|
3
|
-
<%= f.select :cluster_id, @
|
3
|
+
<%= f.select :cluster_id, @clusters_session.map{|c|[c.name,c.id]}, { include_blank: true }, { class: 'form-control' } %>
|
4
4
|
</td>
|
5
5
|
<td class="action vertical-align-middle text-center" width="44">
|
6
6
|
<%= link_to_remove_association f, class: 'btn btn-danger btn-xs text-danger' do %>
|
@@ -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_of_my_own.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 %>
|
@@ -4,7 +4,7 @@
|
|
4
4
|
%>
|
5
5
|
<% if cluster_organization.is_in_cluster_mode? || other_visibility_reason %>
|
6
6
|
<%
|
7
|
-
cluster_collection = @
|
7
|
+
cluster_collection = @clusters_session || cluster_of_my_own
|
8
8
|
label = t('klastera.cluster.title')
|
9
9
|
%>
|
10
10
|
<% if f.nil? %>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<% if cluster_organization.is_in_cluster_mode? %>
|
2
|
-
<% @
|
2
|
+
<% @clusters_session = cluster_of_my_own %>
|
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)%>">
|
@@ -27,7 +27,7 @@
|
|
27
27
|
<table id="cluster-entities" class="table table-striped">
|
28
28
|
<tbody class="cluster-entity-rows">
|
29
29
|
<%= f.fields_for :cluster_entities do |cluster_entity|%>
|
30
|
-
<% next unless @
|
30
|
+
<% next unless @clusters_session.map(&:id).include?(cluster_entity.try(:object).try(:cluster_id)) %>
|
31
31
|
<%= render 'layouts/klastera/cluster_entity_fields', f: cluster_entity %>
|
32
32
|
<% end %>
|
33
33
|
</body>
|
@@ -19,20 +19,4 @@
|
|
19
19
|
<script type="text/javascript">
|
20
20
|
var popoverTemplate = '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
|
21
21
|
$('.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>
|
22
|
+
</script>
|
data/lib/klastera.rb
CHANGED
@@ -5,14 +5,17 @@ module Klastera
|
|
5
5
|
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
|
+
UNCLUSTERED_ENTITY = 'without_cluster'
|
8
9
|
KLSTR_HELPERS = %i[
|
9
|
-
cluster_user cluster_organization user_has_more_than_one_cluster
|
10
|
-
|
11
|
-
user_clusters_string_list
|
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 __cluster_scope
|
12
12
|
]
|
13
13
|
|
14
14
|
class << self
|
15
15
|
|
16
|
+
##
|
17
|
+
#
|
18
|
+
#
|
16
19
|
def set_cluster_entities_attributes!(entity,array_cluster_ids)
|
17
20
|
cluster_entities_attributes = {}
|
18
21
|
entity_cluster_entities = entity.try(:cluster_entities) || []
|
@@ -35,94 +38,109 @@ module Klastera
|
|
35
38
|
cluster_entities_attributes
|
36
39
|
end
|
37
40
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
def
|
42
|
-
self.filter_clusterized_collection_with!(
|
43
|
-
organization,
|
44
|
-
cluster_id,
|
45
|
-
self.cluster_scope!(user,organization,scope,includes)
|
46
|
-
)
|
47
|
-
end
|
48
|
-
|
49
|
-
#
|
50
|
-
# In order to this works, active_record_collection argument
|
51
|
-
# should be passed through cluster_scope! method before.
|
52
|
-
#
|
53
|
-
def filter_clusterized_collection_with!(cluster_organization,cluster_id,active_record_collection)
|
54
|
-
if cluster_organization.is_in_cluster_mode?
|
55
|
-
if cluster_id.present?
|
56
|
-
cluster_array = [cluster_id]
|
57
|
-
# Based on force/use/show definition we don't really need this validation.
|
58
|
-
# A force cluster organization won't return an entity out of a cluster, but
|
59
|
-
# we don't know if the clusterized entities are fully definition-compliant.
|
60
|
-
cluster_array << nil if cluster_organization.optional_suborganization_mode?
|
61
|
-
active_record_collection = active_record_collection.joins(:cluster_entities).where("cluster_entities.cluster_id": cluster_array)
|
62
|
-
end
|
63
|
-
# you may use a block only with clusterizable data
|
64
|
-
yield(active_record_collection) if block_given?
|
65
|
-
end
|
66
|
-
active_record_collection
|
67
|
-
end
|
68
|
-
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
def set_collection_before_group_by_entity!(cluster_organization,active_record_collection,model_class,entity_params)
|
41
|
+
##
|
42
|
+
#
|
43
|
+
#
|
44
|
+
def set_collection_before_group_by_entity!(active_record_collection,entity_params,organization)
|
73
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
|
74
47
|
entity_params = entity_params.slice(*entity_params_keys).values
|
75
|
-
|
48
|
+
model_class = active_record_collection.model.base_class
|
49
|
+
model_relations = model_class.reflections.keys
|
50
|
+
if model_relations.include?(entity_params[0])
|
76
51
|
entity_params << "#{entity_params[0]}_id".to_sym
|
77
52
|
if entity_params[0] == 'cluster'
|
78
|
-
entity_params <<
|
79
|
-
active_record_collection = Klastera.
|
80
|
-
|
53
|
+
entity_params << I18n.t("klastera.#{UNCLUSTERED_ENTITY}") if organization.is_in_cluster_mode?
|
54
|
+
active_record_collection = Klastera.filter_clusterized!(
|
55
|
+
active_record_collection,
|
81
56
|
entity_params[2],
|
82
|
-
|
57
|
+
organization
|
83
58
|
)
|
84
59
|
end
|
85
|
-
yield(
|
86
|
-
active_record_collection,
|
87
|
-
entity_params_keys.zip(entity_params).to_h)
|
60
|
+
yield( active_record_collection, entity_params_keys.zip(entity_params).to_h )
|
88
61
|
end
|
89
62
|
end
|
90
63
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
def
|
95
|
-
|
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
|
96
80
|
end
|
97
81
|
|
98
82
|
##
|
99
|
-
#
|
100
|
-
# organization if the cluster mode is not active.
|
83
|
+
# If the cluster mode is not active, this returns a scope filtered by clusters of its organization/users/cluster
|
101
84
|
#
|
102
|
-
def cluster_scope!(user,organization,
|
85
|
+
def cluster_scope!(scope,user,organization,cluster_id=nil)
|
103
86
|
scope_klass = scope_class(scope).where(organization_id: organization)
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
cluster_ids =
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
or_these_cluster_ids = cluster_ids.present? ? " OR cluster_entities.cluster_id IN (#{cluster_ids.join(",")})" : ""
|
113
|
-
scope_klass = scope_klass.joins("
|
114
|
-
LEFT OUTER JOIN cluster_entities
|
115
|
-
ON entity_id = #{scope.table_name}.id
|
116
|
-
AND entity_type = '#{scope}'
|
117
|
-
").where("cluster_entities.id IS NULL#{or_these_cluster_ids}")
|
118
|
-
end
|
119
|
-
# Provisional fix to avoid SQL clashes due to DISTINCT ON clause
|
120
|
-
scope_klass = scope_class(scope).where(organization_id: organization).where(id: scope_klass.map(&:id))
|
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
|
121
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)
|
122
112
|
end
|
123
113
|
scope_klass
|
124
114
|
end
|
125
115
|
|
116
|
+
#
|
117
|
+
# Optimized version of cluster_scope!
|
118
|
+
#
|
119
|
+
def same_cluster_scope!(scope,user,organization,cluster_id=nil)
|
120
|
+
scope_klass = scope_class(scope)
|
121
|
+
if organization.is_in_cluster_mode?
|
122
|
+
scope_klass = scope_klass.eager_load(:organization,cluster_entities: :cluster)
|
123
|
+
|
124
|
+
if cluster_id.present?
|
125
|
+
cluster_ids = cluster_id.is_a?(Array) ? cluster_id : [cluster_id]
|
126
|
+
else
|
127
|
+
clusters = cluster_of!(user,organization)
|
128
|
+
cluster_ids = clusters.map(&:id).compact.sort
|
129
|
+
end
|
130
|
+
|
131
|
+
scope_klass = scope_klass.select("DISTINCT ON (#{scope.table_name}.id) #{scope.table_name}.id, #{scope.table_name}.*, clusters.*")
|
132
|
+
|
133
|
+
if organization.required_suborganization_mode?# || cluster_id # If cluster_id is passed NULL clusters are expected, rigth?
|
134
|
+
scope_klass = scope_klass.where( cluster_entities: { cluster_id: cluster_ids } )
|
135
|
+
else
|
136
|
+
or_these_cluster_ids = cluster_ids.present? ? " OR cluster_entities.cluster_id IN (#{cluster_ids.join(",")})" : ""
|
137
|
+
scope_klass = scope_klass.where("cluster_entities.id IS NULL#{or_these_cluster_ids}")
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
scope_klass.where(organization_id: organization)
|
142
|
+
end
|
143
|
+
|
126
144
|
##
|
127
145
|
# TODO:
|
128
146
|
# Implement a validation to ensure that
|
@@ -135,19 +153,19 @@ module Klastera
|
|
135
153
|
end
|
136
154
|
|
137
155
|
##
|
138
|
-
# Returns a
|
139
|
-
#
|
156
|
+
# Returns a Cluster::ActiveRecord_Relation if the organization is using the cluster mode.
|
140
157
|
# Use this only to get current_user/organization clusters
|
141
|
-
# understanding this wont be useful out of a cluster mode context.
|
142
158
|
#
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
159
|
+
def cluster_of!(user,organization)
|
160
|
+
# A cluster user with role Root or Admin retrieve every cluster of its organizations
|
161
|
+
and_this_user = user.can_admin_clusters? ? nil : user
|
162
|
+
# We weill return a cluster active record collection that can be filtered
|
163
|
+
active_record_collection = ::ClusterUser.clusters_of(organization,and_this_user)
|
164
|
+
# Add a empty cluster instance to handle models without a cluster assignation.
|
165
|
+
if organization.optional_mode? # For use and show modes only
|
166
|
+
active_record_collection << ::Cluster.new({nid: UNCLUSTERED_ENTITY, name: I18n.t("klastera.#{UNCLUSTERED_ENTITY}")})
|
149
167
|
end
|
150
|
-
|
168
|
+
active_record_collection
|
151
169
|
end
|
152
170
|
|
153
171
|
##
|
@@ -165,12 +183,11 @@ module Klastera
|
|
165
183
|
end
|
166
184
|
|
167
185
|
##
|
168
|
-
#
|
169
|
-
# that why, we perfomed this logic here
|
186
|
+
# cluster_of! needs a user and a organization. that why we perfomed this logic here
|
170
187
|
#
|
171
188
|
def user_clusters_string_list!(user,organization,cluster_entities,separator,attribute=:name)
|
172
|
-
@
|
173
|
-
self.entity_clusters_string_list!(cluster_entities, separator, attribute, @
|
189
|
+
@clusters_session ||= self.cluster_of!(user,organization)
|
190
|
+
self.entity_clusters_string_list!(cluster_entities, separator, attribute, @clusters_session.map(&:id))
|
174
191
|
end
|
175
192
|
end
|
176
193
|
|
@@ -183,8 +200,8 @@ module Klastera
|
|
183
200
|
end
|
184
201
|
|
185
202
|
def user_has_more_than_one_cluster
|
186
|
-
@
|
187
|
-
@
|
203
|
+
@clusters_session ||= cluster_of_my_own
|
204
|
+
@clusters_session.size > 1
|
188
205
|
end
|
189
206
|
|
190
207
|
def set_the_lonely_cluster
|
@@ -192,7 +209,7 @@ module Klastera
|
|
192
209
|
parameters = params.require( form_model ) rescue nil
|
193
210
|
lonely_cluster = parameters.blank? ? false : parameters.permit( :lonely_cluster ).present?
|
194
211
|
if lonely_cluster
|
195
|
-
params[form_model][:cluster_id] =
|
212
|
+
params[form_model][:cluster_id] = cluster_of_my_own.first.try(:id)
|
196
213
|
end
|
197
214
|
end
|
198
215
|
|
@@ -210,26 +227,20 @@ module Klastera
|
|
210
227
|
[ :cluster_id ].concat( ::ClusterFilter.attributes )
|
211
228
|
end
|
212
229
|
|
213
|
-
def
|
214
|
-
Klastera.
|
230
|
+
def filter_clusterized(active_record_collection,cluster_id)
|
231
|
+
Klastera.filter_clusterized!(active_record_collection, cluster_id, cluster_organization)
|
215
232
|
end
|
216
233
|
|
217
|
-
def
|
218
|
-
Klastera.
|
234
|
+
def cluster_scope(scope,cluster_id=nil)
|
235
|
+
Klastera.cluster_scope!(scope, cluster_user, cluster_organization, cluster_id)
|
219
236
|
end
|
220
237
|
|
221
|
-
def
|
222
|
-
Klastera.
|
223
|
-
scope,
|
224
|
-
cluster_id,
|
225
|
-
cluster_user,
|
226
|
-
cluster_organization,
|
227
|
-
includes
|
228
|
-
)
|
238
|
+
def same_cluster_scope(scope,cluster_id=nil)
|
239
|
+
Klastera.same_cluster_scope!(scope, cluster_user, cluster_organization, cluster_id)
|
229
240
|
end
|
230
241
|
|
231
|
-
def
|
232
|
-
Klastera.
|
242
|
+
def cluster_of_my_own
|
243
|
+
Klastera.cluster_of!(cluster_user, cluster_organization)
|
233
244
|
end
|
234
245
|
|
235
246
|
def user_clusters_string_list(object_entity,separator,attribute=:name)
|
@@ -242,6 +253,10 @@ module Klastera
|
|
242
253
|
)
|
243
254
|
end
|
244
255
|
|
256
|
+
def set_collection_before_group_by_entity(active_record_collection,entity_params,&block)
|
257
|
+
Klastera.set_collection_before_group_by_entity!(active_record_collection, params, cluster_organization, &block)
|
258
|
+
end
|
259
|
+
|
245
260
|
included do
|
246
261
|
Klastera::KLSTR_HELPERS.each do |action|
|
247
262
|
if respond_to?(:helper_method)
|
data/lib/klastera/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
module Klastera
|
2
|
-
VERSION = "1.2
|
3
|
-
end
|
2
|
+
VERSION = "1.3.2"
|
3
|
+
end
|
@@ -13,6 +13,11 @@ namespace :klastera do
|
|
13
13
|
klass = args.entity.constantize
|
14
14
|
ActiveRecord::Base.transaction do
|
15
15
|
klass.where.not(cluster_id: nil).each do |entity|
|
16
|
+
if entity.cluster.blank?
|
17
|
+
puts "Cluster ID #{entity.cluster_id} was not found!"
|
18
|
+
puts "skip..."
|
19
|
+
next
|
20
|
+
end
|
16
21
|
Klastera::ClusterEntity.create(entity: entity, cluster: entity.cluster)
|
17
22
|
end
|
18
23
|
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: 1.2
|
4
|
+
version: 1.3.2
|
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-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|