neighbor 0.1.2 → 0.2.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e64a3292445759187b7c08fd9cc54fe0da340ea3e3ab5de909620de6395b4984
4
- data.tar.gz: 274d73ed464a0503973c5549022dca8820257fe470c9746b5599da9a153d46e3
3
+ metadata.gz: da5f64785d929d673be65b5cf224894b6e5d6bbd8d66a27b84339ced5e8c1334
4
+ data.tar.gz: fbb217005dcd6a3c1d946eec178acf7f0f87ac3521e2deb4b7c8c2bc5433b633
5
5
  SHA512:
6
- metadata.gz: 17df9f8a5848337fc570c6fa685f54f4aa9d49bea8e52e74fbec098e0bc9e450b655c7d96cb66c6e8b6aaae4824f2c831ea26a14f7f46b50ef64c955cfb9839b
7
- data.tar.gz: a47165d174ec86ebfdcd128e62c50b85d097aac535b6b6d424ea31988428a57237cdade2829d8e22a15e566ac7597371c95fabafad42cdbc5d56f063abab55e4
6
+ metadata.gz: d293a32bfd18e00851bcbbaecac507374e9105071fbf7ab8446eaea093b9741b573356a7b292bf19dd420c2d7208c62023be4f7106711e19f9e84970aedba2bd
7
+ data.tar.gz: 07ed7bccab0924fe183df1176937d6ce98d1e53313e2be53d96605d691ad697ff4e14abc722b7a3501b20a5ca2880774625a306c274f81d308895fa78a1bf068
data/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ ## 0.2.2 (2022-07-13)
2
+
3
+ - Added support for configurable attribute name
4
+ - Added support for multiple attributes per model
5
+
6
+ ## 0.2.1 (2021-12-15)
7
+
8
+ - Added support for Active Record 7
9
+
10
+ ## 0.2.0 (2021-04-21)
11
+
12
+ - Added support for pgvector
13
+ - Added `normalize` option
14
+ - Made `dimensions` optional
15
+ - Raise an error if `nearest_neighbors` already defined
16
+ - Raise an error for non-finite values
17
+ - Fixed NaN with zero vectors and cosine distance
18
+
19
+ Breaking changes
20
+
21
+ - The `distance` option has been moved from `has_neighbors` to `nearest_neighbors`, and there is no longer a default
22
+
1
23
  ## 0.1.2 (2021-02-21)
2
24
 
3
25
  - Added `nearest_neighbors` scope
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2021 Andrew Kane
3
+ Copyright (c) 2021-2022 Andrew Kane
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -9,18 +9,26 @@ Nearest neighbor search for Rails and Postgres
9
9
  Add this line to your application’s Gemfile:
10
10
 
11
11
  ```ruby
12
- gem 'neighbor'
12
+ gem "neighbor"
13
13
  ```
14
14
 
