Module: Klastera

Extended by:
ActiveSupport::Concern
Defined in:
lib/klastera.rb,
lib/klastera/engine.rb,
lib/klastera/version.rb,
app/models/klastera/cluster.rb,
app/models/klastera/transfer.rb,
app/models/klastera/cluster_user.rb,
app/models/klastera/cluster_entity.rb,
app/models/klastera/cluster_filter.rb,
app/helpers/klastera/application_helper.rb,
app/controllers/klastera/clusters_controller.rb,
app/controllers/klastera/application_controller.rb

Defined Under Namespace

Modules: ApplicationHelper Classes: ApplicationController, Cluster, ClusterEntity, ClusterFilter, ClusterUser, ClustersController, Engine, Transfer

Constant Summary collapse

UNCLUSTERED_POSITION =
9999
UNCLUSTERED_ENTITY =
'without_cluster'.freeze
KLSTR_HELPERS =
%i[ cluster_user cluster_organization cluster_list cluster_scope cluster_scope_through_of user_clusters_string_list cluster_scope_left_join ].freeze
VERSION =
"1.5.4"

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.cluster_list!(organization, user, include_unclustered = true) ⇒ Object

Returns which clusters a user can see avoiding unnecessary queries if the cluster restraint doesn’t apply



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/klastera.rb', line 28

def cluster_list!(organization,user,include_unclustered=true)
  # Only the cluster mode on and the mandatory user-cluster relation will use a join clause to get the clusters list
  if organization.is_in_cluster_mode? && user.cannot_skip_cluster_clause?
    active_record_collection = ::ClusterUser.clusters_of(organization,user)
  else
    active_record_collection = ::Cluster.where({ organization_id: organization }).order(order: :asc)
  end

  active_record_collection = active_record_collection.order(order: :asc)

  if include_unclustered && organization.optional_suborganization_mode? # For show and use modes only
    active_record_collection.to_a.append(
      ::Cluster.new({nid: UNCLUSTERED_ENTITY, name: I18n.t("klastera.#{UNCLUSTERED_ENTITY}"), order: UNCLUSTERED_POSITION })
    )
  end
  active_record_collection
end

.cluster_scope!(scope_klass, user, organization, cluster_filter = nil, force_cluster_clause = false) ⇒ Object

The cleanest and fast way to clusterize a entity!



107
108
109
110
111
112
113
114
115
# File 'lib/klastera.rb', line 107

def cluster_scope!(scope_klass, user, organization, cluster_filter=nil, force_cluster_clause=false)
  scope = scope_class(scope_klass)
  should_clusterize_scope?(user,organization,cluster_filter,force_cluster_clause) do |should,cluster_ids|
    if should
      scope = scope.eager_load(:organization,cluster_entities: :cluster).where( cluster_entities: { cluster_id: cluster_ids } )
    end
  end
  scope.where(organization_id: organization)
end

.cluster_scope_left_join!(scope_klass, organization) ⇒ Object

A helper that returns a CLUSTER SCOPE to build queries that need explicit LEFT OUTER JOIN clause, instead of the default INNER JOIN provide by ActiveRecord’s joins method



171
172
173
174
175
176
177
178
179
# File 'lib/klastera.rb', line 171

def cluster_scope_left_join!(scope_klass,organization)
  cluster_entities_arel_table = Klastera::ClusterEntity.arel_table
  cluster_arel_table = ::Cluster.arel_table
  cluster_entities_cluster = cluster_entities_arel_table.join(cluster_arel_table, Arel::Nodes::OuterJoin).on(
    cluster_entities_arel_table[:cluster_id].eq(cluster_arel_table[:id]),
  ).join_sources

  scope_class(scope_klass).where(organization_id: organization).joins(Klastera::ClusterEntity.left_join_sources_of(scope_klass)).joins(cluster_entities_cluster)
end

.cluster_scope_through_of!(relation, cluster_entity_klass, scope_klass, user, organization, cluster_filter = nil, force_cluster_clause = false) ⇒ Object

 Filter non-clustered entity through a clusterized one



120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/klastera.rb', line 120

