klastera 1.2.3.2 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d912b888e108c33f2a258ebbe682233d943b21f83b81fb5d79295703262bef1
4
- data.tar.gz: 3f7004752065d0a1c7f925dfc3198367e1612153e34d8dba47f558df4afbad69
3
+ metadata.gz: a8ffbcdd772da4880e6fac3a2fa9165754a873673afb5f2108161d099e634622
4
+ data.tar.gz: 8541bb3c9c26b8e0084e42b91ef9262b443dbaf1250e4bd209a2fde9db6d92a9
5
5
  SHA512:
6
- metadata.gz: 2d9da626373702d6a7453ff46d7231e5596eb1c15f4619c70e51e7f54cc1836f403b45da950dc1e686d861aa9d227dccacda106695303c75fea02edaca1fd9dc
7
- data.tar.gz: 6d5ad5c6d6a5dcf2b13ded5181da671261c1735c19ead485c7560fc5a409de3aa6f699d7516a7b99712e34620a9c8aa940ba166d53596e70d1a9c42df3d43ff5
6
+ metadata.gz: e43f85ef19e054e32274ad4c5c99db112333c76fa831383e72f3e0f7d7ad312d5b48519c33794a46ad3148dd4d50cca62117620e756b12933f0889a9891ad631
7
+ data.tar.gz: dc98ab10fc1588bec841af4b1e3a4caee5b976986e8023b7528c70190ee2b27fb2e230855773aaa1fcf1a28392380b9f63d186b9165e4f5704acf2631cd3d40f
@@ -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.to!(::Cluster.related_entities.map{|re|re.constantize})
42
- @cluster.destroy
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
- ::Cluster.total_records_assign_to(self) > 0
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
- def kcluster_id
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
- # Returns a hash with the users with cluster relation
22
- #
23
- def user_clusters_from(organization)
24
- users_hash = {}
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
- # Returns a Cluster::ActiveRecord_Relation
28
+ # Return a hash of users and its clusters
36
29
  #
37
- ##
38
- def clusters_from(user,organization)
39
- clusters = []
40
- unless user.can_admin_clusters?
41
- # We could return cu.clusters but you may quering this result and a array can't handle order, where and etc.
42
- # So we take only the cluster_ids and perfome a new search in order to get a Cluster::ActiveRecord_Relation
43
- # like the else block.
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
- clusters
38
+ users
55
39
  end
56
40
  end
57
41
  end
@@ -9,10 +9,7 @@ module Klastera::Concerns::Clusterizable
9
9
 
10
10
  validates :cluster_id, presence: true, if: proc { self.try(:cluster_id) && self.organization.required_suborganization_mode? }
11
11
  validate :at_least_one_cluster_entity, if: proc { self.organization.required_suborganization_mode? }
12
-
13
- scope :related_clusters, ->() {
14
- includes(:cluster_entities).where("cluster_entities.entity_id = #{self.table_name}.id")
15
- }
12
+ validate :uniqueness_of_cluster_entity_record
16
13
 
17
14
  def at_least_one_cluster_entity
18
15
  if cluster_entities.length == 0 || cluster_entities.reject{|cluster_entity| cluster_entity._destroy == true}.empty?
@@ -20,8 +17,15 @@ module Klastera::Concerns::Clusterizable
20
17
  end
21
18
  end
22
19
 
20
+ ##
21
+ # This is a legacy method and we don't recommend using it.
22
+ # Implement directly Klastera.entity_clusters_string_list instead of this method.
23
+ # TODO: In order to deprecate it, you will need to perform some changes in the main app
24
+ ##
23
25
  def clusters_string_separated_by(separator,attribute=:name)
24
- self.cluster_entities.map{|ce|ce.cluster.try(attribute)}.join(separator)
26
+ Klastera.entity_clusters_string_list!(
27
+ self.cluster_entities, separator, attribute
28
+ )
25
29
  end
26
30
 
27
31
  def has_one_cluster_entity
@@ -29,6 +33,12 @@ module Klastera::Concerns::Clusterizable
29
33
  return errors.add(:cluster_entities, I18n.t('klastera.messages.has_one_cluster_entity'))
30
34
  end
31
35
  end
36
+
37
+ def uniqueness_of_cluster_entity_record
38
+ if cluster_entities.map(&:cluster_id).uniq.size != cluster_entities.size
39
+ return errors.add(:cluster_entities, I18n.t('klastera.messages.duplicated_cluster_entity') )
40
+ end
41
+ end
32
42
  end
