kakugosearch-rails 0.1.0
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 +7 -0
- data/README.md +81 -0
- data/lib/kakugosearch/client.rb +44 -0
- data/lib/kakugosearch/configuration.rb +11 -0
- data/lib/kakugosearch/railtie.rb +13 -0
- data/lib/kakugosearch/searchable.rb +61 -0
- data/lib/kakugosearch/version.rb +3 -0
- data/lib/kakugosearch.rb +29 -0
- metadata +122 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d82d1ce3c7b5c8abe1fcfb8999aad36314d95bc4313e41ef05b505cb0448460c
|
|
4
|
+
data.tar.gz: 33a03799ff363d9edb1e030c258172fbbe2dda0dd2ed8dc77da45a5e92508fce
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e0860bf171a2bfb0a8a43b69fd920dbd17d66f4d99c3754519ee05615d78e0d0f89eef58fe7e602b1b3a13f2a3d8259c55b55cf9b0533a0768184d27327f0546
|
|
7
|
+
data.tar.gz: 35a86afd4f05e692eb2827602605761097b6189c59ca7db13216e50e6c04bd73ef7397433556ad30e16452ab929518796e7ca12df6080557a1797a802881972e
|
data/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# kakugosearch-rails
|
|
2
|
+
|
|
3
|
+
Rails integration for [KakugoSearch](https://github.com/your-org/kakugosearch) — a lightweight, AI-enhanced full-text search engine written in Rust.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add to your `Gemfile`:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem "kakugosearch-rails"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then run:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bundle install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Configuration
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
# config/initializers/kakugosearch.rb
|
|
23
|
+
KakugoSearch.configure do |config|
|
|
24
|
+
config.url = "http://localhost:7700" # KakugoSearch server URL
|
|
25
|
+
config.api_key = ENV["KAKUGOSEARCH_AI_API_KEY"] # optional, for AI reranking
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
Include `KakugoSearch::Searchable` in any ActiveRecord model and declare which fields to index:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
class Article < ApplicationRecord
|
|
35
|
+
include KakugoSearch::Searchable
|
|
36
|
+
|
|
37
|
+
kakugosearch_index(
|
|
38
|
+
index: "articles", # index name (defaults to table_name)
|
|
39
|
+
fields: {
|
|
40
|
+
title: :title, # KakugoSearch field => AR attribute/method
|
|
41
|
+
body: :content,
|
|
42
|
+
url: :permalink,
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Records are automatically indexed on `save` and removed from the index on `destroy`.
|
|
49
|
+
|
|
50
|
+
### Searching
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
# Basic search
|
|
54
|
+
results = Article.kakugosearch("rust programming")
|
|
55
|
+
|
|
56
|
+
# With options
|
|
57
|
+
results = Article.kakugosearch("machine learning",
|
|
58
|
+
limit: 10,
|
|
59
|
+
ai: true, # enable AI reranking
|
|
60
|
+
ai_weight: 0.5 # blend of BM25 vs AI (0.0–1.0)
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Returns ActiveRecord objects in ranked order.
|
|
65
|
+
|
|
66
|
+
## Behaviour Notes
|
|
67
|
+
|
|
68
|
+
- **Synchronous** — indexing happens inline with `save`/`destroy`. Wrap in `after_commit` if you need post-transaction indexing.
|
|
69
|
+
- **Errors on indexing are logged, not raised** — a failed HTTP call to KakugoSearch will not break your model save.
|
|
70
|
+
- **Errors on search do raise** — a `KakugoSearch::Error` is raised so callers can handle it.
|
|
71
|
+
|
|
72
|
+
## Development
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
bundle install
|
|
76
|
+
bundle exec rspec
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
MIT
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require "net/http"
|
|
2
|
+
require "json"
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module KakugoSearch
|
|
6
|
+
class Client
|
|
7
|
+
def initialize(config = KakugoSearch.configuration)
|
|
8
|
+
@config = config
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# POST /indexes/{index}/documents
|
|
12
|
+
def index_documents(index, docs)
|
|
13
|
+
uri = URI("#{@config.url}/indexes/#{index}/documents")
|
|
14
|
+
request(Net::HTTP::Post, uri, docs)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# DELETE /indexes/{index}/documents/{id}
|
|
18
|
+
def delete_document(index, id)
|
|
19
|
+
uri = URI("#{@config.url}/indexes/#{index}/documents/#{id}")
|
|
20
|
+
request(Net::HTTP::Delete, uri)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# GET /indexes/{index}/search
|
|
24
|
+
def search(index, query, limit: 20, ai: false, ai_weight: 0.3)
|
|
25
|
+
params = URI.encode_www_form(q: query, limit: limit, ai: ai, ai_weight: ai_weight)
|
|
26
|
+
uri = URI("#{@config.url}/indexes/#{index}/search?#{params}")
|
|
27
|
+
request(Net::HTTP::Get, uri)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def request(method_class, uri, body = nil)
|
|
33
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
|
|
34
|
+
req = method_class.new(uri)
|
|
35
|
+
req["Content-Type"] = "application/json"
|
|
36
|
+
req["Accept"] = "application/json"
|
|
37
|
+
req["Authorization"] = "Bearer #{@config.api_key}" if @config.api_key
|
|
38
|
+
req.body = body.to_json if body
|
|
39
|
+
response = http.request(req)
|
|
40
|
+
JSON.parse(response.body)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require "rails/railtie"
|
|
2
|
+
|
|
3
|
+
module KakugoSearch
|
|
4
|
+
class Railtie < Rails::Railtie
|
|
5
|
+
initializer "kakugosearch.include_searchable" do
|
|
6
|
+
ActiveSupport.on_load(:active_record) do
|
|
7
|
+
# Searchable is opt-in; Railtie just ensures it's available without
|
|
8
|
+
# manual require in application code.
|
|
9
|
+
require "kakugosearch/searchable"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
require "active_support/concern"
|
|
2
|
+
|
|
3
|
+
module KakugoSearch
|
|
4
|
+
module Searchable
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
class_attribute :_kakugosearch_index_name, instance_writer: false
|
|
9
|
+
class_attribute :_kakugosearch_fields, instance_writer: false
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class_methods do
|
|
13
|
+
# kakugosearch_index index: "articles", fields: { title: :title, body: :content }
|
|
14
|
+
def kakugosearch_index(index: nil, fields: {})
|
|
15
|
+
self._kakugosearch_index_name = index || table_name
|
|
16
|
+
self._kakugosearch_fields = fields
|
|
17
|
+
|
|
18
|
+
after_save :kakugosearch_index_document
|
|
19
|
+
after_destroy :kakugosearch_delete_document
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Article.kakugosearch("rust programming", limit: 10, ai: false)
|
|
23
|
+
def kakugosearch(query, limit: 20, ai: false, ai_weight: 0.3)
|
|
24
|
+
result = KakugoSearch.client.search(
|
|
25
|
+
_kakugosearch_index_name, query,
|
|
26
|
+
limit: limit, ai: ai, ai_weight: ai_weight
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
hits = Array(result["hits"])
|
|
30
|
+
ids = hits.map { |h| h["id"] }
|
|
31
|
+
return none if ids.empty?
|
|
32
|
+
|
|
33
|
+
# Preserve ranked order
|
|
34
|
+
records_by_id = where(id: ids).index_by { |r| r.id.to_s }
|
|
35
|
+
ids.filter_map { |id| records_by_id[id.to_s] }
|
|
36
|
+
rescue => e
|
|
37
|
+
raise KakugoSearch::Error, "Search failed: #{e.message}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def kakugosearch_index_document
|
|
44
|
+
doc = { id: id.to_s }
|
|
45
|
+
self.class._kakugosearch_fields.each do |field, method_name|
|
|
46
|
+
doc[field] = public_send(method_name)
|
|
47
|
+
end
|
|
48
|
+
KakugoSearch.client.index_documents(self.class._kakugosearch_index_name, [doc])
|
|
49
|
+
rescue => e
|
|
50
|
+
logger = defined?(Rails) ? Rails.logger : Logger.new($stderr)
|
|
51
|
+
logger.error("[KakugoSearch] Failed to index #{self.class.name}##{id}: #{e.message}")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def kakugosearch_delete_document
|
|
55
|
+
KakugoSearch.client.delete_document(self.class._kakugosearch_index_name, id.to_s)
|
|
56
|
+
rescue => e
|
|
57
|
+
logger = defined?(Rails) ? Rails.logger : Logger.new($stderr)
|
|
58
|
+
logger.error("[KakugoSearch] Failed to delete #{self.class.name}##{id}: #{e.message}")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
data/lib/kakugosearch.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require "kakugosearch/version"
|
|
2
|
+
require "kakugosearch/configuration"
|
|
3
|
+
require "kakugosearch/client"
|
|
4
|
+
|
|
5
|
+
module KakugoSearch
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def configuration
|
|
10
|
+
@configuration ||= Configuration.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def configure
|
|
14
|
+
yield configuration
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def client
|
|
18
|
+
@client ||= Client.new(configuration)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Reset memoized client when config changes (useful in tests)
|
|
22
|
+
def reset!
|
|
23
|
+
@configuration = nil
|
|
24
|
+
@client = nil
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
require "kakugosearch/railtie" if defined?(Rails)
|
metadata
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: kakugosearch-rails
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Allan Farinas
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: railties
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '6.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '6.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rspec
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '3.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: webmock
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '3.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '3.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: activerecord
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '6.0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '6.0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: sqlite3
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
description: Drop-in ActiveRecord concern that keeps KakugoSearch indexes in sync
|
|
83
|
+
with your models via save/destroy callbacks, plus a search class method.
|
|
84
|
+
email:
|
|
85
|
+
- hello@allanfarinas.com
|
|
86
|
+
executables: []
|
|
87
|
+
extensions: []
|
|
88
|
+
extra_rdoc_files: []
|
|
89
|
+
files:
|
|
90
|
+
- README.md
|
|
91
|
+
- lib/kakugosearch.rb
|
|
92
|
+
- lib/kakugosearch/client.rb
|
|
93
|
+
- lib/kakugosearch/configuration.rb
|
|
94
|
+
- lib/kakugosearch/railtie.rb
|
|
95
|
+
- lib/kakugosearch/searchable.rb
|
|
96
|
+
- lib/kakugosearch/version.rb
|
|
97
|
+
homepage: https://github.com/alpha-san/kakugosearch-rails
|
|
98
|
+
licenses:
|
|
99
|
+
- MIT
|
|
100
|
+
metadata:
|
|
101
|
+
homepage_uri: https://github.com/alpha-san/kakugosearch-rails
|
|
102
|
+
source_code_uri: https://github.com/alpha-san/kakugosearch-rails
|
|
103
|
+
changelog_uri: https://github.com/alpha-san/kakugosearch-rails/blob/main/CHANGELOG.md
|
|
104
|
+
bug_tracker_uri: https://github.com/alpha-san/kakugosearch-rails/issues
|
|
105
|
+
rdoc_options: []
|
|
106
|
+
require_paths:
|
|
107
|
+
- lib
|
|
108
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
109
|
+
requirements:
|
|
110
|
+
- - ">="
|
|
111
|
+
- !ruby/object:Gem::Version
|
|
112
|
+
version: '3.0'
|
|
113
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '0'
|
|
118
|
+
requirements: []
|
|
119
|
+
rubygems_version: 3.6.9
|
|
120
|
+
specification_version: 4
|
|
121
|
+
summary: Rails integration for the KakugoSearch AI-enhanced search engine
|
|
122
|
+
test_files: []
|