15
- And run:
15
+ ## Choose An Extension
16
+
17
+ Neighbor supports two extensions: [cube](https://www.postgresql.org/docs/current/cube.html) and [vector](https://github.com/pgvector/pgvector). cube ships with Postgres, while vector supports approximate nearest neighbor search.
18
+
19
+ For cube, run:
16
20
 
17
21
  ```sh
18
- bundle install
19
- rails generate neighbor:install
22
+ rails generate neighbor:cube
20
23
  rails db:migrate
21
24
  ```
22
25
 
23
- This enables the [cube extension](https://www.postgresql.org/docs/current/cube.html) in Postgres
26
+ For vector, [install pgvector](https://github.com/pgvector/pgvector#installation) and run:
27
+
28
+ ```sh
29
+ rails generate neighbor:vector
30
+ rails db:migrate
31
+ ```
24
32
 
25
33
  ## Getting Started
26
34
 
@@ -30,6 +38,8 @@ Create a migration
30
38
  class AddNeighborVectorToItems < ActiveRecord::Migration[6.1]
31
39
  def change
32
40
  add_column :items, :neighbor_vector, :cube
41
+ # or
42
+ add_column :items, :neighbor_vector, :vector, limit: 3 # dimensions
33
43
  end
34
44
  end
35
45
  ```
@@ -38,7 +48,7 @@ Add to your model
38
48
 
39
49
  ```ruby
40
50
  class Item < ApplicationRecord
41
- has_neighbors dimensions: 3
51
+ has_neighbors
42
52
  end
43
53
  ```
44
54
 
@@ -48,49 +58,76 @@ Update the vectors
48
58
  item.update(neighbor_vector: [1.0, 1.2, 0.5])
49
59
  ```
50
60
 
51
- > With cosine distance (the default), vectors are normalized before being stored
52
-
53
61
  Get the nearest neighbors to a record
54
62
 
55
63
  ```ruby
56
- item.nearest_neighbors.first(5)
64
+ item.nearest_neighbors(distance: "euclidean").first(5)
57
65
  ```
58
66
 
59
67
  Get the nearest neighbors to a vector
60
68
 
61
69
  ```ruby
62
- Item.nearest_neighbors([1, 2, 3])
70
+ Item.nearest_neighbors([0.9, 1.3, 1.1], distance: "euclidean").first(5)
63
71
  ```
64
72
 
65
73
  ## Distance
66
74
 
67
- Specify the distance metric
75
+ Supported values are:
76
+
77
+ - `euclidean`
78
+ - `cosine`
79
+ - `taxicab` (cube only)
80
+ - `chebyshev` (cube only)
81
+ - `inner_product` (vector only)
82
+
83
+ For cosine distance with cube, vectors must be normalized before being stored.
68
84
 
69
85
  ```ruby
70
86
  class Item < ApplicationRecord
71
- has_neighbors dimensions: 20, distance: "euclidean"
87
+ has_neighbors normalize: true
72
88
  end
73
89
  ```
74
90
 
75
- Supported values are:
76
-
77
- - `cosine` (default)
78
- - `euclidean`
79
- - `taxicab`
80
- - `chebyshev`
81
-
82
- For inner product, see [this example](examples/disco_user_recs.rb)
91
+ For inner product with cube, see [this example](examples/disco_user_recs_cube.rb).
83
92
 
84
93
  Records returned from `nearest_neighbors` will have a `neighbor_distance` attribute
85
94
 
86
95
  ```ruby
87
- nearest_item = item.nearest_neighbors.first
96
+ nearest_item = item.nearest_neighbors(distance: "euclidean").first
88
97
  nearest_item.neighbor_distance
89
98
  ```
90
99
 
91
100
  ## Dimensions
92
101
 
93
- By default, Postgres limits the `cube` data type to 100 dimensions. See the [Postgres docs](https://www.postgresql.org/docs/current/cube.html) for how to increase this.
102
+ The cube data type is limited 100 dimensions by default. See the [Postgres docs](https://www.postgresql.org/docs/current/cube.html) for how to increase this. The vector data type is limited to 1024 dimensions.
103
+
104
+ For cube, it’s a good idea to specify the number of dimensions to ensure all records have the same number.
105
+
106
+ ```ruby
107
+ class Movie < ApplicationRecord
108
+ has_neighbors dimensions: 3
109
+ end
110
+ ```
111
+
112
+ ## Indexing
113
+
114
+ For vector, add an approximate index to speed up queries. Create a migration with:
115
+
116
+ ```ruby
117
+ class AddIndexToItemsNeighborVector < ActiveRecord::Migration[6.1]
118
+ def change
119
+ add_index :items, :neighbor_vector, using: :ivfflat, opclass: :vector_l2_ops
120
+ end
121
+ end
122
+ ```
123
+
124
+ Use `:vector_cosine_ops` for cosine distance and `:vector_ip_ops` for inner product.
125
+
126
+ Set the number of probes
127
+
128
+ ```ruby
129
+ Item.connection.execute("SET ivfflat.probes = 3")
130
+ ```
94
131
 
95
132
  ## Example
96
133
 
@@ -107,7 +144,7 @@ And add `has_neighbors`
107
144
 
108
145
  ```ruby
109
146
  class Movie < ApplicationRecord
110
- has_neighbors dimensions: 20
147
+ has_neighbors dimensions: 20, normalize: true
111
148
  end
112
149
  ```
113
150
 
@@ -122,19 +159,33 @@ recommender.fit(data)
122
159
  Use item factors for the neighbor vector
123
160
 
124
161
  ```ruby
162
+ movies = []
125
163
  recommender.item_ids.each do |item_id|
126
- Movie.create!(name: item_id, neighbor_vector: recommender.item_factors(item_id))
164
+ movies << {name: item_id, neighbor_vector: recommender.item_factors(item_id)}
127
165
  end
166
+ Movie.insert_all!(movies) # use create! for Active Record < 6
128
167
  ```
129
168
 
130
169
  And get similar movies
131
170
 
132
171
  ```ruby
133
172
  movie = Movie.find_by(name: "Star Wars (1977)")
134
- movie.nearest_neighbors.first(5).map(&:name)
173
+ movie.nearest_neighbors(distance: "cosine").first(5).map(&:name)
135
174
  ```
136
175
 
137
- [Complete code](examples/disco_item_recs.rb)
176
+ See the complete code for [cube](examples/disco_item_recs_cube.rb) and [vector](examples/disco_item_recs_vector.rb)
177
+
178
+ ## Upgrading
179
+
180
+ ### 0.2.0
181
+
182
+ The `distance` option has been moved from `has_neighbors` to `nearest_neighbors`, and there is no longer a default. If you use cosine distance, set:
183
+
184
+ ```ruby
185
+ class Item < ApplicationRecord
186
+ has_neighbors normalize: true
187
+ end
188
+ ```
138
189
 
139
190
  ## History
140
191
 
@@ -155,5 +206,11 @@ To get started with development:
155
206
  git clone https://github.com/ankane/neighbor.git
156
207
  cd neighbor
157
208
  bundle install
209
+ createdb neighbor_test
210
+
211
+ # cube
158
212
  bundle exec rake test
213
+
214
+ # vector
215
+ EXT=vector bundle exec rake test
159
216
  ```
@@ -2,12 +2,12 @@ require "rails/generators/active_record"
2
2
 
3
3
  module Neighbor
4
4
  module Generators
5
- class InstallGenerator < Rails::Generators::Base
5
+ class CubeGenerator < Rails::Generators::Base
6
6
  include ActiveRecord::Generators::Migration
7
7
  source_root File.join(__dir__, "templates")
8
8
 
9
9
  def copy_migration
10
- migration_template "migration.rb", "db/migrate/install_neighbor.rb", migration_version: migration_version
10
+ migration_template "cube.rb", "db/migrate/install_neighbor_cube.rb", migration_version: migration_version
11
11
  end
12
12
 
13
13
  def migration_version
@@ -0,0 +1,5 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ enable_extension "vector"
4
+ end
5
+ end
@@ -0,0 +1,18 @@
1
+ require "rails/generators/active_record"
2
+
3
+ module Neighbor
4
+ module Generators
5
+ class VectorGenerator < Rails::Generators::Base
6
+ include ActiveRecord::Generators::Migration
7
+ source_root File.join(__dir__, "templates")
8
+
9
+ def copy_migration
10
+ migration_template "vector.rb", "db/migrate/install_neighbor_vector.rb", migration_version: migration_version
11
+ end
12
+
13
+ def migration_version
14
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,42 +1,105 @@
1
1
  module Neighbor
2
2
  module Model
3
- def has_neighbors(dimensions:, distance: "cosine")
4
- distance = distance.to_s
5
- raise ArgumentError, "Invalid distance: #{distance}" unless %w(cosine euclidean taxicab chebyshev).include?(distance)
6
-
7
- # TODO make configurable
8
- # likely use argument
9
- attribute_name = :neighbor_vector
3
+ def has_neighbors(attribute_name = :neighbor_vector, dimensions: nil, normalize: nil)
4
+ attribute_name = attribute_name.to_sym
10
5
 
11
6
  class_eval do
12
- attribute attribute_name, Neighbor::Vector.new(dimensions: dimensions, distance: distance)
7
+ @neighbor_attributes ||= {}
8
+
9
+ if @neighbor_attributes.empty?
10
+ def self.neighbor_attributes
11
+ parent_attributes =
12
+ if superclass.respond_to?(:neighbor_attributes)
13
+ superclass.neighbor_attributes
14
+ else
15
+ {}
16
+ end
17
+
18
+ parent_attributes.merge(@neighbor_attributes || {})
19
+ end
20
+ end
21
+
22
+ raise Error, "has_neighbors already called for #{attribute_name.inspect}" if neighbor_attributes[attribute_name]
23
+ @neighbor_attributes[attribute_name] = {dimensions: dimensions, normalize: normalize}
24
+
25
+ attribute attribute_name, Neighbor::Vector.new(dimensions: dimensions, normalize: normalize, model: self, attribute_name: attribute_name)
26
+
27
+ return if @neighbor_attributes.size != 1
28
+
29
+ scope :nearest_neighbors, ->(attribute_name, vector = nil, distance:) {
30
+ if vector.nil? && !attribute_name.nil? && attribute_name.respond_to?(:to_a)
31
+ vector = attribute_name
32
+ attribute_name = :neighbor_vector
33
+ end
34
+ attribute_name = attribute_name.to_sym
35
+
36
+ options = neighbor_attributes[attribute_name]
37
+ raise ArgumentError, "Invalid attribute" unless options
38
+ normalize = options[:normalize]
39
+ dimensions = options[:dimensions]
13
40
 
14
- scope :nearest_neighbors, ->(vector) {
15
41
  return none if vector.nil?
16
42
 
43
+ distance = distance.to_s
44
+
17
45
  quoted_attribute = "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(attribute_name)}"
18
46
 
47
+ column_info = klass.type_for_attribute(attribute_name).column_info
48
+
19
49
  operator =
20
- case distance
21
- when "taxicab"
22
- "<#>"
23
- when "chebyshev"
24
- "<=>"
50
+ if column_info[:type] == :vector
51
+ case distance
52
+ when "inner_product"
53
+ "<#>"
54
+ when "cosine"
55
+ "<=>"
56
+ when "euclidean"
57
+ "<->"
58
+ end
25
59
  else
26
- "<->"
60
+ case distance
61
+ when "taxicab"
62
+ "<#>"
63
+ when "chebyshev"
64
+ "<=>"
65
+ when "euclidean", "cosine"
66
+ "<->"
67
+ end
27
68
  end
28
69
 
70
+ raise ArgumentError, "Invalid distance: #{distance}" unless operator
71
+
72
+ # ensure normalize set (can be true or false)
73
+ if distance == "cosine" && column_info[:type] == :cube && normalize.nil?
74
+ raise Neighbor::Error, "Set normalize for cosine distance with cube"
75
+ end
76
+
77
+ vector = Neighbor::Vector.cast(vector, dimensions: dimensions, normalize: normalize, column_info: column_info)
78
+
29
79
  # important! neighbor_vector should already be typecast
30
80
  # but use to_f as extra safeguard against SQL injection
31
- vector = Neighbor::Vector.cast(vector, dimensions: dimensions, distance: distance)
32
- order = "#{quoted_attribute} #{operator} cube(array[#{vector.map(&:to_f).join(", ")}])"
81
+ query =
82
+ if column_info[:type] == :vector
83
+ connection.quote("[#{vector.map(&:to_f).join(", ")}]")
84
+ else
85
+ "cube(array[#{vector.map(&:to_f).join(", ")}])"
86
+ end
87
+
88
+ order = "#{quoted_attribute} #{operator} #{query}"
33
89
 
34
90
  # https://stats.stackexchange.com/questions/146221/is-cosine-similarity-identical-to-l2-normalized-euclidean-distance
35
91
  # with normalized vectors:
36
92
  # cosine similarity = 1 - (euclidean distance)**2 / 2
37
93
  # cosine distance = 1 - cosine similarity
38
94
  # this transformation doesn't change the order, so only needed for select
39
- neighbor_distance = distance == "cosine" ? "POWER(#{order}, 2) / 2.0" : order
95
+ neighbor_distance =
96
+ if column_info[:type] != :vector && distance == "cosine"
97
+ "POWER(#{order}, 2) / 2.0"
98
+ elsif column_info[:type] == :vector && distance == "inner_product"
99
+ "(#{order}) * -1"
100
+ else
101
+ order
102
+ end
40
103
 
41
104
  # for select, use column_names instead of * to account for ignored columns
42
105
  select(*column_names, "#{neighbor_distance} AS neighbor_distance")
@@ -44,10 +107,14 @@ module Neighbor
44
107
  .order(Arel.sql(order))
45
108
  }
46
109
 
47
- define_method :nearest_neighbors do
110
+ def nearest_neighbors(attribute_name = :neighbor_vector, **options)
111
+ attribute_name = attribute_name.to_sym
112
+ # important! check if neighbor attribute before calling send
113
+ raise ArgumentError, "Invalid attribute" unless self.class.neighbor_attributes[attribute_name]
114
+
48
115
  self.class
49
116
  .where.not(self.class.primary_key => send(self.class.primary_key))
50
- .nearest_neighbors(send(attribute_name))
117
+ .nearest_neighbors(attribute_name, send(attribute_name), **options)
51
118
  end
52
119
  end
53
120
  end
@@ -1,29 +1,61 @@
1
1
  module Neighbor
2
2
  class Vector < ActiveRecord::Type::Value
3
- def initialize(dimensions:, distance:)
3
+ def initialize(dimensions:, normalize:, model:, attribute_name:)
4
4
  super()
5
5
  @dimensions = dimensions
6
- @distance = distance
6
+ @normalize = normalize
7
+ @model = model
8
+ @attribute_name = attribute_name
7
9
  end
8
10
 
9
- def self.cast(value, dimensions:, distance:)
11
+ def self.cast(value, dimensions:, normalize:, column_info:)
10
12
  value = value.to_a.map(&:to_f)
11
- raise Error, "Expected #{dimensions} dimensions, not #{value.size}" unless value.size == dimensions
12
13
 
13
- if distance == "cosine"
14
+ dimensions ||= column_info[:dimensions]
15
+ raise Error, "Expected #{dimensions} dimensions, not #{value.size}" if dimensions && value.size != dimensions
16
+
17
+ raise Error, "Values must be finite" unless value.all?(&:finite?)
18
+
19
+ if normalize
14
20
  norm = Math.sqrt(value.sum { |v| v * v })
15
- value.map { |v| v / norm }
16
- else
17
- value
21
+
22
+ # store zero vector as all zeros
23
+ # since NaN makes the distance always 0
24
+ # could also throw error
25
+
26
+ # safe to update in-place since earlier map dups
27
+ value.map! { |v| v / norm } if norm > 0
18
28
  end
29
+
30
+ value
31
+ end
32
+
33
+ def self.column_info(model, attribute_name)
34
+ attribute_name = attribute_name.to_s
35
+ column = model.columns.detect { |c| c.name == attribute_name }
36
+ {
37
+ type: column.try(:type),
38
+ dimensions: column.try(:limit)
39
+ }
40
+ end
41
+
42
+ # need to be careful to avoid loading column info before needed
43
+ def column_info
44
+ @column_info ||= self.class.column_info(@model, @attribute_name)
19
45
  end
20
46
 
21
47
  def cast(value)
22
- self.class.cast(value, dimensions: @dimensions, distance: @distance) unless value.nil?
48
+ self.class.cast(value, dimensions: @dimensions, normalize: @normalize, column_info: column_info) unless value.nil?
23
49
  end
24
50
 
25
51
  def serialize(value)
26
- "(#{cast(value).join(", ")})" unless value.nil?
52
+ unless value.nil?
53
+ if column_info[:type] == :vector
54
+ "[#{cast(value).join(", ")}]"
55
+ else
56
+ "(#{cast(value).join(", ")})"
57
+ end
58
+ end
27
59
  end
28
60
 
29
61
  def deserialize(value)
@@ -1,3 +1,3 @@
1
1
  module Neighbor
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.2"
3
3
  end
data/lib/neighbor.rb CHANGED
@@ -7,10 +7,14 @@ require "neighbor/version"
7
7
  module Neighbor
8
8
  class Error < StandardError; end
9
9
 
10
- module RegisterCubeType
10
+ module RegisterTypes
11
11
  def initialize_type_map(m = type_map)
12
12
  super
13
13
  m.register_type "cube", ActiveRecord::ConnectionAdapters::PostgreSQL::OID::SpecializedString.new(:cube)
14
+ m.register_type "vector" do |_, _, sql_type|
15
+ limit = extract_limit(sql_type)
16
+ ActiveRecord::ConnectionAdapters::PostgreSQL::OID::SpecializedString.new(:vector, limit: limit)
17
+ end
14
18
  end
15
19
  end
16
20
  end
@@ -25,16 +29,24 @@ ActiveSupport.on_load(:active_record) do
25
29
 
26
30
  # ensure schema can be dumped
27
31
  ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:cube] = {name: "cube"}
32
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:vector] = {name: "vector"}
28
33
 
29
34
  # ensure schema can be loaded
30
35
  if ActiveRecord::VERSION::MAJOR >= 6
31
- ActiveRecord::ConnectionAdapters::TableDefinition.send(:define_column_methods, :cube)
36
+ ActiveRecord::ConnectionAdapters::TableDefinition.send(:define_column_methods, :cube, :vector)
32
37
  else
33
38
  ActiveRecord::ConnectionAdapters::TableDefinition.define_method :cube do |*args, **options|
34
39
  args.each { |name| column(name, :cube, options) }
35
40
  end
41
+ ActiveRecord::ConnectionAdapters::TableDefinition.define_method :vector do |*args, **options|
42
+ args.each { |name| column(name, :vector, options) }
43
+ end
36
44
  end
37
45
 
38
46
  # prevent unknown OID warning
39
- ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(Neighbor::RegisterCubeType)
47
+ if ActiveRecord::VERSION::MAJOR >= 7
48
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.singleton_class.prepend(Neighbor::RegisterTypes)
49
+ else
50
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(Neighbor::RegisterTypes)
51
+ end
40
52
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: neighbor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-22 00:00:00.000000000 Z
11
+ date: 2022-07-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '5.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '5.2'
27
27
  description:
28
28
  email: andrew@ankane.org
29
29
  executables: []
@@ -33,8 +33,10 @@ files:
33
33
  - CHANGELOG.md
34
34
  - LICENSE.txt
35
35
  - README.md
36
- - lib/generators/neighbor/install_generator.rb
37
- - lib/generators/neighbor/templates/migration.rb.tt
36
+ - lib/generators/neighbor/cube_generator.rb
37
+ - lib/generators/neighbor/templates/cube.rb.tt
38
+ - lib/generators/neighbor/templates/vector.rb.tt
39
+ - lib/generators/neighbor/vector_generator.rb
38
40
  - lib/neighbor.rb
39
41
  - lib/neighbor/model.rb
40
42
  - lib/neighbor/vector.rb
@@ -58,7 +60,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
58
60
  - !ruby/object:Gem::Version
59
61
  version: '0'
60
62
  requirements: []
61
- rubygems_version: 3.2.3
63
+ rubygems_version: 3.3.7
62
64
  signing_key:
63
65
  specification_version: 4
64
66
  summary: Nearest neighbor search for Rails and Postgres