3scale/porta

View on GitHub
app/models/metric.rb

Summary

Maintainability
B
4 hrs
Test Coverage
# TODO: parameter name was deprecated on 21.02.2014 and should be removed at one point.
Class `Metric` has 32 methods (exceeds 20 allowed). Consider refactoring.
Metric has at least 27 methods
class Metric < ApplicationRecord
include Backend::ModelExtensions::Metric
include SystemName
include BackendApiLogic::MetricExtension
include Searchable
 
self.background_deletion = %i[pricing_rules usage_limits plan_metrics proxy_rules]
 
before_destroy :avoid_destruction
before_validation :associate_to_service, :fill_owner
 
# update Service's updated_at when Metric caches for nicer cache keys
belongs_to :service, touch: true, inverse_of: false
belongs_to :owner, polymorphic: true, touch: true, inverse_of: :metrics
 
has_many :line_items, inverse_of: :metric
has_many :pricing_rules, :dependent => :destroy
has_many :usage_limits, :dependent => :destroy
has_many :plan_metrics, :dependent => :destroy
 
has_many :proxy_rules, :dependent => :destroy, inverse_of: :metric
 
audited :allow_mass_assignment => true
has_system_name uniqueness_scope: %i[owner_type owner_id], human_name: :friendly_name
 
acts_as_tree
 
attr_accessible is recommended over attr_protected
attr_protected :service_id, :parent_id, :tenant_id, :audit_ids
validates :unit, presence: true, unless: :child?
validates :friendly_name, uniqueness: { scope: %i[owner_type owner_id], case_sensitive: true }, presence: true
validates :system_name, :unit, :friendly_name, :owner_type, length: { maximum: 255 }
validates :owner, presence: true
 
validate :only_hits_has_children
 
# alias_attribute :name, :system_name
 
deprecate service: "refer to #owner",
service_id: "use #owner_type and #owner_id",
deprecator: ThreeScale::Deprecation::Deprecator.new
 
# After add the index the results for .first changed.
# http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html
#
# Rails 3 may not order this query by the primary key and the order will
# depend on the database implementation. In order to ensure that behavior, use User.order(:id).first instead.
#
default_scope -> { order(:id) }
scope :top_level, -> { where(parent_id: nil) }
scope :order_by_unit, -> { order('unit') }
 
scope :by_provider, ->(provider) {
where.has { ((owner_type == 'Service') & owner_id.in(provider.services.pluck(:id))) | ((owner_type == 'BackendApi') & owner_id.in(provider.backend_apis.pluck(:id))) }
}
 
# Create one of the predefined, default metrics.
#
# == Arguments
#
# +type+:: Which default metric to create. Currently only :hits are supported.
Metric#self.create_default! has unused parameter 'type'
def self.create_default!(type, attributes = {})
service_id = attributes.delete(:service_id)
metric = new(attributes.merge(:friendly_name => 'Hits', :system_name => 'hits', :unit => 'hit',
:description => 'Number of API hits'))
metric.service_id = service_id
metric.save!
metric
end
 
def self.ids_indexed_by_names
all.index_by(&:system_name).downcase_keys.transform_values(&:id)
end
 
def self.ancestors_ids
includes([:parent]).inject({}) do |memo, metric|
ancestors_ids = metric.ancestors.map(&:id)
memo[metric.id] = ancestors_ids unless ancestors_ids.empty?
memo
end
end
 
def self.hits
extended_system_name = self.hits_extended_system_name_as_sql
collection = top_level.where.has { system_name.eq('hits') | system_name.eq(extended_system_name) }.presence || top_level
collection.first
end
 
def self.hits!
hits or raise ActiveRecord::RecordNotFound
end
 
# alias
def name
self.system_name
end
 
# Is this default metric of given type?
#
# == Arguments
#
# +type+:: Only :hits are supported right now.
def default?(type)
if type == :hits
system_name.to_s =~ self.class.hits_extended_system_name_regex
else
system_name && system_name.to_sym == type.to_sym
end
end
 
def hits?
default?(:hits)
end
 
def child?
parent_id.present?
end
 
def parent?
not children.empty?
end
 
def only_hits_has_children
return if !child? || parent.system_name =~ self.class.hits_extended_system_name_regex
errors.add :parent_id, "You can only create methods under 'hits'"
end
 
def unit
child? ? parent.unit : self[:unit]
end
 