33
43
 
34
44
  module ClassMethods
@@ -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.modes_as_strings , message: I18n.t('klastera.clusters.wrong_option') }, if: proc{ use_cluster_as.present? }
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, :entities_transfered
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-12">
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-12">
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">
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-12">
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.<br/>¿Está seguro?</h4>
6
- <% if ::Cluster.total_records_assign_to(@cluster) > 0 %>
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 four-icons"><span class="fa fa-cogs"></span></th>
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 four-icons">
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>
@@ -18,4 +18,4 @@
18
18
 
19
19
  </div>
20
20
 
21
- <%= render 'layouts/remote_form/modal'%>
21
+ <%= render 'layouts/remote_form/modal', width: 'md' %>
@@ -1,6 +1,6 @@
1
1
  <tr class="nested-fields">
2
2
  <td class="field">
3
- <%= f.select :cluster_id, cluster_clusters.map{|c|[c.name,c.id]}, { include_blank: true }, { class: 'form-control' } %>
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 = cluster_clusters.map{|c|[c.name,(c.id||c.nid)]} %>
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 = @cluster_clusters || cluster_clusters
7
+ cluster_collection = @clusters_session || cluster_of_my_own
8
8
  label = t('klastera.cluster.title')
9
9
  %>
10
10
  <% if f.nil? %>
@@ -1,4 +1,5 @@
1
1
  <% if cluster_organization.is_in_cluster_mode? %>
2
+ <% @clusters_session = cluster_of_my_own %>
2
3
  <div class="col-xs-12">
3
4
  <% if hide_title||=false %>
4
5
  <div class="form-group file required <%=f.object.class.name.parameterize%>_cluster_entity<%=' has-error' if f.object.errors.has_key?(:cluster_entities)%>">
@@ -26,6 +27,7 @@
26
27
  <table id="cluster-entities" class="table table-striped">
27
28
  <tbody class="cluster-entity-rows">
28
29
  <%= f.fields_for :cluster_entities do |cluster_entity|%>
30
+ <% next unless @clusters_session.map(&:id).include?(cluster_entity.try(:object).try(:cluster_id)) %>
29
31
  <%= render 'layouts/klastera/cluster_entity_fields', f: cluster_entity %>
30
32
  <% end %>
31
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>
@@ -61,6 +61,7 @@ es:
61
61
  at_least_one_cluster_entity: Debe agregar al menos un cluster
62
62
  record_action_successfully: Registro %{a} exitosamente
63
63
  cant_delete_the_last_record_in_required_suborganization_mode: No se puede eliminar el único cluster de esta organización
64
+ duplicated_cluster_entity: Hay un cluster duplicado
64
65
  new_cluster_id:
65
66
  nil: Cluster no existe
66
67
  same: Cluster no puede ser el mismo
@@ -5,8 +5,17 @@ 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 __cluster_scope
12
+ ]
13
+
8
14
  class << self
9
15
 
16
+ ##
17
+ #
18
+ #
10
19
  def set_cluster_entities_attributes!(entity,array_cluster_ids)
11
20
  cluster_entities_attributes = {}
12
21
  entity_cluster_entities = entity.try(:cluster_entities) || []
@@ -29,94 +38,119 @@ module Klastera
29
38
  cluster_entities_attributes
30
39
  end
31
40
 
32
- #
33
- # I like to wrap methods
34
- #
35
- def cluster_scope_filtered!(scope,cluster_id,user,organization,includes=[])
36
- self.filter_clusterized_collection_with!(
37
- organization,
38
- cluster_id,
39
- self.cluster_scope!(user,organization,scope,includes)
40
- )
41
- end
42
-
43
- #
44
- # In order to this works, active_record_collection argument
45
- # should be passed through cluster_scope! method before.
46
- #
47
- def filter_clusterized_collection_with!(cluster_organization,cluster_id,active_record_collection)
48
- if cluster_organization.is_in_cluster_mode?
49
- if cluster_id.present?
50
- cluster_array = [cluster_id]
51
- # Based on force/use/show definition we don't really need this validation.
52
- # A force cluster organization won't return an entity out of a cluster, but
53
- # we don't know if the clusterized entities are fully definition-compliant.
54
- cluster_array << nil if cluster_organization.optional_suborganization_mode?
55
- active_record_collection = active_record_collection.joins(:cluster_entities).where("cluster_entities.cluster_id": cluster_array)
56
- end
57
- # you may use a block only with clusterizable data
58
- yield(active_record_collection) if block_given?
59
- end
60
- active_record_collection
61
- end
62
-
63
- #
64
- #
65
- #
66
- 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)
67
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
68
47
  entity_params = entity_params.slice(*entity_params_keys).values
