weaviate_record 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WeaviateRecord
4
+ module Queries
5
+ # This class contains functions to perform count operation on Weaviate Relations
6
+ module Count
7
+ # Return the count of records matching the given conditions or search filters.
8
+ #
9
+ # +:bm25+ will not work here because it is not supported in aggregation queries
10
+ # +:limit+ and +:offset+ does not work with aggregation queries too
11
+ #
12
+ # ==== Example:
13
+ # Article.where(title: 'movie').count #=> 1
14
+ def count
15
+ query = to_query.slice(:class_name, :near_text, :near_vector, :near_object, :where)
16
+ query[:fields] = 'meta { count }'
17
+ @connection.client.query.aggs(**query).dig(0, 'meta', 'count')
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WeaviateRecord
4
+ module Queries
5
+ # This module contains functions to perform limit query
6
+ module Limit
7
+ # Limit the number of records to be fetched from the database
8
+ #
9
+ # ==== Example:
10
+ # articles = Article.limit(5)
11
+ # articles.size #=> 5
12
+ def limit(limit_value)
13
+ raise TypeError, 'Limit should be as integer' unless limit_value.to_i.to_s == limit_value.to_s
14
+
15
+ @limit = limit_value
16
+ @loaded = false
17
+ self
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WeaviateRecord
4
+ module Queries
5
+ # This module includes methods to perform 'near object' queries.
6
+ module NearObject
7
+ # Performs a similarity search based on the given object.
8
+ # This method takes either id of the object or the object itself and returns the list of objects
9
+ # that are nearer to it in terms of vector distance. You can also limit the distance by passing
10
+ # the distance parameter.
11
+ #
12
+ # ==== Example:
13
+ # Article.create(content: 'This is a movie about friendship, action and adventure')
14
+ # # => #<Article:0x00000001052091e8 id: "983c0970-2c65-4c38-a93f-2ca9272d784b"... >
15
+ # obj = Article.create(content: 'This is a review about a movie')
16
+ # # => #<Article:0x00000001052091e8 id: "0476e426-7e7f-4010-bfad-20c57a65c5c7"... >
17
+ #
18
+ # Article.near_object(obj)
19
+ # # => [... #<Article:0x00000001052091e8 id: "983c0970-2c65-4c38-a93f-2ca9272d784b"... > ]
20
+ def near_object(object, distance: WeaviateRecord.config.similarity_search_threshold)
21
+ unless object.is_a?(WeaviateRecord::Base) || object.is_a?(String)
22
+ raise TypeError, "Invalid type #{object.class} for near object query"
23
+ end
24
+
25
+ raise TypeError, 'Invalid uuid' if object.is_a?(String) && !Constants::UUID_REGEX.match?(object)
26
+ raise TypeError, 'Invalid value for distance' unless distance.is_a?(Numeric)
27
+
28
+ @near_object = "{ id: #{(object.is_a?(WeaviateRecord::Base) ? object.id : object).inspect}, " \
29
+ "distance: #{distance} }"
30
+ @loaded = false
31
+ self
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WeaviateRecord
4
+ module Queries
5
+ # This module contains functions to perform near_text query (Context Based Search)
6
+ module NearText
7
+ # Perform a similarity search on Weaviate collection.
8
+ # This method also takes an optional distance parameter to specify the threshold of similarity.
9
+ # You can also pass multiple texts to search in the collection.
10
+ #
11
+ # ==== Example:
12
+ # Article.create(content: 'This is a movie about friendship, action and adventure')
13
+ # # => #<Article:0x00000001052091e8 id: "983c0970-2c65-4c38-a93f-2ca9272d784b"... >
14
+ #
15
+ # Article.near_text('review about a movie')
16
+ # # => [#<Article:0x00000001052091e8 id: "983c0970-2c65-4c38-a93f-2ca9272d784b"... >]
17
+ def near_text(*texts, distance: WeaviateRecord.config.similarity_search_threshold)
18
+ raise TypeError, 'invalid value for text' unless texts.all? { |text| text.is_a?(String) }
19
+ raise TypeError, 'Invalid value for distance' unless distance.is_a?(Numeric)
20
+
21
+ @near_text_options[:distance] = distance
22
+ @near_text_options[:concepts] += texts.map! { |text| text.gsub('"', "'") }
23
+ @loaded = false
24
+ self
25
+ end
26
+
27
+ private
28
+
29
+ def formatted_near_text_value
30
+ texts = @near_text_options[:concepts].map(&:inspect).join(', ')
31
+
32
+ "{ concepts: [#{texts}], distance: #{@near_text_options[:distance]} }"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WeaviateRecord
4
+ module Queries
5
+ # This module provides method for near vector query
6
+ module NearVector
7
+ # Performs a similarity search based on the given vector.
8
+ # This method takes a vector (Array of float values) and
9
+ # returns the list of objects that are nearer to it in terms of vector distance.
10
+ # You can also limit the distance by passing the distance parameter.
11
+ #
12
+ # ==== Example:
13
+ # Article.create(content: 'This is a movie about friendship, action and adventure')
14
+ # # => #<Article:0x00000001052091e8 id: "983c0970-2c65-4c38-a93f-2ca9272d784b"... >
15
+ #
16
+ # vector = Article.select(_additional: :vector).where(id: "983c0970-2c65-4c38-a93f-2ca9272d784b").vector
17
+ # # => [-0.37226558, 0.10700592, -0.3906307, 0.1064298 ... ]
18
+ #
19
+ # Article.near_vector(vector)
20
+ # # => [... #<Article:0x00000001052091e8 id: "983c0970-2c65-4c38-a93f-2ca9272d784b"... > ]
21
+ def near_vector(vector, distance: WeaviateRecord.config.similarity_search_threshold)
22
+ raise TypeError, "Invalid type #{vector.class} for near vector query" unless vector.is_a?(Array)
23
+ raise TypeError, 'Invalid vector' unless vector.all? { |v| v.is_a?(Float) }
24
+ raise TypeError, 'Invalid value for distance' unless distance.is_a?(Numeric)
25
+
26
+ @near_vector = "{ vector: #{vector}, distance: #{distance} }"
27
+ @loaded = false
28
+ self
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WeaviateRecord
4
+ module Queries
5
+ # This module contains function to offset Weaviate records
6
+ module Offset
7
+ # Offset the number of records to be fetched from the database
8
+ #
9
+ # ==== Example:
10
+ # Article.count #=> 10
11
+ # articles = Article.offset(5)
12
+ # articles.size #=> 5
13
+ def offset(offset_value)
14
+ raise TypeError, 'Offset should be an integer' unless offset_value.to_i.to_s == offset_value.to_s
15
+
16
+ @offset = offset_value
17
+ @loaded = false
18
+ self
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WeaviateRecord
4
+ module Queries
5
+ # This module contains function to sort Weaviate records
6
+ module Order
7
+ # Sort the records based on the given attributes.
8
+ # You can pass multiple attributes to sort the records.
9
+ # This sorting specification will be ignored if you are performing keyword (bm25) search.
10
+ #
11
+ # ==== Example:
12
+ # Article.order(:title)
13
+ # # Sorts the records based on title in ascending order
14
+ #
15
+ # Article.order(:title, created_at: :desc)
16
+ # # Sorts the records based on title in ascending order and created_at in descending order
17
+ def order(*args, **kw_args)
18
+ raise ArgumentError, 'expected at least one argument' if args.empty? && kw_args.empty?
19
+
20
+ sorting_specifiers = combine_arguments(args, kw_args)
21
+ assign_sort_options(sorting_specifiers)
22
+ @loaded = false
23
+ self
24
+ end
25
+
26
+ private
27
+
28
+ def validate_attribute_and_order(attribute, sorting_order)
29
+ unless attribute.is_a?(Symbol) || attribute.is_a?(String)
30
+ raise TypeError, 'Invalid type for sorting attribute, should be either type or symbol'
31
+ end
32
+
33
+ return if %i[asc desc].include? sorting_order
34
+
35
+ raise WeaviateRecord::Errors::SortingOptionError, 'Invalid sorting order'
36
+ end
37
+
38
+ def combine_arguments(args, kw_args)
39
+ [*args.map! { |attribute| convert_to_sorting_specifier(attribute) },
40
+ *kw_args.map { |attribute, sorting_order| convert_to_sorting_specifier(attribute, sorting_order) }]
41
+ end
42
+
43
+ def convert_to_sorting_specifier(attribute, sorting_order = :asc)
44
+ validate_attribute_and_order(attribute, sorting_order)
45
+ attribute = map_to_weaviate_attribute(attribute)
46
+
47
+ "{ path: [#{attribute.to_s.inspect}], order: #{sorting_order} }"
48
+ end
49
+
50
+ def map_to_weaviate_attribute(attribute)
51
+ return '_id' if attribute.to_s == 'id'
52
+ return attribute unless WeaviateRecord::Constants::SPECIAL_ATTRIBUTE_MAPPINGS.key?(attribute.to_s)
53
+
54
+ "_#{WeaviateRecord::Constants::SPECIAL_ATTRIBUTE_MAPPINGS[attribute.to_s]}"
55
+ end
56
+
57
+ def assign_sort_options(sorting_specifiers)
58
+ @sort_options = if @sort_options.nil?
59
+ merge_sorting_specifiers(*sorting_specifiers)
60
+ elsif @sort_options.start_with?('[')
61
+ merge_sorting_specifiers(@sort_options[2...-2], *sorting_specifiers)
62
+ else
63
+ merge_sorting_specifiers(@sort_options, *sorting_specifiers)
64
+ end
65
+ end
66
+
67
+ def merge_sorting_specifiers(*sorting_specifiers)
68
+ return sorting_specifiers[0] if sorting_specifiers.size == 1
69
+
70
+ "[ #{sorting_specifiers.join(', ')} ]"
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WeaviateRecord
4
+ module Queries
5
+ # This module contains function to perform select query on Weaviate
6
+ module Select
7
+ # Select the attributes to be fetched from the database.
8
+ # You can also pass nested attributes to be fetched.
9
+ # Meta attributes that needs to be fetched should be passed aa a value for key '_additional'.
10
+ # In Weaviate, +id+ is also a meta attribute.
11
+ # If select is not called on a Weaviate query, by default
12
+ # it will fetch all normak attributes with id and timestamps.
13
+ #
14
+ # ==== Example:
15
+ # Article.select(:content, :title)
16
+ # Article.select(_additional: :vector)
17
+ # Article.select( _additional: [:id, :created_at, :updated_at])
18
+ # Article.select(_additional: { answer: :result })
19
+ #
20
+ # Article.all #=> fetches id, content, title, created_at, updated_at
21
+ #
22
+ # There is one more special scenario where you can pass the graphql query directly.
23
+ # It will be used for summarization offered by summarizer module.
24
+ #
25
+ # ==== Example:
26
+ # Article.select(_additional: 'summary(properties: ["content"]) { result }')
27
+ def select(*args)
28
+ args.each do |arg|
29
+ if arg.is_a? Hash
30
+ @select_options[:nested_attributes].merge! arg
31
+ else
32
+ @select_options[:attributes] << arg.to_s unless @select_options[:attributes].include?(arg.to_s)
33
+ end
34
+ end
35
+ @loaded = false
36
+ self
37
+ end
38
+
39
+ private
40
+
41
+ def combined_select_attributes
42
+ attributes = format_array_attribute(@select_options[:attributes])
43
+ return attributes if @select_options[:nested_attributes].empty?
44
+
45
+ "#{attributes} #{format_nested_attribute(@select_options[:nested_attributes])}"
46
+ end
47
+
48
+ def create_or_process_select_attributes(custom_selected, attributes)
49
+ if custom_selected
50
+ attributes.gsub(/(?<=\s)(#{WeaviateRecord::Constants::SPECIAL_ATTRIBUTE_MAPPINGS.keys.join('|')})(?=\s)/,
51
+ WeaviateRecord::Constants::SPECIAL_ATTRIBUTE_MAPPINGS)
52
+ else
53
+ [
54
+ *WeaviateRecord::Schema.find_collection(@klass).attributes_list,
55
+ '_additional { id creationTimeUnix lastUpdateTimeUnix }'
56
+ ].join(' ')
57
+ end
58
+ end
59
+
60
+ def format_array_attribute(array)
61
+ array.map do |attribute|
62
+ case attribute
63
+ when String, Symbol then attribute.to_s
64
+ when Array then format_array_attribute(attribute)
65
+ when Hash then format_nested_attribute(attribute)
66
+ else raise TypeError
67
+ end
68
+ end.join(' ')
69
+ end
70
+
71
+ def format_nested_attribute(hash)
72
+ return_string = String.new
73
+ hash.each do |key, value|
74
+ return_string << key.to_s << ' { '
75
+ case value
76
+ when String, Symbol then return_string << value.to_s << ' } '
77
+ when Array then return_string << format_array_attribute(value) << ' } '
78
+ when Hash then return_string << format_nested_attribute(value) << ' } ' else raise TypeError
79
+ end
80
+ end
81
+ return_string.rstrip
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module WeaviateRecord
6
+ module Queries
7
+ # This module contains function to perform where query on Weaviate
8
+ module Where
9
+ # Perform a where query on the collection. You can pass a string or keyword arguments as a query.
10
+ # It follows the same syntax as ActiveRecord where query. Chaining of where queries is also supported.
11
+ #
12
+ # ==== Example:
13
+ # Article.where('title = ?', 'Hello World')
14
+ # Article.where(title: 'Hello World')
15
+ # Article.where('title = ? AND content = ?', 'Hello World', 'This is a content')
16
+ # Article.where(title: 'Hello World').where(content: 'This is a content')
17
+ def where(query = '', *values, **kw_args)
18
+ validate_arguments(query, values, kw_args)
19
+ keyword_query = process_keyword_conditions(kw_args)
20
+ string_query = process_string_conditions(query, *values)
21
+ combined_query = combine_queries(keyword_query, string_query)
22
+ @where_query = @where_query ? create_logical_condition(@where_query, 'And', combined_query) : combined_query
23
+ @loaded = false
24
+ self
25
+ end
26
+
27
+ # :enddoc:
28
+
29
+ def self.to_ruby_hash(string_condition)
30
+ pattern = /(?<=\s)\w+:|(?<=operator:\s)\w+/
31
+ keys_and_operator = string_condition.scan(pattern).uniq
32
+ json_equivalent = keys_and_operator.map { |i| i.end_with?(':') ? "#{i[0...-1].inspect}:" : i.inspect }
33
+ JSON.parse string_condition.gsub(pattern, keys_and_operator.zip(json_equivalent).to_h)
34
+ rescue StandardError
35
+ raise WeaviateRecord::Errors::WhereQueryConversionError, 'invalid where query format'
36
+ end
37
+
38
+ private
39
+
40
+ def validate_arguments(query, values, kw_args)
41
+ if values.empty? && kw_args.empty?
42
+ raise WeaviateRecord::Errors::InvalidWhereQueryError, 'invalid argument for where query'
43
+ end
44
+
45
+ return unless values.size != query.count('?')
46
+
47
+ raise WeaviateRecord::Errors::InvalidWhereQueryError, 'invalid number of arguments'
48
+ end
49
+
50
+ def process_keyword_conditions(hash)
51
+ return nil if hash.empty?
52
+
53
+ conditions = hash.each_pair.map do |key, value|
54
+ create_query_condition([key.to_s, value.is_a?(Array) ? 'CONTAINS_ANY' : '=', value])
55
+ end
56
+ conditions.inject { |acc, condition| create_logical_condition(acc, 'AND', condition) }
57
+ end
58
+
59
+ def process_string_conditions(query, *values)
60
+ return nil unless query.present? && values.present?
61
+
62
+ logical_operator_match = /\s+(AND|OR)\s+/i.match(query)
63
+ return create_query_condition_from_string(query, values) unless logical_operator_match
64
+
65
+ pre_condition = create_query_condition_from_string(logical_operator_match.pre_match, values)
66
+ post_condition = process_string_conditions(logical_operator_match.post_match, *values)
67
+
68
+ create_logical_condition(pre_condition, logical_operator_match[1], post_condition)
69
+ end
70
+
71
+ def create_query_condition_from_string(condition, values)
72
+ equation = condition.split
73
+ raise WeaviateRecord::Errors::InvalidWhereQueryError, 'unable to process the query' unless equation.size == 3
74
+ raise WeaviateRecord::Errors::InvalidWhereQueryError, 'insufficient values for formatting' if values.empty?
75
+
76
+ equation[-1] = values.shift
77
+ create_query_condition(equation)
78
+ end
79
+
80
+ def combine_queries(first_query, second_query)
81
+ if first_query.present? && second_query.present?
82
+ create_logical_condition(first_query, 'And', second_query)
83
+ else
84
+ first_query.presence || second_query
85
+ end
86
+ end
87
+
88
+ def create_query_condition(equation)
89
+ return null_condition(equation[0]) if equation[2].nil?
90
+
91
+ handle_timestamps_condition(equation)
92
+ "{ path: [\"#{equation[0]}\"], " \
93
+ "operator: #{map_operator(equation[1])}, " \
94
+ "#{map_value_type(equation[2])}: #{equation[2].inspect} }"
95
+ end
96
+
97
+ def handle_timestamps_condition(equation_array)
98
+ return nil unless equation_array[0] == 'created_at' || equation_array[0] == 'updated_at'
99
+
100
+ equation_array[0] = "_#{WeaviateRecord::Constants::SPECIAL_ATTRIBUTE_MAPPINGS[equation_array[0]]}"
101
+ equation_array[2] = equation_array[2].to_datetime.strftime('%Q')
102
+ end
103
+
104
+ def null_condition(attribute)
105
+ "{ path: [\"#{attribute}\"], operator: IsNull, valueBoolean: true }"
106
+ end
107
+
108
+ def create_logical_condition(pre_condition, operator, post_condition)
109
+ "{ operator: #{operator.capitalize}, " \
110
+ "operands: [#{pre_condition}, #{post_condition}] }"
111
+ end
112
+
113
+ def map_operator(operator)
114
+ WeaviateRecord::Constants::OPERATOR_MAPPING_HASH.fetch(operator) do
115
+ raise WeaviateRecord::Errors::InvalidOperatorError, "Invalid conditional operator #{operator}"
116
+ end
117
+ end
118
+
119
+ def map_value_type(value)
120
+ WeaviateRecord::Constants::TYPE_MAPPING_HASH.fetch(value.class) do |klass|
121
+ raise WeaviateRecord::Errors::InvalidValueTypeError, "Invalid value type #{klass} for comparison"
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WeaviateRecord
4
+ class Relation
5
+ # This module contains methods which helps to build query for Weaviate
6
+ module QueryBuilder
7
+ # This will return the query that will be sent to Weaviate. More like +to_sql+ in ActiveRecord.
8
+ #
9
+ # ==== Example:
10
+ # Article.select(:title, :content).near_text('friendship movie').limit(5).offset(2).to_query
11
+ # Returns:
12
+ # {:class_name=>"Article",
13
+ # :limit=>"5",
14
+ # :offset=>"2",
15
+ # :fields=>"title content",
16
+ # :near_text=>"{ concepts: [\"friendship movie\"], distance: 0.55 }"}
17
+ def to_query
18
+ query_params = basic_params
19
+ fill_up_keyword_search_param(query_params)
20
+ fill_up_similarity_search_param(query_params)
21
+ fill_up_conditions_param(query_params)
22
+ fill_up_sort_param(query_params)
23
+ fill_up_question_param(query_params)
24
+
25
+ query_params
26
+ end
27
+
28
+ private
29
+
30
+ def basic_params
31
+ { class_name: @klass.to_s, limit: @limit.to_s, offset: @offset.to_s,
32
+ fields: combined_select_attributes }
33
+ end
34
+
35
+ def fill_up_keyword_search_param(query_params)
36
+ query_params[:bm25] = @keyword_search if @keyword_search.present?
37
+ end
38
+
39
+ def fill_up_similarity_search_param(query_params)
40
+ query_params[:near_text] = formatted_near_text_value unless @near_text_options[:concepts].empty?
41
+ query_params[:near_vector] = @near_vector if @near_vector
42
+ query_params[:near_object] = @near_object if @near_object
43
+ end
44
+
45
+ def fill_up_conditions_param(query_params)
46
+ query_params[:where] = @where_query if @where_query
47
+ end
48
+
49
+ def fill_up_sort_param(query_params)
50
+ # Weaviate doesn't support sorting with bm25 search at the time of writing this code.
51
+ query_params[:sort] = @sort_options if @keyword_search.blank? && @sort_options
52
+ end
53
+
54
+ def fill_up_question_param(query_params)
55
+ query_params[:ask] = @ask if @ask
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module WeaviateRecord
6
+ # This class is used to build weaviate queries
7
+ class Relation
8
+ extend Forwardable
9
+ include Enumerable
10
+ include Queries::Bm25
11
+ include Queries::Count
12
+ include Queries::Limit
13
+ include Queries::NearText
14
+ include Queries::NearVector
15
+ include Queries::NearObject
16
+ include Queries::Offset
17
+ include Queries::Order
18
+ include Queries::Select
19
+ include Queries::Where
20
+ include Queries::Ask
21
+ include QueryBuilder
22
+
23
+ def_delegators(:records, :empty?, :present?, :[], :first, :last)
24
+
25
+ # :stopdoc:
26
+ def initialize(klass)
27
+ @select_options = { attributes: [], nested_attributes: {} }
28
+ @near_text_options = { concepts: [], distance: WeaviateRecord.config.similarity_search_threshold }
29
+ @limit = ENV['QUERY_DEFAULTS_LIMIT'] || 25
30
+ @offset = 0
31
+ @klass = klass
32
+ @records = []
33
+ @loaded = false
34
+ @connection = WeaviateRecord::Connection.new(@klass)
35
+ end
36
+ # :startdoc:
37
+
38
+ # To enumerate over each record in the Weaviate relation
39
+ def each(&block)
40
+ records.each(&block)
41
+ end
42
+
43
+ # Gets all the records from Weaviate matching the given conditions or search filters given in the query.
44
+ # This will return an array of WeaviateRecord objects.
45
+ def all
46
+ records
47
+ rescue StandardError => e
48
+ e
49
+ end
50
+
51
+ # Deletes all the records from Weaviate matching the given conditions or search filters given in the query.
52
+ # This will return the result of batch delete operation given by Weaviate.
53
+ #
54
+ # ==== Example:
55
+ # Article.where(title: nil).destroy_all
56
+ # # => {"failed"=>0, "limit"=>10000, "matches"=>3, "objects"=>nil, "successful"=>3}
57
+ def destroy_all
58
+ unless @where_query
59
+ raise WeaviateRecord::Errors::MissingWhereCondition, 'must specifiy atleast one where condition'
60
+ end
61
+
62
+ response = @connection.delete_where(Queries::Where.to_ruby_hash(@where_query))
63
+ return response['results'] if response.is_a?(Hash) && response.key?('results')
64
+
65
+ raise WeaviateRecord::Errors::ServerError,
66
+ response == '' ? 'Unauthorized' : response.dig('error', 'message').presence
67
+ end
68
+
69
+ alias inspect all
70
+ alias to_a all
71
+
72
+ private
73
+
74
+ def records
75
+ return @records if @loaded
76
+
77
+ query = to_query
78
+ custom_selected = query[:fields].present?
79
+ query[:fields] = create_or_process_select_attributes(custom_selected, query[:fields])
80
+ result = @connection.client.query.get(**query)
81
+ @loaded = true
82
+ @records = result.map { |record| @klass.new(custom_selected: custom_selected, **record) }
83
+ @records
84
+ end
85
+ end
86
+ end