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.5.1"
Class Method Summary collapse
-
.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.
-
.cluster_scope!(scope_klass, user, organization, cluster_filter = nil, force_cluster_clause = false) ⇒ Object
The cleanest and fast way to clusterize a entity!.
-
.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.
-
.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.
-
.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 Optimized version that uses preloaded associations when available.
-
.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.
-
.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).
-
.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.
-
.user_clusters_string_list!(user, organization, cluster_entities, separator, attribute = :name) ⇒ Object
cluster_list! needs a user and a organization.
Instance Method Summary collapse
- #cluster_list(include_unclustered = true) ⇒ Object
- #cluster_organization ⇒ Object
- #cluster_scope(scope_klass, cluster_filter = nil, force_cluster_clause = false) ⇒ Object
- #cluster_scope_left_join(scope_klass) ⇒ Object
- #cluster_scope_through_of(relation, cluster_entity_klass, scope_klass, cluster_filter = nil, force_cluster_clause = false) ⇒ Object
- #cluster_user ⇒ Object
- #group_by_cluster_scope(scope_klass, cluster_filter = [], scope_scopes = []) ⇒ Object
- #set_cluster_filter ⇒ Object
- #user_clusters_string_list(object_entity, separator, attribute = :name) ⇒ Object
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!
140 141 142 143 144 145 146 147 148 |
# File 'lib/klastera.rb', line 140 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
204 205 206 207 208 209 210 211 212 |
# File 'lib/klastera.rb', line 204 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
153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/klastera.rb', line 153 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 Optimized version that uses preloaded associations when available
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/klastera.rb', line 51 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 # Performance optimization: Use preloaded data when available if cluster_entities.respond_to?(:loaded?) && cluster_entities.loaded? && _cluster_entities.all? { |ce| ce.association(:cluster).loaded? } # Use in-memory data (much faster) _cluster_entities.map do |ce| ce.cluster.try(attribute) end.compact.sort.join(separator) else # Fallback to original method with database query # But use a single query instead of N+1 cluster_ids = _cluster_entities.filter_map(&:cluster_id).uniq return "" if cluster_ids.empty? records = ::Cluster.where(id: cluster_ids).pluck(:id, attribute) return "" if records.empty? # clusters_hash = if records.any? && !records.first.is_a?(Array) # records.map { |id| [id, id] }.to_h # else # records.to_h # end # _cluster_entities.map do |ce| # clusters_hash[ce.cluster_id] # end.compact.sort.join(separator) clusters_hash = records.first.is_a?(Array) ? records.to_h : records.index_by(&:itself) _cluster_entities .filter_map { |entity| clusters_hash[entity.cluster_id] } .uniq .sort .join(separator) end 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
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/klastera.rb', line 170 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.
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/klastera.rb', line 115 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
96 97 98 99 |
# File 'lib/klastera.rb', line 96 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
234 235 236 |
# File 'lib/klastera.rb', line 234 def cluster_list(include_unclustered=true) Klastera.cluster_list!(cluster_organization, cluster_user, include_unclustered) end |
#cluster_organization ⇒ Object
221 222 223 |
# File 'lib/klastera.rb', line 221 def cluster_organization current_organization end |
#cluster_scope(scope_klass, cluster_filter = nil, force_cluster_clause = false) ⇒ Object
238 239 240 |
# File 'lib/klastera.rb', line 238 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
254 255 256 |
# File 'lib/klastera.rb', line 254 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
242 243 244 |
# File 'lib/klastera.rb', line 242 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_user ⇒ Object
217 218 219 |
# File 'lib/klastera.rb', line 217 def cluster_user current_user end |
#group_by_cluster_scope(scope_klass, cluster_filter = [], scope_scopes = []) ⇒ Object
246 247 248 |
# File 'lib/klastera.rb', line 246 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_filter ⇒ Object
225 226 227 228 229 230 231 232 |
# File 'lib/klastera.rb', line 225 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
250 251 252 |
# File 'lib/klastera.rb', line 250 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 |