def cluster_scope_through_of!(relation, cluster_entity_klass, scope_klass, user, organization, cluster_filter=nil, force_cluster_clause=false)
  unclusterized_scope = scope_class(scope_klass)

  if organization.is_in_cluster_mode? && ( force_cluster_clause || user.cannot_skip_cluster_clause? )
    unclusterized_scope = unclusterized_scope.joins(relation).joins(Klastera::ClusterEntity.left_join_sources_of(cluster_entity_klass))
  end

  if scope_klass.respond_to?(:organization)
    unclusterized_scope = unclusterized_scope.where(organization_id: organization)
  end

  unclusterized_scope.where("#{relation}_id" => cluster_scope!(cluster_entity_klass, user, organization, cluster_filter, force_cluster_clause))
end

.entity_clusters_string_list!(cluster_entities, separator, attribute = :name, allowed_cluster_ids = nil) ⇒ Object

Return a string with cluster attribute separated by separator argument A array of cluster ids can be passed fo filter the result



50
51
52
53
54
55
56
57
58
# File 'lib/klastera.rb', line 50

def entity_clusters_string_list!(cluster_entities,separator,attribute=:name,allowed_cluster_ids=nil)
  _cluster_entities = cluster_entities.reject(&:nil?)
  if allowed_cluster_ids.is_a?(Array)
    _cluster_entities.select!{|ce| allowed_cluster_ids.include?(ce.cluster_id)}
  end
  _cluster_entities.map do |ce|
    ce.cluster.try(attribute)
  end.compact.sort.join(separator)
end

.group_by_cluster_scope!(scope_klass, user, organization, cluster_filter = [], scope_scopes = []) ⇒ Object

Returns an array with a clusterized scoped result and its grouped version



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/klastera.rb', line 137

def group_by_cluster_scope!(scope_klass, user, organization, cluster_filter=[], scope_scopes=[])
  cluster_ids = cluster_filter.is_a?(Array) ? cluster_filter : [cluster_filter]
  kluster_scope = cluster_scope!(scope_klass, user, organization, cluster_ids.compact, organization.is_in_cluster_mode? )

  scope_scopes.each do |tuple_scope|
    scope_name, scope_arg = tuple_scope
    kluster_scope = scope_arg.present? ? kluster_scope.send(scope_name,scope_arg) : kluster_scope.send(scope_name)
  end

  group_by_block = ->(o) {
    if organization.is_in_cluster_mode?
      o.cluster.present? ? o.cluster.name : UNCLUSTERED_POSITION
    else
      I18n.t("klastera.group_by_cluster_scope.#{scope_klass.model_name.plural}")
    end
  }

  grouped_cluster_scope = kluster_scope.group_by(&group_by_block).sort_by{|k,v|k.to_s}

  grouped_cluster_scope.dup.each do |group|
    if group.first == UNCLUSTERED_POSITION
      grouped_cluster_scope.delete(group)
      group[0] = I18n.t("klastera.#{UNCLUSTERED_ENTITY}")
      grouped_cluster_scope.append(group)
    end
  end

  [ kluster_scope, grouped_cluster_scope ]
end

.scope_class(object) ⇒ Object

TODO: Implement a validation to ensure that  object is a ActiveRecord::Base class (or just try to guess how to retrieve the argument class)



21
22
23
# File 'lib/klastera.rb', line 21

def scope_class(object)
  object
end

.should_clusterize_scope?(user, organization, cluster_filter = nil, force_cluster_clause = false) {|should, cluster_ids| ... } ⇒ Boolean

We will try to avoid cluster clause except when: 1.- cluster mode is active AND

2a.- cluster_filter is present (someone wants to filter by cluster)

OR

2b.- the current user has some limitations and must checks they cluster relation
      - User is having clusters in optional_suborganization mode
      - User IS NOT having clusters in required_suborganization mode

For the other hand, with force_cluster_clause we can skip the previous logic if cluster_filter_id is present when the optional_suborganization mode is on. BUT! Be aware that if the cluster_filter is not present, the value of force_cluster_clause will be overridden by the returned value of cannot_skip_cluster_clause? method.

Yields:

  • (should, cluster_ids)

Returns:

  • (Boolean)


82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/klastera.rb', line 82

