diff --git a/app/models/application_model.rb b/app/models/application_model.rb index c3d267e5f..cc60a294f 100644 --- a/app/models/application_model.rb +++ b/app/models/application_model.rb @@ -17,6 +17,7 @@ class ApplicationModel < ActiveRecord::Base include ApplicationModel::HasExternalSync include ApplicationModel::ChecksImport include ApplicationModel::CanTouchReferences + include ApplicationModel::CanQueryCaseInsensitiveWhereOrSql self.abstract_class = true end diff --git a/app/models/application_model/can_query_case_insensitive_where_or_sql.rb b/app/models/application_model/can_query_case_insensitive_where_or_sql.rb new file mode 100644 index 000000000..5f40757c8 --- /dev/null +++ b/app/models/application_model/can_query_case_insensitive_where_or_sql.rb @@ -0,0 +1,43 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ +module ApplicationModel::CanQueryCaseInsensitiveWhereOrSql + extend ActiveSupport::Concern + + included do + + # Builds a case insenstive WHERE ... OR ... SQL query. + # + # @see .or_cis + # + # @example + # Organization.where_or_cis(%i[name note], "%zammad%").to_sql + # #=> "SELECT `organizations`.* FROM `organizations` WHERE (`organizations`.`name` LIKE '%zammad%' OR `organizations`.`note` LIKE '%zammad%')" + # + # @return [ActiveRecord::Relation] the ActiveRecord relation that can be combined or executed + scope :where_or_cis, ->(*args) { where(or_cis(*args)) } + end + + # methods defined here are going to extend the class, not the instance of it + class_methods do + + # Builds a case insenstive OR SQL grouping. This comes in handy for join queries. + # For direct WHERE queries prefer .where_or_cis scope. + # + # @param [Array] attributes the attributes that should get queried case insensitive. + # @param [String] query the SQL query that should be used for each given attribute. + # + # @example + # Organization.joins(:users).where(User.or_cis(%i[firstname lastname email], "%#{query}%")) + # + # @return [Arel::Nodes::Grouping] can be passed to ActiveRecord queries + def or_cis(attributes, query) + # use Arel to create an Array of case insenstive + # LIKE queries based on the current DB adapter + cis_matches = attributes.map do |attribute| + arel_table[attribute].matches(query) + end + + # return the by OR joined Arel queries + cis_matches.inject(:or) + end + end +end diff --git a/app/models/organization/search.rb b/app/models/organization/search.rb index 27fc92b50..08d38dd65 100644 --- a/app/models/organization/search.rb +++ b/app/models/organization/search.rb @@ -77,9 +77,11 @@ returns # fallback do sql query # - stip out * we already search for *query* - query.delete! '*' - organizations = Organization.where( - 'name LIKE ? OR note LIKE ?', "%#{query}%", "%#{query}%" - ).order('name').offset(offset).limit(limit).to_a + organizations = Organization.where_or_cis(%i[name note], "%#{query}%") + .order('name') + .offset(offset) + .limit(limit) + .to_a # use result independent of size if an explicit offset is given # this is the case for e.g. paginated searches @@ -87,9 +89,11 @@ returns return organizations if organizations.length > 3 # if only a few organizations are found, search for names of users - organizations_by_user = Organization.select('DISTINCT(organizations.id), organizations.name').joins('LEFT OUTER JOIN users ON users.organization_id = organizations.id').where( - 'users.firstname LIKE ? or users.lastname LIKE ? or users.email LIKE ?', "%#{query}%", "%#{query}%", "%#{query}%" - ).order('organizations.name').limit(limit) + organizations_by_user = Organization.select('DISTINCT(organizations.id), organizations.name') + .joins('LEFT OUTER JOIN users ON users.organization_id = organizations.id') + .where(User.or_cis(%i[firstname lastname email], "%#{query}%")) + .order('organizations.name') + .limit(limit) organizations_by_user.each do |organization_by_user| diff --git a/spec/models/organization_spec.rb b/spec/models/organization_spec.rb new file mode 100644 index 000000000..7cd92fbb2 --- /dev/null +++ b/spec/models/organization_spec.rb @@ -0,0 +1,13 @@ +require 'rails_helper' + +RSpec.describe Organization do + + context '.where_or_cis' do + + it 'finds instance by querying multiple attributes case insensitive' do + # search for Zammad Foundation + organizations = described_class.where_or_cis(%i[name note], '%zammad%') + expect(organizations).not_to be_blank + end + end +end