Skip to content

Commit b9192e8

Browse files
committed
Working version
1 parent 8e89d4a commit b9192e8

File tree

8 files changed

+117
-3
lines changed

8 files changed

+117
-3
lines changed

CONTRIBUTING.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,14 @@ Run a specific test:
5757
```bash
5858
bundle exec rake test TEST=test/path/to/your_test_file.rb
5959
```
60+
61+
## Roadmap
62+
63+
### In Progress
64+
65+
* Support nearest neighbor queries
66+
67+
### Future
68+
69+
* Support creating vector indexes
70+
* Support creating embedding jobs

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,3 @@ rails db:migrate
3232
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
3333

3434
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
35-

lib/generators/lantern/lantern_generator.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33

44
module Lantern
55
module Generators
6-
class InstallGenerator < Rails::Generators::Base
6+
class LanternGenerator < Rails::Generators::Base
77
include ActiveRecord::Generators::Migration
88
source_root File.join(__dir__, 'templates')
99

1010
def copy_migration
1111
migration_template 'lantern.rb', 'db/migrate/install_lantern.rb', migration_version: migration_version
1212
end
1313

14+
private
15+
1416
def migration_version
1517
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
1618
end

lib/lantern.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
require_relative 'lantern/version'
2+
require_relative 'lantern/model'
3+
require 'active_support'
24

35
module Lantern
46
class Error < StandardError; end
57
end
68

9+
ActiveSupport.on_load(:active_record) do
10+
extend Lantern::Model
11+
end
12+
713
require_relative 'lantern/railtie' if defined?(Rails::Railtie)

lib/lantern/model.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
module Lantern
2+
module Model
3+
def has_neighbors(*attribute_names)
4+
attribute_names.map!(&:to_sym)
5+
6+
scope :nearest_neighbors, ->(attribute_name, vector) {
7+
attribute_name = attribute_name.to_sym
8+
9+
unless vector.all? { |v| v.is_a?(Integer) }
10+
vector = vector.map(&:to_f)
11+
end
12+
13+
vector_literal = vector.join(',')
14+
order_expression = "#{quoted_table_name}.#{connection.quote_column_name(attribute_name)} <-> ARRAY[#{vector_literal}]"
15+
16+
select("#{quoted_table_name}.*, #{order_expression} AS distance")
17+
.where.not(attribute_name => nil)
18+
.order(Arel.sql(order_expression))
19+
}
20+
21+
define_method :nearest_neighbors do |attribute_name|
22+
attribute_name = attribute_name.to_sym
23+
self.class
24+
.where.not(id: id)
25+
.nearest_neighbors(attribute_name, self[attribute_name])
26+
end
27+
end
28+
end
29+
end

test/lantern_generator_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
require "generators/lantern/lantern_generator"
33

44
class LanternGeneratorTest < Rails::Generators::TestCase
5-
tests Lantern::Generators::InstallGenerator
5+
tests Lantern::Generators::LanternGenerator
66
destination File.expand_path('../tmp', __dir__)
77
setup :prepare_destination
88

test/model_test.rb

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
require "test_helper"
2+
3+
class Item < ActiveRecord::Base
4+
has_neighbors :embedding
5+
end
6+
7+
class LanternTest < Minitest::Test
8+
def setup
9+
@conn = ActiveRecord::Base.connection
10+
11+
@conn.execute "DROP TABLE IF EXISTS items"
12+
@conn.execute <<~SQL
13+
CREATE TABLE items (
14+
id serial PRIMARY KEY,
15+
embedding REAL[]
16+
)
17+
SQL
18+
19+
@vectors = [
20+
[1.0, 0.0, 0.0],
21+
[0.0, 1.0, 0.0],
22+
[0.0, 0.0, 1.0]
23+
]
24+
25+
@vectors.each do |vector|
26+
@conn.execute "INSERT INTO items (embedding) VALUES (ARRAY#{vector})"
27+
end
28+
end
29+
30+
def test_instance_nearest_neighbors
31+
item = Item.first
32+
neighbors = item.nearest_neighbors(:embedding).limit(2)
33+
34+
assert_equal 2, neighbors.length
35+
assert_equal [2, 3], neighbors.pluck(:id)
36+
37+
distances = neighbors.map { |neighbor| neighbor.distance }
38+
expected_distances = [2.0, 2.0]
39+
assert_equal expected_distances, distances
40+
end
41+
42+
def test_class_nearest_neighbors
43+
neighbors = Item.nearest_neighbors(:embedding, [0.5, 0.0, 0.5]).limit(2)
44+
45+
assert 2, neighbors.length
46+
assert_equal [1, 3], neighbors.pluck(:id)
47+
48+
distances = neighbors.map { |neighbor| neighbor.distance }
49+
expected_distances = [0.5, 0.5]
50+
assert_equal expected_distances, distances
51+
end
52+
53+
def test_invalid_dimensions
54+
assert_raises(ActiveRecord::StatementInvalid) do
55+
Item.nearest_neighbors(:embedding, [1.0, 0.0]).limit(2).to_a
56+
end
57+
end
58+
59+
def teardown
60+
@conn.execute "DROP TABLE IF EXISTS items"
61+
end
62+
end

test/test_helper.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@
22
Bundler.require(:default)
33
require "minitest/autorun"
44
require "active_record"
5+
require "dotenv/load"
6+
7+
ActiveRecord::Base.establish_connection(ENV["DATABASE_URL"])
8+
9+
ActiveRecord::Base.connection.enable_extension("lantern")

0 commit comments

Comments
 (0)