69
- if model_class.try(:reflections).try(:keys).try(:include?,entity_params[0])
48
+ model_class = active_record_collection.model.base_class
49
+ model_relations = model_class.reflections.keys
50
+ if model_relations.include?(entity_params[0])
70
51
  entity_params << "#{entity_params[0]}_id".to_sym
71
52
  if entity_params[0] == 'cluster'
72
- entity_params << :unclustered if cluster_organization.is_in_cluster_mode?
73
- active_record_collection = Klastera.filter_clusterized_collection_with!(
74
- cluster_organization,
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,
75
56
  entity_params[2],
76
- active_record_collection
57
+ organization
77
58
  )
78
59
  end
79
- yield(
80
- active_record_collection,
81
- entity_params_keys.zip(entity_params).to_h)
60
+ yield( active_record_collection, entity_params_keys.zip(entity_params).to_h )
82
61
  end
83
62
  end
84
63
 
85
- ##
86
- # Returns a ::Cluster::ActiveRecord_Relation from a given scope
87
- #
88
- def clusters_from!(user,organization,scope,includes=[])
89
- cluster_scope!(user,organization,scope,includes).related_clusters
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
90
80
  end
91
81
 
92
82
  ##
93
- # Returns a scope filtered by clusters or its
94
- # 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
95
84
  #
96
- def cluster_scope!(user,organization,scope,includes=[])
85
+ def cluster_scope!(scope,user,organization,cluster_id=nil)
97
86
  scope_klass = scope_class(scope).where(organization_id: organization)
98
- session_clusters(user,organization) do |clusters|
99
- if organization.is_in_cluster_mode?
100
- scope_klass = scope_klass.select("DISTINCT ON (#{scope.table_name}.id) #{scope.table_name}.id, #{scope.table_name}.*")
101
- scope_klass = scope_klass.includes(includes) if includes.present?
102
- cluster_ids = clusters.map(&:id)
103
- if organization.required_suborganization_mode?
104
- scope_klass = scope_klass.joins(:cluster_entities).where( cluster_entities: { cluster_id: cluster_ids } )
105
- else
106
- or_these_cluster_ids = cluster_ids.present? ? " OR cluster_entities.cluster_id IN (#{cluster_ids.join(",")})" : ""
107
- scope_klass = scope_klass.joins("
108
- LEFT OUTER JOIN cluster_entities
109
- ON entity_id = #{scope.table_name}.id
110
- AND entity_type = '#{scope}'
111
- ").where("cluster_entities.id IS NULL#{or_these_cluster_ids}")
112
- end
113
- # Provisional fix to avoid SQL clashes due to DISTINCT ON clause
114
- 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
115
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)
116
112
  end
117
113
  scope_klass
118
114
  end
119
115
 
