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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8780a6928f5249deb34be42cac1f52a226113865b9f29ddb12e1a36f018141ed
4
- data.tar.gz: 1d99b709cea49a08533192ad8a1df621d78ea4d4ee59e1c266f06e952e6d8063
3
+ metadata.gz: 7fae7d83f8b939309532cb345a38bc9c4f76bfca455b16ccda6ad17283f6851e
4
+ data.tar.gz: f62a4b00a2c526c47b95464263dcca6de69d880b6095fdba41204fd14eff66b2
5
5
  SHA512:
6
- metadata.gz: 62c72441e00d9edb6c17525453cde71d864389c9342dbe54df3538609814aef0cdcbc3f7c49f49134d526e2da1f99ff1cc258b750cd024a7031afd3a80977d2d
7
- data.tar.gz: 806978a3fae34b0a2ddda331ce37b90de2e66f9c77bd707859b721ac7c83980f706b5eb432a00875db8b26b4c8901b11307ef62ab063952d013eef30eca69772
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.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
@@ -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.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,5 +1,5 @@
1
1
  <% if cluster_organization.is_in_cluster_mode? %>
2
- <% @cluster_clusters = cluster_clusters %>
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 @cluster_clusters.map(&:id).include?(cluster_entity.try(:object).try(:cluster_id)) %>
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>
@@ -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
- cluster_scope cluster_clusters cluster_scope_filtered clusters_from
11
- user_clusters_string_list set_collection_before_group_by_entity
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 << :unclustered if cluster_organization.is_in_cluster_mode?
98
- active_record_collection = Klastera.filter_clusterized_collection_with!(
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
- cluster_organization
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
- # Returns a ::Cluster::ActiveRecord_Relation from a given scope
110
- #
111
- def clusters_from!(scope,user,organization)
112
- cluster_scope!(scope,user,organization).related_clusters
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
- # Returns a scope filtered by clusters or its
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
- session_clusters(user,organization) do |clusters|
122
- if organization.is_in_cluster_mode?
123
- scope_klass = scope_klass.select("DISTINCT ON (#{scope.table_name}.id) #{scope.table_name}.id, #{scope.table_name}.*")
124
- cluster_ids = clusters.map(&:id)
125
- if organization.required_suborganization_mode?
126
- scope_klass = scope_klass.joins(:cluster_entities).where( cluster_entities: { cluster_id: cluster_ids } )
127
- else
128
- or_these_cluster_ids = cluster_ids.present? ? " OR cluster_entities.cluster_id IN (#{cluster_ids.join(",")})" : ""
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 cluster array if organization is using the cluster mode
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
- def session_clusters(user,organization)
161
- clusters = organization.is_in_cluster_mode? ? ::ClusterUser.clusters_from(user,organization) : []
162
- if block_given?
163
- clusters = clusters.reject{|c|c.id.blank?}
164
- yield(clusters)
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
- clusters
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
- # cluster_clusters needs the logged user and its organization
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
- @_session_clusters ||= self.session_clusters(user,organization)
189
- self.entity_clusters_string_list!(cluster_entities, separator, attribute, @_session_clusters.map(&:id))
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
- @cluster_clusters ||= cluster_clusters
203
- @cluster_clusters.size > 1
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] = cluster_clusters.first.try(: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 filter_clusterized_collection_with(cluster_id,active_record_collection)
230
- Klastera.filter_clusterized_collection_with!(cluster_id,active_record_collection,cluster_organization)
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 cluster_clusters
238
- Klastera.session_clusters(cluster_user,cluster_organization)
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 cluster_scope_filtered(scope,cluster_id)
242
- Klastera.cluster_scope_filtered!(
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 clusters_from(scope)
251
- Klastera.clusters_from!(scope,cluster_user,cluster_organization)
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,&block)
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
@@ -1,3 +1,3 @@
1
1
  module Klastera
2
- VERSION = "1.2.4.2"
2
+ VERSION = "1.3.3.1"
3
3
  end
@@ -13,6 +13,11 @@ namespace :klastera do
13
13
  klass = args.entity.constantize
14
14
  ActiveRecord::Base.transaction do
15
15
  klass.where.not(cluster_id: nil).each do |entity|
16
+ if entity.cluster.blank?
17
+ puts "Cluster ID #{entity.cluster_id} was not found!"
18
+ puts "skip..."
19
+ next
20
+ end
16
21
  Klastera::ClusterEntity.create(entity: entity, cluster: entity.cluster)
17
22
  end
18
23
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: klastera
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.4.2
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-06-15 00:00:00.000000000 Z
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.1.2
130
+ rubygems_version: 3.0.8
131
131
  signing_key:
132
132
  specification_version: 4
133
133
  summary: Clusterization Engine