# frozen_string_literal: true module JSONAPI module ActiveRelationRetrieval def find_related_ids(relationship, options = {}) self.class.find_related_fragments(self, relationship, options).keys.collect { |rid| rid.id } end module ClassMethods # Finds Resources using the `filters`. Pagination and sort options are used when provided # # @param filters [Hash] the filters hash # @option options [Hash] :context The context of the request, set in the controller # @option options [Hash] :sort_criteria The `sort criteria` # @option options [Hash] :include_directives The `include_directives` # # @return [Array<Resource>] the Resource instances matching the filters, sorting and pagination rules. def find(filters, options = {}) sort_criteria = options.fetch(:sort_criteria) { [] } join_manager = ActiveRelation::JoinManager.new(resource_klass: self, filters: filters, sort_criteria: sort_criteria) paginator = options[:paginator] records = apply_request_settings_to_records(records: records(options), sort_criteria: sort_criteria,filters: filters, join_manager: join_manager, paginator: paginator, options: options) resources_for(records, options[:context]) end # Counts Resources found using the `filters` # # @param filters [Hash] the filters hash # @option options [Hash] :context The context of the request, set in the controller # # @return [Integer] the count def count(filters, options = {}) join_manager = ActiveRelation::JoinManager.new(resource_klass: self, filters: filters) records = apply_request_settings_to_records(records: records(options), filters: filters, join_manager: join_manager, options: options) count_records(records) end # Returns the single Resource identified by `key` # # @param key the primary key of the resource to find # @option options [Hash] :context The context of the request, set in the controller def find_by_key(key, options = {}) record = find_record_by_key(key, options) resource_for(record, options[:context]) end # Returns an array of Resources identified by the `keys` array # # @param keys [Array<key>] Array of primary keys to find resources for # @option options [Hash] :context The context of the request, set in the controller def find_by_keys(keys, options = {}) records = find_records_by_keys(keys, options) resources_for(records, options[:context]) end # Returns an array of Resources identified by the `keys` array. The resources are not filtered as this # will have been done in a prior step # # @param keys [Array<key>] Array of primary keys to find resources for # @option options [Hash] :context The context of the request, set in the controller def find_to_populate_by_keys(keys, options = {}) records = records_for_populate(options).where(_primary_key => keys) resources_for(records, options[:context]) end # Finds Resource fragments using the `filters`. Pagination and sort options are used when provided. # Note: This is incompatible with Polymorphic resources (which are going to come from two separate tables) # # @param filters [Hash] the filters hash # @option options [Hash] :context The context of the request, set in the controller # @option options [Hash] :sort_criteria The `sort criteria` # @option options [Hash] :include_directives The `include_directives` # @option options [Boolean] :cache Return the resources' cache field # # @return [Hash{ResourceIdentity => {identity: => ResourceIdentity, cache: cache_field}] # the ResourceInstances matching the filters, sorting, and pagination rules along with any request # additional_field values def find_fragments(filters, options = {}) include_directives = options.fetch(:include_directives, {}) resource_klass = self fragments = {} linkage_relationships = to_one_relationships_for_linkage(include_directives[:include_related]) sort_criteria = options.fetch(:sort_criteria) { [] } join_manager = ActiveRelation::JoinManager.new(resource_klass: resource_klass, source_relationship: nil, relationships: linkage_relationships.collect(&:name), sort_criteria: sort_criteria, filters: filters) paginator = options[:paginator] records = apply_request_settings_to_records(records: records(options), filters: filters, sort_criteria: sort_criteria, paginator: paginator, join_manager: join_manager, options: options) if options[:cache] # This alias is going to be resolve down to the model's table name and will not actually be an alias resource_table_alias = resource_klass._table_name pluck_fields = [sql_field_with_alias(resource_table_alias, resource_klass._primary_key)] cache_field = attribute_to_model_field(:_cache_field) pluck_fields << sql_field_with_alias(resource_table_alias, cache_field[:name]) linkage_fields = [] linkage_relationships.each do |linkage_relationship| linkage_relationship_name = linkage_relationship.name if linkage_relationship.polymorphic? && linkage_relationship.belongs_to? linkage_relationship.resource_types.each do |resource_type| klass = resource_klass_for(resource_type) linkage_table_alias = join_manager.join_details_by_polymorphic_relationship(linkage_relationship, resource_type)[:alias] primary_key = klass._primary_key linkage_fields << {relationship_name: linkage_relationship_name, resource_klass: klass, field: sql_field_with_alias(linkage_table_alias, primary_key), alias: alias_table_field(linkage_table_alias, primary_key)} pluck_fields << sql_field_with_alias(linkage_table_alias, primary_key) end else klass = linkage_relationship.resource_klass linkage_table_alias = join_manager.join_details_by_relationship(linkage_relationship)[:alias] fail "Missing linkage_table_alias for #{linkage_relationship}" unless linkage_table_alias primary_key = klass._primary_key linkage_fields << {relationship_name: linkage_relationship_name, resource_klass: klass, field: sql_field_with_alias(linkage_table_alias, primary_key), alias: alias_table_field(linkage_table_alias, primary_key)} pluck_fields << sql_field_with_alias(linkage_table_alias, primary_key) end end sort_fields = options.dig(:_relation_helper_options, :sort_fields) sort_fields.try(:each) do |field| pluck_fields << Arel.sql(field) end rows = records.pluck(*pluck_fields) rows.each do |row| rid = JSONAPI::ResourceIdentity.new(resource_klass, pluck_fields.length == 1 ? row : row[0]) fragments[rid] ||= JSONAPI::ResourceFragment.new(rid) attributes_offset = 2 fragments[rid].cache = cast_to_attribute_type(row[1], cache_field[:type]) linkage_fields.each do |linkage_field_details| fragments[rid].initialize_related(linkage_field_details[:relationship_name]) related_id = row[attributes_offset] if related_id related_rid = JSONAPI::ResourceIdentity.new(linkage_field_details[:resource_klass], related_id) fragments[rid].add_related_identity(linkage_field_details[:relationship_name], related_rid) end attributes_offset+= 1 end end if JSONAPI.configuration.warn_on_performance_issues && (rows.length > fragments.length) warn "Performance issue detected: `#{self.name.to_s}.records` returned non-normalized results in `#{self.name.to_s}.find_fragments`." end else linkage_fields = [] linkage_relationships.each do |linkage_relationship| linkage_relationship_name = linkage_relationship.name if linkage_relationship.polymorphic? && linkage_relationship.belongs_to? linkage_relationship.resource_types.each do |resource_type| klass = resource_klass_for(resource_type) linkage_table_alias = join_manager.join_details_by_polymorphic_relationship(linkage_relationship, resource_type)[:alias] primary_key = klass._primary_key select_alias = "jr_l_#{linkage_relationship_name}_#{resource_type}_pk" select_alias_statement = sql_field_with_fixed_alias(linkage_table_alias, primary_key, select_alias) linkage_fields << {relationship_name: linkage_relationship_name, resource_klass: klass, select: select_alias_statement, select_alias: select_alias} end else klass = linkage_relationship.resource_klass linkage_table_alias = join_manager.join_details_by_relationship(linkage_relationship)[:alias] fail "Missing linkage_table_alias for #{linkage_relationship}" unless linkage_table_alias primary_key = klass._primary_key select_alias = "jr_l_#{linkage_relationship_name}_pk" select_alias_statement = sql_field_with_fixed_alias(linkage_table_alias, primary_key, select_alias) linkage_fields << {relationship_name: linkage_relationship_name, resource_klass: klass, select: select_alias_statement, select_alias: select_alias} end end if linkage_fields.any? records = records.select(linkage_fields.collect {|f| f[:select]}) end records = records.select(concat_table_field(_table_name, Arel.star)) resources = resources_for(records, options[:context]) resources.each do |resource| rid = resource.identity fragments[rid] ||= JSONAPI::ResourceFragment.new(rid, resource: resource) linkage_fields.each do |linkage_field_details| fragments[rid].initialize_related(linkage_field_details[:relationship_name]) related_id = resource._model.attributes[linkage_field_details[:select_alias]] if related_id related_rid = JSONAPI::ResourceIdentity.new(linkage_field_details[:resource_klass], related_id) fragments[rid].add_related_identity(linkage_field_details[:relationship_name], related_rid) end end end end fragments end # Finds Resource Fragments related to the source resources through the specified relationship # # @param source_rids [Array<ResourceIdentity>] The resources to find related ResourcesIdentities for # @param relationship_name [String | Symbol] The name of the relationship # @option options [Hash] :context The context of the request, set in the controller # @option options [Boolean] :cache Return the resources' cache field # # @return [Hash{ResourceIdentity => {identity: => ResourceIdentity, cache: cache_field, related: {relationship_name: [] }}}] # the ResourceInstances matching the filters, sorting, and pagination rules along with any request # additional_field values def find_related_fragments(source_fragment, relationship, options = {}) if relationship.polymorphic? # && relationship.foreign_key_on == :self source_resource_klasses = if relationship.foreign_key_on == :self relationship.class.polymorphic_types(relationship.name).collect do |polymorphic_type| resource_klass_for(polymorphic_type) end else source.collect { |fragment| fragment.identity.resource_klass }.to_set end fragments = {} source_resource_klasses.each do |resource_klass| inverse_direct_relationship = _relationship(resource_klass._type.to_s.singularize) fragments.merge!(resource_klass.find_related_fragments_from_inverse([source_fragment], inverse_direct_relationship, options, true)) end fragments else relationship.resource_klass.find_related_fragments_from_inverse([source_fragment], relationship, options, false) end end def find_included_fragments(source_fragments, relationship, options) if relationship.polymorphic? # && relationship.foreign_key_on == :self source_resource_klasses = if relationship.foreign_key_on == :self relationship.class.polymorphic_types(relationship.name).collect do |polymorphic_type| resource_klass_for(polymorphic_type) end else source_fragments.collect { |fragment| fragment.identity.resource_klass }.to_set end fragments = {} source_resource_klasses.each do |resource_klass| inverse_direct_relationship = _relationship(resource_klass._type.to_s.singularize) fragments.merge!(resource_klass.find_related_fragments_from_inverse(source_fragments, inverse_direct_relationship, options, true)) end fragments else relationship.resource_klass.find_related_fragments_from_inverse(source_fragments, relationship, options, true) end end def find_related_fragments_from_inverse(source, source_relationship, options, connect_source_identity) relationship = source_relationship.resource_klass._relationship(source_relationship.inverse_relationship) raise "missing inverse relationship" unless relationship.present? parent_resource_klass = relationship.resource_klass include_directives = options.fetch(:include_directives, {}) # ToDo: Handle resources vs identities source_ids = source.collect {|item| item.identity.id} filters = options.fetch(:filters, {}) linkage_relationships = to_one_relationships_for_linkage(include_directives[:include_related]) sort_criteria = [] options[:sort_criteria].try(:each) do |sort| field = sort[:field].to_s == 'id' ? _primary_key : sort[:field] sort_criteria << { field: field, direction: sort[:direction] } end join_manager = ActiveRelation::JoinManager.new(resource_klass: self, source_relationship: relationship, relationships: linkage_relationships.collect(&:name), sort_criteria: sort_criteria, filters: filters) paginator = options[:paginator] records = apply_request_settings_to_records(records: records(options), sort_criteria: sort_criteria, source_ids: source_ids, paginator: paginator, filters: filters, join_manager: join_manager, options: options) fragments = {} if options[:cache] # This alias is going to be resolve down to the model's table name and will not actually be an alias resource_table_alias = self._table_name parent_table_alias = join_manager.join_details_by_relationship(relationship)[:alias] pluck_fields = [ sql_field_with_alias(resource_table_alias, self._primary_key), sql_field_with_alias(parent_table_alias, parent_resource_klass._primary_key) ] cache_field = attribute_to_model_field(:_cache_field) pluck_fields << sql_field_with_alias(resource_table_alias, cache_field[:name]) linkage_fields = [] linkage_relationships.each do |linkage_relationship| linkage_relationship_name = linkage_relationship.name if linkage_relationship.polymorphic? && linkage_relationship.belongs_to? linkage_relationship.resource_types.each do |resource_type| klass = resource_klass_for(resource_type) linkage_fields << {relationship_name: linkage_relationship_name, resource_klass: klass} linkage_table_alias = join_manager.join_details_by_polymorphic_relationship(linkage_relationship, resource_type)[:alias] primary_key = klass._primary_key pluck_fields << sql_field_with_alias(linkage_table_alias, primary_key) end else klass = linkage_relationship.resource_klass linkage_fields << {relationship_name: linkage_relationship_name, resource_klass: klass} linkage_table_alias = join_manager.join_details_by_relationship(linkage_relationship)[:alias] primary_key = klass._primary_key pluck_fields << sql_field_with_alias(linkage_table_alias, primary_key) end end sort_fields = options.dig(:_relation_helper_options, :sort_fields) sort_fields.try(:each) do |field| pluck_fields << Arel.sql(field) end rows = records.distinct.pluck(*pluck_fields) rows.each do |row| rid = JSONAPI::ResourceIdentity.new(self, row[0]) fragments[rid] ||= JSONAPI::ResourceFragment.new(rid) parent_rid = JSONAPI::ResourceIdentity.new(parent_resource_klass, row[1]) fragments[rid].add_related_from(parent_rid) if connect_source_identity fragments[rid].add_related_identity(relationship.name, parent_rid) end attributes_offset = 2 fragments[rid].cache = cast_to_attribute_type(row[attributes_offset], cache_field[:type]) attributes_offset += 1 linkage_fields.each do |linkage_field| fragments[rid].initialize_related(linkage_field[:relationship_name]) related_id = row[attributes_offset] if related_id related_rid = JSONAPI::ResourceIdentity.new(linkage_field[:resource_klass], related_id) fragments[rid].add_related_identity(linkage_field[:relationship_name], related_rid) end attributes_offset += 1 end end else linkage_fields = [] linkage_relationships.each do |linkage_relationship| linkage_relationship_name = linkage_relationship.name if linkage_relationship.polymorphic? && linkage_relationship.belongs_to? linkage_relationship.resource_types.each do |resource_type| klass = linkage_relationship.resource_klass.resource_klass_for(resource_type) primary_key = klass._primary_key linkage_table_alias = join_manager.join_details_by_polymorphic_relationship(linkage_relationship, resource_type)[:alias] select_alias = "jr_l_#{linkage_relationship_name}_#{resource_type}_pk" select_alias_statement = sql_field_with_fixed_alias(linkage_table_alias, primary_key, select_alias) linkage_fields << {relationship_name: linkage_relationship_name, resource_klass: klass, select: select_alias_statement, select_alias: select_alias} end else klass = linkage_relationship.resource_klass primary_key = klass._primary_key linkage_table_alias = join_manager.join_details_by_relationship(linkage_relationship)[:alias] select_alias = "jr_l_#{linkage_relationship_name}_pk" select_alias_statement = sql_field_with_fixed_alias(linkage_table_alias, primary_key, select_alias) linkage_fields << {relationship_name: linkage_relationship_name, resource_klass: klass, select: select_alias_statement, select_alias: select_alias} end end parent_table_alias = join_manager.join_details_by_relationship(relationship)[:alias] source_field = sql_field_with_fixed_alias(parent_table_alias, parent_resource_klass._primary_key, "jr_source_id") records = records.select(concat_table_field(_table_name, Arel.star), source_field) if linkage_fields.any? records = records.select(linkage_fields.collect {|f| f[:select]}) end resources = resources_for(records, options[:context]) resources.each do |resource| rid = resource.identity fragments[rid] ||= JSONAPI::ResourceFragment.new(rid, resource: resource) parent_rid = JSONAPI::ResourceIdentity.new(parent_resource_klass, resource._model.attributes['jr_source_id']) if connect_source_identity fragments[rid].add_related_identity(relationship.name, parent_rid) end fragments[rid].add_related_from(parent_rid) linkage_fields.each do |linkage_field_details| fragments[rid].initialize_related(linkage_field_details[:relationship_name]) related_id = resource._model.attributes[linkage_field_details[:select_alias]] if related_id related_rid = JSONAPI::ResourceIdentity.new(linkage_field_details[:resource_klass], related_id) fragments[rid].add_related_identity(linkage_field_details[:relationship_name], related_rid) end end end end fragments end # Counts Resources related to the source resource through the specified relationship # # @param source_rid [ResourceIdentity] Source resource identifier # @param relationship_name [String | Symbol] The name of the relationship # @option options [Hash] :context The context of the request, set in the controller # # @return [Integer] the count def count_related(source, relationship, options = {}) relationship.resource_klass.count_related_from_inverse(source, relationship, options) end def count_related_from_inverse(source_resource, source_relationship, options = {}) relationship = source_relationship.resource_klass._relationship(source_relationship.inverse_relationship) related_klass = relationship.resource_klass filters = options.fetch(:filters, {}) # Joins in this case are related to the related_klass join_manager = ActiveRelation::JoinManager.new(resource_klass: self, source_relationship: relationship, filters: filters) records = apply_request_settings_to_records(records: records(options), resource_klass: self, source_ids: source_resource.id, join_manager: join_manager, filters: filters, options: options) related_alias = join_manager.join_details_by_relationship(relationship)[:alias] records = records.select(Arel.sql("#{concat_table_field(related_alias, related_klass._primary_key)}")) count_records(records) end # This resource class (ActiveRelationResource) uses an `ActiveRecord::Relation` as the starting point for # retrieving models. From this relation filters, sorts and joins are applied as needed. # Depending on which phase of the request processing different `records` methods will be called, giving the user # the opportunity to override them differently for performance and security reasons. # begin `records`methods # Base for the `records` methods that follow and is not directly used for accessing model data by this class. # Overriding this method gives a single place to affect the `ActiveRecord::Relation` used for the resource. # # @option options [Hash] :context The context of the request, set in the controller # # @return [ActiveRecord::Relation] def records_base(_options = {}) _model_class.all end # The `ActiveRecord::Relation` used for finding user requested models. This may be overridden to enforce # permissions checks on the request. # # @option options [Hash] :context The context of the request, set in the controller # # @return [ActiveRecord::Relation] def records(options = {}) records_base(options) end # The `ActiveRecord::Relation` used for populating the ResourceSet. Only resources that have been previously # identified through the `records` method will be accessed. Thus it should not be necessary to reapply permissions # checks. However if the model needs to include other models adding `includes` is appropriate # # @option options [Hash] :context The context of the request, set in the controller # # @return [ActiveRecord::Relation] def records_for_populate(options = {}) records_base(options) end # The `ActiveRecord::Relation` used for the finding related resources. # # @option options [Hash] :context The context of the request, set in the controller # # @return [ActiveRecord::Relation] def records_for_source_to_related(options = {}) records_base(options) end # end `records` methods def apply_join(records:, relationship:, resource_type:, join_type:, options:) if relationship.polymorphic? && relationship.belongs_to? case join_type when :inner records = records.joins(resource_type.to_s.singularize.to_sym) when :left records = records.joins_left(resource_type.to_s.singularize.to_sym) end else relation_name = relationship.relation_name(options) # if relationship.alias_on_join # alias_name = "#{relationship.preferred_alias}_#{relation_name}" # case join_type # when :inner # records = records.joins_with_alias(relation_name, alias_name) # when :left # records = records.left_joins_with_alias(relation_name, alias_name) # end # else case join_type when :inner records = records.joins(relation_name) when :left records = records.left_joins(relation_name) end end # end records end def relationship_records(relationship:, join_type: :inner, resource_type: nil, options: {}) records = relationship.parent_resource.records_for_source_to_related(options) strategy = relationship.options[:apply_join] if strategy records = call_method_or_proc(strategy, records, relationship, resource_type, join_type, options) else records = apply_join(records: records, relationship: relationship, resource_type: resource_type, join_type: join_type, options: options) end records end def join_relationship(records:, relationship:, resource_type: nil, join_type: :inner, options: {}) relationship_records = relationship_records(relationship: relationship, join_type: join_type, resource_type: resource_type, options: options) records.merge(relationship_records) end # protected def find_record_by_key(key, options = {}) record = apply_request_settings_to_records(records: records(options), primary_keys: key, options: options).first fail JSONAPI::Exceptions::RecordNotFound.new(key) if record.nil? record end def find_records_by_keys(keys, options = {}) apply_request_settings_to_records(records: records(options), primary_keys: keys, options: options) end def apply_request_settings_to_records(records:, join_manager: ActiveRelation::JoinManager.new(resource_klass: self), resource_klass: self, source_ids: nil, filters: {}, primary_keys: nil, sort_criteria: nil, sort_primary: nil, paginator: nil, options: {}) options[:_relation_helper_options] = { join_manager: join_manager, sort_fields: [] } records = resource_klass.apply_joins(records, join_manager, options) if source_ids source_join_details = join_manager.source_join_details source_primary_key = join_manager.source_relationship.resource_klass._primary_key source_aliased_key = concat_table_field(source_join_details[:alias], source_primary_key, false) records = records.where(source_aliased_key => source_ids) end if primary_keys records = records.where(_primary_key => primary_keys) end unless filters.empty? records = resource_klass.filter_records(records, filters, options) end if sort_primary records = records.order(_primary_key => :asc) else order_options = resource_klass.construct_order_options(sort_criteria) records = resource_klass.sort_records(records, order_options, options) end if paginator records = resource_klass.apply_pagination(records, paginator, order_options) end records end def apply_joins(records, join_manager, options) join_manager.join(records, options) end def apply_pagination(records, paginator, order_options) records = paginator.apply(records, order_options) if paginator records end def apply_sort(records, order_options, options) if order_options.any? order_options.each_pair do |field, direction| records = apply_single_sort(records, field, direction, options) end end records end def apply_single_sort(records, field, direction, options) context = options[:context] strategy = _allowed_sort.fetch(field.to_sym, {})[:apply] options[:_relation_helper_options] ||= {} options[:_relation_helper_options][:sort_fields] ||= [] if strategy records = call_method_or_proc(strategy, records, direction, context) else join_manager = options.dig(:_relation_helper_options, :join_manager) sort_field = join_manager ? get_aliased_field(field, join_manager) : field options[:_relation_helper_options][:sort_fields].push("#{sort_field}") records = records.order(Arel.sql("#{sort_field} #{direction}")) end records end # Assumes ActiveRecord's counting. Override if you need a different counting method def count_records(records) if Rails::VERSION::MAJOR >= 6 || (Rails::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR >= 1) records.count(:all) else records.count end end def filter_records(records, filters, options) if _polymorphic _polymorphic_resource_klasses.each do |klass| records = klass.apply_filters(records, filters, options) end else records = apply_filters(records, filters, options) end records end def construct_order_options(sort_params) if _polymorphic warn "Sorting is not supported on polymorphic relationships" else super(sort_params) end end def sort_records(records, order_options, options) apply_sort(records, order_options, options) end def sql_field_with_alias(table, field, quoted = true) Arel.sql("#{concat_table_field(table, field, quoted)} AS #{alias_table_field(table, field, quoted)}") end def sql_field_with_fixed_alias(table, field, alias_as, quoted = true) Arel.sql("#{concat_table_field(table, field, quoted)} AS #{alias_as}") end def concat_table_field(table, field, quoted = false) if table.blank? split_table, split_field = field.to_s.split('.') if split_table && split_field table = split_table field = split_field end end if table.blank? # :nocov: if quoted quote_column_name(field) else field.to_s end # :nocov: else if quoted "#{quote_table_name(table)}.#{quote_column_name(field)}" else # :nocov: "#{table.to_s}.#{field.to_s}" # :nocov: end end end def alias_table_field(table, field, quoted = false) if table.blank? || field.to_s.include?('.') # :nocov: if quoted quote_column_name(field) else field.to_s end # :nocov: else if quoted # :nocov: quote_column_name("#{table.to_s}_#{field.to_s}") # :nocov: else "#{table.to_s}_#{field.to_s}" end end end def quote_table_name(table_name) if _model_class&.connection _model_class.connection.quote_table_name(table_name) else quote(table_name) end end def quote_column_name(column_name) return column_name if column_name == "*" if _model_class&.connection _model_class.connection.quote_column_name(column_name) else quote(column_name) end end # fallback quote identifier when database adapter not available def quote(field) %{"#{field.to_s}"} end def apply_filters(records, filters, options = {}) if filters filters.each do |filter, value| records = apply_filter(records, filter, value, options) end end records end def get_aliased_field(path_with_field, join_manager) path = JSONAPI::Path.new(resource_klass: self, path_string: path_with_field) relationship_segment = path.segments[-2] field_segment = path.segments[-1] if relationship_segment join_details = join_manager.join_details[path.last_relationship] table_alias = join_details[:alias] else table_alias = self._table_name end concat_table_field(table_alias, field_segment.delegated_field_name) end def apply_filter(records, filter, value, options = {}) strategy = _allowed_filters.fetch(filter.to_sym, Hash.new)[:apply] if strategy records = call_method_or_proc(strategy, records, value, options) else join_manager = options.dig(:_relation_helper_options, :join_manager) field = join_manager ? get_aliased_field(filter, join_manager) : filter.to_s records = records.where(Arel.sql(field) => value) end records end def warn_about_unused_methods if Rails.env.development? if !caching? && implements_class_method?(:records_for_populate) warn "#{self}: The `records_for_populate` method is not used when caching is disabled." end end end def implements_class_method?(method_name) methods(false).include?(method_name) end end end end