klastera 1.2.4.2 → 1.3.3.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 +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 +123 -113
- data/lib/klastera/version.rb +1 -1
- data/lib/tasks/klastera_tasks.rake +5 -0
- 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: 7fae7d83f8b939309532cb345a38bc9c4f76bfca455b16ccda6ad17283f6851e
|
4
|
+
data.tar.gz: f62a4b00a2c526c47b95464263dcca6de69d880b6095fdba41204fd14eff66b2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 591f79de0e4332c44454b172eee9aea5491cfbee45ed2d942f67841a6918ff22323d0f0219e15ee60709672de8e190e9d466a822b96eb82b213dfecec5b22647
|
7
|
+
data.tar.gz: bb1b6e5ec7c8c4795f1662f7d0d9e58a758f0594547bd2ac64408efcb77a02fef103a0f1c349a6243d0023620f2fc23c631761f78ea5ced6a2c9640428b215eb
|
@@ -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
|
-
joins(: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,18 @@ 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
|
-
|
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 same_cluster_scope
|
12
|
+
cluster_scope_through_of
|
12
13
|
]
|
13
14
|
|
14
15
|
class << self
|
15
16
|
|
17
|
+
##
|
18
|
+
#
|
19
|
+
#
|
16
20
|
def set_cluster_entities_attributes!(entity,array_cluster_ids)
|
17
21
|
cluster_entities_attributes = {}
|
18
22
|
entity_cluster_entities = entity.try(:cluster_entities) || []
|
@@ -35,57 +39,10 @@ module Klastera
|
|
35
39
|
cluster_entities_attributes
|
36
40
|
end
|
37
41
|
|
38
|
-
#
|
39
|
-
# In cases you don't have the active_record_collection object to filter, this method helps you
|
40
|
-
# calling cluster_scope! before and pass the collction to filter_clusterized_collection_with!,
|
41
|
-
# but at the end of the day, I just like to wrap methods.
|
42
|
-
#
|
43
|
-
def cluster_scope_filtered!(scope,cluster_id,user,organization)
|
44
|
-
self.filter_clusterized_collection_with!(
|
45
|
-
cluster_id,
|
46
|
-
self.cluster_scope!(scope,user,organization),
|
47
|
-
organization
|
48
|
-
)
|
49
|
-
end
|
50
|
-
|
51
|
-
#
|
52
|
-
# In order to this works, active_record_collection argument
|
53
|
-
# should be passed through cluster_scope! method before.
|
54
|
-
#
|
55
|
-
def filter_clusterized_collection_with!(cluster_id,active_record_collection,cluster_organization)
|
56
|
-
if cluster_organization.is_in_cluster_mode?
|
57
|
-
|
58
|
-
# IMPORTANT
|
59
|
-
# The next block was commented on because cluster_scope! method is not returning
|
60
|
-
# the cluster_entities within the scope anymore and it doesn't make any sense trying
|
61
|
-
# to filter based on force/use/show logic. Nevertheless, someday we will need it again.
|
62
|
-
|
63
|
-
# # If cluster_id is nil we will try to filter including unclustered entities active_record_collection wsas
|
64
|
-
# unless cluster_id.present?
|
65
|
-
# # Based on force/use/show definition we don't really need this validation. A force cluster organization won't return an entity that
|
66
|
-
# # doesn't belong to a cluster. Nevertheless we don't know if active_record_collection argument is fully definition-compliant.
|
67
|
-
# if cluster_organization.optional_suborganization_mode?
|
68
|
-
# cluster_array << nil
|
69
|
-
# end
|
70
|
-
# end
|
71
|
-
|
72
|
-
if cluster_id.present?
|
73
|
-
cluster_array = [cluster_id] unless cluster_id.is_a?(Array)
|
74
|
-
# The ActiveRecordCollection argument should have previously eager-loaded the cluster entities, thus the join statement is unnecessary.
|
75
|
-
active_record_collection = active_record_collection.joins(:cluster_entities).where("cluster_entities.cluster_id": cluster_array)
|
76
|
-
end
|
77
|
-
|
78
|
-
# You should use a block with clusterable data only
|
79
|
-
yield(active_record_collection) if block_given?
|
80
|
-
end
|
81
|
-
active_record_collection
|
82
|
-
end
|
83
|
-
|
84
42
|
##
|
85
43
|
#
|
86
44
|
#
|
87
|
-
|
88
|
-
def set_collection_before_group_by_entity!(active_record_collection,entity_params,cluster_organization)
|
45
|
+
def set_collection_before_group_by_entity!(active_record_collection,entity_params,organization,include_nil_by_rule=true)
|
89
46
|
entity_params_keys = [:entity_name,:entity_attribute,:entity_id,:entity_id_attribute,:unamed]
|
90
47
|
entity_params[:entity_id] ||= nil #Ensures the entity_id attribute presence even if there is no filter
|
91
48
|
entity_params = entity_params.slice(*entity_params_keys).values
|
@@ -94,51 +51,108 @@ module Klastera
|
|
94
51
|
if model_relations.include?(entity_params[0])
|
95
52
|
entity_params << "#{entity_params[0]}_id".to_sym
|
96
53
|
if entity_params[0] == 'cluster'
|
97
|
-
entity_params <<
|
98
|
-
active_record_collection = Klastera.
|
99
|
-
entity_params[2],
|
54
|
+
entity_params << I18n.t("klastera.#{UNCLUSTERED_ENTITY}") if organization.is_in_cluster_mode?
|
55
|
+
active_record_collection = Klastera.filter_clusterized!(
|
100
56
|
active_record_collection,
|
101
|
-
|
57
|
+
entity_params[2],
|
58
|
+
organization,
|
59
|
+
include_nil_by_rule
|
102
60
|
)
|
103
61
|
end
|
104
62
|
yield( active_record_collection, entity_params_keys.zip(entity_params).to_h )
|
105
63
|
end
|
106
64
|
end
|
107
65
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
def
|
112
|
-
|
66
|
+
#
|
67
|
+
# In order to this work, the active_record_collection argument should be a cluster_scope! return.
|
68
|
+
#
|
69
|
+
def filter_clusterized!(active_record_collection,cluster_id,organization,include_nil_by_rule=true)
|
70
|
+
if organization.is_in_cluster_mode? && cluster_id.present?
|
71
|
+
# Ensures that an array of ids is used in the statement
|
72
|
+
cluster_array = cluster_id.is_a?(Array) ? cluster_id.dup : [cluster_id]
|
73
|
+
# If we receive a UNCLUSTERED_ENTITY request, it means that we should filter by entities without clusters.
|
74
|
+
# The optional_mode?(show/use) condition add to this method an ambivalent use,
|
75
|
+
# where you can filter a unique value or multiple including NULL ones.
|
76
|
+
cluster_array << nil if include_nil_by_rule || cluster_id == UNCLUSTERED_ENTITY && ( cluster_array.delete(UNCLUSTERED_ENTITY) || organization.optional_mode? )
|
77
|
+
active_record_collection = active_record_collection.where(cluster_entities: { cluster_id: cluster_array.uniq })
|
78
|
+
# You should use a block with clusterable data only
|
79
|
+
yield(active_record_collection) if block_given?
|
80
|
+
end
|
81
|
+
active_record_collection
|
113
82
|
end
|
114
83
|
|
115
84
|
##
|
116
|
-
#
|
117
|
-
# organization if the cluster mode is not active.
|
85
|
+
# If the cluster mode is not active, this returns a scope filtered by clusters of its organization/users/cluster
|
118
86
|
#
|
119
|
-
def cluster_scope!(scope,user,organization)
|
87
|
+
def cluster_scope!(scope,user,organization,cluster_id=nil)
|
120
88
|
scope_klass = scope_class(scope).where(organization_id: organization)
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
scope_klass = scope_klass.joins("
|
130
|
-
LEFT OUTER JOIN cluster_entities
|
131
|
-
ON entity_id = #{scope.table_name}.id
|
132
|
-
AND entity_type = '#{scope}'
|
133
|
-
").where("cluster_entities.id IS NULL#{or_these_cluster_ids}")
|
134
|
-
end
|
135
|
-
# Provisional fix to avoid SQL clashes due to DISTINCT ON clause
|
136
|
-
scope_klass = scope_class(scope).eager_load(:cluster_entities).where(id: scope_klass.map(&:id), organization_id: organization)
|
89
|
+
if organization.is_in_cluster_mode?
|
90
|
+
|
91
|
+
if cluster_id.present?
|
92
|
+
# Ensures that an array of id is used in the statement
|
93
|
+
cluster_ids = cluster_id.is_a?(Array) ? cluster_id : [cluster_id]
|
94
|
+
else
|
95
|
+
clusters = cluster_of!(organization,user)
|
96
|
+
cluster_ids = clusters.map(&:id).compact
|
137
97
|
end
|
98
|
+
|
99
|
+
scope_klass = scope_klass.select("DISTINCT ON (#{scope.table_name}.id) #{scope.table_name}.id, #{scope.table_name}.*")
|
100
|
+
|
101
|
+
if organization.required_suborganization_mode?
|
102
|
+
scope_klass = scope_klass.joins(:cluster_entities).where( cluster_entities: { cluster_id: cluster_ids } )
|
103
|
+
else
|
104
|
+
or_these_cluster_ids = cluster_ids.present? ? " OR cluster_entities.cluster_id IN (#{cluster_ids.join(",")})" : ""
|
105
|
+
scope_klass = scope_klass.joins("
|
106
|
+
LEFT OUTER JOIN cluster_entities
|
107
|
+
ON entity_id = #{scope.table_name}.id
|
108
|
+
AND entity_type = '#{scope}'
|
109
|
+
").where("cluster_entities.id IS NULL#{or_these_cluster_ids}")
|
110
|
+
end
|
111
|
+
|
112
|
+
# Provisional fix to avoid unresolved SQL clashes with the main application due to DISTINCT ON clause
|
113
|
+
scope_klass = scope_class(scope).eager_load(:cluster_entities).where(id: scope_klass.map(&:id), organization_id: organization)
|
138
114
|
end
|
139
115
|
scope_klass
|
140
116
|
end
|
141
117
|
|
118
|
+
#
|
119
|
+
# Optimized version of cluster_scope!
|
120
|
+
#
|
121
|
+
def same_cluster_scope!(scope,user,organization,cluster_id=nil)
|
122
|
+
scope_klass = scope_class(scope)
|
123
|
+
if organization.is_in_cluster_mode?
|
124
|
+
scope_klass = scope_klass.eager_load(:organization,cluster_entities: :cluster)
|
125
|
+
|
126
|
+
if cluster_id.present?
|
127
|
+
cluster_ids = cluster_id.is_a?(Array) ? cluster_id : [cluster_id]
|
128
|
+
else
|
129
|
+
clusters = cluster_of!(organization,user)
|
130
|
+
cluster_ids = clusters.map(&:id).compact.sort
|
131
|
+
end
|
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
|
+
|
144
|
+
#
|
145
|
+
# Filter non-clustered entity through a clusterized one
|
146
|
+
#
|
147
|
+
def cluster_scope_through_of!(relation,cluster_entity_klass,scope_klass,user,organization,cluster_id=nil)
|
148
|
+
scope_class(scope_klass).joins(relation => :cluster_entities).where(
|
149
|
+
organization_id: organization,
|
150
|
+
"#{relation}_id" => same_cluster_scope!(
|
151
|
+
cluster_entity_klass, user, organization, cluster_id
|
152
|
+
)
|
153
|
+
)
|
154
|
+
end
|
155
|
+
|
142
156
|
##
|
143
157
|
# TODO:
|
144
158
|
# Implement a validation to ensure that
|
@@ -151,19 +165,21 @@ module Klastera
|
|
151
165
|
end
|
152
166
|
|
153
167
|
##
|
154
|
-
# Returns a
|
155
|
-
#
|
168
|
+
# Returns a Cluster::ActiveRecord_Relation if the organization is using the cluster mode.
|
156
169
|
# Use this only to get current_user/organization clusters
|
157
|
-
# understanding this wont be useful out of a cluster mode context.
|
158
170
|
#
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
if
|
163
|
-
|
164
|
-
|
171
|
+
def cluster_of!(organization,user)
|
172
|
+
# A cluster user with role Root or Admin retrieve every cluster of its organizations
|
173
|
+
# Thats why we pass a nil object to avoid user filter, but if you pass a nil user as argument
|
174
|
+
# The result is the same as if the user was a admin, BE CAREFUL!
|
175
|
+
and_this_user = user.try(:can_admin_clusters?) ? nil : user
|
176
|
+
# We weill return a cluster active record collection that can be filtered
|
177
|
+
active_record_collection = ::ClusterUser.clusters_of(organization,and_this_user)
|
178
|
+
# Add a empty cluster instance to handle models without a cluster assignation.
|
179
|
+
if organization.optional_mode? # For use and show modes only
|
180
|
+
active_record_collection << ::Cluster.new({nid: UNCLUSTERED_ENTITY, name: I18n.t("klastera.#{UNCLUSTERED_ENTITY}")})
|
165
181
|
end
|
166
|
-
|
182
|
+
active_record_collection
|
167
183
|
end
|
168
184
|
|
169
185
|
##
|
@@ -181,12 +197,11 @@ module Klastera
|
|
181
197
|
end
|
182
198
|
|
183
199
|
##
|
184
|
-
#
|
185
|
-
# that why, we perfomed this logic here
|
200
|
+
# cluster_of! needs a user and a organization. that why we perfomed this logic here
|
186
201
|
#
|
187
202
|
def user_clusters_string_list!(user,organization,cluster_entities,separator,attribute=:name)
|
188
|
-
@
|
189
|
-
self.entity_clusters_string_list!(cluster_entities, separator, attribute, @
|
203
|
+
@clusters_session ||= Klastera.cluster_of!(organization,user)
|
204
|
+
self.entity_clusters_string_list!(cluster_entities, separator, attribute, @clusters_session.map(&:id))
|
190
205
|
end
|
191
206
|
end
|
192
207
|
|
@@ -199,8 +214,8 @@ module Klastera
|
|
199
214
|
end
|
200
215
|
|
201
216
|
def user_has_more_than_one_cluster
|
202
|
-
@
|
203
|
-
@
|
217
|
+
@clusters_session ||= cluster_of_my_own
|
218
|
+
@clusters_session.size > 1
|
204
219
|
end
|
205
220
|
|
206
221
|
def set_the_lonely_cluster
|
@@ -208,7 +223,7 @@ module Klastera
|
|
208
223
|
parameters = params.require( form_model ) rescue nil
|
209
224
|
lonely_cluster = parameters.blank? ? false : parameters.permit( :lonely_cluster ).present?
|
210
225
|
if lonely_cluster
|
211
|
-
params[form_model][:cluster_id] =
|
226
|
+
params[form_model][:cluster_id] = cluster_of_my_own.first.try(:id)
|
212
227
|
end
|
213
228
|
end
|
214
229
|
|
@@ -226,29 +241,24 @@ module Klastera
|
|
226
241
|
[ :cluster_id ].concat( ::ClusterFilter.attributes )
|
227
242
|
end
|
228
243
|
|
229
|
-
def
|
230
|
-
Klastera.
|
244
|
+
def filter_clusterized(active_record_collection,cluster_id,include_nil_by_rule=true)
|
245
|
+
Klastera.filter_clusterized!(active_record_collection, cluster_id, cluster_organization, include_nil_by_rule)
|
231
246
|
end
|
232
247
|
|
233
|
-
def cluster_scope(scope)
|
234
|
-
Klastera.cluster_scope!(scope,cluster_user,cluster_organization)
|
248
|
+
def cluster_scope(scope,cluster_id=nil)
|
249
|
+
Klastera.cluster_scope!(scope, cluster_user, cluster_organization, cluster_id)
|
235
250
|
end
|
236
251
|
|
237
|
-
def
|
238
|
-
Klastera.
|
252
|
+
def same_cluster_scope(scope,cluster_id=nil)
|
253
|
+
Klastera.same_cluster_scope!(scope, cluster_user, cluster_organization, cluster_id)
|
239
254
|
end
|
240
255
|
|
241
|
-
def
|
242
|
-
Klastera.
|
243
|
-
scope,
|
244
|
-
cluster_id,
|
245
|
-
cluster_user,
|
246
|
-
cluster_organization
|
247
|
-
)
|
256
|
+
def cluster_scope_through_of(relation,cluster_entity_klass,scope_klass,cluster_id=nil)
|
257
|
+
Klastera.cluster_scope_through_of!(relation, cluster_entity_klass, scope_klass, cluster_user, cluster_organization, cluster_id )
|
248
258
|
end
|
249
259
|
|
250
|
-
def
|
251
|
-
Klastera.
|
260
|
+
def cluster_of_my_own
|
261
|
+
Klastera.cluster_of!(cluster_organization,cluster_user)
|
252
262
|
end
|
253
263
|
|
254
264
|
def user_clusters_string_list(object_entity,separator,attribute=:name)
|
@@ -261,8 +271,8 @@ module Klastera
|
|
261
271
|
)
|
262
272
|
end
|
263
273
|
|
264
|
-
def set_collection_before_group_by_entity(active_record_collection,entity_params,&block)
|
265
|
-
Klastera.set_collection_before_group_by_entity!(active_record_collection,params,cluster_organization
|
274
|
+
def set_collection_before_group_by_entity(active_record_collection,entity_params,include_nil_by_rule=true,&block)
|
275
|
+
Klastera.set_collection_before_group_by_entity!(active_record_collection, params, cluster_organization, include_nil_by_rule, &block)
|
266
276
|
end
|
267
277
|
|
268
278
|
included do
|
data/lib/klastera/version.rb
CHANGED
@@ -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.
|
4
|
+
version: 1.3.3.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-22 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
|