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 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,11 @@
1
+ module KakugoSearch
2
+ class Configuration
3
+ attr_accessor :url, :api_key, :default_index
4
+
5
+ def initialize
6
+ @url = "http://localhost:7700"
7
+ @api_key = ENV["KAKUGOSEARCH_AI_API_KEY"]
8
+ @default_index = nil
9
+ end
10
+ end
11
+ 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
@@ -0,0 +1,3 @@
1
+ module KakugoSearch
2
+ VERSION = "0.1.0"
3
+ end
@@ -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: []