searcher 0.0.1
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.
- data/.gitignore +4 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +46 -0
- data/README.md +38 -0
- data/Rakefile +2 -0
- data/lib/searcher/class_methods.rb +25 -0
- data/lib/searcher/config.rb +18 -0
- data/lib/searcher/version.rb +3 -0
- data/lib/searcher.rb +25 -0
- data/searcher.gemspec +24 -0
- data/searcher.sqlite3 +0 -0
- data/spec/fixtures/models.rb +28 -0
- data/spec/fixtures/schema.rb +22 -0
- data/spec/searcher_spec.rb +21 -0
- data/spec/spec_helper.rb +19 -0
- data/tmp/.gitkeep +0 -0
- metadata +116 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
searcher (0.0.1)
|
5
|
+
activerecord (~> 3.0.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (3.0.0)
|
11
|
+
activesupport (= 3.0.0)
|
12
|
+
builder (~> 2.1.2)
|
13
|
+
i18n (~> 0.4.1)
|
14
|
+
activerecord (3.0.0)
|
15
|
+
activemodel (= 3.0.0)
|
16
|
+
activesupport (= 3.0.0)
|
17
|
+
arel (~> 1.0.0)
|
18
|
+
tzinfo (~> 0.3.23)
|
19
|
+
activesupport (3.0.0)
|
20
|
+
arel (1.0.1)
|
21
|
+
activesupport (~> 3.0.0)
|
22
|
+
builder (2.1.2)
|
23
|
+
diff-lcs (1.1.2)
|
24
|
+
i18n (0.4.1)
|
25
|
+
rspec (2.0.0.beta.22)
|
26
|
+
rspec-core (= 2.0.0.beta.22)
|
27
|
+
rspec-expectations (= 2.0.0.beta.22)
|
28
|
+
rspec-mocks (= 2.0.0.beta.22)
|
29
|
+
rspec-core (2.0.0.beta.22)
|
30
|
+
rspec-expectations (2.0.0.beta.22)
|
31
|
+
diff-lcs (>= 1.1.2)
|
32
|
+
rspec-mocks (2.0.0.beta.22)
|
33
|
+
rspec-core (= 2.0.0.beta.22)
|
34
|
+
rspec-expectations (= 2.0.0.beta.22)
|
35
|
+
sqlite3-ruby (1.3.1)
|
36
|
+
tzinfo (0.3.23)
|
37
|
+
|
38
|
+
PLATFORMS
|
39
|
+
ruby
|
40
|
+
|
41
|
+
DEPENDENCIES
|
42
|
+
activerecord (~> 3.0.0)
|
43
|
+
bundler (>= 1.0.0)
|
44
|
+
rspec
|
45
|
+
searcher!
|
46
|
+
sqlite3-ruby
|
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# Searcher
|
2
|
+
|
3
|
+
Searcher is a pure SQL implementation which lets you find by pre-defined labels, as well as wildcard matching queries. It should be used as a lo-fi precursor to a proper full-text search platform, such as the built-in one to PostgreSQL.
|
4
|
+
|
5
|
+
The idea of this gem came from Joost Schuur.
|
6
|
+
|
7
|
+
This gem is used in Chapter 10 of Rails 3 in Action and was crafted specifically for it. YMMV.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
This gem is only compatible with versions of Active Record that are greater than or equal to 3.0. You *are* using Active Record 3, right?
|
12
|
+
|
13
|
+
Add this gem to your _Gemfile_ (You *are* using Bundler, right?):
|
14
|
+
|
15
|
+
gem 'searcher'
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
To define labels for your field, use the `searcher` method inside your model like this:
|
20
|
+
|
21
|
+
class Ticket < ActiveRecord::Base
|
22
|
+
has_and_belongs_to_many :tags
|
23
|
+
|
24
|
+
searcher do
|
25
|
+
external :tag, :from => :tags, :field => "name"
|
26
|
+
external :state, :from => :state, :field => "name"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
To query for these labels, use the `search` class method on your model:
|
31
|
+
|
32
|
+
Ticket.search("tag:v3.0.0 state:open")
|
33
|
+
|
34
|
+
Boom! There's all your tickets that have the tag v3.0.0 and are marked (state-wise) as being open.
|
35
|
+
|
36
|
+
## Caveats
|
37
|
+
|
38
|
+
Currently Searcher works only with `has_and_belongs_to_many` and `belongs_to` associations, as that is all that is needed in the book.
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'searcher/config'
|
2
|
+
|
3
|
+
module Searcher
|
4
|
+
module ClassMethods
|
5
|
+
def searcher(&block)
|
6
|
+
Searcher.classes << self unless Searcher.classes.include?(self)
|
7
|
+
@config ||= Searcher::Config.new.instance_exec(&block)
|
8
|
+
end
|
9
|
+
|
10
|
+
def search(query)
|
11
|
+
klass = self
|
12
|
+
|
13
|
+
result = query.split(" ").inject(klass) do |k, piece|
|
14
|
+
if piece.include?(":")
|
15
|
+
name, q = piece.split(":")
|
16
|
+
external = @config[:externals][name.to_sym]
|
17
|
+
next unless external
|
18
|
+
send("by_#{name}", q, external[:field])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
result
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Searcher
|
2
|
+
class Config
|
3
|
+
def initialize
|
4
|
+
@config = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def default(field)
|
8
|
+
@config[:default] = field
|
9
|
+
@config
|
10
|
+
end
|
11
|
+
|
12
|
+
def external(field, options)
|
13
|
+
@config[:externals] ||= {}
|
14
|
+
@config[:externals][field.to_sym] = options
|
15
|
+
@config
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/searcher.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'searcher/class_methods'
|
3
|
+
|
4
|
+
module Searcher
|
5
|
+
mattr_accessor :classes
|
6
|
+
def self.classes
|
7
|
+
@classes ||= []
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
ActiveRecord::Base.extend(Searcher::ClassMethods)
|
12
|
+
|
13
|
+
ActiveSupport.on_load(:after_initialize) do
|
14
|
+
Searcher.classes.each do |klass|
|
15
|
+
table = Table(klass.table_name)
|
16
|
+
klass.searcher[:externals].each do |name, config|
|
17
|
+
association = klass.reflect_on_association(config[:from])
|
18
|
+
association_table = Table(association.klass.table_name)
|
19
|
+
if [:has_and_belongs_to_many, :belongs_to].include?(association.macro)
|
20
|
+
scope = lambda { |q, field| klass.joins(config[:from]).where(association_table[field].eq(q)) }
|
21
|
+
klass.scope "by_#{name}", scope
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/searcher.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path("../lib/searcher/version", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "searcher"
|
6
|
+
s.version = Searcher::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = []
|
9
|
+
s.email = []
|
10
|
+
s.homepage = "http://rubygems.org/gems/searcher"
|
11
|
+
s.summary = "Label-based straight-SQL searcher"
|
12
|
+
s.description = "Label-based straight-SQL searcher"
|
13
|
+
|
14
|
+
s.required_rubygems_version = ">= 1.3.6"
|
15
|
+
s.rubyforge_project = "searcher"
|
16
|
+
|
17
|
+
s.add_development_dependency "bundler", ">= 1.0.0"
|
18
|
+
|
19
|
+
s.add_dependency "activerecord", "~> 3.0.0"
|
20
|
+
|
21
|
+
s.files = `git ls-files`.split("\n")
|
22
|
+
s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
|
23
|
+
s.require_path = 'lib'
|
24
|
+
end
|
data/searcher.sqlite3
ADDED
Binary file
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Ticket < ActiveRecord::Base
|
2
|
+
has_and_belongs_to_many :tags
|
3
|
+
belongs_to :state
|
4
|
+
|
5
|
+
searcher do
|
6
|
+
external :tag, :from => :tags, :field => "name"
|
7
|
+
external :state, :from => :state, :field => "name"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Tag < ActiveRecord::Base
|
12
|
+
has_and_belongs_to_many :tickets
|
13
|
+
end
|
14
|
+
|
15
|
+
class State < ActiveRecord::Base
|
16
|
+
has_many :tickets
|
17
|
+
end
|
18
|
+
|
19
|
+
#############
|
20
|
+
### SEEDS ###
|
21
|
+
#############
|
22
|
+
|
23
|
+
# Ticket with a description, tag and state.
|
24
|
+
ticket = Ticket.create(:description => "Hello world! You are awesome.")
|
25
|
+
ticket.tags << Tag.create(:name => "bug")
|
26
|
+
ticket.state = State.create(:name => "Open")
|
27
|
+
ticket.save!
|
28
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
self.verbose = false
|
3
|
+
|
4
|
+
create_table :tickets, :force => true do |t|
|
5
|
+
t.string :description
|
6
|
+
t.integer :state_id
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
|
10
|
+
create_table :tags_tickets, :force => true, :id => false do |t|
|
11
|
+
t.integer :ticket_id, :tag_id
|
12
|
+
end
|
13
|
+
|
14
|
+
create_table :tags, :force => true do |t|
|
15
|
+
t.string :name
|
16
|
+
end
|
17
|
+
|
18
|
+
create_table :states, :force => true do |t|
|
19
|
+
t.string :name
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Searcher do
|
4
|
+
let(:search_results) { subject }
|
5
|
+
let(:first_result) { subject.first }
|
6
|
+
|
7
|
+
context "habtm label search" do
|
8
|
+
subject { Ticket.search("tag:bug") }
|
9
|
+
it "finds a ticket" do
|
10
|
+
first_result.description.should eql("Hello world! You are awesome.")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context "belongs_to label search" do
|
15
|
+
subject { Ticket.search("state:Open") }
|
16
|
+
it "finds a ticket" do
|
17
|
+
first_result.description.should eql("Hello world! You are awesome.")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'searcher'
|
2
|
+
|
3
|
+
here = File.dirname(__FILE__)
|
4
|
+
|
5
|
+
|
6
|
+
require 'logger'
|
7
|
+
ActiveRecord::Base.logger = Logger.new("tmp/activerecord.log")
|
8
|
+
|
9
|
+
# Connect to the test database
|
10
|
+
ActiveRecord::Base.establish_connection(:database => "searcher.sqlite3", :adapter => "sqlite3")
|
11
|
+
|
12
|
+
# Load the schema into the test database
|
13
|
+
load here + '/fixtures/schema.rb'
|
14
|
+
|
15
|
+
# Load the models
|
16
|
+
require here + '/fixtures/models'
|
17
|
+
|
18
|
+
# Call the after_initialize hook defined in lib/searcher.rb
|
19
|
+
ActiveSupport.run_load_hooks(:after_initialize)
|
data/tmp/.gitkeep
ADDED
File without changes
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: searcher
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors: []
|
13
|
+
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-04 00:00:00 +11:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: bundler
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 23
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 0
|
33
|
+
- 0
|
34
|
+
version: 1.0.0
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: activerecord
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 7
|
46
|
+
segments:
|
47
|
+
- 3
|
48
|
+
- 0
|
49
|
+
- 0
|
50
|
+
version: 3.0.0
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
description: Label-based straight-SQL searcher
|
54
|
+
email: []
|
55
|
+
|
56
|
+
executables: []
|
57
|
+
|
58
|
+
extensions: []
|
59
|
+
|
60
|
+
extra_rdoc_files: []
|
61
|
+
|
62
|
+
files:
|
63
|
+
- .gitignore
|
64
|
+
- Gemfile
|
65
|
+
- Gemfile.lock
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- lib/searcher.rb
|
69
|
+
- lib/searcher/class_methods.rb
|
70
|
+
- lib/searcher/config.rb
|
71
|
+
- lib/searcher/version.rb
|
72
|
+
- searcher.gemspec
|
73
|
+
- searcher.sqlite3
|
74
|
+
- spec/fixtures/models.rb
|
75
|
+
- spec/fixtures/schema.rb
|
76
|
+
- spec/searcher_spec.rb
|
77
|
+
- spec/spec_helper.rb
|
78
|
+
- tmp/.gitkeep
|
79
|
+
has_rdoc: true
|
80
|
+
homepage: http://rubygems.org/gems/searcher
|
81
|
+
licenses: []
|
82
|
+
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
|
86
|
+
require_paths:
|
87
|
+
- lib
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
hash: 3
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
version: "0"
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
hash: 23
|
103
|
+
segments:
|
104
|
+
- 1
|
105
|
+
- 3
|
106
|
+
- 6
|
107
|
+
version: 1.3.6
|
108
|
+
requirements: []
|
109
|
+
|
110
|
+
rubyforge_project: searcher
|
111
|
+
rubygems_version: 1.3.7
|
112
|
+
signing_key:
|
113
|
+
specification_version: 3
|
114
|
+
summary: Label-based straight-SQL searcher
|
115
|
+
test_files: []
|
116
|
+
|