refactor: extract custom attribute methods from FilterService (#13743)
- Extracted 6 custom attribute methods (`custom_attribute_query`, `attribute_model`, `attribute_data_type`, `build_custom_attr_query`, `custom_attribute`, `not_in_custom_attr_query`) into a new `Filters::CustomAttributeFilterHelper` module. - Added an inline `rubocop:disable` for the intentional `Lint/ShadowedException` in `coerce_lt_gt_value` — `Date::Error` is a subclass of `ArgumentError`, but both are listed explicitly for clarity. ## Why `app/services/filters/` The existing `Filters::FilterHelper` lives in `app/helpers/filters/`, but that location triggers `Rails/HelperInstanceVariable` for any module that uses instance variables. The extracted methods share state with `FilterService` via instance variables (`@attribute_key`, `@account`, `@custom_attribute`, etc.), so placing them in `app/helpers/` would require a cop disable. `app/services/filters/` is a better fit because: - The module is a service mixin, not a view helper — it's only included by `FilterService` and its subclasses (`Conversations::FilterService`, `Contacts::FilterService`, `AutomationRules::ConditionsFilterService`). - It sits alongside the services that use it. - No cop disables needed. --------- Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com>
This commit is contained in:
@@ -2,6 +2,7 @@ require 'json'
|
||||
|
||||
class FilterService
|
||||
include Filters::FilterHelper
|
||||
include Filters::CustomAttributeFilterHelper
|
||||
include CustomExceptions::CustomFilter
|
||||
|
||||
ATTRIBUTE_MODEL = 'conversation_attribute'.freeze
|
||||
@@ -137,26 +138,8 @@ class FilterService
|
||||
end
|
||||
end
|
||||
|
||||
def custom_attribute_query(query_hash, custom_attribute_type, current_index)
|
||||
@attribute_key = query_hash[:attribute_key]
|
||||
@custom_attribute_type = custom_attribute_type
|
||||
attribute_data_type
|
||||
return '' if @custom_attribute.blank?
|
||||
|
||||
build_custom_attr_query(query_hash, current_index)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def attribute_model
|
||||
@attribute_model = @custom_attribute_type.presence || self.class::ATTRIBUTE_MODEL
|
||||
end
|
||||
|
||||
def attribute_data_type
|
||||
attribute_type = custom_attribute(@attribute_key, @account, attribute_model).try(:attribute_display_type)
|
||||
@attribute_data_type = self.class::ATTRIBUTE_TYPES[attribute_type]
|
||||
end
|
||||
|
||||
def standard_attribute_data_type(attribute_key)
|
||||
@filters.each_value do |section|
|
||||
return section.dig(attribute_key, 'data_type') if section.is_a?(Hash) && section.key?(attribute_key)
|
||||
@@ -173,44 +156,10 @@ class FilterService
|
||||
else
|
||||
raise CustomExceptions::CustomFilter::InvalidValue.new(attribute_name: attribute_key)
|
||||
end
|
||||
rescue Date::Error, ArgumentError, FloatDomainError, TypeError
|
||||
rescue ArgumentError, FloatDomainError, TypeError
|
||||
raise CustomExceptions::CustomFilter::InvalidValue.new(attribute_name: attribute_key)
|
||||
end
|
||||
|
||||
def build_custom_attr_query(query_hash, current_index)
|
||||
filter_operator_value = filter_operation(query_hash, current_index)
|
||||
query_operator = query_hash[:query_operator]
|
||||
table_name = attribute_model == 'conversation_attribute' ? 'conversations' : 'contacts'
|
||||
|
||||
query = if attribute_data_type == 'text'
|
||||
ActiveRecord::Base.sanitize_sql_array(
|
||||
["LOWER(#{table_name}.custom_attributes ->> ?)::#{attribute_data_type} #{filter_operator_value} #{query_operator} ", @attribute_key]
|
||||
)
|
||||
else
|
||||
ActiveRecord::Base.sanitize_sql_array(
|
||||
["(#{table_name}.custom_attributes ->> ?)::#{attribute_data_type} #{filter_operator_value} #{query_operator} ", @attribute_key]
|
||||
)
|
||||
end
|
||||
|
||||
query + not_in_custom_attr_query(table_name, query_hash, attribute_data_type)
|
||||
end
|
||||
|
||||
def custom_attribute(attribute_key, account, custom_attribute_type)
|
||||
current_account = account || Current.account
|
||||
attribute_model = custom_attribute_type.presence || self.class::ATTRIBUTE_MODEL
|
||||
@custom_attribute = current_account.custom_attribute_definitions.where(
|
||||
attribute_model: attribute_model
|
||||
).find_by(attribute_key: attribute_key)
|
||||
end
|
||||
|
||||
def not_in_custom_attr_query(table_name, query_hash, attribute_data_type)
|
||||
return '' unless query_hash[:filter_operator] == 'not_equal_to'
|
||||
|
||||
ActiveRecord::Base.sanitize_sql_array(
|
||||
[" OR (#{table_name}.custom_attributes ->> ?)::#{attribute_data_type} IS NULL ", @attribute_key]
|
||||
)
|
||||
end
|
||||
|
||||
def equals_to_filter_string(filter_operator, current_index)
|
||||
return "IN (:value_#{current_index})" if filter_operator == 'equal_to'
|
||||
|
||||
|
||||
55
app/services/filters/custom_attribute_filter_helper.rb
Normal file
55
app/services/filters/custom_attribute_filter_helper.rb
Normal file
@@ -0,0 +1,55 @@
|
||||
module Filters::CustomAttributeFilterHelper
|
||||
def custom_attribute_query(query_hash, custom_attribute_type, current_index)
|
||||
@attribute_key = query_hash[:attribute_key]
|
||||
@custom_attribute_type = custom_attribute_type
|
||||
attribute_data_type
|
||||
return '' if @custom_attribute.blank?
|
||||
|
||||
build_custom_attr_query(query_hash, current_index)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def attribute_model
|
||||
@attribute_model = @custom_attribute_type.presence || self.class::ATTRIBUTE_MODEL
|
||||
end
|
||||
|
||||
def attribute_data_type
|
||||
attribute_type = custom_attribute(@attribute_key, @account, attribute_model).try(:attribute_display_type)
|
||||
@attribute_data_type = self.class::ATTRIBUTE_TYPES[attribute_type]
|
||||
end
|
||||
|
||||
def build_custom_attr_query(query_hash, current_index)
|
||||
filter_operator_value = filter_operation(query_hash, current_index)
|
||||
query_operator = query_hash[:query_operator]
|
||||
table_name = attribute_model == 'conversation_attribute' ? 'conversations' : 'contacts'
|
||||
|
||||
query = if attribute_data_type == 'text'
|
||||
ActiveRecord::Base.sanitize_sql_array(
|
||||
["LOWER(#{table_name}.custom_attributes ->> ?)::#{attribute_data_type} #{filter_operator_value} #{query_operator} ", @attribute_key]
|
||||
)
|
||||
else
|
||||
ActiveRecord::Base.sanitize_sql_array(
|
||||
["(#{table_name}.custom_attributes ->> ?)::#{attribute_data_type} #{filter_operator_value} #{query_operator} ", @attribute_key]
|
||||
)
|
||||
end
|
||||
|
||||
query + not_in_custom_attr_query(table_name, query_hash, attribute_data_type)
|
||||
end
|
||||
|
||||
def custom_attribute(attribute_key, account, custom_attribute_type)
|
||||
current_account = account || Current.account
|
||||
attribute_model = custom_attribute_type.presence || self.class::ATTRIBUTE_MODEL
|
||||
@custom_attribute = current_account.custom_attribute_definitions.where(
|
||||
attribute_model: attribute_model
|
||||
).find_by(attribute_key: attribute_key)
|
||||
end
|
||||
|
||||
def not_in_custom_attr_query(table_name, query_hash, attribute_data_type)
|
||||
return '' unless query_hash[:filter_operator] == 'not_equal_to'
|
||||
|
||||
ActiveRecord::Base.sanitize_sql_array(
|
||||
[" OR (#{table_name}.custom_attributes ->> ?)::#{attribute_data_type} IS NULL ", @attribute_key]
|
||||
)
|
||||
end
|
||||
end
|
||||
3
app/views/fields/confirmed_at_field/_show.html.erb
Normal file
3
app/views/fields/confirmed_at_field/_show.html.erb
Normal file
@@ -0,0 +1,3 @@
|
||||
<% if field.data %>
|
||||
<%= field.datetime %>
|
||||
<% end %>
|
||||
Reference in New Issue
Block a user