116
+
117
+ # Untested version for more optimized queries
118
+ #
119
+ def __cluster_scope!(scope,user,organization,cluster_id=nil,same_scope:false)
120
+ scope_klass = scope_class(scope)
121
+ if organization.is_in_cluster_mode?
122
+ scope_klass = scope_klass.includes(:organization)
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
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?
134
+ scope_klass = scope_klass.includes(cluster_entities: :cluster).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.joins("
138
+ LEFT OUTER JOIN cluster_entities
139
+ ON entity_id = #{scope.table_name}.id
140
+ AND entity_type = '#{scope}'
141
+ ").joins("
142
+ LEFT OUTER JOIN clusters
143
+ ON clusters.id = cluster_entities.cluster_id
144
+ ").where("cluster_entities.id IS NULL#{or_these_cluster_ids}")
145
+ end
146
+ # Provisional fix to avoid unresolved SQL clashes with the main application due to DISTINCT ON clause
147
+ unless same_scope
148
+ scope_klass = scope_class(scope).eager_load(:cluster_entities).where(id: scope_klass.map(&:id))
149
+ end
150
+ end
151
+ scope_klass.where(organization_id: organization)
152
+ end
153
+
120
154
  ##
121
155
  # TODO:
122
156
  # Implement a validation to ensure that
@@ -129,67 +163,44 @@ module Klastera
129
163
  end
130
164
 
131
165
  ##
132
- # Returns a cluster array if organization is using the cluster mode
133
- #
166
+ # Returns a Cluster::ActiveRecord_Relation if the organization is using the cluster mode.
134
167
  # Use this only to get current_user/organization clusters
135
- # understanding this wont be useful out of a cluster mode context.
136
168
  #
137
- ##
138
- def session_clusters(user,organization)
139
- clusters = organization.is_in_cluster_mode? ? ::ClusterUser.clusters_from(user,organization) : []
140
- if block_given?
141
- clusters = clusters.reject{|c|c.id.blank?}
142
- yield(clusters)
169
+ def cluster_of!(user,organization)
170
+ # A cluster user with role Root or Admin retrieve every cluster of its organizations
171
+ and_this_user = user.can_admin_clusters? ? nil : user
172
+ # We weill return a cluster active record collection that can be filtered
173
+ active_record_collection = ::ClusterUser.clusters_of(organization,and_this_user)
174
+ # Add a empty cluster instance to handle models without a cluster assignation.
175
+ if organization.optional_mode? # For use and show modes only
176
+ active_record_collection << ::Cluster.new({nid: UNCLUSTERED_ENTITY, name: I18n.t("klastera.#{UNCLUSTERED_ENTITY}")})
143
177
  end
144
- clusters
178
+ active_record_collection
145
179
  end
146
- end
147
180
 
148
- included do
149
- if respond_to?(:helper_method)
150
- helper_method :cluster_scope
151
- helper_method :user_cluster
152
- helper_method :cluster_user
153
- helper_method :cluster_organization
154
- helper_method :cluster_clusters
155
- helper_method :cluster_scope_filtered
156
- helper_method :user_has_more_than_one_cluster
157
- helper_method :clusters_from
158
- end
159
- if respond_to?(:hide_action)
160
- hide_action :cluster_scope
161
- hide_action :cluster_scope=
162
- hide_action :user_cluster
163
- hide_action :user_cluster=
164
- hide_action :cluster_user
165
- hide_action :cluster_user=
166
- hide_action :cluster_organization
167
- hide_action :cluster_organization=
168
- hide_action :cluster_clusters
169
- hide_action :cluster_clusters=
170
- hide_action :cluster_scope_filtered
171
- hide_action :cluster_scope_filtered=
172
- hide_action :user_has_more_than_one_cluster
173
- hide_action :user_has_more_than_one_cluster=
174
- hide_action :clusters_from
175
- hide_action :clusters_from=
181
+ ##
182
+ # Return a string with cluster attribute separated by separator argument
183
+ # A array of cluster ids can be passed fo filter the result
184
+ #
185
+ def entity_clusters_string_list!(cluster_entities,separator,attribute=:name,allowed_cluster_ids=nil)
186
+ _cluster_entities = cluster_entities.reject(&:nil?)
187
+ if allowed_cluster_ids.is_a?(Array)
188
+ _cluster_entities.select!{|ce| allowed_cluster_ids.include?(ce.cluster_id)}
189
+ end
190
+ _cluster_entities.map do |ce|
191
+ ce.cluster.try(attribute)
192
+ end.compact.join(separator)
176
193
  end
177
- before_action :set_the_lonely_cluster, only: %i[ create update ]
178
- end
179
194
 
180
- def set_the_lonely_cluster
181
- form_model = @form_record ? model_name_from_record_or_class(@form_record).param_key : params[:controller].singularize
182
- parameters = params.require( form_model ) rescue nil
183
- lonely_cluster = parameters.blank? ? false : parameters.permit( :lonely_cluster ).present?
184
- if lonely_cluster
185
- params[form_model][:cluster_id] = cluster_clusters.first.try(:id)
195
+ ##
196
+ # cluster_of! needs a user and a organization. that why we perfomed this logic here
197
+ #
198
+ def user_clusters_string_list!(user,organization,cluster_entities,separator,attribute=:name)
199
+ @clusters_session ||= self.cluster_of!(user,organization)
200
+ self.entity_clusters_string_list!(cluster_entities, separator, attribute, @clusters_session.map(&:id))
186
201
  end
187
202
  end
188
203
 
189
- def cluster_scope(scope,includes=[])
190
- Klastera.cluster_scope!(cluster_user,cluster_organization,scope,includes)
191
- end
192
-
193
204
  def cluster_user
194
205
  current_user
195
206
  end
@@ -198,27 +209,18 @@ module Klastera
198
209
  current_organization
199
210
  end
200
211
 
201
- def cluster_clusters
202
- Klastera.session_clusters(cluster_user,cluster_organization)
203
- end
204
-
205
- def cluster_scope_filtered(scope,cluster_id,includes=[])
206
- Klastera.cluster_scope_filtered!(
207
- scope,
208
- cluster_id,
209
- cluster_user,
210
- cluster_organization,
211
- includes
212
- )
213
- end
214
-
215
212
  def user_has_more_than_one_cluster
216
- @cluster_clusters ||= cluster_clusters
217
- @cluster_clusters.size > 1
213
+ @clusters_session ||= cluster_of_my_own
214
+ @clusters_session.size > 1
218
215
  end
219
216
 
220
- def clusters_from(scope,includes=[])
221
- Klastera.clusters_from!(cluster_user,cluster_organization,scope,includes)
217
+ def set_the_lonely_cluster
218
+ form_model = @form_record ? model_name_from_record_or_class(@form_record).param_key : params[:controller].singularize
219
+ parameters = params.require( form_model ) rescue nil
220
+ lonely_cluster = parameters.blank? ? false : parameters.permit( :lonely_cluster ).present?
221
+ if lonely_cluster
222
+ params[form_model][:cluster_id] = cluster_of_my_own.first.try(:id)
223
+ end
222
224
  end
223
225
 
224
226
  def set_cluster_filter
@@ -234,4 +236,47 @@ module Klastera
234
236
  def cluster_filter_permit_params
235
237
  [ :cluster_id ].concat( ::ClusterFilter.attributes )
236
238
  end
239
+
240
+ def filter_clusterized(active_record_collection,cluster_id)
241
+ Klastera.filter_clusterized!(active_record_collection, cluster_id, cluster_organization)
242
+ end
243
+
244
+ def cluster_scope(scope,cluster_id=nil)
245
+ Klastera.cluster_scope!(scope, cluster_user, cluster_organization, cluster_id)
246
+ end
247
+
248
+ def __cluster_scope(scope,cluster_id=nil,same_scope:false)
249
+ Klastera.__cluster_scope!(scope, cluster_user, cluster_organization, cluster_id, same_scope: same_scope)
250
+ end
251
+
252
+ def cluster_of_my_own
253
+ Klastera.cluster_of!(cluster_user, cluster_organization)
254
+ end
255
+
256
+ def user_clusters_string_list(object_entity,separator,attribute=:name)
257
+ Klastera.user_clusters_string_list!(
258
+ cluster_user,
259
+ cluster_organization,
260
+ object_entity.try(:cluster_entities),
261
+ separator,
262
+ attribute
263
+ )
264
+ end
265
+
266
+ def set_collection_before_group_by_entity(active_record_collection,entity_params,&block)
267
+ Klastera.set_collection_before_group_by_entity!(active_record_collection, params, cluster_organization, &block)
268
+ end
269
+
270
+ included do
271
+ Klastera::KLSTR_HELPERS.each do |action|
272
+ if respond_to?(:helper_method)
273
+ helper_method(action)
274
+ end
275
+ if respond_to?(:hide_action)
276
+ hide_action(helper)
277
+ hide_action("#{helper}=")
278
+ end
279
+ end
280
+ before_action :set_the_lonely_cluster, only: %i[ create update ]
281
+ end
237
282
  end
@@ -1,3 +1,3 @@
1
1
  module Klastera
2
- VERSION = "1.2.3.2"
3
- end
2
+ VERSION = "1.3.1"
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.3.2
4
+ version: 1.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-06-06 00:00:00.000000000 Z
11
+ date: 2020-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -54,7 +54,6 @@ files:
54
54
  - app/controllers/klastera/application_controller.rb
55
55
  - app/controllers/klastera/clusters_controller.rb
56
56
  - app/helpers/klastera/application_helper.rb
57
- - app/helpers/klastera/clusters_helper.rb
58
57
  - app/models/klastera/cluster.rb
59
58
  - app/models/klastera/cluster_entity.rb
60
59
  - app/models/klastera/cluster_filter.rb
@@ -1,4 +0,0 @@
1
- module Klastera
2
- module ClustersHelper
3
- end
4
- end