klastera 1.2.3.2 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
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