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 +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
|