def unit=(value)
self[:unit] = value unless child?
end
 
def type
@type ||= method_metric? ? :method : :metric
end
 
alias method_metric? child?
 
Metric#to_xml has approx 11 statements
Method `to_xml` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.
def to_xml(options = {})
xml = options[:builder] || ThreeScale::XML::Builder.new
 
xml.__send__(:method_missing, type) do |xml|
xml.id_ id unless new_record?
xml.name name # As of February 2014 this is deprecated and should be removed
xml.system_name name
xml.friendly_name friendly_name
xml.service_id service_owner&.id
xml.description description
if method_metric?
xml.metric_id parent_id
else
xml.unit unit
end
end
 
xml.to_xml
end
 
def toggle_visible_for_plan(plan)
plan_metric = find_or_create_plan_metric(plan)
Metric#toggle_visible_for_plan refers to 'plan_metric' more than self (maybe move it to another class?)
plan_metric.toggle :visible
plan_metric.save
end
 
def visible_in_plan?(plan)
if pm = find_plan_metric(plan)
pm.visible?
else # default is ...
true
end
end
 
def toggle_limits_only_text_for_plan(plan)
plan_metric = find_or_create_plan_metric(plan)
Metric#toggle_limits_only_text_for_plan refers to 'plan_metric' more than self (maybe move it to another class?)
plan_metric.toggle :limits_only_text
plan_metric.save
end
 
def limits_only_text_in_plan?(plan)
if pm = find_plan_metric(plan)
pm.limits_only_text?
else # default is ...
true
end
end
 
def toggle_enabled_for_plan(plan)
if enabled_for_plan?(plan)
disable_for_plan plan
else
enable_for_plan plan
end
end
 
def enable_for_plan(plan)
plan.usage_limits.where(value: 0, metric_id: self.id).destroy_all
end
 
def disable_for_plan(plan)
Metric#disable_for_plan calls 'plan.usage_limits' 2 times
used_periods = plan.usage_limits.of_metric(self).pluck(:period).uniq.map(&:to_sym)
unused_periods = UsageLimit::PERIODS - used_periods
 
if unused_periods.present?
plan.usage_limits
.create(period: unused_periods.first, value: 0, metric: self)
else
errors.add :base, :all_periods_used
false
end
end
 
def enabled_for_plan?(plan)
not plan.usage_limits.zero.of_metric(self).exists?
end
 
def disabled_for_plan?(plan)
!enabled_for_plan?(plan)
end
 
private
 
def destroyable?
return true if destroyed_by_association
 
errors.add :base, :cannot_delete_in_use, type: type.to_s.capitalize if belongs_to_latest_config?
errors.add :base, :cannot_delete_hits if system_name == 'hits'
 
errors.empty?
end
 
def belongs_to_latest_config?
if backend_api_metric?
owner.services.map { |service| service.proxy.metric_in_latest_configs?(id) }.any?
else
owner.proxy&.metric_in_latest_configs?(id)
end
end
 
def avoid_destruction
throw :abort unless destroyable?
end
 
def find_or_create_plan_metric(plan)
plan_metric = find_plan_metric(plan)
plan_metric ||= plan.plan_metrics.build :metric => self, :plan => plan
end
 
def find_plan_metric(plan)
plan_metrics = plan.plan_metrics
metric = nil
 
if plan_metrics.loaded?
metric = (@_cached_plan_metrics ||= plan_metrics.to_a)
Metric#find_plan_metric calls 'self.id' 2 times
.find{ |plan_metric| plan_metric.metric_id == self.id }
end
# in case it is not cached, try db search
metric ||= plan_metrics.find_by_metric_id(self.id)
end
 
# keep service_id attribute in sync with parent before we remove all its usages
def associate_to_service
ThreeScale::Deprecation.silence do
if parent
self.service = parent.service
elsif owner.is_a? Service
self.service = owner
end
end
end
 
protected
 
delegate :provider_id_for_audits, :to => :service_owner, :allow_nil => true
 
def fill_owner
return true if owner_type?
 
if new_record? && child?
self.owner = parent.owner
else
ThreeScale::Deprecation.warn "Metrics should be created by specifying owner_id and owner_type instead of service_id"
self.owner_id = service_id
self.owner_type = 'Service'
end
end
 
def service_owner
backend_api_metric? ? nil : owner
end
end