def should_clusterize_scope?(user, organization, cluster_filter=nil, force_cluster_clause=false)
  should = false # I don't know if this is a good idea
  if organization.is_in_cluster_mode? && ( cluster_filter.present? || force_cluster_clause = user.cannot_skip_cluster_clause? ) # yes, this is an assignation
    cluster_ids = []
    # Set another variable as array to get the cluster id(s)
    if cluster_filter.present?
      cluster_ids = cluster_filter.is_a?(Array) ? cluster_filter : [cluster_filter]
    elsif force_cluster_clause
      cluster_ids = ::ClusterUser.clusters_of(organization,user).map(&:id)
    end
    # We will avoid the query unless cluster_ids is having values OR force_cluster_clause is set (see method description)
    if cluster_ids.present? || force_cluster_clause
      # We add the unclustered if the value of cluster_filter have the special without_cluster string or as method description says
      if cluster_ids.delete(UNCLUSTERED_ENTITY) || ( force_cluster_clause && organization.optional_suborganization_mode? )
        cluster_ids << nil
      end
      should = true
    end
  end
  yield(should,cluster_ids)
end

.user_clusters_string_list!(user, organization, cluster_entities, separator, attribute = :name) ⇒ Object

cluster_list! needs a user and a organization. that why we perfomed this logic here



63
64
65
66
# File 'lib/klastera.rb', line 63

def user_clusters_string_list!(user,organization,cluster_entities,separator,attribute=:name)
  @clusters_session ||= Klastera.cluster_list!(organization,user)
  self.entity_clusters_string_list!(cluster_entities, separator, attribute, @clusters_session.map(&:id))
end

Instance Method Details

#cluster_list(include_unclustered = true) ⇒ Object



201
202
203
# File 'lib/klastera.rb', line 201

def cluster_list(include_unclustered=true)
  Klastera.cluster_list!(cluster_organization, cluster_user, include_unclustered)
end

#cluster_organizationObject



188
189
190
# File 'lib/klastera.rb', line 188

def cluster_organization
  current_organization
end

#cluster_scope(scope_klass, cluster_filter = nil, force_cluster_clause = false) ⇒ Object



205
206
207
# File 'lib/klastera.rb', line 205

def cluster_scope(scope_klass, cluster_filter=nil, force_cluster_clause=false)
  Klastera.cluster_scope!(scope_klass, cluster_user, cluster_organization, cluster_filter, force_cluster_clause)
end

#cluster_scope_left_join(scope_klass) ⇒ Object



221
222
223
# File 'lib/klastera.rb', line 221

def cluster_scope_left_join(scope_klass)
  Klastera.cluster_scope_left_join!(scope_klas,cluster_organization)
end

#cluster_scope_through_of(relation, cluster_entity_klass, scope_klass, cluster_filter = nil, force_cluster_clause = false) ⇒ Object



209
210
211
# File 'lib/klastera.rb', line 209

def cluster_scope_through_of(relation, cluster_entity_klass, scope_klass, cluster_filter=nil, force_cluster_clause=false)
  Klastera.cluster_scope_through_of!(relation, cluster_entity_klass, scope_klass, cluster_user, cluster_organization, cluster_filter, force_cluster_clause)
end

#cluster_userObject



184
185
186
# File 'lib/klastera.rb', line 184

def cluster_user
  current_user
end

#group_by_cluster_scope(scope_klass, cluster_filter = [], scope_scopes = []) ⇒ Object



213
214
215
# File 'lib/klastera.rb', line 213

def group_by_cluster_scope(scope_klass, cluster_filter=[], scope_scopes=[])
  Klastera.group_by_cluster_scope!(scope_klass, cluster_user, cluster_organization, cluster_filter, scope_scopes)
end

#set_cluster_filterObject



192
193
194
195
196
197
198
199
# File 'lib/klastera.rb', line 192

def set_cluster_filter
  cluster_filter_params = params.require(:cluster_filter) rescue {}
  @cluster_filter = ::ClusterFilter.new(
    cluster_filter_params.present? ? cluster_filter_params.permit(
      [ :cluster_id ].concat( ::ClusterFilter.attributes )
    ) : {}
  )
end

#user_clusters_string_list(object_entity, separator, attribute = :name) ⇒ Object



217
218
219
# File 'lib/klastera.rb', line 217

def user_clusters_string_list(object_entity, separator, attribute=:name)
  Klastera.user_clusters_string_list!(cluster_user, cluster_organization, object_entity.try(:cluster_entities), separator, attribute)
end