eyepiece 0.2.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +28 -0
- data/lib/eyepiece/brief.rb +17 -0
- data/lib/eyepiece/quick_brief_scope.rb +1 -15
- data/lib/eyepiece/quick_search.rb +1 -81
- data/lib/eyepiece/search.rb +83 -0
- data/lib/eyepiece/version.rb +1 -1
- data/lib/eyepiece.rb +6 -2
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3de6b747ca261f9422fcb57f8f8fd68ab28a62a355a2ce76cfaee2f0722455b9
|
|
4
|
+
data.tar.gz: ff6e67ab38a25f74d6ad94cf80f6a97ba25177188a7fc6ccf80df583fa0a32a8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b3aa77870677d4faf576211862e0a0f08804a1d94d36e15c30ad3056e6b5fb0899244ab6055cee6610f4ff8c1e1ddc9c2aeb2781c5b2317944bf50001a6f460
|
|
7
|
+
data.tar.gz: e6c4ec2e238f8a7cff904c43280e4c6883e8c8932926c459312df518d79e2a70fda740a78f9131f3ae5690375a0f1cd1b808041984765ef39fd4cbf2f6ca5117
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [0.3.0] - 2026-04-09
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Rename `QuickSearch` to `Search` and `QuickBriefScope` to `Brief`
|
|
11
|
+
- Old names are preserved as backward-compatible aliases
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- `CHANGELOG.md` with `changelog_uri` in gemspec metadata
|
|
15
|
+
|
|
16
|
+
## [0.2.0] - 2026-04-09
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- Replace `ArgumentError` with custom `TooManyRecordsError`
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- Replace `IO.popen` with `Dir.glob` in gemspec and add ActiveRecord require
|
|
23
|
+
|
|
24
|
+
## [0.1.0] - 2026-04-09
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- `QuickSearch` parameterized module for fast ILIKE searching on ActiveRecord models
|
|
28
|
+
- `QuickBriefScope` parameterized module for brief scope selection
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Eyepiece
|
|
4
|
+
class Brief < Module
|
|
5
|
+
def initialize(*fields, method: :brief)
|
|
6
|
+
@module = Module.new do
|
|
7
|
+
define_method method do
|
|
8
|
+
reselect(*fields)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def included(base)
|
|
14
|
+
base.send(:extend, @module)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -1,17 +1,3 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
class QuickBriefScope < Module
|
|
5
|
-
def initialize(*fields, method: :brief)
|
|
6
|
-
@module = Module.new do
|
|
7
|
-
define_method method do
|
|
8
|
-
reselect(*fields)
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def included(base)
|
|
14
|
-
base.send(:extend, @module)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
3
|
+
require_relative "brief"
|
|
@@ -1,83 +1,3 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
class QuickSearch < Module
|
|
5
|
-
class Searcher
|
|
6
|
-
def self.call(...)
|
|
7
|
-
new(...).call
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def initialize(*fields, scope:, txt:, require_one:)
|
|
11
|
-
self.scope = scope
|
|
12
|
-
self.fields = fields
|
|
13
|
-
self.txt = Array(txt)
|
|
14
|
-
self.require_one = require_one
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def call
|
|
18
|
-
phrases =
|
|
19
|
-
txt.map.with_index(1).to_h do |phrase, index|
|
|
20
|
-
key = format("txt%<index>d", index: index).to_sym
|
|
21
|
-
[key, "%#{phrase}%"]
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
records = scope.where(query, **phrases)
|
|
25
|
-
|
|
26
|
-
if require_one
|
|
27
|
-
raise_error(records.length) if records.length > 1
|
|
28
|
-
records.first
|
|
29
|
-
else
|
|
30
|
-
records
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
private
|
|
35
|
-
|
|
36
|
-
def columns
|
|
37
|
-
fields.map do |field|
|
|
38
|
-
ActiveRecord::Base.connection.quote_column_name(field)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def query
|
|
43
|
-
queries =
|
|
44
|
-
txt.flat_map.with_index(1) do |_, index|
|
|
45
|
-
columns.map do |field|
|
|
46
|
-
"#{field}::text ILIKE :txt#{index}"
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
queries.join(" OR ")
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def raise_error(count)
|
|
54
|
-
raise(
|
|
55
|
-
Eyepiece::TooManyRecordsError,
|
|
56
|
-
format(
|
|
57
|
-
"too many records (%<count>d) for \"%<query>s\"",
|
|
58
|
-
count: count,
|
|
59
|
-
query: txt.join(", "),
|
|
60
|
-
),
|
|
61
|
-
)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
attr_accessor :fields, :scope, :txt, :require_one
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def included(base)
|
|
68
|
-
base.send(:extend, @module)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def initialize(*fields)
|
|
72
|
-
@module = Module.new do
|
|
73
|
-
define_method :like do |*txt, require_one: true|
|
|
74
|
-
Searcher.call(*fields, scope: self, txt: txt, require_one: require_one)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
define_method :mlike do |*txt, require_one: false|
|
|
78
|
-
Searcher.call(*fields, scope: self, txt: txt, require_one: require_one)
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
end
|
|
3
|
+
require_relative "search"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Eyepiece
|
|
4
|
+
class Search < Module
|
|
5
|
+
class Searcher
|
|
6
|
+
def self.call(...)
|
|
7
|
+
new(...).call
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def initialize(*fields, scope:, txt:, require_one:)
|
|
11
|
+
self.scope = scope
|
|
12
|
+
self.fields = fields
|
|
13
|
+
self.txt = Array(txt)
|
|
14
|
+
self.require_one = require_one
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
phrases =
|
|
19
|
+
txt.map.with_index(1).to_h do |phrase, index|
|
|
20
|
+
key = format("txt%<index>d", index: index).to_sym
|
|
21
|
+
[key, "%#{phrase}%"]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
records = scope.where(query, **phrases)
|
|
25
|
+
|
|
26
|
+
if require_one
|
|
27
|
+
raise_error(records.length) if records.length > 1
|
|
28
|
+
records.first
|
|
29
|
+
else
|
|
30
|
+
records
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def columns
|
|
37
|
+
fields.map do |field|
|
|
38
|
+
ActiveRecord::Base.connection.quote_column_name(field)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def query
|
|
43
|
+
queries =
|
|
44
|
+
txt.flat_map.with_index(1) do |_, index|
|
|
45
|
+
columns.map do |field|
|
|
46
|
+
"#{field}::text ILIKE :txt#{index}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
queries.join(" OR ")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def raise_error(count)
|
|
54
|
+
raise(
|
|
55
|
+
Eyepiece::TooManyRecordsError,
|
|
56
|
+
format(
|
|
57
|
+
"too many records (%<count>d) for \"%<query>s\"",
|
|
58
|
+
count: count,
|
|
59
|
+
query: txt.join(", "),
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
attr_accessor :fields, :scope, :txt, :require_one
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def included(base)
|
|
68
|
+
base.send(:extend, @module)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def initialize(*fields)
|
|
72
|
+
@module = Module.new do
|
|
73
|
+
define_method :like do |*txt, require_one: true|
|
|
74
|
+
Searcher.call(*fields, scope: self, txt: txt, require_one: require_one)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
define_method :mlike do |*txt, require_one: false|
|
|
78
|
+
Searcher.call(*fields, scope: self, txt: txt, require_one: require_one)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
data/lib/eyepiece/version.rb
CHANGED
data/lib/eyepiece.rb
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "eyepiece/version"
|
|
4
|
-
require_relative "eyepiece/
|
|
5
|
-
require_relative "eyepiece/
|
|
4
|
+
require_relative "eyepiece/search"
|
|
5
|
+
require_relative "eyepiece/brief"
|
|
6
6
|
|
|
7
7
|
module Eyepiece
|
|
8
8
|
class Error < StandardError; end
|
|
9
9
|
class TooManyRecordsError < Error; end
|
|
10
|
+
|
|
11
|
+
# Backward-compatible aliases
|
|
12
|
+
QuickSearch = Search
|
|
13
|
+
QuickBriefScope = Brief
|
|
10
14
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: eyepiece
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- John Doe
|
|
@@ -31,9 +31,12 @@ executables: []
|
|
|
31
31
|
extensions: []
|
|
32
32
|
extra_rdoc_files: []
|
|
33
33
|
files:
|
|
34
|
+
- CHANGELOG.md
|
|
34
35
|
- lib/eyepiece.rb
|
|
36
|
+
- lib/eyepiece/brief.rb
|
|
35
37
|
- lib/eyepiece/quick_brief_scope.rb
|
|
36
38
|
- lib/eyepiece/quick_search.rb
|
|
39
|
+
- lib/eyepiece/search.rb
|
|
37
40
|
- lib/eyepiece/version.rb
|
|
38
41
|
- sig/eyepiece.rbs
|
|
39
42
|
homepage: https://github.com/example/eyepiece
|
|
@@ -42,6 +45,7 @@ licenses:
|
|
|
42
45
|
metadata:
|
|
43
46
|
homepage_uri: https://github.com/example/eyepiece
|
|
44
47
|
source_code_uri: https://github.com/example/eyepiece
|
|
48
|
+
changelog_uri: https://github.com/example/eyepiece/blob/main/CHANGELOG.md
|
|
45
49
|
rdoc_options: []
|
|
46
50
|
require_paths:
|
|
47
51
|
- lib
|
|
@@ -58,5 +62,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
58
62
|
requirements: []
|
|
59
63
|
rubygems_version: 3.6.9
|
|
60
64
|
specification_version: 4
|
|
61
|
-
summary:
|
|
65
|
+
summary: Search and Brief scope modules for ActiveRecord models
|
|
62
66
|
test_files: []
|