require "klastera/engine" module Klastera mattr_accessor :organization_class extend ActiveSupport::Concern class << self def set_cluster_entities_attributes!(entity,array_cluster_ids) cluster_entities_attributes = {} entity_cluster_entities = entity.try(:cluster_entities) || [] entity_clusters = entity_cluster_entities.map(&:cluster_id).sort array_cluster_ids = array_cluster_ids.map(&:id).sort if array_cluster_ids != entity_clusters now = DateTime.now entity_cluster_entities.each_with_index do |ec,index| remove_cluster = true if array_cluster_ids.include?(ec.cluster_id) remove_cluster = false array_cluster_ids.delete(ec.cluster_id) end cluster_entities_attributes[index] = { id: ec.id, cluster_id: ec.cluster_id, _destroy: remove_cluster } end array_cluster_ids.each_with_index do |cluster_id,index| cluster_entities_attributes[now.to_i+index] = { cluster_id: cluster_id, _destroy: false } end end cluster_entities_attributes end # # I like to wrap methods # def cluster_scope_filtered!(scope,cluster_id,user,organization,includes=[]) self.filter_clusterized_collection_with!( organization, cluster_id, self.cluster_scope!(user,organization,scope,includes) ) end # # In order to this works, active_record_collection argument # should be passed through cluster_scope! method before. # def filter_clusterized_collection_with!(cluster_organization,cluster_id,active_record_collection) if cluster_organization.is_in_cluster_mode? if cluster_id.present? cluster_array = [cluster_id] # Based on force/use/show definition we don't really need this validation. # A force cluster organization won't return an entity out of a cluster, but # we don't know if the clusterized entities are fully definition-compliant. cluster_array << nil if cluster_organization.optional_suborganization_mode? active_record_collection = active_record_collection.joins(:cluster_entities).where("cluster_entities.cluster_id": cluster_array) end # you may use a block only with clusterizable data yield(active_record_collection) if block_given? end active_record_collection end # # # def set_collection_before_group_by_entity!(cluster_organization,active_record_collection,model_class,entity_params) entity_params_keys = [:entity_name,:entity_attribute,:entity_id,:entity_id_attribute,:unamed] entity_params = entity_params.slice(*entity_params_keys).values if model_class.try(:reflections).try(:keys).try(:include?,entity_params[0]) entity_params << "#{entity_params[0]}_id".to_sym if entity_params[0] == 'cluster' entity_params << :unclustered if cluster_organization.is_in_cluster_mode? active_record_collection = Klastera.filter_clusterized_collection_with!( cluster_organization, entity_params[2], active_record_collection ) end yield( active_record_collection, entity_params_keys.zip(entity_params).to_h) end end ## # Returns a ::Cluster::ActiveRecord_Relation from a given scope # def clusters_from!(user,organization,scope,includes=[]) cluster_scope!(user,organization,scope,includes).related_clusters end ## # Returns a scope filtered by clusters or its # organization if the cluster mode is not active. # def cluster_scope!(user,organization,scope,includes=[]) scope_klass = scope_class(scope).where(organization_id: organization) session_clusters(user,organization) do |clusters| if organization.is_in_cluster_mode? scope_klass = scope_klass.select("DISTINCT ON (#{scope.table_name}.id) #{scope.table_name}.id, #{scope.table_name}.*") scope_klass = scope_klass.includes(includes) if includes.present? cluster_ids = clusters.map(&:id) if organization.required_suborganization_mode? scope_klass = scope_klass.joins(:cluster_entities).where( cluster_entities: { cluster_id: cluster_ids } ) else or_these_cluster_ids = cluster_ids.present? ? " OR cluster_entities.cluster_id IN (#{cluster_ids.join(",")})" : "" scope_klass = scope_klass.joins(" LEFT OUTER JOIN cluster_entities ON entity_id = #{scope.table_name}.id AND entity_type = '#{scope}' ").where("cluster_entities.id IS NULL#{or_these_cluster_ids}") end # Provisional fix to avoid SQL clashes due to DISTINCT ON clause scope_klass = scope_class(scope).where(organization_id: organization).where(id: scope_klass.map(&:id)) end end scope_klass end ## # TODO: # Implement a validation to ensure that # object is a ActiveRecord::Base class # (or just try to guess how to retrieve the argument class) # ## def scope_class(object) object end ## # Returns a cluster array if organization is using the cluster mode # # Use this only to get current_user/organization clusters # understanding this wont be useful out of a cluster mode context. # ## def session_clusters(user,organization) clusters = organization.is_in_cluster_mode? ? ::ClusterUser.clusters_from(user,organization) : [] if block_given? clusters = clusters.reject{|c|c.id.blank?} yield(clusters) end clusters end end included do if respond_to?(:helper_method) helper_method :cluster_scope helper_method :user_cluster helper_method :cluster_user helper_method :cluster_organization helper_method :cluster_clusters helper_method :cluster_scope_filtered helper_method :user_has_more_than_one_cluster helper_method :clusters_from end if respond_to?(:hide_action) hide_action :cluster_scope hide_action :cluster_scope= hide_action :user_cluster hide_action :user_cluster= hide_action :cluster_user hide_action :cluster_user= hide_action :cluster_organization hide_action :cluster_organization= hide_action :cluster_clusters hide_action :cluster_clusters= hide_action :cluster_scope_filtered hide_action :cluster_scope_filtered= hide_action :user_has_more_than_one_cluster hide_action :user_has_more_than_one_cluster= hide_action :clusters_from hide_action :clusters_from= end before_action :set_the_lonely_cluster, only: %i[ create update ] end def set_the_lonely_cluster form_model = @form_record ? model_name_from_record_or_class(@form_record).param_key : params[:controller].singularize parameters = params.require( form_model ) rescue nil lonely_cluster = parameters.blank? ? false : parameters.permit( :lonely_cluster ).present? if lonely_cluster params[form_model][:cluster_id] = cluster_clusters.first.try(:id) end end def cluster_scope(scope,includes=[]) Klastera.cluster_scope!(cluster_user,cluster_organization,scope,includes) end def cluster_user current_user end def cluster_organization current_organization end def cluster_clusters Klastera.session_clusters(cluster_user,cluster_organization) end def cluster_scope_filtered(scope,cluster_id,includes=[]) Klastera.cluster_scope_filtered!( scope, cluster_id, cluster_user, cluster_organization, includes ) end def user_has_more_than_one_cluster @cluster_clusters ||= cluster_clusters @cluster_clusters.size > 1 end def clusters_from(scope,includes=[]) Klastera.clusters_from!(cluster_user,cluster_organization,scope,includes) end def set_cluster_filter @filter = ::ClusterFilter.new(cluster_filter_params) end def cluster_filter_params parameters = params.require(:cluster_filter) rescue nil return {} if parameters.blank? parameters.permit(*cluster_filter_permit_params) end def cluster_filter_permit_params [ :cluster_id ].concat( ::ClusterFilter.attributes ) end end