neighbor 0.1.2 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/LICENSE.txt +1 -1
- data/README.md +83 -26
- data/lib/generators/neighbor/{install_generator.rb → cube_generator.rb} +2 -2
- data/lib/generators/neighbor/templates/{migration.rb.tt → cube.rb.tt} +0 -0
- data/lib/generators/neighbor/templates/vector.rb.tt +5 -0
- data/lib/generators/neighbor/vector_generator.rb +18 -0
- data/lib/neighbor/model.rb +87 -20
- data/lib/neighbor/vector.rb +42 -10
- data/lib/neighbor/version.rb +1 -1
- data/lib/neighbor.rb +15 -3
- metadata +9 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da5f64785d929d673be65b5cf224894b6e5d6bbd8d66a27b84339ced5e8c1334
|
4
|
+
data.tar.gz: fbb217005dcd6a3c1d946eec178acf7f0f87ac3521e2deb4b7c8c2bc5433b633
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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
|
12
|
+
gem "neighbor"
|
13
13
|
```
|
14
14
|
|
15
|
-
|
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
|
-
|
19
|
-
rails generate neighbor:install
|
22
|
+
rails generate neighbor:cube
|
20
23
|
rails db:migrate
|
21
24
|
```
|
22
25
|
|
23
|
-
|
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
|
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,
|
70
|
+
Item.nearest_neighbors([0.9, 1.3, 1.1], distance: "euclidean").first(5)
|
63
71
|
```
|
64
72
|
|
65
73
|
## Distance
|
66
74
|
|
67
|
-
|
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
|
87
|
+
has_neighbors normalize: true
|
72
88
|
end
|
73
89
|
```
|
74
90
|
|
75
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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 "
|
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
|
File without changes
|
@@ -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
|
data/lib/neighbor/model.rb
CHANGED
@@ -1,42 +1,105 @@
|
|
1
1
|
module Neighbor
|
2
2
|
module Model
|
3
|
-
def has_neighbors(dimensions
|
4
|
-
|
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
|
-
|
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
|
-
|
21
|
-
|
22
|
-
"
|
23
|
-
|
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
|
-
|
32
|
-
|
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 =
|
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
|
-
|
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
|
data/lib/neighbor/vector.rb
CHANGED
@@ -1,29 +1,61 @@
|
|
1
1
|
module Neighbor
|
2
2
|
class Vector < ActiveRecord::Type::Value
|
3
|
-
def initialize(dimensions:,
|
3
|
+
def initialize(dimensions:, normalize:, model:, attribute_name:)
|
4
4
|
super()
|
5
5
|
@dimensions = dimensions
|
6
|
-
@
|
6
|
+
@normalize = normalize
|
7
|
+
@model = model
|
8
|
+
@attribute_name = attribute_name
|
7
9
|
end
|
8
10
|
|
9
|
-
def self.cast(value, dimensions:,
|
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
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
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,
|
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
|
-
|
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)
|
data/lib/neighbor/version.rb
CHANGED
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
|
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::
|
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.
|
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:
|
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: '
|
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: '
|
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/
|
37
|
-
- lib/generators/neighbor/templates/
|
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.
|
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
|